• Блог
  • ZoomX. Плагины для Smarty

Smarty из коробки содержит несколько модификаторов и кастомных функций. Но, конечно, их не хватит на все случаи в жизни. Поэтому придётся расширять этот список. Делается это через плагины. С помощью них вы можете создать свои модификаторы, функции, блоки, функции для компилятора, фильтры (pre и post) и ресурсы. Подключаются они двумя способами:

  • автоматически (Smarty сам подключает нужный файл плагина по требованию);
  • динамически (вы сами должны подключить плагин или фильтр через специальные методы registerPlugin() и registerFilter()).

Файловые плагины

Мне кажется, это самый простой способ. Нужно создать файл в папке с плагинами Smarty. У ZoomX уже есть папка плагинов, в которой находятся Smarty плагины для MODX. Можно создавать файлы в ней. Или выбрать другое место. В этом случае необходимо указать этот путь в системной настройке zoomx_custom_plugin_dir. Но тут есть важная тонкость. Для того, чтобы Smarty его увидел, файл нужно назвать в соответствии с определёнными правилами, описанными в документации — сначала указывается тип плагина (function, modifier, block и т.д.), затем точка, дальше название, которое будет использоваться в шаблоне, и заканчивается всё расширением php. Т.е. для модификатора chunk это должно выглядеть так — modifier.chunk.php.

После того, как файл создали, в нём нужно объявить функцию, у которой название также должно формироваться по следующему правилу — smarty_тип плагина_название. Для нашего примера функция будет называться smarty_modifier_chunk. Теперь, если модификатор chunk будет указан в шаблоне, то Smarty автоматически его подгрузит.

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

В Fenom, который подключается pdoTools, тоже есть такой модификатор. Пишется он так {5 | resource : 'pagetitle'}. В итоге модификатор resource имеет неоднозначный синтаксис — в одном случае указывается поле текущего ресурса ({'pagetitle' | resource}), в другом id документа. Соответственно и параметры зависят от режима.

Мы пойдём другим путём — создадим отдельный модификатор. Назовём его коротко — field. За основу возьмём плагин модификатора resource. Файл назовём соответственно modifier.field.php. Сама функция получится такой:

<?php
/*
 * Smarty plugin
 * -------------------------------------------------------------
 * Файл:    modifier.field.php
 * Тип:     modifier
 * Имя:     field
 * Назначение:  Get a field of the specified resource.
 * -------------------------------------------------------------
 */
function smarty_modifier_field($id, $field)
{
    global $modx;

    $id = (int)$id;
    if ($id && $resource = $modx->getObject('modResource', ['id' => $id])) {
        return $resource->{$field};
    }

    return null;
}

Внимание!

Тут важно понимать, что разработчик берёт на себя ответственность за безопасность, так как в данном случае он может открыть данные закрытого ресурса. Поэтому, было бы правильнее делать проверку прав для текущего пользователя. Ну и для случая, если таких модификаторов на странице несколько, ресурс лучше закэшировать.

Ну и теперь вызываем его в шаблоне — получим поле pagetitle ресурса с id=5.

{5|field:"pagetitle"}
// Можно вернуть дефолтное значение
{5|field:"pagetitle"|default:"Ресурс не найден!"}

Динамические плагины

Тут уже никакой магии не будет — придётся создать плагин на событие OnModxInit (можно и на OnHandleRequest) и в нём добавить функцию Smarty плагина. Никакие правила именования тут уже не нужны. Для примера добавим тот же модификатор field.

<?php
// Плагин
// Событие OnModxInit

if ($modx->context->key !== 'mgr') {
    function fieldModifier($id, $field)
    {
        global $modx;
    
        $id = (int)$id;
        if ($id && $resource = $modx->getObject('modResource', ['id' => $id])) {
            return $resource->{$field};
        }
    
        return null;
    }

    // Регистрируем функцию модификатора
    $smarty = parserx();
    $smarty->registerPlugin("modifier","field", "fieldModifier");
}

