Создание плагина для Joomla

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

Joomla

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

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

Как подключаются плагины

В статье "Общая информация о плагинах" мы говорили о том, что для вызова плагина сначала используется JPluginHelper::importPlugin() для включения его класса и методов в рабочую память. Если мы присмотримся к тому, как работает этот метод, мы увидим, что код (который выполняет подключение) расположен в приватном методе import() класса JPluginHelper (libraries/joomla/plugin/helper.php):

if (!isset($paths[$path]))
{
    require_once $path;
}
$paths[$path] = true;

Первая строка проверяет, был ли добавлен определенный плагин. Переменная $paths является ассоциативным массивом, содержащим все плагины, которые уже были подключены. Ключом является полный путь до файла плагина, а значением является имя класса. Используя PHP функцию isset() мы проверяем, есть ли такой элемент в массиве. Если нет, то с помощью require_once подключаем этот файл. И наконец, значение этого элемента устанавливается в логическое true, что гарантирует установку этого элемента в массиве. Поэтому require_once уже не будет вызвано для того же самого файла.

Здесь необходимо понять две важные вещи:

  • как уже обсуждалось ранее, обычно в плагине объявляется класс, поэтому не происходит вызова кода. Единственное что происходит, класс и его методы загружаются в рабочую память для того, чтобы потом его методы могли быть вызваны в цикле. В данном случае, в результате выполнения метода JPluginHelper::importPlugin() не происходит исполнения кода.
  • ничто в Joomla не заставляет плагин быть объявлением класса. Плагин может быть простым PHP скриптом - таким, который бы исполнялся сразу при его подключении. Если мы сделаем такой плагин, то он будет вызван незамедлительно после вызова метода JPluginHelper::importPlugin(). Это предоставляет механизм загрузки PHP скриптов при подключении плагинов.

Как загружаются классы Joomla

Теперь нам необходимо понять важную вещь о том, как в Joomla происходит загрузка базовых классов в рабочую память. Если мы взглянем на функцию jimport, которая обычно используется для загрузки базовых классов Joomla, мы увидим, что это просто функция в файле libraries/loader.php. Обратите ваше внимание на то, что это независимая функция, а не метод класса. Именно поэтому она вызывается только с именем функции и без имени класса. Вот код этой функции:

function jimport($path)
{
    return JLoader::import($path);
}

Он просто вызывает метод JLoader::import(). Первые строки метода JLoader::import() следующие:

// Only import the library if not already attempted.
if (!isset(self::$imported[$key]))

Это проверка - подключен ли класс или нет. Переменная self::$imported является статическим ассоциативным массивом с ключом (переменная $key), равным аргументу, переданному в JImport (например, "joomla.plugin.plugin"), и значением, равным логическому true или false. Когда класс подключен, элемент добавляется в массив и значение устанавливается в true, если подключение было выполнено успешно, и в false, если неуспешно. Поэтому, как только класс был подключен, Joomla не будет пытаться подключить его ещё раз.

Методы JLoader::load(), JLoader::register() также проверяют до загрузки класса, не был ли класс загружен ранее. И здесь мы делаем важный вывод: если класс уже существует (загружен в рабочую память), мы пропускаем загрузку этого класса. Метод просто возвращает значение true и выходит. Ни один из методов Joomla не загрузит класс повторно.

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

Системные плагины очень рано загружаются в рабочую память в цикле исполнения Joomla, раньше большинства (но не всех) базовых классов Joomla. Это поможет нам достичь желаемого результата.

Пример: переопределение класса JTableNested

Давайте сделаем быстрый пример для иллюстрации вышеописанного. Мы переопределим базовый класс JTableNested. Этот класс является родительским классом для всех классов вложенных таблиц в Joomla (например, JTableCategory для таблицы #__categories). В этом примере мы продемонстрируем, как переопределить класс, но оставим читателю возможность придумать, какой именно код и поведение хотелось бы изменить.

Вот шаги, которые необходимо предпринять:

  1. Создайте новую папку plugins/system/myclasses в директории установки Joomla и скопируйте туда файл libraries/joomla/database/tablenested.php. В итоге вы получите файл plugins/system/myclasses/tablenested.php (не забудьте добавить файл index.html для всех создаваемых папок).
  2. Отредактируйте новый файл и замените существующий метод rebuild() следующим кодом:

    public function rebuild($parentId = null, $leftId = 0, $level = 0, $path = '')
    {
        exit('Из файла myclasses/tabelnested.php');
    }

    Этот код просто докажет, что был загружен наш переопределенный класс, вместо базового класса. Когда мы нажмем "Перестроить" (например, в "Менеджере категорий: Материалы"), программа должна будет сделать выход с сообщением "Из файла myclasses/tabelnested.php".

  3. Теперь мы должны добавить плагин для загрузки нашего класса вместо базового класса. Мы назовем плагин "myclasses". Для этого, создайте новый файл с именем myclasses.php в папке plugins/system/myclasses.
  4. В новый файл (plugins/system/myclasses/myclasses.php) добавьте следующий код:

    <?php
    /**
     * Демонстрация плагина для замены базового класса.
     * Он исполняется перед первым импортом системы (перед
     * событием onBeforeInitialise).
     */
    
    // Запрет прямого доступа.
    defined('_JEXEC') or die;
    
    // Заменяем базовый класс JTableNested переопределенной версией.
    include_once JPATH_ROOT.'/plugins/system/myclasses/tablenested.php';
    

    Обратите внимание, что это код не объявляет класс. Это просто скрипт, а значит он будет исполнен во время подключения системных плагинов, перед первым системным событием. Этот код просто включает наш новый файл tablenested.php.

  5. Создайте XML-файл манифеста для этого плагина (plugins/system/myclasses/myclasses.xml) со следующим кодом:

    <?xml version="1.0" encoding="utf-8"?>
    <extension version="2.5" type="plugin" group="system">
        <name>plg_system_myclasses</name>
        <author>Mark Dexter and Louis Landry</author>
        <creationDate>November 2012</creationDate>
        <copyright>Copyright (C) 2012 Mark Dexter and Louis Landry.</copyright>
        <license>GPL2</license>
        <authorEmail>admin [at] joomla.org</authorEmail>
        <authorUrl>www.joomla.org</authorUrl>
        <version>1.0.0</version>
        <description>Демонстрация плагина MyClasses</description>
        <files>
            <filename plugin="myclasses">myclasses.php</filename>
            <filename>index.html</filename>
        </files>
        <config>
        </config>
    </extension>

  6. Зайдите в панель управления Joomla, выберите "Менеджер расширений", выполните "Поиск" и установите плагин. Не забудьте включить плагин в "Менеджере плагинов".
  7. Зайдите в "Материалы" -> "Менеджер категорий" и кликните на "Перестроить". Joomla должна остановиться и вы должны увидеть сообщение "Из файла myclasses/tabelnested.php". Это покажет нам, что мы успешно переопределили базовый класс.

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

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

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

echo '<pre>';
print_r(JLoader::getClassList());
echo '</pre>';
die();

Удачи в разработке!

Оригинальная статья: Mark Dexter and Louis Landry
Dmitry Rekun
Работаю в банковской сфере, а с веб-разработкой (непосредственно с Joomla) столкнулся в 2007 году. Теперь это моё хобби и время от времени вторая работа. Какое-то время вёл свой блог, но решил попробовать работать в команде. И вот c 2012 года я здесь :)