Безобразный user info php. Всеобъемлющая история шаблонов

Для начала мы усовершенствуем страничку регистрации, добавив возможность загружать аватар. Исходное изображение должно быть формата jpg, gif или png. Так же оно должно быть не более 2 Мб. Не беспокойтесь, после его сжатия скриптом, размер аватара будет около 3 кб и формат jpg. Откройте страницу reg. php и допишите в теге < form > строчку enctype="multipart/form-data" ,как в примере:


Регистрация










Теперь сохраняем reg.php

2.Затем необходимо создать еще одно поле в таблице users . Заходим в phpmyadmin , выбираем нужную базу и таблицу.


Выставляем все значения, как на рисунке:

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

3.Переходим к файлу save _ user . php и дописываем следующий код после удаления пробелов у логина и пароля:

//удаляем лишние пробелы
$login = trim($login);

// дописываем новое********************************************

//добавляем проверку на длину логина и пароля
if (strlen($login) < 3 or strlen($login) > 15) {
exit ("Логин должен состоять не менее чем из 3 символов и не более чем из 15.");
}
if (strlen($password) < 3 or strlen($password) > 15) {
exit ("Пароль должен состоять не менее чем из 3 символов и не более чем из 15.");
}

if (!empty($_POST["fupload"])) //проверяем, отправил ли пользователь изображение
{
$fupload=$_POST["fupload"]; $fupload = trim($fupload);
if ($fupload =="" or empty($fupload)) {
unset($fupload);// если переменная $fupload пуста, то удаляем ее
}
}
if (!isset($fupload) or empty($fupload) or $fupload =="")
{
//если переменной не существует (пользователь не отправил изображение),то присваиваем ему заранее приготовленную картинку с надписью "нет аватара"
$avatar = "avatars/net-avatara.jpg"; //можете нарисовать net-avatara.jpg или взять в исходниках
}
else
{
//иначе - загружаем изображение пользователя
$path_to_90_directory = "avatars/";//папка, куда будет загружаться начальная картинка и ее сжатая копия

If(preg_match("/[.](JPG)|(jpg)|(gif)|(GIF)|(png)|(PNG)$/",$_FILES["fupload"]["name"]))//проверка формата исходного изображения
{
$filename = $_FILES["fupload"]["name"];
$source = $_FILES["fupload"]["tmp_name"];
$target = $path_to_90_directory . $filename;
move_uploaded_file($source, $target);//загрузка оригинала в папку $path_to_90_directory
if(preg_match("/[.](GIF)|(gif)$/", $filename)) {
$im = imagecreatefromgif($path_to_90_directory.$filename) ; //если оригинал был в формате gif, то создаем изображение в этом же формате. Необходимо для последующего сжатия
}
if(preg_match("/[.](PNG)|(png)$/", $filename)) {
$im = imagecreatefrompng($path_to_90_directory.$filename) ;//если оригинал был в формате png, то создаем изображение в этом же формате. Необходимо для последующего сжатия
}

If(preg_match("/[.](JPG)|(jpg)|(jpeg)|(JPEG)$/", $filename)) {
$im = imagecreatefromjpeg($path_to_90_directory.$filename); //если оригинал был в формате jpg, то создаем изображение в этом же формате. Необходимо для последующего сжатия
}
//СОЗДАНИЕ КВАДРАТНОГО ИЗОБРАЖЕНИЯ И ЕГО ПОСЛЕДУЮЩЕЕ СЖАТИЕ ВЗЯТО С САЙТА www.codenet.ru
// Создание квадрата 90x90
// dest - результирующее изображение
// w - ширина изображения
// ratio - коэффициент пропорциональности
$w = 90; // квадратная 90x90. Можно поставить и другой размер.
// создаём исходное изображение на основе
// исходного файла и определяем его размеры
$w_src = imagesx($im); //вычисляем ширину
$h_src = imagesy($im); //вычисляем высоту изображения
// создаём пустую квадратную картинку
// важно именно truecolor!, иначе будем иметь 8-битный результат
$dest = imagecreatetruecolor($w,$w);
// вырезаем квадратную серединку по x, если фото горизонтальное
if ($w_src>$h_src)
imagecopyresampled($dest, $im, 0, 0,
round((max($w_src,$h_src)-min($w_src,$h_src))/2),
0, $w, $w, min($w_src,$h_src), min($w_src,$h_src));
// вырезаем квадратную верхушку по y,
// если фото вертикальное (хотя можно тоже серединку)
if ($w_src<$h_src)
imagecopyresampled($dest, $im, 0, 0, 0, 0, $w, $w,
min($w_src,$h_src), min($w_src,$h_src));
// квадратная картинка масштабируется без вырезок
if ($w_src==$h_src)
imagecopyresampled($dest, $im, 0, 0, 0, 0, $w, $w, $w_src, $w_src);
$date=time(); //вычисляем время в настоящий момент.
imagejpeg($dest, $path_to_90_directory.$date.".jpg");//сохраняем изображение формата jpg в нужную папку, именем будет текущее время. Сделано, чтобы у аватаров не было одинаковых имен.
//почему именно jpg? Он занимает очень мало места + уничтожается анимирование gif изображения, которое отвлекает пользователя. Не очень приятно читать его комментарий, когда краем глаза замечаешь какое-то движение.
$avatar = $path_to_90_directory.$date.".jpg";//заносим в переменную путь до аватара.
$delfull = $path_to_90_directory.$filename;
unlink ($delfull);//удаляем оригинал загруженного изображения, он нам больше не нужен. Задачей было - получить миниатюру.
}
else
{
//в случае несоответствия формата, выдаем соответствующее сообщение
exit ("Аватар должен быть в формате JPG,GIF или PNG");
}
//конец процесса загрузки и присвоения переменной $avatar адреса загруженной авы
}



// дописали новое********************************************
// Далее идет все из первой части статьи,но необходимо дописать изменение в запрос к базе.
//подключаемся к базе
// проверка на существование пользователя с таким же логином
$result = mysql_query("SELECT id FROM users WHERE login="$login"",$db);
if (!empty($myrow["id"])) {
exit ("Извините, введённый вами логин уже зарегистрирован. Введите другой логин.");
}
// если такого нет, то сохраняем данные
$result2 = mysql_query ("INSERT INTO users (login,password,avatar) VALUES("$login","$password","$avatar")");
// Проверяем, есть ли ошибки
if ($result2=="TRUE")
{
echo "Вы успешно зарегистрированы! Теперь вы можете зайти на сайт. Главная страница";
}
else {
echo "Ошибка! Вы не зарегистрированы.";
}
?>

4. Необходимо добавить одну таблицу в ту же базу. В ней будут хранится ip-адреса, которые допустили ошибки при входе. Таким образом мы сможем ограничить доступ тем, кто ошибся больше трёх раз подряд на минут 15. Думаю программам, подбирающим пароли, долго придется возиться.
Зайдем в phpmyadmin и создадим новую таблицу с 3-мя полями:


ip - ip-адрес.
date - дата неудачного входа за последние 15 минут у пользователя с данным ip. col - количество ошибок за последние 15 минут у пользователя с данным ip.
Отлично! Готово, теперь изменим файл проверки логина и пароля, ведь теперь у нас пароль зашифрован. Открываем testreg.php и удаляем все, что дальше удаления пробелов с логина и пароля. Далее добавляем следующий код:

