Часть 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