Использование разных шаблонов плагинов для каждой темы дизайна

10

Товарищи! Недавно столкнулся с проблемой централизованной работы плагинов с шаблонами в темах дизайна. Т.е. она отсутствовала, есть некоторые плагины в которых написано: создайте шаблон в теме дизайна , тогда будет использоваться он и тд...

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

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

Сам класс тут https://github.com/webasyst/webasyst-framework/pul...


Как работает класс?

В классе есть три режима выбора шаблонов,

  1. Шаблоны из плагина
  2. Шаблоны из текущей темы дизайна
  3. Шаблоны, переданные напрямую из плагина - такой режим дает возможность устанавливать свои шаблоны плагина не только на отдельную тему дизайна, но и на каждую витрину отдельно.

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

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


ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ1. Использование класса для управления внутренними шаблонами плагина, сугубо для внутреннего использования в плагине

Описываем массив в спец методе в плагине:

<?php
//....
public function getRegisterTemplates() {
        return array(
            'plugin' => array(
                /* BACKEND */
                'backend.sidebar.html' => array(
                    'path' => '/templates/backend/sidebar.html',
                ),
                'settings.products.tab.html' =>  array(
                    'path' => '/templates/backend/settings/SettingsProductsTab.html',
                ),
                'backend_css' => array(
                    'path' => '/css/shopMypluginPluginBackend.css',
                ),
                'backend_js' => array(
                    'path' => '/js/shopMypluginPluginBackendSettings.js',
                ),
                /* frontend */
                //  ......
            ),
            'theme' => array()
        );
    }

Используем:

<?php
//....
public function execute() {
        
        $plugin = wa('shop')->getPlugin('brands');
        $templates = new waPluginTemplates($plugin);
        $view = wa()->getView();
        $view->assign($data);
        //.... получаем html с использованием шаблона /templates/backend/sidebar.html
        $sidebar_html = $view->fetch($templates->getTemplate('backend.sidebar.html'));
        //......
        $view->assign('sidebar', $sidebar_html);
        //..... получаем html с использованием шаблона /templates/backend/settings/SettingsProductsTab.html
        $products_tab = $view->fetch($templates->getTemplate('settings.products.tab.html'));
        $view->assign('products_tab_html', $products_tab);
        
        // подождите, у нас еще и статические файлы записаны, отдаем их готовыми линками в представление
        $js_link = '<script src="'.$templates->getTemplateUrl('backend_js').'"></script>';
        $css_link = '<link href="'.$templates->getTemplateUrl('backend_css').'" rel="stylesheet" type="text/css">';
        $view->assign('js_link', $js_link);
        $view->assign('css_link', $css_link);
        // Или сразу передаем в HEADER
        $this->getResponse()->addJs($templates->getTemplateUrl('backend_js'));
        $this->getResponse()->addCss($templates->getTemplateUrl('backend_сыы'));
    }

Вы решили поменять адрес любого файла или переименовать? Не проблема, файлы берутся по своим строковым ключам, поэтому изменив адрес в массиве вы измените сразу во всем плагине.


2. Возможность использования дополнительных пользовательских файлов js или css, чтобы пользователи сами могли добавить стили в ваш плагин для адаптации под тему

добавляем описание файла в ячейку массива типа шаблонов theme

<?php
//....
public function getRegisterTemplates() {
        return array(
            'plugin' => array(
                //  ......
            ),
            'theme' => array(
                'style.css'  =>  array(
                    'path' =>  'style.css',
                    'description' => 'Дополнительный файл Css для переопределения поведения плагина для темы',
                    'content' => '/* Впишите сюда стили и плагин сам их подключит в нужном месте */ ',
                ),
            )
        );
    }

Важно! Если в типе THEME в данных файла указать ячейку 'content', то даже при совпадении ключа шаблона из массива шаблонов типа plugin, будет браться контент для записи в файл из ячейки 'content', файл плагина будет проигнорирован, но если установить тип использовать шаблоны плагина, будет браться файл из плагина. Таким образом можно манипулировать разными файлами.


