Использование плагинов в v.0.4

Безболезненное расширение функционала — достаточно проблематичный вопрос (под «безболезненностью» я понимаю весь спектр удобства для пользователей движка).

Я думаю, вы еще не забыли, что для добавления новых возможностей в версиях до 0.3.1 включительно используются Хаки и Модули. При этом установка любого стороннего творения дело достаточно хитрое и небезопасное, часто требующее ориентирования в архитектуре и особенностях движка. Для преодоления этих проблем в ядро v.0.4 была введена система плагинов:


К чему мы стремились создавая систему плагинов?

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

0. Что такое плагин?

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

В дистрибутиве LS на SVN сейчас имеет один плагин — Profiler. Он достаточно прост, и не демонстрирует всех возможностей системы плагинов, зато вполне рабочий =) Profiler — это визуальная оболочка для просмотров данных профилирования работы движка.

1. Как пользоваться плагинами?

Для начала нужный вам плагин нужно залить целой директорией (!) в папку /plugins/.

Если вы правильно это сделали то на страничке /admin/plugins/ (доступна только администраторам) этот плагин появиться в списке доступных. Формат этого списка приведен на скриншоте (у меня три плагина, только потому что я их сделал для написания статьи, если скачаете версию с SVN — там будет только один).

По умолчанию плагин будет деактивирован. В списке рядом с его названием выведена дополнительная информация: автор, версия плагина, описание, домашняя страница (как эта информация сохраняется и где храниться — читайте в блоке материалов для разработчиков).

Здесь же в списке вы можете активировать\деактивировать плагин. Если выражаться простым языком — то активируя плагин, вы «включаете» его функционал в общую схему работы движка. Деактивируете = «выключаете». На крайний случай есть возможность удалить не нужный плагин. Внимание! При удалении плагин будет физически удален с сервера, поэтому используйте эту возможность только в крайних случаях =)

Если активация плагина прошла успешно, то система покажет сообщение «Выполнено успешно». В случае ошибки — предупреждение о системной ошибки (или описание ошибки, предусмотренное разработчиком плагина). Также вы можете получить подобное сообщение:


Это означает именно то, что там и написано =) Плагин «Conflict», который я попытался активировать, потребовал от системы изменения шаблона index.tpl (точнее замены его другим), на что система ответила — шаблон уже заменен другим активированным плагином «Test». Такую ситуацию мы назваем «конфликтом плагинов». Для ее решения вам нужно отказаться от одного из конфликтующих плагинов. Конфликтовать плагины могут не только за шаблоны, но и за модули, сущности и экшены.

2. (For developers) Как устроены плагины внутри?

Теперь более подробно основная информация для разработчиков расширений. Итак, мы с вами рассмотрим:

— структура директорий;
— добавление новых элементов функционала;
— сущность делегирования функционала, массив делегатов плагина;
— функции Activate(), Deactivate(), обертка для выполнения операций с базой данных;
— функция инициализации.

Файловая структура плагина во многом повторяет структуру директорий движка. В чем можно убедиться посмотрев на директории плагина Profiler:

В плагине не обязательно должны быть представлены все указанные директории, вы вообще можете планировать сруктуру каталогов как вам удобно. Но есть обязательные требования и некоторые соглашения. Объясню их на примере того же Profiler.

а) Директория плагина должна иметь название вашего плагина в lowercase («profiler»);

б) В корне плагина должен находиться основной функциональный файл, имеющий в названии префикс Plugin и суффикс .class.php: PluginProfiler.class.php. В этом файле должен быть определен класс плагина, имеющий такое же название как и файл (PluginProfiler) и являющийся наследником класса Plugin. Подробнее о содержимом класса плагина мы поговорим ниже;

в) Также в корне должен лежать readme.txt, оформленный по следующему примеру:


Name: Livestreet Profiler Plugin
Author: LiveStreet Developers Team
Homepage: http://livestreet.ru/
Version:  1.0.0
Requires: 0.4.0
Description: Профилирование работы движка LiveStreet-движка.

Я думаю, смысл каждой стоки понятен без пояснений.

г) Конфигурационные файлы нужно хранить в директории /config — при этом они будут автоматически добавляться в основную конфигурацию, с префиксом ключа «plugin.название_вашего_плагина.ключ_конфигурации». Если вам нужно переопределить какое-либо значение существующего в конфигурации ключа либо создать новый ключ в произвольном сегмене, необходимо использовать конструкцию Config::Set(). Пример из плагина Profiler:


$config['per_page']   = 15;  // Число profiler-отчетов на одну страницу

Config::Set('db.table.profiler', '___db.table.prefix___profiler');
Config::Set('router.page.profiler', 'PluginProfiler_ActionProfiler');

return $config;


