Практикум по разработке системы управления контентом (CMS)

Серверные технологии

Показывать лекцию целиком

Фотогалерея - это просто

Php: выводим список картинок-миниатюр, у которых в атрибутах src указываем путь к маленькому изображению (такие изображения-миниатюры хранятся в папке files/th/0/). Каждая картинка (html элемент IMG) обёрнута в ссылку – html-элемент A с атрибутом href="path_to_bigPhoto" (путь к большому изображению, которое хранится в папке files/0/). Весь список помещаем в контейнер div (например) с уникальным идентификатором, содержащим наименование функции javascript. Функция php, вычисляющая уникальность идентификатора для каждого такого "виджета", копит эти идентификаторы в массиве Page::$config['markers'], а потом в конце страницы мы пишем javascript, который, во-первых, перечисляет массив всех "видждетов", потом по очереди вызывает функции, собранные в этом массиве (Catalog, Form, YMap...):

dict.markers = {"catalog":[{"num_id":1,"subj":"catalog","real":false}]}
initMarkers();

Все javascript-обработчики для Каталога, Формы или других задач хранятся в объектах и пишутся в javascript-коде с большой буквы; на сервере же все они пишутся с маленькой буквы и список в результате генерируется почему-то тоже с маленьких букв. Поэтому функция последовательного вызова обработчиков (initMarkers) должна преобразовывать наименования вызываемых объектов с помощью пользовательской функции capitalize.

Javascript: функция инициализации Каталога init() ищет в переданном по идентификатору элементе div картинки с условной меткой (в нашем случае это class="thumb"). Картинки заносятся в список. Этот список может служить основой для всяческих "лент" и "слайдеров". Весь код занимает 90 строк:

var Catalog = {
	img_list: [],
	micro_list: [],
	kolhoz: true,
	tN: 'img',
	cN: 'thumb',
	init: function (id, subj) {
		var box = gi(id);
		if (box) {
			this.img_index = 0
			box.img_list = [];
			box.Big = this.getBig(box); //для Большой Картинки
			//this.box.micro_list = []; //будущая микронавигация
			//box.micro = ce('div');
			//cc(box.micro, 'micro')
			box.img_index = 0;
			setE(box, this.tN, {className:this.cN}, 
			  {onclick:Catalog.showBig, box:box}, 
			  'inList', 'Catalog')
			this.addLister(box.Big)
		}
	},
	
	inList: function (el){
		Catalog.img_list[Catalog.img_index] = el 
		  //Для слияния разных контейнеров с фото
		el.box.img_list[el.box.img_index] = el
		el.c_num = Catalog.img_index
		//el.micer = el.box.micro.insertBefore(ce('span'), 
    el.box.micro.firstChild)
		Catalog.img_index += 1
		el.box.img_index += 1
		Catalog.addEditLink(el);
	},
	
	addEditLink: function (el){
		el.item = findParent(el, 'div', 'item');
		var carr = el.className.split(/\s/);
		if (Editor.editable) {
			ac(buildEl2('span', {sql:['_edit', 'file', carr[1]], className:'_edit2', 
	onclick:Editor.preBuildForm, el:el.parentNode}, 'Редактировать file ' + carr[1]), el.item)
		
			cc(el.item, '_edit')
		}
	},
	
	addLister: function (Big){
		var pre = {prev:'prevI', next:'nextI'};
		if (!Big.prev) {
			for (var id in pre) {
 Big[id] = ac(buildEl2('div', {className:pre[id], onclick: Catalog.showNext}), Big);
			}
		}
	},
	
	getBig: function (box){
		return (box.Big) 
			? box.Big
			: ac(buildEl2('div', {className:'bigImgBox disnone'}));
	},
	
	getBigI: function (Big){
		if (!Big.BigI) {
			var tC = ac(buildEl2('div', {className:'tCell'}), Big);
			Big.BigI = ac(buildEl2('img', {className:'bigImg'}), tC);
		}
		return Big.BigI;
	},
	
	showBig: function (e, o){
		prevent(e);
		o = o || this; //img (thumb)
		var Big = o.box.Big;
		Catalog.getBigI(Big).src = o.parentNode.href;
		Hider.show(Big);
		Big.prev.img = o;
		Big.next.img = o;
		window.scrollTo(0,0)
	},
	
	showNext: function (e, o){
		o = o || this
		var img = o.img, idx = img.c_num, img2, box = img.box, img_list = box.img_list;
		idx = (hc(o.className, 'nextI')) ? idx – 1 : idx + 1
		img2 = img_list[idx]
		//cc(img.micer, null, 'curr') //будущая микронавигация
		//cc(img2.micer, 'curr')
		if (img2) {
			Catalog.showBig(null, img2)
			box.Big.prev.img = img2
			box.Big.next.img = img2
		}
	}
}