3. Использование подготовленных разных файлов стилей и js для тем дизайна или создание файлов адаптации под каждую тему с разным контентом

Зачем? Часто во время показа плагина на витрине,он не вписывается в дизайн темы, вспомогательный файл стилей в самой теме с уже готовыми стилями поможет.

Создаем кастомный массив:

<?php
$themes_templates = array(
                'topshop' =>array(
                    'style.css'  =>  array(
                        'path' => 'style.css',
                        'description' => 'Дополнительный файл Css для переопределения поведения плагина для темы Топшоп',
                        'content' => '.topshop .muplugin h3 { color: #cc0000; }  /* Стилей конечно же больше */  ',
                    ),
                ),
                'yourshop'  =>  array(
                    'style.css'  =>  array(
                        'path' =>  'style.css',
                        'description' => 'Дополнительный файл Css для переопределения поведения плагина для темы Yourshop',
                        'content' => '.yourshop-main .muplugin h3 { color: #cc0000; } /* Стилей конечно же больше */ ',
                    ),
                ),
    );

Создаем экшен записи файлов в темы:

<?php
//....
public function execute() {
        $themes_templates = array(
            /* Тот самый массив для тем */
        );
        $plugin = wa('shop')->getPlugin('brands');
        $templates_class = new waPluginTemplates($plugin);
        // Получаем все темы
        $all_themes = waPluginTemplates::getThemes('shop');
        // Пишем в нужные темы нужные кастомные файлы
        foreach ($all_themes as $theme) {
            $templates = array();
            if(array_key_exists($theme['id'], $themes_templates)) {
                $templates = $themes_templates[$theme['id']];
            }
            foreach ($templates as $key => $template) {
                $templates_class->templateAddToTheme($theme, $template['path'],$template['content'],$template['description']);
            }
        }
    }

Ура файлы созданы!

Теперь надо хитро зарегистрировать файл, чтобы класс шаблонов его подхватывал, для этого описываем файл с массиве шаблонов с типом THEME:

<?php
//....
public function getRegisterTemplates() {
        return array(
            'plugin' => array(
                //  ......
            ),
            'theme' => array(
                'style.css'  =>  array(
                    'path' =>  'style.css',
                    'description' => 'Дополнительный файл Css для переопределения поведения плагина для темы',
                ),
            )
        );
    }

Важно! В типе шаблонов 'plugin' должен ОТСУТСТВОВАТЬ ФАЙЛ с ключем 'style.css', Также необходимо записать файлы в темы дизайна до копирования общего списка файлов в темы дизайна! тогда при копировании уже созданные файлы не будут перезаписаны описанным файлом в общем списке шаблонов для темы!

Использование:

<?php
//....
public function execute() {
        $plugin = wa('shop')->getPlugin('brands');
        $templates_class = new waPluginTemplates($plugin);
        // В экшене фронтенда
        // Отдаем основной файл стилей
        $this->getResponse()->addCss($main_css);
        // Если в теме есть файл адаптации, отдаем его позже
        $advanсed_style = $templates_class->getTemplateUrl('style.css');
        if($advanсed_style) {
            $this->getResponse()->addCss($advanсed_style);
        }
    }
4. Переменное использование шаблонов из плагина или темы, возможность менять шаблоны плагина для каждой темы дизайна

Весь смысл класса в данном примере!

Зачем? В моем плагине, была необходимость давать пользователю полный контроль над шаблонами, показываемыми на витрине. Пользователь может сам зайти в настройки плагина, создать все шаблоны для конкретной темы, и менять их по своему усмотрению. Представьте что надо вывести блок в карточке товара в блоке корзины, везде верстка разная, поэтому иногда приходится менять сам шаблон показа под конкретную тему, чаще всего это пытаются делать на костылях через css, а почему, потому что делать настройки под каждую тему невозможно, но можно создать файлы в самой теме и дать возможность их править! Кстати, когда говорят что что-то не работает в моем плагине, я говорю переключитесь режим использования шаблонов плагина, если так же не будет работать напишите снова, редко пишут)

