Все статьи цикла:

  1. Разработка расширения для Joomla! 3.0 – подготовка
  2. Разработка расширения для Joomla! 3.0 – начинаем разработку
  3. Разработка расширения для Joomla! 3.0 – создаем ядро
  4. Разработка расширения для Joomla! 3.0 – больше функционала
  5. Текущая статья

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

Шаг 1: Создаем интерфейс администратора

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

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

Мы пройдемся по каждому из них, а начнем с точки входа компонента.

administrator/components/com_lendr/lendr.php

<?php // No direct access
defined( '_JEXEC' ) or die( 'Restricted access' );
 
//load classes
JLoader::registerPrefix('Lendr', JPATH_COMPONENT_ADMINISTRATOR);
 
//Load plugins
JPluginHelper::importPlugin('lendr');
 
//application
$app = JFactory::getApplication();
 
// Require specific controller if requested
$controller = $app->input->get('controller','display');
// Create the controller
 
$classname  = 'LendrControllers'.ucwords($controller);
$controller = new $classname();
 
// Perform the Request task
$controller->execute();



Этот код очень похож на код из точки входа фронт-энда и в основном служит для перенаправления трафика на нужный контроллер.

administrator/components/com_lendr/controllers/display.php

<?php defined( '_JEXEC' ) or die( 'Restricted access' ); 
 
class LendrControllersDisplay extends JControllerBase
{
  public function execute()
  {
    // Get the application
    $app = $this->getApplication();
 
    // Get the document object.
    $document     = JFactory::getDocument();
 
    $viewName     = $app->input->getWord('view', 'statistics');
    $viewFormat   = $document->getType();
    $layoutName   = $app->input->getWord('layout', 'default');
    $app->input->set('view', $viewName);
 
    // Register the layout paths for the view
    $paths = new SplPriorityQueue;
    $paths->insert(JPATH_COMPONENT . '/views/' . $viewName . '/tmpl', 'normal');
 
    $viewClass  = 'LendrViews' . ucfirst($viewName) . ucfirst($viewFormat);
    $modelClass = 'LendrModels' . ucfirst($viewName);
    $view = new $viewClass(new $modelClass, $paths);
    $view->setLayout($layoutName);
 
    // Render our view.
    echo $view->render();
 
    return true;
  }
}


Вы должны заметить схожесть между этим контроллером и дефолтным контроллером фронт-энда. В них мы выполняем одинаковые задачи, но здесь мы устанавливаем другое представление по умолчанию.

Далее рассмотрим файл html.php, который расположен в папке представления по умолчанию (это представление statistics).

administrator/components/com_lendr/views/statistics/html.php

<?php defined( '_JEXEC' ) or die( 'Restricted access' ); 
 
class LendrViewsStatisticsHtml extends JViewHtml
{
  function render()
  {
    $app = JFactory::getApplication();
 
    //retrieve task list from model
    $model = new LendrModelsStatistics();
    $this->stats = $model->getStats();
    $this->addToolbar();
 
    //display
    return parent::render();
  } 
    /**
     * Add the page title and toolbar.
     * 
     */
    protected function addToolbar()
    {
        $canDo  = LendrHelpersLendr::getActions();
        // Get the toolbar object instance
 
        $bar = JToolBar::getInstance('toolbar');
        JToolbarHelper::title(JText::_('COM_LENDR_STATISTICS'));
 
        if ($canDo->get('core.admin'))
        {
            JToolbarHelper::preferences('com_lendr');
        }
    }
}


Первое, на что хочется обратить внимание - мы расширяем класс JViewHtml. Это предоставляет нам базовый функционал.

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

Функция addToolbar() обычно используется для добавления элементов на верхнюю панель инструментов. Так как мы делаем простую панель администратора с ограниченным функционалом, у нас будет всего один элемент на панели инструментов. Итак, сначала мы получаем экземпляр стандартного toolbar-класса, а затем назначаем заголовок и кнопки этому экземпляру. Обратите внимание на то, что мы используем языковые константы.

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

Еще раз обратите внимание на то, что мы не подключаем файл напрямую. За нас это делает авто-загрзучик Joomla основываясь на соглашении по именованию файлов/классов. Мы определили это в lendr.php.

administrator/components/com_lendr/helpers/lendr.php

<?php
/**
 * @package     Joomla.Administrator
 * @subpackage  com_lendr
 *
 * @copyright   Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
defined('_JEXEC') or die;
/**
 * Lendr component helper.
 *
 * @package     Joomla.Administrator
 * @subpackage  com_lendr
 */
