Как создать свой модуль (пример)

В данном разделе документации рассказано как создать модуль для diafan.CMS. На первый взгляд это легко, но, как и везде, есть подводные камни. Приложение к данному разделу - шаблон модуля, который можно скачать по этой ссылке.

Cтруктура модуля diafan.CMS

В папке admin находится административная часть модуля. Пока она состоит из единственного файла example.admin.php. В корне расположены три файла example.model.php – example.view.php – example.php «Модель-представление-контроллер».

Цитата из wikipedia

Концепция MVC позволяет разделить данные, представление и обработку действий пользователя на три отдельных компонента:

  • Модель (англ. Model). Модель предоставляет знания: данные и методы работы с этими данными, реагирует на запросы, изменяя своё состояние. Не содержит информации, как эти знания можно визуализировать.
  • Представление, вид (англ. View). Отвечает за отображение информации (визуализация). Часто в качестве представления выступает форма (окно) с графическими элементами.
  • Контроллер (англ. Controller). Обеспечивает связь между пользователем и системой: контролирует ввод данных пользователем и использует модель и представление для реализации необходимой реакции.

Для того чтобы подключить наш сырой модуль к diafan.CMS необходимо на вашем сайте открыть служебную страницу http://site.ru/admin/adminsite/ - «Страницы админки» и добавить новую страницу с модулем

Обратите внимание на псевдоссылку, она должна называться так же, как и модуль!

Следующим шагом будет добавление нашего модуля на какую-нибудь страницу сайта. Идем в админку, создаем обычную текстовую страницу и во вкладке "Дополнительно" прикрепляем к ней наш новый модуль example.

Все, модуль готов к использованию!

Правда пока он ничего не делает, поэтому давайте сделаем его функциональным.
Давайте сделаем модуль, который будет позволять размещать объявления на сайте.

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

Создаем таблицу в БД

Для начала определимся с задачами, который должен выполнять модуль:

  • позволять зарегистрированным пользователям сайта добавлять объявления
  • позволять администратору редактировать объявления
  • позволять администратору размещать объявления

Объявления будут размещены в базе данных в таблице prefix_example, и нам необходимо разработать ее структуру.

Префикс по умолчанию - diafan_. Вы могли переименовать его при установке diafan.CMS, поэтому создавайте таблицу по подобию других таблиц в БД.

Для работы с БД можно использовать PHPMyAdmin, который обычно есть на хостинге.

user_id - id нашего зарегистрированного пользователя, который добавил объявление
created - время создания и
text - текст объявления

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

Панель администрирования

Начнем разработку нашего модуля с админки, перед этим не забыв включить «Режим разработки» в параметрах сайта, чтобы видеть лог наших возможных ошибок.

Обязательно включайте режим разработки и отключайте кеширование в настройках сайта при любых правках любых модулей.

Если открыть админку нашего нового модуля, мы ничего не увидим, потому, что мы ничего не описали. Когда административная часть diafan.CMS подключает администрирование модуля, то ядро системы сразу автоматически подключает для управления информацией модуля все действия. В модуле для начала достаточто просто описать, что и как надо выводить и что редактировать.

Откроем файл example.admin.php и опишем поля нашей таблицы в массиве variable_table.

	$this->variable_table = array(
	 'user_id'=>'select', // по нашей задумке это идентификатор пользователя
	 'created'=>'datetime',
	 'text'=>'editor'
	 );

Описывая эти поля мы показываем diafan.CMS, что мы хотим править и в каком виде. Для редактирования пользователя удобнее сделать выпадающий список, где были бы имена зарегистрированных пользователей. Поэтому мы указали, что для управления полем user_id нам нужен тип (select). Поскольку мы указали этот тип, в виде выпадающего списка, то нам тогда нужно сделать массив $this->select_arr['user_id'] и заполнить его значениями, чтобы выпадающему списку было что выводить. Значения, конечно, это имена пользователей из таблицы prefix_users_site. Для этого в функции конструктора класса допишем нехитрые строчки кода:

	$this->select_arr['user_id']=array();
	$res=DB::query("SELECT id,name FROM {users_site}");  // запросик в таблицу пользователей, чтобы вытянуть имена
	while($row=DB::fetch_array($res)) 
	{
	 $this->select_arr['user_id'][$row['id']]=$row['name'];
	}

