![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Главная | Учебник | Статьи | FAQ | Книги | Ссылки |
Глава 10. PHP: система оценки материалов
Сценарий выставления оценок, о котором пойдет речь в этой главе, вы наверняка неоднократно видели на сайтах Интернет-журналов и сетевых библиотек. Зачем он нужен, понятно - владельцам сайтов хочется знать, какие материалы из представленных наиболее нравятся посетителям. Последние внакладе не остаются: прочитав статью или книгу, всегда хочется поделиться эмоциями от ее прочтения, причем желательно в максимально легкой форме. Форма же выставления оценки как раз это сделать и позволит: все мероприятие сводится к выбору нужного пункта меню и нажатию кнопки. А в награду можно еще и сравнить свое мнение с усредненным взглядом других посетителей.
Предположим, что у нас есть некий материал, которому посетители должны выставлять оценки с помощью разрабатываемого сценария.
Что должен делать этот сценарий?
Вначале - принимать от посетителя значение оценки. Для этого не нужно никаких ухищрений, достаточно поместить на исходную страницу форму с элементами <select...>...<option> (отображающимися на странице как выпадающее меню), и в сценарии - обработчике этой формы выбранное пользователем значение будет доступно через переменную $_POST["имя_элемента_select"].
Примечание:
В первых версиях PHP все переменные, передаваемые через формы, были доступны в сценариях по их именам, указанным в параметрах name форм.
В PHP версий 4.0 и старше эти переменные также доступны в массиве $HTTP_POST_VARS['имя элемента'].
В PHP версий 4.1 и старше эти переменные также доступны в массиве $_POST['имя элемента'].
На доступность передаваемых переменных также влияют параметры в файле php.ini:
Дальше результат следует обработать. Если эта оценка - самая первая за всю историю существования статьи, то ее необходимо сохранить - проще всего записать в текстовый файл. А если ранее статья уже была оценена, то следует считать уже имеющееся значение из файла и посчитать его среднее с новодобавленным (нам ведь нужна именно средняя оценка, не так ли?), после чего записать высчитанное среднее в тот же самый текстовый файл.
Среднюю оценку можно посчитать по очевидной формуле
(средний балл * количество оценок) + новодобавленная оценка количество оценок + 1
Кроме того, среднюю оценку желательно показать и посетителю. Однако, так как при расчете среднего результат может быть выражен и довольно длинной десятичной дробью, то его следует округлить до сотых или десятых.
После выставления оценки целесообразно отправлять посетителя на специальную страницу с благодарностью о выполненном им действии и каталогом других статей или разделов сайта. Желательно, чтобы на этой странице отображались сведения о результате голосования других посетителей - вычисленный средний балл.
Желательна также и реализация "защиты от накруток" - чтобы один и тот же пользователь не мог голосовать несколько раз. Для этого можно использовать cookie: помечать с помощью cookie браузер посетителя при выставлении им оценки и впоследствии позволять выставлять оценку только при отсутствии такого cookie. Использование cookie - не очень надежная защита (ничего не мешает посетителю удалять cookie перед каждой новой попыткой выставления оценки или использовать разные браузеры, да и на общедоступных компьютерах проголосовать можно будет лишь единожды независимо от числа их пользователей до тех пор, пока cookie не будет удален), но для подавляющего большинства ситуаций ее вполне достаточно.
Комментарий:
Cookie - это файл в специальном формате, который присылается сервером браузеру посетителя сайта, расположенном на этом сервере. Браузер, если он поддерживает cookie (и эта поддержка в нем не отключена), помещает его в особое место и впоследствии отправляет назад на сервер при поступлении от него запроса. Иными словами, cookie позволяет серверу хранить свою информацию на компьютерах посетителей и считывать ее оттуда при необходимости. Подробнее о cookie и командах для работы с ним читайте в главе 8.
В первых версиях PHP все переменные, передаваемые через cookies, были доступны в сценариях по их именам, равным именам хранящих их cookie.
В PHP версий 4.0 и старше эти переменные также доступны в массиве $HTTP_COOKIE_VARS['имя cookie'].
В PHP версий 4.1 и старше эти переменные также доступны в массиве $_COOKIE['имя cookie'].
На доступность переменных в cookie также влияют параметры в файле php.ini:
Наверняка на сайте будет находиться немало статей, к которым стоит добавить форму выставления оценок. Для того, чтобы не вставлять в каждый файл множество строк одинакового кода, стоит выполнить весь код выставления оценки в отдельном модуле и включать его с помощью PHP-команды include в статьи, оценка которых посетителями необходима.
Учтя все вышеприведенные условия и пожелания, можно составить общий план системы оценивания:
Допустим, что первый файл будет называться niz.php, а второй - otziv.php.
" Вставка первого файла с формой для голосования будет производиться командой <?php include ("niz.php"); ?>.
" Количество оценок и средний балл будут записываться в файлы с именами, составленными на основе имени web-страницы, на которой располагалась форма ввода.
" Сценарий - обработчик формы голосования будет записывать число оценок и средний балл в эти файлы, выводить эти данные на web-страницу, а также помечать браузер посетителя cookie с информацией о состоявшемся голосовании.
" В файле с формой для голосования будет содержаться программа, в случае отсутствия пометки-cookie выводящая на экран форму, а в случае ее наличия - выводящая сведения о количестве оценок и среднем балле.
Разумеется, возможны и другие варианты - записывать средний балл и число оценок в один и тот же файл, не показывать посетителю средний балл и т.д., но стоит остановиться на чем-то одном.
В каждый файл со статьей, в то место, где должна располагаться форма для выбора оценки, следует вставить одну строчку кода:
<?php include ("niz.php"); ?>
Код этого файла выводит форму для выбора оценки, а в том случае, если посетитель уже выставлял свою оценку - сведения о среднем балле и количестве оценок.
<?php
Файлы со значениями среднего балла и количества оценок будут храниться в папке с именем cnt. Запишем для удобства ее имя в переменную:
$dirct="cnt";
А сами эти файлы будут иметь имена, составляемые на основе имени файла со статьей. Сначала "вытащим" это имя из полного имени файла:
$nom=substr(basename($PHP_SELF), 0, -4);
Примечание:
Функция substr (строка, начало выделения, количество выделяемых символов) выполняет выделение из строки ее части. Строка (или переменная, ее содержащая) должна быть указана в первом параметре команды. Второй параметр - позиция, с которой начинается выделяемая часть (вернее, число символов, которые необходимо пропустить до начала выделения части строки), а третий - количество выделяемых символов.
Второй параметр может быть отрицательным. В этом случае отсчет позиции начала выделяемой части будет идти не с начала, а с конца строки. Иными словами, в результате выполнения команды substr ("qwertyuiop", -3, 2) из строки "qwertyuiop" будет выделена строка io - она начинается за 3 символа от конца исходной строки и продолжается 2 символа.
Третий параметр тоже может быть отрицательным. В этом случае будет выделена строка, начинающаяся с указанной во втором параметре позиции и оканчивающаяся за столько символов до конца строки, сколько указано в третьем параметре. Иными словами, в результате выполнения команды substr ("qwertyuiop", 3, -2) из строки "qwertyuiop" будет выделена строка rtyui - она начинается после 3 символа исходной строки и заканчивается за 2 символа до ее окончания.
В том случае, если параметры установлены так, что выделить согласно им символы из строки оказывается невозможно (например, второй параметр больше, чем число ее символов), то результатом работы команды substr будет пустая строка - "".
Функция basename (полное имя файла, расширение файла) - выделяет имя файла из его полного имени, указанном в ее параметре, вместе с путем по дереву директорий.
$PHP_SELF - переменная, в которую интерпретатором PHP автоматически записывается значение полного пути (вместе с путем по дереву папок и с расширением) к текущему файлу. Ее можно в сценарии так же, как и другие переменные, однако изменять ее значение нельзя.
...а сами файлы назовем на основе этого имени, получив имя файла с количеством оценок прибавлением к нему окончания "kol", а имя файла со средним баллом - окончания "est":
$kolvooc="$nom"."kol"; $ocenka="$nom"."est";
Сценарий обработки оценки (о нем читайте ниже) будет устанавливать в браузер посетителя cookie с именем "(имя_статьи)haveest". Наличие такого cookie в браузере посетителя будет означать, что посетитель уже голосовал. Поэтому проверим его наличие в этом браузере (попросту посмотрев, есть ли какое-либо значение у соответствующей переменной):
if ($_COOKIE[$nom."haveest"]=="") { ?>
...и в том случае, если cookie отсутствует - выведем форму для голосования:
<form method="post" action="otziv.php">
Передадим в скрытом поле формы имя файла со статьей без расширения - для определения на основе него сценарием-обработчиком имен файлов со сведениями о количестве оценок и среднем балле, а также полное имя, вместе с путем, файла со статьей - для вывода ссылки "Назад" на странице со сценарием-обработчиком. Можно было бы передавать через форму только полное имя файла со статьей, а "чистое" имя файла статьи определять в сценарии-обработчике точно так же, как и в сценарии из niz.php - на основе функции basename, но для сокращения длины кода в обработчике воспользуемся передачей его через форму.
<input name="nom" type="hidden" value="<?php echo $nom; ?>"> <input type=hidden name=nazad value="<?php echo ($PHP_SELF); ?>">
Выведем форму ввода оценки:
Поставьте оценку статье: <SELECT NAME=ocen> <OPTION VALUE=5>5 (Отлично) <OPTION VALUE=4>4 (Хорошо) <OPTION VALUE=3>3 (Удовлетворительно) <OPTION VALUE=2>2 (Плохо) <OPTION VALUE=1>1 (Очень плохо) </SELECT>
В результате в сценарий-обработчик будут переданы элементы массивов $_POST ['ocen'], $HTTP_POST_VARS['ocen'] и переменная $ocen (при соответствующих версиях PHP и настройках в php.ini) со значениями, равными параметру value выбранного пользователем пункта выпадающего списка.
Выведем кнопку отправки формы
<input name="submit" type="submit" value="Послать оценку"></form>
И это все, что подлежит выводу, если голосования еще не было.
<? }
Если же голосование уже состоялось...
else {
То выведем сведения об уже выставленных оценках.
echo ("Оценок этой статье - ");
Если файл со сведениями о количестве оценок существует (он создается при первом оценивании)...
if (file_exists("$dirct/$kolvooc")==True) {
Примечание:
file_exists (имя файла)- команда, возвращающая True, если файл, имя которого (с относительным путем) указано в ее параметре, существует, и False, если это не так.
...то вставим его значение в документ.
include ("$dirct/$kolvooc");
Если же такового файла нет, то есть документ ни разу не оценивался...
} else { ...выведем значение "0". echo ("0"); }
Если файл со средним баллом существует...
if (file_exists("$dirct/$ocenka")==True) { ...то надо вывести его значение. echo (". Cредний балл - ");
Но просто включить содержимое файла на страницу нельзя - средний балл может быть и длинной десятичной дробью. Поэтому откроем файл для чтения командой fopen...
$hdl = fopen("$dirct/$ocenka", "r+");
Примечание:
fopen ("имя файла", "способ открытия") - команда открытия файла. Для того, чтобы из программы на PHP считать содержимое какого-либо файла или записать в него данные, этот файл нужно сначала открыть командой fopen. При этом открытому файлу присваивается некое "внутреннее имя" - так называемый дескриптор, и именно его возвращает функция fopen. Первый параметр fopen - имя файла (вместе с относительным или абсолютным путем к нему), второй - способ открытия файла.
В зависимости от второго параметра функции fopen файл может быть открыт по-разному - для чтения, для записи, с очисткой содержимого или без таковой. Возможные параметры fopen такие:
r - открыть файл только для чтения и приготовиться читать его с начала.
r+ - открыть файл для чтения и для записи и приготовиться работать с ним с его начала.
w - открыть файл только для записи, предварительно удалив из него все содержимое, причем если файл с указанным именем не существует, то создается новый файл с таким именем.
w+ - открыть файл как для записи, так и для возможного последующего чтения, предварительно удалив из него все содержимое, причем если файл с указанным именем не существует, то создается новый файл с таким именем.
a - открыть файл только для записи и приготовиться дописывать данные в его конец. Если файл с указанным именем не существует, то создается новый файл с таким именем.
a+ - открыть файл для записи и для чтения и приготовиться дописывать данные в его конец. Если файл с указанным именем не существует, то создается новый файл с таким именем.
Открываемый файл может располагаться и на удаленном сервере - в этом случае он будет доступен только для чтения независимо от параметров открытия файла. Путь к файлу в таком следует указывать полностью - начиная с http:// или ftp://.
...и считаем в переменную $sred все содержимое этого файла.
$sred = fread($hdl, filesize("$dirct/$ocenka"));
Примечание:
Функция fread (дескриптор файла, длина считываемого фрагмента) считывает из открытого файла, для которого получен указанный в ее первом параметре дескриптор, столько байт, сколько указано в ее втором параметре (чтение начинается с местонахождения так называемого указателя файла - отметки, показывающей текущее место работы с файлом; указатель двигается при чтении или записи в файл, а также при использовании команды fseek).
Функция filesize (полный путь к файлу) возвращает размер указанного в ее параметре файла в байтах.
Файл можно закрыть...
fclose($hdl);
...а переменную $sred - округлить до десятых
$sred=round ($sred, 1);
Примечание:
Функция round (число, количество разрядов) округляет дробное число в ее первом параметре до количества разрядов, указанного в ее втором параметре. Скажем, round (число, 1) округлит число до десятых, round (число, 2) - до сотых и т.д.
В PHP до четвертой версии функция round могла округлять числа только до целых, поэтому в том случае, если вы располагаете только такой версией PHP, то команда округления до десятых должна выглядеть как
$sred=(round ($sred*10))/10; ...и вывести на страницу. echo ("$sred."); }
На этом сценарий вывода формы ввода заканчивается.
}?>
Код в этом файле рассчитывает новый средний балл статьи на основе переданной через форму оценки посетителя и текущего среднего балла, записывает значения среднего балла и количества оценок в соответствующие файлы, а также помечает браузер посетителя cookie для недопущения повторного голосования того же самого посетителя.
Обратите внимание, что этот сценарий должен находиться в самом начале web-страницы - перед ним не должно быть даже пробела (так как в нем используется команда установки cookie - см.врезку).
<?php
Для начала проверим, нет ли случайно в браузере посетителя cookie с пометкой о голосовании, и весь дальнейший код будем выполнять только тогда, когда его нет:
if ($_COOKIE[$nom."haveest"]=="")
Может возникнуть вопрос: а зачем, собственно, проверять на этой странице, установлен ли cookie? Ведь форма для выставления оценки на предыдущей странице появится на ней только в том случае, если cookie отсутствует, а, не увидев формы, посетитель не сможет и отправить оценку.
Это так, но тот, кто хочет сделать "накрутку" статьи, проголосовав за нее множество раз, может сохранить на своем компьютере локальную копию статьи с формой для выставления оценки, и уже с нее осуществлять голосования. А на локальной копии страницы отображение формы от наличия или отсутствия cookie не зависит (там ведь нет PHP-сценария). Поэтому и имеет смысл осуществить проверку.
Итак, если посетитель еще не голосовал за данную статью...
{
...установим в его браузер cookie, говорящий, что такое голосование наконец совершилось. Время жизни cookie зададим в 30 дней - пожалуй, хватит.
SetCookie($nom."haveest","1",time()+2592000);
Запишем в переменные имена директории с файлами оценок и самих этих файлов, составив имя файла с числом оценок путем прибавления к имени страницы со статьей, которой выставлена оценка, окончания kol, а имя файла со средним баллом - путем прибавления окончания est:
$dirct="cnt"; $kolvooc="$nom"."kol"; $ocenka="$nom"."est";
Если файлы оценок не существуют (то есть выставляемая оценка - вообще первая по счету)...
if ((file_exists("$dirct/$kolvooc")!=True)||(file_exists("$dirct/$ocenka")!=True)) {
...то запишем в файл со сведениями о количестве оценивших число 1 (так ведь и есть, не правда ли?)...
$hdl = fopen("$dirct/$kolvooc", "a+"); fwrite($hdl, 1); fclose($hdl);
Примечание:
fwrite (дескриптор файла, записываемая в файл строка) - записывает указанную во втором параметре строку в файл, дескриптор которого указан в ее первом параметре. То место в файле, с которого совершается чтение данных и в которое осуществляется запись, называется указателем файла. (Если файл представить как тетрадь, то указатель - это открытая страница, вернее, номер открытой страницы.) При открытии файла командой fopen с параметрами r, r+, w или w+ указатель файла ставится на его начало, а при открытии с параметром a или а+ - в самый конец. При записи в файл командой fwrite в том случае, если указатель находится не в конце файла, записываемые данные пишутся поверх имеющихся. Если же файл был открыт командой fopen с параметром а или а+, то вне зависимости от позиции указателя запись в файл идет в его конец, то есть - после всех данных файла.
...а в файл со сведениями о среднем балле - выставленную посетителем оценку (она ведь и есть "среднее" от самой себя).
$hdl = fopen("$dirct/$ocenka", "a+");
Однако просто записывать содержимое переданной переменной в этот файл опасно - вдруг какой-нибудь хакер сохранит страницу с формой на жесткий диск, посмотрит ее HTML-код и вставит вместо значения оценки какой-нибудь деструктивный PHP-код. Тогда при вставке содержимого файла (то есть среднего балла) в web-страницу этот код выполнится, и последствия могут быть весьма неприятные. На предыдущей странице, впрочем, уже есть некая защита от такого действия (содержимое файла со средним баллом перед вставкой в web-страницу обрабатывается командой round, возвращающей 0 при любом нечисловом значении этого содержимого), но перестраховаться не помешает - перед записью оценки обработаем ее командой intval, конвертирующей содержимое переданной переменной в целое число. Если в этой переменной почему-либо окажется PHP-код, то он благополучно превратится в 0, и желания хакера останутся неисполненными.
fwrite($hdl,intval($_POST['ocen']));
Примечание:
Функция intval (строка) - возвращает числовое значение того, что в строке. Если в строке не число, то возвращает 0.
fclose($hdl);
Также поместим соответствующие значения в переменные $kvo (количество проголосовавших) и $sred (средний балл) для того, чтобы потом их вывести на страницу и порадовать посетителя сведениями, что он был первый проголосовавший, а средний балл равен его оценке:
$kvo=1; $sred=intval($_POST['ocen']);
Если же файлы со сведениями об оценке уже существуют...
} else {
...то считаем для начала содержимое файла с количеством оценок в переменную $kvo...
$hdl = fopen("$dirct/$kolvooc", "r+"); $kvo = fread($hdl, filesize("cnt/$kolvooc"));
...а затем увеличим значение этой переменной на 1 - что и будет новым количеством оценок, с учетом последней выставленной:
$kvo++;
Теперь нам надо вернуть точку считывания (так называемый "указатель") в начало файла - дабы записать в файл новое значение количества оценок. Ведь в результате проведения операции чтения количества оценок из файла точка считывания - указатель - переместилась в его конец. Для совершения данной операции воспользуемся командой rewind:
rewind($hdl);
Примечание:
Команда rewind (дескриптор открытого файла) перемещает точку считывания и записи данных в файл (т.е. указатель файла), в начало этого файла. Если вы записываете что-либо в файл после считывания из него данных, то вам необходимо перед записью воспользоваться этой командой.
Следует помнить, что если файл был открыт командой fopen с параметром а или а+, то независимо от положения указателя запись новых данных командой fwrite будет осуществляться в конец файла.
Запишем новое значение количества оценок в предназначенный для хранения этой величины файл...
fwrite($hdl,$kvo);
...и закроем его.
fclose($hdl);
Теперь разберемся со средним баллом. Откроем файл, где хранится его значение...
$hdl = fopen("$dirct/$ocenka", "r+"); ...запишем это значение в переменную... $sred= fread($hdl, filesize("cnt/$ocenka"));
...и рассчитаем новую величину среднего балла - на основе его старого значения, а также информации о количестве оценок и новой оценки.
$sred=($sred*($kvo-1)+$ocen)/$kvo;
Теперь запишем эти сведения в предназначенный для них файл - точно так же, как и парой абзацев выше.
rewind($hdl); fwrite($hdl,$sred); fclose($hdl); Собственно, и все. }
Запишем в переменную $vivod информацию для посетителя об итоговом результате и краткую благодарность - содержимое этой переменной потом выведем на web-страницу:
$vivod="Благодарим вас за оценку!<br>Всего проголосовало: ".$kvo."<br>Средний балл: ".$sred;
Если же посетитель уже голосовал за данную статью... } else { ...то поместим в $vivod сообщение об этом и ничего делать не станем. $vivod="Вы уже голосовали за эту статью!"; } ?>
После этого можно размещать остальной текст страницы (не забудьте, что из-за команды установки cookie вышеприведенный сценарий должен располагаться в самом начале файла!). В том же месте, где вы пожелаете вывести сообщение посетителю о результате его действий по оцениванию, вставьте код:
<?php echo ($vivod); ?>
который выведет текст сформулированного выше сообщения.
Остальной текст страницы - на ваше усмотрение. Разместите на ней каталог разделов сайта, список статей или просто красиво оформите. Если же пожелаете поставить на ней ссылку на оцениваемую статью - то просто разместите в нужном месте выводящий эту ссылку код:
<?php echo ("<a href=$_POST['nazad']>Назад</a>"); ?>
Как всегда, сценарий можно совершенствовать до бесконечности. Можно, например, увеличить количество возможных оценок, которые посетители могут выставлять статьям - использовать 10-балльную или иную систему. Все в вашей власти - творите...
Для большей наглядности ниже приводится текст сценария целиком, без разрывов.
Вставка в файлы со статьями:
<?php include ("niz.php"); ?> Файл niz.php <?php $dirct="cnt"; $nom=substr(basename($PHP_SELF), 0, -4); $kolvooc="$nom"."kol"; $ocenka="$nom"."est"; if ($_COOKIE[$nom."haveest"]=="") { ?> <form method="post" action="otziv.php"> <input name="nom" type="hidden" value="<?php echo $nom; ?>"> <input type=hidden name=nazad value="<?php echo ($PHP_SELF); ?>">
Поставьте оценку статье:
<SELECT NAME=ocen> <OPTION VALUE=5>5 (Отлично) <OPTION VALUE=4>4 (Хорошо) <OPTION VALUE=3>3 (Удовлетворительно) <OPTION VALUE=2>2 (Плохо) <OPTION VALUE=1>1 (Очень плохо) </SELECT> <input name="submit" type="submit" value="Послать оценку"></form> <? } else { echo ("Оценок этой статье - "); if (file_exists("$dirct/$kolvooc")==True) { include ("$dirct/$kolvooc"); } else { echo ("0"); } if (file_exists("$dirct/$ocenka")==True) { echo (". Cредний балл - "); $hdl = fopen("$dirct/$ocenka", "r+"); $sred = fread($hdl, filesize("$dirct/$ocenka")); fclose($hdl); $sred=round ($sred, 1); echo ("$sred."); } } ?>
В самом начале:
<?php if ($_COOKIE[$nom."haveest"]=="") { SetCookie($nom."haveest","1",time()+2592000); $dirct="cnt"; $kolvooc="$nom"."kol"; $ocenka="$nom"."est"; if ((file_exists("$dirct/$kolvooc")!=True)||(file_exists("$dirct/$ocenka")!=True)) { $hdl = fopen("$dirct/$kolvooc", "a+"); fwrite($hdl, 1); fclose($hdl); $hdl = fopen("$dirct/$ocenka", "a+"); fwrite($hdl,intval($_POST['ocen'])); fclose($hdl); $kvo=1; $sred=intval($_POST['ocen']); } else { $hdl = fopen("$dirct/$kolvooc", "r+"); $kvo = fread($hdl, filesize("cnt/$kolvooc")); $kvo++; rewind($hdl); fwrite($hdl,$kvo); fclose($hdl); $hdl = fopen("$dirct/$ocenka", "r+"); $sred= fread($hdl, filesize("cnt/$ocenka")); $sred=($sred*($kvo-1)+$ocen)/$kvo; rewind($hdl); fwrite($hdl,$sred); fclose($hdl); } $vivod="Благодарим вас за оценку!<br>Всего проголосовало: ".$kvo."<br>Средний балл: ".$sred; } else { $vivod="Вы уже голосовали за эту статью!"; } ?>
Там, где надо вывести сообщение:
<?php echo ($vivod); ?>
Там, где надо вывести ссылку "Назад":
<?php echo ("<a href=$nazad>Назад</a>"); ?>