Блог Веб-разработчика.

Встала задача разработать плагин (для joomla 3.3), регистрирующий все новые статьи, создаваемые в админке системы, в xml карте сайта. На сайте включен режим генерации sef-ссылок (search engine friendly url) основанный на sef-плагине, идущем в стандартной сборке joomla. Поэтому и в карте сайта необходимо записывать адреса новых статей именно в человеко-понятном виде. Но, оказывается в административной панели сайта сгенерировать sef-ссылку на материал стандартными средствами - невозможно. Моему удивлению по этому поводу, мягко говоря, не было предела. Но, как и все в этой жизни, оно постепенно рассеялось и начались сосредоточенные поиски выхода. И оказывается выход есть.

Собственно все началось с попытки применить привычную конструкцию вида:

$nonSefUrl = "index.php?option=com_content&view=article&id=3:first-video-dance&catid=13&Itemid=103";
JRoute::_($nonSefUrl);

где в качестве параметра передается длинный динамический адрес, сформированный с перечислением всех параметров запроса, получить который проблем не составляет. Этот подход не сработал. Беглый поиск по документации joomla, а затем по просторам интернета, собственно и вызвали то самое нескончаемое удивление, сопровождаемое тихими но грубоватыми эмоциями.

Стало очевидно, что нужно каким-то образом в администривной части системы, в коде плагина запросить приложение фронтенда (‘Site’). Решение “в лоб”:

$app = JFactory::getApplication("site");

тоже не сработало, так как метод getApplication возвращает ссылку на уже созданный экземпляр приложения, в контексте которого выполняется запрос. Так как код плагина выполняется в контексте административной части системы, то метод и возвращает объект приложения ‘administrator’.

Попытка напрямую изменить ссылку, содержащуюся в свойстве “JFactory::$application”, с объекта “JApplicationAdministrator” на “JApplicationSite” - привела к полному разрушению работы административной панели сайта, которое не удавалось восстановить даже если далее по коду этому свойству снова вернуть ссылку на объект приложения “Administrator”.

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

$app = JApplicationCms::getInstance("site");
$router = $app::getRouter("site");
$router->build($nonSefUrl)

То есть обращаясь напрямую к классу “JApplicationCms”, просим его выдать экземпляр объекта “JApplicationSite”. Далее получаем доступ к роутеру фронтенда, и уже методу “JRouterSite::build()” передаем “человеко-НЕпонятный” адрес материала. Этот способ и несколько других похожих, действительно на выходе дают sef-ссылку, однако сгенерированную с ошибкой.

1) index.php?option=com_content&view=article&id=3:first-video-dance&catid=13&Itemid=103
2) /video/13-video/dances/3-first-video-dance
3) /video/13-dances/3-first-video-dance

На основе адреса в первой строке, получается адрес во второй, а должен быть как в третьей. При этом к каждой строке дописывает слово “administrator”, его опустил, так как эта проблема решается путем простого вырезания подстроки. А вот расстановку псевдонимов категорий вырезанием лечить побоялся, так как не уверен что для всех случаев (различное количество вложенности категорий материалов) это стандартная ситуация. Посчитав такой подход контрпродуктивным, решил углубиться в код системы joomla и найти причину, по которой происходит искажение человеко-понятной ссылки на статью.

Причина ошибки.

Путь поиска был достаточно тернистым и не простым, поэтому излагать его конкретные шаги не вижу смысла. Сам поиск привел меня к роутеру компонента com_content, именно в нем, при работе в контексте приложения административной панели, возникала ошибка, приводящая к нарушению генерации ссылок. А именно: в файле “components/com_content/router.php” в теле метода “ContentRouter::build()” (строка 35 файла) идет вызов статического метода getApplication():

$app = JFactory::getApplication();

Разработчики видимо совсем не предполагали (а зря!), что метод “ContentRouter::build()” будет вызываться в контексте административной части системы, поэтому поставили вызов который во фронтенде гарантированно вернет ссылку на объект “JApplicationSite”, но в административной части эта строка возвращает объект “JApplicationAdministrator”, и именно этот момент ломает логику работы функции построения человеко-понятных ссылок. Если же вместо этой строки указать:

$app = JApplicationCms::getInstance("site");

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

Решение проблемы.

Можно действовать “в лоб”, изменив тело метода “ContentRouter::build()” и сохранив файл. Однако этот простой способ не оправдает себя при обновлении системы до более новых версий. Конечно это происходит не каждый день, но все же придется в голове держать еще один пунктик и следить за ним - что очень не удобно и рисковано. Поэтому пойдем более совершенным путем.