Снова описываем массив, только сейчас он будет большим и часть ключей файлов будет одинаковая во всех типах шаблонов:

<?php
public function getRegisterTemplates() {
        array(
            'plugin' => array(
                /* BACKEND */
                //......
                'backend_js' => array(
                    'path' => '/js/shopMypluginPluginBackendSettings.js',
                ),
                /* frontend */
                'frontend.js' => array(
                    'path' => '/js/frontend.js',
                    'description' => 'Оcновной код JS'
                ),
                'frontend.css' => array(
                    'path' => '/css/frontend.css',
                    'description' => 'Оcновной код CSS'
                ),
                /* ШАБЛОНЫ КОТОРЫЕ БУДЕМ КОПИРОВАТЬ В ТЕМЫ */
                'product.html' =>  array(
                    'path' => '/templates/frontend/product.html',
                    'description' => 'Основной файл показа отзывов продукта с формой'
                ),
                'search.html' => array(
                    'path' => '/templates/frontend/search.html',
                    'description' => 'Показывает доп блок при поиске'
                ),
                
                'advanced.css' => array(
                    'path' => '/css/advanced.css',
                    'description' => 'Дополнительные стили плагина'
                ),
                
            ),
            'theme' => array(
                'product.html' =>  array(
                    'path' => 'product.html',
                    'description' => 'Основной файл показа отзывов продукта с формой'
                ),
                'search.html' => array(
                    'path' => 'search.html',
                    'description' => 'Показывает доп блок при поиске'
                ),
                'advanced.css' => array(
                    'path' => 'advanced.css',
                    'description' => 'Дополнительные стили плагина'
                ),
              
            ),
        );
    }

Что в массиве:

1. В массиве с типом шаблонов 'plugin' имеются файлы для использования исключительно внутри экшенов плагина, файлы для работы на витрине - frontend.js и frontend.css, а также именно те файлы которые будут интегрированы в темы дизайна ('product.html' , 'search.html' ,'advanced.css' ). Заметьте, что у данных файлов ключи одинаковые с файлами , описанными в массиве шаблонов для тем дизайна.

Создадим экшен копирования файлов

<?php
public function execute() {
        $plugin = wa('shop')->getPlugin('brands');
        $templates = new waPluginTemplates($plugin);
        $themes = waPluginTemplates::getThemes('shop');
        if(!empty($themes) && is_array($themes)) {
            $return = true;
            foreach ($themes as $theme) {
                if(($theme instanceof  waTheme)) {
                    // Пишем шаблоны в тему
                    if(!$templates->templatesCopyToTheme($theme)) {
                        $return = false;
                    }
                }
            }
            if(!$return) {
                $this->setError('Не удалось создать файлы в темах дизайна');
            }
        }
    }

В классе также имеются методы копирования шаблонов в одну тему дизайна и копирование одного шаблона в тему дизайна по его ключу!

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