//удаляем лишние пробелы
$login = trim($login);
$password = trim($password);

// заменяем новым********************************************
// подключаемся к базе
include ("bd.php");// файл bd.php должен быть в той же папке, что и все остальные, если это не так, то просто измените путь
// минипроверка на подбор паролей
$ip=getenv("HTTP_X_FORWARDED_FOR");
if (empty($ip) || $ip=="unknown") { $ip=getenv("REMOTE_ADDR"); }//извлекаем ip
mysql_query ("DELETE FROM oshibka WHERE UNIX_TIMESTAMP() - UNIX_TIMESTAMP(date) > 900");//удаляем ip-адреса ошибавшихся при входе пользователей через 15 минут.
$result = mysql_query("SELECT col FROM oshibka WHERE ip="$ip"",$db);// извлекаем из базы количество неудачных попыток входа за последние 15 у пользователя с данным ip
$myrow = mysql_fetch_array($result);
if ($myrow["col"] > 2) {
//если ошибок больше двух, т.е три, то выдаем сообщение.
exit("Вы набрали логин или пароль неверно 3 раз. Подождите 15 минут до следующей попытки.");
}
$password = md5($password);//шифруем пароль
$password = strrev($password);// для надежности добавим реверс
$password = $password."b3p6f";
//можно добавить несколько своих символов по вкусу, например, вписав "b3p6f". Если этот пароль будут взламывать методом подбора у себя на сервере этой же md5,то явно ничего хорошего не выйдет. Но советую ставить другие символы, можно в начале строки или в середине.
//При этом необходимо увеличить длину поля password в базе. Зашифрованный пароль может получится гораздо большего размера.

$result = mysql_query("SELECT * FROM users WHERE login="$login" AND password="$password"",$db); //извлекаем из базы все данные о пользователе с введенным логином и паролем
$myrow = mysql_fetch_array($result);
if (empty($myrow["id"]))
{
//если пользователя с введенным логином и паролем не существует
//Делаем запись о том, что данный ip не смог войти.
$select = mysql_query ("SELECT ip FROM oshibka WHERE ip="$ip"");
$tmp = mysql_fetch_row ($select);
if ($ip == $tmp) {//проверяем, есть ли пользователь в таблице "oshibka"
$result52 = mysql_query("SELECT col FROM oshibka WHERE ip="$ip"",$db);
$myrow52 = mysql_fetch_array($result52);
$col = $myrow52 + 1;//прибавляем еще одну попытку неудачного входа
mysql_query ("UPDATE oshibka SET col=$col,date=NOW() WHERE ip="$ip"");
}
else {
mysql_query ("INSERT INTO oshibka (ip,date,col) VALUES ("$ip",NOW(),"1")");
//если за последние 15 минут ошибок не было, то вставляем новую запись в таблицу "oshibka"
}

exit ("Извините, введённый вами логин или пароль неверный.");
}
else {
nbsp; //если пароли совпадают, то запускаем пользователю сессию! Можете его поздравить, он вошел!
$_SESSION["password"]=$myrow["password"];
$_SESSION["login"]=$myrow["login"];
$_SESSION["id"]=$myrow["id"];//эти данные очень часто используются, вот их и будет "носить с собой" вошедший пользователь

//Далее мы запоминаем данные в куки, для последующего входа.
//ВНИМАНИЕ!!! ДЕЛАЙТЕ ЭТО НА ВАШЕ УСМОТРЕНИЕ, ТАК КАК ДАННЫЕ ХРАНЯТСЯ В КУКАХ БЕЗ ШИФРОВКИ
if ($_POST["save"] == 1) {
//Если пользователь хочет, чтобы его данные сохранились для последующего входа, то сохраняем в куках его браузера
setcookie("login", $_POST["login"], time()+9999999);
setcookie("password", $_POST["password"], time()+9999999);
}}
echo "";//перенаправляем пользователя на главную страничку, там ему и сообщим об удачном входе
?>

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

// вся процедура работает на сессиях. Именно в ней хранятся данные пользователя, пока он находится на сайте. Очень важно запустить их в самом начале странички!!!
session_start();
include ("bd.php");// файл bd.php должен быть в той же папке, что и все остальные, если это не так, то просто измените путь
if (!empty($_SESSION["login"]) and !empty($_SESSION["password"]))
{
//если существует логин и пароль в сессиях, то проверяем их и извлекаем аватар
$login = $_SESSION["login"];
$password = $_SESSION["password"];
$result = mysql_query("SELECT id,avatar FROM users WHERE login="$login" AND password="$password"",$db);
$myrow = mysql_fetch_array($result);
//извлекаем нужные данные о пользователе
}
?>


Главная страница


Главная страница

if (!isset($myrow["avatar"]) or $myrow["avatar"]=="") {
//проверяем, не извлечены ли данные пользователя из базы. Если нет, то он не вошел, либо пароль в сессии неверный. Выводим окно для входа. Но мы не будем его выводить для вошедших, им оно уже не нужно.
print <<


HERE;

If (isset($_COOKIE["login"])) //есть ли переменная с логином в COOKIE. Должна быть, если пользователь при предыдущем входе нажал на чекбокс "Запомнить меня"
{
//если да, то вставляем в форму ее значение. При этом пользователю отображается, что его логин уже вписан в нужную графу
echo " value="".$_COOKIE["login"]."">";
}

print <<




HERE;

If (isset($_COOKIE["password"]))//есть ли переменная с паролем в COOKIE. Должна быть, если пользователь при предыдущем входе нажал на чекбокс "Запомнить меня"
{
//если да, то вставляем в форму ее значение. При этом пользователю отображается, что его пароль уже вписан в нужную графу
echo " value="".$_COOKIE["password"]."">";
}

Print <<



Запомнить меня.






Зарегистрироваться



Вы вошли на сайт, как гость

HERE;
}
else
{
//при удачном входе пользователю выдается все, что расположено ниже между звездочками.

print <<
Вы вошли на сайт, как $_SESSION (выход)


Эта ссылка доступна только зарегистрированным пользователям

Ваш аватар:




HERE;

//************************************************************************************
//при удачном входе пользователю выдается все, что расположено ВЫШЕ между звездочками.
}
?>

6. Необходимо сделать возможность выйти из аккаунта пользователям, которые вошли. На главной странице уже была ссылка на выход. Но этого файла пока не существует. Так создадим новый файл exit.php с кодом:

session_start();
if (empty($_SESSION["login"]) or empty($_SESSION["password"]))
{
//если не существует сессии с логином и паролем, значит на этот файл попал невошедший пользователь. Ему тут не место. Выдаем сообщение об ошибке, останавливаем скрипт
exit ("Доступ на эту страницу разрешен только зарегистрированным пользователям. Если вы зарегистрированы, то войдите на сайт под своим логином и паролем
Главная страница");
}

unset($_SESSION["password"]);
unset($_SESSION["login"]);
unset($_SESSION["id"]);// уничтожаем переменные в сессиях
exit("");
// отправляем пользователя на главную страницу.
?>

Ну вот и все! Пользуйтесь на здоровье! Удачи!

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

1. Истоки php

С чего, собственно начался php для большинства из нас? С вставок скрипта в HTML-страницы. К примеру, собственный каталог ссылок. Страница со ссылками из определённой рубрики:

". mysql_result($result, 0). ""; mysql_free_result($result); $result = mysql_query("SELECT id, title, ratio, description, site_date FROM site WHERE rubrika=". intval($id). " ORDER BY ratio DESC, site_date DESC, title"); if (mysql_error()) echo mysql_error(); else while ($row = mysql_fetch_assoc($result)) echo "{$row}
{$row}
Записано: {$row}, рейтинг: {$row}

Просто и незатейливо, но главное, что этот код работает. Сложности начинаются, если попробовать написать название рубрики в заголовке страницы. Для этого придётся создать вставку php внутри тега , перенести туда код, выполняющий первый запрос (выборка имени рубрики), сохранять результат запроса в переменную, которую затем выводить в теле страницы.</p> <p>Следующее осложнение - попытка работать с cookies и выдавать их в зависимости от того, какие рубрики посещает пользователь. Чтобы лучше представить это: пусть мы решили, что на наш хоумпейдж пойдут толпы посетителей, и для потенциальных рекламодателей мы делаем баннерный движок с таргетингом. Скажем, пользователь, посетивший рубрики рок-музыка и музыкальные инструменты, на следующей странице увидит баннер с рекламой магазина электрогитар.</p> <p>В таком случае нам придётся сделать вставку php-кода в самом начале файла и передвинуть туда проверку наличия рубрики, ведь куки можно выдавать только до выдачи первого байта документа.</p> <p><i>Конечно, вы скажете, что это можно легко сделать при помощи механизма пользовательских сессий, что проще с точки зрения выдачи контента и безопаснее, потому что пользователь может вручную менять содержимое кук. Да, это так, но во-первых здесь представляется видение новичка программирования, делающего свою домашнюю страничку, во-вторых речь идёт о далёком прошлом - виртуально мы с вами находимся в 1998 году. </i></p> <p>Не сильно облегчает жизнь и вывод одинаковых блоков HTML- и php-кода в подключаемые файлы. Описанная выше задача - построить простой каталог ссылок с рубриками - пожалуй, верхний предел возможностей данного подхода. При выполнении более сложных задач очень велик риск столкнуться с большими трудозатратами.</p> <p>2. Усложнение кода</p> <p>Логичным развитием предыдущего подхода является полный переход на php в том смысле, что все страницы представляют собой php-код - в начале файла стоит открывающий тег "<?php", и дальше php-код не прерывается. Весь вывод делается через echo (print). Такой код, вроде бы, выглядит логичнее. Пример: страница подписки на новые ссылки в каталоге. В форме предлагается выбрать, на какие рубрики подписывается пользователь.</p><p> <?php include "db-connect.inc"; include "login-check.inc"; if (!$logged_in) { header ("Location: /register.php"); exit(); } include "page-header.inc"; print("<h2> class="club_article_small_caption">Новости для клиентов</h2> <table cellpadding=\"4\" cellspacing=\"0\" border=\"0\">"); $result = mysql_query("SELECT id, title, announce, news_date FROM news ORDER BY news_date DESC LIMIT 10"); if (mysql_error()) print(mysql_error()); else while ($row = mysql_fetch_assoc($result)) print("<tr><td>{$row}"); if ($row["news_date"] > date("Y-m-d", time() - 3600*24*14)) print("<font color=\"#cc0000\">new!</font>"); print("</td><td> <a href=\"read.php?id={$row}\"><b>{$row}</b></a> <br>{$row}</td></tr>"); print("</table>"); include "page-footer.inc"; ?> </p><p> <?php ... else while ($row = mysql_fetch_assoc($result)) print_announce($row); ... ?> </p><p>Но это нисколько не упрощает работу, и час расплаты обязательно наступит: возникнет необходимость переделать дизайн сайта. Дизайнер править такое просто откажется. Только тот, кто написал такой код, сможет в нём разобраться и заменить старый HTML на новый. Если этот человек уже не работает над проектом, то проект придётся переписывать почти с нуля или же он умрёт, превратившись в памятник человеческой недальновидности.</p> <p>Другой вариант развития подхода смешанного HTML и php. У php есть такое свойство как advanced-escaping . Он позволяет внутри языковых конструкций выводить обычный HTML-код:</p><p> <?php include "db-connect.inc"; include "login-check.inc"; if (!$logged_in) { header ("Location: /register.php"); exit(); } include "page-header.inc"; ?><h2 class="club_article_small_caption">Новости для клиентов</h2> <table cellpadding="4" cellspacing="0" border="0"><?php $result = mysql_query("SELECT id, title, announce, news_date FROM news ORDER BY news_date DESC LIMIT 10"); if (mysql_error()) print(mysql_error()); else while ($row = mysql_fetch_assoc($result)) { ?><tr><td><?=$row["news_date"]?><?php if ($row["news_date"] > date("Y-m-d", time() - 3600*24*14)) { ?><font color="#cc0000">new!</font><?php } ?></td><td> "> <b><?=$row["title"]?></b> <br><?=$row["announce"]?></td></tr><?php } ?></table><p>Верстальщик или дизайнер, открыв этот файл в HomeSite, увидят HTML-разметку с подсветкой и вполне смогут редактировать оформление страниц. В простых сайтах такой метод вполне пригоден. В сложных - ни к чёрту не годится.</p> <p>3. Переворот</p> <p>С выходом PHP 3.0... Кстати, не мешало бы восстановить хронологию, раз уж речь идёт о всеобъемлющей истории. Итак, хронология, которую удалось восстановить при помощи архива листа рассылки php announce и страницы из веб-архива :</p><p>08.1997 PHP 3.0 Beta1 06.06.1998 PHP 3.0 Release 19.07.1999 PHP 4.0 Beta1 22.05.2000 PHP 4.0 Release 10.12.2001 PHP 4.1 Release 22.04.2002 PHP 4.2 Release 10.10.2002 PHP 4.3.0pre1 (pre1 - это не бета, но ещё не Release Candidate) </p><p>Так вот, с выходом PHP 3.0 появился инструментарий для применения нового подхода в php-программировании - буферизации страниц в скриптах. (Важно заметить, что это совсем не та буферизация, которая есть сейчас .)</p> <p>Со временем задачи построения сайтов усложнялись, php использовался всё шире, сайты становились всё "динамичнее". Чем больше сайт, тем проблематичнее сделать всё на подключаемых файлах в смешанном php и HTML. Вполне логично следующим шагом расширения php стала обработка HTML-страницы в памяти скрипта и выдача её пользователю целиком или по частям.</p> <p>Реализовать данных подход помогла худо-бедно реализованная в PHP3 поддержка (ну хорошо, эмуляция) объектно-ориентированного программирования. Были созданы <i>классы шаблонов </i>, которые позволяли легко оперировать шаблоном страницы и данными, которые в него должны быть внесены.</p> <p>Это расширяло возможности программ, но одновременно требовало больше системных ресурсов. Сегодня можно сказать, что выгоды от применения такого подхода перекрыли затраты системных ресурсов, однако ещё года два-три назад много людей считало, что держать весь документ в памяти - непозволительная роскошь для веб-программирования.</p> <p>Итак, образно говоря, произошёл переворот в технологии: если раньше PHP прятался в HTML-коде и робко добавлял свою информацию, то теперь он вышел на первое место и держит весь HTML в своих лапах.</p> <p>4. Чем плохи самопальные классы шаблонов</p> <p>Спустя полгода я решил кое-что изменить в планах. Морализаторства на тему плохости самопальных классов не будет. Они хороши. Пользуйтесь ими, и прибудет с вами то, что вам нужно! Только учитывайте, что вам будут мешать некоторые обстоятельства.</p> <p>Первое. Если класс писался специально, это значит, что он несколько оторван от жизни. Пусть за классом стоит большой опыт разработок, всё равно он делался в тепличных условиях. Получившаяся технология будет иметь свои специфические условия использования, под которые придётся подгонять будущие разработки, либо переделывать класс.</p> <p>Второе. Класс, который синтезируется из наработок в разных проектах, тоже требует изменений - чем глобальнее он, тем больше.</p> <p>Итак, свои собственные классы делать можно (классы шаблонов, насколько я знаю, очень многие используют свои собственные). Если вы идёте дедуктивным методом, строя класс из оценки того, что понадобится, есть небольшая вероятность, что получится что-то гениальное. И большая вероятность, что получится не совсем то, что нужно. Можно применить индукцию - тогда после большой и долгой работы вы гарантированно получите то, что работает, но не обязательно гениальное.</p> <p>Плохость самоделок, которую я упоминал в предыдущих частях, заключается в первом случае в очень большой вероятности получить что попало, работающее не так, как вам мечталось, требующее к себе особого подхода и много кодирования. Во втором случае вы можете долго упорно работать и получить то, что уже было кем-то сделано и опубликовано до вас.</p> <p>Что касается сравнения технологий, то главный вопрос - уместность. Очень простой сайт можно делать на подключаемых файлах и не думать о классах и движках. Сайт посложнее лучше делать на шаблонах. Если сложность возрастает и на одной странице многое зависит от разных условий - меню навигации в зависимости от статуса и т.д. - лучше подумать о связке XML&XSLT.</p> <p>Пример: форум phpBB. В нём нельзя поменять оформление полностью, как вам хочется, потому что очень много элементов оформления находятся не в шаблонах, а в php-коде. Всякие формы для входа, меню управления для вошедшего пользователя и т.п. - вставляются в страницу через php-код, а не как вложенный шаблон. Cамое популярное решение - не всегда самое лучшее.</p> <p>Дмитрий Котеров так комментирует опыт изучения phpBB: написав класс шаблона, многие, будучи полны энтузиазма, начинают пихать всё под этот класс. Идея такого класса состоит в том, чтобы разделить код и оформление, но авторы phpBB наоборот перемешали их насколько это было возможно.</p> <p>Но на мой взгляд, даже при нормальной реализации этого подхода (класса шаблона) код получился бы громоздкий, файлов с шаблонами было бы очень много. В последнее время прихожу к выводу, что на классах шаблонов в принципе нельзя толком отделить php-код от данных и оформления. Со сменой дизайна сайта на таком движке обязательно придётся копаться в php-скриптах, и делать это будет не дизайнер, а вы.</p><p> <table border> <tr> <td colspan=2> <b>__global__ </b> <p> (hidden and automatically added) </td> </tr> <tr> <td><b>block1 </b></td> <td> <table border> <tr> <td colspan=2><b>block2 </b></td> </tr> <tr> <td><b>inner1 </b></td> <td><b>inner2 </b></td> </tr> </table> </td> </tr> </table> </p><p>В документации написано, что нет необходимости указывать в шаблоне, какой блок является дочерним от какого. Класс это сам поймёт.</p> <p>В документации к этому классу написано, что он знает, что inner1 - дочерний для блока block2, и нет необходимости сообщать ему об этом. Чтобы вставить данные в блок, достаточно выполнить такой код столько раз, сколько нужно строк:</p><p> <?php $tpl->setVariable(...); $tpl->parseCurrentBlock(); ?> </p><p>Чтобы внести контент в block1, нужно выполнить такое:</p><p> <?php $tpl->setCurrentBlock("inner1"); $tpl->setVariable(...); $tpl->parseCurrentBlock(); $tpl->setVariable(...); $tpl->parseCurrentBlock(); $tpl->parse("block1"); ?> </p><p>В итоге код скрипта выглядит так:</p><p> <?php $tpl = new HTML_Template_IT(); // загрузка шаблона или указание его через класс $tpl->loadTemplatefile(string filename [, boolean removeUnknownVariables, boolean removeEmptyBlocks]) // установка переменных "global", т.е. переменных, не входящих в блок (дочерний блок) $tpl->setVariable(string variablename, mixed value); // ещё один способ указания переменных - через массив $tpl->setVariable(array (string varname => mixed value)); // Будем использовать какой-нибудь блок, пусть даже глубоко вложенный в другие $tpl->setCurrentBlock(string blockname); // Повторяем столько, сколько нужно $tpl->setVariable(array (string varname => mixed value)); $tpl->parseCurrentBlock(); // получаем результат или печатаем его вызывая $tpl->show() $tpl->get(); ?> </p><p>Код удобнее и понятнее, чем с FastTemplate.</p> <p>Но многие программисты всё равно пишут свои собственные классы шаблона. Более простые, но имеющие некоторые функции, которых нет в общедоступных классах. Я писал собственный класс, в котором сделал блоки, в которые автоматически вставлялись результаты SQL-запросов, в добавок имелись шапка и хвост блока (например, теги <table> и </table>), которые появлялись в результате только если в SQL-запросе были строки.</p> <p>В PHP3 появился модуль функций обработки XML. При помощи функций этого модуля можно создавать собственных обработчики XML-кода, но, однако, нельзя проверять правильность XML-документа.</p> <p><i>Небольшой экскурс в теорию о том, что такое XML и зачем он нужен. </i></p> <p><i>XML - это способ записи структурированных данных. Под "структурированными данными" обычно подразумевают такие вещи, как электронные таблицы, адресные книги, конфигурационные параметры, финансовые транзакции, технические чертежи и так далее. XML представляет собой набор правил (вы можете также считать их инструкциями или соглашениями) для разработки текстовых форматов, которые позволят вам структурировать ваши данные. </i></p> <p>Пояснение о том, что такое структура.</p><p> <eshop> <!-- категории товаров --> <category id="3"> <title>Мясные продукты Рыба килограмм 100 Мясо килограмм 200 Отходы производства Икра рыбья килограмм 10 Растительные продукты Подсолнух штука 50

Вот так в XML можно записать структурированные данные о продовольственном магазине, в котором товары разделены на категории, а категории могут быть подчинены друг другу. Конечно, эти данные можно записывать по-другому, скажем, текст с табуляторами в качестве разделителей и с указанием id родительской категории (кстати, при записи данных в формате XML, как видно выше, его указывать не нужно). XML - это уже готовый формат для записи такой структуры, в рамках которого вам остаётся лишь придумать названия узлов (тегов) и правила, в которые должна укладываться структура (например, что товар не может быть расположен вне категории или что уровней категорий может быть не больше трёх). Далее:

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

Разработчики php предлагают обрабатывать отдельные элементы XML-документа функциями php. Пример из руководства по php:

"B", "EMPHASIS" => "I", "LITERAL" => "TT"); // Функция для начала тега (может выводить сложный текст, но в данном примере выдаёт // просто соответствующий тег). function startElement($parser, $name, $attrs) { global $map_array; if ($htmltag = $map_array[$name]) { print "<$htmltag>"; } } // Функция для конца тега function endElement($parser, $name) { global $map_array; if ($htmltag = $map_array[$name]) { print ""; } } // Функция для текста (текстового узла) function characterData($parser, $data) { print $data; } $xml_parser = xml_parser_create(); // use case-folding so we are sure to find the tag in $map_array xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, true); xml_set_element_handler($xml_parser, "startElement", "endElement"); xml_set_character_data_handler($xml_parser, "characterData"); // Чтение XML-файла if (!($fp = fopen($file, "r"))) { die("could not open XML input"); } while ($data = fread($fp, 4096)) { if (!xml_parse($xml_parser, $data, feof($fp))) { die(sprintf("XML error: %s at line %d", xml_error_string(xml_get_error_code($xml_parser)), xml_get_current_line_number($xml_parser))); } } xml_parser_free($xml_parser); ?>

Данный пример можно расширить, добавив массив имён тегов, которые будут обрабатываться иным образом, и соответствующие условия в функции startElement и endElement.

Код выглядит просто ужасно. Для использования в преобразовании XML в HTML-страницы он практически непригоден, хотя кое-где применялся, например на сайте How IT works . Конечно, можно писать собственные автоматизаторы, которые читали бы конфигурационные файлы и создавали массивы, но по суммарным трудозатратам выгоднее использовать мощные классы шаблонов, такие как Smarty.

Работа над этим проектом началась ещё в 1999 году, когда группа разработчиков начала писать спецификацию движка шаблонов. Определив, что он должен делать, разработчики пытались написать модуль на C, но в конце концов решили, что лучше сделать класс на php, доступный и понятный всем разработчикам сайтов.

Сейчас Smarty - один из проектов PHP, его сайт находится на сервере PHP по адресу smarty.php.net .

Формально Smarty - класс шаблона. На самом же деле, его функциональность на порядок выше, чем у множества *Template.

Во-первых, Smarty производит не только вставку переменных в шаблон, но и выполняет php-код в самом шаблоне. Во-вторых, шаблоны в Smarty преобразуются в php-скрипты, и всю грязную работу по вставке переменных в текст и выполнению логических конструкций отдаёт встроенному в php парсеру. Эти php-скрипты при включённом кешировании сохраняются в файлы, которые вызываются при следующих обращениях к шаблонам. Когда шаблоны меняются, скрипты генерируются снова. Такое кеширование существенно ускоряет работу скриптов.

Также Smarty умеет обрабатывать встроенные в шаблоны логические конструкции if-else, преобразуя их в php-код. Аналогично обрабатываются конструкции, названные variable modifiers. Они позволяют убрать некоторые функции из основного скрипта, перенеся их в шаблон и их кешируемые php-скрипты.

{* Написать заголовок заглавными буквами *}

{$title|upper},

{* Сократить топик до 40 символов, поставив... в конце *} Topic: {$topic|truncate:40:"..."}

Этот текст преобразуется в такой код:

_run_mod_handler("upper", true, $this->_tpl_vars["Name"]); ?> Topic: _run_mod_handler("truncate", true, $this->_tpl_vars["Name"], 40, "..."); ?>

Доступен и набор встроенных переменных таких, как $smarty.now (текущее время). Идилическую картину дополняет вывод окна отладки, содержащего значения переменных, вызов вложенных шаблонов, поддержка настраиваемых фильтров ввода/вывода и плагинов.

Всё было бы прекрасно, будь Smarty меньше в размерах. Сейчас же (версия 2.3.1) "весит" 144 килобайта и на AMD K6 266 MHz работает ощутимо медленно. Разработчики Smarty рекомендуют использовать Zend Accelerator или PHP Accelerator .

DOM - на данный момент последнее слово в шаблонах документов в php. Я описывал модуль в статье «XML: спецификация и функции DOM в PHP».

Несмотря на многословность и большой объём кода, выполняющего достаточно простые операции, DOM XML имеет большие перспективы.

Главный недостаток текстового подхода в редактировании XML-документа (редактирования в скрипте, естественно) - то, что нужно либо внимательно вчитываться в спецификицию XML, либо не быть до конца быть уверенным в том, что же получилось в итоге.

Например, вставить текст в узел - простая задача. Однако в XML-документе служебные символы и символы, не входящие в таблицу ISO, должны быть преобразованы в XML-сущности (<) или в номера символов UTF-8 (&x0442;). В модуле DOM XML достаточно сконвертировать текст в UTF-8 и вызвать метод create_text_node.

На моём сайте пока что работает старый движок, генерирующий текстовую строку, и иногда он выкидывает интересные фокусы с не-well-form-ными документами. Кроме того, объекты можно передавать как параметр функции, а имя вызываемого метода делать переменным.

Наконец, главное преимущество DOM XML - то, что бинарный модуль для текстовой обработки документа в php отсутствует. Это значит, что вам самим придётся писать парсер.

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

Операции с документом через DOM нужно группировать в свои собственные функции, то есть строить DOM-овый класс шаблона.

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

document_element(); $child = $root1->child_nodes(); $root2 = $dom2->document_element(); for ($i = 0; $i < sizeof($child); $i++) $root2->append_child($child[$i]->clone_node()); ?>

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

Options FollowSymLinks RewriteEngine On # проверка наличия файла с тем же именем # и расширением phpxml для корня раздела RewriteCond %{DOCUMENT_ROOT}/$1/index.phpxml -f # если такой файл есть, правило срабатывает RewriteRule ^(+)/?$ /$1/index.php # это правило сработает для адреса, переписанного # по предыдущему правилу, и для всех остальных # файлов, отправляя запрос в скрипт composer.php. RewriteCond %{DOCUMENT_ROOT}/$1.phpxml -f RewriteRule ^(+)\.php$ /composer.php [L]

В скрипте composer.php будет открываться файл с тем же именем, что и запрашивался, но с расширением phpxml. Сервер Apache при обработке правил уже проверил факт существования этого файла.

6.1 Валидация документов

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

С базой было просто: получаешь массив и делаешь из него текстову строку (циклом или классом шаблона наподобие php-templates), либо получить строку при помощи sql2xml дерево объектов (или опять же строку). С файлом работать сложнее, потому что на всякий случай желательно проверять целостность данных.

Есть два подхода в создании разметки. Первый - разметка, ориентированная на данные (data-centric markup), второй - разметка, ориентированная на документ (document-centric markup).

Пример разметки ориентированной на данные:

ETCC FIA-organized racing series. Uses racing cars. BTCC British Touring Car Championship Analogue to ETCC, except that races take. http://btcc.co.uk DTM Deuche Tourenwagen Masters

Два уровня иерархии - строка + поле (естественно, если их станет 3 или 4, качественно ничего не изменится), в общем, то же самое, что и запрос из базы данных, отформатированный при помощи sql2xml. Пример документо-ориентированной разметки:

FIA organized about 20 racing servies, including 2 touring cars championships. European Touring Car Championship (ETCC) uses significantly tuned road cars, mostly BMW. Another FIA series is British Touring Car (BTCC) is analogue to ETCC, except that races take. Allthough, these are not the world"s most famous championships. DTM (Deuche Tourenwagen Masters), the most famous Touring racing series not only in Germany.

Этот вариант написан свободным человеческим языком, но данные в элементах acro и title остались те же, их можно получить из документа теми же запросами XPath или XQuery: /championships/championship/title Первый вариант разметки можно представить в виде таблицы базы данных. Можно усложнить его, для примера - фрагмент документа:

DTM Deuche Tourenwagen Masters Most famous German Touring racing series. Laurent Aiello Bernd Schneider Marcel Fassler Jean Alesi Cristian Albers

От этого документ не перестанет быть data-centric. Новый документ можно представить как 2 таблицы, соединяемые при запросе оператором LEFT JOIN.

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

Кстати, в этом заключается основное отличие XML-баз данных от не-XML. XML-database работает и с разметкой, ориентированной на документ (а не только в том, что результаты выдаются в XML). Разметку же типа data-centric вполне можно хранить в реляционной БД, а на выходе форматировать утилитой вроде sql2xml .

Так вот, возвращаясь к валидации документов, нужно заметить, что документо-ориентированную разметку очень сложно проверить на соответсвтие DTD или XML-схеме простым строковым парсером.

Ещё пример - разметка формы в моём классе TXF:

0"/>

Я проверяю это через DTD-схему. Проверка соответствия схеме документа - стандартная функция библиотеки DOM XML.

Для общеиспользуемых форматов обмена данными, например, RSS, есть спецификации и DTD-схемы (XML-схемы). Чтобы не забивать голову написанием собственного RSS-парсера, можно просто добавить в документ схему и проверить на допустимость. Затем доставать из документа нужные узлы через get_content или выражения XPath.

6.2 Лирическое отступление

Срочно завершать написание этой части серии меня заставила последняя заметка Spectator-a под названием «Шаблоны в PHP для чайников» .

Призывает забыть слова типа XML, XHTML «и прочий X...», попутно даёт заслуженного пинка Smarty, и, завершая свой ЭКСпромт, призывает умников бриться бритвой Оккама.

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

К чёрту бритву! К чёрту это дурацкое правило "не плодить сущности сверх необходимого". Это слишком избитая истина, чтобы её напоминать людям, а для того, чтобы оценивать других она вообще не годится. Количество "сверх необходимого" для каждого человека разное. Заказчик сайта может сказать: "А зачем мне ваши скрипты, сделайте в простом HTML, чтобы мы потом FrontPage-ом могли редактировать! Читали про бритву Оккама?"

Я долгое время считал, что основная причина, почему стоит использовать новые технологии в программировании - это эффект масштаба. Как мне написали в комментариях к старой статье, "с ростом объёмов сайта ты утонешь в своих шаблонах". И в своих статьях я упирал именно на это - мол, пожалуйста, балуйтесь со своим хитрым include и кодом, смешанным с HTML, оно поперёк горла встанет, когда надо будет сделать много программного кода.

На самом деле, есть ещё одна причина. Это навыки каждого конкретного человека. Кто хорошо знает XML, XSLT и XPath, либо умеет быстро находить решения задач в этой среде, тот в большем количестве случаев выполнит проект именно на XML-технологиях. Ему это сделать будет просто и легко, потому что он это хорошо умеет. Кто не знает или знает плохо - будет делать "простонародными" способами.

Остаётся лишь вопрос, будут ли его решения на XML-технологиях эффективнее, чем если бы он делал на классах шаблона или смешанном PHP&HTML? Да, будут. Приведу аллегорическое сравнение.

Вы видели, как играют в волейбол опытные игроки и "ламеры"? Команды "кульков" правдами и неправдами перекидывают мяч на другую сторону, чтобы соперник уронил мяч у себя. Нападающий удар они делать не умеют, и, руководствуясь пресловутой бритвой, играют просто. Опытный игрок намного лучше кулька умеет перекидывать мяч. Однако большего результата он добьётся, если выполнит хороший нападающий удар.




Есть еще вопросы или что-то непонятно - добро пожаловать на наш

Недомыслие — это когда от страсти становится не до мыслей.

Смешные афоризмы

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

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

И, повернувшись к сыну, он сказал: - Запомни этот урок. Никогда не руби дерево зимой. Никогда не принимай решения по недомыслию, в неподходящее время или когда находишься в плохом состоянии души. Жди. Будь терпеливым, плохие времена пройдут. Помни, что весна возвратится.

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

Недомыслие – союзник идиотства, бестолковости, дурости и безмозглости.

Парень, разыгрывая любознательность, спрашивает у симпатичной стюардессы: - Девушка, а что означает ТУ-154-2Б? – Сам что ли не можешь додуматься? Ну, ТУ - это значит, что самолет выпущен конструктором Туполевым, 150 - количество мест в салоне, а 4 - это сколько членов экипажа. - А 2Б? – Ну, видно недомыслие твой конёк! Это мы с Маринкой.

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

Недомыслие – отработанный способ доставить неудобства своему окружению.

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

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

Недомыслие приходит в ум гораздо раньше здравомыслия. Оно всегда сопровождается поспешными суждениями.

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

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

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

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

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

Муж вернулся из командировки. Дома никого. Решил спрятаться, чтобы сделать сюрприз жене. Вдруг видит, в квартиру входит жена с каким-то мужчиной. Заходят в спальню. Дверь закрывается, муж скорее к замочной скважине. И видит, как жена целуется с этим мужчиной, он снимает с нее всю одежду, бросаются оба на кровать, он снимает с себя одежду, а трусы бросает в сторону входной двери, где прячется муж, и они, повиснув на ручке, закрывают мужу замочную скважину и весь обзор. И тут муж с досадой думает: — Ну, вот зря день потерял, опять полная неопределенность!

Петр Ковалев

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

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

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

Давайте с самого начала обсудим архитектуру модульной системы на выбранной нами псевдо-системе.

Все модули представлены ввиде подключаемых к главному документу (индекс-файлу) вставок. Запрос модуля происходит из строки запроса QUERY_STRING, и название подключаемого модуля передаётся в качестве аргумента act. В некотором месте индекса файла происходит изъятие и обработка данного параметра. После, если у пользователя достаточно прав для доступа к модулю в контексте чтения, происходит проверка существования указанного в строке запроса модуля, и если таковой существует, то происходит его подключение к индекс файлу.

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

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

Значение do буду фиксированными, данная переменная будет принимать следующие значения:

  • main - главная часть модуля (доступно в контексте чтения)
  • config - раздел настройки модуля (доступно в контексте записи)
  • create - произвести некоторые действия, по добавлению информации в БД (доступно в контексте записи)
  • delete - доступ к разделу, предоставляющему возможности удалить некоторую информацию, в контексте данного модуля (доступно в контексте записи)
  • edit - доступ к редактированию информации в контексте модуля (доступно в контексте записи)

В целом, этот список можно увеличить, при этом всё зависит лишь только от масштабов проекта и его потребностей в функционале.

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

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

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

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

Что ж, давайте рассмотрим эту особую структуру. Она будет следующей: [ module_indefier: + \: + \;] *

То есть идёт список из пар: имя модуля ":" права чтения "," права записи ";". При этом данная метка обновляется в момент внесения изменений о правах доступа пользователя к системе. Если в системе появляется информация о модуле, который не вошёл в данную метку, то стоит просто произвести процедуру редактирования, и данные сохранятся автоматически.

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

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

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

Таблица `modules`:

CREATE TABLE `modules` (`id` bigint(20) NOT NULL auto_increment, `indefier` text collate utf8_unicode_ci NOT NULL, `title` text collate utf8_unicode_ci NOT NULL, PRIMARY KEY (`id`)) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Таблица `secure_groups`:

CREATE TABLE `secure_groups` (`id` bigint(20) NOT NULL auto_increment, `title` text collate utf8_unicode_ci NOT NULL, `perms` text collate utf8_unicode_ci NOT NULL, PRIMARY KEY (`id`)) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ;

Таблица `users`

CREATE TABLE `users` (`id` bigint(20) NOT NULL auto_increment, `login` text collate utf8_unicode_ci NOT NULL, `passwd` text collate utf8_unicode_ci NOT NULL, `groupId` int(1) NOT NULL default "0", PRIMARY KEY (`id`)) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ;

temp=array(); $this->temp["_result"]=0; $this->temp["_uid"]=explode("::",$_COOKIE["site_hash"]); $this->temp["_uid"]=$this->temp["_uid"]; $this->temp["_gid"]=$this->getUserSecurityAccess($this->temp["_uid"]); $this->temp["_conn_id"]=mysql_connect("host","user","passwd"); mysql_select_db("database"); $this->temp["_q1"]=mysql_query("SELECT perms" ."FROM `secure_groups`" ."WHERE id=".$this->temp["_gid"]); $this->temp["_access_stamp"]=mysql_fetch_assoc($this->temp["_q1"]); $this->temp["_access_stamp"]=$this->temp["_access_stamp"]["perms"]; $this->temp["_access_stamp"]=explode(";",$this->temp["_access_stamp"]); $this->temp["_access_stamp"]=array_slice($this->temp["_access_stamp"],0,-1); foreach($this->temp["_access_stamp"] as $this->temp["v"]){ $this->temp["_mod_access"]=explode(":",$this->temp["v"]); $this->temp["_mod_indefier"]=$this->temp["_mod_access"]; if($this->temp["_mod_indefier"]==$module){ $this->temp["_perms"]=explode(",",$this->temp["_mod_access"]); switch($act){ case "r": $this->temp["_result"]=($this->temp["_perms"]==1)? 1:0; break; case "w": $this->temp["_result"]=($this->temp["_perms"]==1)? 1:0; break; } break; } } mysql_close($conn_id); return $this->temp["_result"]; } } ?>

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

