Создание плагина. От идеи до публикации. Часть 1.

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

Приступим
В начале создадим в папке plugins папку для нашего плагина с именем carousel и в нем файл plugin.xml c описанием создаваемого плагина (см. картинку ниже).


Файл содержит шесть основных секций, носящих скорее описательный характер, эти секции:
1. name: наименование плагина (используется как поле для сортировки плагинов в полном их и в tpl );
2. author: имя автора плагина (используется только в tpl);
3. homepage: домашняя страница плагина (используется только в tpl);
4. version: версия плагина (используется только в tpl);
5. requires: требования, которым должна удовлетворять платформа для того, что бы плагин успешно мог функционировать, в нашем случае указываем только версию livestreet – 1.x. (используется при проверке возможности установки плагина, возможен доступ из tpl, хотя в стандартных шаблонах не используется);
6. description: описание плагина. Заметьте, что теги описания содержат атрибут lang, указывающий язык приведенных данных (используется только в tpl).
В приведенном примере не указана секция settings, которая определяет страницу настроек плагина, но она будет рассмотрена далее. Представленная структура файла является типовой и с небольшими изменениями используется во всех плагинах, но для более полного изучения структуры Вы можете посмотреть метод GetList класса ModulePlugin, реализованного в файле Plugin.class.php и plugin.tpl любого шаблона – отвечающий за вывод страницы админки со списком плагинов.
Вот теперь мы создали минимально-необходимый набор файлов (один), для того, что бы плагин отобразился в системе, однако для того, что бы была возможность его активировать и, собственно, полноценно работать нужно создать основной класс плагинов, от которого будут создаваться его экземпляры.

Создание основного класса плагина
Основным классом плагина является файл с именем [PluginName]Plugin.class.php, в нашем случае CarouselPlugin.class.php, унаследованный от общего для всех плагинов класса Plugin, и содержащий один единственный (пока единственный) метод Init.
<?php
if (!class_exists('Plugin')) {
    die('You are bad hacker, try again, baby!');
}

class PluginCarousel extends Plugin {

    /** Инициализация плагина */
    public function Init() {
        parent::Init();

    }

}

Перед объявлением класса запрещаем прямой доступ к файлу. В архитектуре LS, работа с плагинами реализуется через класс Plugin (еще раз скажу, что они от него наследуются) и поэтому логично сделать проверку на наличие родительского класса. И ниже – сам класс плагина, в теле метода инициализации плагина первой строкой указан вызов родительского метода инициализации, его можно не указывать поскольку родительский метод пуст, но для красоты кода и как задел на будущее (вдруг разработчики LS придумают инициализацию родителя, или Ваш проект этого потребует) — оставим.
Теперь плагин можно активировать/деактивировать!

Добавление кнопки в редактор
Здесь и далее подразумевается, что в LS используется редактор Markitup!
Для того, что бы добавлять слайдер-карусель в текст создаваемого топика необходимо добавить кнопку в редакторе и… навязать на нее соответствующий функционал. Настройки самого редактора хранятся в файле, находящимся тут: engine/lib/internal/template/js/settings.js. В этом файле создается объект ls.settings, который имеет метод получения натроек – getMarkitup(). Разумеется, мы не станем трогать файлы движка и реализуем добавление кнопки в плагине. Делается это так:
Объект ls.settings, уже существует до инициализации плагина, поэтому мы можем переопределить его родной метод получения настроек на необходимый нам, тот, который будет добавлять заветную кнопочку. В корневой папке плагина создадим папку js с файлом carouselScript.js, содержащий следующий код:

/** Получим текущие настройки редактора */
var newMarkitupSettings = ls.settings.getMarkitup();

ls.settings.getMarkitup = function() {
    /** В текущий набор кнопок добавим еще одну, окружающую картинки тегом "<carousel>" */
    newMarkitupSettings.markupSet = newMarkitupSettings.markupSet.concat([
        {separator: '---------------'},
        {
             className   : 'editor-carousel',
            key         : 'G',
            openWith    : '<carousel>',
            closeWith   : '</carousel>',
            beforeInsert: function() {

            }
        }
    ]);

    return newMarkitupSettings;
};

Подход, который здесь использован довольно простой: во временную переменную newMarkitupSettings мы сохраняем текущие настройки редактора, а затем переопределяем функцию получения настроек ls.settings.getMarkitup() собственной, в которой мы добавляем разделитель и новую кнопку. Если с разделителем все ясно, то новая кнопка задается объектом, свойства которого и определяют параметры создаваемой кнопки:
— className: класс CSS для текущей кнопки;
— key: Горячая клавиша, используется в комбинации с Ctrl;
— openWith: открывающий тег;
— closeWith: закрывающий тег;
— beforeInsert: функция, выполняющаяся до окружения выделенного текста указанными выше тегами openWith и closeWith.
И на последок, для того, что бы все заработало, необходимо подключить созданные файлы скриптов к LS. Сделаем это в методе init нашего плагина как указано в коде ниже.

class PluginCarousel extends Plugin {