Существует возможность с помощью плагинов переопределять практически любые классы системы joomla и ее расширений (как стандартных, так и сторонних). И мое решение - пойти именно по этому пути. Мы создадим плагин, с помощью которого переопределим класс “ContentRouter”, заменив в его определении всего одну строчку (строка 35). Для этих целей создадим плагин типа “system”, и в коде плагина, без определения класса, подключим файл с переписанным определением класса “ContentRouter”, который мы предварительно разместим в файловой системе этого плагина.

Структура файлов этого плагина может выглядеть примерно так:

где “contentrouteroverride/contentrouter/router.php” - это скопированный стандартный файл в определением класса “ContentRouter”, в котором стандартная строка 35, заменена на:

$app = JApplicationCms::getInstance("site");

Код самого плагина можно сформировать следующим образом:

<?php
defined("_JEXEC") or die("Access denied.");
defined("DS") or define(DS,DIRECTORY_SEPARATOR);
$application = JFactory::getApplication();
if("com_content" == JRequest::getCMD("option") && $application->isAdmin()) {
    require_once dirname(__FILE__).DS."contentrouter".DS."router.php";
}

Обратите внимание, что в нем не определен, как это обычно бывает, функциональный класс “plgSystemContentRouterOverride”, а задан код, который исполняется тут же при загрузке этого файла. В начале, как обычно, защита от прямого обращения, также если не определена константа определяющая вид слеша в путях до файлов (зависит от типа операционной системы на сервере), то она тоже формируется. Далее запрашивается ссылка на объект приложения, в контексте которого запущено выполнение запроса, и в блоке проверяется значение параметра “option” (содержит тип компонента, должен быть равен “com_content”) и проверяется исполнение кода в контексте “Administrator” - если оба условия выполняются, то загружается файл с переписанным классом “ContentRouter”, определение которого отныне доступно в памяти.

Сам экземпляр класса “ContentRouter”  в работе компонента создается следующим образом: в файле “/libraries/cms/router/site.php” в методе “JRouterSite::getComponentRouter()” проверяется существование класса “ContentRouter”, и только если его нет - происходит загрузка стандартного роутера, который идет в составе компонента “com_content”. Но если код исполняется в окружении административной части системы и в контексте “com_content”, то к моменту обращения к методу “JRouterSite::getComponentRouter()”, в памяти уже будет существовать определение класса “ContentRouter”, загруженное из нашего плагина. Таким образом загрузка стандартного роутера компонента материалов будет заблокирована, а экземпляр роутера будет создан на основе переопределенного класса из “contentrouteroverride/contentrouter/router.php”.

Теперь мы можем в административной части системы, силами плагинов относящихся к типу “content”, создавать корректные sef-ссылки на материалы лицевой панели сайта и обрабатывать их по своему усмотрению. Чего очень и хотелось в самом начале.

Чуть ниже предлагаю ссылку для скачивания плагина “contentrouteroverride”. Если захотите его установить, то распакуйте архив в соответствующую папку внутри “system” и через менеджер расширений “поиск” - установите и не забудьте включить в менеджере плагинов. Если обнаружите какие-то проблемы в использовании плагина - буду очень признателен за сообщение об этом.

Ссылки для скачивания.

Здесь Вы можете скачать сопроводительные материалы к статье:

contentrouteroverride.zip (6Kb)

* * * * * * * * * * * *

Если информация этой статьи будет интересна и полезна Вашему кругу друзей и знакомых, то Вы можете опубликовать ссылку - тогда им проще будет ее найти. Они Вам будут благодарны:).

Комментарии к статье:


Всеволод Чупрыгин © webengineer.pro 2014. Все права защищены.
Копирование материалов сайта разрешено только с указанием имени автора (Всеволод Чупрыгин) и прямой индексируемой ссылки на источник на сайте www.WebEngineer.pro.
ИП Чупрыгин Всеволод Андреевич, ИНН: 333410747832, ОГРН: 311333426300044
http://vkontakte.ru/chuprygin_va, Google +

.
Проверить аттестат
Мы принимаем Webmoney Мы принимаем практически все платежи через Robokassa Мы принимаем Яндекс.Деньги Мы принимаем платежи через QIWI. Мы принимаем платежи через привязанные к QIWI карты VISA/Mastercard.