class LendrHelpersLendr
{
  public static $extension = 'com_lendr';
 
  /**
   * @return  JObject
   */
  public static function getActions()
  {
    $user = JFactory::getUser();
    $result = new JObject;
    $assetName = 'com_lendr';
    $level = 'component';
    $actions = JAccess::getActions('com_lendr', $level);
 
    foreach ($actions as $action)
    {
      $result->set($action->name, $user->authorise($action->name, $assetName));
    }
 
    return $result;
  }
}


В хелпере у нас всего одна функция getActions(), которая обращается к файлу access.xml, расположенному в корне папки компонента, и загружает в объект значения, найденные в XML.

Давайте посмотрим на файл access.xml

administrator/components/com_lendr/access.xml

<?xml version="1.0" encoding="utf-8"?>
<access component="com_lendr">
  <section name="component">
    <action name="core.admin" title="JACTION_ADMIN" description="JACTION_ADMIN_COMPONENT_DESC" />
  </section>
</access>


В этом файле мы установили администраторские привилегии. Мы проверяем их в методе addToolbar(), и если у пользователя есть эти привелегии, мы отображаем ему кнопку настроек компонента.

Подробнее об этом файле можно прочитать в статье

Настройки компонента

Настройки нашего компонента можно найти через кнопку на панели администратора. Так же как и в других компонентах, при клике на эту кнопку мы будем перенаправлены на компонент com_config и представление соответствующего компонента. Данные для этого представления берутся из одного конкретного файла - config.xml. Он также расположен в корне папки компонента.

administrator/components/com_lendr/config.xml

<?xml version="1.0" encoding="utf-8"?>
<config>
  <fieldset name="component"
    label="COM_LENDR_OPTIONS"
    description="COM_LENDR_OPTIONS_DESC"
  >
    <field name="required_account" type="radio"
      default="0"
     
      label="COM_LENDR_REQUIRED_ACCOUNT"
      description="COM_LENDR_REQUIRED_ACCOUNT_DESC">
      
      <option value="0">JNO</option>
      <option value="1">JYES</option>
    </field>
    <field name="new_reviews" type="radio"
      default="1"
     
      label="COM_LENDR_ALLOW_NEW_REVIEWS"
      description="COM_LENDR_ALLOW_NEW_REVIEWS_DESC">
      
      <option value="0">JNO</option>
      <option value="1">JYES</option>
    </field>
    <field name="new_wishlists" type="radio"
      default="1"
     
      label="COM_LENDR_ALLOW_NEW_WISHLISTS"
      description="COM_LENDR_ALLOW_NEW_WISHLISTS_DESC">
      
      <option value="0">JNO</option>
      <option value="1">JYES</option>
    </field>
    <field name="new_waitlists" type="radio"
      default="1"
     
      label="COM_LENDR_ALLOW_NEW_WAITLISTS"
      description="COM_LENDR_ALLOW_NEW_WAITLISTS_DESC">
      
      <option value="0">JNO</option>
      <option value="1">JYES</option>
    </field>
  </fieldset>
  <fieldset name="permissions"
    description="JCONFIG_PERMISSIONS_DESC"
    label="JCONFIG_PERMISSIONS_LABEL"
  >
    <field name="rules" type="rules"
      component="com_lendr"
      filter="rules"
      validate="rules"
      label="JCONFIG_PERMISSIONS_LABEL"
      section="component" />
  </fieldset>
</config>


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

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

components/com_lendr/controllers/default.php

// Line 11 - 19
$params = JComponentHelper::getParams('com_lendr');
if ($params->get('required_account') == 1) 
{
    $user = JFactory::getUser();
    if ($user->get('guest'))
    {
        $app->redirect('index.php',JText::_('COM_LENDR_ACCOUNT_REQUIRED_MSG'));
    }
}


Сначала мы получаем объект параметров, используя Joomla Component Helper. Как только мы получили эти параметры, мы можем проверить, нужно ли пользователю быть залогиненым.

Давайте посмотрим, как еще мы используем параметры из настроек компонента.

components/com_lendr/views/book/tmpl/_entry.php

// Line 43 - 48
<?php if($this->params->get('new_wishlists') == 1 ): ?>
  <li><a href="javascript:void(0);" on-click="addToWishlist('<?php echo $this->book->book_id; ?>');"><?php echo JText::_('COM_LENDR_ADD_WISHLIST'); ?></a></li>
