PmWiki.ru минималистичный wiki-движок, работающий без базы данных

PmWiki как CMS: шаг второй

продолжаем создавать классическую CMS из PmWiki

Мы продолжаем разрабатывать уникальную Систему Управления Сайтами (CMS) на базе движка PmWiki. Мы адаптируем отличный wiki-движок к классическим задачам сайтостроения, в результате чего рождается уникальный симбиоз wiki-концепции с функциональностью традиционной CMS. Все примененные "фичи" используются в реальности на этом сайте прямо сейчас.

Напомню, что преимущественно конфигурация PmWiki хранится в файле /local/config.php , в котором подключаются php-плагины или "рецепты" из директории /cookbook

Модификации config.php после публикации сайта:

Установлен плагин SourceBlock

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

Определяем формат представления дат для всего сайта:

$TimeFmt = '%Y-%m-%d'; # dates as "2005-09-08"

разберемся с аттачами: мультизагрузка файлов в архиве с автоматическим разархивированием на сервере

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

Кроме того, "постраничное" хранение аттачей позволило сделать исключительно простой и удобный метод загрузки файлов на сервер с помощью плагина multiupload.php Достаточно собрать файлы в zip-архив и загрузить их на сервер через форму в разделе аплоада данной страницы (?action=upload). Необходимая уникальная директория будет создана автоматически, файл будет подгружен в нее, разархивирован и автоматически удален. Глупый юзер может загружать какой угодно хлам, лишь бы в нем не было кириллических имен файлов (хотя, может и с ними будет работать, не пробовал).

  1. $UploadPrefixFmt = '/$Group/$Name';
  2. $UploadMaxSize = 10485760; // лимит на загружаеый файл - 10 МБ
  3. include_once("$FarmD/cookbook/multiupload.php"); // плагин для загрузки группы файлов в zip-архиве
  4. $UploadExts = array( // разрешим аплоад файлов разных типов
  5.   'prproj' => 'text/plain',
  6. );

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

Задаем новую переменную для страниц - дату создания.

Эта переменная будет служить основным критерием сортировки Базы Знаний сайта:

$FmtPV['$Created'] = "strftime(\$GLOBALS['TimeFmt'], \$page['ctime'])";

Стоит отметить, что это Page Variable (PV), а не Page Text Variable (PTV). Разница в том, что PV более "низкоуровневая" - ее значение записывается в отдельное "поле" страницы сайта, то есть в отдельную строку физического файла с описанием страницы, например "wiki.d/Base.PmWikiAsCMS-step2". Переменные PTV не записываются в отдельные поля, а остаются в поле с контентом страницы, где-то внутри него. Преимущества PV - это более быстрый поиск, выборки и сортировки, а также возможность создания отдельной формы для ввода значения в режиме редактирования. Недостаток - необходимость создания поля в конфиге CMS.

Добавляем новые поля в форму редактирования материала:

$EnablePageTitlePriority = 1;
делаем приоритетным первое определение title в странице, чтобы при include других страниц, он не заменялся. Похоже, это может быть лишней директивой, т.к. далее обработка синтаксиса (:Title:) будет вообще отключена в плагине EditFormCustomFields

$ctimeDispFmt = "%Y-%m-%d";
формат вывода даты для поля даты создания статьи

include_once('cookbook/strptime.php');
т.к. периодически мощные доработки сайта я произвожу на локальном веб-сервере XAMPP под Windows, а нижеследующий плагин использует функцию strptime, доступную в PHP на уровне ядра только под Unix-like системами, придется эту функцию надстроить отдельным решением, а именно скриптом от француза Саурона

include_once('cookbook/editformcustomfields.php');
плагин для генерации пользовательских форм с добавленными формами Description и Ctime (дата создания статьи)

DisableMarkup("keywords");
DisableMarkup("description");
Отключаем обработку keywords и description в теле wiki-контента, т.к. теперь для них есть отдельные поля как в форме редактирования, так и в самом wiki-файле. По-умолчанию, почему-то PmWiki хоть и хранит description в отдельной переменной, но при формировании страницы парсит ее из текста. Теперь будет брать из переменной, только соответствующую запись надо внести в шаблон.