При щелчке по картинке создаётся (или используется раньше созданный) DIV 800 х 600 пикселов (можно другой размер), в него помещается элемент IMG с src, взятым из атрибута href ссылки, в которую обёрнута каждая миниатюра.

Картинок много, и хотелось бы их листать, "не отходя от витрины". Добавляем к контейнеру большого изображения стрелки вправо и влево (next, prev). К стрелкам присоединяем javascript-функцию showNext, которая ищет в списке картинок следующую картинку (по индексу текущей картинки +- 1) и отображает в большом окне эту новую картинку.

Список картинок хранится как свойство текущего контейнера. Если на странице несколько таких контейнеров (несколько разных групп фото), они не пересекаются. Но у нас зарезервировано свойство kolhoz: можно указывать его true и в этом случае хранить список картинок как свойство общего объекта Catalog (при листании будут показываться все фото из разных групп страницы подряд). Это дольше описывать, чем реализовать (2-3 строчки кода).

Зарезервировано ещё несколько строк для микронавигации. Часто можно увидеть на сайтах под большой картинкой такие кружочки (или квадратики), символизирующие список всех фото. Это тоже несложно добавить – больше кода, наверное, будет в CSS.

Кстати, о CSS: общий css сайта пока не превышает 8 килобайт, и Каталогу там посвящено примерно 30 строк. В основном, это колдовство с Большой Картинкой и Стрелками вокруг неё (вправо-влево).

(рис 4.1) (рис 4.2) (рис 4.3) (рис 4.4) (рис 4.5) (рис 4.6)

Доступ к объектам редактирования

Ещё раз напомним, что сайт состоит из элементов HTML, в которых мы показываем пользователю данные, извлечённые из БД на сервере. Данные хранятся в ячейках mysql-таблиц. Для доступа к данным со страницы HTML надо послать на сервер фоновый HTTP-запрос, содержащий наименование mysql-таблицы, номер строки (id) и наименование поля (не всегда: если наименования поля нет, форма будет создаваться для всех полей, хранимых в данной строке таблицы).

Везде, где на html-странице выводятся mysql-данные, мы создаём ссылки для редакторского доступа к этим данным. То же и для фото: к каждой миниатюре в списке изображений на станице мы прикрепляем ссылку для редактирования соответствующей строки в таблице Файлы. Ссылки всегда создаются только на стороне клиента (Javascript); они, разумеется, создаются только в том случае, если от Php поступает соответствующий сигнал – в конце страницы написано Editor.editable = true;

Но что если у вас на сайте в шапке размещён "слайдер", картинки в котором мелькают так быстро, что не успеешь кликнуть по ссылке редактирования? И что если вы захотели на время скрыть страницу (но не удалять насовсем)? Как получить доступ к таким неотображаемым объектам? Надо создавать специальные списки для всех mysql-таблиц. Чтобы не загромождать текущую страницу такими списками (или даже просто ссылками на них), поместим все эти "общие" ссылки на страницу входа в Редактор – edit.com. На этой странице ведь сейчас ничего нет, кроме формы авторизации (либо надписи "Пользователь: admin").

