Веб-программирование на PHP. (Часть IV)

Руслан Курепин
http://kurepin.ru/main.phtml

Веб-программирование на PHP (Часть III)

31. Flash, "Куда дальше?", сверка кода
32. Опять оформление, навигатор, новости
33. Новости на главную страницу
34. На ушко о конструкторах
35. Добиваем новости
36. Размещаем рекламу
37. Счетчики, куда дальше?
38. Голосование, защита от накруток
39. Голосование (Продолжение-1)
40. Голосование (Продолжение-2)
41. Голосование (окончание)


^^^   31. Flash, "Куда дальше?", сверка кода

Здравствуйте, мои недешевые читатели!

Несколько дней я вам не писал. Был занят. Учился. Да-да, учился. Плох тот препод, который не стремится к новым знаниям. К сожалению, вынужден констатировать, что в наших ВУЗах дела в области современных компьютерных технологий обстоят так, что ученики зачастую намного "продвинутее" своих преподавателей. Преподаватели, в свою очередь, не желают это принимать и попросту "гасят умников".

Ну да ладно. Я не препод, но очень благодарный ученик. Люблю учиться, особенно, когда надо не только слушать кого-то, но и самому делать что-то.

Чему я учился? Я учился рисовать на Macromedia flash. Потратил на это почти полные три дня и чему-то научился. Нарисовал несколько интерактивных роликов и несколько баннеров. Баннеры можно посмотреть тут: http://caricatura.ru/agency/.

Технология flash мне очень понравилась. Не только за то, что с ее помощью можно создавать качественные презентации и красивые заставки, но и главным образом за то, что с ее помощью можно разрешать сложные задачи html-программирования, связанные с общением сайта и посетителя. Например, с помощь flash можно обновлять некую информацию не перегружая сайта и не создавая фреймов. И другие возможности, связанные с удачной реализацией интерактива. Как-нибудь, обязательно посвящу статью использованию флеша в создании сайта. Конечно, с точки зрения уникальной функциональности, а не красоты.

Теперь подступаем ближе к нашим парнокопытным (или бараны -- непарнокопытные?). Вот, значит, какие мысли меня все больше стали посещать...

Довольно широко мой курс по PHP стали читать во всевозможных рассылках и дры. Аудитория растет. Причем, среди читателей стали появляться люди достаточно продвинутые в программировании. Может быть без особого опыта в PHP, но быстро осваивающие этот язык, так как он не сильно отличается от того же C или Perl. Разумеется, вопросы пошли более серьезные, интересные предложения в области алгоритмов и так далее. Так вот, дорогие мои читатели! Чего вы мне в рот-то смотрите? Предлагаю вам не стесняться, а приниматься за дело. Каждый из вас может написать собственный класс к нашему проекту, посвященный той или иной конкретной задаче: поиску, ротации баннеров, рассылкам, хелпу, трансляции новостей, голосованию, гостевым книгам, форуму, рейтингам, статистике и так далее!

Давайте делать проект вместе! Только оставим за мной функции координатора, чтобы не было накладок. Сообщайте мне о том, что в решили сделать то-то и то-то, а я буду иметь это в виду и предостерегу других от дублирования работ по одной теме.

Объектно-ориентированное программирование (ООП) тем и прелестно, что позволяет разрабатывать проекты большими группами, заботясь только о системе взаимоотношений между модулями. Мне кажется, что я достаточно четко задал тон формату классов и его вполне можно придерживаться. Разумеется, наиболее сложные и интересные моменты в каждом таком направлении мы будем решать вместе. Код будем публиковать и доводить до совершенства только сообща.

Итак, жду от вас предложений!

Теперь я обращаюсь к тем, кто не может или не хочет взваливать на себя бремя того или иного раздела сайта. Отныне вы не остаетесь читателями-наблюдателями. От вас требуются идеи! Интересные идеи по направлению развития проекта. Мне не хочется решать выдуманные самому для себя задачи. Давайте договоримся, что каждый следующий урок я буду черпать из предложений, составленных и обсужденных вами в моем форуме на forum.kurepin.ru. Договорились? Я надеюсь, что договорились. Кстати, из того же обсуждения желающие присоединиться к проекту смогут черпать и для себя идеи.

На этом позвольте раскланяться. Жду идеи в форум. Тема беседы будет называться... "Куда дальше?!!".

А чтобы данный выпуск не показался вам совсем скучным -- предлагаю скачать очередной SFX-zip всего кода сайта на текущий момент.


^^^   32. Опять оформление, навигатор, новости

Согласно триду "Куда дальше", что на форуме forum.kurepin.ru, сегодня мы уделяем внимание внешнему виду сайта.

Надо вспомнить, что мы изначально ориентировали наш проект на создание "странички обозревателя". То есть, сайт должен выполнять функции управляемого сайта с текстами, рубриками, новостями, рассылками. Вот и будем внешний вид ориентировать на это. Но перед тем, как перейти к скриптам, хочу ответить на популярный вопрос: "Почему мы храним тексты в файлах на диске, а не в базе данных?".

Хранение текстов в базе, текстовых файлах или еще где-то -- дело программиста. Лично я могу назвать несколько десятков причин в пользу того или иного способа. Но в нашем случае это связано только с тем, что мне хочется, как можно шире охватить возможности PHP -- показать максимум приемов программирования и способов хранения и обработки информации.

И еще. Прошлую задачу по SQL успешно решили, ответы можно найти, как всегда, на forum.kurepin.ru, а я задаю следующую.

На том же форуме недавно вставал вопрос о том, как передавать данные из формы в PHP, если в форме присутствуют "чекбоксы". Как определить -- отмечен был чекбокс или нет? Как правильно давать имена чекбоксам при формировании их списка из базы. Этот вопрос мы быстро разрешили.

И на основе этой беседы я задаю задание более сложное. Представьте, что вы программируете систему документооборота. У вас, помимо всего, существует условие доступа нескольких уровней для нескольких групп. И вам надо написать web-функцию редактирования доступа пользователю. Пример. Как это на экране.

Управляющий выбирает из списка пользователей одну персону и на экран выводится таблица:

[доступ] [название группы]
[доступ] [название группы]
[доступ] [название группы]
[доступ] [название группы]

где:

доступ -- выпадающее меню с вариантами (нет доступа, только чтение, редактирование, администрирование);

название группы -- есть название группы, к которой применяется данный доступ.

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

Управляющий выставляет напротив каждой группы уровень доступа и нажимает "Сохранить". Данные сохраняются.

"Как правильно сгенерировать такую форму?" -- это первый вопрос. То есть, как правильно назвать все переменные формы и какие значения им присвоить.

И вопрос второй -- более интересный: "Как обработать эти данный в PHP, после нажатия кнопки "Сохранить", напишите оптимальный и/или красивый код".

Подумаете над вопросом чуть позже, а мы возвращаемся к нашим кудрявым... Переделаем навигатора. Это полоска сверху и снизу, если вы уже забыли. Я посмотрел, подумал... и решил, что навигатор у нас будет выполнять функцию "зеркала местонахождения". То есть, он будет показывать путь, который прошел пользователь от головной страницы сайта. Это модная и очень удобная "фича". Для этого немного переделаем файл navigator.inc, в котором хранится сам навигатор. Что переделаем?

1. Установим выравнивание к левому краю.
2. Вместо готового текста сделаем присваивание текста переменной $NAVIGATOR в vars.classи ее вывод в navigator.inc. Это делается для того, чтобы в любом html-файле нашего проекта мы могли как угодно переопределить вывод этой строки: дополнить, изменить, стереть ее.
3. Добавим отдельный стиль файл style.css для навигатора.

Вот, что у меня получилось:

1. В vars.inc дописал:

// Переменные
var $NAVIGATOR="<font class=text_navigator><b>Смотрим:</b></font> 
<a class=text_navigator href=/>начало</a>"; // верхний и нижний навигатор
2. Файл navigator.inc теперь выглядит вот так:
<table width="100%" cellspacing="0" cellpadding="3" bgcolor=#eeeeee>
<tr>
    <td>
    <? echo $my->NAVIGATOR; ?>
</td>
</tr></table>
3. А в style.css добавил:
.text_navigator
{
 font family: Verdana, Arial Cyr;
 font size: 8pt;
 color: #FF6600;
 font-weight: normal; 
}
A.text_navigator { COLOR: #FF6600; FONT-FAMILY: Verdana, Arial Cyr; 
                   FONT-SIZE: 8pt; TEXT-DECORATION: none }
A.text_navigator:link { COLOR: #FF6600; FONT-FAMILY: Verdana, Arial Cyr; 
                   FONT-SIZE: 8pt; TEXT-DECORATION: none }
A.text_navigator:visited { COLOR: #FF6600; FONT-FAMILY: Verdana, Arial Cyr; 
                   FONT-SIZE: 8pt; TEXT-DECORATION: none }
A.text_navigator:hover { COLOR: #FF0000; FONT-FAMILY: Verdana, Arial Cyr; 
                   FONT-SIZE: 8pt; TEXT-DECORATION: underline }
Теперь можно управлять навигатором, показывая пользователю место его расположения в древе сайта.

Мы будем это делать путем добавления к переменной навигатора необходимый текст. Делать это желательно на каждой странице и обязательно до начала вывода страницы. Например, в файл /cat/index.php следует добавить где-нибудь в начале, но уже после создания экземпляра класса и получения названия рубрики:

  $my->NAVIGATOR.="<font class=text_navigator> -> рубрики -> 
                  \"<a href=/cat/?cat=$cat class=text_navigator>"
                  .$my->out_cat_name."</a>\"</font>";
