Создание компонента для Joomla

Часть 3 - поддержка меню и обработка ошибок

Joomla
Добавление в избранное
Сохранить
1

Часть 3 - поддержка меню

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

Добавление пункта меню

Добавить недостающий функционал довольно легко. Просто создайте файл site/views/helloworld/tmpl/default.xml с кодом:

<?xml version="1.0" encoding="utf-8"?>
<metadata>
    <layout title="COM_HELLOWORLD_HELLOWORLD_VIEW_DEFAULT_TITLE">
        <message>COM_HELLOWORLD_HELLOWORLD_VIEW_DEFAULT_DESC</message>
    </layout>
</metadata>

Теперь в списке будет появляться наш HelloWorld!:

Добавление пункта меню

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

Добавление параметров в меню

Сейчас наш компонент отображает только одно сообщение - Hello World!. Joomla 2.5 предоставляет возможность добавлять параметры в типы меню, тем самым мы можем расширить количество сообщений. В нашем случае, это делается в том же самом файле site/views/helloworld/tmpl/default.xml:

<?xml version="1.0" encoding="utf-8"?>
<metadata>
    <layout title="COM_HELLOWORLD_HELLOWORLD_VIEW_DEFAULT_TITLE">
        <message>COM_HELLOWORLD_HELLOWORLD_VIEW_DEFAULT_DESC</message>
    </layout>
    <fields name="request">
        <fieldset name="request">
            <field
                name="id"
                type="list"
                label="COM_HELLOWORLD_HELLOWORLD_FIELD_GREETING_LABEL"
                description="COM_HELLOWORLD_HELLOWORLD_FIELD_GREETING_DESC"
                default="1"
            >
                <option value="1">Hello World!</option>
                <option value="2">Good bye World!</option>
            </field>
        </fieldset>
    </fields>
</metadata>

Здесь важно отметить, что request группа полей обозначает обязательные параметры. Мы добавили поле с типом list и список сообщений для этого поля.

Пункт меню - обязательные параметры

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

site/models/helloworld.php

<?php
// Запрет прямого доступа.
defined('_JEXEC') or die;
 
// Подключаем библиотеку modelitem Joomla.
jimport('joomla.application.component.modelitem');
 
/**
 * Модель сообщения компонента HelloWorld.
 */
class HelloWorldModelHelloWorld extends JModelItem
{
    /**
     * Получаем сообщение.
     *
     * @param   int  $id  Id сообщения.
     *
     * @return  string  Сообщение, которое отображается пользователю.
     */
    public function getItem($id = null)
    {
        // Если id не установлено, то получаем его из состояния.
        $id = (!empty($id)) ? $id : (int) $this->getState('message.id');       
 
        if (!isset($this->_item))
        {
            switch ($id)
            {
                case 2:
                    $this->_item = 'Good bye World!';
                    break;            
    
                case 1:
                default:
                    $this->_item = 'Hello World!';
                    break;
            }
        }        
 
        return $this->_item;
    }  
 
    /**
     * Метод для авто-заполнения состояния модели.
     *
     * Заметка. Вызов метода getState в этом методе приведет к рекурсии.
     *
     * @return  void
     */
    protected function populateState()
    {
        $app = JFactory::getApplication();
 
        // Получаем Id сообщения из Запроса.
        $id = $app->input->getInt('id', 0);
 
        // Добавляем Id сообщения в состояние модели.
        $this->setState('message.id', $id);
 
        parent::populateState();
    }
}

Обратите внимание, что мы используем метод populateState() для автозаполнения состояния модели. Состояние модели - это объект JObject, который хранится в свойстве $state класса JModel. Метод populateState() вызывается единожды при первой попытке установить состояние модели через метод setState(), которая происходит в методе getModel() контроллера.

Пока мы заполняем состояние модели лишь одной переменной - Id сообщения. Таким образом, мы можем получать доступ к текущему Id сообщения, не запрашивая его постоянно из объекта Запроса.

Поддержка настроек пункта меню

Для того, чтобы компонент мог изменять заголовок страницы в браузере, заголовок страницы компонента и метаданные в зависимости от настроек в пункте меню, нам необходимо добавить эту поддержку в представление site/views/helloworld/view.html.php:

<?php
// Запрет прямого доступа.
defined('_JEXEC') or die;

 
// Подключаем библиотеку представления Joomla.
jimport('joomla.application.component.view');
 
