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

Часть 12 - поддержка ACL

Joomla

Часть 12 - поддержка ACL

Запрещаем доступ к компоненту

Основной идеей ACL является запрет на действия для групп пользователей. Самое первое действие, которое мы запретим - это доступ к компоненту в администраторской части. Для этого мы добавляем проверку в точку входа admin/helloworld.php:

<?php
// Запрет прямого доступа.
defined('_JEXEC') or die;
 
// Проверка доступа.
if (!JFactory::getUser()->authorise('core.manage', 'com_helloworld'))
{
    throw new Exception(JText::_('JERROR_ALERTNOAUTHOR'), 401);
}
 
// Устанавливаем обработку ошибок в режим использования Exception.
JError::$legacy = false;
 
// Подключаем хелпер.
JLoader::register('HelloWorldHelper', dirname(__FILE__) . '/helpers/helloworld.php');
 
// Подключаем библиотеку контроллера Joomla.
jimport('joomla.application.component.controller');
 
// Получаем экземпляр контроллера с префиксом HelloWorld.
$controller = JControllerLegacy::getInstance('HelloWorld');
 
// Исполняем задачу task из Запроса.
$input = JFactory::getApplication()->input;
$controller->execute($input->getCmd('task', 'display'));
 
// Перенаправляем, если перенаправление установлено в контроллере.
$controller->redirect();

С помошью метода JUser->authorise() мы проверяем, есть ли у пользователя право управления компонентом. Если нет, то мы генерируем исключение с сообщением о запрете.

Отображаем только доступные кнопки панели инструментов

Какие именно кнопки необходимо отобразить, зависит от доступов в списке контроля пользователя. Изменим код представления для списка записей admin/views/helloworlds/view.html.php:

<?php
// Запрет прямого доступа.
defined('_JEXEC') or die;
 
// Подключаем библиотеку представления Joomla.
jimport('joomla.application.component.view');
 
/**
 * HTML представление списка сообщений компонента HelloWorld.
 */
class HelloWorldViewHelloWorlds extends JViewLegacy
{
    /**
     * Сообщения.
     *
     * @var  array
     */
    protected $items;
 
    /**
     * Постраничная навигация.
     *
     * @var  object
     */
    protected $pagination;
 
    /**
     * Доступы пользователя.
     *
     * @var  object
     */
    protected $canDo;
 
    /**
     * Отображаем список сообщений.
     *
     * @param   string  $tpl  Имя файла шаблона.
     *
     * @return  void
     *
     * @throws  Exception
     */
    public function display($tpl = null)
    {
        try
        {
            // Получаем данные из модели.
            $this->items = $this->get('Items');
 
            // Получаем объект постраничной навигации.
            $this->pagination = $this->get('Pagination');
 
            // Получаем доступы пользователя.
            $this->canDo = HelloWorldHelper::getActions();
 
            // Устанавливаем панель инструментов.
            $this->addToolBar();
 
            // Отображаем представление.
            parent::display($tpl);
        }
        catch (Exception $e)
        {
            throw new Exception($e->getMessage());
        }
    }
 
    /**
     * Устанавливает панель инструментов.
     *
     * @return void
     */
    protected function addToolBar()
    {
        JToolBarHelper::title(JText::_('COM_HELLOWORLD_MANAGER_HELLOWORLDS'), 'helloworld');
 
        if ($this->canDo->get('core.create'))
        {
            JToolBarHelper::addNew('helloworld.add');
        }
 
        if ($this->canDo->get('core.edit'))
        {
            JToolBarHelper::editList('helloworld.edit');
        }
 
        if ($this->canDo->get('core.delete'))
        {
            JToolBarHelper::divider();
            JToolBarHelper::deleteList('', 'helloworlds.delete');
        }
 
        if ($this->canDo->get('core.admin'))
        {
            JToolBarHelper::divider();
            JToolBarHelper::preferences('com_helloworld');
        }
    }
}

Также мы меняем представление редактирования записи admin/views/helloworld/view.html.php:

<?php
// Запрет прямого доступа.
defined('_JEXEC') or die;
 
// Подключаем библиотеку представления Joomla.
jimport('joomla.application.component.view');
 
/**
 * HTML представление редактирования сообщения.
 */
class HelloWorldViewHelloWorld extends JViewLegacy
{
    /**
     * Сообщение.
     *
     * @var  object
     */
    protected $item;
 
