Пишем простой, расширяемый движок. Начало.
Решил поделиться своими знаниями :) CMS на основе этого примера Я написал в 2007 году. После этого она претерпела кучу изменений. Внимание. Мне лень разбираться, почему тут конвертируются кавычки. Так что не забудьте изменить кавычки на стандартные. Ну и да. Тут может быть много ошибок. Потому что пишу Я это почти по памяти и не проверяю (да-да, Я вот такой вот хитрый и злой). Да и код оформлен в списках, что не шибко может быть удобно злобным копипастерам! :) Комментарии приветствуются.
Задача: написать несложную систему управления сайтом, чтобы можно было быстренько дописать нужные модули и внедрить их в систему.
Средства: крутиться это всё будет под Apache‘м в связке с PHP5 (ибо будет использоваться шаблонизатор на основе XSLT) должны быть установлены модули MySQLi, DOM) и MySQL‘ем. Разрабатываю Я на Eclipse IDE с утсановленным плагином PDT. Я предполагаю, что те, кто это будут читать уже умеют поднять AMP на какой-либо платформе :) Если нет, то сначала подумайте – а надо ли оно вам? Если надо, Я как-нибудь распишу, как всё настроить :)
Что получим:
Это будет простенькая CMS, которая будет написана на PHP5 с использованием ООП. В качестве шаблонизатора мы будем использовать XSLT. Т.е. с помощью PHP подготавливаем XML’ку для последующей обработки XSL. В результате получаем HTML, который и засовываем в нужное место на страничке. Система будет работать используя mod_rewrite и поддерживать альясы страниц.
Создаём БД.
Нам потребуется пара табличек в БД. У всех таблиц будут префиксы, чтобы можно было избежать конфликта с другими похожими таблицами в базе.
Во-первых, надо будет где-то хранить обработчиков наших страниц. Обработчиками назовём те классы, который будут создавать нам страницу какого-то определённого типа. Т.е. новостную. Или карту сайта, к примеру. Можно это делать в одном файле, и его подключать. Недостаток этого в том, что каждый раз при добавлении нового обработчика надо будет закачивать обновлённый файл на сервер. При этом если другой программист добавит туда свой обработчик, а вы об этом знать не будете – вы его затрёте. И будет lulz :)
Во-вторых, надо будет где-то хранить сами страницы.
Итак, приступим. Создаём таблицу, где будем хранить обработчики и таблицу страниц:
CREATE TABLE `e_handlers` ( `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, // первичный ключ `classname` varchar(50) NOT NULL, // имя класса обработчика `classpath` varchar(255) NOT NULL, // имя файла класса `description` varchar(255) DEFAULT '', // описание, к примеру "простая страница" `enable` tinyint(4) DEFAULT 1 // доступен для создания страниц или нет ); CREATE TABLE `e_pages` ( `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, // первичный ключ `id_parent` int(11) DEFAULT '0', // родитель страницы. для вложенности `id_handler` int(11) NOT NULL, // ID обработчика страницы `is_mainpage` enum('0','1') DEFAULT '0', // если это главная страница `alias` varchar(255) DEFAULT NULL, // альяс страницы `title` varchar(255) DEFAULT NULL, // заголовок страницы `keywords` varchar(255) DEFAULT NULL, // ключевые слова `description` varchar(255) DEFAULT NULL, // описание страницы `body` text, // собственно текст страницы `in_menu` enum('0','1') DEFAULT '1', // показывать в меню сайта `title_menu` varchar(255) DEFAULT NULL, // заголовок для меню `in_map` enum('0','1') DEFAULT '1', // показывать в карте сайта );
После чего добавляем вручную (админки то у нас нет ;( ) два обработчика и две страницы для начала работы. Один обработчик – это типовые страницы (назовём их так). Ну или просто страницы, которых можно наплодить сколько угодно и назовём его Parent. Второй обработчик – это обработчик главной страницы, назовём его Mainpage. Ведь мы можем захотеть, чтобы главная страница у нас отличалась от всех остальных, правда? И соотстветственно две страницы – просто страница и главная страница. Вот собственно и две самые необходимые нам таблицы.
mysql> INSERT INTO `e_handlers` VALUES(0,'Parent', 'Parent.class.php','Типовая страница',1); --обработчик Parent mysql> INSERT INTO `e_handlers` VALUES(0,'Mainpage', 'Mainpage.class.php','Главная страница',1); --обработчик Mainpage mysql> INSERT INTO `e_pages` VALUES(0,0,2,1,'','Главная страница сайта','','','Текст главной страницы','1','На главную','1'); --создаём главную страницу mysql> INSERT INTO `e_pages` VALUES(0,0,2,0,'','страница сайта','','','Текст тестовой страницы страницы','1','Какая-то страничка','1'); --создаём тестовую страницу
Далее создаём структуру каталогов на диске
/ - корень проекта
/admin - административная часть
/class - папка с классами
/template - папка с шаблонами
Начинаем программирование :)
Создаём в корне файлы index.php, config.php, load.php, functions.php.
config.php:
- $const = “”; // доступные константы (будет позже)
- $domain = “http://localhost:2000/”; // домен, на котором крутится сайт
- $pfx = “e”; // префикс базы данных
- $charset = “UTF-8”; // кодировка для подстановки в html
- $db_charset = “UTF8”; // кодировка для подключения к БД
- $db_user = “user”; // имя пользователя БД
- $db_pass = “user”; // пароль
- $db_host = “localhost”; // хост БД
- $db_name = “engine”; // сама БД
- $db = new mysqli(&$db_host, &$db_user, &$db_pass, &$db_name);
- $db->query(“SET NAMES {$db_charset}”);
- // загружаем все классы
- $classes_query_str = “SELECT `classpath`,`classname` FROM `{$pfx}_handlers`”;
- $classes_query = $db->query($classes_query_str);
- while($classes = $classes_query->fetch_object()) {
- if(file_exists($_SERVER[‘DOCUMENT_ROOT’].’/class/’.$classes->classpath))
- require_once $_SERVER[‘DOCUMENT_ROOT’].’/class/’.$classes->classpath;
- else
- exit(“Class {$classes->classname} not found”);
- }
index.php:
- <?php
- require_once ‘load.php’;
- ?>
load.php:
- <?php
- session_start();
- require_once $_SERVER[‘DOCUMENT_ROOT’].’/config.php’; // конфиг
- require_once $_SERVER[‘DOCUMENT_ROOT’].’/functions.php’; // файл с общими функциями
- // подключаем классы
- $classes_query_str = “SELECT `classpath` FROM `{$pfx}_handlers`”;
- $classes_query = $db->query($classes_query_str);
- while($classes = $classes_query->fetch_object())
- require_once $_SERVER[‘DOCUMENT_ROOT’].’/’.$classes->classpath;
- // создаем перменные альясов, страницы, и id страницы
- isset($_GET[‘page’]) ? $page = clearVar($_GET[‘page’]) : $page = 1;
- isset($_GET[‘alias’]) ? $alias = clearVar($_GET[‘alias’]) : $alias = ”;
- isset($_GET[‘uid’]) ? $uid = clearVar($_GET[‘uid’]) : $uid = 0;
- $page = (int)$page;
- $uid = (int)$uid;
- //получаем информацию о странице
- $info_page = getPageInfo($alias,$uid,$page);
- // теперь вызываем класс
- if($info_page) {
- // ищем нужный обработчик
- $query_handler_str = “SELECT `classname` FROM `{$pfx}_handlers` WHERE `id` ={$info_page[‘id_handler’]}”;
- $query_handler = $db->query($query_handler_str);
- if($query_handler->num_rows < 1)
- exit(‘Error: no such handler’);
- else {
- $handler = $query_handler->fetch_object();
- $page = new $handler->classname($info_page);
- print $page->getContent();
- }
- }
В файл functions.php добавляем следующие функции:
- /**
- * Выводит информацию о переменной
- * @param $text информация для вывода
- * @param $exit прекращать работу после вывода
- */
- function vardump($text, $exit = false) {
- print ‘<pre>’;
- var_dump($text);
- print ‘</pre>’;
- if($exit)
- exit;
- }
- /**
- * Вырезает из переменной ненужные символы
- * @param $var переменная для очистки
- * @return чистая переменная
- */
- function clearVar($var) {
- return str_replace(‘”‘,””,str_replace(“‘”,””,stripcslashes(trim(htmlspecialchars($var)))));
- }
- /**
- * Получаем информацию по странице
- * @param $alias альяс для страницы в базе данных
- * @param $uid id страницы в базе данных
- * @param $page номер страницы в базе данных
- * @return массив со всеми значениями
- */
- function getPageInfo($alias,$uid,$page) {
- global $db,$pfx;
- // если установлен alias
- if($alias)
- $query_page_str = “SELECT * FROM `{$pfx}_pages` WHERE `alias` = ‘{$alias}’ AND `is_delete` = ‘0’”; // запрос по альясу
- else // иначе
- $query_page_str = “SELECT * FROM `{$pfx}_pages` WHERE `is_mainpage` = ‘1’”; // запрос главной страницы
- $query_page = $db->query(&$query_page_str);
- if($query_page->num_rows < 1) { // а если ничего нету
- if($uid) {
- $query_page_str = “SELECT * FROM `{$pfx}_pages` WHERE `id` = {$uid}”; // пытаемся найти страницу с таким id
- $query_page = $db->query(&$query_page_str);
- if($query_page->numRows < 1) {
- print ‘No pages found by UID ;(‘;
- return false;
- }
- }
- else {
- print ‘No pages found ;(‘;
- return false;
- }
- }
- $info = $query_page->fetch_object();
- ($uid == 0) ? $info->page_id = 0 : $info->page_id = &$uid;
- $info->page_alias = &$alias;
- $info->page_pagenum = &$page;
- return $info;
- }
Теперь в папке /class создаём два файла:
Parent.class.php:
- <?php
- class Parent {
- var $template_page = ‘template/Parent/page.php’;
- var $pageinfo = ”;
- var $template_menu = ‘template/Parent/menu.xsl’;
- /**
- * устанавливает данные о странице, которые будут доступны в классе
- */
- function __construct($info) {
- $this->pageinfo = &$info;
- }
- /**
- * Составляет страницу
- * @return unknown_type
- */
- function getContent() {
- global $domain;
- ob_start();
- $head = $this->getDoctype();
- $head .= $this->getTitle();
- $head .= $this->getMetaInf();
- $head .= $this->getIncludes();
- $menu = $this->getMenu();
- $body = $this->getBody();
- require_once $this->template_page;
- $page = ob_get_contents();
- $page .= $this->getLastTag();
- ob_clean();
- $page = str_replace(“~/”,$domain,$page);
- print $page;
- }
- /**
- * Возвращает DOCTYPE
- * @return XHTML1.0 DOCTYPE
- */
- function getDoctype() {
- return ‘<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1
- /DTD/xhtml1-transitional.dtd”>’.”n”;
- }
- /**
- * Возвращает заголовок страницы
- * @return заголовок страницы
- */
- function getTitle() {
- global $db,$pfx;
- $head = ‘<html xmlns=”http://www.w3.org/1999/xhtml” xml:lang=”en” lang=”ru”>’.”n”;
- $head .= “t<head>n”;
- $title_query_str = “SELECT `title`,`title_menu` FROM `{$pfx}_pages` WHERE `id` = {$this->pageinfo[‘id’]}”;
- $title_query = $db->query(&$title_query_str);
- $title = $title_query->fetch_object;
- isset($title->title) ? $title_str = $title->title : $title_str = $title->title_menu;
- $head .= “tt<title>{$title_str}</title>n”;
- return $head;
- }
- /**
- * Добавляет META информацию на страницу: кодировка, автор, генератор, ключевые слова, описание
- * @return META тэги
- */
- function getMetaInf() {
- global $pfx,$db,$charset;
- $query_meta_str = “SELECT `keywords`,`description` FROM `{$pfx}_pages` WHERE `id` =
- {$this->pageinfo[‘id’]} “;
- $query_meta = $db->query(&$query_meta_str);
- $meta = $query_meta->fetch_object();
- $kw = &$meta->keywords;
- $dsc = &$meta->description;
- $meta = “tt<meta name=”Author” content=”Maksim Voloshin” />n”;
- $meta .= “tt<meta name=”GENERATOR” content=”The best engine :) ” />n”;
- $meta .= “tt<meta name=”Author e-mail” content=”me@maksimvoloshin.ru” />n”;
- $meta .= “tt<meta http-equiv=”Content-Type” content=”text/html;charset={$charset}” />n”;
- $meta .= “tt<meta name=”keywords” content=”{$kw}” />n”;
- $meta .= “tt<meta name=”description” content=”{$dsc}” />n”;
- return $meta;
- }
- /**
- * Подключает CSS и javascript файлы
- * @return нужные тэги
- */
- function getIncludes() {
- $style = “tt<link rel=”Stylesheet” href=”~/template/style.css” type=”text/css” />n”;
- $style .= “tt<link rel=’favorite icon’ type=’image/gif’ href=’~/favicon.gif’ />n”;
- $js = “tt”.'<script type=”text/javascript” language=”JavaScript” src=”~/template/script.js”></script>’.”n”;
- $closehead = “t</head>nt<body>n”;
- return $style.$js.$closehead;
- }
- function getMenu() {
- global $pfx,$db;
- $query_menu_str = “SELECT `id`,`title_menu`,`title`,`alias` FROM `{$pfx}_pages` WHERE `in_menu` = ‘1’”;
- $query_menu = $db->query(&$query_menu_str);
- $doc = new DOMDocument(“1.0″,”utf-8”);
- $rootnode = $doc->createElement(“root”);
- $root = $doc->appendChild($rootnode);
- while($menu = $query_menu->fetch_object())) {
- $menuitem = $doc->createElement(“menu”);
- if($this->pageinfo[‘id’] == $menu->id)
- $menuitem->setAttribute(“active”,”active_menu”);
- else
- $menuitem->setAttribute(“active”,”nonactive_menu”);
- if(!empty($menu->alias))
- $menuitem->setAttribute(“link”,”~/”.$menu->alias.”/”);
- else
- $menuitem->setAttribute(“link”,”~/page/”.$menu->id.”/”);
- if(!empty($menu->title_menu))
- $menuitem->appendChild($doc->createTextNode($menu->title_menu));
- else
- $menuitem->appendChild($doc->createTextNode($menu->title));
- $root->appendChild($menuitem);
- }
- $xsl = new DOMDocument;
- $xsl->load($this->template_menu);
- $xslt = new XSLTProcessor;
- $xslt->importStylesheet($xsl);
- return $xslt->transformToXML($doc);
- }
- /**
- * Переопредлять в новых классах
- * @return
- */
- function getBody() {
- global $db,$pfx;
- $query_body = $db->query(“SELECT `body` FROM `{$pfx}_pages` WHERE `id` = {$this->pageinfo[‘id’]}”);
- $body_arr = $query_body->fetch_object();
- return $body_arr->body;
- }
- }
Теперь создаём в папке /template папку Parent, в ней создаём файлы page.php и menu.xsl. Первый будет шаблоном страницы, второй будет шаблоном меню.
page.php:
- <?php echo $head; ?>
- <?php echo $menu; ?>
- <div>
- <?php echo $body; ?>
- </div>
menu.xsl:
- <?xml version=”1.0″ encoding=”UTF-8″?>
- <xsl:stylesheet version=”1.0″ xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”>
- <xsl:output method=”xml” omit-xml-declaration=”yes” />
- <xsl:template match=”/root/menu”>
- <div>
- <a href=”{@link}” class=”{@active}”>
- <xsl:value-of select=”text()” />
- </a>
- </div>
- </xsl:template>
- </xsl:stylesheet>
Так как у нас в системе зарегистрирован класс для главной страницы, просто определяем его. Создаём в папке /class файл Mainpage.class.php:
- <?php
- class Mainpage extends Parent {
- }
- ?>
Работать над ним мы будем как-нибудь потом. В этом классе, к примеру, можно переопределить шаблон старницы $template_page и в результате будет использоваться он, а не тот, который задан в Parent.
Осталось определить правила rewrite’а. Для этого в директиве DocumentRoot апачи должно стоять AllowOverride All. Создаём файл .htaccess в корне нашего сайта со следующим содержимым:
- RewriteEngine On
- #страницы
- RewriteRule ^(.*)/(.*)/$ index.php?alias=$1&uid=$2 [QSA,L]
- RewriteRule ^(.*)/$ index.php?alias=$1 [QSA,L]
QSA – позволит нам добавлять к этому правилу GET переменные из запроса. Т.е. /page/1/?a=1 превратится в index.php?alias=page&uid=1&a=1.
L – показывает что это последнее правило, и других применять больше не нужно.
И да. Если сайт лежит не в корне – то надо сделать RewriteBase /dirname или добавлять dirname в правила.
Ещё можно создать в папке /template файлы style.css (и определить там active_menu и nonactive_menu) и script.js – а то firebug ругаться будет (у вас ведь стоит firebug, а то как же вы верстаете? :) )
Для чего Я сделал функцию vardump – смотри тут
Ну-с. Момент истины. Заходим на http://localhost и видим либо меню из двух пунктов и текст: “Текст главной страницы”, либо кучу ошибок, которые можно постить тут :)
P.S. Всем доброго утра =D
Движок не работает. Куча ошибок и предупреждений.
https://prnt.sc/ikcg1j
https://prnt.sc/ikcgaa
https://cloud.mail.ru/public/7hjx/gYYs4QUM7
Можешь подправить всё, чтобы заработало и отправить архивчик на мыло?
Может быть что-нибудь ещё сделать?) проект там запилить, или ещё что-то)))
Этому движку уже чёрт знает сколько лет.
Понятно все с вами. Ламеры!
Лол :) ещё вопрос кто тут ламер ) человек, который не может поправить пару ошибок или человек, который написал этот движок 7 лет назад и 5 лет не занимается РНР?)
Сначала сам научись PHP потом умничай тут. Правильно тебя обозвали ламером.
Автор кода школьник,чего вы от него хотите?
лол, статья написана в 2011 году, а если вы к 2020 не смогли выучить РНР, то это ваши проблемы )))