<?php
public function execute() {
        $plugin = wa('shop')->getPlugin('brands');
        $templates_class = new waPluginTemplates($plugin);
        // Если включен режим отладки берем шаблоны из плагина
        if(waSystemConfig::isDebug()) {
            $templates_class->setTemplatesType('plugin');
        } else {
            // берем шаблоны и темы дизайна, если существуют, иначе возьмет из плагина
            $templates_class->setTemplatesType('theme');
        }
        
        // В экшене фронтенда
        // Отдаем статические файлы плагина в хеадер ...plugin/js/frontend.js  и  ...plugin/css/frontend.css
        $this->getResponse()->addJs($templates_class->getTemplateUrl('frontend.js'));
        $this->getResponse()->addCss($templates_class->getTemplateUrl('frontend.css'));
        $view = wa()->getView();
        // Получаем шаблон для продукта ...theme_path/plugin_id/product.html  а если не существует в теме то ...plugin/templates/frontend/product.html
        $product_template = $templates_class->getTemplate('product.html');
        $view->assign('product', $this->product);
        // Получаем готовый код с шаблоном продукта
        $product_html = $view->fetch($product_template);
        //.....
        $view->assign('search', $this->search);
        //.....
        // Получаем шаблон поиска ...theme_path/plugin_id/search.html  а если не существует в теме то ...plugin/templates/frontend/search.html
        $search_template = $templates_class->getTemplate('search.html');
        // Получаем готовый код с шаблоном продукта
        $search_html = $view->fetch($search_template);
        return $search_html;
    }
5. Использование шаблонов переданных напрямую из плагина

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

Снова всем знакомый массив шаблонов:

<?php
return array(
            'plugin' => array(
                //......
            ),
            'theme' => array(
                //......
            ),
            'custom' => array(
                'product.html' =>  'ТУТ САМ КОД ШАБЛОНА',
                'search.html' => 'ТУТ САМ КОД ШАБЛОНА',
                'advanced.css' =>'ТУТ САМ КОД СТИЛЕЙ ДИЗАЙНА',
              
            ),
        );
    }

Примерный код использования:

<?php
public function execute() {
        $plugin = wa('shop')->getPlugin('brands');
        // Получаем общий массив шаблонов, с динамически добавленными шаблонами CUSTOM, это придуманный метод
        $storefront_temlstes = $plugin ->getTempaltesByStorefront();
        // Передаем шаблоны в класс
        $templates = new waPluginTemplates($plugin,$storefront_temlstes);
        $view = wa()->getView(); 
        //.....
        $view->assign('search', $this->search);
        //.....
        // Получаем шаблон поиска из переданного в класс шаблона,
        // а если не найден шаблон по ключу то берем из плагина ...plugin/templates/frontend/search.html
        $search_template = $templates->getTemplate('search.html');
        // Получаем готовый код 
        $search_html = $view->fetch($search_template);
        return $search_html;
    }
6. Создание произвольных шаблонов в темах дизайна и их использование

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

Пример создания произвольного шаблона в теме дизайна:

<?php
public function execute() {
        $plugin = wa('shop')->getPlugin('brands');
        $templates = new waPluginTemplates($plugin);
        // Получаем какую-то сущность
        $entity = waRequest::post('entity');
        
        // Получаем название шаблона
        $template_name = waRequest::post('template_name');
        // Защищаемся от перезаписи основных шаблонов префиксом
        $filename = 'custom.'.strtolower($template_name).'.html';
        // Получаем контент нового файла по умолчанию из файла плагина
        $template_content = $templates->getPluginTemplateContent('entity.html');
        if(!$template_content && waRequest::issetPost('content')) {
            // Можно и произвольный код записать
            $template_content = waRequest::post('content');
        } else {
            $template_content = '';
        }
        // Получаем все темы для приложения
        $themes = waPluginTemplates::getThemes('shop');
        if(!empty($themes) && is_array($themes)) {
            $return = true;
            foreach ($themes as $theme) {
                if($theme && ($theme instanceof  waTheme)) {
                    // Пишем пишем произвольный шаблон в тему
                    if(!$templates->templateAddToTheme($theme, $filename, $template_content)) {
                        $return = false;
                    }
                }
            }
            if(!$return) {
                $this->setError('Не удалось создать файлы в темах дизайна');
            }
        }
        // Тут уже пишем саму сущность и цепляем название шаблона
        $entity['template'] = $filename;
    }

Получение произвольного шаблона отдельно не реализовано из-за этого немного пришлось скостылить в одном методе для возможности получения произвольного шаблона, конечно можно было сделать метод getThemeCUstomTemplate($filename)...