    /**
     * Объект формы.
     *
     * @var  object
     */
    protected $form;
 
    /**
     * JavaScript файл валидации формы.
     *
     * @var  string
     */
    protected $script;
 
    /**
     * Доступы пользователя.
     *
     * @var  object
     */
    protected $canDo;
 
    /**
     * Отображает представление.
     *
     * @param   string  $tpl  Имя файла шаблона.
     *
     * @return  void
     *
     * @throws  Exception
     */
    public function display($tpl = null)
    {
        try
        {
            // Получаем данные из модели.
            $this->form = $this->get('Form');
            $this->item = $this->get('Item');
            $this->script = $this->get('Script');
 
            // Получаем доступы пользователя.
            $this->canDo = HelloWorldHelper::getActions($this->item->catid, $this->item->id);
 
            // Устанавливаем панель инструментов.
            $this->addToolBar();
 
            // Отображаем представление.
            parent::display($tpl);
 
            // Устанавливаем документ.
            $this->setDocument();
        }
        catch (Exception $e)
        {
            throw new Exception($e->getMessage());
        }
    }
 
    /**
     * Устанавливает панель инструментов.
     *
     * @return  void
     */
    protected function addToolBar()
    {
        JFactory::getApplication()->input->set('hidemainmenu', true);
        $isNew = ($this->item->id == 0);
 
        JToolBarHelper::title($isNew ? JText::_('COM_HELLOWORLD_MANAGER_HELLOWORLD_NEW') : JText::_('COM_HELLOWORLD_MANAGER_HELLOWORLD_EDIT'), 'helloworld');
 
        // Устанавливаем действия для новых и существующих записей.
        if ($isNew)
        {
            // Для новых записей проверяем право создания.
            if ($this->canDo->get('core.create'))
            {
                JToolBarHelper::apply('helloworld.apply', 'JTOOLBAR_APPLY');
                JToolBarHelper::save('helloworld.save', 'JTOOLBAR_SAVE');
                JToolBarHelper::custom('helloworld.save2new', 'save-new.png',
                                        'save-new_f2.png', 'JTOOLBAR_SAVE_AND_NEW', false
                                        );
            }
 
            JToolBarHelper::cancel('helloworld.cancel', 'JTOOLBAR_CANCEL');
        }
        else
        {
            // Для существующих записей проверяем право редактирования.
            if ($this->canDo->get('core.edit'))
            {
                // Мы можем сохранять новую запись.
                JToolBarHelper::apply('helloworld.apply', 'JTOOLBAR_APPLY');
                JToolBarHelper::save('helloworld.save', 'JTOOLBAR_SAVE');
 
                // Мы можем сохранять  в новую запись, но нужна проверка на создание.
                if ($this->canDo->get('core.create'))
                {
                    JToolBarHelper::custom('helloworld.save2new', 'save-new.png',
                                            'save-new_f2.png', 'JTOOLBAR_SAVE_AND_NEW', false
                                            );
                }
            }
 
            // Для сохранения копии записи проверяем право создания.
            if ($this->canDo->get('core.create'))
            {
                JToolBarHelper::custom('helloworld.save2copy', 'save-copy.png',
                                        'save-copy_f2.png', 'JTOOLBAR_SAVE_AS_COPY', false
                                        );
            }
 
            JToolBarHelper::cancel('helloworld.cancel', $isNew ? 'JTOOLBAR_CANCEL' : 'JTOOLBAR_CLOSE');
        }
    }
 
    /**
     * Метод для установки свойств документа.
     *
     * @return  void
     */
    protected function setDocument()
    {
        $document = JFactory::getDocument();
        $document->addScript(JURI::root() . $this->script);
        $document->addScript(
            JURI::root() . "administrator/components/com_helloworld/views/helloworld/submitbutton.js");
        JText::script('COM_HELLOWORLD_HELLOWORLD_ERROR_UNACCEPTABLE');
    }
}

Эти два файла используют метод getActions() для получения доступов пользователя. Этот метод мы определяем в хелпере admin/helpers/helloworld.php:

<?php
// Запрет прямого доступа.
defined('_JEXEC') or die;
 
/**
 * Хелпер HelloWorld компонента.
 */
abstract class HelloWorldHelper
{
    /**
     * Кэш для доступных действий.
     *
     * @var  JObject
     */
    private static $actions;
 