Функция secure::getUserId()

Используя данную функцию, мы подразумеваем, что во время авторизации пользователя в системе в переменной среде $_COOKIE была установлена переменная `site_hash`, состоящая из идентификатора пользователя в системе и хеша для проверки аутентичности его в системе. Функция просто изымает значение идентификатора, возращая его значение на выходе.

Функция secure::getUserSecurityAccess($id)

На выходе данная функция возвращает идентификатор профиля безопасности текущего пользователя в системе.

Функция secure::checkUserPermission($module,$act))

Производится запрос к БД, относительно прав пользователя на произведение действий чтения/записи в контексте переданного в качестве параметра модуля.

Осталось лишь описать процедуру формирования переменной в среде $_COOKIE, и тему статьи можно будет считать расскрытой.

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

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

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

  • `ulogin` - логин пользователя
  • `upasswd` - пароль пользователя
  • `stime` - время сессии, устанавливаемое пользователем (от 1 до 5 часов)
  • `auth` - имя кнопки отправки

Вот, в целом и всё. Осталось лишь пробовать, экспериментировать, ошибатся и находить решение, что я всецело и оставляю вам.

Надеюсь, что мы скоро встретимся, а для тех кто имеет ко мне вопрос в отношении статьи, да и не только - писать на [email protected], либо на [email protected].