<?php endif; ?>
<?php if($this->params->get('new_reviews') == 1 ): ?>
  <li><a href="#newReviewModal" data-toggle="modal"><?php echo JText::_('COM_LENDR_WRITE_REVIEW'); ?></a></li>
<?php endif; ?>


Здесь мы следуем тому же принципу, что и в контроллере. Единственное, мы вызываем Joomla Component Helper для установки объекта параметров в представлении html.php и обращаемся к нему через $this->params.

Шаг 2: Доработки кода

Мы рассмотрим два аспекта доработки. Первое, мы должны добавить возможность удалять объекты и второе, мы должны иметь список книг для просмотра всех книг в системе.

Удаления

Удаление объектов может осуществляться несколькими способами. В одном случае мы не будем удалять данные из базы данных, а в другом мы будем удалять их полностью. Очень часто просто требуется спрятать информацию, а не удалять данные с сайта. Самым простым и эффективным способом для этого будет установка значения переменной published в 0 (ноль). В некоторых других компонентах значение 0 не означает удаление, и вы можете встретить –1.

components/com_lendr/controllers/delete.php

<?php defined( '_JEXEC' ) or die( 'Restricted access' ); 
 
class LendrControllersDelete extends JControllerBase
{
  public function execute()
  {
    $app = JFactory::getApplication();
    $return = array("success"=>false);
    
    $type = $app->input->get('type','waitlist');
 
    $modelName = 'LendrModels'.ucfirst($type);    
    $model = new $modelName();
 
    if ( $row = $model->delete() )
    {
      $return['success'] = true;
      $return['msg'] = JText::_('COM_LENDR_BOOK_DELETE_SUCCESS');
    } else {
      $return['msg'] = JText::_('COM_LENDR_BOOK_DELETE_FAILURE');
    }
 
    echo json_encode($return);
  }
}


В нашем контроллере удаления у нас есть функция, которая передает данные в соответствующую модель для обработки. Мы устанавливаем её основываясь на типе, переданном через переменную в JInput.

components/com_lendr/models/book.php

/**
  * Delete a book
  * @param int      ID of the book to delete
  * @return boolean True if successfully deleted
  */
  public function delete($id = null)
  {
    $app  = JFactory::getApplication();
    $id   = $id ? $id : $app->input->get('book_id');
    $book = JTable::getInstance('Book','Table');
    $book->load($id);
    $book->published = 0;
 
    if($book->store()) 
    {
      return true;
    } else {
      return false;
    }
  }


Код в модели довольно прост. Мы находим id книги, которую хотим удалить, затем получаем экземпляр таблицы Book и загружаем соответствующую строку. Как только строка загружена, мы легко можем установить статус published в 0 и затем сохранить результат. Если строка сохранена успешно, мы возвращаем значение true, в противном случае false.

Кроме того, удаление книги необходимо сделать и в модели waitlist.

components/com_lendr/models/waitlist.php

/**
  * Delete a book from a waitlist
  * @param int      ID of the book to delete
  * @return boolean True if successfully deleted
  */
  public function delete($id = null)
  {
    $app  = JFactory::getApplication();
    $id   = $id ? $id : $app->input->get('waitlist_id');
 
    if (!$id)
    {
      if ($book_id = $app->input->get('book_id')) 
      {
        $db = JFactory::getDbo();
        $user = JFactory::getUser();
        $query = $db->getQuery(true);
        $query->delete()
            ->from('#__lendr_waitlists')
            ->where('user_id = ' . $user->id)
            ->where('book_id = ' . $book_id);
        $db->setQuery($query);
        if($db->query()) {
          return true;
        }
      } 
    } else {
      $waitlist = JTable::getInstance('Waitlist','Table');
      $waitlist->load($id);
 
      if ($waitlist->delete()) 
      {
        return true;
      }      
    }
 
    return false;
  }


В этом примере мы жестко удаляем строку в таблице базы данных.

Если у нас есть конкретный waitlist ID, мы можем загрузить нужный объект и удалить его напрямую. Если у нас нет waitlist ID, мы можем выполнить поиск необходимой нам строки, используя book ID и user ID персоны и потом удалить соответствующую строку.

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

components/com_lendr/assets/js/lendr.js

function deleteBook(book_id,type) 
{
  jQuery.ajax({
    url:'index.php?option=com_lendr&controller=delete&format=raw&tmpl=component',
    type:'POST',
    data: 'book_id='+book_id+'&type='+type,
    dataType: 'JSON',
    success:function(data)
    {
      alert(data.msg);
      if(data.success)
      {
        jQuery("tr#bookRow"+book_id).hide();
      }
    }
  });
}


