Наиболее примитивный редактор похож на phpmyadmin: он выводит на экран строки таблицы mysql с кнопками "Редактировать"; при нажатии на кнопку открывается html-форма, в которой можно поменять значения полей текущей строки, а потом нажать кнопку "Сохранить" – и всё запишется в БД.
В большинстве случаев такая схема работы пугает пользователя – в Редакторе информация выглядит совсем не так, как на сайте. Прямой доступ к mysql-таблицам чаще бывает нужен программисту. Для небольших сайтов разумнее не создавать "двойной доступ" (отдельно в Редакторе), а организовать редактирование "как есть": пользователь уже видит нужный фрагмент информации на публичной странице сайта – можно прямо к этому фрагменту присоединить кнопку "Редактировать" (которая будет, например, всплывать при наведении мыши и при щелчке вызывать форму редактирования, всплывающую поверх страницы). Вверху страницы можно добавить всплывающую кнопку "Добавить страницу", если логика сайта это позволяет. А более сложные операции, как показывает практика, всё равно пользователи стараются перекладывать на веб-студию, заказывая услугу "сопровождение сайта".
Правда, при такой концепции ("онлайн-редактирования" или "прямого редактирования") всё равно будут некоторые задачи, требующие отдельного интерфейса (например, загрузка файлов на сервер или изменение некоторых настроек, относящихся ко всему сайту). Ну, и в любом случае нужна будет форма авторизации пользователей.
Хотя "прямое редактирование" кажется более простым и естественным, оно часто преподносится в CMS как бонус, как нечто очень необычно удобное, дополнительное, а первичным и наиболее разработанным является "настоящий", "внутренний" редактор. Так происходит по той же причине, что и ошибки в HTTP-протоколе – из-за всё более сильно развивающейся специализации. Доступ к редактированию через публичную часть сайта требует хорошего знания javascript, а php-разработчики обычно им пренебрегают, считая, что со всеми задачами спокойно можно справиться с помощью прилепленной сверху jQuery. Но не с этой задачей, требующей чёткого взаимодействия серверных и клиентских скриптов (а также CSS).
Первое, что всегда требуется для редактирования сайта, – это система авторизации: понятно ведь, что мы не можем дать доступ к изменению и удалению материалов любому посетителю (хотя иногда так делают – например, в Википедии).
1. Наиболее распространённый способ отличить авторизованного пользователя – хранить информацию об авторизации в php-сессии. Сессию php надо открывать специальной командой. Наш код сейчас начнёт стремительно разрастаться в разные стороны, поэтому лучше сразу начать при открытии сессии проверять, не давали ли мы такую команду раньше (чтобы не вызвать ошибки Php):
if (!session_id()) session_start();
2. Для тестовой версии мы заведём только одного пользователя admin с паролем admin, и "зашьём" его в код:
<?php
$pw = _es($_POST, 'password');
if ($pw md5($_POST['user'] . $pw) == md5('adminadmin')) {
$_SESSION['user'] = $user;
header('Location: index4.php');
}
/**** функции *******/
function _es ($arr, $key, $def = null) {
return isset($arr[$key]) ? $arr[$key] : $def;
}
?>
md5 здесь как бы намекает нам, что реальный пароль надо хранить в зашифрованном виде (в нашем случае пока не имеет смысла).
3. Мы обращаемся к новой сущности кода – функции. До этого времени наш код был "плоским", команды шли сплошным потоком (иногда разветвляясь с помощью инструкций if-else). Какова причина создания нашей первой пользовательской функции (_es)? Фукнции нужны для удобства, для экономии усилий: один раз мы использовали конструкцию проверки параметра $_GET для установления ID страницы по умолчанию; сейчас нам второй раз понадобилось делать ровно то же самое – проверить параметр $_POST.
Это общее правило: если задача встречается в коде больше одного раза, эту задачу надо оформлять отдельной функцией. Кажется, функция _es очень мало упрощает работу, но это "мало" при многократном использовании начинает приносить пользу. Эта функция ещё и будет замедлять работу программы (тратится время на вызов дополнительной функции). Это общие проблемы "конструктивного" кода: мы усложняем, укрупняем блоки для удобства управления кодом (через новые абстракции) и платим за это производительностью.
4. После успешной авторизации мы возвращаем пользователя на Главную страницу сайта (сейчас это index4.php, но вообще будет что-то вроде "./").
5. Мы подготовились к приёму post-данных для авторизации, теперь надо организовать отправку этих данных через форму. Мы будем выводить эту форму на странице по знаку – если в параметрах
А всё, что получилось, вы можете посмотреть с помощью параметра
В нашем текущем файле ):
<?php //... $page = <<<PAGE <!DOCTYPE html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Авторизация</title> <link rel="stylesheet" href="site.css" type="text/css"> <script type="text/javascript" src="site.js"></script> </head> <body> <div class="page"> <div class="body"> <form class="auth" action="" method="post"> <p>Имя: <input name="user"></p> <p>Пароль: <input name="password" type="password"></p> <p><input type="submit" value="Отправить"></p> </form> </div> </body> PAGE; print $page; ?>
Сличив, однако, один из первых наших вариантов (листинг 1.6 ) с кодом 2.1 , мы увидим, что наконец настало удачное время выделить тот самый заветный "повторяющийся кусок кода" в отдельную функцию (которую можно даже назвать "шаблоном") и открыть тем самым следующий этап работы по созданию CMS. Вы видите, что этот кусок начинается со слова DOCTYPE и заканчивается примерно на "/head>". На самом деле он заканчивается ниже, но мы не будем пока разрывать элементы HTML (хотя бы чтобы не запутаться).
Есть ещё одна незадача: внутри повторяющегося кода HTML есть несовпадение: слово "Авторизация" вместо слова "Астрология". Незадачу решаем добавлением в новую функцию параметра:
<?php
//...
/**** функции *******/
//...
function _head($title) {
return <<<TPL
<!DOCTYPE html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>{$title}</title>
<link rel="stylesheet" href="site.css" type="text/css">
<script type="text/javascript" src="site.js"></script>
</head>
TPL;
}
Не забудем использовать новую функцию во фрагменте вывода основного шаблона страницы:
<?php
//...
$head = _head($row['title']);
$page = <<<PAGE
{$head}
<body>
<p><img src="files/{$row['img1']}" />{$row['html1']}</p>
<p><img src="files/{$row['img2']}" />{$row['html2']}</p>
</body>
PAGE;
print $page;
//...
Результат - в файле из предыдущей главы: http://nichtig.ru/index4.php?code
Вы ещё не устали постоянно вводить параметры вида index4.php?id=1, index4.php?edit в адресную строку? Давайте создадим небольшое меню. В режиме отладки (DEBUG is true) дописываем туда ?edit и прочие полезные любопытному человеку вещи. Пока у нас одна страница, меню в общем-то и не работает, просто показывает текущую страницу жирным ("мы здесь"). Если две страницы, уже интереснее. Ну, а поскольку сейчас режим DEBUG у нас включён, вы будете видеть в меню минимум 5 пунктов. Вот код функции:
<?php
function menu1() {
$menu = '';
$link = mysqli_connect("localhost", "admin", "adminadmin@", "unsinn");
$sql = 'set names utf8';
mysqli_query($link, $sql);
$sql = 'select `id`, `title` from `mypages`';
$result = mysqli_query($link, $sql);
while ($row = mysqli_fetch_assoc($result)) {
$menu .= menu1_item($row);
}
if (DEBUG) $menu .=
' • <a href="?code">PHP-код файла</a>'
. ' • <a href="?out">Выход</a>'
. ' • <a href="?edit">Авторизация</a>';
return $menu;
}
Вы можете видеть, как сильно возрастает сложность кода.
1. Начнём с более простого: у нас появилась константа DEBUG. Мы ввели её и инициализировали в начале файла index4-1.php вместо переменной $debug – потому что внутри функций такие ("глобальные") переменные не видны. Можно, конечно, заставить функцию увидеть глобальную переменную, но ведь это сделано в Php не случайно (что глобальные переменные не видны внутри функций), и мы пока вполне можем следовать этой заданной линии поведения (а не сразу начинать плыть против течения).
2. Далее, мы ввели ссылку с параметром ?out, чтобы дать возможность пользователю отменить текущую авторизацию (и, например, авторизоваться под другим именем). Это значит, мы должны описать в скрипте (в нашей программе, в файле index4-1.php), действия, которые надо делать при получении параметра out. Добавляем ещё один "else if" в нашей "секции" array_key_exists($key, $_GET) и указываем, что при получении параметра out надо выполнять функцию sessdestroy(). А внизу файла, где функции, опишем эту функцию (она просто уничтожает сессию и пересылает пользователя на основную страницу, без $_GET-параметров).
3. Меню генерирует ссылки. Каждая такая ссылка у нас будет создаваться отдельной функцией-шаблоном "menu1_item()". Сейчас это кажется лишним: почему бы не написать a href="?{$id}">{$title}</a> прямо внутри функции menu1(), зачем создавать для такой малости отдельную функцию? Мы сразу думаем о будущем Каталоге Товаров, где один пункт меню будет содержать и картинку, и заголовок Категории, и фрагмент описания... А главное, даже наши простые ссылки вовсе не так просты: мы проверяем (обязательно!), не совпадает ли адрес ссылки меню с адресом текущей страницы, и в случае совпадения вместо тэга "<a>" используем тэг "<b>":
<?php
function menu1_item($arr) {
extract($arr);
$href = '?id=' . $id;
$loc_id = _es($_GET, 'id', 1);
$nohref = $loc_id == $id ? true : false;
return !$nohref
? <<<TPL
<a href="{$href}">{$title}</a>
TPL
: <<<TPL
<b>{$title}</b>
TPL;
}
Потому что это дурной тон – заставлять посетителя щёлкать по ссылкам, ведущим на ту страницу, где он сейчас находится.
4. И последняя маленькая (trifle) проблемка: что вот это вот за ерунда, что за чепуха, что за unsinn – строка:
$link = mysqli_connect("localhost", "admin", "adminadmin@", "unsinn");
– внутри функции menu1()? Кажется, мы уже один раз в нашем файле писали что-то подобное. И, если это не дежавю, то это опять повторяющийся фрагмент, который надо выносить в функцию. Причём, не в одну. Поколдовав немного с логикой работы программы, мы достаточно быстро придём к выводу, что наступил случай организации нескольких функций в общий класс, потому что такая организация обойдётся здесь дешевле "обычных функций". ("Скидывать товар предполагалось на загородных рынках, но партия была такой большой, что темные люди, поколдовав над своими калькуляторами, решили проплатить телерекламу, чтобы ускорить оборот" – "Generation „П"").
Один из самых важных аргументов для помещения кода в более крупные блоки – это абстрагирование, то есть скрытие некоторого комплекса действий программы за одним общим именем. Это полезно и удобно. Такой пример. Долгое время в Php работали функции вида mysql_connect, затем Php объявил их устаревшими и стало можно использовать только новые функции, вида mysqli_connect, с другим синтаксисом (нельзя было слепо заменить в вашем коде строку "mysql" на строку "mysqli").
Если вы построили работу с БД, как в файле , то вам придётся лазать по всему коду и везде исправлять логику работы, синтаксис. Если же вы, как в следующей версии нашей программы ( ), будете использовать в коде функции своего собственного класса-обёртки (вида Mysql::select_table), то вам нужно будет изменить логику работы только в одном месте – в вашем классе Mysql: ведь вызовы рабочих функций во всём коде останутся теми же самыми "Mysql::select_table". Во многом именно ради этого всё и усложняется – чтобы при рефакторинге (посмотрите в словаре, что это) или при использовании программы на другом сайте можно было менять код только в одном месте.
Проследить логику работы класса проще "с конца". У нас две "рабочих" функции (select_row для извлечение одной строки из mysql-таблицы и select_table для извлечение нескольких строк), которые будут многократно вызываться из какого-то другого кода. Любая из этих функций сразу обращается к чему-то вроде конструктора – к "двойной звезде" состоящей из функции query, которая обращается к собственно конструктору – функции _init, которая устанавливает соединение с БД, кодировку соединения и текущую БД.
Функция query устроена сложнее. Mysql может возвращать данные, а может и не возвращать (например, при запросах вида update). Наша функция предусматривает оба варианта: если данные есть, второй элемент возвращаемого массива будет содержать число больше нуля, и "рабочие" функции могут приступать к передаче данных вышестоящему коду.
Внутри "конструктора" _init мы используем функцию query, которая сама вызывает _init. Есть опасность "зацикливания". Но функция _init вообще не начинает работу, если соединение уже было установлено (переменная self::$link не пуста), так что зацикливания не будет. Другое дело, что наш класс не может, например, сейчас соединяться с разными БД. Но, когда нам это понадобится, мы сделаем. Это важный принцип программирования: не буду думать об этом сегодня, буду думать о этом завтра. Иначе вы вообще ничего сделать не сможете (будете только "думать").
***
Обратите внимание: мы изменили архитектуру программы (добавили новый класс), от этого изменилась работа всей системы. Кое-что испортилось: сейчас при обращении к несуществующей записи в БД (например, index4_2.php?id=222) мы будем видеть не ответ 404, а рекомендации по настройке подключения к Mysql.
Так бывает всегда при серьёзных изменениях структуры кода - появляются новые (или старые) ошибки.
Мы не будем сейчас "латать дыры", потому что следующим шагом опять поменяем структуру, и там решим проблему правильных ответов сервера на более общем уровне.
Способы защиты сайта от всевозможных угроз муссируются на многочисленных интернет-форумах: sql-injection, js и css-хаки, XSS, shell'ы... Реализация наиболее серьёзных угроз связана со взломом хостинга, а не сайта, поэтому нет большого смысла о них беспокоиться. А у нас пока есть наша собственная нерешённая проблема. Выше мы обеспечили адекватный ответ сервера (404) для URL вида ?id=22 при несуществующей записи в БД. Но кто сказал, что только через параметр 'id' можно прислать на сервер ложные данные? Можно заставить поисковые системы (ПС) проиндексировать страницы нашего сайта, например, с параметром ?bd=22, или ?ed=22, или ?ebd=322 – все они будут выдавать одно и то же содержимое с http кодом "200 OK", но технически являться разными страницами.
Это вообще неправильно – давать http-ответы с кодом "200" в ответ на любые запрашиваемые параметры. Список "валидных" параметров должен быть извествен серверу, надо составить список всех допустимых параметров и при появлении незнакомого либо выдавать код "301 moved permanently" (и перенаправлять пользователя на "канонический" URL), либо уже знакомый нам "404 not found". Пока мы ещё вообще не решили, долго ли будет жить эта система с вопросиками в URL (или мы завтра перейдём на ЧПУ), используем второй вариант – с 404. Заодно упорядочим связь накопившихся параметров URL с функциями, выдающими в ответ какое-то содержимое сайта:
<?php
//...
function response() {
$valid = array(
'id' => 'simple',
'edit' => 'auth_form',
'out' => 'sessdestroy',
'code' => 'print_code',
);
$n = count($_GET);
if ($n > 0) {
$param = each($_GET); // самое простое: пропускаем только первый параметр
if ($n > 1 || !isset($valid[$param['key']])) {
_404();
}
else {
if ($param['key'] == 'id') {
Page::$data = get_data('mypages', _es($param, 'value', 1));
if (!Page::$data) {
_404();
}
}
Page::$valid[$param['key']]();
}
}
else {
check_auth(); // Запрос POST принимаем только без параметров GET
Page::$data = Page::default_();
Page::simple();
}
}
//...
?>
Упорядочивание принимаемых в запросе параметров привело к существенным изменениям. Мы добавили новый класс Page, в котором будут храниться все функции, выдающие разное содержание в http-ответе. Логика ответов сервера получилась уже довольно сложная:
GET возвращаем ответ 404;id пытаемся получить данные из таблицы mypages – строку с id равным значению параметра;pages получить не удаётся, возвращаем ответ 404;Page::simple(), предварительно установив данные для Page по умолчанию, то есть id = 1; если здесь данных нет, заполняем данные фиксированными значениями (content coming soon || настройте mysql);Существенные изменения в функциях:
edit (влекущем вызов функции Page::auth_form()) проверяем ещё и POST и авторизуем пользователя (если пароль подходит).Not found" теперь наш код может возвращать из нескольких мест, мы оформляем этот ответ отдельной функцией _404();get_data() для получения данных (одной строки) с параметрами "Таблица" и "Номер строки" – предполагаем, что таблиц у нас вскоре станет больше одной.Наш файл приобретает всё более приличные очертания, код готовится к разделению по файлам: мы вызываем в общем пространстве только одну функцию response(). Но не будем торопиться: следующий шаг всё разрушит, и нам опять придётся наводить порядок.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.