    /** Инициализация плагина */
    public function Init() {
        parent::Init();

        /** Для добавления кнопки в редатктор запустим скрипт, корректирующий markitup */
        $this->Viewer_AppendScript(dirname(__FILE__) . "/js/carouselScript.js");
    }

}

С помощью метода AppendScript файлы скрипта, переданные в параметре метода, добавляются в очередь на подключение инструментами движка LS. Каждый раз при инициализации плагина будет выполняться подключение созданных скриптов которые, в свою очередь, будут расширять настройки редактора. Если в Вашем проекте используется TinyMCE, то указанные действия необходимо будет осуществить для метода получения настроек TinyMCE – getTinymce(), расположенном в том же файле: engine/lib/internal/template/js/settings.js.

После всех вышеприведенных действий получим следующую структуру каталогов и файлы в них. Кнопка в редакторе появилась, но на ней нет картинки и она сразу не заметна, поищите ее)

Стилизация кнопки
Добавим несколько штрихов к внешнему виду кнопки – добавим на нее изображение. Для этого изображение нужно найти и вопрос где? Я использую сервис Iconizer. Изображение должно быть 16px на 16px и после того как мы найдем нужное, скопируем его в созданную в корне плагина папку images.
В настройках добавляемой кнопки мы определили ее класс, теперь, создадим файл style.css в каталоге styles, находящегося в корне плагина со следующим содержанием:
.editor-carousel {
    background: url('../images/layers.png') no-repeat;
}

Для того, что бы файл каскадных стилей был доступен его необходимо подключить. Опять же, в методе init нашего плагина добавим следующую строчку кода ниже строк подключения скриптов:

/** Добавление стилей */
$this->Viewer_AppendStyle(dirname(__FILE__) ."/css/style.css");


Итоги

Первый этап создания плагина пройден. Вспомним еще раз, что было сделано:
1. Создана структура каталогов плагина;
2. Создан файл plugin.xml с описанием создаваемого плагина;
3. На панель редактора добавлена кнопка, создающая в тексте тег пару тегов «carousel».

Продолжение
Часть 2, Часть 3

21 комментарий

avatar
Спасибо за большую проделанную работу по написанию мануала для новичков!
avatar
Прекрасное пособие. И хорошо написано.
  • aex
  • 0
avatar
Спасибо
avatar
Вопрос, какой текстовой редактор юзает автор?
avatar
Если имеется ввиду редактор ls, то стандартный Markitup (на картинках с кнопками он так выглядит в шаблоне Social), а если IDE, то PhpStorm.
avatar
После добавления кнопки, перестал выводится текст всплывающей подсказки при наведении мышы и текст в окне ввода для остальных кнопок, кнопки работают, но тектов нету, с чем может быть связано?
avatar
Связано такое «странное» поведение со следующими особенностями реализации плагина и MarkitUp-а и особенностями языка JS:
Добавление кнопки через плагин реализовано подменой функции getMarkitup так:
var newMarkitupSettings = ls.settings.getMarkitup();
, потом к переменной newMarkitupSettings добавляются настройки новой кнопки. Как указано здесь, в этой статье. Но, так как переопределяемая getMarkitup — это именно функция, а не объект и возвращает рассчитанное значение. То есть без плагина getMarkitup срабатывает в момент вывода редактора и имеет доступ к массиву надписей, а с плагином — функция вызывается до загрузки надписей и, соответственно, доступа к ним не имеет. Для того что бы исправить ситуацию необходимо фрагмент кода, который указан выше:
/** Получим текущие настройки редактора */
var newMarkitupSettings = ls.settings.getMarkitup();

ls.settings.getMarkitup = function() {
    /** В текущий набор кнопок добавим еще одну, окружающую картинки тегом "<carousel>" */
    newMarkitupSettings.markupSet = newMarkitupSettings.markupSet.concat([
        {separator: '---------------'},
        {
             className   : 'editor-carousel',
            key         : 'G',
            openWith    : '<carousel>',
            closeWith   : '</carousel>',
            beforeInsert: function() {

            }
        }
    ]);

    return newMarkitupSettings;
};

Заменить на полный набор кнопок редактора, взятый из settings.js с добавлением кнопки карусели:
ls.settings.getMarkitup = function () {
    return {
        onShiftEnter:  	{keepDefault:false, replaceWith:'<br />\n'},
        onCtrlEnter:  	{keepDefault:false, openWith:'\n<p>', closeWith:'</p>'},
        onTab:    		{keepDefault:false, replaceWith:'    '},
        markupSet:  [
            {name:'H4', className:'editor-h4', openWith:'<h4>', closeWith:'</h4>' }, /// И т.д.
   /// Далее идут настройки кнопок, взятые из settings.js..................................
            {separator:'---------------'},
            {
                name: "Карусель",
                className:'editor-carousel',
                key:'G',
                openWith:($("#window_upload_carousel").length == 0) ? '<carousel>' : '',
                closeWith:($("#window_upload_carousel").length == 0) ? '</carousel>' : '',
                beforeInsert:function () {
                    ls.blocks.initSwitch('upload-carousel');
                    jQuery("#window_upload_carousel")
                        .jqm()
                        .jqmShow()
                }
            }
        ]
    }
};