Здесь мы передаем book_id и type – тип важен для удаления в контроллере (так мы перенаправляем запрос на нужную модель). Потом мы отображаем результирующее сообщение, генерируемое контроллером удаления, и если удаление прошло успешно, мы удаляем соответствующую строку из списка.

Представление списка книг

Список книг, это ешё одна задача, которую необходимо доработать. Она довольно простая и не требует много кода. Сначала мы должны изменить представление books. Вот что необходимо сделать:

components/com_lendr/views/book/html.php

// Lines 8 - 19
$layout = $this->getLayout();
$this->params = JComponentHelper::getParams('com_lendr');
 
//retrieve task list from model
$model = new LendrModelsBook();
if($layout == 'list')
{
  $this->books = $model->listItems();
  $this->_bookListView = LendrHelpersView::load('Book','_entry','phtml');
} else {
…


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

Далее добавим шаблон списка книг:

components/com_lendr/views/book/tmpl/list.php

<table cellpadding="0" cellspacing="0" width="100%">
  <thead>
    <tr>
      <th><?php echo JText::_('COM_LENDR_DETAILS'); ?></th>
      <th><?php echo JText::_('COM_LENDR_STATUS'); ?></th>
      <th><?php echo JText::_('COM_LENDR_ACTIONS'); ?></th>
    </tr>
  </thead>
  <tbody id="book-list">
    <?php for($i=0, $n = count($this->books);$i<$n;$i++) { 
            $this->_bookListView->book = $this->books[$i];
            $this->_bookListView->type = 'book';
            echo $this->_bookListView->render();
    } ?>
  </tbody>
</table>


Вы должны заметить сходство между этим шаблоном и шаблоном библиотеки. Единственным различием является то, что мы загружаем все книги, а не книги конкретной библиотеки.

Шаг 3: Меню и чистка файлов

Создание ссылок меню

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

  • ссылка на список профилей
  • ссылка на список всех книг

Ссылки меню основываются на соответствующих XML-файлах метаданных, которые расположены в папках представлений. Ниже пример такого файла для списка профилей.

joomla_root/components/com_lendr/views/profile/tmpl/list.xml

<?xml version="1.0" encoding="utf-8"?>
<metadata>
  <layout title="COM_LENDR_PROFILE_LIST">
    <message><![CDATA[COM_LENDR_PROFILE_LIST_DESC]]></message>
  </layout>
</metadata>


Имя файла должно совпадать с именем файла шаблона внутри папки представления.

Заметьте, что при создании ссылки на файл шаблона, мы определяем объект <layout>, а если мы создаем ссылку на представление, то мы создаем объект <view>. Языковые файлы должны быть сохранены в системном языковом файле администраторской части.

administrator/languages/en-GB/en-GB.com_lendr.sys.ini

COM_LENDR = "Lendr"
COM_LENDR_SETTINGS = "Lendr Settings"
COM_LENDR_PROFILE_LIST = "Profile List"
COM_LENDR_PROFILE_LIST_DESC = "Display list of all profiles"
COM_LENDR_BOOK_LIST = "Book List"
COM_LENDR_BOOK_LIST_DESC = "Display list of all books"


Эти строки используются тогда, когда мы обращаемся к ним извне компонента Lendr. Это означает, что эти строки всегда загружаются самой Joomla и не только тогда, когда мы находимся внутри index.php?option=com_lendr. Одним из таких мест является модальное окно создания ссылки меню.

Добавляя XML-файл метаданных в нужную нам папку, мы можем видеть этот шаблон в разделе меню. Этот файл может содержать различные расширенные настройки для добавления дополнительных параметров (например, возможность создавать конкретную книгу или профиль),

Удаляем ненужные файлы и функции

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

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

Шаг 4: Дополнительные возможности

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

Тэги

Тэги были добавлены в Joomla 3.1. С помощью них вы сможете назначать тэги книгам и группировать их по тэгам.

Категории

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

Web сервисы / JSON

Используя базовою модель веб-сервисов мы можем продемонстрировать методы для получения данных непосредственно из Lendr компонента без использования стандартных представлений и шаблонов. Получайте данные в формате JSON для использования их в других системах.

Следующая статья: обслуживание релизов

Эта статья является последней в серии разработки расширения для Joomla 3.0, однако в следующей свое статье мы обсудим некоторые идеи по обслуживанию релизов и поддержке компонента. Мы рассмотрим GitHub, соглашения по нумерации версий, планирование релизов и другое.

Код компонента на GitHub