А вывод названия рубрики в тексте уже можно убрать. А можно и не убирать. Давайте лучше его отцентруем и придадим вид заголовка при помощи CSS. Для этого у нас в CSS определен тип text_title. Только цвет мы ему изменим с серого на коричневый -- так веселее.

Таким образом, файл /cat/index.php у нас выглядит теперь следующим образом:

<?
  if(!isset($cat)) { header("location:/"); exit(); }

  require("/home/atos/php.kurepin.ru/req/out.class");

  $my=new class_out;
  $my->sql_connect();

  $my->out_cat_id=$cat;
  $err=$my->out_text_by_cat();
  if($err) $my->html_error=$my->err_to_html($err);

  $my->NAVIGATOR.="<font class=text_navigator> -> рубрики -> 
                   \"<a href=/cat/?cat=$cat class=text_navigator>"
                  .$my->out_cat_name."</a>\"</font>";

  $my->html_headers();
  include($my->PATH_INC."/top.inc");
  include($my->PATH_INC."/adv_top.inc");
  include($my->PATH_INC."/navigator.inc");
  flush();

?>

<table width="100%" cellspacing="0" cellpadding="3">
<tr>
  <td valign=top>
    <div align=center class=text_title><? echo $my->out_cat_name; ?></div>
    <br>
    <? echo $my->out_text_by_cat; ?><br>
  </td>

  <td width=200 bgcolor=#eeeeee valign=top>
    <? include($my->PATH_INC."/menu_main.inc");?><br>
</td>
</tr></table>

<?

 flush();

 include($my->PATH_INC."/navigator.inc");
 include($my->PATH_INC."/adv_top.inc");
 include($my->PATH_INC."/bottom.inc");

 $my->sql_close();
?>
Можно было бы и не брать в тэг <a> ссылку на данную рубрику. Все равно, она ведет на эту же страницу. Но пользователя надо приучать посетителя к тому, что навигатор позволяет кликать по любому слову в его строке. Кроме того, заход на эту же страницу бывает удобен, когда пользователь на странице что-то натворил и хочет ее обновить. В этом случае он и будет кликать в эту ссылку.

Слово "рубрики" я добавил в навигатор на будущее. Надо будет сделать страницу, посвященную рубрикам, на которой каждому названию рубрики будет дана своя аннотация или просто расшифровка короткого названия рубрики. Мне кажется, что это не помешает.

Теперь давайте немного причешем заглавную страницу. Или "морду", как ее часто называют web-дизайнеры.

Предлагаю для начала разместить на морде информацию о сайте и подключить новости. Краткую информацию о сайте можно вписать прямо файл index.php, так как этот текст вряд ли нам еще где-то понадобится. Пишу...

<font class="text_"><font color=red><b>Внимание!</b></font> Данный сайт 
является практическим комментарием к курсу web-программирования на PHP. 
Сайт обновляется и пополняется новыми возможностями параллельно появлению 
новых выпусков курса на сайте <a href=kurepin.ru class="text_">kurepin.ru</a>.
<br><br><div align=right>
<a href=mailto:atos@21.ru class="text_">Руслан Курепин</a><br>
</div><br>
</font>
... и копирую это в index.php

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

Давайте так! Мы будем хранить новости в текстовом файле, но саму работу с новостями овеем особым подходом и особой обработкой.

Я говорю о том, что новости мы будем добавлять, сортировать, вырезать, составлять дайджесты и прочее -- из текстового файла, а не из базы данных. Таким образом, мы сможем хорошенько потренироваться в работе с файлами. А работы с базой у нас и так будет предостаточно.

В начале сегодняшнего выпуска я и писал о том, что хранение данных в файлах и в БД имеют свои достоинства и недостатки. Поэтому, уметь работать с файлами -- это тоже очень важно. Может быть даже важнее, чем с SQL-базой. Вам не лишнее будет узнать, что все огромные проекты в Рунете делаются на файлах гораздо чаще, чем на SQL. В качестве примера можно привести такие известные всем проекты, как: счетчик Rambler, баннерная сеть RLE, издание Lenta.ru и другие. И тому есть свои причины, поверьте.

Я опять отвлекся. Значит так. Нам надо разработать форму хранения новостей в текстовых файлах. Предлагаю хранить следующим образом. Каждая новость располагается в одной строке и состоит из четырех составляющих:
1. Дата
2. Время
3. Анонс
4. Новость

Все четыре составляющие записываются в одной строке, а в качестве уникального разделителя предлагаю использовать двойную тильду "~~". Такое сочетание крайне редко используется в тексте. Разве что в графическом "украшательстве текстов", поэтому нам не светит включать в новость такое сочетание символов: мы смело можем считать это сочетание разделителем составляющих новости.

Строки хранятся в файле -- одна за другой.

Пустые строки игнорируются.

Строки, начинающиеся с "#", считаем комментариями и внимания не обращаем.

Кстати, этот знак имеет тоже несколько неофициальных названий, среди которых самым распространенным среди программистов является "тюрьма".

Чуть позже, когда приступим к программированию работы с новостями, мы определим несколько глобальных переменных для новостей, а пока просто включим новости в головную страницу из файла. Новости будем хранить в отдельной директории /news/ на одном уровне с другими основными папками: inc, req и другими. В этой директории будут хранится файлы-исходники, из которых будут формироваться другие файлы, видимые на сайте. Те файлы, что видны на сайте, временные файлы, файлы-архивы.

Основной/активный файл с кратким списком новостей назовем main.inc, его и прицепим на главную страницу. Вот так выглядит файл index.php -- морда нашего проекта:

<?

  require("/home/atos/php.kurepin.ru/req/out.class");

  $my=new class_out;
  $my->sql_connect();

  $my->html_headers();
  include($my->PATH_INC."/top.inc");
  include($my->PATH_INC."/adv_top.inc");
  include($my->PATH_INC."/navigator.inc");
  flush();

?>

<table width="100%" cellspacing="0" cellpadding="3">
<tr>
  <td valign=top>
    
<font class="text_"><font color=red><b>Внимание!</b></font> Данный сайт 
является практическим комментарием к курсу web-программирования на PHP. 
Сайт обновляется и пополняется новыми возможностями параллельно появлению новых 
выпусков курса на сайте <a href=kurepin.ru class="text_">kurepin.ru</a>.
<br><br><div align=right>
<a href=mailto:atos@21.ru class="text_">Руслан Курепин</a><br>
</div>
<br>
</font>

<!-- news -->
<font class=text_title>Новости</font>

<p><? if(file_exists($my->PATH_NEWS."/main.inc")) include($my->PATH_NEWS."/main.inc"); ?>

<!-- /news -->

<br>
</td>
  <td width=200 bgcolor=#eeeeee>
    <? include($my->PATH_INC."/menu_main.inc");?><br>
</td>
</tr></table>

<?

 flush();

 include($my->PATH_INC."/navigator.inc");
 include($my->PATH_INC."/adv_top.inc");
 include($my->PATH_INC."/bottom.inc");

 $my->sql_close();
?>
Интересный момент -- обратите внимание -- все фрагменты в этом файле подключаются без проверки на существование файла, а новости я предварил проверкой. Как думаете, почему?

И не забудьте в vars.class добавить новую переменную. Догадались какую? Ну, PHP вам укажет на ошибку, если забудете.


^^^   33. Новости на главную страницу

Как и обещал, сегодня работаем с новостями. Если я еще помню вчерашний выпуск, мы придумали формат для хранения новости. Исходя из этого формата нам надо бы получить краткий вариант новостей для "морды" и полный вариант для раздела "новости" с разбиением всего списка новостей на страницы. Начнем с новостей на титульную страницу (морду).

У нас для анонсов предусмотрено специально поле строке записи новости. Кроме нее возьмем дату новости, если новости у нас выходят не часто (раз в день или реже) или время новости, если новости у нас идут как из пулемета. Еще нам понадобится "ограничитель" количества коротких новостей -- еще одна переменная в vars -- NEWS_MAIN_MAX -- мы ее назовем. А имя файла для титульных новостей мы определили в прошлом выпуске: /news/main.inc. Этого будет достаточно, мне кажется.

Теперь большой и философский вопрос о том, в какой класс нам запихнуть функции работы с новостями.

Для добавления новостей и их редакции вполне подойдет class_in, а вот для вывода... надо рассудить здраво. Давайте рассуждать вместе... Новости у нас могу вызываться в нескольких местах:

1. На главной странице
2. На странице "новости"
3. На любой другой странице может вызываться анонс в виде текстового рекламного блока.

Напрашивается класс out... Согласны? Вижу, что согласны. Ох, люблю я вас поводить за нос! Ну какой может быть вывод новостей через класс? Вы что, собираетесь выдергивать новости из архива для каждого пользователя? Конечно же нет. Тут разговора быть не может -- новостные файлы должны формироваться на диск в момент какого-либо изменения в главном новостном файле и подключаться на сайт простым includ-ом.

А это значит, что все (почти все) функции работы с новостями у нас пойдут в класс in. Вернее, я предлагаю породить от класса in класс class_in_news и в нем хранить все функции обработки новостей. Итак. Создаем файл /req/in_news.class.

И... снова ударяемся в рассуждения. Рассуждения, друзья мои, -- в нашем деле далеко не последняя вещь, если не первая.

Когда нам надо обращаться к формированию файлов новостей? Очевидно, что только после внесения каких-то изменений в основной новостной файл. Это значит, что нам нужна функция, создающая новостные файлы. Эту функцию мы будем вызывать изо всех функций, вносящих какое-либо изменение в основной файл новостей. Перечитывать этот абзац до полного понимания.