Как видно из кода, с помощью Config::Set() плагин добавляет новую таблицу базы данных в список таблиц и новое правило роутинга (для нового экшена).

д) Автоматически будут подключены PHP-файлы из директории /include, загружены языковые файлы из /templates/language/, инициализированы хуки из /classes/hooks/;

е) Структура каталогов actions, modules и entity такая же как у движка. Отличие заключается в том, что эти классы в названии должны иметь приставку PluginНазвание. Например, PluginProfiler_ActionProfiler (action`ы должны быть наследниками класса ActionPlugin). Соответственно, вызов функции модуля плагина аналогичны вызову функции модуля движка:


$aResult=$this->PluginProfiler_Profiler_GetReportsByFilter($aFilter,$iPage,Config::Get('plugin.profiler.per_page'));

(вызов функции GetReportsByFilter() модуля Profiler плагина Profiler).

е) templates размещаются аналогично в директории skin, при этом в папке default должны размещаться шаблоны, которые будут использованы плагином в том случае, если текущий skin сайта в плагине не предусмотрен. Для получения пути до корректной директории скина в плагине можно использовать конструкцию


$this->getTemplatePathPlugin()


Теперь о делегировании. Делегирование — это возможность указать системе необходимость использовать вместо одного объекта другой. Сейчас поддерживается четыре типа делегатов: action, module, entity, template. Для делегирования используется функция Delegate модуля Plugin.

Типичные примеры использования:


// Использовать вместо шаблона index.tpl шаблон my_plugin_index.tpl 
$this->Plugin_Delegate('template','index.tpl','my_plugin_index.tpl');
// Использовать вместо модуля User модуль PluginTest_User
// При этом любой вызов $this->User_* будет перенаправлен на $this->PluginTest_User_*
$this->Plugin_Delegate('module','User','PluginTest_User');


Делегаты можно создавать в произвольный момент, но если значение делегатов известны для плагина заранее (например, вы создаете плагин для того, чтобы переопределить работу ActionProfile), то в этом случае необходимо создать в классе плагина специальную переменную


/**
 * Указанные в массивы делегаты будут переданы движку автоматически 
 * перед инициализацией плагина
 */
protected $aDelegates=array(
	'template'=>array('index.tpl'=>'my_plugin_index.tpl'),
	'action'  =>array('ActionProfile'=>'PluginTest_ActionProfile')
);



«Стратегическими» функциями в классе вашего плагина являются функции:

Activate() — процедуры выполняемые в момент активации плагина. Например, создание таблиц в базе данных, создание нужных каталогов, проверка возможности работы плагина в текущей конфигурации или на данном сервере… Да мало ли, что еще может пригодиться =) Если функция вернет false, то система будет считать, что активация не пройдена, и не занесет плагин в список активированных.

Исходя из того, что наиболее часто выполняемыми операциями активации является выполнение SQL запросов, мы упростили эту процедуру разработчикам плагинов. Для выполнения нужных запросов в БД, вам достаточно сложить их в один файл, а в функции активации вызвать:


$this->ExportSQL(dirname(__FILE__).'/sql.sql');

(пример из плагина Profiler).

Deactivate() — вызывается при деактивации плагина. Тут я думаю все понятно.

Init() — функция инициализации плагина. Думаю к таковым вы уже привыкли =) (инициализацию поддерживают как action`ы, так и module`и).

3. На завершение...

Хотелось показать на конкретном примере, как реализовывать хаки в виде плагинов, но из-за ограничения в размере статьи приходится отложить это на следующую публикацию. В этом есть и положительный момент — вы можете в комментариях указать на тот хак, на примере которого я составлю следующую заметку! Так что, you are welcome.

Как обычно — тестирования, баг-репорты и пожелания приветствуются!

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

avatar
Спасибо за делегирование. этого очень нехватало.
в качестве первого плагина предлагаю Ленту Друзей XD
livestreet.ru/addons/23/
многим пользователям будет полезна :)
avatar
Заявка принята к рассмотрению =)
avatar
Ждал подобного, но вы пошли дальше — молодцы!
И про прочтении возникла мысль — мож, и бредовая, но думал-то я ее пока несколько секунд всего — а вот бы еще «цепочки делегирования» организовать!
avatar
Не могу сказать ничего дельного, так как не разработчик. Поддержу лишь, сказав что "это большой шаг для движка!"
avatar
А вот и первая ложка дегтя:
action`ы должны быть наследниками класса ActionPlugin

Значит, если я хочу создать расширение типового класса (ActionBlog, ActionTopic, etc), то у меня два пути:

1) Создавать плагин (действительно очень удобный механизм и прогрессивный), но при этом полностью воспроизводить в нем оригинальный класс, который я хотел бы расширить. Даже если мне надо добавить всего лишь пару строк, я не смогу воспользоваться такой классной штукой из области ООП, как наследование, и вынужден идти на сплошной копипаст. Что является, как говорят в авиации, «предпосылкой к происшествию» — любое изменение в оригинальном классе может привести к несовместимости плагина с движком.