    /**
     * Конфигурируем подменю.
     *
     * @param   string  $submenu  Активный пункт меню.
     *
     * @return  void
     */
    public static function addSubmenu($submenu)
    {
        // Добавляем пункты подменю.
        JSubMenuHelper::addEntry(
            JText::_('COM_HELLOWORLD_SUBMENU_MESSAGES'),
            'index.php?option=com_helloworld',
            $submenu == 'messages'
        );
 
        JSubMenuHelper::addEntry(
            JText::_('COM_HELLOWORLD_SUBMENU_CATEGORIES'),
            'index.php?option=com_categories&view=categories&extension=com_helloworld',
            $submenu == 'categories'
        );
 
        // Устанавливаем глобальные свойства.
        $document = JFactory::getDocument();
        $document->addStyleDeclaration('.icon-48-helloworld ' .
            '{background-image: url(../media/com_helloworld/images/hello-48x48.png);}');
 
        if ($submenu == 'categories')
        {
            $document->setTitle(JText::_('COM_HELLOWORLD_ADMINISTRATION_CATEGORIES'));
        }
    }
 
    /**
     * Получаем доступы для действий.
     *
     * @param   int  $categoryId  Id категории.
     * @param   int  $messageId   Id сообщения.
     *
     * @return  object
     */
    public static function getActions($categoryId = 0, $messageId = 0)
    {
        // Определяем имя ассета (ресурса).
        if (empty($messageId) && empty($categoryId))
        {
            $assetName = 'com_helloworld';
            $section = 'component';
        }
        elseif (empty($messageId))
        {
            $assetName = 'com_helloworld.category.' . (int) $categoryId;
            $section = 'category';
        }
        else
        {
            $assetName = 'com_helloworld.message.' . (int) $messageId;
            $section = 'message';
        }
 
        if (empty(self::$actions))
        {
            // Получаем список доступных действий для компонента.
            $accessFile = JPATH_ADMINISTRATOR . '/components/com_helloworld/access.xml';
            $actions = JAccess::getActionsFromFile($accessFile, "/access/section[@name='" . $section . "']/");
 
            // Для сообщения добавляем действие core.admin.
            if ($section == 'message')
            {
                $adminAction = new stdClass;
                $adminAction->name = 'core.admin';
 
                array_push($actions, $adminAction);
            }
 
            self::$actions = new JObject;
 
            foreach ($actions as $action)
            {
                // Устанавливаем доступы пользователя для действий.
                self::$actions->set($action->name, JFactory::getUser()->authorise($action->name, $assetName));
            }
        }
 
        return self::$actions;
    }
}

В методе getActions() мы сначала определяем имя ассета и далее определяем для него список доступных действий, который читаем из файла access.xml с помощью метода getActionsFromFile() класса JAccess.

Добавляем колонку asset_id в таблицу базы данных

Для того, чтобы ACL поддерживался нашей таблицей, необходимо добавить колонку asset_id в таблицу #__helloworld базы данных. Изменяем файл admin/sql/install.mysql.utf8.sql на следующий:

DROP TABLE IF EXISTS `#__helloworld`;

