Warning: session_start(): open(/home/webmaster/www/livestreet.ru/tmp/sess_nqs803mm0ggd2thfti72jjqdj3, O_RDWR) failed: No space left on device (28) in /home/webmaster/www/livestreet.ru/www/engine/modules/session/Session.class.php on line 101 Использование плагинов в v.0.4 / Техническая документация LiveStreet / LiveStreet CMS
Безболезненное расширение функционала — достаточно проблематичный вопрос (под «безболезненностью» я понимаю весь спектр удобства для пользователей движка).
Я думаю, вы еще не забыли, что для добавления новых возможностей в версиях до 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). Соответственно, вызов функции модуля плагина аналогичны вызову функции модуля движка:
(вызов функции 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 комментариев
Спасибо за делегирование. этого очень нехватало.
в качестве первого плагина предлагаю Ленту Друзей XD livestreet.ru/addons/23/
многим пользователям будет полезна :)
Ждал подобного, но вы пошли дальше — молодцы!
И про прочтении возникла мысль — мож, и бредовая, но думал-то я ее пока несколько секунд всего — а вот бы еще «цепочки делегирования» организовать!
action`ы должны быть наследниками класса ActionPlugin
Значит, если я хочу создать расширение типового класса (ActionBlog, ActionTopic, etc), то у меня два пути:
1) Создавать плагин (действительно очень удобный механизм и прогрессивный), но при этом полностью воспроизводить в нем оригинальный класс, который я хотел бы расширить. Даже если мне надо добавить всего лишь пару строк, я не смогу воспользоваться такой классной штукой из области ООП, как наследование, и вынужден идти на сплошной копипаст. Что является, как говорят в авиации, «предпосылкой к происшествию» — любое изменение в оригинальном классе может привести к несовместимости плагина с движком.
2) Использовать менее удобное, но более универсальное и более правильное с точки зрения ООП кастомное расширение. Но при таком подходе это «достаточно хитрое и небезопасное расширение» становится как бы «нелегитимным» с точки зрения модульности.
Вообще идеальным решением была бы возможность делегировать отдельные функции. Но это не является классикой ООП и приведет скорее к «костыльному» решению, чем к архитектурно-изящному.
То о чем ты говоришь — «добавить пару строк» лучше делать через хуки. На event`ы можно ведь вешать хуки. Это достаточно удобный механизм. Если же нужно сменить ЛОГИКУ работу — тогда пользоваться делегатами.
Вообще идеальным решением была бы возможность делегировать отдельные функции...
Да, это первое, что и мне в голову пришло, когда про плагины увидел. Но согласен, что это очень скользский путь.
На event`ы можно ведь вешать хуки.
На любые? По-моему, был предопределенный перечень событий, которые вызывали хуки, и перечень этот был конечным. Или в 0.4 реально на любой ивент можно хук вешать? И тут еще такая байда — в каких-то случаях надо хук ДО события вызвать, а в каких-то — ПОСЛЕ. Но, если я верно Макса понял и можно в плагинах юзать расширение стандартных модулей, то «ложка дегтя» попросту «умножается на ноль», то бишь — ее нет, и все отлично! :)
А, ну так это меняет дело! А глянул в транк — хуки и на проверку полей сделали! За это — отдельное человеческое спасибо! (только очепятка там: ckeck_blog_fields)
А использовать так же, как и раньше, ничего не изменилось?
наследовать от ActionPlugin нужно только в случаи собственного экшена плагина, если нужно расширить базовый экшен(например, ActionBlog), то достаточно объявить его делегат и унаследовать его от ActionBlog
Макс, если я правильно тебя понял, то при необходимости, скажем, заменить стандартный метод ActionMyBlog->EventAddBlog() на собственный, я должен создать подобный плагин:
class PluginMyBlog_ActionMyBlog extends ActionBlog {
protected $aDelegates=array(
'action' =>array('ActionBlog'=>'PluginMyBlog_ActionMyBlog')
);
protected function EventAddBlog() {
// my extended code here
}
}
И в этом случае получим и нормальное ООП-расширение станлартного модуля, и грамотно впишемся в механизм плагинов. Все верно?
Не совсем. Это ты создаешь 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
}
}
Как-то так, на орфографию код не проверял — сочиняю прямо здесь =)
Алексей, то ли я дурак, то ли у меня лыжи не едут, но не врублюсь, как мне из экшен-делегата /plugins/myblog/classes/actions/ActionBlog.class.php получить путь к текущему скину плагина? Хочу в этом модуле цеплять свой css, который в папке скина плагина лежит.
Метод getTemplatePathPlugin() доступен только в самом плагине, поэтому отпадает. Ниже ты еще упоминал Plugin::GetTemplatePath($sPluginName), но я его не вижу в Plugin.class.php. Куда-то не туда смотрю?
Ух, все — теперь я окончательно понял :) что плагинная система ЖУ нереально крута и можно в движке изменять все и вся практически не меняя исходный код. круть!
просто сначала я не врубился, раз для экшенов работает указание базового класса при наследовании, то будет и для модулей работать, а в связи с этим и про вызов метода родителя совсем забыл… :(
А у меня вопросик: Когда я захожу на страницу /admin/ у меня пишет ошибку Ошибка: 404
Залогинился под администраторским аккаунтом. Не знаю в чем может быть проблема.
Круто! А давайте для примера хаков используем такой хак, как превью для топика. Я просто хочу узнать можно ли будет дополнениям безболезненно внедряться в файлы уже изменённых шаблонов. Или как это реализовано
Что ты имеешь ввиду? Режим «Предпросмотр» и так уже реализован…
ли будет дополнениям безболезненно внедряться в файлы уже изменённых шаблонов
Странная постановка на самом деле.
а) «внедряться» в файлы можно только путем Viewer_Assign() с последующим выводом этой переменной в шаблоне. Описанный в статье механизм делегирования означает полную замену, а не «внедрение»;
б) «уже измененных шаблонов» — звучит равно как «Может ли хирург вырезать аппендицит, учитывая что структура тела была изменена внеземной формой радиации?»
так как я не программер вопрос у меня тоже идиотский
я собираюсь переделывать у себя систему рейтинга.
там надо будет переписать принцип начисления рейтинга, и разрешить изменять\отменять свой голос. до этого я делал это хаком. теперь хочется всё это превратить в плагин
раньше я просто делал изменения в файлах, теперь, если я правильно всё понимаю можно просто их заменять в папке плагина
вот например файлы которые я тогда менял
я так понимаю надо сделать их копии в папке плагина, оставить в них только функции, которые я буду менять и собственно их менять. а потом просто указать, что я поменял вот эти и эти функции…
Без знания ООП тебе будет сложновато разобраться. Дождись лучше конкретного примера, сделаешь по аналогии. Или расскажи, что ты меняешь — я этот случай проанализирую в статье.
Исходя из того, что наиболее часто выполняемыми операциями активации является выполнение SQL запросов, мы упростили эту процедуру разработчикам плагинов...
А вот бы еще предусмотреть в вызове $this->ExportSQL(...); чтоб в SQL-запросах префикс таблиц реальный (текущий — из конфига) подставлялся. ;)
Потрясающая штука. Вы действительно шагнули на новый уровень!
Единственное, что пока не складывается в моей голове — реализация нескольких плагинов, претендующих на один ресурс. Скажем простой пример — в шаблон профиля один плагин добавляет контакт Skype, а второй Телефон. Как я понимаю в этом случае возникнет конфликт.
Двинемся глубже. Для тех двух типов контактов нам потребуется изменение и action, module, entity и mapper (кстати, их можно делегировать?)… И с таким пересечением действительно часто приходится сталкиваться. Как быть в таких случаях?
Проблема конфликтности плагинов классическая для всех плагин-систем. Вопрос а) к пользователю — что ему больше нужно, б) к разработчиком — писать наиболее гибко (чем более гибко и не назойливо мой плагин, тем более востребованным он будет).
Грубо говоря, я делаю плагин, который добавляет номер телефона в инфо о пользователи. А еще нужен скайп, вконтакте и т.д. Будет ли мой плагин востребован — ровно до тех пор, пока не будет заменителя. И когда-то найдется разработчик, который напишет легко кастомизируемое пользователем расширение, с произвольным расширением данных…
Тут нужно понимать, что задача разработчиков предоставить механизм, а не вкручивать мозги разработчикам =)
Mapper нельзя делегировать, мепперы подключаются в каждом модуле отдельно, поэтому для делегирования меппера нужно переопределить тот код в модуле, который отвечает за подключение меппера.
Да про мапперы я что-то поторопился… Но я лишь для наглядности взял такой простой пример. Чаще всего пересечения идут между абсолютно разной функциональностью. Допустим возьмем шаблон topic_list. Он у нас описывает вид разных типов топиков. Совершенно логично, что топик-музыка и внутренний голос — это будут именно разные авторы и плагины. А пользователь захочет просто закачать два плагина и включив их увидеть у себя и то и другое. А третий плагин возможно будет добавлять количество просмотров топика.
Еще хороший пример — добавление в профиль пользователя микроблога и галереи одновременно.
В нашем случае придется либо отказаться от какого-то плагина, либо сделать новый, который будет совмещать необходимые конкретному проекту возможности. :-)
Честно говоря, я пока и сам не вижу решения, но проблема типичная для хаков.
тут можно былоб сделать по аналогии с Drupal Forms
Те имеем простой массив данных, который рендерится движком с учетом шаблонов модулей
Если надо внести какие изменения, просто альтерим массив и вносим свои изменения в него.
Если не понятно, поясню.
А у меня вопрос по скинам для лагинов. Скин для плагина должен совпадать с текущим скином сайта? Или можно задавать свой скин, отличный от глобального?
при этом в папке default должны размещаться шаблоны, которые будут использованы плагином в том случае, если текущий skin сайта в плагине не предусмотрен
Для обслуживания этого процесса в ActionPlugin есть функция getTemplatePathPlugin(), есть еще Plugin::GetTemplatePath($sPluginName) — обе эти функции возвращают адрес директории с шаблонами плагина с учетом наличия (либо не наличия) нужного скина.
Я несколько иное имел ввиду. Скажем, у плагина есть несколько скинов, в т.ч. и совпадающий по названию с глобальным, но в плагине (напр., через конфиг-файл) можно указать, какой именно из них использовать. Из ответа я понял, что такого нет. А было б неплохо кроме getTemplatePathPlugin() предусмотреть «зеркальный» метод setTemplatePathPlugin().
Не вижу логики.
У нас в плагине предусмотрены шаблоны плагинов default, new, develper. На сайте используется скин new, а я указываю в плагине выводить шаблоны не new, а developer… Я правильно понял идею? Зачем такое может понадобиться?
Понял правильно. Пример использования этого механизма: можно создать несколько скинов для плагина, схожих по дизайну с глобальным скином new, но с разной версткой (скажем, для профайла юзера с доп. полями, для админки, и т.д.), и дать возможность юзеру выбрать, какой скин использовать.
Просто getTemplatePathPlugin возвращает путь к директории со скином. Если у нас скин тот же самый new, но есть несколько различных вариантов его оформления — можно складывать эти шаблоны по директория /skin/new/my_format/index.tpl и вызывать как
разное оформление это и есть разные шаблоны(скины)
можно создать скины new1, new2 и т.п., а разработчик пусть сам определяет какой скин отображать. Он в принципе и сейчас это может сделать, но удобнее будет через setTemplatePathPlugin. Т.е. оформление плагина может быть абсолютно любым и не привязано к текущему шаблону, просто по дефолту ищется совпадение с текущим.
Не стоит городить дополнительный функционал, это решается уже имеющимися средствами.
Наткнулся на проблемку. Хотя, пока не решил — баг это или фича :)
Итак, создал экшен-делегат. Пусть это будет /plugins/myblog/classes/actions/ActionBlog.class.php, как в примере выше. В этом экшене я хочу подключить несколько блоков. Причем один блок — стандартный, например, blogs, другой — собственный, идущий в комплекте с плагином.
Внимание вопрос: а где вьюер будет искать шаблоны для этих блоков? Ответ: в папке «глобального» скина, заданного для сайта в целом. Мне, конечно, не трудно туда шаблоны самопального блока скопировать. Но тогда ломается стройная схема плагинов, когда все — в одной кучке. Как быть?
По поводу блоков: там был уже предусмотрен механизм создания блока из плагина — для этого нужно добавить параметр блока 'plugin'. Посмотри как это реализовано в самом Smarty-плагине:
Или нет?
По меню посмотри @732 ревизию. Вставил минимум того, что планирую реализовать по menu-контейнерам (но это уже, наверное в 0.5 пойдет) =) Ту проблему, которую ты описал, косячно немного, но решает.
Основная идея в том, что система будет воспринимать параметр menu в первую очередь как название контейнера, а уж если такого нет — искать туда файл. Если контейнер определен, то Smarty подтянет на место меню заранее подготовленное меню (рендериться при Shutdown`е модуля Viewer).
Для добавления плагин меню, нужно в шаблоне из плагина указать название контейнера, например menu='my_plugin_blog', а в функции Init() плагина передать название шаблона в этот контейнер. Например,
Ситуация с меню вообще отдельная (достаточно сложная с архитектурной точки зрения). Обсуждалось неоднократно.
Я лично вижу выход только в создании некой абстракции (на подобие Zend`овских ViewsHelper) — вопрос не только в том, как добавить новое меню, а как например добавить новый пункт в старое, не заменяя его полностью? (ведь плагинов, которые пожелают это сделать может быть несколько)…
У тебя есть какие-нибудь предложения по этому поводу?
Так глубоко (как модификация меню) не копал пока. Хотя и думал в эту сторону. И пока больше склоняюсь к тому, что такие вещи должны, все же, не на уровне шаблонов решаться, а на уровне экшена (т.е. структура меню формируется в экшене, а в шаблоне уже только вывод). Все же если мы о модели 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»
Вуаля! Что нам и требовалось! И шаблон с инклюдами не трогаем ни пальцем!
И, кстати, раз уж разговор об этом зашел: мож стоит дать доступ непосредственно к экземпляру Смарти, который во вьюере сидит? Понимаю, что не очень кошерно, но в исключительных случаях у разработчика будет возможность какие-то «костыли» втыкать на более низком уровне.
Про ресурсы почитаю. Никогда не сталкивался. Но на вскидку скажу, что это не совсем то — здесь идет речь о возможности извлечения шаблонов не из файловой системы, а из других источников (как описано в примере — из базы данных).
В примере — да, из бд. Но, по-моему, это лишь для демонстрации того, что источники могут быть разными. Но нам ведь ничего не мешает в ф-ции xxx_get_template() читать не бд, а файл по какому-то определенному пути.
Кстати, прямо сейчас, раз все равно шаблоны для миграции на 0.4 надо слегка модифицировать, можно пойти очень простым путем, чтобы включать свое меню в плагинах. Берем кусок
… Также в корне должен лежать readme.txt, оформленный по следующему примеру...
Есть предложение — оставить readme.txt на откуп разработчикам (все же исторически сложилось, что в подобных файлах инфа для пользователя размещается). А для системы использовать какой-нить XML (скажем, пусть это будет plugin.xml). Тогда можно будет расширять формат этого файла, добавляя в него доп. ноды.
Напр., туда можно помещать инфу о статическом делегировании (как альтернативу описанному в посте свойству-переменной protected $aDelegates=array(...)). Тогда возможные конфликты можно будет выявлять не во время активации, а до активации плагина. (При этом уже имеющиеся механизмы делегирования оставить, но указывать на этот, как на предпочтительный).
В этом же файле можно будет и меню плагина описывать. Причем, можно предусмотреть в перспективе как полностью новое меню, которое будет создаваться плагином, так и модификацию имеющегося меню (напр., добавление нового пункта меню для экшена ActionBlogs и привязка к нему экшена, который будет обрабатываться плагином).
Это то, что на поверхности лежит, мож, со временем еще что придумается. Но тем и хорош XML, что очень хорошо расширяется и легко обеспечить совместимость снизу вверх.
Хотя ее использование и не прописано в коде системы плагинов. Честно говоря, не знаю нужно ли это. Можно, конечно, довести систему до того уровня, когда LS будет на основе переданного XML \ UML сама писать плагины… Только зачем?
Негодования никакого нет, просто стараюсь подойти более системно и универсально. Я не сторонник того, чтобы XML совать везде и всюду, где ни попадя. В каких-то случаях вполне достаточно обычного текстового формата. Но в данном случае XML-формат мне кажется более уместным. И проверка на конфликты ДО активации представляется тоже вполне логичной.
Но я ж не настаиваю, а просто высказываю пожелания, как предложено, и даже где-то пытаюсь их обосновать. :)
ЗЫ А создание плагина на основе какого-то файла описания — не такая уж и глупая мысль. ;)
А создание плагина на основе какого-то файла описания — не такая уж и глупая мысль.
Я знаю. У меня на в комнате на стенке висит доска для маркеров, на ней нарисована архитектура «саморасширяемой» livestreet (вплоть до UML-кодогенерации).
Если бы это было глупо, я бы про это не говорил. Но это настолько далекое будущее…
Смотрю сделали описания плагинов в xml, предусмотрите сразу многоязычность, т.к. собирались лс делать на широкий круг в том числе и иностранцев. В зависимости от того какой язык стоит в системе такую xml и показывать, или в одной xml несколько записей для разных языков.
Баг: если делается попытка активации плагина, а файл PluginName.class.php не найден, то получаем неопределенную переменную: Undefined variable: bResult in U:\home\local\tiande-online.ru\engine\modules\plugin\Plugin.class.php on line 145
Я вот подумал — наверное не очень хорошо, что файл, в котором инфа сохраняется по плагинам, лежит в папке /plugins. Я хоть и не параноик, но если можно не давать права на запись на папку, где исполняемые скрипты лежат, то я стараюсь этого не делать. Да и вообще, отделение кода от данных — это классика (а plugins.dat — это все же хоть и нано, но данные).
Может, стоит завести какую-то специальную папку для данных? Которую и другие разработчики могли бы использовать подобным же образом?
Когда выводится список плагинов в админке, то формировать его надо бы не по всем вложенным папкам, а только по тем, где есть правильный файл описания. Сейчас, если XML-файла нет (или он кривой), то пустая строка выводится.
Такой вопрос: что делать, если нужно незначительно изменить системные модули и экшены, нужно ли создавать их измененные копии и делегировать их?
Например, есть плагин расширенной юзер панели (с новыми функциями и т.п.) и нужно добавить ещё одно поле (допустим, Город) при регистрации нового юзера. Соответственно необходимо добавить по одной-две строки в файлы /templates/skin/new/actions/ActionRegistration/index.tpl, /classes/actions/ActionRegistration.class.php, /classes/modules/user/mapper/UserMapper.class.php. Как лучше поступить в таком случае? Напрямую редактировать эти файлы или создавать их измененные копии внутри плагина ImprovedPanel?
Это для проверки на возможность плагина работать вместе с другими?
Вообще, я думал о необходимости такой проверки: плагин А для своей работы требует активацию плагина Б — даже в plugin.xml отвел спец. секцию в requires. Но руки никак не дойдут =)
Нет, это не альтернатива requires. Секция requires (как я это понимаю) нужна, чтоб на этапе активации плагина проверять зависимость от других плагинов (и если пофантазировать и вспомнить идеи, которые когда-то MaxSvargal высказывал, то в будущем можно и автоматическую подгрузку сделать требуемых плагинов).
Но я щас не об этом. Я хотел бы проверять активирован ли какой-то другой плагин, чтобы, напр., исходя из этого в коде определять, можно ли использовать функционал этого другого плагина.
Здесь атрибут condition может быть [eq|ge|gt|le|lt]
А класс Plugin содержит метод GetVesrion(), который возвращает версию плагина (из его же plugin.xml).
Тогда можно будет более гибко указывать и зависимость от версии движка, и от версий других плагинов.
Я правильно понимаю, что на данный момент (0.4.2) в движке существует пересекающийся функционал — переопределение методов в классах и делегирующие хуки? Они же делают фактически одно и то же, НО делегирование нужно только для одной вещи — для полной замены tpl-файла на свой? Больше ни для чего оно не нужно, в остальных случаях (модули и т.п.) грамотнее использовать переопределение.
подскажите пожалуйста как мне прикрутить 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 ошибку то есть эдитор не может попасть из-за реврайта в директорию акшенов
если же выношу сам эдитор в другую директорию то не получается подгркзить сам эдитор из дир
помогите пожалуйста люди а то меня уволят на…
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.
Warning: Unknown: open(/home/webmaster/www/livestreet.ru/tmp/sess_nqs803mm0ggd2thfti72jjqdj3, O_RDWR) failed: No space left on device (28) in Unknown on line 0
Warning: Unknown: Failed to write session data (files). Please verify that the current setting of session.save_path is correct (/home/webmaster/www/livestreet.ru/tmp) in Unknown on line 0
90 комментариев
в качестве первого плагина предлагаю Ленту Друзей XD
многим пользователям будет полезна :)
И про прочтении возникла мысль — мож, и бредовая, но думал-то я ее пока несколько секунд всего — а вот бы еще «цепочки делегирования» организовать!
Значит, если я хочу создать расширение типового класса (ActionBlog, ActionTopic, etc), то у меня два пути:
1) Создавать плагин (действительно очень удобный механизм и прогрессивный), но при этом полностью воспроизводить в нем оригинальный класс, который я хотел бы расширить. Даже если мне надо добавить всего лишь пару строк, я не смогу воспользоваться такой классной штукой из области ООП, как наследование, и вынужден идти на сплошной копипаст. Что является, как говорят в авиации, «предпосылкой к происшествию» — любое изменение в оригинальном классе может привести к несовместимости плагина с движком.
2) Использовать менее удобное, но более универсальное и более правильное с точки зрения ООП кастомное расширение. Но при таком подходе это «достаточно хитрое и небезопасное расширение» становится как бы «нелегитимным» с точки зрения модульности.
Крепко задумался…
То о чем ты говоришь — «добавить пару строк» лучше делать через хуки. На event`ы можно ведь вешать хуки. Это достаточно удобный механизм. Если же нужно сменить ЛОГИКУ работу — тогда пользоваться делегатами.
На любые? По-моему, был предопределенный перечень событий, которые вызывали хуки, и перечень этот был конечным. Или в 0.4 реально на любой ивент можно хук вешать? И тут еще такая байда — в каких-то случаях надо хук ДО события вызвать, а в каких-то — ПОСЛЕ. Но, если я верно Макса понял и можно в плагинах юзать расширение стандартных модулей, то «ложка дегтя» попросту «умножается на ноль», то бишь — ее нет, и все отлично! :)
А использовать так же, как и раньше, ничего не изменилось?
И в этом случае получим и нормальное ООП-расширение станлартного модуля, и грамотно впишемся в механизм плагинов. Все верно?
Т.е. у тебя будет файл /plugins/myblog/PluginMyblog.class.php
А в файле /plugins/myblog/classes/actions/ActionBlog.class.php
Как-то так, на орфографию код не проверял — сочиняю прямо здесь =)
Метод getTemplatePathPlugin() доступен только в самом плагине, поэтому отпадает. Ниже ты еще упоминал Plugin::GetTemplatePath($sPluginName), но я его не вижу в Plugin.class.php. Куда-то не туда смотрю?
как в этом случае быть? чтобы не копировать весь код модуля Text? и еще, как из переопределенного методы вызвать оригинальный?
2. parent::
просто сначала я не врубился, раз для экшенов работает указание базового класса при наследовании, то будет и для модулей работать, а в связи с этим и про вызов метода родителя совсем забыл… :(
Спасибо за проделанную работу :)
Залогинился под администраторским аккаунтом. Не знаю в чем может быть проблема.
Обновился с версии 0.3.1
Помогите кто-нибудь, где копать?
Обновился с версии 0.3.1
Помогите кто-нибудь, где копать?
P.S.
Сам уже разобрался…
В \config\config.php
Забыл строку
$config['router']['page']['admin'] = 'ActionAdmin';
Что ты имеешь ввиду? Режим «Предпросмотр» и так уже реализован…
Странная постановка на самом деле.
а) «внедряться» в файлы можно только путем Viewer_Assign() с последующим выводом этой переменной в шаблоне. Описанный в статье механизм делегирования означает полную замену, а не «внедрение»;
б) «уже измененных шаблонов» — звучит равно как «Может ли хирург вырезать аппендицит, учитывая что структура тела была изменена внеземной формой радиации?»
я собираюсь переделывать у себя систему рейтинга.
там надо будет переписать принцип начисления рейтинга, и разрешить изменять\отменять свой голос. до этого я делал это хаком. теперь хочется всё это превратить в плагин
раньше я просто делал изменения в файлах, теперь, если я правильно всё понимаю можно просто их заменять в папке плагина
вот например файлы которые я тогда менял
я так понимаю надо сделать их копии в папке плагина, оставить в них только функции, которые я буду менять и собственно их менять. а потом просто указать, что я поменял вот эти и эти функции…
Без знания ООП тебе будет сложновато разобраться. Дождись лучше конкретного примера, сделаешь по аналогии. Или расскажи, что ты меняешь — я этот случай проанализирую в статье.
это конечно неполная и устаревшая версия хака, но в целом там видно, что надо делать
если найду архив с изменёнными файлами докину ссылку
Единственное, что пока не складывается в моей голове — реализация нескольких плагинов, претендующих на один ресурс. Скажем простой пример — в шаблон профиля один плагин добавляет контакт Skype, а второй Телефон. Как я понимаю в этом случае возникнет конфликт.
Двинемся глубже. Для тех двух типов контактов нам потребуется изменение и action, module, entity и mapper (кстати, их можно делегировать?)… И с таким пересечением действительно часто приходится сталкиваться. Как быть в таких случаях?
Грубо говоря, я делаю плагин, который добавляет номер телефона в инфо о пользователи. А еще нужен скайп, вконтакте и т.д. Будет ли мой плагин востребован — ровно до тех пор, пока не будет заменителя. И когда-то найдется разработчик, который напишет легко кастомизируемое пользователем расширение, с произвольным расширением данных…
Тут нужно понимать, что задача разработчиков предоставить механизм, а не вкручивать мозги разработчикам =)
Mapper нельзя делегировать, мепперы подключаются в каждом модуле отдельно, поэтому для делегирования меппера нужно переопределить тот код в модуле, который отвечает за подключение меппера.
Еще хороший пример — добавление в профиль пользователя микроблога и галереи одновременно.
В нашем случае придется либо отказаться от какого-то плагина, либо сделать новый, который будет совмещать необходимые конкретному проекту возможности. :-)
Честно говоря, я пока и сам не вижу решения, но проблема типичная для хаков.
Те имеем простой массив данных, который рендерится движком с учетом шаблонов модулей
Если надо внести какие изменения, просто альтерим массив и вносим свои изменения в него.
Если не понятно, поясню.
Для обслуживания этого процесса в ActionPlugin есть функция getTemplatePathPlugin(), есть еще Plugin::GetTemplatePath($sPluginName) — обе эти функции возвращают адрес директории с шаблонами плагина с учетом наличия (либо не наличия) нужного скина.
У нас в плагине предусмотрены шаблоны плагинов default, new, develper. На сайте используется скин new, а я указываю в плагине выводить шаблоны не new, а developer… Я правильно понял идею? Зачем такое может понадобиться?
можно создать скины new1, new2 и т.п., а разработчик пусть сам определяет какой скин отображать. Он в принципе и сейчас это может сделать, но удобнее будет через setTemplatePathPlugin. Т.е. оформление плагина может быть абсолютно любым и не привязано к текущему шаблону, просто по дефолту ищется совпадение с текущим.
Не стоит городить дополнительный функционал, это решается уже имеющимися средствами.
Итак, создал экшен-делегат. Пусть это будет /plugins/myblog/classes/actions/ActionBlog.class.php, как в примере выше. В этом экшене я хочу подключить несколько блоков. Причем один блок — стандартный, например, blogs, другой — собственный, идущий в комплекте с плагином.
Внимание вопрос: а где вьюер будет искать шаблоны для этих блоков? Ответ: в папке «глобального» скина, заданного для сайта в целом. Мне, конечно, не трудно туда шаблоны самопального блока скопировать. Но тогда ломается стройная схема плагинов, когда все — в одной кучке. Как быть?
Аналогичным образом и экшена-делегата устанавливается шаблон для отрисовки страницы:
Но, наверное, логично было бы добавить методы типа AddPluginBlocks($sPlugin, $sGroup, $aBlocks) и SetPluginTemplate($sPlugin, $sTemplate).
Я там в @733 только немного пофиксил под новую функцию.
По поводу SetTemplate(), если я тебя правильно понял, то ты говоришь про это:
Или нет?
По меню посмотри @732 ревизию. Вставил минимум того, что планирую реализовать по menu-контейнерам (но это уже, наверное в 0.5 пойдет) =) Ту проблему, которую ты описал, косячно немного, но решает.
Основная идея в том, что система будет воспринимать параметр menu в первую очередь как название контейнера, а уж если такого нет — искать туда файл. Если контейнер определен, то Smarty подтянет на место меню заранее подготовленное меню (рендериться при Shutdown`е модуля Viewer).
Для добавления плагин меню, нужно в шаблоне из плагина указать название контейнера, например menu='my_plugin_blog', а в функции Init() плагина передать название шаблона в этот контейнер. Например,
Аналогичным вызовом, можно заменить существующее меню своим.
:(
Чего-нибудь могут разработчики посоветовать?
Я лично вижу выход только в создании некой абстракции (на подобие Zend`овских ViewsHelper) — вопрос не только в том, как добавить новое меню, а как например добавить новый пункт в старое, не заменяя его полностью? (ведь плагинов, которые пожелают это сделать может быть несколько)…
У тебя есть какие-нибудь предложения по этому поводу?
Что же касается сегодняшнего дня, использования в плагинах меню и вообще «кусочного» собирания шаблона (через {include file=«name.tpl»}), то вижу такое решение: В Smarty есть такое понятие, как «ресурс» (см.
1) Объявить ресурсы «plugin» и «menu.plugin», которые будут ссылаться скин плагина
2) В плагиновском экшене я указываю:
Тогда в header_nav.tpl я получу (после подстановки переменной):
Что будет означать «взять шаблон blabla.tpl из ресурса menu.plugin»
Вуаля! Что нам и требовалось! И шаблон с инклюдами не трогаем ни пальцем!
И, кстати, раз уж разговор об этом зашел: мож стоит дать доступ непосредственно к экземпляру Смарти, который во вьюере сидит? Понимаю, что не очень кошерно, но в исключительных случаях у разработчика будет возможность какие-то «костыли» втыкать на более низком уровне.
и меняем его:
И все! А в плагине разработчики пусть набивают menu_text, чем хотят.
И есть ли там исправление (если установлено на IIS то русские тэги не работают)?
Есть предложение — оставить readme.txt на откуп разработчикам (все же исторически сложилось, что в подобных файлах инфа для пользователя размещается). А для системы использовать какой-нить XML (скажем, пусть это будет plugin.xml). Тогда можно будет расширять формат этого файла, добавляя в него доп. ноды.
Напр., туда можно помещать инфу о статическом делегировании (как альтернативу описанному в посте свойству-переменной protected $aDelegates=array(...)). Тогда возможные конфликты можно будет выявлять не во время активации, а до активации плагина. (При этом уже имеющиеся механизмы делегирования оставить, но указывать на этот, как на предпочтительный).
В этом же файле можно будет и меню плагина описывать. Причем, можно предусмотреть в перспективе как полностью новое меню, которое будет создаваться плагином, так и модификацию имеющегося меню (напр., добавление нового пункта меню для экшена ActionBlogs и привязка к нему экшена, который будет обрабатываться плагином).
Это то, что на поверхности лежит, мож, со временем еще что придумается. Но тем и хорош XML, что очень хорошо расширяется и легко обеспечить совместимость снизу вверх.
В readme.txt система читает только первые строки.
Остальное содержание можно писать как угодно.
Хотя ее использование и не прописано в коде системы плагинов. Честно говоря, не знаю нужно ли это. Можно, конечно, довести систему до того уровня, когда LS будет на основе переданного XML \ UML сама писать плагины… Только зачем?
Но я ж не настаиваю, а просто высказываю пожелания, как предложено, и даже где-то пытаюсь их обосновать. :)
ЗЫ А создание плагина на основе какого-то файла описания — не такая уж и глупая мысль. ;)
Я знаю. У меня на в комнате на стенке висит доска для маркеров, на ней нарисована архитектура «саморасширяемой» livestreet (вплоть до UML-кодогенерации).
Если бы это было глупо, я бы про это не говорил. Но это настолько далекое будущее…
Undefined variable: bResult in U:\home\local\tiande-online.ru\engine\modules\plugin\Plugin.class.php on line 145
Может, стоит завести какую-то специальную папку для данных? Которую и другие разработчики могли бы использовать подобным же образом?
Например, есть плагин расширенной юзер панели (с новыми функциями и т.п.) и нужно добавить ещё одно поле (допустим, Город) при регистрации нового юзера. Соответственно необходимо добавить по одной-две строки в файлы /templates/skin/new/actions/ActionRegistration/index.tpl, /classes/actions/ActionRegistration.class.php, /classes/modules/user/mapper/UserMapper.class.php. Как лучше поступить в таком случае? Напрямую редактировать эти файлы или создавать их измененные копии внутри плагина ImprovedPanel?
На базе него можно такую простеньку ф-цию написать:
Вообще, я думал о необходимости такой проверки: плагин А для своей работы требует активацию плагина Б — даже в plugin.xml отвел спец. секцию в requires. Но руки никак не дойдут =)
Но я щас не об этом. Я хотел бы проверять активирован ли какой-то другой плагин, чтобы, напр., исходя из этого в коде определять, можно ли использовать функционал этого другого плагина.
Здесь атрибут condition может быть [eq|ge|gt|le|lt]
А класс Plugin содержит метод GetVesrion(), который возвращает версию плагина (из его же plugin.xml).
Тогда можно будет более гибко указывать и зависимость от версии движка, и от версий других плагинов.
Я правильно понимаю, что на данный момент (0.4.2) в движке существует пересекающийся функционал — переопределение методов в классах и делегирующие хуки? Они же делают фактически одно и то же, НО делегирование нужно только для одной вещи — для полной замены tpl-файла на свой? Больше ни для чего оно не нужно, в остальных случаях (модули и т.п.) грамотнее использовать переопределение.
Ошибка: Файл плагина не найден
Подскажите как его запустить)
* Добавление топика
*
* @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 ошибку то есть эдитор не может попасть из-за реврайта в директорию акшенов
если же выношу сам эдитор в другую директорию то не получается подгркзить сам эдитор из дир
помогите пожалуйста люди а то меня уволят на…