/**
 * HTML представление сообщения компонента HelloWorld.
 */
class HelloWorldViewHelloWorld extends JViewLegacy
{
    /**
     * Сообщение.
     *
     * @var  string
     */
    protected $item;
 
    /**
     * Параметры.
     *
     * @var  object
     */
    protected $params;
 
    /**
     * Переопределяем метод display класса JViewLegacy.
     *
     * @param   string  $tpl  Имя файла шаблона.
     *
     * @return  void
     */
    public function display($tpl = null)
    {
        try
        {
            // Получаем сообщение из модели.
            $this->item = $this->get('Item');
 
            // Получаем параметры приложения.
            $app          = JFactory::getApplication();
            $this->params = $app->getParams();
 
            // Подготавливаем документ.
            $this->_prepareDocument();
 
            // Отображаем представление.
            parent::display($tpl);
        }
        catch (Exception $e)
        {
            JFactory::getApplication()->enqueueMessage(JText::_('COM_HELLOWORLD_ERROR_OCCURRED'), 'error');
            JLog::add($e->getMessage(), JLog::ERROR, 'com_helloworld');
        }
    }
 
    /**
     * Подготавливает документ.
     *
     * @return  void
     */
    protected function _prepareDocument()
    {
        $app   = JFactory::getApplication();
        $menus = $app->getMenu();
        $title = null;
 
        // Так как приложение устанавливает заголовок страницы по умолчанию,
        // мы получаем его из пункта меню.
        $menu = $menus->getActive();
 
        if ($menu)
        {
            $this->params->def('page_heading', $this->params->get('page_title', $menu->title));
        }
        else
        {
            $this->params->def('page_heading', JText::_('COM_HELLOWORLD_DEFAULT_PAGE_TITLE'));
        }
 
        // Получаем заголовок страницы в браузере из параметров.
        $title = $this->params->get('page_title', '');
 
        if (empty($title))
        {
            $title = $app->getCfg('sitename');
        }
        elseif ($app->getCfg('sitename_pagetitles', 0) == 1)
        {
            $title = JText::sprintf('JPAGETITLE', $app->getCfg('sitename'), $title);
        }
        elseif ($app->getCfg('sitename_pagetitles', 0) == 2)
        {
            $title = JText::sprintf('JPAGETITLE', $title, $app->getCfg('sitename'));
        }
 
        if (empty($title))
        {
            $title = $this->item;
        }
 
        // Устанавливаем заголовок страницы в браузере.
        $this->document->setTitle($title);
 
        // Добавляем поддержку метаданных из пункта меню.
        if ($this->params->get('menu-meta_description'))
        {
            $this->document->setDescription($this->params->get('menu-meta_description'));
        }
 
        if ($this->params->get('menu-meta_keywords'))
        {
            $this->document->setMetadata('keywords', $this->params->get('menu-meta_keywords'));
        }
 
        if ($this->params->get('robots'))
        {
            $this->document->setMetadata('robots', $this->params->get('robots'));
        }
    }
}

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

Но откуда у Приложения появился метод getParams()? В классе JApplication такого метода нет. Хитрость в том, что метод JApplication::getInstance() возвращает объект текущего приложения, класс которого располагается в файле /includes/application.php. Для публичной части - это JSite, а для административной части - JAdministrator. Метод getParams() находится в классе JSite.

И откуда у представления взялось свойство document, c помощью которого мы манипулируем страницей?  Если мы посмотрим на класс JView, то не обнаружим там такого свойства. Так как же оно попало в представление? А попало это свойство в представление из метода display() контроллера, который назначает его представлению, передавая объект класса JDocument. А точнее объект одного из дочерних классов: JDocumentHtml, JDocumentRaw и т.п. В нашем случае - это JDocumentHtml.

Изменям шаблон сообщения

Добавим вывод заголовка страницы компонента в шаблон site/views/helloworld/tmpl/default.php:

<?php
// Запрет прямого доступа.
defined('_JEXEC') or die;
?>
<?php if ($this->params->get('show_page_heading')) : ?>
<h1>
 <?php echo $this->escape($this->params->get('page_heading')); ?>
</h1>
<?php endif; ?>
<h2><?php echo $this->item; ?></h2>

Здесь мы воспользовались методом escape(), чтобы экранировать спецсимволы HTML и избежать возможной XSS атаки.

Обработка ошибок