Как, кстати, теперь можно попасть на страницу авторизации? Раньше, в версии 06 (http://hichtig.ru/06/) мы делали для режима DEBUG верхнее меню, где, в частности, была ссылка на страницу редактирования. Теперь мы от такого меню отказались. Поэтому надо либо набирать адрес edit.com руками, либо можно нажать сочетание клавиш Ctrl + Alt + e.

Так вот, на странице edit.com мы создали список ссылок (сейчас их три: Страницы, Файлы, Сообщения) для доступа к любой строке любой таблицы Mysql. На самом деле это не ссылки, а эмуляция "кнопок" – элементы SPAN, при щелчке по которым создаётся "выпадающий" список и элемент input, при вводе в который букв или цифр выпадающий список фильтруется (запросами вида select * from file where title like '%море%', которые формируются на сервере при обращении к URL вида js_list.com?table=fileid=3).

При щелчке по элементу списка значение вставляется в поле input и генерируется форма редактирования найденной строки (в текущей таблице mysql). Можно не щёлкать по списку, если знаете точно номер строки – просто ввести этот номер в поле и нажать красную стрелку справа от поля, тогда тоже сгенерируется форма с данными, соответствующими введённому номеру (id).

Вы помните, что все javascript-обработчики у нас начинают работу при получении специального сигнала от Php. По какому сигналу javascript в браузере начинает генерировать кнопки редактирования элементов? (это вопрос для домашнего задания)

(рис 4.7) (рис 4.8) (рис 4.9) (рис 4.10) (рис 4.11) (рис 4.12)

HTTP-протокол, заголовки сервера

В версии 05 (http://hichtig.ru/05/) мы добавили нашей CMSphp-функцию _headers(), хотя до этого всё вроде бы прекрасно "работало само": веб-сервер Apache отправлял нужные заголовки ответа. Правда, мы тоже отправляли "свои" заголовки – в двух случаях:

  • когда надо было сообщить, что страница не найдена (код ответа 404);
  • когда надо было перенаправить пользователя на другую страницу (заголовок "Location: " с невидимым кодом ответа 302 – "временно перемещён").
  • Необходимость отправлять "свои" заголовки возникла прежде всего в связи с фоновыми запросами к серверу (вида js_list.com?table=fileid=3), которые должны возвращать вовсе не html, а javascript. А веб-сервер по умолчанию все ответы, организованные с участием Php, сопровождает заголовком "Content-type: text/html...". Поэтому php-функция js_list обязана отправить заголовок Content-type: text/javascript... (у нас это делает не она сама, а дополнительная функция js_send). Важно не забывать и о кодировке; полностью этот заголовок должен выглядеть так:

    Content-Type: text/javascript; charset=UTF-8
    

    Сейчас по умолчанию на всех линукс-серверах и так используется кодировка UTF-8. Но вдруг ваш веб-сервер работает под Windows? Поэтому заголовок Content-type мы отправляем из php всегда, для любого типа содержимого (и javascript, и html).

    Обычная вежливость также заставляет нас отправлять заголовок 304 Not Modified, если страница с прошлого раза не изменилась – чтобы браузер не скачивал её заново, а мог брать из своего кэша; и чтобы наш веб-сервер лишний раз не напрягался, генерируя страницу заново.

    Насчёт "прошлого раза" здесь должен позаботиться сам браузер (или другой клиент) – если он раньше уже открывал эту страницу, он отправит нам заголовок If_Modified_Since с датой создания страницы. А для этого, в свою очередь, мы должны отправлять браузеру даты создания страниц – в заголовках вида Last-Modified: ....

    Мы так и делаем: дата создания страницы хранится у нас в поле ctime ("creation time"). Сейчас эту дату можно считать верной лишь приближённо, так как на странице, например, мог появиться более поздний комментарий (и при этом содержимое страницы изменится – кэш браузера устареет). Это значит, нужно добавить специальную функцию, которая при изменении каждой сущности, связанной с данной страницей (Комментарии, Файлы), будет обновлять время создания страницы – поле ctime. Сделаем эту функцию позже (или вы сами попробуйте сделать в качестве домашнего задания).

    ЧПУ и "роутер"

    ЧПУ расшифровывается как "человекопонятный УРЛ", где аббревиатура УРЛ (Universal Resource Locator) используется в значении "адрес страницы". Внешнее, формальное отличие заключается в том, что в ЧПУ мы не используем параметры запроса (вида ?id=1), а пишем в адресе просто "1": localhost/1. Или "localhost/1.html", с расширением.

    Можно однако применить и более содержательные адреса – писать их транслитом или использовать перевод на английский: "kontakty.html" или "contacts.html". Мы будем использовать транслит, потому что он требует меньше ресурсов при автоматической генерации (и человеческих, и машинных). Но редактор всегда может своей рукой написать вместо транслита английские слова (или латинские). Все пробелы будут заменены на подчёркивания.

    У вас должен возникнуть вопрос: как запрос адреса contacts.html заставит работать программу, вызываемую из файла index.php? Если сказать коротко, это делается с помощью перенаправления запросов в файле .htaccess. Мы не будем здесь рассматривать синтаксис mod_rewrite сервера Apache, в нашем учебном сайте файл .htaccess совсем небольшой, с достаточно очевидными настройками. Мы описывали выполняемые им задачи в Главе 19, приведём здесь его код полностью:

    AddDefaultCharset UTF-8
    
    RewriteEngine On
    
    RewriteCond %{HTTP_HOST} ^www\.nichtig\.ru$
    RewriteRule ^(.*)$ http://nichtig.ru/$1 [L,R=301]
    
    RewriteRule main-1.html$ http://nichtig.ru/ [L,R=301]
    
     RewriteCond %{REQUEST_FILENAME} !-f
     RewriteCond %{REQUEST_FILENAME} !-l
     RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule .* index.php [L]
    
    Options -Indexes 
    

    Для использования длинных адресов нам понадобится ещё одно поле в таблице page – назовём его url. Заполнять его – не такая тривиальная задача. После сохранения страницы надо будет вызывать функцию set_page_url, проверять в этой функции, что написал редактор в поле url (и если отправил пустое поле, заполнять его транслитом из поля title), затем изменять запись в таблице page.

    Заполнять поле url приходится именно после сохранения страницы, потому что в него мы дописываем в конец значение поля id (через дефис): contacts-4.html. Это решает несколько задач:

  • уникальность адресов в таблице (можно создавать несколько страниц "Обо мне" – к каждой в конце будет дописываться уникальный id);
  • на стороне клиента, в javascript можно получать идентификаторы страниц;
  • мы сохраняем в качестве основного ключа id и внутри php будем использовать именно его – короче и удобнее.
  • Теперь, когда мы "договорились" о системе адресов, можно организовать их распознание на сервере и выдачу соответствующих данных. Создадим для этого целый отдельный класс – Route. В нём одна основная, "рабочая" функция response() и три вспомогательных.

  • Смотрим на адрес: contacts-3.html или write_wr.com; определяем сущность по расширению – связь сущностей с расширениями в массиве Config::$private['subjects'].
  • Если сущность command, вызываем через основную функцию класса Command command_wr ту функцию, которая указана в имени (без расширения) – write_wr, get_data_js, load_sql... Разумеется, мы вызываем только те функции, которые тут же и перечислены (в функции Route::response), а для неизвестных даём ответ 404.
  • Если сущность page, ищем запись в таблице сложным способом: если имя оканчивается на дефис-число, ищем по id, если числа в конце имени нет, ищем по полю url (это поле также является уникальным индексом) – вдруг кто-то не захотел, чтобы в конце имени были цифры.
  • Например, я не захотел – при создании новой страницы: там создаётся имя "new_page", ему уникальные цифры в конце не нужны принципиально, потому что в системе в данный момент времени может быть только одна "новая страница".

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

  • Если сущность не найдена, даём ответ 404.
  • Если страница по идентификатору не найдена, даём ответ 404.
  • Если страница найдена, залезаем в класс Page и заполняем полученными из БД данными массив Page::$data['row'].
  • Вызываем функцию Page::$tpl(), где значение $tpl берём из соответствующего поля в полученных данных ("tpl").
  • Запрещаем в адресе параметры (строку после знака "?") для сущности page, для command разрешаем – для вызова пользовательских функций класса Ab с параметрами. Пользовательских – в том смысле, что какой-то другой программист, не автор нашей CMS, позже может написать какую-то свою функцию для обработки данных на сайте, и ему не надо будет её "регистрировать", изменяя код нашей базовой функции Route::response. Эту новую функцию надо будет писать в файле Abadon.php, в классе Ab.
  • (рис 4.13) (рис 4.14) (рис 4.15) (рис 4.16) (рис 4.17) (рис 4.18)

    Загрузка и хранение файлов на сервере

    В папке files у нас будет склад файлов, которые редактор загрузил на сервер. Потом из этого склада можно файлы брать и использовать на сайте для разных задач (например, для создания Фотогалереи). Файлы можно искать по имени (Дом.jpg), можно просматривать каждую картинку – искать "по содержанию". Второй способ явно медленней. Но что делать, если вы загрузили 1000 картинок с наименованиями вида DSC3424.jpg?

    Я считаю, что всё равно надо связывать каждую картинку с какими-то ключевыми словами – так искать будет гораздо удобнее и быстрее. Поэтому система хранения у нас будет организована так: все имена при загрузке файлов превращаются в последовательные числа (1.jpg, 2.jpg, 3.jpg...); старые имена загружаемых файлов сохраняются в БД в таблице file, в поле title; это поле потом можно изменить – записать туда более содержательный текст, чем первоначальный DSC4545.

    В таблице file есть ещё несколько полей: например, descr, в которое можно записывать более длинный текст (можно использовать HTML), или поле prop1, в которое можно записывать всё, что угодно (например, адрес ссылки для баннера). Только надо договориться с программистом, чтобы он использовал нужные вам поля в соответствующих шаблонах (Фотогалереи, Каталога товаров...). А, поскольку программист сейчас – это вы, договориться будет несложно.

    На странице edit.com (которую можно открыть, нажав клавиши Ctrl + Alt + e), в меню "Файлы" вы вводите текст, который будет искаться в таблице file, по полям title, descr, id – так вы можете быстро найти нужный файл по ключевым словам, которые вы же туда и записали.

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

    <?php
    	$command = 'convert ' . $dest[0] . ' -resize ' . $dim[0] . 'x' . $dim[1] . '\> -quality 80 ' . $dest[1];
    	system($command);
    

    Здесь $dest[0] и $dest[1], соответственно, – исходный файл и уменьшенный файл; $dim[0] и $dim[1] – требуемые размеры изображения, которые мы храним в файле config.php (и получаем при работе из Config::$public['img_size']).

    Для выполнения команды в операционной системе сервера должен быть установлена система обработки изображений ImageMagick (именно её вызывает команда convert) и у Php должен быть доступ к выполнению команд системы (такой доступ бывает не на каждом хостинге).

    Команда системы convert хороша тем, что она очень компактна, в двух её значках – "\>" – спрятана логика, для которой на php понадобится несколько строк кода. Эти значки предписывают конвертеру не менять размеры изображения, если они не больше требуемых. Впрочем, на php можно работать с изображениями более гибко; но это не предмет нашего курса (мы используем в нашей учебной CMS только изменение размеров картинок).

    ***

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

    На сервере для каждого файла сначала создаётся запись в таблице file, затем мы узнаём id новой записи с помощью mysql-функции LAST_INSERT_ID() и перемещаем загруженный файл в нужную папку с нужным именем (id + расширение файла). Мы допускаем, что в одной папке будет храниться до 1000 файлов, а следующие будем складывать в другую папку (по номерам: 0, 1, 2...). Вы можете найти в других CMS разные хитрые способы случайного распределения файлов по разным папкам, но для планируемых нами объёмов (несколько тысяч файлов) сложная система не нужна, и мы по возможности стараемся соблюдать принцип KISS (англ. keep it simple, stupid).

    Всё это делает функция Command::upload_file(), которая, в свою очередь, вызывается из функции Command::onwrite_file() (как бы по событию "после записи в таблицу file") в файле Command.php.

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

    Почтовые уведомления

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

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

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

    Вот и напишем в функции Command::onwrite_msg(), чтобы она отправляла на адрес автора сайта текст из полученных данных $_POST['set']['text_q']:

    <?php
    	mail('onerror@mail.ru', 'Новое сообщение на сайте', $_POST['set']['text_q']);
    

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

    Дело в том, что протокол отправки писем очень чувствителен к переносам строки – коду . А мы просто будем удалять сочетание \r\n из тела письма:

    <?php
    	$msg = preg_replace('/\r/', "\n", $_POST['set']['text_q']);
    	mail('onerror@mail.ru', 'Новое сообщение на сайте', $msg);
    

    Ну, и добавится ещё одно ограничение для нашей CMS: для отправки почтовых уведомлений мы используем php функцию mail(), которая требует наличия сервера sendmail. Это значит: веб-сервер должен быть на линуксе, и владелец хостинга должен разрешить использование функции mail(). Хостинг, удовлетворяющий таким условиям, найти несложно, но и обратная ситуация встречается достаточно часто. Поэтому надо просто обращать внимание на такие детали. Или сразу привыкать пользоваться виртуальным сервером (VPS), который вы можете настраивать по своему усмотрению.

    Конечно, можно и совсем обойтись без функции /). На самом деле можно даже библиотеку не использовать, а самому написать (получится килобайт 10-30), но тогда надо будет сначала выучить упомянутую выше спецификацию (RFC 5321). Так что выбора, по большому счёту, у вас и нет (вы же не осилите?..).

    Чтобы при отладке программы (на домашнем компьютере) не сыпались ошибки, добавим в конфигруацию пункт mailer, значения которого могут быть "mail", "smtp" (для сторонних библиотек) или "null" – тогда почту отправлять вовсе не будем пытаться. В какой именно раздел файла конфигурации добавим новый пункт, обсудим в следующей главе.

    Конфигурация. Версия 08

    Все настройки хранятся у нас в файлах exe/config.php и exe/dns.php (mysql). Хранить настройки в отдельных файлах удобно: это повышает читаемость (да и прочность) кода. К тому же можно привести массив в config.php к однородному виду (сделать всё в два уровня), и тогда можно будет править параметры через http (то есть прямо на сайте). Практика, однако, показывает, что заказчики сайтов не пользуются такой возможностью – всегда обращаются к разработчику для изменения любых параметров конфигурации. Поэтому мы не будем усложнять нашу маленькую CMS в эту сторону – вся конфигурация будет правиться только в файле.

    Почему параметр из предыдущей главы (о почтовых уведомлениях) нельзя хранить в файле config.php? Можно, но не совсем правильно. У нас прослеживается три типа параметров:

  • для конкретного сайта,
  • для всей CMS,
  • для "хоста" – компьютера, на котором работает веб-сервер.
  • Например, Mysql – совершенно отдельная система, она не входит в CMS (и даже не входит в систему ApachePhp); поэтому мы выделили эти настройки в отдельный файл dns.php. То же и с доступом к командам ОС (convert, например) – мы должны записать в dns.php, есть ли у нашей CMS такая возможность, и разветвлять рабочий код соответственно.

    Есть ли на нашем хостинге (сервере) возможность отправлять сообщения через sendmail, тоже не имеет прямого отношения к CMS, и этот параметр мы тоже поместим в dns.php.

    ***

    Интересен вопрос о второй группе параметров – "для всей CMS". У нас почти все параметры именно такие. Более того, 95% всего рабочего кода у нас не относится к конкретному сайту (ведь именно к этому мы и стемились, начиная писать CMS). И пора переходить на следующую ступень – выделить код "ядра" в отдельную папку, стоящую выше конкретных сайтов (и сразу много сайтов смогут использовать общий код этого ядра). Ядро у нас получилось так себе – не ядро, а ядрышко, зёрнышко, всего 60 килобайт. Так и назовём его в соответствии с размером – не "kernel", а "Kern", Kern.sys (расширение – для надёжности, чтоб не путать эту папку с другими "просто папками").

    В папку "бибилиотеки" Kern.sys поместим файлы Command.php, common.php, config.php, dns.php, functions.php, а на сайтах, в папках exe, останутся файлы Site.php, users.php, Abadon.php. Строго говоря, для "персональных" параметров сайта надо добавить ещё файл site_config.php, и перекрывать его параметрами значения из общего файла config.php. Но пока в этом нужды нет, а когда (и если) возникнет – сделаем.

    Эти изменения требует переезда на следующую версию – 08 http://nichtig.ru/08/. Возникает сразу пара небольших проблем:

  • у нас 10 М файлов на сайте и мы не хотим их размножать по всем версиям;
  • вам надо будет как-то получать zip-архив "библиотеки", которая теперь находится не в папке сайта.
  • Первую проблему решаем, добавив в наш "контекстный коммандер" Abadon.php следующую функцию:

    <?php
    class Ab {
    	static function lfile () {
    		$f = SITE . '/files';
    		if (!file_exists($f)) symlink(SITE . '/../07/files', $f);
    	}
    //...
    

    Функция вызывается так: http://nichtig.ru/08/addon.com?lfile (нужен авторизованный пользователь со статусом 2).

    Вторую задачу решить сложнее из-за запутанной логики скачивания архивов. Там javascript отправляет на сервер запрос downzip.com с параметром type, который описан прямо в функции Command::downzip(). Мы добавили сейчас параметр "8", который потребует совершенно отдельной обработки – и в коде ниже придётся добавить самый "верхний" if: если параметр равен 8, пакуем рекурсивно папку Kern.sys (функцией work_zip):

    <?php
    class Command {
    //...
    	static function downzip () {
    //...
    		if ($type == $rtypes['lib']) {
    			work_zip($Zip, LIB, LIB);
    		}
    //...
    

    Ну, и последняя деталь. Вы видите в примере константу LIB, которой не было в предыдущей версии. Мы не хотим связывать себя конкретными обязательствами по месту расположения нашего Kern.sys, поэтому создаём (прямо в файле index.php) функцию lib_find, которая будет искать свою "библиотеку" начиная с папки самого сайта (да, можно помещать Kern.sys хоть внутри сайта):

    <?php
    define('SITE', realpath(dirname(__FILE__)));
    //...
    define('LIB', lib_find(EXE, 'Kern.sys'));
    //...
    function lib_find($path, $lib_name) {
    	$res = null;
    	DIRECTORY_SEPARATOR == '/' || $path = str_replace('\\', '/', $path); 
    	$path_arr = explode('/', $path);
    	while(array_pop($path_arr)) {
    		$test = implode('/', $path_arr) . '/' . $lib_name;
    		if (file_exists($test)) {
    			$res = $test;
    			break;
    		}
    	}
    	return $res;
    }
    

    Остаётся пока вопрос, как все эти пути будут работать под Windows. Протестируйте это, если у вас есть Windows. Если возникнут проблемы, пишите комментарии. Впрочем, и так известно, что возникнут. Поэтому заменим сразу строчку определения константы SITE:

    <?php
    define('SITE', str_replace('\\', '/', realpath(dirname(__FILE__))));
    

    Вот теперь проверяйте (это домашнее задание). Проверять надо так:

  • скачайте себе на компьютер "ядро" ;
  • скачайте файлы самого сайта http://nichtig.ru/08/downzip.com?type=1 ;
  • распакуйте всё это в нужные папки вашего домашнего веб-сервера;
  • скачайте и загрузите его в вашу базу данных.
  • Второе задание: создайте систему редактирования файла Kern.sys/config.php

    Инструкции для пользователей

    Написать код рабочей CMS и описать её работу – разные задачи. И вторая отнюдь не проще. Начнём с инструкции по установке.

    Инструкция по установке CMS "Шутка" (назовём её так):

  • Скачайте к себе на компьютер архив http://nichtig.ru/08/source/Unsinn_proglib.zip .
  • Распакуйте архив в любую папку своего веб-сервера, доступную по http.
  • [можно пропустить] Перенесите папку Kern.sys на один-два уровня выше (чтобы она была недоступна по http).
  • Добавьте в файл exe/users.php пользователя со статусом "2". Это можно сделать, например, с помощью команды консоли в вашем каталоге exe: "./add_user.sh my-name my-password 2" (где вместо my-name и my-password вы должны написать своё имя и пароль). Или допишите элемент массива руками (вычислив md5 для пароля в другой программе).
  • Отредактируйте файл Kern.sys/dns.php – впишите туда правильные имя пользователя, пароль и БД для подключения к Mysql.
  • Откройте в браузере http-адрес, по которому находится ваш новый сайт. Нажмите Ctrl + Alt + e для перехода на страницу авторизации (или введите в адресную строку адрес edit.com и перейдите по нему). Авторизуйтесь с именем и паролем пользователя, которого вы создали (со статусом "2").
  • Нажмите кнопку "Создать таблицы mysql" в списке красных ссылок справа, убедитесь по отчёту, что таблицы созданы.
  • Наполняйте сайт содержимым: создавайте новые страницы и редактируйте. Добавляйте фото.
  • Откройте файл exe/Site.php и начинайте создавать шаблоны (php, выводящий mysql-данные в окружении HTML-тэгов в нужных местах страницы) для нового сайта.
  • Добавляйте в файл site.css правила для отображения HTML-элементов нового сайта.
  • Вернуться к учебному плану