Небольшая модификация разметки:

  1. Markup('[[<<]]','inline','/\\[\\[&lt;&lt;\\]\\]/',"<div class='clear'></div>");
  2. Markup("'+","<'''''","/'\\+(.*?)\\+'/",'<span style=\'font-size:122%\'>$1</span>');
  3. Markup("'-","<'''''","/'\\-(.*?)\\-'/",'<span style=\'font-size:80%\'>$1</span>');

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

Включаем функцию подсчета ссылок, в том числе мертвых

include_once('scripts/refcount.php');
Делается это с целью получить возможность найти все "задуманные", но не написанные статьи; а также все ссылки на них на всех страницах.

Комплексный анализ контента всего сайта осуществляется скриптом /scripts/refcount.php Скрипт рассчитан на работу с формой ввода поиска, использующей метод post. Однако, оказывается, параметры также могут задаваться через адресную строку, например вот так можно вывести все "мертвые" ссылки сайта кучей по одной ссылке. Синтаксис обращения можно понять изучив вышеуказанный пример и файл /scripts/refcount.php

Включаем поддержку отображения swf-файлов, удобное встраивание видео с vimeo и youtube

include_once("$FarmD/cookbook/swf-sites.php");
include_once("$FarmD/cookbook/swf.php");
см. Cookbook:Flash

Научаем wiki искать статьи в группе Base, если соответствующей статьи еще нет в данной группе:

$PagePathFmt = array('$Group.$1','Base.$1','$1.$1','$1.$DefaultName'); Таким образом на базе движка будет организована система из многих сайтов (доменов), на каждом из которых будет поднят свой внешне независимый проект. Однако База Знаний (или, проще, статьи) для всех проектов будет единой.

Отличной демонстрацией задумки является сайт удален: он работает на том же движке (т.е. в той же директории), что и web.finar.ru Об этом не догадаться до тех пор, пока не сделаешь ссылку на какую-нибудь статью группу Base. Следуя по тегам и внутренним ссылкам этой статьи, можно будет добраться до любой статьи из Base, а также, возможно, и до контента web.finar.ru (группы Web).

Кстати, специально для kazakov.of-site.ru и только для него (т.е. для группы Market) был реализован следующий параграф.

Внедряем ограниченную поддержку bbcode для упрощения форматирования

В коде require_once ("cookbook/bbcode.php"); не было бы решительно ничего примечательного, если бы это не делалось только для сайта kazakov.of-site.ru. Как подключить плагин только для одного сайта системы, или для одной Группы? Создать в /local php-файл с соответствующим группе именем и внести необходимую "инъекцию" только в него. Например, Market.php для группы Market.

Убираем подчеркивание и стрелку у картинок, являющихся ссылками на внешние сайты