В Joomla по умолчанию используется свой внутренний механизм обработки ошибок с помощью классов JError и JException. Но начиная с версии Платформы 12.1 они отмечены как устаревшие и указано, что стоит использовать встроенный в PHP механизм исключений.

Наш компонент как можно меньше должен строится на устаревших классах или методах, поэтому в методе display() задействован класс Exception. Если возникло исключение, то мы делаем две вещи:

  1. Отображаем сообщение об ошибке пользователю с помошью метода enqueueMessage().
  2. Логируем ошибку с помощью метода JLog::add(). В первом параметре мы передаем ошибку, которую получаем из объекта исключения. Во втором параметре передаем уровень ошибки JLog::ERROR. А в третьем параметре указываем, что категория ошибки com_helloworld.

Но есть одно но. Чтобы использовать Exception, необходимо установить свойство $legacy класса JError в значение false. Самым лучшим местом для этого конечно же будет точка входа компонента site/helloworld.php. Добавьте сразу после подключения логирования следущий код:

// Устанавливаем обработку ошибок в режим использования Exception.
JError::$legacy = false;

Собираем пакет установки компонента

Не забудьте поменять номер версии в файле helloworld.xml:

<version>0.0.3</version>

helloworld.xml

<?xml version="1.0" encoding="utf-8"?>
<extension type="component" version="2.5.0" method="upgrade">
 
    <name>Hello World!</name>
    <!-- Следующие элементы необязательны -->
    <creationDate>Июль 2012</creationDate>
    <author>Вася Пупкин</author>
    <authorEmail>Ваш e-mail</authorEmail>
    <authorUrl>Ваш сайт</authorUrl>
    <copyright>Информация о копирайте</copyright>
    <license>Информация о лицензии</license>
    <!--  Версия записывается в таблицу компонентов -->
    <version>0.0.3</version>
    <!-- Описание необязательно -->
    <description>Описание компонента Hello World! ...</description>
 
    <!-- Запускается при обновлении -->
    <update>
        <schemas>
            <schemapath type="mysql">sql/updates/mysql</schemapath>
        </schemas>
    </update>
 
    <!-- Раздел основных файлов сайта -->
    <!-- Обратите внимание на значение аттрибута folder: Этот аттрибут описывает папку нашего пакета-установщика из которой должны копироваться файлы. 
    Поэтому указанные в этом разделе файлы будут скопированы из папки /site/ нашего пакета-установщика в соответствующую папку установки. -->
    <files folder="site">
        <filename>index.html</filename>
        <filename>controller.php</filename>
        <filename>helloworld.php</filename>
        <folder>models</folder>
        <folder>views</folder>
    </files>
 
    <!-- Администрирование -->
    <administration>
        <!-- Раздел Меню -->
        <menu>Hello World!</menu>
        <!-- Раздел основных файлов администрирования  -->
        <!-- Обратите внимание на значение аттрибута folder: Этот аттрибут описывает папку нашего пакета-установщика из которой должны копироваться файлы. 
        Поэтому указанные в этом разделе файлы будут скопированы из папки /admin/ нашего пакета-установщика в соответствующую папку установки. -->
        <files folder="admin">
            <filename>index.html</filename>
            <filename>helloworld.php</filename>
            <folder>sql</folder>
        </files>
    </administration>
 
</extension>

Содержимое директории с кодом:

helloworld.xml
site/index.html
site/controller.php
site/helloworld.php
site/models/index.html
site/models/helloworld.php
site/views/index.html
site/views/helloworld/index.html
site/views/helloworld/view.html.php
site/views/helloworld/tmpl/index.html
site/views/helloworld/tmpl/default.php
site/views/helloworld/tmpl/default.xml
admin/index.html
admin/helloworld.php
admin/sql/index.html
admin/sql/updates/index.html
admin/sql/updates/mysql/index.html
admin/sql/updates/mysql/0.0.1.sql

Запакуйте директорию в архивный файл (zip, tar, tar.gz, bz2) или скачайте напрямую с GitHub. Далее установите его, используя менеджер расширений Joomla. Теперь вы можете добавить и настроить пункт меню для компонента, и выбрать сообщение, которое будет отображаться пользователю.

В следущей части мы добавим поддержку базы данных.

Код для этой части

Скачать com_helloworld часть 3

Актуальный код части 3 на GitHub

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

Подпишитесь на рассылку новостей CMScafe