Вы можете сейчас сказать, сколько и каких файлов нам надо будет создавать? Я, честно говоря, сильно затрудняюсь это сделать. Поэтому, создадим первую функцию in_news_files_create(), которую и будем вызывать для формирования новостных файлов. То есть это будет некий аккумулятор функций, создающей структуру новостных файлов на диске.

Первой "рабочей" функцией станет in_news_main(). Именно в ней мы сделаем выборку нужных нам строк в файл /news/main.inc.

В одном из выпусков я уже рассказывал о работе с файлами, поэтому не стану заострять внимания на используемых ключевых словах языка, скажу лишь только, что использовать мы будем самые заурядные команды.

Как происходит сборка файла для морды. Алгоритм прост как барабан. Надо прочесть от начала файла $NEWS_MAIN_MAX строк, выдрать из них дату и анонсную часть текста и записать это в файл. Проделаем это в реале:

Да, чуть не забыл! Исходный файл для хранения новостей так и будем звать -- source.inc -- исходник.

Создали файл /news/source.inc:

# Новости
18.02.02~~12:30~~Новый выпуск PHP на <a href=http://kurepin.ru class="text_">kurepin.ru</a>
~~Новый выпуск PHP на <a href=http://kurepin.ru class="text_">kurepin.ru</a>. 
В этот раз речь пойдет о внешних(видимых) фрагментах сайта: форматирование строки 
навигатора, добавление новостей на главную страницу

17.02.02~~12:30~~Новый выпуск PHP на <a href=http://kurepin.ru class="text_">kurepin.ru</a>
~~Новый выпуск PHP на <a href=http://kurepin.ru class="text_">kurepin.ru</a>. 
В этот раз речь пойдет о внешних(видимых) фрагментах сайта: форматирование строки 
навигатора, добавление новостей на главную страницу
Ну... ничего страшного, пусть будет такой убогий для начала.

Далее создаем в class_in_news функцию in_news_main(). Вот такой файл у меня получился:

<?

require("/home/atos/php.kurepin.ru/req/in.class");

class class_in_news extends class_in
{
// Класс работы с новостями

 function in_news_main()
 {
  // открываем файлы для чтения и записи
  if(!$r=fopen($this->PATH_NEWS."/source.inc","r")) return(41);
  if(!$w=fopen($this->PATH_NEWS."/main.inc","w")) return(42); 

  $i=$this->NEWS_MAIN_MAX; // устанавливаем счетчик

  while((!feof($r)) and ($i>0))
  {
   $str=fgets($r, 10240);
   if((substr($str,0,1)!="#") and (substr_count($str,"~~")>2))
   {
    list($dt, $tm, $anons, $text)=explode("~~",$str);
    fputs($w,"<font class=\"text_\"><b>$dt</b> $anons<br>\n");
    $i--;
   }
  }

  fclose($r);
  fclose($w);

  return(0);
 }

 function in_news_files_create()
 {
  $err=$this->in_news_main();
  if($err) return($err);

  return(0);
 }
}

?>
Пояснять надо? Поясняю.

Открыли файл для записи/перезаписи и для чтения.
Установили убывающий счетчик на нужное количество строк новостей.
Начали цикл while по условию счетчика или конца файла

Прочли строку
Проверили, если начинается она не с "тюрьмы" и имеет менее более двух разделителей "~~" (чтобы не обрабатывать строки неправильно созданные)
Разделили строку на четыре составляющие
Две нужные записали в новый файл с нужным форматированием
Уменьшили счетчик на единицу
Закрыли цикл while
Закрыли файлы
Вернули нулевой код ошибки

Все, как всегда, просто и логично. Как и должно быть в коде программы.

Если вы внимательно просмотрели код, то заметили два новых номера ошибки. Добавим их в класс утилит:

$err[41]="Не могу открыть файл ".$this->PATH_NEWS."/source.inc для чтения";
$err[42]="Не могу открыть файл ".$this->PATH_NEWS."/main.inc для записи";
Все, новости на морду у нас теперь создаются! Ура! Только эта... как выполнить-то функцию, а? Она же у нас предназначена для автоматического выполнения из других функций...

Вот мы и подошли к созданию еще одной важной директории нашего проекта. Даже не одной, а сразу двух! Дело в том, что в жизни любого, даже самого продуманного сайта возникают ситуации, требующие ручного запуска той или иной автоматической функции. Это может потребоваться и для наших новостей. Кроме этого, существуют функции, которые необходимо запускать как из скриптов, так и с некоторой периодичностью из cron-а.

В директорию /utils/ мы будем складывать файлы для ручного выполнения каких-либо операций, а в директорию /cron/ будем складывать скрипты для периодического выполнения. Пожалуйста, создайте обе эти директории в своих проектах.

Файлы, живущие в этих директориях имеют, как правило, всего несколько строк кода -- они всего лишь подключают нужный класс и вызывают нужную функцию.

Положив в папку /utils/ файл create_news.php такого содержания:

<?
  require("/home/atos/php.kurepin.ru/req/in_news.class");

  $my=new class_in_news;
  $my->sql_connect();

  $err=$my->in_news_files_create();
  if($err) $my->err_to_str($err);

  $my->sql_close();
?>
Этот файл мы можем выполнить из командной строки, придав ему вид выполняемой программы. Лично я этого никогда не делаю. Мне приятнее запускать подобные скрипты, подставляя их имена в качестве параметра к php, собранному для командной строки. Но это всего лишь дело привычки.

Главное что? Главное, что скрипт этот я запустил и получил на сайте новостную колонку из коротких анонсов. Потом надо будет снабдить их ссылкой на полную новость, но это уже после того, как будет создана страница трансляции полных новостей.


^^^   34. На ушко о конструкторах

Вновь хочу занять ваши мозги. На форуме буквально только что мой тезка Руслан задал вопрос про декларации переменных и про конструкторы в PHP.

Что такое декларация (объявление) переменных? Я об этом писал в одном из первых выпусков курса. Это заявление имени переменной при помощи ключевого слова var.

var $variable_name;

PHP не станет ругаться, если вы не объявите переменную. Вернее, будет, если соответственно настроить это в конфигурационном файле php.ini (php3.ini для PHP3). Но при этом будет обрабатывать ваши переменные, если вы их не декларировали явно. На мой взгляд, это не правильно. Или мы не декларируем переменные как во многих языках, либо прописываем их явно, как, скажем, в Pascal, и никаких "серединок". Но, тут я бессилен что-либо сделать.

Вопрос же Руслана заключался вот в чем (кому лишний раз лень заглянуть на форум).

Вот так PHP не ругается:

var $variable1="123abc";

а вот так -- ругается:

var $variable1="123abc";
var $variable2=$variable1;

Что делать?

Если вы хотите в начале выполнения вашей программы сделать присвоения переменным с участием других переменных, то для этого надо сначала объявить все переменные, а затем совершить все необходимые присвоения. Причем, надо не забывать, что присвоения в классах могут быть только в теле какой-то процедуры.

Насколько я понимаю, PHP не обрабатывает заявления var переменна = переменная по очень просто причине: директива var предписывает транслятору выделить в памяти место под переменную. А как его выделить, когда явно не определен тип переменной и ее размер?

Но не надо печалиться. Зато PHP нам не запрещает декларировать и работать с переменными в любом месте скрипта. Поэтому, я иногда ставлю декларирование переменных непосредственно перед функциями, работающих с ними.

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

В общем-то, поклонники ООП могли бы меня упрекнуть, что я так долго не говорил о конструкторах. Мол, конструктор -- это важно, это начало, с него стартует выполнение программы. В некоторых языках вообще невозможно выполнение программы без объявления конструктора. И так далее.

Ну и начхать мне на этот конструктор! И вам рекомендую, если вы не теоретик, а практик. Давайте рассуждать вместе, а вы меня поправите, укажете на ошибки и в одном из следующих выпусков я поменяю свое отношение к конструкторам.

Значит так! Конструктр в PHP это такая же функция, как и любая другая, только запускается она автоматически при объявлении нового экземпляра класса, а определяется это ее именем: если имя функции совпадает с именем класса, то это конструктор.

Вроде бы, полезная штуковина! Запхал в этот самый конструктор все необходимые присвоения и в ус не дуй. Но не тут то было! Есть одно неудобство, которое перевешивает удобство автозапуска.

Предположим, что мы работаем с классом init, у которого есть одноименный конструктор. В этот конструктор мы запхали все нужные присвоения и нам комфортно. Потом мы породили от этого класса дочерний -- utils, в котором тоже есть переменные, нуждающиеся в присвоении. Что делать? Пихать их в init? Абсурдно, если init -- самостоятельный, используемый отдельно класс. Значит надо создавать конструктор для класса utils? Да, другого выхода нет. Создаем.

Блямс! А конструктор init перестал выполняться после появления конструктора utils! Другими словами, он перестал быть конструктором. Что делать? Перетаскивать данные из конструктора init в utils? Глупо, тогда init точно перестанет работать самостоятельно. Тащить данные в обратную сторону тоже не резон, мы это заметили чуть ранее. Остается один вариант -- вызвать выполнение конструктора init из конструктора utils как обычной процедуры.

Фуу-у-у, вытерли пот со лба и продолжили работу. Создали класс sql, в котором тоже можно создать конструктор, сделать в нем свои присвоения, затем вызвать конструктор предыдущего класса. И так далее, по древу всего проекта.

Вот такому пути, друзья мои, вас научат в умных книжках. Потому, что это -- правильно и вроде бы даже красиво. Но это только в книжках. А на практике это выглядит несколько иначе.

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

