This page in: English - Deutsch - Español - Français - Italiano - Lietuvių - Nederlands - Norsk - Polski - Русский - Svenska - Tiếng Việt - عربي - 日本語 简体中文
Руководство по модулям
Это руководство описывает процесс создания нового модуля для CMS Made Simple (далее «CMSMS»).
Необходимые знания
Далее в этом руководстве предполагается, что Вы обладаете достаточными навыками в следующих областях знаний:
- Программирование на языке PHP.
- Основные понятия объектно-ориентированного программирования (инкапсуляция, наследование и т.п.).
- MySQL (или в общем Базы Данных) – таблицы, поля, типы данных, синтаксис языка запросов SQL, и т.д. Также, предполагается, что Вы умеете использовать какое-либо средство администрирования баз данных (такое как, например, PhpMyAdmin) для управления базой данных CMSMS.
- Как работает CMSMS с точки зрения конечного пользователя – т.е. Вы знаете, что такое шаблоны, таблицы стилей, страницы и контент, что существует механизм прав, который накладывает определенные ограничения на определенный функционал системы, и что
{тэги}
включенные в текст шаблонов отображают контент модулей.
Знание принципов работы модулей в CMSMS не требуется – т.к. это предмет рассмотрения этого руководства!
Какой модуль мы собираемся создать?
Мы собираемся создать модуль каталога продукции со всеми основополагающими функциями. Нам необходимо дать возможность администраторам добавлять информацию о продукции в базу данных из панели управления и создавать на ее основе страницы сайта, которые будут отображать все продукты в нашей системе. В последствии Вам может понадобиться реализовать возможности выбора различных вариантов представления материала (количество колонок на странице и др.), классификации продукции по типу, производителю и т.п. (таким образом, различные страницы будут содержать различный набор продуктов), разбиения каталога на страницы (когда на каждой странице выводится лишь заданное число продуктов), добавления и отображения изображений продуктов, и поиска по каталогу продукции. Из соображений целесообразности, мы не будем нагромождать нашу модель, и реализуем лишь самые необходимые поля информации о продукте – наименование, описание и цена, и будем выводить все продукты в виде простого списка на одной странице. Наш модуль мы назовем «Catlist» (мы умышленно выбрали такое безрассудное название, чтобы исключить конфликты с существующим более благозвучным модулем Cataloger).
Структура модуля CMSMS
С первого взгляда принцип работы и структура модуля могут показаться замысловатыми. Для начала, все необходимое для работы модуля располагается в директории, имя которой должно совпадать с названием Вашего модуля. Так как мы назвали наш модуль «Catlist», то и директория нашего модуля будет иметь имя «Catlist». Она должна находиться в директории «Modules» установленной CMSMS. Если Вы посмотрите на любой из существующих модулей (например, на Skeleton), то увидите множество файлов и поддиректорий, которые можно грубо разделить на следующие типы:
- Файлы вида
action.___.php
: эти файлы содержат код, отвечающий за обработку определенных «действий», которые совершает CMSMS – например, когда панель управления должна проверить права доступа, или новый контент должен быть сохранен в базе данных, система исполняет эти файлы. Так же как и файлы видаmethod.___.php
(ниже по списку), эти файлы не являются обязательными – их код можно поместить в файлИмяМодуля.module.php
. Так, в данном руководстве мы не будем использовать файлыaction.___.php
, но имейте ввиду, что по мере усложнения кода, Вы можете сделать конструкцию модуля более гибкой и удобной, выделив часть кода в отдельные файлыaction.___.php
. - Директория
images
: любые изображения, участвующие в отображении интерфейса модуля (или в панели управления, или непосредственно на сайте), следует помещать в эту директорию. - Файл
index.html
: Это всего лишь пустой HTML-файл. В случае если пользователь вручную задаст адрес директории модуля в адресной строке своего браузера, этот файл предотвратит нежелательную, из соображений безопасности, публикацию списка файлов в этой директории. - Директория
lang
: Для того чтобы Ваш модуль поддерживал несколько языков, Вы можете поместить в этой директории языковые файлы (по одному на каждый язык), а затем в коде модуля, вместо непосредственного использования фиксированных текстовых строк, Вы можете использовать функцию$this->Lang('string_name')
для получения текста на нужном языке. - Файлы вида
method.___.php
: эти файлы содержат код, отвечающий за обработку определенных «методов», которые вызываются CMSMS – обычно для установки, обновления и удаления модуля. Так же как и файлы видаaction.___.php
(выше по списку), эти файлы не являются обязательными – их код можно поместить в файлИмяМодуля.module.php
. Так, в данном руководстве мы не будем использовать файлыmethod.___.php
, но имейте ввиду, что по мере усложнения кода, Вы можете сделать конструкцию модуля более гибкой и удобной, выделив часть кода в отдельные файлыmethod.___.php
. - Файл
ИмяМодуля.module.php
: этот файл – ядро Вашего модуля – это то где все и происходит! Весь код, отвечающий за установку и удаление нашего модуля, добавление информации о продукции в базу данных и отображение ее на сайте будет размещаться в этом файле. - Директория
templates
: любые шаблоны, используемые для отображения контента Вашего модуля – как в панели управления, так и непосредственно на страницах сайта, – следует помещать в эту директорию.
Как CMSMS отображает контент модуля
Самая сложная для новичка вещь – понять как в действительности CMSMS формирует контент модуля, отображаемый на странице сайта. Существует два различных варианта включения контента модуля в страницу сайта:
- Использование тэга
{module}
на странице: Администратор создает новую страницу с типом контентаcontent
и включает в ее текст тэг (например,{cms_module module='Catlist'}
), ответственный за вызов нужной функции из файлаCatlist.module.php
нашего модуля, в свою очередь которая получает данные из базы данных, затем внедряет эти данные в один из шаблонов модуля, который впоследствии включается в текст готовой страницы сайта. - Создание нового типа контента: Новый тип контента добавляется в соответствующее поле выбора на странице редактирования контента в панели управления. Когда администратор выбирает его, ему показывается форма ввода параметров контента, соответствующая требованиям нашего модуля. Эти параметры соответствуют тем параметрам, которые мы определим для тэга
{module}
нашего модуля. Страница с нашим типом контента имеет тот же эффект, как если бы она была создана с типом контентаcontent
, а в поле «Контент» был указан тэг{module}
нашего модуля с необходимыми параметрами, за тем исключением, что форма ввода своего типа контента более удобна, т.к. содержит поля ввода для всех параметров используемого модуля и пользователю нет необходимости помнить их имена. Замечание автора оригинала: Мне не известны все детали работы новых типов контента – возможно, кто-то может внести в этот вопрос большую ясность? В данном руководстве мы будем применять первый метод – использование тэга{module}
на странице с базовым типом контентаcontent
.
Первые шаги: Создание директории модуля
ОК, мы, наконец, вполне готовы к созданию нашего модуля! Для начала откройте директорию «Modules» установленной CMSMS. Создайте в ней директорию «Catlist». Убедитесь в правильности написания и регистра букв в ее имени – это важно для того, чтобы обращение к модулю в коде осуществлялось в точности по тому же имени. Теперь откройте текстовый редактор, которым привыкли пользоваться, и создайте новый файл Catlist.module.php
(и снова следите за правильностью набора имени). Начнем с самого необходимого – вставьте следующий фрагмент кода (взятый из модуля Skeleton) в свой файл:
<?php /* Описание базового класса. Имя файла должно быть "ИмяКласса.module.php", или, в нашем случае - Catlist.module.php */ class Catlist extends CMSModule { /*--------------------------------------------------------- GetName() Функция должна возвращать точное имя класса модуля. Если имена класса и модуля не совпадают - поверьте, все будет плохо. Это имя, которое отображается на странице Модули в панели управления. ---------------------------------------------------------*/ function GetName() { return 'Catlist'; } /*--------------------------------------------------------- GetFriendlyName() Функция может возвращать любую строку. Это более благозвучное имя модуля, которое отображается в меню панели управления и на страницах управления модулем (если модуль имеет административный компонент). ---------------------------------------------------------*/ function GetFriendlyName() { return 'Простой Каталог Продукции'; } /*--------------------------------------------------------- GetVersion() Функция может возвращать любую строку, желательно номер или нечто указывающее на используемую версию модуля. CMS будет использовать это значение для определения актуальности текущей версии, а сам модуль - для подбора подходящего алгоритма обновления по запросу пользователя. ---------------------------------------------------------*/ function GetVersion() { return '0.1'; } /*--------------------------------------------------------- IsPluginModule() Эта функция возвращает true или false, обозначая тем самым могут ли пользователи внедрять данный модуль в в страницы или шаблоны посредством тэга вида {cms_module module='Skeleton' param1=val param2=val...} Если такая возможность не предполагается для Вашего модуля, функция должна возвращать "false". (Требуется, если Вы хотите в последствии использовать метод ''DoAction''.) ---------------------------------------------------------*/ function IsPluginModule() { return true; } } ?>
ОК, этого, конечно, не достаточно, но с чего-то надо начинать. Комментарии в коде поясняют назначение каждой функции (хотя, оно и без них вполне очевидно). Сохраните этот файл в только что созданной директории «Catlist» под именем Catlist.module.php
.
Теперь, давайте посмотрим как в CMSMS реализуется многоязычность. Я, к сожалению, не знаю никаких языков кроме английского, поэтому не могу писать тексты на других языках. Но мы вполне можем сделать возможным для любого будущего пользователя добавление перевода нашего модуля на его родной язык (все таки, это проект с открытым исходным кодом)! Как мы уже упоминали, вместо непосредственного использования строковых констант в Вашем коде, Вы можете применять функцию $this->Lang()
для получения нужного текста из определенного языкового файла. Давайте подготовим все необходимое для этого. Создайте новую директорию lang
внутри директории Catlist
модуля. Вернитесь в Ваш текстовый редактор и создайте новый файл en_US.php
в директории lang
. Пока, добавим единственную строку, содержащую «понятное имя» нашего модуля (так как системное имя модуля – едино для всех языков – так же как, например «Coke» – и в Африке «Coke»). Вставьте этот фрагмент кода в новый файл:
<?php $lang['friendlyname'] = 'Simple Catalog Product List'; ?>
Теперь, вернитесь к файлу Catlist.module.php
и замените строчку кода функции GetFriendlyName()
на следующую:
return $this->Lang('friendlyname');
Для того, чтобы добавить поддержку русского языка в наш модуль, мы должны создать новый файл ru_RU.php
(или fr_FR.php
для французского языка и т.д.) в директории lang
и вставить в него этот фрагмент кода:
<?php $lang['friendlyname'] = 'Простой Каталог Продукции'; ?>
Аналогичным образом можно добавлять поддержку для сколь угодно большего количества языков.
Инсталляция и деинсталляция модуля
Большинству модулей необходимы доступ к базе данных CMSMS (чтобы сохранять и извлекать наши данные), новые права доступа (чтобы администраторы сайта могли разрешать или запрещать определенным категориям пользователей выполнять определенные операции с модулем), а так же настройки (чтобы администраторы сайта могли изменять различные установки, изменяющие поведение модуля). Эти требования должны быть выполнены в процессе установки модуля, и, если мы реализуем это в функции Install()
, то она будет вызвана в момент нажатия пользователем ссылки «Загрузить и установить» в панели управления. Для нашего модуля мы создадим одну таблицу в базе данных для хранения информации о продукции и одно право доступа, которое позволяет (или запрещает) пользователям добавлять, редактировать, удалять продукты из каталога. Никаких настроек модуля у нас не будет.
Сперва, добавьте эту функцию в файл Catlist.module.php
:
function Install() { }
Таблица базы данных будет очень простой – в ней будут поля для идентификатора, наименования, описания и цены продукта. CMSMS использует библиотеку ADODB для взаимодействия с базой данных (см. Руководство ADODB для подробной информации). Вставьте следующий фрагмент кода в файл Catlist.module.php
(между фигурных скобок класса Catlist
):
function Install() { // Получить ссылку на базу данных $db = $this->cms->db; // Специальная инструкция MySQL, пропускаемая другими базами данных $taboptarray = array('mysql' => 'TYPE=MyISAM'); // Создать новый ADODB-объект таблицы $dict = NewDataDictionary($db); // Добавить новые поля, разделенные запятой. // См. руководство ADODB для списка доступных типов полей. // В нашем случае, поле id - целочисленное, name - имеет тип varchar(100), // description - текстовое поле и price - вещественного типа. $flds = "id I KEY, name C(100), description X, price F"; // Послать команду ADODB создания новой таблицы с именем // "module_catlist_products" и полями, описанными выше. // Обратите внимание на принцип именования нашей таблицы, // позволяющий по имени таблицы определить принадлежность таблицы // конкретному модулю и избежать конфликтов с другими модулями, // и применяйте его при добавлении новых таблиц в базу данных. $sqlarray = $dict->CreateTableSQL(cms_db_prefix().'module_catlist_products', $flds, $taboptarray); $dict->ExecuteSQLArray($sqlarray); // Создать "таблицу последовательности" для внутреннего // использования CMSMS и ADODB. Она позволяет автоматически // увеличивать значение поля id для каждой новой записи таблицы. $db->CreateSequence(cms_db_prefix().'module_catlist_products_seq'); }
Теперь добавьте в конец функции еще одну строку кода, который создает в системе новое право доступа:
function Install() { /* ... текст создания базы данных ... */ // Создать право доступа. // Первый аргумент - это системное имя нового права доступа. // Второй - более полное пользовательское наименование права доступа. $this->CreatePermission('Catlist Admin', 'Manage Catlist'); }
Также добавьте эту функцию (между фигурных скобок класса Catlist
), которая будет показана администратору после успешной установки модуля:
function InstallPostMessage() { return $this->Lang('postinstall'); }
И, конечно, нам нужно добавить определения текста этого сообщения в английском языковом файле:
$lang['postinstall'] = 'Catlist successfully installed!';
и в русском:
$lang['postinstall'] = 'Catlist успешно установлен!';
Т.к. установка нашего модуля потребовала добавления компонентов в CMSMS, логично, что мы должны удалить эти компоненты при удалении нашего модуля из CMSMS (да, эта удручающая мысль, но давайте будем цивилизованными программистами и покажем достойный пример подрастающему поколению разработчиков). Немудрено, что делать мы это будем в функции Uninstall()
. По существу, нам надо отменить все, что мы сделали в функции Install()
. Добавьте этот фрагмент кода в файл Catlist.module.php
(между фигурных скобок класса Catlist
):
function Uninstall() { // Получить ссылку на базу данных $db = $this->cms->db; // Удалить таблицу базы данных $dict = NewDataDictionary($db); $sqlarray = $dict->DropTableSQL(cms_db_prefix().'module_catlist_products'); $dict->ExecuteSQLArray($sqlarray); // Удалить "таблицу последовательности" $db->DropSequence(cms_db_prefix().'module_catlist_products_seq'); // Удалить право доступа $this->RemovePermission('Catlist Admin'); }
И опять же, нам нужно показать сообщение администратору об успешном окончании деинсталляции. А также мы хотим обезопасить администратора от непреднамеренного удаления модуля с помощью стандартного вопроса «Вы уверены...?» (последнее особенно важно ввиду того, что в базе данных может быть информация о продукции, которая будет потеряна в процессе деинсталляции). И, как обычно, добавьте эти функции в файл Catlist.module.php
(между фигурных скобок класса Catlist
):
function UninstallPreMessage() { return $this->Lang('uninstall_confirm'); } function UninstallPostMessage() { return $this->Lang('postuninstall'); }
И, конечно, нам нужно добавить определения текста этого сообщения в английском языковом файле:
$lang['postinstall'] = 'Catlist successfully installed!';
и в русском:
$lang['postinstall'] = 'Catlist успешно установлен!';
И конечно, добавьте соответствующие определения в английский языковой файл:
$lang['uninstall_confirm'] = 'All product data in the catalog will be deleted!' . 'Are you sure you want to uninstall the Catlist module?'; $lang['postuninstall'] = 'Catlist successfully un-installed.';
и для порядка в русский:
$lang['uninstall_confirm'] = 'Вся информация в каталоге будет потеряна!' . 'Вы уверены, что хотите удалить модуль Catlist?'; $lang['postuninstall'] = 'Catlist успешно удален.';
ОК, теперь у нас есть все необходимое для испытания нашего модуля!
Вывод продукции на страницах сайта
Как мы уже говорили, нам необходимо создать функцию DoAction
в файле модуля Catlist.module.php
– эта функция вызывается в тот момент, когда CMSMS встречает в тексте страницы тэг {cms_module module='Catlist'}
, и возвращает в качестве своего значения html-код (в нашем случае – это список всей продукции из базы данных), которым CMSMS заместит «вызывающий» тэг. Добавьте эту функцию в класс Catlist
в файл Catlist.module.php
:
function DoAction($action, $id, $params, $returnid=-1) { }
Пока, мы не будем рассматривать параметры функции, но во избежание исключительной ситуации они должны присутствовать в определении функции. Функции возвращает различный результат в зависимости от обстоятельств, обуславливающих ее вызов (например, на обычной странице сайта она должна вернуть простой перечень продукции в каталоге, а в панели управления – форму с возможностью изменять данные в каталоге). Наша функция узнает, что от нее требуют по переданному ей при вызове значению параметра $action
. Мы же хотим, чтобы наша функция реагировала лишь на появление тэга модуля в тексте страницы, и это действие имеет стандартное имя default
. Так что, давайте немного расширим функционал функции DoAction
:
function DoAction($action, $id, $params, $returnid=-1) { if ($action == 'default') { $db =& $this->GetDb(); $sql = 'SELECT * FROM ' . cms_db_prefix().'module_catlist_products;'; $dbresult =& $db->Execute($sql); $list = "<br /><br />\n"; $list .= "<ul>\n"; while ($dbresult && $row = $dbresult->FetchRow()) { $list .= "<li><b>" . $row['name'] . '</b><br />'; $list .= $row['description'] . '<br />'; $list .= money_format('%n', $row['price']) . '<br />'; $list .= "</li>\n"; } $list .= "</ul>\n"; $list .= "<br /><br />\n"; } // Передать в Smarty global $gCms; $this->smarty->assign('list', $list); /** * Вставьте: {cms_module module='Catlist'}{$list} в ваш шаблон * Далее будет представлен способ сделать это без {$list}... **/ return; }
Этот код запрашивает из базы данных информацию из таблицы, в которой хранится информация о продукции. Затем перебирает в цикле результаты запроса, добавляя их к результирующему html-тексту. Чтобы посмотреть на результат, Вы должны добавить один-два пробных продукта в базу данных. Так как у нас пока нет административного интерфейса, из которого мы могли бы это сделать, Вы должны вставить пару записей непосредственно в базу данных (с помощью phpMyAdmin или подобное средство). Далее, войдите в панель управления сайтом и зайдите на страницу Модули (пункт пеню Расширения), найдите в появившемся списке наш модуль Catlist и нажмите «Установить». Далее, добавьте новую страницу контента (или отредактируйте существующую) и добавьте в поле Контент тэг нашего модуля – {cms_module module='Catlist'}{$list}
. Сохраните страницу и просмотрите ее на своем сайте. Та-дам! Вы должны увидеть список продуктов, которые добавили в базу данных.
Использование шаблонов Smarty
Теперь, мы научимся использовать мощный механизм шаблонизатора Smarty. Цель этой части руководства – отделить ядро модуля (то как это работает) от представления (то как это показывается).
Для начала, мы должны создать новую директорию templates
.
Внутри этой директории мы будем размещать все шаблоны, которые будут использоваться исключительно нашим модулем.
Давайте создадим новый файл display.tpl
, в который вставим следующий фрагмент кода:
<br /> <br /> <ul> {section name=element loop=$list} <li><b>{$list[element].name}</b><br /> {$list[element].description}<br /> {$list[element].price} €<br /></li> {/section} </ul> <br /> <br />
Этот шаблон использует синтаксис Smarty.
Строчка <li><b>{$list[element].name}</b><br />{$list[element].description}<br />{$list[element].price} €<br /></li>
выводит наименование, описание и цену текущего элемента, а строчка {section name=element loop=$list}
делает это циклом для каждого элемента из списка $list
.
Теперь, когда у нас есть шаблон, необходимо передать все элементы в шаблон для их вывода. Для этого изменим следующим образом метод DoAction
:
function DoAction($action, $id, $params, $returnid=-1) { if ($action == 'default') { $db =& $this->GetDb(); $sql = 'SELECT * FROM ' . cms_db_prefix().'module_catlist_products;'; $dbresult =& $db->Execute($sql); // Создать новый массив всех продуктов $list = array(); // Для каждого продукта из базы данных while ($dbresult && $row = $dbresult->FetchRow()) { // Получить текущее количество элементов в массиве $i = count($list); // Добавить текущий продукт в массив $list[$i]['name'] = $row['name']; $list[$i]['description'] = $row['description']; $list[$i]['price'] = $row['price']; } // Передать массив элементов в Smarty $this->smarty->assign('list', $list); // Вывести заполненный данными шаблон echo $this->ProcessTemplate('display.tpl'); } return; }
Теперь можно удалить из текста страницы избыточный тэг {$list}
, оставив лишь аккуратный {cms_module module='Catlist'}
.
Создание административного интерфейса
Что за модуль без интерфейса управления? В этой части руководства мы научимся это делать.
Как мы уже знаем, контент модуля на пользовательской странице генерируется в результате события default
. В отношении содержания модуля в панели управления все точно так же с той лишь разницей, что событие называется defaultadmin
. Так, давайте напишем код, управляющий выводом перечня продукции.
Для начала, расширим функционал функции DoAction
с помощью следующего кода:
function DoAction($action, $id, $params, $returnid=-1) { if ($action == 'default') { // код из предыдущей части } if ($action == 'defaultadmin') { // код для панели управления будет располагаться здесь } return; }
Итак, что же мы будем выводить в нашей панели управления? В первую очередь нам нужны основные функции:
- Добавление, редактирование и удаление продуктов из базы данных.
- Редактирование шаблона, с помощью которого продукты выводятся на страницах сайта.
В целях удобства мы разделим страницу на две вкладки – на одной расположим список продукции, а на второй разместим поле редактирования шаблона.
Эти вкладки могут быть созданы с помощью следующего кода:
// Выбираем активную вкладку. Если активная вкладка не задана параметром, // то выбираем вкладку 'shows' как наиболее частоиспользуемую. if (!empty($params['active_tab'])) $tab = $params['active_tab']; else $tab = 'shows'; // Выводим все вкладки. Чтобы код был аккуратнее, сначала задаем // настройки вкладок, потом включаем файлы вида function.{вкладка}.php, // которые содержат код каждой вкладки. echo $this->StartTabHeaders(); echo $this->SetTabHeader('shows', 'Shows', $tab == 'shows' ? true : false); echo $this->SetTabHeader('template', 'Template', $tab == 'template' ? true : false); echo $this->EndTabHeaders(); // Вывести содержание каждой вкладки echo $this->StartTabContent(); echo $this->StartTab('shows'); include 'function.shows.php'; echo $this->EndTab(); echo $this->StartTab('template'); include 'function.template.php'; echo $this->EndTab(); echo $this->EndTabContent();
ОК, разберем что мы сделали:
- Переменная
$tab
хранит имя вкладки, которая должна быть активна. Может быть так, что – например, после сохранения шаблона, – необходимо отобразить вкладку шаблона, а не стандартную – со списком продукции. Поэтому мы проверяем передано ли значение параметраactive_tab
и сохраняем его значение в переменной$tab
. При установки заголовков вкладок мы проверяем является ли каждая из них активной, и, если это так, делаем ее активной. - Для вывода вкладок мы должны сначала вывести их заголовки (параметры функции
SetTabHeader
: системное имя, отображаемый заголовок, флаг активности). - И, наконец, мы выводим содержание вкладок. Чтобы сделать код аккуратнее, мы выделили код содержания вкладок в отдельные файлы.
Чтобы страница администрирования модуля была включена в меню панели управления, в файле ИмяМодуля.module.php
должны быть определены функции HasAdmin()
, GetAdminSection()
, VisibleToAdminUser()
и GetFriendlyName()
. Функция GetFriendlyName()
была приведена ранее, реализация остальных представлены ниже:
/*--------------------------------------------------------- HasAdmin() Функция возвращает true или false в зависимости от того, нужна ли ссылка на страницу администрирования модуля в меню панели управления или нет. ---------------------------------------------------------*/ function HasAdmin() { return true; } /*--------------------------------------------------------- GetAdminSection() Функция возвращает название пункта меню панели управления, под которым должна размещаться ссылка на страницу администрирования модуля. Допустимые значения: 'content', 'extensions', 'usergroups' и другие. ---------------------------------------------------------*/ function GetAdminSection() { return 'extensions'; } /*--------------------------------------------------------- VisibleToAdminUser() Depending on permissions, tell whether the menuitem can be shown. ---------------------------------------------------------*/ function VisibleToAdminUser() { return true; }
Продолжение следует...