Все, этого вполне достаточно чтобы работало добавление в административной панели по нажатию "Добавить" (http://site.ru/admin/example/addnew/)! Можно добавить и сохранить объявление. Заметьте, что порядок следования пунктов повторяется и на странице в админке.

Как вы могли заметить названия полей "Пользователь, Дата, Описание" появились сами собой, но это только так кажется. Так получилось потому, что наши имена совпали с описанными ранее константами. Они считываются из файла /language/ru.php, где определены константы

define('_ADMIN_USER_ID', 'Пользователь');
define('_ADMIN_CREATED', 'Дата');
define('_ADMIN_TEXT', 'Описание');

А вот если бы в variable_table вы прописали новое поле, например

$this->variable_table['salat']='text';

ничего бы не вывелось. Для того чтобы добавить описание поля нужно в языковом файле /language/ru.php добавить новую константу по схеме
define('_ADMIN_{ИМЯ МОДУЛЯ}_{ИМЯ ПЕРЕМЕННОЙ}', '{Название}');
в данном случае нужно было бы добавить define('_ADMIN_EXAMPLE_SALAT', 'Салат');, так как мы работаем с модулем example

Так как из административной панели тестовое объявление мы добавили, давайте теперь заниматься отображением списка объявлений. Ведь здесь http://site.ru/admin/example/ пока ничего не выводится, хотя в базе данных уже красуется наше тестовое объявление. Все делается просто - достаточно объявить переменную $this->name='text'; в функции конструктора класса и мы увидим наше объявление! diafan.CMS сформирует привычный список и если мы добавим несколько объявлений, они все будут в виде списка.

Для пущей красоты и информативности, помимо текста, давайте добавим дополнительные поля "дата создания" и "пользователя", которому объявление принадлежит. Для этого объявим еще одну системную переменную config_other_row:

$this->config_other_row = array('created','user_id');

Ура, теперь у нас в списке выводится объявление, время добавления и id пользователя.

Как вы видите, функция list_row выводит поля из базы данных в том виде, в котором они в нее записаны (дату в unix-формате), но это можно легко исправить!

Напишем две простые функции, переопределяющие системное отображение. Формат названия функции таков: other_row_переменная($row), где $row – массив значений для текущего элемента:

 protected function other_row_created($row)
 {
	return '<td>'.date("d.m.Y", $row['created']).'</td>';
 }
 
 protected function other_row_user_id($row)
 {
	return '<td>'.DB::query_result("SELECT name FROM {users_site} WHERE id='%d' LIMIT 1",$row['user_id']).'</td>';
 }

То есть, если diafan.CMS обнаруживает эти функции с названием по маске other_row_***, она понимает, что поля created и user_id нужно выводить так, как описано в этих функциях.

Результат налицо

На данном этапе мы редактировать объявления можем, но прочих функций в списке нет. Если нам нужно иметь возможность удалять объявления из списка, то для этого нужно просто прописать в массиве config_module параметр del

$this->config_module['del']=1;
(ознакомиться с полным списком стандартных параметров можно в документации)

Кстати если бы поле created мы бы назвали data то нам достаточно было бы, просто, в config_module добавить $this->config_module['datetime']=1;

Идем далее. Расширим функциональность нашего модуля, чтобы наши объявления сортировались, удалялись и активировались по нашему велению.
В базу данных добавим поля удаления в корзину, активности и сортировки:

И пропишем в config_module
$this->config_module = array('del'=>1,'act'=>1,'order'=>1,'trash'=>1);

Теперь у нас в списке объявлений появятся иконки удаления, активности и их можно будет сотрировать.

По умолчанию в списке выводится по 20 объявлений на странице. Если это мало, нужно в конструкторе класса переопределить переменную $this->nastr=150; (которая в ядре сайта по умолчанию =20) и теперь на странице будет показываться 150 объявлений.

Также не стоит забывать про переменную $this->where, которая позволяет нам добавлять собственные параметры к выборке фунукции list_row.

Давайте с ее помощью для примера организуем перед списком фильтр объявлений по имени пользователя.


За отображение административной части модуля, как вы могли уже заметить отвечать функция show_module с ней и будем работать.

Сделаем простую форму с GET запросом

public function show_module()
 {
	$this->addnew_init('example');

	echo '<form method="GET">'._ADMIN_USER_ID.': 
	 <select name="user"><option value="">'._LANG_ALL.'</option>';

	foreach ($this->select_arr['user_id'] as $value => $name)
	{
	 echo '<option value="'.$value.'">'.$name.'</option>';
	}

	echo '</select><input type="submit" value=" OK "></form>';
	
	if(!empty($_GET['user']))
	{
	 $this->where="AND user_id='".$this->diafan->get_param($_GET,'user','',2)."'";
	}

	echo '<table class="list">';
	$this->list_row();
	echo '</table>';

	return TRUE;
 }

В общем, вид представления административной части модуля, зависит лишь от вашей фантазии. Никто не принуждает вас использовать функция list_row, вы можете написать любой собственный обработчик.

Скачиваем пример файла example.admin.php

Мощным средством расширения функционала модуля является тип function.

Он позволяет определять пользовательские функции для редактирования и сохранения поля. Давайте сделаем так, чтобы при редактировании user_id имя пользователя у нас было не просто выпадающим списком, а выбиралось с помощью AJAX запроса:

Переназначим variable_table, укажем для user_id не select, а function

$this->variable_table = array(
	 'user_id' => 'function',
	 'created' => 'datetime',
	 'text' => 'editor'
	);

И объявим 2 функции

 protected function edit_user_id_module()
 {
	return TRUE;
 }

 protected function save_user_id_module()
 {
	return TRUE;
 }

Займемся визуальным отображением редактирования поля user_id и будем править функцию edit_user_id_module. Выведем то, что нам передается в функцию, и посмотрим, что есть там интересного

print_r($this);

[edit] => 2 // параметр id редактируемого элемента передаваемый в URL 

// все значения этого поля из таблицы
[values] => Array
 (
 [id] => 2
 [user_id] => 2
 [text] => 

тестовое объявление

[created] => 1322127780 [trash] => 0 [act] => 1 [sort] => 2 ) // текущее поле и его значение из базы данных [key] => user_id [value] => 2

Раздолья для фантазии много, но мы напишем простой jquery обработчик, отправляющий AJAX запрос

protected function edit_user_id_module()
 {
	echo '
<script type="text/javascript">
$(document).ready(function(){
 $(".userid").click(function(){
	$.ajax({
			url : window.location.href,
			type : "POST",
			dataType : "json",
			data : { module: \''.$this->diafan->module.'\', action: 1, user_id: $(this).attr("rel") },
			success : (function(response)
			{
				alert(response.name);
			})
		});
 });
});
</script>
<td class="td_first">Нажми</td><td><div class="userid" rel="'.$this->value.'"><b>ЗДЕСЬ</b></div></td>';
	return TRUE;
 }

Обработчик по нажатию на элемент с классом userid отправляет AJAX запрос текущей странице с параметрами module, action и user_id.

Теперь нам нужно написать example.admin.ajax.php (скачайте example.admin.function.zip) в котором собственно и будет происходить обработка запроса. example.admin.ajax.php подключается лишь тогда когда одновременно отправляются POST переменные action и module! Module должен соответствовать названию нашего модуля.

Часть кода AJAX обработчика (полный код смотрите в скачанном файле)

if (!empty($_POST['user_id']))
	{
	 $this->result["name"] = DB::query_result("SELECT name FROM {users_site} WHERE id='%d' LIMIT 1", $_POST['user_id']);
	}
…
$this->send_json();

Соответственно в функции save_user_id_module обрабатывается сохранение поля. Тут вы можете вставлять данные в другую таблицу, обрабатывать самостоятельно переменные $_POST, дважды досчитать до бесконечности.

Для того чтобы записать данные в таблицу example, которую использует наш модуль можно сделать такую хитрость. При сохранении diafan.CMS использует 2 массива $this->query и $this->value. В нашем случае в функцию достаточно дописать

$this->query[] = "user_id='%d'";
$this->value[] = “1”;

Таким образом, добавив к SQL запросу UPDATE поле user_id со значением 1.

Еще одной интересной переменной является массив $this->oldrow в котором находится текущая выборка из базы данных.

Как вы могли сами увидеть, function предоставляет огромные возможности для расширения функционала административной части модуля, все зависит от вашей фантазии и правильного крепления рук к телу.

Еще есть замечательный тип в variable_table, module, он гласит, что необходимо подключить к редактированию сторонний модуль.

Как это работает.

Там где нужно подключить модуль example, в его file.admin.php в variable_table прописываем

$this->variable_table[‘example’]=’module’;

При таком раскладе при редактировании элемента модуля file diafan.CMS будет искать файлы

/modules/example/admin/example.admin.edit.php

/modules/example/admin/example.admin.save.php

/modules/example/admin/example.admin.del.php

Подключать их и обрабатывать. Это работает практически так же как и тип function, только между модулями. Семплы обработчиков можно скачать здесь.

Вкладки для части модуля и кофигурация

В diafan.CMS административную часть модуля можно еще дополнять различными расширениями, вкладками (подразделами модуля, например, как заказы в магазине). Загляните в папку /modules/shop/admin

Делается это так: создается файл example.admin.bolt.php и в нем инициализируется класс

class Example_admin_bolt extends Frame_admin

Потом мы идем в http://site.ru/admin/adminsite и добавляем подраздел нашему модулю с псевдоссылкой /example/bolt/. Количество расширений модуля и их вложенность зависят лишь от вашей фантазии.

example.admin.nano.bolt.php

class Example_admin_nano_bolt extends Frame_admin

/example/nano/bolt

Есть еще особый тип расширения – конфигурация

Семпл example.admin.config.php скачать можно здесь

Регистрируется он так же как и обычное расширение: в site.ru/admin/adminsite/ добавляем подраздел «Настройки» с псевдоссылкой «example/config»

Основной его отличительной особенностью является переменная config_module

$this->config_module = array(
             'config' => 1 //файл настроек модуля
);

Если она установлена, то сохранение происходит не в таблицу указанную в $this->table, а в таблицу {config}, для доступа к которой предусмотрена специальная функция ядра configmodules, которую можно вызвать, откуда угодно.

Пользовательская часть

Если мы подключили модуль к странице сайта, то при ее открытии из конструктора example.php вызывается функция show_module. Как вы можете видеть из примера при инициализации модуля подключается модель результат исполнения записывается в переменную $this->result, а после в show_module вызывается функция show из файла example.view.php

Функция контролера (example.php) init выполняется всегда при подключении модуля. В ней описывается логика выбора модели (example.model.php). Для начала рассмотрим самые базовые возможности.

Массив rewrite_variable_names – в нем описываются имена, которые будут считаны из URL, как переменные.

    public function init()
    {
	$this->rewrite_variable_names = array('page', 'show', 'cat');
	$this->diafan->rewrite_variable_names = $this->rewrite_variable_names;
…	
	$this->get_global_variables();
    }
    

http://site.ru/example/page5/

Полный список доступных переменных смотрите здесь http://cms.diafan.ru/dokument/full-manual/developers/architecture/

Если позарез нужна своя переменная передаваемая через URL, или просто GET - это не кошерно, можно залезть в /includes/init.php и в строке 117 дополнить массив rewrite_variable_names_all.

Шаблонный тег

Теперь модуль мы умеем выводить (функция show_module), а ведь есть еще такая тема как шаблонный тег, который можно вставить в основной шаблон сайта либо прописать в контенте страницы сайта.

В документации уже есть раздел про то, как создать свой шаблонный тег.

diafan.CMS не делает никаких ограничений на количество аргументов, и название функции!

Давайте напишем функцию которая будет нам выводить последние объявление

Сначала идем в example.php – контроллер

    public function show_last($attributes)
    {
	// принимаем атрибут count
	$attributes = $this->get_attributes($attributes, 'count');

	// подключаем модель
	include_once(ABSOLUTE_PATH.'modules/example/example.model.php');
	// получаем результат
	$model = new Example_model($this->diafan);
	// не забываем о правиле ВСЕ ПЕРЕМЕННЫЕ ПЕРЕДАННЫЕ ПОЛЬЗОВАТЕЛЕМ, НУЖНО ПРОВЕРЯТЬ!
	$result = $model->show_last(intval($attributes['count']));
	// вывродим шаблон
	$this->diafan->_tpl->get('show_last', 'example', $result);
	return TRUE;
    }
    

example.model.php – модель

public function show_last($count)
    {
	if (!($res = DB::query("SELECT text FROM {example} ORDER BY id DESC".(!empty($count)?' LIMIT '.$count:''))))
	    return FALSE;
	
	$rows = array();
	while ($row = DB::fetch_array($res))
	    $rows[] = $row;
	return $rows;
    }

example.view.php – представление

public function show_last($result)
    {
	if (!$result)
	{
	    echo 'нет объявлений';
	    return FALSE;
	}

	foreach ($result as $row)
	{
	    echo '
'.$row['text'].'
'; } return TRUE; }

Теперь в /themes/site.php либо на любой странице через визуальный редактор (из административной панели) пропишем:

<insert name="show_last" count="2" module="example">

И в этом месте мы увидим 2 последних объявления

Но наш модуль все равно сыроват, при заходе на страницу с прикрепленным модулем мы ничего не видим. Давайте сделаем так – список объявлений разбитый на страницы. При нажатии на объявление оно откроется в отдельном окне в котором можно будет вписать комментарий.

Это не сложно, не пугайтесь :)

    public function show()
    {

	// данные будем кешировать
	$cache_meta = array(
	    "name" => "list", // метка кеша
	    "language" => _LANG,
	    "page" => $this->diafan->page > 1 ? $this->diafan->page : 1,
	    "site_id" => $this->diafan->cid,
	);

	// если данных нет в кеше занесем их
	if (!$this->result = $this->diafan->_cache->get($cache_meta, $this->diafan->module))
	{
	    $this->result = array();

	    ////navigation//
	    // $this->diafan->_paginator->nastr = 1; - берется из конфигурации модуля
	    $this->diafan->_paginator->page = $this->diafan->page;
	    $this->diafan->_paginator->navlink = $this->diafan->_route->current_link("page");
	    $this->diafan->_paginator->navlink_tpl = $this->diafan->_route->current_link("", array("page" => "%d"));
	    $this->diafan->_paginator->nen = DB::query_result("SELECT COUNT(id) FROM {example} WHERE act='1' AND trash='0'");

	    $this->result["paginator"] = $this->diafan->_paginator->get();
	    ////navigation///

	    $result = DB::query_range("SELECT id, created, text FROM {example} WHERE act='1' AND trash='0' ORDER BY created DESC, id DESC", $this->diafan->_paginator->polog, $this->diafan->_paginator->nastr);

	    while ($row = DB::fetch_array($result))
	    {
		$row['created'] = $this->format_date($row['created'], $this->diafan->module, $this->diafan->cid);
		$row['link'] = $this->diafan->_route->link($this->diafan->cid, $this->diafan->module, 0, $row['id']);

		$this->result['rows'][] = $row;
	    }
	    //сохранение кеша
	    $this->diafan->_cache->save($this->result, $cache_meta, $this->diafan->module);
	}

	$this->result["paginator"] = $this->diafan->_tpl->get('get', 'paginator', $this->result["paginator"]);

	return $this->result;
    }
    

Как вы видите по коду здесь, мы использовали кеш. Это необходимо если вы хотите уменьшить нагрузку на базу данных и сократить время отображения страницы.

На время разработки мы всегда рекомендуем вам отключать кеширование в параметрах сайта, потому что по умолчанию кеш сбрасывается лишь при изменении элемента в панели администрирования, то есть все ваши манипуляции с базой данных в файле *.model.php со включенным кешированием вы можете увидеть не сразу.

Накидаем небольшой пример, чтобы было понятнее

	$cache_meta = array(
	    "name" => "trololo", // метка кеша
	    "language" => _LANG,
	    "page" => $this->diafan->page > 1 ? $this->diafan->page : 1,
	    "site_id" => $this->diafan->cid,
	);
	// если кеш сохранен он будет в $data если нет то выполнится условие
	$data=array();
	if(!$data=$this->diafan->_cache->get($cache_meta, $this->diafan->module))
	{
	    $data=array(‘котейка’,’черный’);
	    //сохранение кеша
	    $this->diafan->_cache->save($data, $cache_meta, $this->diafan->module);
	}

Про постраничную навигацию подробнее читайте здесь

Остановимся подробнее на функции format_date эта функция наследуется из класса model и подробно расписана по ссылке выше.

Она форматирует дату в соответствии с конфигурацией модуля.

Давайте добавим нашему модулю в конфигурацию, кроме количества объявлений на странице еще и формат даты!

Создадим файл конфигурации /modules/example/admin/example.admin.config.php и наполним его:

    public function __construct($diafan)
    {
	$this->diafan = $diafan;

	$this->variable_table = array(
	    'nastr' => 'numtext', // количество объявлений выводимой на странице
	    'format_date' => 'select' // формат даты
	);

	$this->select_arr["format_date"] = array(
	    0 => _ADMIN_FORMAT_DATE_0,
	    1 => _ADMIN_FORMAT_DATE_1,
	    2 => _ADMIN_FORMAT_DATE_2,
	    3 => _ADMIN_FORMAT_DATE_3,
	    4 => _ADMIN_FORMAT_DATE_4
	);

	$this->config_module = array(
	    'config' => 1  //файл настроек модуля
	);
    }

Получился функциональный модуль аж с двумя изменяемыми настройками и кешированием. Для начала совсем не плохо, но сделаем еще лучше! Сделаем вывод отдельный объявлений с возможностью добавлять к ним комментарии.

(смотрите файл example.module.comment.zip)

Модель и представление делаем по накатанной программе, а чтобы добавить комментарии необходимо в конфиге (example.admin.config.php) подключить строчку

$this->variable_table['comments' ] = 'module' и в модели вызвать обработчик

	if ($this->diafan->configmodules("comments"))
	{
	    $comments_html = $this->diafan->_comments->get();
	}

Функция configmodules проверяет установлена ли данная переменная в конфигурации модуля и если да, подключает комментарии.



Не понятно? Уточните