Авторизация на сессиях и куках, PHP+MySQL v.2

Сентябрь 19th, 2013 Рубрики: PHP coubertin.cz www.colvillewoodworking.com

Авторизация

Что же, давно просили более расширенный вариант авторизации. Данная авторизация использует классы. Опять оговорюсь что это примерная реализация а не оптимальная. Можно улучшить защиту, добавить разделение прав и много чего еще. Я уже не говорю о таких банальных вещах как рефакторинг. Тем не менее она вполне сносно написана во время очередного night programming.

Пожалуй стоит начать с описании логики.
Как известно время хранения сессии 30 минут. Железно увеличивать ее в настройках сервера не стоит, это повысит нагрузку на сервер и скорее всего приведет к потери производительности. Так же мы можем назначить свою папку для хранения сессий, но тогда нам нужно писать инструмент для ее очистки — иначе мы будем хранить все сессии которые когда либо существовали, а нам этого не нужно. Это опять таки увеличит нагрузку, снизит время отклика, а если вы используете маленький хостинг план то у вас вообще место может закончится, при условии что очистка этой папки была написана с ошибками.

Оптимальный вариант это комбинировать сессии с куками. В первую очередь при обращении к странице проверяется существование сессии. Если сессии нету то мы проверяем наличие куки.

Соответственно если кук нету мы отправляем человека на форму авторизации. Если же они есть мы сравниваем их с записью которая есть в нашей базе. Тут уже много вариантов для увеличения безопасности. Будь то случайно сгенерированный код, user agent, ip пользователя и черт знает еще что. Оговорюсь что проверку на ip в данное время делать не стоит. Дело в том что сейчас очень распространены мобильные устройства, мощные смартфоны и планшеты… Все чаще появляется мобильный трафик.
При чем тут это? Все просто. При использовании мобильного интернета вы не имеете белого ip (я уже не говорю о статическом ip) и каждый раз когда вы заново подключаетесь к сети меняется сервер через который идет ваш трафик (соответственно и ip) да и ваш ip тоже меняется.