2) Использовать менее удобное, но более универсальное и более правильное с точки зрения ООП кастомное расширение. Но при таком подходе это «достаточно хитрое и небезопасное расширение» становится как бы «нелегитимным» с точки зрения модульности.

Крепко задумался…
avatar
Вообще идеальным решением была бы возможность делегировать отдельные функции. Но это не является классикой ООП и приведет скорее к «костыльному» решению, чем к архитектурно-изящному.

То о чем ты говоришь — «добавить пару строк» лучше делать через хуки. На event`ы можно ведь вешать хуки. Это достаточно удобный механизм. Если же нужно сменить ЛОГИКУ работу — тогда пользоваться делегатами.
avatar
Вообще идеальным решением была бы возможность делегировать отдельные функции...
Да, это первое, что и мне в голову пришло, когда про плагины увидел. Но согласен, что это очень скользский путь.

На event`ы можно ведь вешать хуки.
На любые? По-моему, был предопределенный перечень событий, которые вызывали хуки, и перечень этот был конечным. Или в 0.4 реально на любой ивент можно хук вешать? И тут еще такая байда — в каких-то случаях надо хук ДО события вызвать, а в каких-то — ПОСЛЕ. Но, если я верно Макса понял и можно в плагинах юзать расширение стандартных модулей, то «ложка дегтя» попросту «умножается на ноль», то бишь — ее нет, и все отлично! :)
avatar
появились хуки на евенты
avatar
Не понял, они же были уже. Или я какой-то нюанс не уловил?
avatar
Сейчас сделан вызов хуков перед и после каждого event`а. А также перед и после инициализации и шатдауна экшена.
avatar
А, ну так это меняет дело! А глянул в транк — хуки и на проверку полей сделали! За это — отдельное человеческое спасибо! (только очепятка там: ckeck_blog_fields)

А использовать так же, как и раньше, ничего не изменилось?
avatar
Очепятку я правил сегодня днем:
trac.lsdev.ru/livestreet/changeset/761
avatar
наследовать от ActionPlugin нужно только в случаи собственного экшена плагина, если нужно расширить базовый экшен(например, ActionBlog), то достаточно объявить его делегат и унаследовать его от ActionBlog
avatar
Макс, если я правильно тебя понял, то при необходимости, скажем, заменить стандартный метод ActionMyBlog->EventAddBlog() на собственный, я должен создать подобный плагин:
class PluginMyBlog_ActionMyBlog extends ActionBlog {
   protected $aDelegates=array(
      'action'  =>array('ActionBlog'=>'PluginMyBlog_ActionMyBlog')
   );

   protected function EventAddBlog() {
      // my extended code here
   }
}

И в этом случае получим и нормальное ООП-расширение станлартного модуля, и грамотно впишемся в механизм плагинов. Все верно?
avatar
Не совсем. Это ты создаешь Action внутри плагина. А список делегатов определяется в классе плагина.

Т.е. у тебя будет файл /plugins/myblog/PluginMyblog.class.php

class PluginMyblog extends Plugin {
   // Вот здесь указываем делегат
   protected $aDelegates=array(
      'action'  =>array('ActionBlog'=>'PluginMyblog_ActionMyblog')
   );
}

А в файле /plugins/myblog/classes/actions/ActionBlog.class.php

require_once(Config::Get('path.root.server').'classes/actions/ActionBlog.php');
class PluginMyblog_ActionMyblog extends ActionBlog {
   protected function EventAddBlog() {
      // my extended code here
   }
}

Как-то так, на орфографию код не проверял — сочиняю прямо здесь =)
avatar
А, все, догнал. Спасибо!
avatar
Не за что. Я планирую написать еще одну статью по плагинам с конкретным примером для разработчиков, построенном на решении реальной задачи.
avatar
Алексей, то ли я дурак, то ли у меня лыжи не едут, но не врублюсь, как мне из экшен-делегата /plugins/myblog/classes/actions/ActionBlog.class.php получить путь к текущему скину плагина? Хочу в этом модуле цеплять свой css, который в папке скина плагина лежит.

Метод getTemplatePathPlugin() доступен только в самом плагине, поэтому отпадает. Ниже ты еще упоминал Plugin::GetTemplatePath($sPluginName), но я его не вижу в Plugin.class.php. Куда-то не туда смотрю?
avatar
Упс, пропустил обновление. Спасиб
avatar
а как тогда из расширить функционал модуля? допустим, я хочу расширить модуль Text, например, добавить парсинг новых тегов.

как в этом случае быть? чтобы не копировать весь код модуля Text? и еще, как из переопределенного методы вызвать оригинальный?
avatar
1. Наследование
2. parent::
avatar
Ух, все — теперь я окончательно понял :) что плагинная система ЖУ нереально крута и можно в движке изменять все и вся практически не меняя исходный код. круть!

просто сначала я не врубился, раз для экшенов работает указание базового класса при наследовании, то будет и для модулей работать, а в связи с этим и про вызов метода родителя совсем забыл… :(

Спасибо за проделанную работу :)
avatar
А у меня вопросик: Когда я захожу на страницу /admin/ у меня пишет ошибку Ошибка: 404
Залогинился под администраторским аккаунтом. Не знаю в чем может быть проблема.
avatar
Читайте внимательно.
то на страничке /admin/plugins/
avatar
то на страничке /admin/plugins/
У меня 404 ошибка на этой странице.
Обновился с версии 0.3.1
Помогите кто-нибудь, где копать?
avatar
войдите на сайт под администратором
avatar
то на страничке /admin/plugins/
У меня 404 ошибка на этой странице.
Обновился с версии 0.3.1
Помогите кто-нибудь, где копать?

P.S.
Сам уже разобрался…
В \config\config.php
Забыл строку
$config['router']['page']['admin'] = 'ActionAdmin';
avatar
Круто!
avatar
Круто! А давайте для примера хаков используем такой хак, как превью для топика. Я просто хочу узнать можно ли будет дополнениям безболезненно внедряться в файлы уже изменённых шаблонов. Или как это реализовано
avatar
превью для топика

Что ты имеешь ввиду? Режим «Предпросмотр» и так уже реализован…
ли будет дополнениям безболезненно внедряться в файлы уже изменённых шаблонов

Странная постановка на самом деле.

а) «внедряться» в файлы можно только путем Viewer_Assign() с последующим выводом этой переменной в шаблоне. Описанный в статье механизм делегирования означает полную замену, а не «внедрение»;

б) «уже измененных шаблонов» — звучит равно как «Может ли хирург вырезать аппендицит, учитывая что структура тела была изменена внеземной формой радиации?»
avatar
так как я не программер вопрос у меня тоже идиотский
я собираюсь переделывать у себя систему рейтинга.
там надо будет переписать принцип начисления рейтинга, и разрешить изменять\отменять свой голос. до этого я делал это хаком. теперь хочется всё это превратить в плагин

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

templates/skin/new/jsvote.js
classes/modules/rating/Rating.class.php
classes/modules/user/mapper/User.mapper.class.php
include/ajax/voteUser.php


я так понимаю надо сделать их копии в папке плагина, оставить в них только функции, которые я буду менять и собственно их менять. а потом просто указать, что я поменял вот эти и эти функции…
  • Vilz
  • 0
avatar
Нет.

Без знания ООП тебе будет сложновато разобраться. Дождись лучше конкретного примера, сделаешь по аналогии. Или расскажи, что ты меняешь — я этот случай проанализирую в статье.
avatar
livestreet.ru/addons/3/

это конечно неполная и устаревшая версия хака, но в целом там видно, что надо делать
если найду архив с изменёнными файлами докину ссылку
avatar
Ок, посмотрю.
avatar
Исходя из того, что наиболее часто выполняемыми операциями активации является выполнение SQL запросов, мы упростили эту процедуру разработчикам плагинов...
А вот бы еще предусмотреть в вызове $this->ExportSQL(...); чтоб в SQL-запросах префикс таблиц реальный (текущий — из конфига) подставлялся. ;)
avatar
Если я чем-то не похвастался, это не значит, что его не реализовал =)

trac.lsdev.ru/livestreet/browser/trunk/engine/classes/Plugin.class.php#L98
avatar
Потрясающая штука. Вы действительно шагнули на новый уровень!

Единственное, что пока не складывается в моей голове — реализация нескольких плагинов, претендующих на один ресурс. Скажем простой пример — в шаблон профиля один плагин добавляет контакт Skype, а второй Телефон. Как я понимаю в этом случае возникнет конфликт.

Двинемся глубже. Для тех двух типов контактов нам потребуется изменение и action, module, entity и mapper (кстати, их можно делегировать?)… И с таким пересечением действительно часто приходится сталкиваться. Как быть в таких случаях?
  • Carw
  • 0
avatar
Это я имел ввиду выше, когда говорил про «цепочки делегирования». Но, сдается мне, вопрос это очень непростой.
avatar
Проблема конфликтности плагинов классическая для всех плагин-систем. Вопрос а) к пользователю — что ему больше нужно, б) к разработчиком — писать наиболее гибко (чем более гибко и не назойливо мой плагин, тем более востребованным он будет).

Грубо говоря, я делаю плагин, который добавляет номер телефона в инфо о пользователи. А еще нужен скайп, вконтакте и т.д. Будет ли мой плагин востребован — ровно до тех пор, пока не будет заменителя. И когда-то найдется разработчик, который напишет легко кастомизируемое пользователем расширение, с произвольным расширением данных…

Тут нужно понимать, что задача разработчиков предоставить механизм, а не вкручивать мозги разработчикам =)

Mapper нельзя делегировать, мепперы подключаются в каждом модуле отдельно, поэтому для делегирования меппера нужно переопределить тот код в модуле, который отвечает за подключение меппера.
avatar
Да про мапперы я что-то поторопился… Но я лишь для наглядности взял такой простой пример. Чаще всего пересечения идут между абсолютно разной функциональностью. Допустим возьмем шаблон topic_list. Он у нас описывает вид разных типов топиков. Совершенно логично, что топик-музыка и внутренний голос — это будут именно разные авторы и плагины. А пользователь захочет просто закачать два плагина и включив их увидеть у себя и то и другое. А третий плагин возможно будет добавлять количество просмотров топика.

Еще хороший пример — добавление в профиль пользователя микроблога и галереи одновременно.

В нашем случае придется либо отказаться от какого-то плагина, либо сделать новый, который будет совмещать необходимые конкретному проекту возможности. :-)

Честно говоря, я пока и сам не вижу решения, но проблема типичная для хаков.
avatar
тут можно былоб сделать по аналогии с Drupal Forms
Те имеем простой массив данных, который рендерится движком с учетом шаблонов модулей
Если надо внести какие изменения, просто альтерим массив и вносим свои изменения в него.
Если не понятно, поясню.
avatar
А у меня вопрос по скинам для лагинов. Скин для плагина должен совпадать с текущим скином сайта? Или можно задавать свой скин, отличный от глобального?
avatar
Читаем внимательно.
при этом в папке default должны размещаться шаблоны, которые будут использованы плагином в том случае, если текущий skin сайта в плагине не предусмотрен

Для обслуживания этого процесса в ActionPlugin есть функция getTemplatePathPlugin(), есть еще Plugin::GetTemplatePath($sPluginName) — обе эти функции возвращают адрес директории с шаблонами плагина с учетом наличия (либо не наличия) нужного скина.
avatar
Я несколько иное имел ввиду. Скажем, у плагина есть несколько скинов, в т.ч. и совпадающий по названию с глобальным, но в плагине (напр., через конфиг-файл) можно указать, какой именно из них использовать. Из ответа я понял, что такого нет. А было б неплохо кроме getTemplatePathPlugin() предусмотреть «зеркальный» метод setTemplatePathPlugin().
avatar
Не вижу логики.
У нас в плагине предусмотрены шаблоны плагинов default, new, develper. На сайте используется скин new, а я указываю в плагине выводить шаблоны не new, а developer… Я правильно понял идею? Зачем такое может понадобиться?
avatar
Понял правильно. Пример использования этого механизма: можно создать несколько скинов для плагина, схожих по дизайну с глобальным скином new, но с разной версткой (скажем, для профайла юзера с доп. полями, для админки, и т.д.), и дать возможность юзеру выбрать, какой скин использовать.
avatar
можно в конструкторе экшена плагина задать необходимый $sTemplatePathPlugin, хотя наличие setTemplatePathPlugin будет правильнее
avatar
Просто getTemplatePathPlugin возвращает путь к директории со скином. Если у нас скин тот же самый new, но есть несколько различных вариантов его оформления — можно складывать эти шаблоны по директория /skin/new/my_format/index.tpl и вызывать как

getTemplatePathPlugin().Config::Get(ключ_где_храниться_формат).'/путь_до_шаблона'
avatar
разное оформление это и есть разные шаблоны(скины)
можно создать скины new1, new2 и т.п., а разработчик пусть сам определяет какой скин отображать. Он в принципе и сейчас это может сделать, но удобнее будет через setTemplatePathPlugin. Т.е. оформление плагина может быть абсолютно любым и не привязано к текущему шаблону, просто по дефолту ищется совпадение с текущим.
Не стоит городить дополнительный функционал, это решается уже имеющимися средствами.
avatar
лагины, блин. я без претензий. но галины… блин :-D
avatar
Наткнулся на проблемку. Хотя, пока не решил — баг это или фича :)

Итак, создал экшен-делегат. Пусть это будет /plugins/myblog/classes/actions/ActionBlog.class.php, как в примере выше. В этом экшене я хочу подключить несколько блоков. Причем один блок — стандартный, например, blogs, другой — собственный, идущий в комплекте с плагином.

Внимание вопрос: а где вьюер будет искать шаблоны для этих блоков? Ответ: в папке «глобального» скина, заданного для сайта в целом. Мне, конечно, не трудно туда шаблоны самопального блока скопировать. Но тогда ломается стройная схема плагинов, когда все — в одной кучке. Как быть?
avatar
Сам спросил — сам ответил. :) Для плагиновых блоков это делается примерно так:
$this->Viewer_AddBlocks('right', Plugin::GetTemplatePath('myblog').'/block.myblock.tpl');

Аналогичным образом и экшена-делегата устанавливается шаблон для отрисовки страницы:
$this->SetTemplate(Plugin::GetTemplatePath('myblog').'/actions/ActionBlog/mytest.tpl');

Но, наверное, логично было бы добавить методы типа AddPluginBlocks($sPlugin, $sGroup, $aBlocks) и SetPluginTemplate($sPlugin, $sTemplate).
avatar
По поводу блоков: там был уже предусмотрен механизм создания блока из плагина — для этого нужно добавить параметр блока 'plugin'. Посмотри как это реализовано в самом Smarty-плагине:

trac.lsdev.ru/livestreet/browser/trunk/engine/modules/viewer/plugs/insert.block.php#L35

Я там в @733 только немного пофиксил под новую функцию.

По поводу SetTemplate(), если я тебя правильно понял, то ты говоришь про это:

trac.lsdev.ru/livestreet/browser/trunk/engine/classes/ActionPlugin.class.php#L71

Или нет?
По меню посмотри @732 ревизию. Вставил минимум того, что планирую реализовать по menu-контейнерам (но это уже, наверное в 0.5 пойдет) =) Ту проблему, которую ты описал, косячно немного, но решает.

Основная идея в том, что система будет воспринимать параметр menu в первую очередь как название контейнера, а уж если такого нет — искать туда файл. Если контейнер определен, то Smarty подтянет на место меню заранее подготовленное меню (рендериться при Shutdown`е модуля Viewer).