Код для получения шаблона:

<?php
public function execute() {
        $plugin = wa('shop')->getPlugin('brands');
        $templates = new waPluginTemplates($plugin);
        // Получаем какую-то сущность и из нее шаблон записанный ранее
        $filename = $this->entity['template'];
        $template = $templates->getTemplate($filename);
        
        $view = wa()->getVIew();
        $view->assign('entity',$this->entity);
        return $view->fetch($template);
    }


Документация класса

Класс принимает массив данных о шаблонах или сам забирает из спец метода плагина, есть три типа шаблонов

  • plugin - Шаблоны плагина
  • theme - Шаблоны темы + возможность добавления произвольных шаблонов в тему
  • custom - Кастомные шаблоны переданные напрямую в виде готовых шаблонов из плагина, можно использовать для

Первый тип (plugin) - шаблоны плагина, в массиве имеются ключи и их значения, сами значения:

  • path - путь к файлу шаблона или файлу css, js от корня плагина
  • description - необязательный параметр , нигде не используется, но можете сами выводить для пользователей в качестве описания шаблона

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

  • path - путь к файлу шаблона или файлу css, js от корня плагина в теме - theme_path/plugins/plugin_id/, все файлы плагина будут записываться именно в данную поддиректорию темы дизайна, т.е. в массиве указан файл 'path' => 'product.html', он будет записан в теме по адресу ...theme_path/plugins/plugin_id/product.html
  • 'description' - это описание файла для темы, показывается при редактировании в редакторе темы дизайна
  • 'content' - это сам контент файла, его стоит указывать только если вы хотите просто создать файл в теме, например для дополнительного css под дизайн,если вы хотите чтобы шаблон скопировался из шаблонов плагина, этот параметр должен отсутствовать.

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