CREATE TABLE `#__helloworld` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `asset_id` INT(10) NOT NULL DEFAULT '0',
 `greeting` varchar(25) NOT NULL,
 `catid` int(11) NOT NULL DEFAULT '0',
 `params` TEXT NOT NULL DEFAULT '',
 PRIMARY KEY  (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
 
INSERT INTO `#__helloworld` (`greeting`) VALUES
 ('Hello World!'),
 ('Good bye World!');

Не забываем про SQL-файл обновления admin/sql/updates/mysql/0.0.12.sql:

ALTER TABLE`#__helloworld` ADD COLUMN `asset_id` INT(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `id`;

Устанавливаем значения доступов в таблице ассетов

Для того, чтобы иметь возможность хранить доступы для каждого сообщения в таблице ассетов, нам необходимо изменить наш класс таблицы. JTable предоставляет нам такой интерфейс, поэтому мы должны добавить информацию о значениях доступов (правила доступов), а также мы должны предоставить имя ассета, название ассета и id родительского ассета, используя наш класс таблицы HelloWorldTableHelloWorld. Для этого мы переопределяем 3 метода:

  • _getAssetName(): уникальное имя ассета
  • _getAssetTitle(): более дружественное название ассета для его определения (не обязательно уникальное)
  • _getAssetParentId(): asset_id родителя в таблице ассетов (от него наследуются доступы)

admin/tables/helloworld.php

<?php
// Запрет прямого доступа.
defined('_JEXEC') or die;
 
// Подключаем библиотеку таблиц Joomla.
jimport('joomla.database.table');/**
 * Класс таблицы HelloWorld.
 */
class HelloWorldTableHelloWorld extends JTable
{
    /**
     * Конструктор.
     *
     * @param   JDatabase  &$db  Коннектор объекта базы данных.
     */
    public function __construct(&$db)
    {
        parent::__construct('#__helloworld', 'id', $db);
    }
 
    /**
     * Переопределяем bind метод JTable.
     *
     * @param   array  $array   Массив значений.
     * @param   array  $ignore  Массив значений, которые должны быть игнорированы.
     *
     * @return  boolean  True если все прошло успешно, в противном случае false.
     */
    public function bind($array, $ignore = array())
    {
        if (isset($array['params']) && is_array($array['params']))
        {
            // Конвертируем поле параметров в JSON строку.
            $parameter = new JRegistry;
            $parameter->loadArray($array['params']);
            $array['params'] = (string) $parameter;
        }
 
        // Правила.
        if (isset($array['rules']) && is_array($array['rules']))
        {
            $rules = new JAccessRules($array['rules']);
            $this->setRules($rules);
        }
 
        return parent::bind($array, $ignore);
    }
 
    /**
     * Переопределяем load метод JTable.
     *
     * @param   int      $pk     Первичный ключ.
     * @param   boolean  $reset  Сбрасывать данные перед загрузкой или нет.
     *
     * @return  boolean  True если все прошло успешно, в противном случае false.
     */
    public function load($pk = null, $reset = true)
    {
        if (parent::load($pk, $reset))
        {
            // Конвертируем поле параметров в регистр.
            $params = new JRegistry;
            $params->loadString($this->params);
            $this->params = $params;
 
            return true;
        }
        else
        {
            return false;
        }
    }
 
    /**
     * Метод для вычисления уникального имени ассета.
     *
     * @return  string  Имя ассета.
     */
    protected function _getAssetName()
    {
        $k = $this->_tbl_key;
 
        return 'com_helloworld.message.' . (int) $this->$k;
    }
 
    /**
     * Метод для получения названия ассета.
     *
     * @return  string  Название ассета.
     */
    protected function _getAssetTitle()
    {
        return $this->greeting;
    }
 
    /**
     * Метод для получения id родителя записи.
     *
     * @param   JTable  $table  Объект JTable родителя ассета.
     * @param   int     $id     Искомый Id.
     *
     * @return  int  Id родителя записи.
     */
    protected function _getAssetParentId($table = null, $id = null)
    {
        // Получаем таблицу ассетов.
        $assetParent = JTable::getInstance('Asset');
 
        // По умолчанию: если родительский ассет не найден, то берем глобальный.
        $assetParentId = $assetParent->getRootId();
 
        // Ищем родительский ассет.
        if (($this->catid) && !empty($this->catid))
        {
            // В качестве родительского ассета записи выступает категория.
            $assetParent->loadByName('com_helloworld.category.' . (int) $this->catid);
        }
        else
        {
            // В качестве родительского ассета записи выступает компонент.
            $assetParent->loadByName('com_helloworld');
        }
 
        // Возвращаем найденный id родителя записи.
        if ($assetParent->id)
        {
            $assetParentId = $assetParent->id;
        }
 
        return $assetParentId;
    }
}

Кроме этих трех методов, мы также добавили обработку правил в методе bind() - в нем мы устанавливаем список текущих правил доступа для нашего сообщения, которые хранятся в нашем классе таблицы в виде объекта класса JAccessRules. Теперь при сохранении записи информация о доступах будет корректно сохраняться в таблице ассетов.

Проверяем значения доступов

В контроллере HelloWorld нам необходимо проверить доступы core.add и core.edit - есть ли у пользователя право создания и редактирования сообщения? Изменяем файл admin/controllers/helloworld.php:

<?php
// Запрет прямого доступа.
defined('_JEXEC') or die;
 
// Подключаем библиотеку controllerform Joomla.
jimport('joomla.application.component.controllerform');
 
/**
 * HelloWorld контроллер.
 */
class HelloWorldControllerHelloWorld extends JControllerForm
{
    /**
     * Переопределение метода для проверки,
     * может ли пользователь добавлять запись.
     *
     * @param   array  $data  Массив данных.
     *
     * @return  boolean  True, если разрешено редактировать запись.
     */
    protected function allowAdd($data = array())
    {
        // Получаем значение категории из массива.
        $categoryId = JArrayHelper::getValue($data, 'catid', 0, 'int');
 
        if ($categoryId)
        {
            // Проверка добавления на уровне категории.
            return JFactory::getUser()->authorise('core.create', $this->option . '.category.' . $categoryId);
        }
        else
        {
            // Проверка добавления на уровне компонента.
            return parent::allowAdd($data);
        }
    }
 
    /**
     * Переопределение метода для проверки,
     * может ли пользователь редактировать существующую запись.
     *
     * @param   array   $data  Массив данных.
     * @param   string  $key   Имя первичного ключа.
     *
     * @return  boolean  True, если разрешено редактировать запись.
     */
    protected function allowEdit($data = array(), $key = 'id')
    {
        $recordId = (int) isset($data[$key]) ? $data[$key] : 0;
 
        if ($recordId)
        {
            // Проверка редактирования на уровне записи.
            return JFactory::getUser()->authorise('core.edit', $this->option . '.message.' . $recordId);
        }
        else
        {
            // Проверка редактирования на уровне компонента.
            return parent::allowEdit($data, $key);
        }
    }
}

Обратите внимание, что как и в модели мы использовали свойство $this->option. Оно определяется автоматически в конструкторе родительского класса JControllerForm с помощью метода getName() класса JController.

Также в модели HelloWorld мы проверяем доступ core.delete - есть ли у пользователя право удаления? Изменяем файл admin/models/helloworld.php и добавляем в конце метод canDelete():

/**
     * Метод для проверки, может ли пользователь удалять существующую запись.
     *
     * @param   object  $record  Объект записи.
     *
     * @return  boolean  True, если разрешено удалять запись.
     */
    protected function canDelete($record)
    {
        if (!empty($record->id))
        {
            return JFactory::getUser()->authorise('core.delete', $this->option . '.message.' . (int) $record->id);
        }
        else
        {
            return parent::canDelete($record);
        }
    }
}

Хотелось бы отметить еще одну важную деталь - если мы делаем проверку на уровне записи, то она поднимается вверх по иерархии ассетов. Например, если право удаления записи не было задано для уровня записи, то далее проверяется право удаления в категории, которой принадлежит запись. Если и там право удаления найти не удалось, то проверка поднимается выше - на уровень компонента. Самой высшей точкой проверки являются Общие настройки прав. И как вы уже наверное поняли, эту иерахию мы выстраиваем в методе _getAssetParentId() нашего класса таблицы.

Добавляем настройку ACL на уровне записи

Теперь, когда у нас есть проверка доступов, мы можем добавить недостающий интерфейс для настройки ACL на уровне записи. Вносим изменения в шаблон редактирования записи admin/views/helloworld/tmpl/edit.php и добавляем интерфейс в нижнюю часть:

<?php
// Запрет прямого доступа.
defined('_JEXEC') or die;
 
// Загружаем тултипы.
JHtml::_('behavior.tooltip');
 
// Загружаем проверку формы.
JHtml::_('behavior.formvalidation');
 
// Получаем параметры из формы.
$params = $this->form->getFieldsets('params');
?>
<form action="<?php echo JRoute::_('index.php?option=com_helloworld&layout=edit&id=' . (int) $this->item->id); ?>" method="post" name="adminForm" id="helloworld-form">
    <div>
        <fieldset>
            <legend><?php echo JText::_('COM_HELLOWORLD_HELLOWORLD_DETAILS'); ?></legend>
            <ul>
                <?php foreach ($this->form->getFieldset('details') as $field) : ?>
                    <li><?php echo $field->label; echo $field->input; ?></li>
                <?php endforeach; ?>
            </ul>
        </fieldset>
    </div>
 
    <div>
        <?php echo JHtml::_('sliders.start', 'helloworld-slider');
 
        foreach ($params as $name => $fieldset):
            echo JHtml::_('sliders.panel', JText::_($fieldset->label), $name . '-params');
 
            if (isset($fieldset->description) && trim($fieldset->description)) : ?>
                <p><?php echo $this->escape(JText::_($fieldset->description));?></p>
            <?php endif;?>
 
            <fieldset >
                <ul>
                    <?php foreach ($this->form->getFieldset($name) as $field) : ?>
                        <li><?php echo $field->label; ?><?php echo $field->input; ?></li>
                    <?php endforeach; ?>
                </ul>
            </fieldset>
        <?php endforeach; ?>
 
        <?php echo JHtml::_('sliders.end'); ?>
    </div>
 
    <!--  начало ACL интерфейса -->
    <div></div>
 
    <?php if ($this->canDo->get('core.admin')) : ?>
        <div>
            <?php echo JHtml::_('sliders.start', 'permissions-sliders-' . $this->item->id, array('useCookie' => 1)); ?>
 
                <?php echo JHtml::_('sliders.panel', JText::_('COM_HELLOWORLD_FIELDSET_RULES'), 'access-rules'); ?>
                <fieldset>
                    <?php echo $this->form->getLabel('rules'); ?>
                    <?php echo $this->form->getInput('rules'); ?>
                </fieldset>
 
            <?php echo JHtml::_('sliders.end'); ?>
        </div>
    <?php endif; ?>
    <!-- конец ACL интерфейса -->
 
    <div>
        <input type="hidden" name="task" value="" />
        <?php echo JHtml::_('form.token'); ?>
    </div>
</form>

Добавляем возможность редактирования отдельной записи

Так как мы можем настраивать доступы на уровне отдельной записи, нам необходимо сделать так, чтобы при выключении доступов на редактирование на уровне компонента (в "Настройках"), мы все равно могли бы иметь возможность редактировать определенные записи. Для этого вносим изменения в шаблон списка записей admin/views/helloworlds/tmpl/default_body.php:

<?php
// Запрет прямого доступа.
defined('_JEXEC') or die;
 
foreach ($this->items as $i => $item) :
    $canEdit = JFactory::getUser()->authorise('core.edit', 'com_helloworld.message.' . $item->id); ?>
 
    <tr>
        <td>
            <?php echo JHtml::_('grid.id', $i, $item->id); ?>
        </td>
        <td>
            <?php if ($canEdit) : ?>
                <a href="/<?php echo JRoute::_('index.php?option=com_helloworld&task=helloworld.edit&id=' . (int)$item->id); ?>">
                    <?php echo $this->escape($item->greeting); ?>
                </a>
            <?php else : ?>
                <?php echo $this->escape($item->greeting); ?>
            <?php endif; ?>
        </td>
        <td>
            <?php echo $item->id; ?>
        </td>
    </tr>
<?php endforeach; ?>

Мы добавили проверку доступа на редактирование записи и если такой доступ есть, добавляется ссылка, которая ведет на форму редактирования записи.

Добавляем языковые константы

Откройте файл admin/language/en-GB/en-GB.com_helloworld.ini и добавьте:

COM_HELLOWORLD_FIELDSET_RULES="Rules"

Откройте файл admin/language/ru-RU/ru-RU.com_helloworld.ini и добавьте:

COM_HELLOWORLD_FIELDSET_RULES="Права"

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

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

<version>0.0.12</version>


helloworld.xml

<?xml version="1.0" encoding="utf-8"?>
<extension type="component" version="2.5.0" method="upgrade">
 
    <name>COM_HELLOWORLD</name>
    <!-- Следующие элементы необязательны -->
    <creationDate>Июль 2012</creationDate>
    <author>Вася Пупкин</author>
    <authorEmail>Ваш e-mail</authorEmail>
    <authorUrl>Ваш сайт</authorUrl>
    <copyright>Информация о копирайте</copyright>
    <license>Информация о лицензии</license>
    <!--  Версия записывается в таблицу компонентов -->
    <version>0.0.12</version>
    <!-- Описание необязательно -->
    <description>COM_HELLOWORLD_DESCRIPTION</description>
 
    <!-- Запускается при установке -->
    <install>
        <sql>
            <file driver="mysql" charset="utf8">sql/install.mysql.utf8.sql</file>
        </sql>
    </install>
    <!-- Запускается при удалении -->
    <uninstall>
        <sql>
            <file driver="mysql" charset="utf8">sql/uninstall.mysql.utf8.sql</file>
        </sql>
    </uninstall>
    <!-- Запускается при обновлении -->
    <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>language</folder>
        <folder>models</folder>
        <folder>views</folder>
    </files>
 
    <media destination="com_helloworld" folder="media">
        <filename>index.html</filename>
        <folder>images</folder>
    </media>
 
    <!-- Администрирование -->
    <administration>
        <!-- Раздел Меню -->
        <menu img="../media/com_helloworld/images/hello-16x16.png">COM_HELLOWORLD_MENU</menu>
        <!-- Раздел основных файлов администрирования  -->
        <!-- Обратите внимание на значение аттрибута folder: Этот аттрибут описывает папку нашего пакета-установщика из которой должны копироваться файлы. 
        Поэтому указанные в этом разделе файлы будут скопированы из папки /admin/ нашего пакета-установщика в соответствующую папку установки. -->
        <files folder="admin">
            <filename>index.html</filename>
            <filename>access.xml</filename>
            <filename>config.xml</filename>
            <filename>controller.php</filename>
            <filename>helloworld.php</filename>
            <folder>controllers</folder>
            <folder>helpers</folder>
            <folder>models</folder>
            <folder>sql</folder>
            <folder>tables</folder>
            <folder>views</folder>
        </files>
        <languages folder="admin">
            <language tag="en-GB">language/en-GB/en-GB.com_helloworld.ini</language>
            <language tag="en-GB">language/en-GB/en-GB.com_helloworld.sys.ini</language>
            <language tag="ru-RU">language/ru-RU/ru-RU.com_helloworld.ini</language>
            <language tag="ru-RU">language/ru-RU/ru-RU.com_helloworld.sys.ini</language>
        </languages>
    </administration>
 
</extension>

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

helloworld.xml
site/index.html
site/helloworld.php
site/controller.php
site/language/index.html
site/language/en-GB/index.html
site/language/en-GB/en-GB.com_helloworld.ini
site/language/ru-RU/index.html
site/language/ru-RU/ru-RU.com_helloworld.ini
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.xml
site/views/helloworld/tmpl/default.php
admin/index.html
admin/access.xml
admin/config.xml
admin/controller.php
admin/helloworld.php
admin/controllers/index.html
admin/controllers/helloworld.php
admin/controllers/helloworlds.php
admin/helpers/index.html
admin/helpers/helloworld.php
admin/language/index.html
admin/language/en-GB/index.html
admin/language/en-GB/en-GB.com_helloworld.ini
admin/language/en-GB/en-GB.com_helloworld.sys.ini
admin/language/ru-RU/index.html
admin/language/ru-RU/ru-RU.com_helloworld.ini
admin/language/ru-RU/ru-RU.com_helloworld.sys.ini
admin/models/index.html
admin/models/helloworld.php
admin/models/helloworlds.php
admin/models/fields/index.html
admin/models/fields/helloworld.php
admin/models/forms/index.html
admin/models/forms/helloworld.js
admin/models/forms/helloworld.xml
admin/models/rules/index.html
admin/models/rules/greeting.php
admin/sql/index.html
admin/sql/install.mysql.utf8.sql
admin/sql/uninstall.mysql.utf8.sql
admin/sql/updates/index.html
admin/sql/updates/mysql/index.html
admin/sql/updates/mysql/0.0.1.sql
admin/sql/updates/mysql/0.0.4.sql
admin/sql/updates/mysql/0.0.9.sql
admin/sql/updates/mysql/0.0.10.sql
admin/sql/updates/mysql/0.0.12.sql
admin/tables/index.html
admin/tables/helloworld.php
admin/views/index.html
admin/views/helloworld/index.html
admin/views/helloworld/view.html.php
admin/views/helloworld/submitbutton.js
admin/views/helloworld/tmpl/index.html
admin/views/helloworld/tmpl/edit.php
admin/views/helloworlds/index.html
admin/views/helloworlds/view.html.php
admin/views/helloworlds/tmpl/index.html
admin/views/helloworlds/tmpl/default.php
admin/views/helloworlds/tmpl/default_body.php
admin/views/helloworlds/tmpl/default_foot.php
admin/views/helloworlds/tmpl/default_head.php
language/index.html
language/en-GB/index.html
language/en-GB/en-GB.com_helloworld.sys.ini
language/ru-RU/index.html
language/ru-RU/ru-RU.com_helloworld.sys.ini
media/index.html
media/images/index.html
media/images/hello-16x16.png
media/images/hello-48x48.png

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

В следующей части мы рассмотрим добавление скриптов установки/удаления/обновления и добавление сервера обновлений.

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

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

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

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