PHP и MySQL. Часть 6

PHP и MySQL. Часть 6

Продолжение. Начало см. КГ №№ 11, 22, 24, 29, 38 .

Сценарий member.php завершает страницу отображением меню с использованием функции display_ user_menu(). Пример вывода сценария member.php можно увидеть на рис. 4. Следующим шагом будет рассмотрение функций check_valid_ user() и login(). Сама функция check_ valid_user() повторного подключения к базе данных не выполняет, однако проверяет, что с пользователем связан зарегистрированный сеанс.
Функция login() подключается к базе данных и проверяет в ней наличие комбинации имени и пароля для данного пользователя. Если эти записи присутствуют, возвращается значение true, в противном случае либо когда данные пользователя не могут быть проверены — false. Их исходный код изложен ниже.

function login($username, $password)
{ $conn = db_connect();
if (!$conn)
return 0;
$result = mysql_query("select * from user
where username='$username'
and passwd = password('$password')");
if (!$result)
return 0;
if (mysql_num_rows($result> 0)
return 1;
else return 0;}

function check_valid_user()
{ global $valid_user;
if (session_is_registered("valid_ user"))
{echo "Logged in as $valid_user.";
echo "<br> ";}
else
{
do_html_heading("Ошибка:");
echo "Вы не вошли в систему.<br> ";
do_html_url("login.php", "Login");
do_html_footer();
exit;
}
}

Как видно из рисунка 4, меню предоставляет пользователю пункт Logout. Щелчок на этой ссылке приводит к вызову сценария logout.php. Код сценария подобен тому, который приводился в прошлой статье, и здесь мы его упоминать не будем. Он практически в точности повторяет сценарий выхода из системы прошлого примера.
Кроме выхода из системы, пользователь может захотеть изменить свой пароль. Форма, предназначенная для смены пароля пользователя, генерируется сценарием change_passwd_ form.php. Он также довольно прост и использует функции вывода. Затем после отправки формы запускается сценарий change_passwd.php. Он работает по уже указанной выше схеме: подключаем файл функций, затем начинаем сессию, проверяем, связан ли с пользователем сеанс (помните функцию check_valid_user()?), проверяем заполнение полей в форме по массиву $HTTP_POST_VARS. Если данные, введенные пользователем, правильны, то есть новый пароль и его копия совпадают, и старый пароль указан правильно, предпринимается попытка изменения пароля.
После всех этих проверок вызывается функция change_password(), которая с помощью функции login() проверяет правильность ввода прежнего пароля. Если пароль введен верно, функция соединяется с базой и обновляет пароль новым значением.

function change_password ($username, $old_password, $new_password)
{
if (login($username, $old_password))
{
if (!($conn = db_connect())) return false;
$result = mysql_query("update user set passwd = password( '$new_password' ) where username = '$username'");
if (!$result) return false;
else return true;
}
else return false;
}
Но, помимо смены пароля, необходимо предусмотреть часто возникающую ситуацию, когда пользователь попросту забывает пароль. Обратите внимание, что на титульной странице (рис. 1 из прошлой части статьи) содержится ссылка Forgot your password?, которая ведет к сценарию forgort_form.php. Так как этот сценарий ничего, кроме вывода стандартного HTML-кода и кода формы, не делает, его описание опускаем. Вообще следует отметить, что все сценарии, выводящие HTML-формы, подобны, как близнецы, изменения касаются только имени функции, ответственной за вывод самой формы. Остается добавить только, что при отправке формы запускается сценарий forgot_passwd.php, который стоит детально рассмотреть.

<?
require_once("bookmark_fns.php");
do_html_header("Resetting password");
if ($password=reset_password($username))
{
if (notify_password($username, $password))
echo "Новый пароль был выслал на e-mail.";
else
echo "Не могу выслать пароль."
}
else
echo "Не могу изменить пароль";
do_html_url("login.php", "Login");
do_html_footer();
?>
Основу сценария составляют две функции: reset_password() и notify_ password(). Первая генерирует случайный пароль и помещает его в базу данных:

function reset_password($username)
// установить случайное значение для пароля
// возвращается новый пароль либо false, если ошибка
{
// получение случайного слова длиной от 6 до 13 символов
$new_password = get_random_word(6, 13);
// добавление числа от 0 до 999
// для некоторого усложнения пароля
srand ((double) microtime() * 1000000);
$rand_number = rand(0, 999);
$new_password .= $rand_number;
// изменение пароля в базе данных или возврат значения false
if (!($conn = db_connect())) return false;
$result = mysql_query( "update user set passwd = password( '$new_password' ) where username = '$username'");
if (!$result)
return false; // пароль не изменен
else
return $new_password; // пароль изменен успешно
}
Эта функция генерирует случайный пароль, получив случайное слово из словаря и добавив к нему случайное число от 0 до 999. Получение случайного слова обеспечивается функцией get_random_word():

function get_random_word($min_ length, $max_length)
// поиск случайного слова из словаря с заданной длиной
{
// генерация случайного слова
$word = "";
$dictionary = "/usr/dict/words"; // словарь ispell
$fp = fopen($dictionary, "r");
$size = filesize($dictionary);
// переходим на случайное слово словаря
srand ((double) microtime() * 1000000);
$rand_location = rand(0, $size);
fseek($fp, $rand_location);
// получение следующего целого слова допустимой длины
while (strlen($word)< $min_length || strlen($word> $max_length)
{
if (feof($fp))
fseek($fp, 0);//если конец, то перейти на начало
$word=fgets($fp,80);//пропускаем первое слово — вдруг неполное?
$word = fgets($fp, 80); // потенциальный пароль
};
$word=trim($word); // усекаем \n из функции fgets
return $word;
}

Такая функция будет работать только под Unix, так как в примере используется стандартный словарь программы орфографии ispell, который обычно находится по пути usr/dict/words. Если используется другая операционная система или нет желания использовать словарь, можно загрузить список слов для ispell по следующему адресу: http://ficus-www.cs.ucla.edu/ficus-members/geoff/ispell-dictionaries.html . На этом сайте содержатся словари на многих языках, поэтому можно генерировать пароли даже на эсперанто. Впрочем, данную процедуру можно заменить и другой, более для вас подходящей и имеющей другой алгоритм генерации случайных слов.

Чтобы получить случайное слово из файла, выбирается случайная позиция в диапазоне от 0 до значения размера файла, из которого выполняется чтение. В таком случае, вероятнее всего, будет прочитана часть слова, поэтому текущая строка пропускается и выбирается следующее слово путем двукратного обращения к функции fgets(). Две интересных особенности этой функции: если в процессе поиска достигается конец файла, выполняется переход на его начало, и второе — выполняется поиск слова определенной длины. Если она не находится в необходимом диапазоне, поиск продолжается.
Но вернемся к функции reset_password(). После того, как сгенерируется новый пароль, происходит обновление базы данных и возврат нового пароля в главный сценарий. Затем он передается в функцию notify_password(), которая и отправит его пользователю по электронной почте:

function notify_password($username, $password) // уведомление пользователя об изменении пароля
{
if (!($conn = db_connect())) return false;
$result = mysql_query("select email from user where username = '$username'");
if (!$result)
return false; // пароль не изменен
else if (mysql_num_rows($result)==0)
return false; // пользователя в базе нет
else
{
$email=mysql_result($result, 0, "email");
$from="From: support@phpbookmark \r\n";
$mesg="Ваш пароль изменен на $password \r\n"."Измените его, когда в следующий раз зайдете на сайт. \r\n";
if (mail($email,"PHPBookmark-изменение пароля", $mesg, $from))
return true;
else
return false;
}}

Функция по имени пользователя и новому паролю выполняет поиск адреса электронной почты в базе данных и применяет РНР-функцию mail() для отправки пароля пользователю. Безопаснее будет предоставить пользователю действительно случайный пароль, составленный из комбинации букв верхнего и нижнего регистров, чисел и знаков пунктуации вместо случайного слова и числа. Такой пароль, состоящий из слова и числа, проще запоминается пользователем, чем случайная последовательность букв и цифр. К тому же, в случайной строке часто бывает затруднительно определить, где 0 (нуль), а где О (буква "О"). Файл словаря может содержать около 45.000 слов. То есть, даже если взломщик знает способ формирования пароля, ему придется угадывать один вариант примерно из 22.500.000. По-моему, такой уровень защищенности достаточен для приложений подобного уровня.

Теперь без внимания у нас осталась только часть приложения, отвечающая за хранение и извлечение закладок. Для добавления закладок следует щелкнуть на ссылке Add BM в меню пользователя. В результате отображается форма, показанная на рис. 5, сценарий которой использует только функции нашего API, а в результате вызывается сценарий add_bms.php:

<?
require_once("bookmark_fns.php");
session_start();
do_html_header("Добавляем ссылку");
check_valid_user();
if (!filled_out($HTTP_POST_VARS))
{
echo "Форма не заполнена. Попробуйте еще раз.";
display_user_menu();
do_html_footer();
exit;
}
else
{
if (strstr($new_url, "http://")=== false) // проверяем URL
$new_url = "http://".$new_url;
if (@fopen($new_url, "r"))
{
if (add_bm($new_url)) // пытаемся добавить закладку
echo "Закладка добавлена.";
else echo "Не могу добавить закладку.";
}
else echo "Неверная ссылка.";
}
// получение закладок пользователя
if ($url_array = get_user_urls($va-lid_user));
display_user_urls($url_array);
display_user_menu();
do_html_footer();
?>
В этом сценарии, кроме всего прочего, проводится проверка допустимости данных, запись в базу данных и вывод информации. Для проверки допустимости данных сначала с помощью функции filled_out() определяется, заполнил ли пользователь форму. Затем выполняются две проверки URL-адреса. Вначале определяется, начинается ли адрес с последовательности htttp://. Если нет, она добавляется в начало адреса. После этого идет проверка, существует ли такой адрес в действительности. Функция fopen() позволяет открыть URL-адрес, начинающийся с префикса http://. Если открыть файл удается, предполагается, что URL-адрес допустим, и тогда вызываем функцию add_bm() для включения данных в базу.
Но функция fopen() способна открывать такие файлы лишь в случае, если сервер имеет прямой доступ в Internet. Если сервер обращается к другим серверам через прокси-сервер, функция fopen() работать не будет.

Функция add_bm() так же проста, как и все остальные. Она очень похожа на функцию по добавлению пользователя в систему. Эта функция проверяет, что данная закладка еще не содержится в базе данных. Маловероятно, что закладка будет вводиться два и более раз, но вполне возможно, что пользователь просто обновляет страницу. Если закладка новая, то она вводится в базу данных.
Снова вернемся к сценарию add_bm.php. Как и в сценарии member.php, вывод завершается функциями get_user_urls() и display_user_ urls(). Но описание этих функций уже больше относится к функциям отображения закладок.
Две эти функции осуществляют, соответственно, извлечение закладок из базы и их отображение. Функция get_user_urls() принимает имя пользователя в качестве параметра и извлекает для него закладки из базы данных. Функция возвращает массив URL-адресов либо значение false, если закладок нет:

function get_user_urls($username)
{
//извлечение из базы всех закладок для текущего пользователя
if (!($conn = db_connect()))
return false;
$result = mysql_query("select bm_URL from bookmark where username = '$username'");
if (!$result) return false;
//создание массива ссылок
$url_array = array();
for ($count = 1; $row = mysql_fetch_row ($result); ++$count)
{
$url_array[$count] = $row[0];
}
return $url_array;
};
Результирующий массив может передаваться из функции get_user_urls() в функцию display_user_urls(). Эта функция просто выводит HTML-содержимое, отвечающее за печать URL-адресов в привлекательном виде "полосатой" таблицы. Она также не будет рассматриваться: там все ясно и понятно.
Кроме всего прочего, пользователь может удалять закладки, и для этого предусмотрены флажки напротив каждого из адресов, представленных на странице. Когда пользователь помечает некоторые закладки для удаления и выбирает из меню опцию Delete BM, передается форма, содержащая URL-адреса. Каждый флажок генерируется с помощью следующего кода функции display_user_urls():

echo "<td> <input type=checkbox name=\"del_me[]\"
value=\"$url\"> </td> ";
Имя каждого флажка — delme[]. Это означает, что, если форма запускает РНР-сценарий, будет предоставлен доступ к массиву $del_me, который содержит все удаляемые закладки. Опция Delete BM запускает сценарий delete_bms.php, показанный ниже:

<?
require_once("bookmark_fns.php");
session_start();
do_html_header("Удаление закладок");
check_valid_user();
if (!filled_out($HTTP_POST_VARS))
{
echo "Не выбраны закладки для удаления. Попробуйте еще раз.";
display_user_menu();
do_html_footer();
exit;
}
else
{
if (count($del_me) > 0)
{
foreach($del_me as $url)
{
if (delete_bm($valid_user, $url))
echo "Удалено: ".htmlspecialchars ($url).".<br> ";
else echo "Не могу удалить ".htmlspecialchars($url).".<br> ";
} }
else
echo "Не выбрано ни одной закладки.";
}
// получение оставшихся закладок пользователя
if ($url_array = get_user_urls ($valid_user));
display_user_urls($url_array);
display_user_menu();
do_html_footer();
?>

Начинается сценарий с обычной проверки допустимости данных. Если выясняется, что пользователь выбрал несколько закладок для удаления, их удаление выполняется в следующем цикле:

foreach($del_me as $url)
{
if (delete_bm($valid_user, $url))
echo "Удалено: ".htmlspecialchars ($url).".<br> ";
else echo "Не могу удалить ".htmlspecialchars($url).".<br> ";
}
А функция delete_bm() осуществляет удаление закладки из базы данных:

function delete_bm($user, $url)
{
// удаляем одну закладку
if (!($conn = db_connect())) return false;
// собственно удаление закладки
if (!mysql_query( "delete from bookmark where username='$user' and bm_url='$url'"))
return false;
return true;
}

И в этой функции также нет ничего сложного. В ней просто предпринимается попытка удаления из базы данных закладки определенного пользователя. Понятно, что удалять необходимо пару "имя пользователя — закладка", так как для других пользователей эта закладка должна остаться. После внесения изменений в базу данных отображается новый список закладок с помощью ранее описанных функций get_user_urls() и display_user_urls().
Итак, что у нас получилось? Что-то работающее, но очень корявенькое. Эту систему еще можно дорабатывать, дорабатывать и дорабатывать достаточно долго. Скажем, ввести понятие "группа" для закладок, выработать механизм получения Top 10 самых частозаносимых закладок и возможность прямого занесения таких ссылок к себе в закладки, переписать интерфейсную часть программы для поддержки тем. Переработать основную часть с целью увеличения быстродействия, сделать автоматический выход пользователей из системы по истечении определенного срока. В общем, этот проект — всего лишь маленькая заготовка, "рыба", как часто называют их программисты. Надеюсь, читатель смог почерпнуть для себя что-то полезное из этих статей.

Продолжение следует.

Денис "Denver" Мигачев, dtm@tut.by



Компьютерная газета. Статья была опубликована в номере 39 за 2003 год в рубрике программирование :: разное

©1997-2024 Компьютерная газета