С уважением Карпенко Кирилл, глава IT-отдела ИНПП.

Суперглобальный массив $_SERVER

Одним из важнейших предопределённых массивов является массив $_SERVER - в него PHP-интерпретатор помещает переменные, полученные от сервера. Без данных переменных сложно организовать полноценную поддержку Web-приложений. Ниже приводится описание наиболее важных элементов суперглобального массива $_SERVER.

Замечание

Просмотреть полный список элементов массива $_SERVER можно либо при помощи функции print_r(), которая распечатывает дамп массива либо при помощи функции phpinfo(), которая выводит информацию о PHP-интерпретаторе.

Элемент $_SERVER["DOCUMENT_ROOT"]

Элемент $_SERVER["DOCUMENT_ROOT"] содержит путь к корневой директории сервера, если скрипт выполняется в виртуальном хосте, в данном элементе указывается путь к корневой директории виртуального хоста. Т.е. в конфигурационном файле httpd.conf виртуальный хост имеет директиву DocumentRoot, которой присвоено значение "D:/main", элемент $_SERVER["DOCUMENT_ROOT"] будет содержать значение "D:main".

Элемент $_SERVER["HTTP_ACCEPT"]

В элементе $_SERVER["HTTP_ACCEPT"] описываются предпочтения клиента относительно типа документа. Содержимое этого элемента извлекается из HTTP-заголовка Accept, который присылает клиент серверу. Содержимое данного заголовка может выглядеть следующим образом