Такой подход мне кажется не очень правильным, поскольку мы убиваем исходную функцию, а в ней могут быть изменения. При использовании такого подхода следует учитывать особенности других плагинов и ранее внесенных в движок изменений. Более правильной реализации я пока не вижу, но и эта мне не нравиться, поэтому решил пожертвовать надписями.
avatar
По поводу добавления кнопок…
Добавил две кнопки в начало таким способом.
oSettings = ls.settings.getMarkitup ();
    oSettingsButtonSet = oSettings ['markupSet'];
    oSettingsButtonSet.unshift (
	{name:'H2', key:'2', className:'editor-h2', openWith:'<h2>', closeWith:'</h2>' },
	{name:'H3', key:'3', className:'editor-h3', openWith:'<h3>', closeWith:'</h3>' }
    );

    $.extend (true, oSettings, {'markupSet': oSettingsButtonSet});

    ls.settings.getMarkitup = function () { return oSettings;}
avatar
Если этот код реализовать в плагине, то кнопки добавляются, но всплывающей подсказки как не было, так и нет.(
avatar
name — это и есть подсказка (title)
li = $('<li class="markItUpButton markItUpButton'+t+(i)+' '+(button.className||'')+'"><a href="" '+key+' title="'+title+'">'+(button.name||'')+'</a></li>')

смотрите код панели на странице
avatar
имеется ввиду, что массив в ls.lang еще не будет заполнен текстовками и title в итоге будет пустым
решение я предложил ниже
avatar
Вот так?

	ls.plugin.myplug.fGetMarkitup = ls.settings.getMarkitup;
	ls.settings.getMarkitup = function() {
		oSettings = ls.plugin.myplug.fGetMarkitup();
		oSettingsButtonSet = oSettings['markupSet'];
		oSettingsButtonSet.push({кнопка});
		$.extend (true, oSettings, {'markupSet': oSettingsButtonSet});
		return oSettings;
	}
avatar
Да, именно так! Спасибо за решение.
avatar
я правильно понимаю, что этот код нужно в файл ява скрипта в папке шаблона плагина добавить и прописать в oSettingsButtonSet.push({кнопка}); кнопку вида:
oSettingsButtonSet.push({name: ls.lang.get('panel_plugin'), className: 'editor-plugin', key: 'p', openWith: '<plugin>', closeWith: '</plugin>'});
при этом еще myplug заменил на название плагина (хотя насколько понимаю это не обязательно). Кнопка не появляется.
avatar
Да. У меня измененный вариант выглядит так:
var carousel_tmp_function = ls.settings.getMarkitup;
ls.settings.getMarkitup = function() {
    oSettings = carousel_tmp_function();
    oSettingsButtonSet = oSettings['markupSet'];
    oSettingsButtonSet.push({
        separator:'---------------'
    });
    oSettingsButtonSet.push(
        {
            name: 'Карусель',
            className:'editor-carousel',
            key:'G',
            openWith:($("#window_upload_carousel").length == 0) ? '<carousel>' : '',
            closeWith:($("#window_upload_carousel").length == 0) ? '</carousel>' : '',
            beforeInsert:function () {
                ls.blocks.initSwitch('upload-carousel');
                jQuery("#window_upload_carousel")
                    .jqm()
                    .jqmShow()
            }
        }
    );
    $.extend (true, oSettings, {'markupSet': oSettingsButtonSet});
    return oSettings;
};
avatar
ха, var забыл добавить, и сижу ковыряю шо ж такое :) Еще есть одна проблемка, возле изображения какой-то непонятный дефект, без изображения тоже выводит:

Выводился он и при первоначальном виде добавления кнопки
avatar
У Вас по прежднему ошибка в коде. Настройки кнопок не применяются.(
avatar
да нет, все работает на 100%, кнопка работает, всплывающие подсказки тоже, плагин работает, просто появляется этот дефект.
когда иконка вставлена через ксс, выглядит так:

комментирую строку в ксс со вставкой иконки (т.е. в теории кнопка толжна быть невидима), кнопка работает, но дефект остается:
avatar
Определите правильный класс для кнопки.
А в этом «правильном классе» поиграйте со значением css: background-position;
Эта точка — часть картинки, которую нужно сдвинуть в сторону или совсем убрать
avatar
Спасибо разобрался. Эта точка является фоном родных маркитап иконок, поскольку они сделаны одним файлом и выводятся в css с помощью указания координат иконки в background-position, прописаны в css у маркитапа для тэга ссылки, а иконку плагина мы выводим с помощью подкласа «editor-», то браузер показывает сразу два бэкграунда, для ссылки и для подкласса. При переопределении.markItUp a {background-position: 10px, 50%;} в файле style.css плагина все стало ок. Большое спасибо, жаль рейтингом слаб, плюсануть не могу.
avatar
Отличное описание, спасибо большое!
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.