Для добавления плагин меню, нужно в шаблоне из плагина указать название контейнера, например menu='my_plugin_blog', а в функции Init() плагина передать название шаблона в этот контейнер. Например,

$this->Viewer_AddMenu('my_plugin_blog',Plugin::GetTemplatePath('my_blog_plugin').'/my_plugin_blog_menu.tpl');


Аналогичным вызовом, можно заменить существующее меню своим.
avatar
Еще проблема — добавление собственного меню. И она уже имеющимися средствами не решается, ибо это не объедешь:
{if $menu}
   {include file=menu.$menu.tpl}
{/if}
:(
Чего-нибудь могут разработчики посоветовать?
avatar
Ситуация с меню вообще отдельная (достаточно сложная с архитектурной точки зрения). Обсуждалось неоднократно.

Я лично вижу выход только в создании некой абстракции (на подобие Zend`овских ViewsHelper) — вопрос не только в том, как добавить новое меню, а как например добавить новый пункт в старое, не заменяя его полностью? (ведь плагинов, которые пожелают это сделать может быть несколько)…

У тебя есть какие-нибудь предложения по этому поводу?
avatar
Так глубоко (как модификация меню) не копал пока. Хотя и думал в эту сторону. И пока больше склоняюсь к тому, что такие вещи должны, все же, не на уровне шаблонов решаться, а на уровне экшена (т.е. структура меню формируется в экшене, а в шаблоне уже только вывод). Все же если мы о модели MVC говорим, то логики в шаблонах должно быть по минимуму. Может быть, стоит подумать о том, чтобы отдельный класс «меню» сделать, и в экшене им оперировать. Либо набор методов во вьюере для операций со структурой меню.

Что же касается сегодняшнего дня, использования в плагинах меню и вообще «кусочного» собирания шаблона (через {include file=«name.tpl»}), то вижу такое решение: В Smarty есть такое понятие, как «ресурс» (см. www.smarty.net/manual/ru/template.resources.php ). Смотрел поверхностно, но, кажись, можно это дело на службу людям поставить. Напр., так:
1) Объявить ресурсы «plugin» и «menu.plugin», которые будут ссылаться скин плагина
2) В плагиновском экшене я указываю:
$this->Viewer_Assign('menu', 'plugin:blabla');
Тогда в header_nav.tpl я получу (после подстановки переменной):
{include file=menu.plugin:blabla.tpl}
Что будет означать «взять шаблон blabla.tpl из ресурса menu.plugin»

Вуаля! Что нам и требовалось! И шаблон с инклюдами не трогаем ни пальцем!

И, кстати, раз уж разговор об этом зашел: мож стоит дать доступ непосредственно к экземпляру Смарти, который во вьюере сидит? Понимаю, что не очень кошерно, но в исключительных случаях у разработчика будет возможность какие-то «костыли» втыкать на более низком уровне.
avatar
Про ресурсы почитаю. Никогда не сталкивался. Но на вскидку скажу, что это не совсем то — здесь идет речь о возможности извлечения шаблонов не из файловой системы, а из других источников (как описано в примере — из базы данных).
avatar
В примере — да, из бд. Но, по-моему, это лишь для демонстрации того, что источники могут быть разными. Но нам ведь ничего не мешает в ф-ции xxx_get_template() читать не бд, а файл по какому-то определенному пути.
avatar
Кстати, прямо сейчас, раз все равно шаблоны для миграции на 0.4 надо слегка модифицировать, можно пойти очень простым путем, чтобы включать свое меню в плагинах. Берем кусок
{if $menu}
   {include file=menu.$menu.tpl}
{/if}
и меняем его:
{if $menu}
   {include file=menu.$menu.tpl}
{elseif $menu_text}
   {$menu_text}
{/if}
И все! А в плагине разработчики пусть набивают menu_text, чем хотят.
avatar
А где можно скачать v.0.4?
И есть ли там исправление (если установлено на IIS то русские тэги не работают)?
avatar
avatar
Пасиб! а что с тэгами неизвестно?
avatar
avatar
Да уж ;) Чет я подзадержался на странице, стоило обновить комменты.
avatar
… Также в корне должен лежать readme.txt, оформленный по следующему примеру...

Есть предложение — оставить readme.txt на откуп разработчикам (все же исторически сложилось, что в подобных файлах инфа для пользователя размещается). А для системы использовать какой-нить XML (скажем, пусть это будет plugin.xml). Тогда можно будет расширять формат этого файла, добавляя в него доп. ноды.

Напр., туда можно помещать инфу о статическом делегировании (как альтернативу описанному в посте свойству-переменной protected $aDelegates=array(...)). Тогда возможные конфликты можно будет выявлять не во время активации, а до активации плагина. (При этом уже имеющиеся механизмы делегирования оставить, но указывать на этот, как на предпочтительный).

В этом же файле можно будет и меню плагина описывать. Причем, можно предусмотреть в перспективе как полностью новое меню, которое будет создаваться плагином, так и модификацию имеющегося меню (напр., добавление нового пункта меню для экшена ActionBlogs и привязка к нему экшена, который будет обрабатываться плагином).

Это то, что на поверхности лежит, мож, со временем еще что придумается. Но тем и хорош XML, что очень хорошо расширяется и легко обеспечить совместимость снизу вверх.
avatar
се же исторически сложилось, что в подобных файлах инфа для пользователя размещается

В readme.txt система читает только первые строки.
Остальное содержание можно писать как угодно.
avatar
P.S. По поводу выявления несовместимостей ДО активации плагина, я предчувствовал негодование. Поэтому в readme.txt содержится вот эта строка:

trac.lsdev.ru/livestreet/browser/trunk/plugins/profiler/readme.txt#L7

Хотя ее использование и не прописано в коде системы плагинов. Честно говоря, не знаю нужно ли это. Можно, конечно, довести систему до того уровня, когда LS будет на основе переданного XML \ UML сама писать плагины… Только зачем?
avatar
Негодования никакого нет, просто стараюсь подойти более системно и универсально. Я не сторонник того, чтобы XML совать везде и всюду, где ни попадя. В каких-то случаях вполне достаточно обычного текстового формата. Но в данном случае XML-формат мне кажется более уместным. И проверка на конфликты ДО активации представляется тоже вполне логичной.

Но я ж не настаиваю, а просто высказываю пожелания, как предложено, и даже где-то пытаюсь их обосновать. :)

ЗЫ А создание плагина на основе какого-то файла описания — не такая уж и глупая мысль. ;)
avatar
А создание плагина на основе какого-то файла описания — не такая уж и глупая мысль.

Я знаю. У меня на в комнате на стенке висит доска для маркеров, на ней нарисована архитектура «саморасширяемой» livestreet (вплоть до UML-кодогенерации).

Если бы это было глупо, я бы про это не говорил. Но это настолько далекое будущее…
avatar
Смотрю сделали описания плагинов в xml, предусмотрите сразу многоязычность, т.к. собирались лс делать на широкий круг в том числе и иностранцев. В зависимости от того какой язык стоит в системе такую xml и показывать, или в одной xml несколько записей для разных языков.
avatar
Добавил
avatar
Баг: если делается попытка активации плагина, а файл PluginName.class.php не найден, то получаем неопределенную переменную:
Undefined variable: bResult in U:\home\local\tiande-online.ru\engine\modules\plugin\Plugin.class.php on line 145
avatar
Fix
avatar
Я вот подумал — наверное не очень хорошо, что файл, в котором инфа сохраняется по плагинам, лежит в папке /plugins. Я хоть и не параноик, но если можно не давать права на запись на папку, где исполняемые скрипты лежат, то я стараюсь этого не делать. Да и вообще, отделение кода от данных — это классика (а plugins.dat — это все же хоть и нано, но данные).

Может, стоит завести какую-то специальную папку для данных? Которую и другие разработчики могли бы использовать подобным же образом?
avatar
Когда выводится список плагинов в админке, то формировать его надо бы не по всем вложенным папкам, а только по тем, где есть правильный файл описания. Сейчас, если XML-файла нет (или он кривой), то пустая строка выводится.
avatar
avatar
Такой вопрос: что делать, если нужно незначительно изменить системные модули и экшены, нужно ли создавать их измененные копии и делегировать их?
Например, есть плагин расширенной юзер панели (с новыми функциями и т.п.) и нужно добавить ещё одно поле (допустим, Город) при регистрации нового юзера. Соответственно необходимо добавить по одной-две строки в файлы /templates/skin/new/actions/ActionRegistration/index.tpl, /classes/actions/ActionRegistration.class.php, /classes/modules/user/mapper/UserMapper.class.php. Как лучше поступить в таком случае? Напрямую редактировать эти файлы или создавать их измененные копии внутри плагина ImprovedPanel?
avatar
Ответил в письме
avatar
А что ответил-то? Если не секрет, конечно :)
avatar
А существует ли какой-то механизм, позволяющий определить, активирован плагин Х или нет?
avatar
Сам спросил — сам ответил: есть метод GetActivePlugins() в классе Plugin.

На базе него можно такую простеньку ф-цию написать:
protected function IsPluginActive($sPlugin) {
        $aPluginList = $this->Plugin_GetActivePlugins();
        foreach ($aPluginList as $sPluginName) {
            if ($sPluginName == $sPlugin) return true;
        }
        return false;
    }
avatar
Это для проверки на возможность плагина работать вместе с другими?

Вообще, я думал о необходимости такой проверки: плагин А для своей работы требует активацию плагина Б — даже в plugin.xml отвел спец. секцию в requires. Но руки никак не дойдут =)
avatar
Нет, это не альтернатива requires. Секция requires (как я это понимаю) нужна, чтоб на этапе активации плагина проверять зависимость от других плагинов (и если пофантазировать и вспомнить идеи, которые когда-то MaxSvargal высказывал, то в будущем можно и автоматическую подгрузку сделать требуемых плагинов).

Но я щас не об этом. Я хотел бы проверять активирован ли какой-то другой плагин, чтобы, напр., исходя из этого в коде определять, можно ли использовать функционал этого другого плагина.
avatar
Секция requires (как я это понимаю) нужна, чтоб на этапе активации плагина проверять зависимость от других плагинов
Да, совершенно верно.
avatar
Предлагаю расширить XML-описание плагина:
<requires>
    <livestreet condition="ge">0.4.0</livestreet>
    <plugins>
      <plugin name="OtherPlugin" version="1.2" condition="eq" />
    </plugins>
</requires>

Здесь атрибут condition может быть [eq|ge|gt|le|lt]
А класс Plugin содержит метод GetVesrion(), который возвращает версию плагина (из его же plugin.xml).

Тогда можно будет более гибко указывать и зависимость от версии движка, и от версий других плагинов.
avatar
А новую функциональность — например, каталог ссылок или что-то подобное — его как лучше реализовать — как плагин или как модуль?
avatar
Апну топик.

Я правильно понимаю, что на данный момент (0.4.2) в движке существует пересекающийся функционал — переопределение методов в классах и делегирующие хуки? Они же делают фактически одно и то же, НО делегирование нужно только для одной вещи — для полной замены tpl-файла на свой? Больше ни для чего оно не нужно, в остальных случаях (модули и т.п.) грамотнее использовать переопределение.
avatar
При установке любого плагина и попытке его активации выдает:

Ошибка: Файл плагина не найден

Подскажите как его запустить)
avatar
подскажите пожалуйста как мне прикрутить fck_editor как пхп класс? а не js как в примере на этом сайте делаю так в фаиле classes/actions/ActionTopic.class добавляю
* Добавление топика
*
* @return unknown
*/
protected function EventAdd() {
require «fck_new/fckeditor.php»;
$oFCKeditor = new FCKeditor(«textred»);
$oFCKeditor->BasePath = "/classes/actions/fck_new/";
и
$this->Viewer_Assign('textred',$oFCKeditor->CreateHtml());
и вывожу в шаблоне эту переменную
пишет 403 ошибку то есть эдитор не может попасть из-за реврайта в директорию акшенов
если же выношу сам эдитор в другую директорию то не получается подгркзить сам эдитор из дир
помогите пожалуйста люди а то меня уволят на…
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.