Таким же образом можно регистрировать функции, блоки и т.д.

Заключение

Лично мне удобнее первый вариант — раз мы отказались от элементов в БД, то зачем опять начинать. Вся разработка в IDE редакторе. В PhpStorm есть полноценная поддержка Smarty. Поэтому кодить достаточно приятно и удобно.

1   562

Комментарии ()

  1. Семён 13 января 2021, 15:08 # 0
    Сергей, приветствую!
    Возник вопрос по роутеру ZoomX
    Допустим есть такой URL mysite.ru/catalog/segment
    Segment может быть как и категорией в каталоге, так и товаром
    Вопрос как разрулить и назначить нужные шаблоны по условию?
    Я пробую так:
    $router->get(
            'catalog/{segment}',
            function () use ($modx) {
              if ($modx->resource->template !== 3) {
                return viewx('category.tpl');
              }
              return viewx('product.tpl');
            }
    );
    
    Думал так прокатит, но MODX в этот момент не имеет доступа к текущему ресурсу и определить по какому либо признаку ресурс не получается, чтобы назначить ему нужный шаблон.
    1. Сергей Шлоков 13 января 2021, 16:50 # 0
      Тогда нужно писать плагин на событие OnLoadWebDocument (когда ресурс будет уже подгружен) и в нём переопределять шаблон для категорий. А в роуте прописывать только шаблон для продуктов («return viewx('product.tpl');»).
      // Плагин
      if ($resource->template !== 3) {
          parserx()->setTpl('category.tpl');
      } 
      
      Или вообще всю логику в плагин перенести, а в роуте возвращать пустую строку.
      1. Сергей Шлоков 15 января 2021, 09:41 # 0
        Получилось?
        1. Семён 15 января 2021, 10:03 # 0
          Я попробовал с плагином, да, всё работает, но тут другая проблема: так у меня логика роутов раскидана получается, часть в routes.php, часть в плагине. Тут либо всё перенесу в плагин в конечном итоге либо что-то другое придумаю. Просто уже часть шаблонов проекта сделал на smarty, и тут понял, что FastRoute-ом не учесть всех вариаций адресов, которые могут получиться в реальности, а зная, контент-менеджеров, они точно наворотят что-нибудь.
          Я пока придумал такой велосипед, он как бы возвращает привязку к шаблонам в адимнке и при этом оставляет возможность пользоваться Smarty. Так при любых URL за ресурсом сохранится его шаблон.
          $router->get(
                  '{segment:.+}',
                  function ($segment) use ($modx) {
                    $templates = [
                            1 => 'index.tpl',
                            2 => 'category.tpl',
                            3 => 'product.tpl',
                            4 => 'contacts.tpl',
                            7 => 'cart.tpl',
                            8 => 'order.tpl',
                    ];
                    $resourceId = $modx->aliasMap[$segment];
                    $resource = $modx->getObject('modResource', $resourceId);
                    if (is_null($resource)) {
                      return viewx('404.tpl');
                    }
                    return viewx($templates[$resource->get('template')]);
                  }
          );
          
          1. Сергей Шлоков 15 января 2021, 10:32 # 0
            Тоже вариант. Только документ можно получить проще
            $resource = $modx->request->getResource('alias', $modx->resourceIdentifier);
            
            В этом случае будет и проверка прав, и запрос документа из кэша (если есть).
            1. Семён 15 января 2021, 10:39 # 0
              О, вот это полезно! Искал разные способы получения, но такой не встречал. Спасибо
            2. Сергей Шлоков 16 января 2021, 20:33 # 0
              Есть ещё один вариант — использовать RESTful концепцию. Т.е. «catalogs/{alias}», «products/{alias}» и т.д. Это более современный подход.
        2. Семён 13 января 2021, 16:18 # 0
          И ещё один вопрос:
          Можно ли отключить роутинг и при этом дальше использовать smarty?
          Где и как в таком случае нужно указывать привязку шаблонов?
          1. Сергей Шлоков 13 января 2021, 16:58 # 0
            Неа. Тут логика такая — если используешь роуты, то значит включаешь режим фреймворка. Т.е. если для какого=то ресурса роут не определён, то он работает в режиме MODX. Сделано для беспроблемной совместимости. Смарти работает только в режиме фреймворка. Как вариант, попробую подумать на тему отдельного использования в рамках концепции дополнения.

            Тут видишь, как начинаешь использовать, сразу возникает много вопросов и интересных предложений. А без использования пока всё в теории. Ребята вроде заинтересовались, но пробовать как-то не торопятся. И у меня планы по переделке этого сайта пока тормозятся. Хотел миникурс на своем примере сделать, но нет времени. Пока присматриваю шаблон для блога.
          2. Семён 16 февраля 2021, 17:08 # 0
            Сергей, приветствую!
            Есть вопрос по ZoomX — как из роутера можно дать команду передать поиск адреса модексу?
            Я по прежнему использую свой велосипед
            $router->get(
                    '{segment:.+}',
                    function ($segment) use ($modx) {
                      $templates = [
                              0 => null,
                              1 => 'index.tpl',
                              2 => 'category.tpl',
                              3 => 'product.tpl',
                              4 => 'contacts.tpl',
                              7 => 'cart.tpl',
                              8 => 'order.tpl',
                              9 => 'test.tpl',
                              10 => 'delivery.tpl',
                              11 => 'how_make_order.tpl'
                      ];
                      $resource = $modx->request->getResource('alias', $segment);
                      if (!$resource) {
                        abortx(404, 'Страница не найдена');
                      }
                      return viewx($templates[$resource->get('template')]);
                    }
            );
            
            Но проблема — не знаю как вывести в таком случае ресурсы без шаблонов и с другим типом содержимого.
            У меня такими являются robots.txt и sitemap.xml — они генерятся с динамикой в содержании, но как их отдать в браузер не пойму.
            Есть какой-то способ прямо в коде роутера сказать — Отдай это модексу, он сам разберётся.
            1. Сергей Шлоков 17 февраля 2021, 22:15 # 0
              Привет, Семён!
              Да уж, велосипед знатный ) Ребята из мира фреймворков много «добрых» слов бы сказали про такую маршрутизацию.
              Как я уже писал, флагом запуска шаблонизатора Smarty является наличие роута для адреса запроса. Есть роут — работает функционал ZoomX, нет — работает MODX. Признак выставляется в свойстве Zoomx\Request::hasRoute.
              В твоём случае роут всегда срабатывает. Поэтому придётся этот флаг переопределять внутри роута
              zoomx()->getRequest()->hasRoute(false);
              // или так
              $modx->request->hasRoute(false);
              
              В этом случае должен сработать MODX.

              П.С. Сорян за поздний ответ. Письма о комменте почему-то не было:(
              1. Семён 18 февраля 2021, 16:49 # 0
                Огромное спасибо, Сергей!
                То, что нужно:v
                1. Сергей Шлоков 18 февраля 2021, 18:30 # 0
                  На здоровье!

                  П.С. Письма так и не приходят.:(
              2. Сергей Шлоков 17 февраля 2021, 22:25 # 0
                Ещё вариант. robots.txt — это же статический файл. Он вообще не должен отдаваться MODX, если сервер правильно настроен. А для sitemap.xml создать отдельный роут и шаблон sitemap.tpl с генерацией карты. Главное в роуте указать правильный тип контента
                $router->get('sitemap.xml', function() {
                	header('Content-Type: application/xml');
                	return viewx('sitemap.tpl');
                }
                

              Вы должны авторизоваться, чтобы оставлять комментарии.

              Выделите опечатку и нажмите Ctrl + Enter, чтобы отправить сообщение об ошибке.