Создание простого плагина. Пошаговая инструкция для новичков. Часть 2

Это продолжение статьи для новичков о пошаговом создании простого плагина для Livestreet.
Часть 1.

7. Конфигурируем модуль
Модуль (Module) — класс, который удобно использовать как прослойку между контроллером (Экшином) и Маппером. Это удобно, если есть необходимость предварительной обработки получаемых/отправляемых данных. Так же, в Модуле удобно хранить какие-то свои функции, т.е., использовать этот класс, как библиотеку.

Откроем файл Модуля
livestreet/plugins/test/classes/modules/test/Test.class.php

Если вы ничего не изменяли, то класс будет пустым.

a) Создадим свойство класса $oMapper, в котором будет храниться объект Маппера:
добавим после объявления класса следующий код:
protected $oMapper;


b) Создадим метод инициализации класса:
public function Init() {

    $this->oMapper=Engine::GetMapper(__CLASS__);

}

Этот метод записывает в свойство класса $oMapper созданный объект Маппера.

c) Создадим методы createBook и getBooks
<?php

class PluginTest_ModuleTest extends Module {

    protected $oMapper;

    public function Init() {
        $this->oMapper=Engine::GetMapper(__CLASS__);
    }

    public function createBook($post){
        //Проходимся по массиву $post с данными
        //будущего объекта и очищаем от html-сущностей
        array_walk($post,function($field){
            htmlspecialchars($field);
        });

        //создаем объект сущности Book
        $book = Engine::GetEntity('PluginTest_ModuleTest_EntityBook');
        $book->setName($post['book_name']);
        $book->setAuthor($post['book_author']);
        $book->setDescription($post['book_description']);

        //Объект book готов, теперь можем передать его
        // Мапперу и записать в базу данных
        if ($iId=$this->oMapper->createBook($book)) {
            // если сохранение в БД прошло успешно,
            //возвращаем ID новосозданного объекта
            $book->setId($iId);
            return $iId;
        }
        // если что-то пошло не так, возвращаем false
        return false;
    }

    public function getBooks() {
        // Просто проксируем запрос от Экшина к Мапперу
        if($res=$this->oMapper->getBooks()){
            return $res;
        }
        return false;
    }
}
?>


8. Конфигурируем сущность (Entity)
Переходим в папку
livestreet/plugins/test/classes/modules/test/entity

и создаем файл класса Сущности(Entity)
Book.entity.class.php


Содержимое файла:
<?php

    class PluginTest_ModuleTest_EntityBook extends Entity
{

}

?>



Да, это пустой класс. Без единого метода. Нам этого достаточно :-)

9. Конфигурируем маппер (Mapper)
Переходим в папку
livestreet/plugins/test/classes/modules/test/mapper

и создаем файл класса Маппера
Test.mapper.class.php


Содержимое файла:
<?php


class PluginTest_ModuleTest_MapperTest extends Mapper
{

    /**
     * Записываем в базу данных новый объект Book или обновляем существующий

     * @param PluginTest_ModuleTest_EntityBook $oBook

     * @return array|bool

     */

    public function createBook(PluginTest_ModuleTest_EntityBook $oBook){

        $sql = "INSERT INTO ".Config::Get('plugin.test.table.test_table')."

			(

			name,

			author,

			description

			)

			VALUES(?, ?, ?)

		";

        if ($iId=$this->oDb->query(

            $sql,

            $oBook->getName(), $oBook->getAuthor(), $oBook->getDescription()

        ))

        {

            return $iId;

        }
        return false;

    }


    /**
     * Делаем выборку всех записей Book из таблицы

     * @return array

     */
 
   public function getBooks(){

        $sql = "SELECT * FROM ".Config::Get('plugin.test.table.test_table');

        $aResult=array();

        if ($aRows=$this->oDb->select($sql)) {

            // На основании каждой строки результата выборки создаем объект

            // класса Book. Это иногда удобно. Но можно и просто возвращать

            // именованный массив.

            foreach ($aRows as $aRow) {

                $aResult[]=Engine::GetEntity('PluginTest_ModuleTest_EntityBook',$aRow);

            }

        }

        return $aResult;

    }
}

?>


Мы создали простенький Маппер для сохранения объекта Book в БД и получения всех объектов Book из БД по запросу.

10. Конфигурируем шаблон (Template)
Перейдем в папку
livestreet/plugins/page/templates