image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/msword, */*

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

Символ * используется для группирования типов в медиа-ряду. К примеру, символом */* задается использование всех типов, а обозначение type/* определяет использование всех подтипов выбранного типа type.

Замечание

Медиа-типы отделяются друг от друга запятыми.

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

Замечание

По умолчанию параметр q принимает значение 1. Кроме того, от медиа-типа он отделяется точкой с запятой.

Пример заголовка типа Accept:

Accept: audio/*; q=0.2, audio/basic

В данном заголовке первым идёт тип audio/* включающий в себя все музыкальные документы и характеризующийся коэффициентом предпочтения 0.2. Через запятую указан тип audio/basic, для которого коэффициент предпочтения не указан и принимает значение по умолчанию равное единице. Цитируя RFС2616 данный заголовок можно интерпретировать следующим образом: “Я предпочитаю тип audio/basic, но мне можно также слать документы любого другого audio-типа, если они будут доступны, после снижения коэффициента предпочтения более чем на 80 %”.

Пример может быть более сложным.

Accept: text/plain; q=0.5, text/html,
text/x-dvi; q=0.8, text/x-c

Замечание

Следует учитывать, что элемент $_SERVER["HTTP_ACCEPT"] содержит точно такую же информацию, но без начального заголовка Accept.

Этот заголовок интерпретируется следующим образом: Типы документов text/html и text/x-c являются предпочтительными, но если они недоступны, тогда клиент отсылающий данный запрос, предпочтёт text/x-dvi, а, если и его нет, то он может принять тип text/plain.

Элемент $_SERVER["HTTP_ACCEPT_LANGUAGE"]

В элементе $_SERVER["HTTP_ACCEPT_LANGUAGE"] описываются предпочтения клиента относительно языка. Данная информация извлекается из HTTP-заголовка Accept-Language, который присылает клиент серверу. Можно привести следующий пример:

Accept-Language: ru, en; q=0.7

Который можно интерпретировать следующим образом: клиент предпочитает русский язык, но в случае его отсутствия согласен принимать документы на английском. Элемент $_SERVER["HTTP_ACCEPT_LANGUAGE"] будет содержать точно такую же информацию, но без заголовка Accept-Language:

ru, en; q=0.7

Содержимое элемента $_SERVER["HTTP_ACCEPT_LANGUAGE"] можно использовать для определения национальной принадлежность посетителей. Однако результаты будут приблизительными, так как многие пользователи используют английские варианты браузеров, которые будут извещать сервер о том, что посетитель предпочитает лишь один язык - английский.

Элемент $_SERVER["HTTP_HOST"]

В элементе $_SERVER["HTTP_HOST"] содержится имя сервера, которое, как правило, совпадает с доменным именем сайта, расположенного на сервере. Как правило, имя, указанное в данном параметре совпадает с именем $_SERVER["SERVER_NAME"]. В параметре приводится лишь доменное имя без названия протокола (http://), т.е.

www.sofftime.ru

Элемент $_SERVER["HTTP_REFERER"]

В элементе $_SERVER["HTTP_REFERER"] приводится адрес страницы, с которой посетитель пришёл на данную страницу. Переход должен осуществляться по ссылке. Создадим две страницы index.php и page.php.

Страница index.php

echo "Ссылка на страницу PHP
"
;
echo .
$_SERVER [ "HTTP_REFERER" ]
?>

Страница page.php будет аналогичного содержания, но ссылка будет указывать на страницу index.php.

Страница page.php

echo "Ссылка на страницу PHP
"
;
echo "Содержимое $_SERVER["HTTP_REFERER"] - " .
$_SERVER [ "HTTP_REFERER" ]
?>

При переходе с одной страницы на другую, под ссылкой будет выводится адрес страницы, с которой был осуществлён переход.

Элемент $_SERVER["HTTP_USER_AGENT"]

Элемент $_SERVER["HTTP_USER_AGENT"] содержит информацию о типе и версии браузера и операционной системы посетителя.

Вот типичное содержание этой строки: "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)". Наличие подстроки "MSIE 6.0" говорит о том, что посетитель просматривает страницу при помощи Internet Explorer версии 6.0. Строка "Windows NT 5.1" сообщает, что в качестве операционной системы используется Windows XP.

Замечание

Для Windows 2000 элемент $_SERVER["HTTP_USER_AGENT"] выглядит следующим образом: "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)")", в то время как для Windows XP - "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)".

Если посетитель воспользуется браузером Opera, то содержание $_SERVER["HTTP_USER_AGENT"]может выглядеть следующим образом: "Mozilla/4.0 (compatible; MSIE 5.0; Windows 98) Opera 6.04 ". Подстрока "MSIE 6.0" здесь так же присутствует, сообщая, что браузер Opera является совместимым с браузером Internet Explorer и использует те же динамические библиотеки Windows. Поэтому, при анализе строки, возвращаемой браузером, следует иметь в виду, что к Internet Explorer относится строка, содержащая подстроку "MSIE 6.0" и не содержащая подстроки "Opera". Кроме того, из данной строки можно заключить, что пользователь использует операционную систему Windows 98.

Замечание

Пользовательский агент браузера Firefox может выглядеть следующим образом Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8) Gecko/20051111 Firefox/1.5.

При использовании браузера Netscape, содержание элемент $_SERVER["HTTP_USER_AGENT"] может выглядеть следующим образом: "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.4) Gecko/20030624 Netscape/7.1". Принадлежность к этому браузеру можно определить по наличию подстроки "Netscape". Кроме того, можно узнать, что посетитель выходит в Интернет, используя операционную версию Linux, с ядром, оптимизированным под Pentium IV, находясь в графической оболочке X-Window. Этот механизм удобно использовать для сбора статистической информации, которая позволяет дизайнерам оптимизировать страницы под наиболее распространенные браузеры.

Элемент $_SERVER["REMOTE_ADDR"]

В элемент $_SERVER["REMOTE_ADDR"] помещается IP-адрес клиента. При тестировании на локальной машине - этот адрес будет равен 127.0.0.1. Однако при тестировании в сети переменная вернёт IP-адрес клиента или последнего прокси-сервера через который клиент попал на сервер. Если клиент использует прокси-сервер узнать его IP-адрес можно при помощи переменной окружения HTTP_X_FORWARDED_FOR, значение которой можно получить при помощи функции getenv().

Замечание

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

Извлечение переменной окружения HTTP_X_FORWARDED_FOR

echo getenv (HTTP_X_FORWARDED_FOR );
?>

Элемент $_SERVER["SCRIPT_FILENAME"]

В элемент $_SERVER["SCRIPT_FILENAME"] помещается абсолютный путь к файлу от корня диска. Так, если сервер работает под управлением операционной системы Windows, то такой путь может выглядеть следующим образом "d:main estindex.php", т.е. путь указывается от диска, в UNIX-подобной операционной системы путь указывается от корневой директории /, например "/var/share/www/test/index.php".

Элемент $_SERVER["SERVER_NAME"]

В элемент $_SERVER["SERVER_NAME"] помещается имя сервера, как правило, совпадающее с доменным именем сайта, расположенного на нём. Например,

www.softtime.ru

Содержимое элемента $_SERVER["SERVER_NAME"] часто совпадает с содержимым элемента $_SERVER["HTTP_HOST"]. Помимо имени сервера суперглобальный массив $_SERVER позволяет выяснить ещё ряд параметров сервера, например IP-адрес сервера, прослушиваемый порт, какой Web-сервер установлен и версию HTTP протокола. Эта информация помещается в элементы $_SERVER["SERVER_ADDR"], $_SERVER["SERVER_PORT"], $_SERVER["SERVER_SOFTWARE"] и $_SERVER["SERVER_PROTOCOL"], соответственно. Ниже приводится пример с использованием данных элементов.

Использование элементов массива $_SERVER

echo "Имя сервера - " . $_SERVER [ "SERVER_NAME" ]. "
" ;
echo "IP-адрес сервера - " . $_SERVER [ "SERVER_ADDR" ]. "
" ;
echo "Порт сервера - " . $_SERVER [ "SERVER_PORT" ]. "
" ;
echo "Web-сервер - " . $_SERVER [ "SERVER_SOFTWARE" ]. "
" ;
echo "Версия HTTP-протокола - " . $_SERVER [ "SERVER_PROTOCOL" ]. "
" ;
?>

Элемент $_SERVER["REQUEST_METHOD"]

В элемент $_SERVER["REQUEST_METHOD"] помещается метод запроса, который применяется для вызова скрипта: GET или POST.