Расширение функциональности модулей: различия между версиями
Olga (обсуждение | вклад) |
Olga (обсуждение | вклад) |
||
Строка 126: | Строка 126: | ||
</pre> | </pre> | ||
− | В этот файл можно добавлять свои методы, которые могут быть использованы в шаблоне при помощи вызова | + | В этот файл можно добавлять свои методы, которые могут быть использованы в шаблоне при помощи вызова news::имя_метода. |
− | + | === Разрешения метода === | |
+ | === Создание кастомного метода === | ||
+ | |||
+ | Перенесём наш метод newslist() из кастомных макросов в этот новый класс. Не буду приводить тут его код, он в точности тот же. Если набрать в браузере | ||
== Собственный класс сайта == | == Собственный класс сайта == |
Версия 13:40, 5 ноября 2020
Некоторые методы UMI CMS оставляют желать лучшего. Например, метод lastlist модуля news не возвращает значение поля anons новости, который может понадобиться в списке новостей. Можно ли с этим что-то сделать?
Кастомные методы стандартных модулей
Открываем директорию /classes/components/news и видим там файлы macros.php и customMacros.php. В файле macros.php находим функцию lastlist(), копируем её и целиком вставляем в customMacros.php после строки public $module;.
Находим там кусок кода:
$line_arr = []; $line_arr['attribute:id'] = $element_id; $line_arr['node:name'] = $element->getName(); $line_arr['attribute:link'] = $umiLinksHelper->getLinkByParts($element); $line_arr['xlink:href'] = 'upage://' . $element_id; $line_arr['void:header'] = $lines_arr['name'] = $element->getName();
Там и правда не добавляется анонс. Поэтому вставляем одну строчку:
$line_arr['attribute:anons'] = $element->anons;
Все эти node: и attribute: предназначены для других шаблонизаторов, но лучше их сохранить для общности.
Открываем главную страницу нашего сайта - и видим ошибку "Ошибка (Error): Class 'Service' not found"! Правда, с этим понятно, что делать, надо в начале файла customMacros.php вставить "use UmiCms\Service;", вот так:
<?php use UmiCms\Service; /** Класс пользовательских макросов */ class NewsCustomMacros { /** @var news $module */ public $module; ... }
Больше ошибок не возникнет, и теперь при вызове метода lastlist() получим также и элемент массива 'anons'. Если изменим в файле content/home/news.phtml код цикла вот так:
<?php if (!empty($newsList['items'])): foreach ($newsList['items'] as $item): ?> <li class="list-group-item"> <em><?= date("d.m.Y", $item['publish_time']) ?></em> <a href="<?= $item['link'] ?>"><h3 umi:element-id="<?= $item['id'] ?>" umi:field-name="name" umi:empty="<?= $this->translate('empty_page_name') ?>"><?= $item['name'] ?></h3></a> <div umi:element-id="<?= $item['id'] ?>" umi:field-name="anons" umi:empty="<?= $this->translate('empty_news_anons') ?>"><?= $item['anons'] ?></div> </li> <?php endforeach; endif; ?>
то увидим также и текст анонсов. Можно просто набрать в браузере http://umi.example.com/udata://news/lastlist/7/.json и увидеть изменения.
Что будет, если внести исправления сразу в файл macros.php? Очевидно, что все исправления будут потеряны при очередном обновлении системы.
Сразу замечу, что переопределять стандартные методы - ОЧЕНЬ ДУРНАЯ ПРАКТИКА. Если с сайтом будет работать другой программист, ему придется потратить кучу сил и времени на поиск ошибки, ведь он будет уверен, что вызывается стандартный метод. Поэтому ВСЕГДА, скопировав код стандартного метода, ПЕРЕИМЕНОВЫВАЙТЕ ЕГО! Пусть этот метод будет целиком и полностью кастомным, и это будет видно уже при вызове. И тогда возникает следующая задача - настройка разрешений.
Настройка разрешений для выполнения метода
Да, переопределение метода - нехороший поступок, но когда мы так делаем, автоматически избегаем множества проблем. Дело в том, что в UMI CMS есть сложная система разрешений, для каждого модуля существует собственный набор, посмотреть его можно в папке модуля в файле permissions.php.
Когда мы создаем метод с новым именем, то он не будет выполняться, т к отсутствует в файле permissions.php. Но это можно исправить, создав в той же папке модуля файл permissions.custom.php.
Предположим, по аналогии с методом lastlist() мы создали метод newslist(), куда внесли все необходимые нам изменения. Если посмотреть содержимое файла permissions.php, увидим, что это массив:
/** Группы прав на функционал модуля */ $permissions = [ /** Права на просмотр новостей */ 'view' => [ 'lastlist', 'listlents', 'rubric', /** ... и так далее, не буду приводить его целиком */ ] ];
По логике, нам надо добавить один элемент в массив $permissions['view'], поэтому в файле permissions.custom.php пишем:
<?php $permissions['view'][] = 'newslist';
Интересный факт: даже если название метода содержит буквы с разной капитализацией, в этом массиве все нужно писать маленькими буквами. Не спрашивайте, почему.
Пока всё просто? Это только кажется. В каждом модуле структура и ключи массива разрешений разные, и в них нет никакой системы. Хорошо, если метод должен быть общедоступным, как правило, можно угадать, в какой именно массив его добавлять. Сложнее, если доступ можно давать только авторизоваанным пользователям, и то не всем. Так что - удачи!
Использование кастомного метода в шаблона
Теперь меняем в файле content/home/news.phtml имя метода:
$newsList = $this->macros('news', 'newslist', [$newsPageId]);
и увидим то, что планировали. И даже есть вызовем этот метод просто в строке URL, на пример, так:
http://umi.example.com/udata://news/newslist/7/.json
(где 7 - это id ленты новостей, на других сайтах может быть другой), то получим json-массив с элементом anons.
Кастомные методы внутри шаблона
Описанный выше способ хорош, но добавленный код находится вне нашего шаблона default. А нам бы хотелось, во-первых, при установке системы на другом хостинге просто закинуть туда готовый шаблон и получить готовый сайт, больше ничего не трогая, особенно системные директории. И во-вторых, хотелось бы для разных сайтов на разных щаблонах использовать разные кастомные методы.
И что приятно, начиная с версии 2.8.5 системы это можно сделать! Для этого создаем внутри нашего шаблона папку classes, внутри неё папку modules, в ней папку с именем модуля, например, news, а там - файл class.php, где создаем отдельный класс. Имя класса должно быть 'имямодуля_custom', а имена методов не должны совпадать с именами стандартных методов модуля. Таким образом, получим файл classes/modules/news/class.php с кодом:
<?php /** Класс пользовательских методов */ class news_custom extends def_module { } ?>
В этот файл можно добавлять свои методы, которые могут быть использованы в шаблоне при помощи вызова news::имя_метода.
Разрешения метода
Создание кастомного метода
Перенесём наш метод newslist() из кастомных макросов в этот новый класс. Не буду приводить тут его код, он в точности тот же. Если набрать в браузере
Собственный класс сайта
До сих пор мы рассматривали очень правильный с точки зрения MVC путь создания своих методов. Однако есть более простой и быстрый, но немного менее "канонический" способ, доступный только для PHP-шаблонизатора. Это создание класса расширения шаблонизатора.
Для начала создадим в директории php папку library. В данном случае название может быть любым. А в ней файл PhpExtension.php с вот таким кодом:
<?php /** Сервисные классы, которые понадобятся для выполнения методов */ use UmiCms\Classes\System\Utils\Captcha\Strategies\GoogleRecaptcha; use UmiCms\Service; /** Расширение php шаблонизатора для шаблона default */ class PhpExtension extends ViewPhpExtension { }
Далее, открываем файл config.ini, который перед этим создали в папке шаблона, там уже есть настройки для "причёсывания" отдаваемых методами массивов, и добавляем туда 2 строчки:
[php-templater] extensions[] = "/templates/default/php/library/PhpExtension"
Важно! Имя класса в принципе может быть любым, но имя файла, содержащего этот класс, а также путь к файлу, прописанный в конфиге, должны в точности его повторять, вплоть до капитализации.
Поскольку extensions - это массив, понятно, что таких классов расширений можно добавить сколько угодно. Но нам хватит и одного.
Все публичные методы этого класса теперь будут доступны в шаблоне при помощи волшебной переменной $this. Но лучше это посмотреть на примере.
Добавление глобальных переменных
Ранее мы сделали одну не очень красивую вещь - добавили полученные прямо в шаблоне настройки к массиву $variables, и вынуждены были и дальше передавать их в методе render(), чтобы не потерялись. Значительно лучше было бы создать какие-то глобальные переменные, которые были бы доступны в любом фрагменте нашего шаблона.
Итак, добавляем в наш класс расширения следующие функции:
<?php /** Сервисные классы, которые понадобятся для выполнения методов */ use UmiCms\Classes\System\Utils\Captcha\Strategies\GoogleRecaptcha; use UmiCms\Service; /** Расширение php шаблонизатора для шаблона default */ class PhpExtension extends ViewPhpExtension { /** * Инициализирует общие переменные для шаблонов. * @param array $variables глобальные переменные запроса */ public function initializeCommonVariables($variables) { $templateEngine = $this->getTemplateEngine(); $templateEngine->setCommonVar('domain', $variables['domain']); $templateEngine->setCommonVar('lang', $variables['lang']); $templateEngine->setCommonVar('settings', $this->requestSettingsContainer($variables)); } /** * Запрашивает актуальный объект настроек и возвращает его * @param array $variables глобальные переменные запроса * @return bool|iUmiObject */ public function requestSettingsContainer($variables) { $set = new selector('objects'); $set->types('object-type')->name('umiSettings', 'settings'); $set->where('domain_id')->equals($variables['domain-id']); $set->where('lang_id')->equals($variables['lang-id']); $set->limit(0, 1); $settings = $set->result(); if (is_array($settings)) return $settings[0]; return false; } }
Здесь использовали методы базового класса ViewPhpExtension для работы с глобальными переменными. Вставим вызов метода initializeCommonVariables() в файл common.phtml:
<?php $this->initializeCommonVariables($variables); ?> <!DOCTYPE html> <html> <?= $this->render($variables, 'layout/head') ?> <body> <?= $this->render($variables, 'layout/header') ?> <?= $this->render($variables, 'layout/main') ?> <?= $this->render($variables, 'layout/footer') ?> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script> </body> </html>
Теперь для получения значения этих переменных в любом фрагменте шаблона достаточно написать $this->getCommonVar('имя переменной'). Например, файл footer.phtml приобретет следующий вид:
<?php $settings = $this->getCommonVar('settings'); ?> <footer> <div class="container"> <span umi:object-id="<?= $settings->id ?>" umi:field-name="copyright"><?= $settings->copyright ?></span> <?= date("Y") ?> </div> </footer>
Аналогичным образом меняем остальные фрагменты шаблона, где используются настройки. Теперь их не надо добавлять к массиву $variables.
Получение блоков для главной страницы
При помощи класса PhpExtension можем улучшить код ещё одного фрагмента - content/home/index.phtml. Добавляем в класс метод getHomePageBlocks():
/** * Возвращает информацию для главной страницы по блокам * @param array $variables глобальные переменные запроса * @return bool|array */ public function getHomePageBlocks($variables) { $blocks = []; $hierarchy = umiHierarchy::getInstance(); $children = $hierarchy->getChildrenTree($variables['pageId'], false, false, 1); foreach ($children as $id => $val) { $element = $hierarchy->getElement($id); if ($element instanceof umiHierarchyElement) { $blocks[$id]['id'] = $element->id; $blocks[$id]['name'] = $element->name; $blocks[$id]['h1'] = $element->h1; $blocks[$id]['altName'] = $element->altName; $blocks[$id]['content'] = $element->content; } } return $blocks; }
Следовало бы ещё cmsController::getInstance()->getResourcesDirectory(); убрать в PhpExtension, для красоты, вот так:
/** * Возвращает путь до директории шаблона * @return bool|string */ public function getTemplateDirectory() { return cmsController::getInstance()->getResourcesDirectory(); }
В результате код content/home/index.phtml станет вполне приличным:
<?php /** * Главная страница: * - Блок с вступительным текстом * - Блок "Новости" * - Блок "Контакты" * Для удаления блока достаточно снять * у соответствующего раздела признак "Отображать в меню" * Для добавления блока надо добавить блок в структуру * раздела и создать в папке content/home файл шаблона * с именем как в поле "Псевдостатический адрес" * */ $blocks = $this->getHomePageBlocks($variables); $resourcesDir = $this->getTemplateDirectory(); foreach ($blocks as $page) { if (isset($page['altName'])) { if ($resourcesDir && file_exists($resourcesDir . 'php/content/home/' . $page['altName'] . '.phtml')) echo $this->render($page, 'content/home/' . $page['altName']); else echo '<div class="alert alert-danger">Отсутствует шаблон: ', $resourcesDir, 'content/home/', $page['altName'], '.phtml</div>'; } } ?>