Потом, когда программа разрастается в размерах, ты начинаешь думать, да нафига мне вот тут, тут и тут эти присвоения, если я их использую только вот здесь. Перенесу-ка я их из конструктора прямо в функцию, которая и с ними работает. Или вообще оформлю в отдельную функцию и буду вызывать только когда мне это надо. Это же сэкономит кучу памяти и времени.

"Да прибью-ка я вообще эти гребаные конструкторы, какой дурак их так реализовал в PHP?".

Примерно так текут мысли некоторых PHP-программистов, попытавшихся обратиться к помощи конструкторов в средних и крупных проектах. Поэтому, откладывая в сторону всякие умные книжки (поверьте, я их все обязательно читаю и часто нахожу в них умные мысли и полезные знания) и признаем, что:

1. Объявление глобальных переменных удобно делать в самом родительском классе. У нас это class_vars.

2. Переменные внутриклассовые надо объявлять вначале данного класса.

3. Переменные, имеющие отношение к конкретной функции надо объявлять перед этой самой функцией.

4. Все присвоения надо производить в теле процедуры, требующей эти самые присвоения.

5. Если есть группа присвоений и приведений переменных, требующаяся в разных местах программы -- надо эти присвоения объединить в отдельную функцию, которую и вызывать по мере надобности.

Все!

Хотел написать про конструкторы только во вступлении, а получился целый выпуск.


^^^   35. Добиваем новости

Шаг 159: http://php.kurepin.ru/step/159/

С наступающим вас праздником, если кто еще считает, что современная Российская Армия -- это праздник.

Ну что, продолжим битву с новостями, или сначала новую задачку? Две предыдущие были блестяще решены Serg_Simf на форуме.

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

А теперь вернемся к новостям. Предлагаю разбивать новости по N штук на странице и хранить их в готовом виде в папке /news/pages/. А показывать их на странице /news.php, указывая в качестве параметра номер страницы новостей. Нам для этого понадобится.

1. Глобальная переменная $NEWS_PAGE_SIZE, в которой будем хранить количество новостей в одном файле. Или нам лучше отделять страницы не по новостям, а по размеру страницы? Да, правильнее будет -- по размеру.

2. Подпапка /news/pages/.

3. "Куча" новостей для проверки. Это я возьму откуда-нибудь.

4. Функция разбиения новостей на страницы: in_news_pages

5. Создать страницу news.php, которая будет отображать страницы новостей

Приступим к реализации.

Переменной присвоим "6" -- это будет 6 килобайт. Можно было бы даже меньше.

Подпапку /news/pages/ создали. Следите за правами доступа в папки.

Кучу новостей я, наверное, стащу со своего caricatura.ru.

Ну-с, теперь главное -- функция. Попробую изложить алгоритм работы функции на словах, а потом его реализовать.

Функция открывает для чтения файл новостей и начинает его читать с головы (с последней новости) в цикле, "переворачивая" страницы после каждого достижения предельного размера страницы, определенного в переменной $NEWS_PAGE_SIZE. Каждый фрагмент записывается в директорию pages под очередным номером: 1, 2, 3...

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