и создадим следующую иерархию папок:


В папке
ActionTest
создадим два файла:

index.tpl и books.tpl

index.tpl — отвечает за отображение данных, обработанных Экшином index, books.tpl отвечает за отображение данных, обработанных экшином books. Эти файлы автоматически подключаются движком LS при обработке соответствующих Экшинов.

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

Содержание файла index.tpl:

{include file='header.tpl'}


<h1>{$aLang.plugin.test.form_title}</h1>

{* Форма ввода данных книги *}

<form action="" method="POST">

	<input type="hidden" name="security_ls_key" value="{$LIVESTREET_SECURITY_KEY}" />


	<p><label for="book_name">{$aLang.plugin.test.book_name}:</label>

		<input type="text" id="book_name" class="input-text input-width-full" name="book_name" value="" class="input-wide" />
	</p>


	<p><label for="book_author">{$aLang.plugin.test.book_author}:</label>

		<input type="text" id="book_author" class="input-text input-width-full" name="book_author" value="" class="input-wide" />
	</p>


	<label for="book_description">{$aLang.plugin.test.book_description}:</label>

	<textarea name="book_description" id="book_description" rows="20" class="input-width-full"></textarea><br />


	<p>

		<button type="submit" class="button button-primary" name="submit_book_save">{$aLang.plugin.test.bt_save_book}</button>

	</p>
</form>


{* Парсинг массива объектов $aBooks, переданных в шаблон в Экшине.
	Проходимся по каждому элемну массива объектов $aBooks и выводим свойства в таблицу
*}

<table cellspacing="0" class="table">

	<thead>
	
        <tr>

		<th width="180px">{$aLang.plugin.test.table_title}</th>

		<th align="center">{$aLang.plugin.test.table_id}</th>

		<th align="center">{$aLang.plugin.test.table_name}</th>

		<th align="center">{$aLang.plugin.test.table_author}</th>

		<th align="center">{$aLang.plugin.test.table_description}</th>

	</tr>

	</thead>
	<tbody>

	{foreach from=$aBooks item=oBook}

		<tr>

			<td>{$oBook->getId()}</td>

			<td>{$oBook->getName()}</td>

			<td>{$oBook->getAuthor()}</td>

			<td>{$oBook->getDescription()}</td>

		</tr>

	{/foreach}

	</tbody>

</table>

{* Подключаем темплейт футера *}
{include file='footer.tpl'}


Содержание файла index.tpl:

{include file='header.tpl'}


<h1>{$aLang.plugin.test.books_page_title}</h1>

{* Парсинг массива объектов $aBooks, переданных в шаблон в Экшине.

	Проходимся по каждому элемну массива объектов $aBooks и выводим свойства в таблицу
*}

<table cellspacing="0" class="table">

	<thead>

	<tr>

		<th width="180px">{$aLang.plugin.test.table_title}</th>

		<th align="center">{$aLang.plugin.test.table_id}</th>

		<th align="center">{$aLang.plugin.test.table_name}</th>

		<th align="center">{$aLang.plugin.test.table_author}</th>

		<th align="center">{$aLang.plugin.test.table_description}</th>

	</tr>

	</thead>

	<tbody>

	{foreach from=$aBooks item=oBook}
            <tr>

		<td>{$oBook->getId()}</td>

		<td>{$oBook->getName()}</td>

		<td>{$oBook->getAuthor()}</td>

		<td>{$oBook->getDescription()}</td>

		</tr>

	{/foreach}

	</tbody>

</table>


{* Подключаем темплейт футера *}

{include file='footer.tpl'}


Структура папок и размещение файлов шаблона:


11. Конфигурируем языковой файл
Откроем языковой файл
livestreet/plugins/test/templates/language/russian.php

Языковой файл состоит из именованного массива, где ключем выступает название переменной, выводимой в шаблоне, а значением — соответствующий текст.
Нам необходимо пройтись по файлам шаблона, и определить все языковые переменные.
Содержание языкового файла:
<?php
/**
 * Русский языковой файл плагина
 */
return array(
    'form_title' => 'Добавление новой книги',
    'book_name' => 'Название книги',
    'book_author' => 'Автор',
    'book_description' => 'Описание',
    'bt_save_book' => 'Сохранить книгу',
    'table_title' => 'Список книг',
    'table_name' => 'Название',
    'table_author' => 'Автор',
    'table_description' => 'Описание',

    'books_page_title' => 'Список сохраненных книг',
);

