Руководство по созданию плагина для v.0.4 на примере "Лента друзей"
Предисловие
Что такое плагины —
По мотивам
Примечание. Долго не публиковал этот материал, держал в черновиках — на случай «мало-ли-что-измениться». Но думаю, кардинальных изменений в механизме плагинов в ближайшем релизе уже не будет.
Пошаговая схема:
1. Создаем директорию и основные файлы плагинаНазовем плагин Friends Feed, но для краткости в названиях файлов и директорий будем использовать просто friends. Поэтому создаем директорию friends в директории plugins.
Вкладываем туда файлы PluginFriends.class.php и plugin.xml
PluginFriend.class.php
<?php
/**
* Плагин предназначен для выведения пользователю списка топиков его друзей
*
* by Alex Kachayev <kachayev@gmail.com>
*/
class PluginFriends extends Plugin {
/**
* Активация плагина
* В принципе, здесь нам делать ничего не нужно
*/
public function Activate() {
return true;
}
/**
* Инициализация плагина
*/
public function Init() {
$this->Viewer_AddMenu('blog',Plugin::GetTemplatePath(__CLASS__).'/menu.blog.tpl');
}
/**
* Деактивация плагина
* В принципе, тут тоже ничего не нужно делать
*/
public function Deactivate() {
return true;
}
}
Примечание. Функции Activate(), Deactivate() можно было и не создавать, добавил просто чтоб напомнить о возможности их использования. В функции Init() «всплывает» достаточно интересный момент — замена стандартного меню, но об этом мы поговорим чуть позже.
Plugin.xml
<?xml version="1.0" encoding="UTF-8"?>
<plugin>
<name>
<lang name="default">Friends Feed</lang>
<lang name="russian">Лента Друзей</lang>
</name>
<author>
<lang name="default">Alex Kachayev</lang>
<lang name="russian">Алексей Качаев</lang>
</author>
<homepage>http://kachayev.ru/</homepage>
<version>0.8.1</version>
<requires>
<livestreet>0.4.0</livestreet>
<plugins>
</plugins>
</requires>
<description>
<lang name="default">Show topics that are wrote by your friends.</lang>
<lang name="russian">Вывод записей друзей отдельной лентой.</lang>
</description>
<delegate>
<module></module>
<action></action>
<template></template>
<entity></entity>
</delegate>
</plugin>
Если все правильно сделали, то с этого момента плагин существует и формально функционирует. Т.е. зайдя в панель управления плагинами (/admin/plugins) вы уже можете увидеть новый «пункт» и произвести активацию\деактивацию. Правда нам это пока ничего не даст, ибо функционала еще не предусмотрено =)
2. Роутинг и конфигурация
Учтем, что лента друзей должна быть доступна по определенному URL. Например, /friends. Значит нам нужно добавить новое правило роутинга. Сделать это нужно в файле конфигурации плагина. Создаем директорию /friends/config/ и вкладываем туда файл config.php — эта конфигурация будет автоматически загружена, если плагин активирован администратором проекта.
Пока от нас требуется только указать новый «пункт» роутинга. Сделаем это вызовом Config::Set(). Для демонстрации работы конфигурации плагина, я также добавлю параметр работы плагина — количество выводимых записей на страницу (в учебных целях, в рабочем проекте я бы воспользовался значением этого параметра для остальных экшенов).
config.php:
/**
* Плагин предназначен для выведения пользователю списка топиков его друзей
*
* by Alex Kachayev <kachayev@gmail.com>
*/
$config['per_page'] = 10; // Число топиков на одну страницу ленты
Config::Set('router.page.friends', 'PluginFriends_ActionFeed');
return $config;
3. Action для вывода записей и модуль
Для начала сделаем модуль для получения записей, написанных друзьями. В папке /plugins/friends/ создаем директорию /classes/modules/friends и вкладываем туда файл Friends.class.php:
<?php
class PluginFriends_Friends extends Module {
public function Init() {
}
/**
* Получает число новых топиков в ленте друзей
*
* @return array
*/
public function GetCountTopicsFriendsNew($sUserId) {
$sDate=date("Y-m-d H:00:00",time()-Config::Get('module.topic.new_time'));
$aFriends = $this->User_GetUsersFriend($sUserId);
if(!is_array($aFriends) or count($aFriends)==0) {
return 0;
}
$aFilter=array(
'user_id' => array_keys($aFriends),
'topic_publish' => 1,
'topic_new' => $sDate,
);
$s=serialize($aFilter);
if (false === ($data = $this->Cache_Get("topic_friend_count_{$s}"))) {
$data = $this->Topic_GetCountTopicsByFilter($aFilter);
$this->Cache_Set($data, "topic_friend_count_{$s}", array('topic_update','topic_new'), 60*5);
}
return $data;
}
/**
* Получает все топики в ленте друзей
*
* @return array
*/
public function GetTopicsFriends($sUserId,$iPage,$iPerPage) {
/**
* Получаем идентификаторы друзей пользователя
*/
$aFriends = $this->User_GetUsersFriend($sUserId);
if(!is_array($aFriends) or count($aFriends)==0) {
return array('count'=>0, 'collection'=>array());
}
$aFilter=array(
'user_id' => array_keys($aFriends),
'topic_publish' => 1,
'blog_type' => array('open','personal')
);
return $this->Topic_GetTopicsByFilter($aFilter,$iPage,$iPerPage);
}
public function Shutdown() {
}
}
?>
Как видите, в этом файле, мы создаем новый модуль PluginFriends_Friends, предназначенный для: а) получения списка топиков в ленте друзей, б) получения количество топиков в ленте друзей. По сути ничего необычно, единственное отличие от простого модуля — приставка в названии с указанием на плагин Friends.
Создаем экшен для обработки вывода записей Ленты Друзей. Для этого в директории /friends/classes/ создаем директорию actions и вкладываем туда файл ActionFeed.class.php:
<?php
class PluginFriends_ActionFeed extends ActionPlugin {
/**
* Главное меню
*
* @var unknown_type
*/
protected $sMenuHeadItemSelect='blog';
/**
* Меню
*
* @var unknown_type
*/
protected $sMenuItemSelect='index';
/**
* Субменю
*
* @var unknown_type
*/
protected $sMenuSubItemSelect='friends';
/**
* Число новых топиков
*
* @var unknown_type
*/
protected $iCountTopicsNew=0;
/**
* Число новых топиков в ленте друзей
*
* @var unknown_type
*/
protected $iCountTopicsFriendsNew=0;
/**
* Число новых топиков в коллективных блогах
*
* @var unknown_type
*/
protected $iCountTopicsCollectiveNew=0;
/**
* Число новых топиков в персональных блогах
*
* @var unknown_type
*/
protected $iCountTopicsPersonalNew=0;
/**
* Инициализация
*
*/
public function Init() {
$this->Viewer_AddBlocks('right',array('stream','tags','blogs'));
/**
* Подсчитываем новые топики
*/
$this->iCountTopicsCollectiveNew=$this->Topic_GetCountTopicsCollectiveNew();
$this->iCountTopicsPersonalNew=$this->Topic_GetCountTopicsPersonalNew();
$this->iCountTopicsFriendsNew=$this->PluginFriends_Friends_GetCountTopicsFriendsNew($this->User_GetUserCurrent()->getId());
$this->iCountTopicsNew=$this->iCountTopicsCollectiveNew+$this->iCountTopicsPersonalNew;
}
/**
* Регистрация евентов
*
*/
protected function RegisterEvent() {
$this->AddEventPreg('/^(page(\d+))?$/i','EventFriends');
}
/**********************************************************************************
************************ РЕАЛИЗАЦИЯ ЭКШЕНА ***************************************
**********************************************************************************
*/
/**
* Реализация евента
*
*/
protected function EventFriends() {
/**
* Меню
*/
$this->sMenuSubItemSelect='friends';
/**
* Передан ли номер страницы
*/
$iPage=$this->GetEventMatch(2) ? $this->GetEventMatch(2) : 1;
/**
* Получаем список топиков
*/
$aResult=$this->PluginFriends_Friends_GetTopicsFriends($this->User_GetUserCurrent()->getId(),1,10);
$aTopics=$aResult['collection'];
/**
* Формируем постраничность
*/
$aPaging=$this->Viewer_MakePaging($aResult['count'],$iPage,10,4,Router::GetPath('friends'));
/**
* Загружаем переменные в шаблон
*/
$this->Viewer_Assign('aTopics',$aTopics);
$this->Viewer_Assign('aPaging',$aPaging);
/**
* Устанавливаем шаблон вывода
*/
$this->SetTemplateAction('friends');
}
/**
* При завершении экшена загружаем переменные в шаблон
*
*/
public function EventShutdown() {
$this->Viewer_Assign('sMenuHeadItemSelect',$this->sMenuHeadItemSelect);
$this->Viewer_Assign('sMenuItemSelect',$this->sMenuItemSelect);
$this->Viewer_Assign('sMenuSubItemSelect',$this->sMenuSubItemSelect);
$this->Viewer_Assign('iCountTopicsNew',$this->iCountTopicsNew);
$this->Viewer_Assign('iCountTopicsFriendsNew',$this->iCountTopicsFriendsNew);
$this->Viewer_Assign('iCountTopicsCollectiveNew',$this->iCountTopicsCollectiveNew);
$this->Viewer_Assign('iCountTopicsPersonalNew',$this->iCountTopicsPersonalNew);
}
}
?>
Это так же самый обычный по виду экшен. Ключевые моменты:
— название PluginFriends_ActionFeed содержит указание на плагин Friends;
— класс экшена унаследован от ActionPlugin.
4. Добавляем шаблоны и пункт меню
Но, сейчас этот экшен работать не будет — нет пока шаблона для работы. С шаблонами есть маленький ньюанс — плагин может использоваться с различными шаблонами (skin). Поэтому чтобы у пользователей не возникало потом проблем с вашим плагином, плагин по умолчанию поддерживает мульти-skin. По умолчанию будет использован skin default. Поскольку наша задача не прорисовать как можно больше вариантов, а посмотреть на общую схему работы, то создаем директорию /templates/skin/default/ и туда вкладываем шаблон нашего экшена: actions/ActionFeed/friends.tpl.
Шаблон максимально простой:
{include file='header.tpl' menu='blog'}
{include file='topic_list.tpl'}
{include file='footer.tpl'}
Осталось последнее — создать пункт меню для попадания в Ленту друзей. Для того, чтобы это сделать нам нужно добавить доп. пункт в меню menu.blog.tpl. Мы сделаем это так: копируем файл menu.blog.tpl из шаблона new движка в директорию /plugins/friends/templates/skin/default/menu.blog.tpl. В этом файле в первую секцию добавим следующий код:
<li {if $sMenuSubItemSelect=='friends'}class="active"{/if}><div><a href="{router page='friends'}">{$aLang.plugin_friends_menu_title}</a>{if $iCountTopicsFriendsNew} +{$iCountTopicsFriendsNew}{/if}</div></li>
Именно это меню подключается в шаблоны в функции инициализации плагина Init() (об этом мы уже говорили):
$this->Viewer_AddMenu('blog',Plugin::GetTemplatePath(__CLASS__).'/menu.blog.tpl');
Последний штрих — языковой файл /templates/language/russian.php с единственной текстовкой:
<?php
/**
* Русский языковой файл плагина Friends
*/
return array(
'plugin_friends_menu_title' => 'Лента друзей'
);
?>
5. The End
Собственно и все. Кажется, ничего не забыл. Если остались вопросы — с радостью отвечу.
105 комментариев
PluginFriends.class.php
Хотя, возможно, стоило бы для «руководства» взять плагин пообширнее охватывающий логику плагинов — с делегированием, например — это ж одна из основных фишек. Думаю в ближайшее время надо найти время что-нибудь из поделок для 0,31 переделать в плагин для 0,4, постараюсь захватить что-нибудь что еще не применяли в примерах.
Поэтому сделал то, что «заказали» в прошлой публикации =)
Один вопрос, как правильно прописывать делегирование внутри ?
я не нашел нигде, пробывал
не помогло. как правильно? :)
Для вывода количества новых вне экшена Friends, нужно задавать отдельно функционал в каждом из экшенов, где нужно это показывать. Например, с помощью механизма хуков.
А про хуки примера не найдется?
Для этого нужно внутри плагина создать хук, в котором получать количество новых записей в ленте, передавать их во Viewer. И повесить хук на shutdown экшенов index, blog
У кого-нибудь получалось прикрутить к плагину скрипты для обратки ajax запросов?
Например, добавлять к адресу серверного скрипта:
url+'?security_ls_key='+LIVESTREET_SECURITY_KEY
Т.к. $this->GetActionClass() содержит значение из $aConfig['route]['page'][{action}], то вместо
должно быть
иначе возникает ошибка.
Соответственно появится ошибка и в 54-ой строке, где $aMatches[1] будет содержать не то, что нужно.
То же самое в 95-ой строчке.
2) Обычно делегирование нужно для модифицирования какого-то файла, поэтому он чаще всего лежит по аналогичному пути (в папке плагина) и имеет такое же имя, как и заменяемый аналог. На мой взгляд есть смысл автоматизировать прописывание этого пути.
Чаще всего будет представлять из себя что-то вроде
На мой взгляд, всё это лучше бы выглядело, например, так:
3) Я не нашел возможности делегировать темплейты, которые подключаются динамически — из других темплейтов. Например, header_top.tpl, paging.tpl, topic_list.tpl. Этого очень не хватает, есть ли возможность что-то придумать для таких ситуаций?
то подразумевается, что полная форма записи такая:
Вот только плохо, что сам модуль Plugin нельзя делегировать :(
Я только не пойму, зачем тогда вообще что-то указывать? =) Можно вспомнить Ruby On Rails и пойти по пути COC — если в качестве делегата ничего не указано — значит делегатов является одноименный ресурс из плагина, который производит делегирование.
Думаю, стоит реализовать оба варианта.
то есть будет подразумеваться, что неуказанный делегат лежит в папке /plugins/myplugin/templates/skin/myskin/.
достаточно написать:
User
Фактически я указываю только название модуля, который хочу подменить и все. Рас я не указал явно делегат — значит считаем, что мой таргет имеет аналогичное с исходным расположение и название — добавляем в начало приставку и все.
Возможное решение: добавить на 162-ой строчке файла /engine/classes/Router.class.php сохранение нового названия экшена, если учитывается делегирование:
Создал тикет.
Еще бы сделали чтобы можно было делегировать темплейт блоков, например block.stream.tpl, отдельно его элементы (block.stream_comment.tpl) можно делегировать, но саму основу нет.
Как понимаю всего то надо в smarty_insert_block строчку
у меня так заработало.
поэтому я добавил у себя проверку в шаблоне при выводе кнопки
и в экшене
может я что не так делаю. поправьте тогда
предлагаю вынести его в отдельный репозиторий и развивать дальше
в экшене небольшая неточность:
в результате сабменю выводится от индекса, надо:
livestreet v0.4.1
livestreet.ru/blog/4777.html
Очень полезная вещь. Странно, что такой функции у LS нету изначально. Хотя с другой стороны — хорошо, когда есть куда развиваться.
Еще раз спасибо!
1. скажите мне какие баги встречаются
2. я проверю свой код (и проект) на наличие таковых, чтобы не выкладывать код, который не факт, что работает правильно.
3. исправляю баги, если такие будут найдены.
4. выкладываю здесь ссылку на архив.
Могу сказать за себя, ставил все на 4.2
Не отображается в меню…
Пытался поставить в Профиль, но тоже какой-то косяк. Скорее всего кривые руки))… лечу их активным чтением документации.
В логе идет ошибка. Собрал криво плагин, от того и прошу его, чтоб разобраться что и куда не так засунул-то в итоге.
www.ex.ua/view_storage/454673494809
Сразу скажу, что LS допиливал, поэтому не все может быть стандартно…
Попробуй.
Спасибо
Я ж говорил — допиливал все под себя.
Еще раз спасибо=)
В фоксе работает на ура, может в курсе в чем проблема?
Вот и получается, что в FF работает (где есть сессия), а в Хроме — нет.
Как вариант решения — прятать ссылку на ленту для анонимных пользователей или при переходе на ленту предлагать выполнить вход (перебрасывать на страницу логина).
Вот и получается, что в FF работает (где есть сессия), а в Хроме — нет.
Как вариант решения — прятать ссылку на ленту для анонимных пользователей или при переходе на ленту предлагать выполнить вход (перебрасывать на страницу логина).
Например в этом же файле в функции Init вначале добавить такой код:
сейчас появляется только когда захожу на test.ru/friends/
1) Адаптировать плагин по шаблон new
2) Чтобы ссылка на ленту друзей была видна из любой страницы
3) RSS ленты друзей — если это реально сделать
Готов заплатить
Но тогда все весь первоночальный экшн перезаписывается моим (?) и вместо /blogs/ я получаю, что страница не найдена, хотя мои ивенты работают. Нужно ли делегир? добавлять в классы плагина, как было написано в комментах? Смотрел другие плагины, там вместо page.blogs стомт page.[PluginName]_blogs, но тогда у меня не работают ивенты плагина. Почему не пойму…
как подобно этому
$this>Viewer_AddMenu('blog',Plugin::GetTemplatePath(__CLASS__).'/menu.blog.tpl');
добавить в sidebar