function in_news_pages()
 {
  // Открываем файл для чтения
  if(!$r=fopen($this->PATH_NEWS."/source.inc","r")) return(41);
  
  $page_num=0; // Порядковый номер страницы

  while(!feof($r)) // Цикл повторяется, пока не достигнем конца файла
  {
   $page_size=0; // Обнуляем размер будущей страницы
   $page_num++; // Номер очередной страницы

   // открываем страницу
   if(!$w=fopen($this->PATH_NEWS."/pages/$page_num","w")) return(43); 

   if($page_num>1) // Это не первая страница?
   {
    // Тогда добавим ссылку на предыдущую
    fputs($w, "<div align=center><a 
     href=/news.php?p=".($page_num-1).">Предыдущая страница</a>
     </div>\n<br>\n");
   }
 
   while( (!feof($r)) && ($page_size<($this->NEWS_PAGE_SIZE*1024)) )
   {
    $str=fgets($r, 10240); // Читаем очередную новость

    if((substr($str,0,1)!="#") and (substr_count($str,"~~")>2))
    {
     list($dt, $tm, $anons, $text)=explode("~~",$str);
     fputs($w,"<font class=\"text_\"><b>$dt $tm</b> $text<br>\n<br>\n");
     $page_size+=strlen($text); // Прибавили к размеру файла размер новости
    }

   }
   if(!feof($r)) fputs($w, "<div align=center><a 
     href=/news.php?p=".($page_num+1).">Следующая страница</a></div>\n");

   fclose($w); // Закрыли готовый файл страницы новостей

  }
  fclose($r);
  
  return(0);
 }

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

Теперь надо вызвать эту функцию из нашего "конструктора новостей", но это вы сделаете самостоятельно.

Осталось только создать файл news.php.

Для этого мы берем любой наш php-файл и записываем туда, где надо вставить текст страницы:

<br>
<div align=center class=text_title>Новости</div>

<p><b>Warning</b>:  Failed opening '/pages/' for inclusion (include_path='') 
in <b>/usr/home/atos/kurepin.ru/data/159</b> on line <b>108</b><br>
Очевидно. Мы просто подсасываем страницу по ее номеру и все.

Какой основной недостаток в таком изложении новостей? В том, что со временем на страницах с определенным URL будет меняться информация. Это не очень удобно для поиковых серверов в сети. Попробуйте сами переписать функцию разбивки на страницы так, чтобы с пополнением новостей содержимое фиксированных адресов URL не менялось. Но нам еще надо обработать нестандартный ввод параметра p. Для этого добавим в начале файла, сразу после создания экземпляра класса:

  $p=(int)$p;
  if($p<1) $p=1;
  if(!file_exists($my->PATH_NEWS."/pages/$p"))
  {
   header("location:http://php.kurepin.ru/news.php");
   exit();
  }
Поясняю.

Сначала приводим номер страницы к целочисленному виду.

Затем проверяем на нулевое или отрицательное значение. Если условие выполняется, то это противоречит логике и надо вернуться на первую страницу. То есть p=1. Почему на первую? Потому, что p будет равно 0, если запросить news.php без параметров. А это равнозначно запросу последних новостей, логично? Логично. Значит надо показать первую страницу.

Далее нам надо проверить существование такой страницы на диске. Если его по каким-то причинам нет (скорее всего, такое возникнет, если пользователь попробует ввести слишком большой номер страницы), мы так же пуляем его на первую страницу.

Не забудьте добавить новую ошибку в utils и подредактировать переменную навигатора, чтобы показывала раздел новостей и номер новостной страницы.

Ну что еще? Добавим ссылку на новости на главную страницу нашего проекта и можно отправляться играть в бильярд. Мне на день рождения такой кий подарили... а я до сих пор его не опробовал.


^^^   36. Размещаем рекламу

Шаг 160: http://php.kurepin.ru/step/160/

Ну не сайт у нас, а тоска сплошная: данных нет, графики нет, ничего не движется, ничего не мешается. Будем сегодня оживлять наш сайт. Что нужно, чтобы сайт стал живым и красивым? Нет, рисовать красивые картинки я не умею, разве что, во flash. Но у меня есть один секрет, как оживить сайт, не рисуя графику и не воруя ее.

Просто добавь рекламы! Помните, каким серым и тусклым был наш сайт? Это можно будет по-прежнему увидеть на разных стадиях разработки нашего проекта в поддиректориях /step/.

Итак. За ближайшие 30 минут мне надо успеть добавить на сайт:

- два баннера, формат 468x60;
- четыре, формат 120x60
- два квадрата: 100x100
- один небоскреб: 120x60
- счетчики: hotlog, rambler, top.mail.ru

...а дальше видно будет.

Как будем подключать баннеры? Т.е. как будем подключать баннерный код? Можно, конечно, вставить его as is, то есть прямо в текстовые файлы. Что, впрочем, вы и сделаете, т.к. разрабатывать нормальную систему ротации никто не решился. Кроме СОН-а, который в форуме предложил использовать чей-то код, написанный для чередования баннеров разных систем.

Я, разумеется, использую свой код для ротации баннеров, который не буду публиковать, т.к. это моя коммерческая разработка. А только вставлю в класс out упрощенную функцию получения фрагментов кода из базы.

В двух словах о данной ротации. Это упрощенный до предела код, изымающий из моей службы ротации коды по номеру "гнезда".
Каждому месту на сайте присваивается свой уникальный номер гнезда и под этот номер в базу заливаются html-фрагменты, которые потом показываются по мере обращения к той или иной странице. На каждое гнездо может быть залито разное количество html-фрагментов, имеющих разные свойства: кол-во показов, приоритет, таргетинг и прочее. Почти как в любой баннерной сети.

Кстати, на своих сайтах вы можете пока использовать ротацию на основе системы adriver.ru, разработанную теми же специалистами, что и небезызвестную баннерную сеть RLE. Она предоставит вам массу возможностей по размещению по вашему проекту рекламы, текстовых блоков и чего угодно другого. Только придется за это заплатить деньгами или отдать 10% трафика ваших рекламных мест.

Надеюсь, что наиболее продвинутые читатели этого курса рано или поздно призадумаются и решатся написать свой похожий ротатор рекламы, чтобы ничего никому не платить. В пределах одного сайта это не так сложно, поверьте!

А я пока добавлю свою функцию в класс out, которая так и называется -- out_rotate(), и принимает она в качестве аргументов два числовых значения: номер акаунта в моей системе ротации и номер гнезда, из которого нужно изъять на сайт очередной фрагмент html- или другого какого кода. Моя система крутить все: от графики до кодов чужих баннерных систем и даже исполняемых скриптов. Я не буду приводить тут код функции, вы ее увидите при следующей "глобальной сверке", или вообще не увидите, если к моменту сверки мы таки создадим что-то свое. Так, добавил...

Теперь давайте вставлять рекламу, что ли... Начнем с головы. Где-то в выпусках о дизайне мы рисовали небольшую схемку сайта. Вот, если я правильно помню, то широкие баннеры у нас должны быть сверху и снизу. К ним же можно приклеить и короткие дополнения -- 120x60. Давайте для верхней рекламы создадим свой inc-файл. Если быть точным, то он у нас уже есть -- adv_top.inc, только пустой. Заполним его вот таким html-фрагментом-табличкой:

<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
    <td align="center"><? $my->out_rotate(15,31); ?></td>
    <td align="center"><? $my->out_rotate(15,1); ?></td>
    <td align="center" width=200><? $my->out_rotate(15,32); ?></td>
</tr>
</table>

15 -- это номер акаунта, а вторые числа -- номера "гнезд". Уже можно наблюдать на сайте http://php.kurepin.ru баннеры сетей XBN и RLE. Причем, показываются они в соотношении 1/2.

Файл adv_bottom.inc оформим тем же манером, только вставим другие номера "гнезд":

<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
    <td align="center"><? $my->out_rotate(15,33); ?></td>
    <td align="center"><? $my->out_rotate(15,2); ?></td>
    <td align="center" width=200><? $my->out_rotate(15,34); ?></td>
</tr>
</table>

Нижний широкий баннер -- сеть ALBN, созданная мной на паритетных началах с Дмитрием Кравчуком.

Так... ну все прямо ожило... все задергалось, все забегало. Замечательно!

В третьей колонке я задал фиксированную ширину в 200 точек, чтобы правый баннер центровался по одной оси с главным меню.

Теперь надо добавить рекламу квадратных баннеров. Куда бы нам их... а давайте справа, под рубрики их вставим. То есть в главное меню. Вставим в виде inc-файла adv_right, который будет содержать вот такой простенький код:

<div align=center>
<? $my->out_rotate(15,21); ?>

<p><? $my->out_rotate(15,22); ?>
</div>

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

Файл adv_right.inc можно вставлять во все файлы в месте построения правой колонки, а можно подвязать к главному меню -- файлу main_menu.inc. Я пока остановлюсь на втором варианте, он меня устраивает. Вот, теперь у нас и квадратики RLE крутятся. Перешел в раздел "новости" и убедился, что все баннеры присутствуют и там. Значит все было сделано правильно.

Надо еще разместить "небоскреб". Ну, для этого формата принято отдавать правую колонку, за пределами контента. Как это сделать? Для этого нам надо в главную таблицу форматирования нашего сайта надо добавить еще одну колонку (<td>). Сейчас я попробую это сделать, но надо определиться -- мы сохраним динамическую ширину сайта или сделаем ее фиксированной?

Вообще, принято считать, что на текстовых сайтах надо делать колонки моноширокими, чтобы бестолковые пользователи, разворачивающие браузер на весь экран, не мучились при прочтении текстов. Я остановлюсь на фиксированной ширине, из расчета разрешения экрана 800x600 точек (пикселов). Математика такая. 800 -- ширина экрана, минус сорок точек на рамки браузера и другие отступы. Получаем 760 пикселов, к которым прибавляем 122 пиксела под высокий баннер и отступ от основного контента. Получается, что ширина обрамляющей таблицы у нас будет равна 882 пиксела.

Теперь к делу. Как нам добавить правую колонку для "небоскреба"? Можно добавить в файл top.inc и bottom.inc обрамление всего сайта еще одной зажимающей таблицей, например. Так и поступим!

<table width="882" cellspacing="0" cellpadding="0" bgcolor=#ffffff>
<tr>
<td valign=top>

Это начало таблицы, которое надо вставить в top.inc перед началом первой таблицы. А в bottom.inc надо добавить закрытие этот <td> и добавить второй, содержащий баннер. Не забудьте, что в обеих ячейках надо поставить выравнивание по потолку.

Получаем:

<table width="100%" cellspacing="0" cellpadding="3">
<tr>
    <td align=center bgcolor=#cccccc>
    copyright (c) 2001-2002 <a href=http://kurepin.ru>Руслан Курепин</a>
</td>
</tr>
</table>

</td>

<-- место для небоскреба -->
<td align=right valign=top>
<?
 $my->out_rotate(15, 91);
?>
</td>

</tr>
</table>

</body></html>

Вот так вот!

Спрашивается, почему я вставил вызов рекламного блока напрямую из bottom.inc, а не через дополнительный рекламный файл? Мысль такая, что этот фрагмент достаточно стабилен, это во-первых. А во-вторых, он и так находится в единственном файле, распространяясь на весь сайт. Так что, можно проигнорировать создание еще одного файла под рекламу. Но вам я рекомендую его таки сделать, чтобы сохранить структуру и не путаться в дальнейшем.

Кстати, можно было бы не создавать обрамляющую таблицу, а поступить гораздо разумнее. Знаете как? Даю подумать, потом переделаем.

Все, рекламой обложились с ног до головы! :) Завтра займемся счетчиками и подумаем над контентом сайта. Ибо, без контента наш сайт напоминает выставку рекламы.


^^^   37. Счетчики, куда дальше?

Времени не так уж много сегодня, выпуск будет коротким: быстренько добавляем наш ресурс в рейтинги Рунета.

Но предварительно мне бы хотелось обсудить одну небольшую тему. Чтобы наш проект выглядел пристойно, необходимо наполнить его осмысленным контентом. Как это сделать? Вернее спросить: где взять контент и что это будет за контент? Есть разные варианты, а я предлагаю создать базу технических текстов по web-программированию и созданию web-сайтов вообще. Но для этого надо будет запретить ковыряться в административной части сайта php.kurepin.ru всем желающим, что несколько не стыкуется с идеологией этого сайта. Он создавался именно для того, чтобы все желающие могли реально пощупать то, что так непонятно изложено в исходниках. Но и держать на сайте бардак мне уже не хочется. Как быть?

Пока вижу только один вариант: разделение прав на доступ и изменение всех данных для раздела /admin/. То есть, зарекомендовавшие себя люди будут заниматься администрированием сайта, а все остальные смогут как и прежде создавать и удалять всевозможные ненужности, которые модераторы будут ежедневно "подметать". Всем будет доступно создание и удаление материалов только в отведенных для этого разделах или разделах, которые они сами создали. Вот такая мысль. Если у вас есть более здравая -- предлагайте ее в форуме.

Теперь, что касается материалов. Я категорически против того, чтобы материалы бездумно и без разрешения таскались со страниц других ресурсов в Сети. Поэтому, я предлагаю нам всем поискать интересных авторов, пишущих на тему дизайна, программирования и т.п., и пригласить их участвовать своими текстами в нашем с вами проекте. Если вы уговорите автора опубликовать свои материалы на php.kurepin.ru -- мы обязательно это сделаем со всеми благодарностями и ссылками.

Можно еще освоить другой путь -- как поступают поисковые системы -- можно транслировать на нашем сайте страницу с другого ресурса в "прямом эфире". Это интересная тема с технической точки зрения, но совершенно ужасная в смысле оформления: надо будет размещать внутри нашего сайта сайт-исходник. А при наличии собственного дизайна -- это будет полный кошмар. Жду ваших предложений на этот счет и всевозможного участия по созданию "библиотеки веб-создателя".

Теперь возвращаемся к нашим молочно-мясным друзьям. Зарегистрируем свой сайт в трех рейтинговых системах:

1. Rambler
2. Top100
2. Hotlog

Сегодня почти все счетчики предлагают вариант установки кода из двух частей. Одна часть -- невидимая, для самого подсчета и вторая -- видимая -- дань уважения сервису рейтинги и/или отображения состояния рейтинга.

Первую часть мы будем помещать в файл top.inc, чтобы она загружалась прежде всего, не пропуская заходы пользователей.

А вторую часть разместим в отдельном файле counters.inc и подключим в главное меню под квадратными баннерами.

Думаю, что вам не стоит объяснять, как зарегистрироваться в том или ином ресурсе, правда? Если вы не в состоянии совладать с такими простыми вещами, то вам на этом курсе и делать нечего, так как дальше все будет сложнее и сложнее.

Смотрим на сайт: php.kurepin.ru, -- там уже установлены счетчики, вы их видите. Если посмотрите source страницы, то в заголовке увидите и невидимый их код.

Что же дальше? А дальше мы будем думать на форуме, у нас там для этого и трид есть специальный. Так и называется: "Куда дальше?". Предлагаю всем присоединиться к обсуждению этого крайне сложного вопроса и совместно определить дальнейшее направление развития сайта.


^^^   38. Голосование, защита от накруток

Ну что, уже заскучали без меня? Ничего, я вас не кину. Пока сами не умучаетесь читать "записки старого бездаря". Морочу я вам голову, а сам — дуб дубом. И чем старше становлюсь, тем больше в этом убеждаюсь. Ну да ладно... Надо поздравить дам со вчерашним праздником, который, как водится в нашей стране, наступил вполне ожиданно и, что тоже не удивительно, на печень.

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

А для тех дам, которые сами хотят быть программистами, я продолжаю свое занудное повествование о методах создания php-сайтов. На форуме есть трид "Куда дальше?", в котором мне предлагают различные темы для грядущих выпусков. Вот мне понравилась идея "про голосования". Об этом и поговорим в двух(?) грядущих выпусках.

Что же это такое -- голосование на сайте? В общем, мы чаще всего сталкиваемся с двумя видами голосования в Сети.

Первый -- голосование по вопросникам: когда нас что-то неожиданно спрашивают и требуют немедленно выбрать наиболее симпатичный вариант и нажать на кнопку.

Второй -- когда нам предлагается что-то оценить. Обычно по предлагаемой шкале оценок, иногда еще просят словами обосновать свою позицию.

Оба варианта сильно похожи "снаружи", т.е. для пользователя. Но сильно разняться для владельца сайта. Дело в том, что в Сетевых голосования существует масса проблем, связанных с так называемой "накруткой" результатов. Что такое "накрутка"? Боже мой, Киса, это же так просто...

Предположим, что вы решили разместить на Конкурсе Юмора свою нетленку. Разместили. Но тут вам, вдруг, захотелось продвинуть свое творение повыше в рейтинге. Т.е. накрутить рейтинг. Что вы для этого сделаете? Правильно: сначала вы проголосуете за себя сам, затем попросите проголосовать за вас ваших друзей. Если этого окажется мало, вы начнете изображать из себя разных людей и продолжать ставить оценки от "чужого имени". И если система вздумает с вами начать бороться, вы начнете искать всевозможные способы обхода "защиты от накруток": будете менять браузеры, компьютеры, бродя по офису или без конца перезванивая провайдеру, чтобы получить новый IP-адрес. Кто "хитрее" -- начинают подставлять в настройки браузера разные открытые proxy, чистить cookie-лист и так далее. Таки образом, вы накручиваете рейтинг своей любимой работе, выдвигая ее все выше и выше.

Дойдя до какого-то предела (обычно, столкнувшись с другими накрутчиками), вы обязательно поменяете тактику и предпримите все описанные способы обхода защиты от накруток, но уже для того, чтобы влупить отрицательные оценки вашим конкурентам. В общем, накрутка рейтинга -- великое и интересное занятие.

Но еще более интересное занятие -- ловить накрутки. Быть разведчиком -- интересно. Но быть контрразведчиком -- еще интереснее. При определенном складе ума, конечно.

Честно говоря, я уже целую псарню слопал на голосованиях и всевозможных рейтингах. Пожалуй, я могу без особенных проблем сделать систему голосования, которая почти на 100% была бы защищена от накруток. Например, можно было бы вставить в конце этого выпуска кнопку и попросить каждого из вас нажать на нее самым хитроумным способом, но прибавлять к голосу единичку только при первом вашем нажатии на нее. Ох, посмеялся бы я, глядя на ваши ухищрения в попытках обмануть эту кнопку.

Спросите, почему бы мне не реализовать свои знания на том же 21.ru? "А зачем?", -- отвечу я вам! Мне приятно, что авторы волнуются за свои тексты и рвутся на сайт со всех доступных компьютеров, чтобы поставить очередную "пятерку" себе и "пару" -- сопернику. Достаточно того, что я сам знаю настоящих лидеров и указываю на них в момент выдачи очередного приза. Ох, как в этот момент начинают бить себя в грудь накрутчики, доказывая, что на самом деле первые -- они.

Это мы подошли к первому и самому главному принципу "контрнакрутчика": никогда не показывай накрутчику, что ты его засек. Пусть думает, что он обхитрил систему. Это не даст ему повода придумать что-то более хитроумное и вы легко почистите базу от его мусора в любое удобное для вас время. Например, перед оглашением окончательных результатов опроса, когда уже ничего поправить (накрутить еще раз) будет нельзя.

Второе правило несколько сложнее в формулировании. Я его попробую не сформулировать, а описать.

Что у нас есть, какие средства, чтобы поймать накрутчика за хвост? Не много, на первый взгляд: IP-адрес проголосовавшего или пометка cookie, оставленная в его браузере во время первого голосования. Оба параметра очень легко поменять накрутчику: в первом случае достаточно перезвонить провайдеру или пройти через анонимный прокси, чтобы изменить IP-адрес, а во втором вообще достаточно просто удалить файл с cookie-записями. Так что же нам делать, как защититься?

Можно идти разными путями. Можно пытаться выдергивать из-за прокси-адреса реальный IP-адрес клиента, можно сравнивать подсети и регионы, можно... можно... можно... Но мой второй принцип борьбы с накрутчиками выражается не в технических изысках (которых, безусловно, тоже много), а в анализе окружающей ситуации, если можно так выразиться.

Другими словами, нужно проанализировать поведение обычного пользователя на вашем сайте, зашедшего в первый раз, и проанализировать поведение человека (возможно, что накрутчика), зашедшего на данную страницу не впервые. Найти между ними принципиальную разницу.

Это не всегда просто. Но если удается найти такое различие, то оно почти всегда дает возможность построить более-менее точный анализ накруток. Конечно, в сумме с другими видами анализа. Показываю на простом примере.

Хочу предупредить сразу, что на тему защиты от накруток я не беседую ни с кем, кроме коммерческих заказчиков, т.к. каждая метода стоит денег; крайне непросто найти оригинальные и нестандартный способы защиты от накруток.

Предположим, что я все-таки разместил в конце страницы голосовалку и решил защититься от накруток, как пошла бы моя мысль? Примерно так: "Обыкновенный пользователь отличается от повторного прежде всего тем, что читает содержимое страницы. То есть, тратит на это время. Значит, если посетитель потратил на страницу мало времени, значит ему не интересно содержимое и голосовать ему нет никакой необходимости -- это накрутчик, который по своей инициативе или по чьей-то просьбе зашел на сайт поставить оценку". И исходя из этой мысли я строю защиту:

1. Создаю таблицу, в которой незаметно регистрирую всех посетителей:
- IP-адрес посететеля
- xIP-адрес посетителя (это реальный IP, который показывают честные proxy-серверы)
- дату и время запроса страницы
- cookie-запись в виде уникального идентификатора посетителя (можно использовать порядковый номер записи в этой таблице)

2. Создаю таблицу голосования
- IP-адрес проголосовашего
- xIP
- дату и время
- cookie
- оценку

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

4. Пишу скрипт заполнения второй базы и размещаю его в новом файле, который вызывается при нажатии на кнопку голосования.

5. Пишу анализатор, который запускаю когда хочу и как хочу.

С первыми четырьмя пунктами все понятно. А как работает пятый? Анализатор работает разными способами: от анализа технических данных, до моих "уникальных" формулах анализа. Например.

Скрипт из пункта 3 должен создавать в базе 1 новую запись, брать ее уникальный ID и записывать его в браузер клиента, в cookie.

А скрипт номер четыре должен запрашивать значение cookie у клиента и класть в базу 2.

Таким образом, если в базе 2 есть номер cookie, то он обязательно должен присутствовать в базе 1. Если это не так, значит номер вымышленный и голос можно легко считать накрученным.

Если в базе 2 поле cookie пусто, это значит, что или голос накрученный, или у клиента должны быть отключены куки. Считать такой голос или нет? Это ваше дело. Я бы отметил его как подозрительный и обработал отдельно.

Далее, можно поискать оценки, поставленные из одной IP-подсети и посмотреть -- кому эта подсеть принадлежит. Если это модемный пул провайдера, то это однозначная накрутка. А если это подсеть какой-то неизвестной организации, то стоит отметить этот набор голосов как подозрительный и обработать другими анализаторами. И так далее, пока не кончится у вас фантазия по сравнению адресов, кук и любых других данных.

В заключение можно пустить в дело наш особенный алгоритм, придуманный для данного конкретного сайта и про работу которого никто, кроме вас, не подозревает. Этот алгоритм должен обработать все, что мы отметили как "подозрительные голоса".

В нашем случае, анализатор произведет несложные, но очень эффективные действия: сравнит время выставления оценки с временем начала просмотра страницы этим пользователем (пользователь должен определяться по cookie или по IP, если куки были пусты в обоих таблицах). Вычтя из времени проставления оценки время запроса сайта, мы получим время прочтения текста. Далее можно математически или мысленно прикинуть: мог ли пользователь за такое время прочесть содержимое и осознать прочитанное или он зашел просто поставить оценку? Это и будет той самой хитростью, которая нам поможет убедиться в том, что подозрительность к той или иной оценке не была не беспочвенной.

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

Можно составлять пути хождения пользователя по сайту, чтобы убедиться в адекватности поведения пользователя. И воспользоваться другими методами.

Главное, помните про первое правило "контрнакрутчика" и хорошо подумайте над вторым. Все стандартные и технические способы уже изучены и подробно описаны во всяких "руководствах".

Есть и принципиально другой способ защиты от балбесов и накрутчиков -- регистрация пользователя различной степени сложности или требование "обосновать оценку", который дает более точный анализ оценок, но сильно снижающий общее количество проголосовавших, поэтому я пока опущу эту тему.

Вот на этом я, пожалуй, закончу теоретическое повествование об отлове накрутчиков, а на следующем уроке мы рассмотрим реализацию простого голосования на сайте с выводом результатов в текстовой и графической формах.


^^^   39. Голосование (Продолжение-1)

Очень печально, но задача про ротэйтор рекламы пока не решена. Обсуждения на форуме идут, но как-то очень вяло, участвующим в беседе явно не хватает широты мысли. Но ничего, я подожду. Уж кому-кому, а мне торопиться некуда. Будем считать, что задача про rotator -- ваша задолженность. Вы еще ее не решили, а я уж придумал для вас новую.

Новая задача -- достаточно распространенная и имеет вагон и маленькую тележку решений. Но я вас призываю не бросаться в Сеть в поисках ее решения, а постараться, все-таки, найти свое решение задаче. Пусть и не лучшее, но свое. А задачу можно назвать: "Построение иерархического древа". Сформулируем задачу следующим образом, чтобы предметнее было ее обсуждать.

Дано:

1. Таблица с договорами (уникальный номер договора, название договора, номер родительского договора, дата договора, его приоритет, другие данные).

2. Каждый вновь создаваемый договор получает свой уникальны номер методом auto_increment в базе данных.

3. Если договор является самостоятельным, ячейке "номер родительского договора" присваивается значение 0. Если же создаваемый договор является дополнением к другому, уже существующему договору, данной ячейке присваивается уникальный номер родительского договора.

4. Любой договор может быть дополнением к другому договору и одновременно быть родительским договором для других дополнений.

5. Любой договор может иметь только одного родителя, но может иметь при этом любое количество дополнений (быть родителем для множества договоров).

6. Договор не может быть сам себе родителем или дополнением.

Требуется: Написать скрипт построения списка договоров в виде древовидной иерархической структуры. В качестве видимых элементов используем названия договоров, в скобках за названием указываем номер или название родительского договора.

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

Лично меня сильно интересуют два метода решения, которые я находил:

1. Рекурсивная работа с ассоциативным массивом

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

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

Вот такая задача. Еще раз напоминаю, что решения из учебников меня не интересует. Интересуют ваши личные варианты, причем, в виде исполняемого кода.

А мы переходим к написанию голосования для нашего сайта. Я подумал и решил, что нам не следует делать голосование "абы о чем", мы сделаем систему рейтинга статей, опубликованных на сайте. Как это будет выглядеть...

В конце последней страницы текста (а у нас будет когда-нибудь, автоматическое разбиение на страницы) будет размещаться форма со следующими полями:

1. Имя цензора
2. E-mail цензора
3. Выпадающее меню с оценками
4. Поле для ввода пояснения к оценке

Эта форма будет привязана к данному тексту по его идентификационному номеру. Голосующий будет ставить оценку и писать пояснение к оценке.

Наша задача при получении оценки и пояснения:

1. Проверить на корректность ввода имени и e-mail-адреса
2. Проверить на корректность оценку (чтобы не подсунули лишнего)
3. Проверить на наличие комментария/пояснения
4. Проверить на отсутствие запрещенных слов в комментарии
5. Привести комментарий к подходящему виду: удалить запрещенные html-тэги, удалить лишние пустые строки и пробелы.
6. Записать данные в базу (добавив дату, время и IP проголосовавшего)
7. Пересчитать и записать в базу рейтинг текста

Я специально взял такую форму голосования -- она наиболее популярна и многофункциональна. Ее можно много где использовать в таком же виде или отключив любое из полей ввода.

Что нам потребуется для решения этой задачи. Прежде всего, потребуется новая таблица в базу данных. Назовем ее tbl_php_voting:

create table tbl_php_voting
(
 v_id     int unsigned not null auto_increment primary key,
 v_text	  int not null,
 v_ip     varchar(15) not null,
 v_dt     datetime not null,
 v_vote   tinyint unsigned not null default 0,
 v_name   varchar(50) not null,
 v_email  varchar(50) not null,
 v_comm   text not null
);
По полям все понятно? Надеюсь, что все. Если вас смутили типы каких-то полей, обратитесь к оригинальному списку типов полей: http://www.mysql.com/doc/C/o/Column_types.html

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


^^^   40. Голосование (Продолжение-2)

Ну вот. Очередная суббота, очередной выпуск, очередная задача.

Надо сказать, что задачу из прошлого выпуска (про построение древа) блестяще решил Nick. Оба метода решения данной задачи можете найти на forum-е. А я обязательно включу одно из решений в наш проект, тем более, то оба они почти на 100% совпали с моими вариантами решений. Простое совпадение ли это? Одно из двух: или совпадение, или мы пришли к оптимальному решению задачи. С удовольствием увидел бы другие варианты!

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

Первые -- те, кто пишут мне электронные письма с просьбой помочь разобраться с каким-нибудь кодом, придумать для них какое-то решение задачи и так далее. Друзья мои, говорю еще раз, -- на все вопросы, связанные с компьютером и программированием я отвечаю ТОЛЬКО в форуме. Так что, задавайте ваши вопросы в форуме (http://forum.kurepin.ru), если желаете получить на них ответ.

Вторая группа -- люди, задающие вопросы, не до конца читая данный курс обучения или вообще его не читая. В результате -- они задают вопросы, ответы на которые в явном виде содержаться в выпусках "Пишем на PHP". Вот что с этими людьми делать я уже не знаю. Я просто начинаю игнорировать подобные вопросы, так как другого выхода не нахожу.

Теперь перейдем к очередной задаче, а уж после -- к продолжению создания голосования. Учите, я стараюсь задавать задачи практические, и удачные решения обязательно войдут в этот курс и наш строящийся сайт. Сегодня я хочу затронуть не очень сложную тему для задачи, но безусловно полезную.

Скажите, вам удобно читать большой текст, когда он загружается целиком на одну страницу? А если вам надо прерваться и продолжить чтение в другой раз?

В общем, задача состоит в том, что тексты надо делить на куски. Причем, делить надо автоматически, а не вручную, во время выкладывания на сайт. Почему? А потому, что никогда не известно, в каком виде и где этот текст может понадобиться. Поэтому, он должен лежать в оригинальном виде, а отдавать его пользователю посредством http, e-mail, ftp или еще как-то, это уже наша задача, которую мы сейчас и сформулируем.

Итак. Необходимо написать код, занимающийся разбиением выводимого текста на страницы по сколько-то килобайт в каждой. Количество килобайт -- величина переменная, содержащаяся в переменной $PAGE_TEXT_SIZE.

Дополнительное условие -- текст не должен разрываться посреди слова или предложения, если это не авторский перевод строки (стихи, или что-то подобное).

Ну и вывод списка всех страниц, конечно. Чтобы на каждой странице было видно -- сколько прочли, текущий номер и сколько еще осталось. Осилим?

И я, как это уже стало традицией, жду два варианта решения этой задачи:

1. Выделение из текста текущей страницы в оперативной памяти (более простой вариант, на мой взгляд)

2. Вариант, когда в оперативную память текст загружается кусками, не превышающими определенный размер. Например, не более $PAGE_TEXT_SIZE * 2.

Лучшая из реализаций данной задачи займет свое достойное место в первом же очередном выпуске "Пишем на PHP", как только будет создана. Ну вот. Ответы, как всегда, принимаю только в форуме, жду повышенной активности.

А теперь, к долгожданному голосованию. Что там я писал в прошлый раз? А, надо нарисовать форму для голосования. Что ж... рисуем... в файл /inc/voting_form.inc мы ее рисуем, да?

За основу я возьму форму из добавления текста. Чем-то они похожи. И вот, значит, какая у меня получилась форма:

<form action="<? echo $PHP_SELF; ?>" method="post" class="form_">
<div align=center class="form_text_title">Оставьте свое мнение о прочитанном
</div><br>
<input type="hidden" name="post" value="Y">
<input type="hidden" name="text" value="<? echo $my->out_text_id; ?>">
<input type="text" name="name" value="<? echo $name; ?>"  
class="form_text_urgent" style="width: 150pt;"> -- ваше имя<br>
<input type="text" name="email" value="<? echo $email; ?>"  
class="form_text_urgent" style="width: 150pt;"> -- ваш email

<p><select name="vote" class="form_drop_urgent">
<?
	foreach($my->VOTING_MENU as $k => $v)
        {
if($vote==$v) $selected="selected"; else $selected="";
echo "<option value=$v $selected>$k</option>\n";
        }
?>
</select>

<p>Поясните вашу оценку комментарием:<br>
<textarea name="comment" cols="60" rows="10" class="form_textarea_urgent" 
style="width: 250pt;">
<? echo $comment; ?>
</textarea>

<p><br>
<input type="submit" value="Добавить" class="form_submit_normal">

<p><font class="text_small">Предупреждение: все оскорбительные и не 
относящиеся к теме текста комментарии удаляются модератором сайта.</font><br>
</form>
Хорошенькая, правда?

Мы ее сохранили в файле /inc/voting_form.inc, как договаривались. Теперь давайте ее разберем. Не такая уж она простая, как может показаться на первый взгляд.

Во-первых, прошу обратить внимание, что регистрация мнения будет у нас реализовываться в этом же файле -- /text/index.php. То есть подтверждение формы приведет пользователя на эту же страницу, только с информацией: было его мнение засчитано, или не было (и по какой причине).

Если пользователь что-то не так заполнил в этой форме, он получит возможность изменить свою ошибку. Для этого в форме предусмотрен вывод данных, которые он ввел до того, как нажать кнопку подтверждения.

Вывод осуществляется обычным способом, через echo имени соответствующей переменной, кроме случая с оценкой. Тут вообще разговор особый.

Если мне не изменяет память, мы еще ни разу не пользовались замечательной функций цикла foreach. К сожалению, до четвертой версии этой функции в PHP не было. И очень это было неудобно. Смысл foreach состоит в том, что это функция цикла как и for, только количество повторений напрямую зависит от количества перебираемых значений. Это очень удобно, когда заранее не известно: сколько заполненных элементов в массиве, сколько файлов в директории и так далее.

В данном случае, мы не знаем, какое количество оценок предусмотрено нашей системой. Все оценки мы разместили в переменном ассоциативном массиве $VOTING_MENU, где каждая оценка состоит из словесного названия и соответствующей ему оценке числовой. Я пока сделал такой вариант (в этом же виде сия переменная добавлена в /req/vars.class:

// Оценки
var $VOTING_MENU=array("Отвратно"=>-5, "Никак"=>0, "Отлично"=>5);
Заодно вспомнили (а кто и впервые узнал) как инициируется ассоциативный массив со значениями.

Так вот, в форме есть небольшой фрагмент php-кода -- выпадающее меню оценок, которое выстраивается на лету, на основе переменной $VOTING_MENU с использованием цикла foreach.

foreach последовательно перебирает массив, передавая переменной $k (от key) ключ, т.е. оценку-слово, а переменной $v (от value) -- значение ячейки массива. Разумеется, вы тут можете использовать любые другие имена переменных, а от переменной "ключ" можно вообще отказаться, перебирая только значения массива.

Второй строкой цикла foreach мы сравниваем полученную переменную $v cо значением, которое, может быть, ввел пользователь ранее. И если совпадение найдено -- присваиваем переменной $selected слово "selected", которую, в свою очередь, подставляем в следующей строке в формирование очередной optinons. Обратите внимание, если пользователь только-только зашел на страницу и еще не голосовал, то значение по умолчанию в меню оценок будет стоять не на первой позиции списка, как это обычно происходит, если ключ "selected" отсутствует в списке, а на нулевом -- среднем для нас значении.

Почему так происходит? А потому, что несуществующая в данный момент переменная $vote при участии в конструкции if равна нулю. А ноль -- это одно из наших значений переменной $VOTING_MENU, за это значит, что условие выполнится и значение будет выбрано. Это удобно, когда ноль является серединным значением в списке оценок. Если надо отступить от этого правила, можно перед циклом добавить нечто вроде:

if(!isset($vote)) $vote=какое-то значение;

Но нам, в данном случае подобная проверка не требуется.

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

Но этим мы займемся на следующем уроке.


^^^   41. Голосование (окончание)

Ну что, что вы от меня еще хотите? Чего вы ждете? Две недели прошло, а вы все ждете, что я напишу продолжение голосовалки? Вернее, ее окончание? Да давно бы уже сами все написали, прислали, выложили на форум...

Кстати, у меня же теперь новый форум: forum.kurepin.ru! Посмотрите, я убежден, что вам понравится. Он работает гораздо быстрее своего предшественника и написан понятным PHP. А разработчик -- ita-studio -- уже выдал мне право вносить в форум свои изменения. Что я и начал уже делать. Надеюсь, этот форум надолго приживется на новом месте. А если получится еще и приличная версия под моей редакцией, то вааще... :)

Так вот. Сегодня мы напишем php-часть для добавления голосов посетителей в базу голосования, а потом подумаем, как это можно полезно использовать.

Но сначала... традиционная задача. Сегодня задачу представляет помощник курса -- Виталий Манаков.

Задача состоит в следующем.

Представьте, что у вас есть на сайте поиск, который должен различать слова и фразы. Ну, действительно, бывает такое, что надо найти текст, содержащий отдельные слова и определенные фразы. Сам часто пользуюсь такими запросами на своем любимом поисковике.

Кстати, позвольте отступить от темы и сообщить всем, кто еще не в курсе, гугель -- самый правильный поисковик картинок. Вам нужно изображение какого-то предмета или фото определенного человека? Нет ничего проще. Заходите на www.google.com (он вас сам перебросит на ближайшее к вам зеркало), жмете на "картинки", вводите "Трактор Беларусь" или "Руслан Курепин" и тут же получаете массу изображений, связанных с тем, что попросили. Мои примеры, конечно, -- не самые популярные запросы, но и с ними Гугель справится без всяких вопросов, уж поверьте.

Вы знаете, как строится поисковый запрос с фразами? Надеюсь, что знаете. Но если не знаете, что я вас научу этому, но по большому секрету: для того, чтобы в запросе, состоящем из цепочки слов, выделить фразу -- надо эту фразу заключить в кавычки.

Значит, задачу можно сформулировать так.

Пожалуйста, предложите наиболее простой способ получения из строки слов два списка или массива: массив слов и массив фраз.

Вот у этой задачи есть огромное количество решений. Причем, можно прибегать как к собственной изобретательности, так и к изучению мануала по PHP.

А теперь поворачиваемся ко всем задом, а к бараном -- передом, и давайте внесем в базу данных мнения посетителей сайта.

Если я ничего не забыл, то у нас на каждое мнение есть:

- ID мнения;
- ID оцененного текста;
- IP посетителя;
- email посетителя;
- время голосования;
- оценка числовая;
- оценка словесная (текст мнения);

Что ж... для добавления мнения нам понадобится всего одна функция, которую мы назовем out_vote_add() и добавим в класс out(). Что? Не самый подходящий класс? Ну что ж... может быть вы и правы. Но меня пока это место жительства устраивает. Можно было бы затрамбовать ее в in, но мне не хочется подключать весь in к файлу вывода текста. Что-то во мне бунтует против этого шага. А вы можете разместить эту функцию где вам будет угодно.

И все в этой функции до противного знакомо:

- Приводим в божеский вид данные;
- Производим проверку на ошибки;
- Добавляем в базу данные;
- Пишем "спасибо" пользователю;

На что более всего похоже? Правильно, похоже на процедуру добавления текста в базу. Было это у нас уже. Значит, можно даже взять что-то из уже написанного и просто поправить/дополнить.

Мой вам совет: не пренебрегайте уже написанным. Чем больше вы будете копировать собственный же код, тем быстрее у вас выработаются собственные стандарты написания кода. А это очеь полезно. Особенно, когда надо разобраться в своих же скриптах полугодовалой давности (в годовалых все равно не разберетесь, без бутылки).

И вот, как у меня это получилось.

В файл /WWW/text/index.php я добавил вызов процедуры добавления данных, если переменная post активна:

  if($post=="Y")
  {
   $err=$my->out_vote_add($text, $name, $email, $vote, $comment);
   if($err) 
   {
    $my->html_error=$my->err_to_html($err);
   }else
   {
    $my->html_error=$my->ok_to_html("Ваше мнение успешно добавлен в базу и 
       будет проверено модератором на корректность");
    unset($name, $email, $vote, $comment);
   }
  }

Обратите внимание! Чем отличается этот вызов процедуры от всех предыдущих, которые мы делали? Ага! Тем, что мы данные не присваиваем глобальным переменным, а передаем их классически -- через сам вызов.
 
Почему в этот раз так? А потому, что в этом нет никакой необходимости -- это во-первых, а во-вторых, вы не забывайте, что мне надо показать читателю все возможные синтаксические и логические обороты PHP, а уж читатель пусть сами выберет, что ему ближе и удобнее.

Ну а в файл out.inc, соответственно, добавим саму процедуру (мы тут уже люди все взрослые и опытные, поэтому я постараюсь писать код компактнее; чем дальше -- тем компактнее):

function out_vote_add($text, $name, $email, $vote, $comment)
 {
  $text=(int)$text;
  $name=substr(AddSlashes(strip_tags($name)),0.50);
  $email=substr(AddSlashes(strip_tags($email)),0,50);
  $vote=(int)$vote;
  if(!in_array($vote, $this->VOTING_MENU)) return(1); // ошибка в оценке или попытка взлома
  $comment=substr(AddSlashes(strip_tags(htmlspecialchars($comment))),0,50);  

  $this->sql_query="insert into tbl_php_voting(v_text, v_ip, v_dt, v_vote, v_name, v_email, v_comm) 
     values('$text','".$this->remote_ip()."',now(),'$vote','$name','$email','$comment')";
  $this->sql_execute();
  if($this->sql_err) return(11);

  return(0);
 }
Как видите, ничего необычного. Разве что, функция htmlspecialchars, которая возвращает переданную ей строку, заменив в ней специфические для WEB символы на их web-коды. Речь идет о треугольных кавычках и Ко. Достаточно бестолковая функция, т.е. далеко не полностью выполняющая возложенные на нее обязанности, но своей мы пока не написали, поэтому предлагаю обратить внимание на эту.

Вообще, честно говоря, эту функцию логичнее применять при выводе данных, а не при вводе их в базу, поэтому ее можно вообще выкинуть из данного скрипта. Но раз уж я ее написал -- пусть пока поживет, а дальше -- будет видно.

Еще вы можете впервые видеть функцию in_array(). В общем-то, как и предыдущая, эта функция объясняет свои действия собственным именем: "в_массиве". Функция проверяет присутствия значения в массиве. И если значение найдено, функция возвращает true, в противном случае -- false.

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

Надо только заметить, что данная функция, при поиске в массиве, может сроку, вроде "000123", распознать как числовую и дать положительный результат при сравнении со значениями типа "00123" или "123.00", будьте внимательны. Хотя, если я не ошибаюсь, в последних версиях PHP эту проблему можно обойти, воспользовавшись дополнительными ключами функции. Читайте чаще мануал, что тут еще скажешь.

Более пояснять нечего, вроде.

Совершенно очевидно, что данные из формы передаются в недра функции out_vote_add(), где приобретают нужную форму и заталкиваются в базу.

Можете добавлять комментарии к текстам, а потом посмотрим, насколько богата ваша фантазия.

Как-нибудь, мы их вытащим на белый свет. Может быть, даже на следующем занятии.

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

Нам теперь надо добавить в наши скрипты один важный SQL-запрос. Вот этот:

$this->sql_query="delete from tbl_php_voting where v_text='".$this->in_text_id."'";
$this->sql_execute();
if($this->sql_err) return(11);
Попробуйте догадаться с первого раза, в какую функцию я его добавил и зачем.


Веб-программирование на PHP (Часть V)