?>


Заключение
Плагин готов. Теперь осталось проверить его работоспособность,

a) Активируем плагин.
— Для этого перейдем в админку установленных плагинов: ваш_сайт.tlt/admin/plugins/
— И активируем наш плагин найдя его в списке плагинов.

b) Проверим успешность создания таблицы плагина.
— Откроем нашу БД, например и убедимся, что таблица prefix__test_tablle была создана.

с) Перейдем на стнаницу плагина ваш_сайт_ls.tld/test и введя данные, сохраним книгу.

d) Перейдем на страницу вывода списка книг ваш_сайт_ls.tld/test/books



UPD1:
Участник сообщества PSNet указал на 2 коварные ошибки/оплошности в данной части руководства:
1. Файлы шаблонов *.tpl
Ни в коем случае не нужно добавлять комментарии в шаблонах перед подключением шаблона header.tpl:

Неправильно:
{* какой-то комментарий *}
{include file='header.tpl'}




Правильно:
{include file='header.tpl'}



Перед подключением шаблона header.tpl не должно быть никаких символов, включая enter, пробел и т.п.

2. Проверка ключа LIVESTREET_SECURITY_KEY для отправляемых форм.
Это — защита от CSRF атак. Это необходимо делать всегда.
Для этого необходимо:
a) Добавить в форму скрытое поле
<input type="hidden" name="security_ls_key" value="{$LIVESTREET_SECURITY_KEY}" />

b) Добавить в соответствующий контроллер (Экшин) проверку формы (данная операция делается в ч.1 данной статьи):
$this->Security_ValidateSendForm();

14 комментариев

avatar
{* Подключаем темплейт хедера *}
{include file='header.tpl'}

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

добавьте в экшен при добавлении формы проверку ключа security_ls_key и напишите что так нужно делать всегда
avatar
По первому да, согласен, комментарий добавлял уже тут, в редакторе. Моя оплошность.
Про ключ — тоже согласен.

Отредактирую утром уже. Большое спасибо за уточнения!
avatar
почему то когда нет книг то вот так
Fatal error: Call to a member function getId() on a non-object in D:\*****\dc39eca987ad1eaac2deab28163c98ce210d1a65.file.books.tpl.php on line 55
avatar
поправка, если нет вообще книг то такая ошибка
avatar
Надо проверить что лежит в массиве $aBooks.
avatar
ну я думаю что это связано с тем что выводить нечего т.к не создан объект, то получается нужно ранее как то завести объект который будет с нулевыми переменными чтоле, поправьте если не прав
avatar
в book.tpl перед foreach надо сделать проверку
{if $aBooks}
{foreach from=$aBooks item=oBook}
<tr>

	<td>{$oBook->getId()}</td>
	<td>{$oBook->getName()}</td>

	<td>{$oBook->getAuthor()}</td>
	<td>{$oBook->getDescription()}</td>
</tr>
{/foreach}
{else}
<tr>

	<td colspan="4">Список книг пуст.</td>
</tr>
{/if}
avatar
точно, спасиб
avatar
Спасибо за подробный мануал. А как сделать плагин который будет расширять методы классов движка, модули, может в шаблоне будет что дополнять? Подозреваю что нужно сделать функцию, которая будет по адресу находить нужную строчку и оборачивать или может вставлять свою строчку с кодом.
avatar
Самый простой вариант — скачайте пару бесплатных плагинов из каталога, и посмотрите, как они устроены )
avatar
Тоже так хотел сделать, просто хотел узнать good practices)
avatar
я, честно говоря, сам изучаю двожок методом проб и ошибок :-)
avatar
В п.10 очепятка: «Содержание файла index.tpl:» указано дважды )
Спасибо автору за труд — отличная статья!
avatar
Günlük kiralık ev konusunda hizmet veren bir firmayız. Siz değerli müşterilerimizin yaşamlarına kalite ve değer katmak üzere kurularak gayrimenkul sektöründe öncü hizmet kalitesinden ödün vermeyen prensip ilkesiyle sizlere profesyonel hizmet vermektedir. Müşterilerinin ihtiyaçlarını tespit ederek uygun fiyatları ve kalitesiyle “ müşteri memnuniyeti “ hedefine ulaşmıştır
Web Sitemiz: gunlukdairem.com
İyi günler dileriz.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.