Думаю что пора приступать к коду.
Для начала создадим две таблички в MySQL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE TABLE IF NOT EXISTS `session` (
  `id_user` INT(5) NOT NULL,
  `code_sess` VARCHAR(15) NOT NULL,
  `user_agent_sess` VARCHAR(255) NOT NULL,
  PRIMARY KEY (`id_user`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `users` (
  `id_user` INT(5) NOT NULL AUTO_INCREMENT,
  `login_user` VARCHAR(60) NOT NULL,
  `passwd_user` VARCHAR(255) NOT NULL,
  `mail_user` VARCHAR(255) NOT NULL,
  PRIMARY KEY (`id_user`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

Из их названий понятно что первая содержит текущие сессии а вторая данные о пользователях.
Далее естественно conf.php — наш файл конфигурации.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
//~ Старт сессии, файл должен быть сохранен без DOM информации
session_start();

include_once 'module.php';

//~ Параметры подключения к бд
$db_host = 'localhost';
$db_login = ''; //~ логин для подключения
$db_passwd = ''; //~ пароль для подключения
$db_name = ''; //~ Имя таблицы

// подключаемся к бд
$db = new mysql(); //~ Создаем новый объект класса
$db -> connect($db_host, $db_login, $db_passwd, $db_name);
?>

Тут даже комментировать нечего. Прописываем данные для MySQL и выполняем подключение.

Далее наш index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?php
include_once 'conf.php';
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
  <title></title>
</head>
<body>
<?php
$r='';

$auth = new auth(); //~ Создаем новый объект класса

//~ Авторизация
if (isset($_POST['send'])) {
  if (!$auth->authorization()) {
    $error = $_SESSION['error'];
    unset ($_SESSION['error']);
  }
}

//~ выход
if (isset($_GET['exit'])) $auth->exit_user();

//~ Проверка авторизации
if ($auth->check()) $r.='Добро пожаловать '.$_SESSION['login_user'].'<br/><a href="?exit">Выйти</a>';
else {
  //~ если есть ошибки выводим их и предлагаем восстановить пароль
  if (isset($error)) $r.=$error.'<a href="recovery.php">Восстановить пароль</a><br/>';

  $r.='
  <a href="join.php">Зарегистрироваться</a>
  <form action="" method="post">
    login <input type="text" name="login" value="'
.@$_POST['login'].'" /><br />
    passwd <input type="password" name="passwd" id="" /><br />
    <input type="submit" value="send" name="send" />
  </form>
  '
;
}
  print $r;
?>
</body>
</html>

Что бы все было просто понять я не стал разделять html и php. Тут тоже все просто, вся магия будет потом ^_^.
Первое что мы делаем это подключаем наш конфиг (соответственно уже тогда у нас выполняется подключение к MySQL и старт сессий). Далее мы создаем объект класса auth и проверяем существует ли попытка авторизоваться.
Чуть ниже мы прописываем выход который принимаем через $_GET.

За проверку авторизации у нас служит метод $auth->check(), он возвращает true или false соответственно. Т.е. мы проверяем авторизации и далее уже выводим «добро пожаловать» или форму регистрации.

Переходим к файлу join.php который отвечает за регистрацию пользователей.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php
include_once 'conf.php';
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
  <title></title>
</head>
<body>
<?php
$reg = new auth();  //~ Создаем новый объект класса
$form = '
  <a href="join.php">Авторизоваться</a><br />
  <form action="" method="post">
    логин <input type="text" name="login" id="" value="'
.@$_POST['login'].'" /><br />
    пароль <input type="password" name="passwd" id="" /><br />
    повторите пароль <input type="password" name="passwd2" id="" /><br />
    Почта <input type="text" name="mail" value="'
.@$_POST['mail'].'" /><br />
    <input type="submit" value="send" name="send" />
  </form>
  '
;
if (isset($_POST['send'])) {
  if ($reg->reg($_POST['login'], $_POST['passwd'], $_POST['passwd2'], $_POST['mail'])) {
    print '
      <h2>Регистрация успешна.</h2>
      Вы можете войти <a href="index.php">авторизоваться</a>.
    '
;
  } else print $form;
} else print $form;

?>
</body>
</html>

Тут все аналогично тому что было раньше. Проверяем наличие отправки формы, проверяем ответ метода $auth->reg() и предлагаем перейти к авторизации.

Далее файл recovery.php который отвечает за восстановление пароля

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php
include_once 'conf.php';
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
  <title></title>
</head>
<body>
<?php
$reg = new auth();  //~ Создаем новый объект класса
$r='';
$form='
  <form action="" method="post">
    логин <input type="text" name="login" id="" value="'
.@$_POST['login'].'" /><br />
    Почта <input type="text" name="mail" value="" /><br />
    <input type="submit" value="send" name="send" />
  </form>
'
;

if (isset($_POST['send'])) {
  //~ запрос на восстановление пароля
  $reply = $reg->recovery_pass($_POST['login'], $_POST['mail']);
  if ($reply=='good') {
    //~ положительный ответ
    $r.='Новый пароль был выслан вам на почту';
  } else {
    //~ ошибка во время восстановления
    $r.=$reply.$form;
  }
} else $r.=$form;
print $r;

?>
</body>
</html>

Тут даже объяснять нечего.

А теперь перейдем к самому большому файлу в котором происходит вся магия — module.php.
Т.к. в нем чуть больше 200 строк то я его приведу кусками поясняя основные моменты.
Начнем с класса mysql

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class mysql {
  ###
  # Подключение к бд
  function connect($db_host, $db_login, $db_passwd, $db_name) {
    mysql_connect($db_host, $db_login, $db_passwd) or die ("MySQL Error: " . mysql_error()); //~ устанавливаем подключение с бд
    mysql_query("set names utf8") or die ("<br>Invalid query: " . mysql_error()); //~ указываем что передаем данные в utf8
    mysql_select_db($db_name) or die ("<br>Invalid query: " . mysql_error()); //~ выбираем базу данных
  }

  ###
  # Запрос к базе и его производные
  function query($query, $type, $num) {
    if ($q=mysql_query($query)) {
      switch ($type) {
        case 'num_row' : return mysql_num_rows($q); break;
        case 'result' : return mysql_result($q, $num); break;
        case 'accos' : return mysql_fetch_assoc($q); break;
        case 'none' : return $q;
        default: return $q;
      }
    } else {
      print 'Mysql error: '.mysql_error();
      return false;
    }
    //~ !!! DANGER !!!
    //~ при переносе в паблик убрать print 'Mysql error: '.mysql_error();
    //~ эта строчка стоит только для отладки и используя ее в паблике можно засветить запросы
  }

  ###
  # экранирование данных
  function screening($data) {
    $data = trim($data); //~ удаление пробелов из начала и конца строки
    return mysql_real_escape_string($data); //~ экранирование символов
  }
}

Этот небольшой класс был написан что бы облегчить работу с MySQL не используя при этом PDO и MySQLi (о них мы поговорим позже).
Он содержит всего три метода, подключение, запросы и экранирование. Метод mysql->query использует три параметра, сам запрос, его тип и количество.
Для простоты можно сделать их необязательными, например так:

1
function query($query, $type=null, $num=null)

Тогда по умолчанию будет возвращаться дескриптор результата запроса (resource, подробно на php.net).

Далее опишу методы класса mysql

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  ###
  # Проверка входных данных при регистрации
  function check_new_user($login, $passwd, $passwd2, $mail) {
    //~ Проверка валидности данных
    if (empty($login) or empty($passwd) or empty($passwd2)) $error[]='Все поля обязательны для заполнения';
    if ($passwd != $passwd2) $error[]='Введенные пароли не совпадают';
    if (strlen($login)<3 or strlen($login)>30) $error[]='Длинна логина должна быть от 3 до 30 символов';
    if (strlen($passwd)<3 or strlen($passwd)>30) $error[]='Длинна пароля должна быть от 3 до 30 символов';
    //~ Валидация почты не используя регулярки http://www.php.net/manual/en/filter.examples.validation.php
    if (!filter_var($mail, FILTER_VALIDATE_EMAIL)) $error[]='Не корректный email';
    //~ Проверяем наличее пользователя с таким именем в бд
    $db = new mysql(); //~ Создаем новый объект класса
    $login = $db->screening($login);
    if ($db->query("SELECT * FROM users WHERE login_user='".$login."';", 'num_row', '')!=0) $error[]='Пользователь с таким именем уже существует';
    if ($db->query("SELECT * FROM users WHERE mail_user='".$mail."';", 'num_row', '')!=0) $error[]='Пользователь с таким email уже существует';

    //~ Возвращаем массив ошибок или положительный ответ
    if (isset($error)) return $error;
    else return 'good';
  }

Комментарий в начале и название говорят сами за себя. Здесь мы просто проверяем данные формируя массив ошибок.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  ###
  # Регистрация
  function reg($login, $passwd, $passwd2, $mail) {
    if (($this->check_new_user($login, $passwd, $passwd2, $mail))=='good') {
      $db = new mysql(); //~ Создаем новый объект класса
      $passwd = md5($db->screening($passwd).'lol'); //~ хеш пароля с солью
      $login = $db->screening($login);
      if ($db->query("INSERT INTO `users` (`id_user`, `login_user`, `passwd_user`, `mail_user`) VALUES (NULL, '".$login."', '".$passwd."', '".$mail."');", '', '')) return true;
      else {
        print 'Возникла ошибка при регистрации нового пользователя. Свяжитесь с администрацией';
        return false;
      }
    } else {
      print $this->error_print($this->check_new_user($login, $passwd, $passwd2, $mail));
      return false;
    }
  }

Сам метод регистрации. Проверяем данные на выходе предыдущим методом, делаем хеш пароля с солью и регистрируем пользователя. Кстати вот очевидный пример где нужно сделать рефакторинг — два вызова auth->check_new_user() когда можно обойтись одним.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  ###
  # Проверка авторизации
  function check() {
    if (isset($_SESSION['id_user']) and isset($_SESSION['login_user'])) return true;
    else {
      //~ проверяем наличие кук
      if (isset($_COOKIE['id_user']) and isset($_COOKIE['code_user'])) {
        //~ куки есть - сверяем с таблицей сессий
        $db = new mysql(); //~ создаем новый объект класса
        $id_user=$db->screening($_COOKIE['id_user']);
        $code_user=$db->screening($_COOKIE['code_user']);
        if ($db->query("SELECT * FROM `session` WHERE `id_user`=".$id_user.";", 'num_row', '')==1) {
          //~ Есть запись в таблице сессий, сверяем данные
          $data = $db->query("SELECT * FROM `session` WHERE `id_user`=".$id_user.";", 'accos', '');
          if ($data['code_sess']==$code_user and $data['user_agent_sess']==$_SERVER['HTTP_USER_AGENT']) {
            //~ Данные верны, стартуем сессию
            $_SESSION['id_user']=$id_user;
            $_SESSION['login_user']=$db->query("SELECT login_user FROM `users` WHERE  `id_user` = '".$id_user."';", 'result', 0);
            //~ обновляем куки
            setcookie("id_user", $_SESSION['id_user'], time()+3600*24*14);
            setcookie("code_user", $code_user, time()+3600*24*14);
            return true;
          } else return false; //~ данные в таблице сессий не совпадают с куками
        } else return false; //~ в таблице сессий не найден такой пользователь
      } else return false;
    }
  }

Метод отвечающий за проверку авторизации. Для начала проверяем наличие сессии. Если сессии отсутствуют то мы проверяем куки. Экранируем их содержимое и сверяем их с таблицей сессий. Если все данные совпадают то мы стартуем сессию. Обновляем куки.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  ###
  # Авторизация
  function authorization() {
    $db = new mysql(); //~ создаем новый объект класса
    $login = $db->screening($_POST['login']);
    $passwd = md5($db->screening($_POST['passwd']).'lol'); //~ хеш пароля с солью
    if ($db->query("SELECT * FROM `users` WHERE  `login_user` =  '".$login."' AND  `passwd_user` = '".$passwd."';", 'num_row', '')==1) {
      //~ пользователь найден в бд, логин совпадает с паролем
      $_SESSION['id_user']=$db->query("SELECT * FROM `users` WHERE  `login_user` =  '".$login."' AND  `passwd_user` = '".$passwd."';", 'result', 0);
      $_SESSION['login_user']=$login;
      //~ добавляем/обновляем запись в таблице сессий и ставим куку
      $r_code = $this->generateCode(15);
      if ($db->query("SELECT * FROM `session` WHERE `id_user`=".$_SESSION['id_user'].";", 'num_row', '')==1) {
        //~ запись уже есть - обновляем
        $db->query("UPDATE `session` SET `code_sess` = '".$r_code."', `user_agent_sess` = '".$_SERVER['HTTP_USER_AGENT']."' WHERE `id_user` = ".$_SESSION['id_user'].";", '', '');
      } else {
        //~ записи нету - добавляем
        $db->query("INSERT INTO `session` (`id_user`, `code_sess`, `user_agent_sess`) VALUES ('".$_SESSION['id_user']."', '".$r_code."', '".$_SERVER['HTTP_USER_AGENT']."');", '', '');
      }
      //~ ставим куки на 2 недели
      setcookie("id_user", $_SESSION['id_user'], time()+3600*24*14);
      setcookie("code_user", $r_code, time()+3600*24*14);
      return true;
    } else {
      //~ пользователь не найден в бд, или пароль не соответствует введенному
      if ($db->query("SELECT * FROM  `users` WHERE  `login_user` =  '".$login."';", 'num_row', 0)==1) $error[]='Введен не верный пароль';
      else $error[]='Такой пользователь не существует';
      $_SESSION['error'] = $this->error_print($error);
      return false;
    }
  }

Данный метод отвечает за авторизацию. Мы экранируем введенные данные и проверяем наличие такого пользователя в базе. При успешном поиске мы запускаем сессию, обновляем данные в таблице сессий, обновляем куки. Иначе сообщаем об полученной ошибке.

1
2
3
4
5
6
7
8
9
  ###
  # Выход
  function exit_user() {
    //~ разрушаем сессию, удаляем куки и отправляем на главную
    session_destroy();
    setcookie("id_user", '', time()-3600);
    setcookie("code_user", '', time()-3600);
    header("Location: index.php");
  }

Тут все просто, удаляем сессию, удаляем куки и перенаправляем на страницу авторизации.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
  ###
  # Восстановление пароля
  function recovery_pass($login, $mail) {
    $db = new mysql(); //~ создаем новый объект класса
    $login = $db->screening($login);
    $db_inf = $db->query("SELECT * FROM `users` WHERE `login_user`='".$login."';", 'accos', '');
    if ($db->query("SELECT * FROM `users` WHERE `login_user`='".$login."';", 'num_row', '')!=1) {
      //~ не найден такой пользователь
      $error[]='Пользователь с таким именем не найден';
      return $this->error_print($error);
    } else {
      //~ проверка email
      if (!filter_var($mail, FILTER_VALIDATE_EMAIL)) $error[]='Введен не корректный email';
      if ($mail != $db_inf['mail_user']) $error[]='Введенный email не соответствует введенному при регистрации ';
      if (!isset($error)) {
        //~ восстанавливаем пароль
        $new_passwd = $this->generateCode(8);
        $new_passwd_sql = md5($new_passwd.'lol');
        $message = "Вы запросили восстановление пароля на сайте %sitename% для учетной записи ".$db_inf['login_user']." \nВаш новый пароль: ".$new_passwd."\n\n С уважением администрация сайта %sitename%.";
        if (mail($mail, "Восстановление пароля", $message, "From: webmaster@sitename.ru\r\n"."Reply-To: webmaster@sitename.ru\r\n"."X-Mailer: PHP/" . phpversion())) {
          //~ почта отправлена, обновляем пароль в базе
          $db->query("UPDATE `users` SET `passwd_user`='".$new_passwd_sql."' WHERE `id_user` = ".$db_inf['id_user'].";", '', '');
          //~ все успешно - возвращаем положительный ответ
        return 'good';
        } else {
          //~ ошибка при отправке письма
          $error[]='В данный момент восстановление пароля не возможно, свяжитесь с администрацией сайта';
          return $this->error_print($error);
        }
      } else return $this->error_print($error);
    }
  }

Метод для восстановления пароля. Экранируем введенный логин и ищем такого пользователя в базе данных. Если не найден — сообщение об ошибке. Если найден то проверяем введенный email (он должен соответствовать указанному при регистрации). Генерируем новый пароль отправляем его на почту и заносим в базу.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  ###
  # Функция генерации случайной строки
  function generateCode($length) {
    $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPRQSTUVWXYZ0123456789";
    $code = "";
    $clen = strlen($chars) - 1;  
    while (strlen($code) < $length) {
      $code .= $chars[mt_rand(0,$clen)];  
    }
    return $code;
  }


  ###
  # Формирование списка ошибок
  function error_print($error) {
    $r='<h2>Произошли следующие ошибки:</h2>'."\n".'<ul>';
    foreach($error as $key=>$value) {
      $r.='<li>'.$value.'</li>';
    }
    return $r.'</ul>';
  }

Два остававшихся метода. Первый генерирует случайную строку указанной длинны а второй делает из массив html список, для более наглядного вывода ошибок.

Вот впрочем и все. Собственно как я уже говорил здесь еще много чего можно улучшить, добавить. Некоторые вещи лучше сделать вообще по другому. Но для примера более сложной авторизации чем предыдущие — более чем сойдет.

Код можно взять из моего репозитория на github.

Теги: , , , ,

57 комментариев к “Авторизация на сессиях и куках, PHP+MySQL v.2”

  1. Иван
    Февраль 28th, 2014 at 16:55
    1

    Здравствуйте. Помогите найти решения, после установки скрипта вместо текста c восстановлением пароля, на почту приходят иероглифы, как поменять на нормальный текст? Везде стоит UTF 8. Во всех браузерах одно и тоже.

    [Ответить]

    ZMan Reply:

    Добрый день, попробуйте «поиграть» с кодировкой в шапке письма

    [Ответить]

  2. Roman
    Март 5th, 2014 at 08:50
    2

    Добрый день!

    1. тоже была ошибка о не найденном классе «auth»
    решил путем того что прописал: «class auth {» после 36 строки в файл module.php, а в конец файла добавил закрывающую фигурную скобку «}»

    2. у меня появился другой вопрос, собственно как авторизация должна и работать по идее.

    хотелось бы конечно реализовать через модальное окно, но это оставлю на будущее.

    просто скажем есть страница, назовем её index2.php, и необходимо чтобы к ней имели имели доступ только зарегистрированные пользователи.
    и скажем страница index3.php к ней могут иметь доступ пользователи без регистрации.

    чтобы ограничить доступ мне необходимо инклудить index.php или join.php из этой темы?

    3. Можете подкинуть идей как грамотнее реализовать группы пользователей и разграничение их прав? Группы пользователей предполагается хранить в mysql и запрашивать из скрипта кто к какой принадлежит.

    [Ответить]

    ZMan Reply:

    Добрый день, по вашим вопросам:
    1-2) в конце статьи есть ссылка на GitHub с исходниками. Скачайте их и посмотрите как сделано. Там тот же код но не разбитый на части. Возможно вы что то упустили из вида.
    3) уже не один раз писал в комментариях:

    1
    2
    3
    4
    5
    if (!$auth->check()) {
    //~ доступно для гостей
    } else {
    //~ не доступно для гостей
    }

    и страница только для авторизированных пользователей (редирект в случае провала проверки авторизации)

    1
    2
    3
    4
    if (!$auth->check()) {
    //~ совершаем процедуру выхода
    $auth->exit_user();
    }

    [Ответить]

  3. Roman
    Март 6th, 2014 at 10:30
    3

    Спасибо за оперативные ответы в комментариях, ваша наглядная реализация более менее помогла понять как должно работать.

    думаю подтяну теорию и буду пробовать делать простенький рефакторинг.
    ссылку на github к сожалению упустил из вида, приму к сведению


    С уважением Роман

    [Ответить]

  4. Лола
    Апрель 4th, 2014 at 16:12
    4

    Ошибку исправила, в файле module.php, когда открыла Блокнотом, действительно, не было прописано название класса auth { А при открытии редактором NotePad++ название было и документ был сохраненным. В общем.. спасибо за «Авторизацию» ! 🙂

    [Ответить]

  5. Апрель 17th, 2014 at 17:21
    5
Страницы комментариев

Написать комментарий