Честно, я не до конца разобрался с этим кодом (не настолько интересная задача, чтобы заморачиваться). Взял отсюда, вставил, и все заработало :), хотя, вероятно, и несколько избыточно.

  1. $EnableUploadOverwrite = 0;
  2. function OtherGroupCss($group)
  3. {
  4.     global $pagename;
  5.     $current = PageVar($pagename, '$Group');
  6.     if($group == $current) return "";
  7.     return " othergroup";
  8. }
  9. $FmtPV['$OtherGroupCss'] = 'OtherGroupCss($group)';
  10. $LinkPageExistsFmt =
  11.    "<a href='\$LinkUrl'>\$LinkText</a>";
  12. $LinkPageCreateFmt =
  13.    "<a class='createlinktext' rel='nofollow' href='{\$PageUrl}'>\$LinkText</a><a class='createlink' href='\$PageUrl'>?</a>";
  14.  
  15.  
  16. $LinkFunctions['http:'] = 'TxtLinkIMap';
  17. $LinkFunctions['https:']  = 'TxtLinkIMap';
  18. $LinkFunctions['mailto:']  = 'MailLinkIMap';
  19. $LinkFunctions['Attach:'] = 'TxtLinkUpload';
  20. SDV($EnableUploadOverwrite,1);
  21. $IMapLinkFmt['Attach:'] =
  22.     "<a class='urllink' href='\$LinkUrl' rel='nofollow'>\$UploadText</a>";
  23. if ($EnableUploadOverwrite)
  24.     $IMapLinkFmt['Attach:'] .=
  25.     "<a class='createlink' href='\$LinkUpload'>&nbsp;&Delta;</a>";
  26. $UrlLinkTxtFmt = "<span class='url'>$UrlLinkFmt</span>";
  27. $MailLinkTxtFmt = "$UrlLinkFmt";
  28. $UrlLinkFmt    = $UrlLinkTxtFmt;
  29. $UrlLinkImgFmt =
  30.     "<a class='urllinkimg' href='\$LinkUrl' target='_blank' rel='nofollow'>\$LinkText</a>";
  31.  
  32. function TxtLinkIMap($pagename,$imap,$path,$title,$txt,$fmt=NULL) {
  33.   global $UrlLinkImgFmt, $UrlLinkTxtFmt;
  34.   if (!$fmt)
  35.       $fmt = preg_match('/^<img/',$txt) ? $UrlLinkImgFmt : $UrlLinkTxtFmt;
  36.   return LinkIMap($pagename,$imap,$path,$title,$txt,$fmt);
  37. }
  38.  
  39. function TxtLinkUpload($pagename, $imap, $path, $title, $txt, $fmt=NULL) {
  40.   global $FmtV;
  41.   $FmtV['$UploadText'] = str_replace('Attach:','',$txt);
  42.   return LinkUpload($pagename,$imap,$path,$title,$txt,$fmt);
  43. }
  44.  
  45. function MailLinkIMap($pagename,$imap,$path,$title,$txt,$fmt=NULL) {
  46.   global $UrlLinkImgFmt, $MailLinkTxtFmt;
  47.   if (!$fmt)
  48.       $fmt = preg_match('/^<img/',$txt) ? $UrlLinkImgFmt : $MailLinkTxtFmt;
  49.   return LinkIMap($pagename,$imap,$path,$title,$txt,$fmt);

Обеспечиваем валидность html-кода в toggle.php - генераторе JavaScript-кнопок

Рецепт Cookbook:Toggle, примененный еще на первом этапе для создания кнопок как в прошлом параграфе, всем хорош, кроме того, что вставляет JavaScript-код кнопки в контенте непосредственно перед ее применением, то есть внутри тега <body>. Это противоречит стандартам HTML, поэтому, специально, чтобы код первой страницы нашего сайта стал полностью валидным, пришлось немного доработать файл toggle.php. Вот как теперь выглядит его конец (со 154-й строчки):

  1.     // здесь введена новая переменная $validStyle затем, чтобы в ней сохранить весь генерируемый  CSS, а затем вставить его в header страницы, дабы код страницы валидировался.
  2.     $validStyle = "#{$id} { display:{$style}; } ";
  3.     if ($args['printhidden']==1) $validStyle.= " @media print { #{$id} {display:block} } ";
  4.     if ($id2) {
  5.         $validStyle.= " #{$id2} { display:{$altstyle}; } ";
  6.         if ($args['printhidden']==1) $validStyle.= " @media print { #{$id2} {display:block} } ";
  7.  
  8.         }
  9.  
  10. $HTMLStylesFmt['toggle'] .= " @media print {.toggle{display:none}} .toggle img {border:none;} $validStyle";  // перемещено сюда с 54 строчки by Finar
  11.  
  12.  
  13.  
  14.    return Keep($out);
  15.  
  16. }
  17. #EOF

tagger.php - мультитэги с древовидной структурой
Это еще один плагин, позволяющий реализовать мультитэговость. Описание его работы довольно объемно, поэтому выношу его в отдельную статью: Кровеносная система finar.ru - мультитэги с древовидной структурой