Описание публичных методов
  • __construct(waPlugin $plugin, $templates = null, $templates_type = null) - Класс принимает объект плагина, массив шаблонов, тип шаблонов, также можно в самом плагине создать публичные методы getRegisterTemplates (должен возвращать массив шаблонов) и getTemplatesType (должен возвращать тип шаблонов, plugin или theme или custom) , тогда не придется каждый раз передавать шаблоны в конструктор.
  • public function setTemplatesType($type) - Устанавливает тип шаблонов.
  • public function getPluginId() - Возвращает строковый идентификатор плагина.
  • public function getApp() - Возвращает строковый идентификатор приложения.
  • public function getTemplate($key, $default_template_type = 'plugin') - Возвращает подготовленный шаблон по ключу для дальнейшей обработки смарти в методе fetch($templates->getTemplate('key')), если совсем не будет найден никакой шаблон вернет строку для смарти ('string: '), вторая переменная указывает дополнительный тип шаблонов, если шаблон не будет найден в теме дизайна, то будет искаться шаблон в типе указанном в данной переменной.
  • public function getTemplateUrl($key, $theme = null) - Возвращает URL шаблона(файла css , js ) от корня домена, принимает строковый идентификатор темы или объект темы для получения адреса файла по ключу из темы, иначе вернет URL файла из плагина, если файл с таким ключом указан в массиве данных шаблонов плагина, иначе вернет false.
  • public function themesTemplatesExists($themes = null) - Проверяет наличие шаблонов из данных массива шаблонов типа theme в переданных темах дизайна, принимает массив объектов тем дизайна.
  • public function themeTemplatesExists($theme = null) - Проверяет наличие шаблонов из данных массива шаблонов типа theme в переданной теме дизайна, принимает объект темы дизайна или ее строковый идентификатор.
  • public function themeTemplateExists($file_path, $theme = null) - Проверяет наличие файла шаблона плагина в директории плагина в теме дизайна, принимает объект темы дизайна или ее строковый идентификатор. Важно! Передается путь файла не от корня темы дизайна , а от корневой директории плагина в теме дизайна.
  • public function templateAddToTheme($theme, $filename = '' , $content = '', $description = '') - Добавление произвольного шаблона плагина в тему дизайна , Если вы хотите скопировать шаблон из плагина воспользуйтесь методом templateCopyToTheme($key).
  • public function templateRevertToTheme($theme, $key) - Перезаписывает файл шаблона плагина в теме дизайна принудительно(возвращает к исходному).
  • public function getThemePluginPath($path = '') - Подставляет в начало путь к папке плагина относительно корня темы, допустим нам надо сгенерировать ссылку на редактирование созданного файла плагина в теме дизайна чтобы получить такую ссылку : ...#/design/theme=default&file=/plugins/PLUGIN_ID/theme_product.html. У нас в массиве ведь хранится путь уже от корня директории плагина в теме ('/theme_product.html'), как раз данный метод решает эту проблему getThemePluginPath('/theme_product.html').
  • public function getPluginPath($path = '') - Возвращает абсолютный путь к файлу шаблонов плагина от корня сервера, $path - путь к файлу относительно корневой директории плагина, в массиве выше как раз указан такой путь 'path' => '/templates/frontend/product.html.
  • public function getThemeTemplatePath($path = '', $theme = null) - Возвращает абсолютный путь к файлам шаблонов темы дизайна от корня сервера, $path - путь к файлу относительно корневой директории плагина в теме дизайна, на примере выше это 'path' => '/theme_product.html'.
  • public function getTheme($theme = null) - Возвращает объект темы дизайна для текущей витрины, или переданной в параметре, $theme - объект waTheme или строковый идентификатор темы, если тема не передана, будет взята текущая тема если запрос из фронтенда.
  • public function setTheme($theme = null) - Устанавливает тему дизайна относительно которой будет работать класс.
  • public function getRegisteredTemplates($type = null) - Возвращает все зарегистрированные шаблоны, либо для плагина, для темы дизайна, кастомные или все вместе , если не передан тип шаблонов, $type string - тип шаблонов.
  • public function getPluginTemplateContent($key) - Возвращает код шаблона плагина по его ключу.
  • public static function getThemes($app_id = null) - Возвращает объекты тем для приложения.
  • public function templatesCopyToTheme($theme, $force = false) - Копирует и создает все шаблоны плагина в теме дизайна, подробнее в методе копирования отдельного шаблона.
  • public function templateCopyToTheme($theme, $key, $force = false) - Копирование шаблона в тему дизайна по ключу шаблона, если файл уже есть в теме дизайна, то он не будет перезаписан для принудительного пересоздания шаблона в теме. Если вы указали в массиве шаблонов темы дизайна одноименный ключ с файлом из массива шаблонов плагина, то можете вызвать метод копирования вашего шаблона плагина в тему дизайна.

Пример:


<?php

return array(        
   'plugin' => array(       
     'product.html' =>  array(    
            'path' => '/templates/frontend/product.html', 
           ),    
    ),       
   'theme' => array(    
        'product.html' => array(   
             'path' => '/theme_product.html',   
             'description' => 'Основной файл показа отзывов продукта с формой',  
         ),      
    ),   
);

В данном массиве у обоих типов есть шаблон с ключом 'product.html', вызвав метод с таким параметром templateCopyToTheme('default', 'product.html') в тему дефолт будет скопирован шаблон плагина /templates/frontend/product.html и запишется он с названием theme_product.html в специальную папку плагина в теме, адрес от корня темы будет таким ...theme_root_path/plugins/PLUGIN_ID/theme_product.html , для получения шаблона во фронтенде из темы надо воспользоваться методом getTemplate('product.html'), если в текущей теме будет файл ..../plugins/PLUGIN_ID/theme_product.html , то вернется он, иначе вернется файл из плагина /templates/frontend/product.html.




22 комментария

Добавить комментарий

Чтобы добавить комментарий, зарегистрируйтесь или войдите