9. Components Part III: Example Component Back End

In the previous two chapters, we looked at the Weblinks component to understand how components and the model-view-controller (MVC) design pattern work in the Joomla! core. In this chapter, we apply what we have learned and create the back-end code for our example component. We have tried to keep the example component as simple as possible while still using most of the important features we have discussed.


Note

The code for the example component may be downloaded from http://www.joomlaprogrammingbook.com. You can download and install the code before reading this chapter, or you can create the component as we go along and only use the downloaded code to check your work. The second option is more work but will likely help you understand the code better.


Example Component Functional Overview

Let’s first review the functional design of our component. We’ll call the component JoomPro Subscriptions. Its purpose is to allow the site administrator to create subscriptions and to allow site users to subscribe to the subscriptions. Each subscription has a description, a category, and a duration (in days). Each subscription is also associated with a normal Joomla user group (as set up in Users → Groups).

When a user subscribes to the subscription, he or she is added to this group automatically. In addition, the start and end dates for this user’s subscription are stored in a database table.

In this example, we are not covering what happens after a user subscribes to a subscription. Given that they are added to a group, one possibility might be that they are given access to a restricted part of the website. Also, the only thing we will require for a user to subscribe is for them to agree to the terms of service. If you understand this simple example, it should not be difficult to see how you might add features such as requiring payment.

We show almost all the code for the component in code listings in this chapter. You can download the entire component as a zip archive from the book’s website, (joomlaprogrammingbook.com).

Detailed Design

To implement our design, we will need back-end manager screens to manage the subscriptions and the subscription categories. We will also need a form to add and edit subscriptions. A subscription will have the following fields: title, description, published state, category id, user group id, duration (days), and access id. We will use an integer id field for the primary key for the subscription.

We will also need a table to store the information for each user’s subscription. This will need to have the user id, subscription id, and the start and end dates for the subscription.

On the front end, we will have a view of subscriptions by category. If a user is authorized to edit our subscription (using the standard access control list [ACL]), they will be able to click on the subscription and subscribe to it. If successful, a “thank-you” screen will show.

Let’s summarize the design in terms of the back-end and front-end views. In the back end we will have the following:

• Subscription Manager: Subscriptions screen (called submanager)

• Subscription Manager: Categories screen (handled by the com_categories component)

• Subscription Manager: Add/Edit screen (called subscription)

In the front end, we will have the following views:

• Show subscriptions by category view (called category).

• Subscribe to a subscription (called form).

We will need to add two database tables, as follows:

• Table to hold the list of subscriptions available (called #__joompro_subscriptions)

• Table to hold the information for each user’s subscriptions (called #__joompro_sub_mapping)

At this point, we have enough information about our component to start coding. Note that there is no “right” order in which to code a component. One way is to start with the database tables and then work back to the models, controllers, and views. Here we will follow the program flow. We will first look at the programs called when we use the back-end manager screen. Then we will look at the back-end add/edit programs. In the next chapter, we go to the front end and look at the category list programs and finally the subscribe programs.

Back-End Files

Table 9.1 shows the back-end files required for our component. This excludes index.html files, which should be present in each folder. The paths are relative to the component’s base folder, administrator/components/com_joomprosubs.

Table 9.1. Back-End Subscriptions Component Folders

Image

The first thing we need to do in the back end is to create a folder called administrator/components/com_joomprosubs. Because all the back-end component files are in that folder, we will reference component files relative to this folder in the rest of this section.

In the component back-end folder, we will create the entry point for our component’s back end, called joomprosubs.php. Recall that Joomla automatically loads this file when we have a back-end URL with “option=com_joomprosubs”.

The code for this file is shown here:

<?php
/**
 * @package    Joomla.Site
 * @subpackage com_joomprosubs
 * @copyright  Copyright (C) 2012 Mark Dexter and Louis Landry. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

// no direct access
defined('_JEXEC') or die;

// Access check.
if (!JFactory::getUser()->authorise('core.manage', 'com_joomprosubs')) {
       return JError::raiseWarning(404, JText::_('JERROR_ALERTNOAUTHOR'));
}

// Include dependencies
jimport('joomla.application.component.controller'),

$controller = JController::getInstance('JoomproSubs'),
$controller->execute(JRequest::getCmd('task'));
$controller->redirect();

This code should be familiar. It is identical to the administrator/components/com_weblinks/weblinks.php file we looked at in Chapter 7, except that we check that the user is authorized for the com_joomprosubs component and we get the controller for “JoomproSubs”.

Subscriptions Manager: Subscriptions Screen

This screen will show the list of subscriptions that the administrator has created. A screenshot is shown in Figure 9.1. It will be similar to the Weblinks Manager and other core manager screens. Note that this screen has all the normal features of a core Joomla component manager screen. These include the following:

• A toolbar with the normal tasks (New, Edit, and so on)

• A filter box for filtering by subscription title

Image

Figure 9.1. Subscriptions Manager screen

• Filter drop-downs for filtering on published status, category, and access

• Check boxes to allow the toolbars to operate on multiple items

• Column headings that allow sorting by that column

• Pagination controls

The following files are used to create this:

• controller.php (JoomproSubsController)

• views/submanager/view.html.php (JoomproSubsViewSubManager)

• helpers/joomprosubs.php (JoomproSubsHelper)

• models/submanager.php (JoomproSubsModelSubManager)

• sql/install.mysql.utf8.sql

• views/submanager/tmpl/default.php

Default Controller

When we load the manager screen, recall from Chapter 7 that the first class loaded is the default controller with the default task of display. In our case, the default controller is JoomproSubsController. The code for this class is shown in Listing 9.1.

Listing 9.1. JoomproSubsController Class (controller.php)


<?php
/**
 * @package      Joomla.Administrator
 * @subpackage   com_joomprosubs
 * @copyright    Copyright (C) 2012 Mark Dexter and Louis Landry. All rights reserved.
 * @license      GNU General Public License version 2 or later; see LICENSE.txt
 */

// No direct access
defined('_JEXEC') or die;

/**
 *Joomprosubs joomprosub Controller
 *
 * @package      Joomla.Administrator
 * @subpackage   com_joomprosubs
 */
class JoomproSubsController extends JController
{
   /**
   * @var    string  The default view.
   * @since  2.5
   */
   protected $default_view = 'submanager';

   /**
    * Method to display a view.
    *
    * @param   boolean     $cachable   If true, the view output will be cached
    * @param   array       $urlparams  An array of safe url parameters and their variable types, for valid values see {@link JFilterInput::clean()}.
    *
    * @return  JController This object to support chaining.
    */
   public function display($cachable = false, $urlparams = false)
   {
      JLoader::register('JoomproSubsHelper', JPATH_COMPONENT.'/helpers/joomprosubs.php'),

      // Load the submenu.
      JoomproSubsHelper::addSubmenu(JRequest::getCmd('view', 'submanager'));

      $view = JRequest::getCmd('view', 'submanager'),
      $layout = JRequest::getCmd('layout', 'default'),
      $id = JRequest::getInt('id'),

      // Check for edit form.
      if ($view == 'subscription' && $layout == 'edit' && !$this->checkEditId('com_joomprosubs.edit.subscription', $id)) {
         // Somehow the person just went to the form - we don't allow that.
         $this->setError(JText::sprintf('JLIB_APPLICATION_ERROR_UNHELD_ID', $id));
         $this->setMessage($this->getError(), 'error'),
         $this->setRedirect(JRoute::_( 'index.php?option=com_joomprosubs&view=submanager', false));
         return false;
      }

      parent::display();

      return $this;
   }
}


This code is almost identical to the WeblinksController class we discussed in Chapter 7. It extends the JController class. Note that we include a protected field called $default_view, set to “submanager”. This wasn’t defined in the Weblinks controller. This field is where we name the default view for the back end of the component. In our example, this is “submanager”. The reason we didn’t need to specify this in Weblinks is that Joomla supplies a default value equal to the component name (without the “com_”). That gave us a value of “weblinks” for com_weblinks, which was correct. However, the default would give us a value of “joomprosubs” in this case, which is not what we want. So we define our default view using $default_view field.

The JoomproSubsController class has one method, called display(). This method includes our helper file. Note that we use the JLoader::register() method instead of require_once. Recall that JLoader::register() executes more quickly and is preferred whenever we need to load a class. We use the constant JPATH_COMPONENT to point to the component folder (in our case, administrator/components/com_joomprosubs) and then we invoke the addSubmenu() method of the helper class.

We then get the view, layout, and subscription id from the request. We check to make sure that the user has not tried to go directly to the edit form. Finally, we call the parent’s (JController) display() method, which gets the view and executes it.

Submanager Controller and Toolbar Tasks

The default controller handles the display task, part of which is to display the toolbar (as shown in Figure 9.1). The first two toolbar icons are New and Edit. Those are handled by the subscription controller, which we discuss later in this chapter. The other tasks on the toolbar are publish, unpublish, archive, check in, trash, options, and help.

Recall from Chapter 7 that the publish, unpublish, archive, and trash tasks all map to the publish() method in the controller. The checkin task maps to the checkin() method. Also, recall that the WeblinksControllerWeblinks class only had one method, called getModel(). This is because it inherits the publish() and checkin() methods from its parent class, JControllerAdmin.

The same thing is true for the JoomproSubsControllerSubManager controller (controllers/submanager.php). It only needs one method—getModel(), as shown in Listing 9.2.

Listing 9.2. JoomproSubsControllerSubManager Class


<?php
/**
 * @copyright   Copyright (C) 2012 Mark Dexter and Louis Landry. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// No direct access.
defined('_JEXEC') or die;

jimport('joomla.application.component.controlleradmin'),

/**
 * Joomprosubs list controller class.
 *
 * @since      2.5
 */
class JoomproSubsControllerSubManager extends JControllerAdmin
{
   /**
    * Proxy for getModel.
    */
   public function getModel($name = 'Subscription', $prefix = 'JoomproSubsModel', $config = array('ignore_request' => true))
   {
      $model = parent::getModel($name, $prefix, $config);
      return $model;
   }
} // end of class


This method allows the controller to get the correct model—in this case, JoomproSubsModelSubscription. It inherits the publish() and checkin() methods from its parent class, JControllerAdmin. So we just use the methods for these tasks with no modification and no extra work.

The other toolbar icons are options and help. Recall that the options icon calls the com_config component using the config.xml file. The contents of config.xml are as follows:

<?xml version="1.0" encoding="utf-8"?>
<config>
   <fieldset name="permissions"
      description="JCONFIG_PERMISSIONS_DESC"
      label="JCONFIG_PERMISSIONS_LABEL"
   >
      <field name="rules" type="rules"
         component="COM_JOOMPROSUBS"
         filter="rules"
         validate="rules"
         label="JCONFIG_PERMISSIONS_LABEL"
         section="component" />
    </fieldset>
</config>

If we had component-level options, we would specify them here and they would show in the Options screen. In our example, we don’t have any options. We only have the permissions. We specify this using a rules field.

We also need to specify the actions for our component by creating a file called access.xml. The contents of the file for our component are as follows:

<?xml version="1.0" encoding="utf-8"?>
<access component="com_joomprosubs">
   <section name="component">
     <action name="core.admin" title="JACTION_ADMIN" description="JACTION_ADMIN_COMPONENT_DESC" />
     <action name="core.manage" title="JACTION_MANAGE" description="JACTION_MANAGE_COMPONENT_DESC" />
     <action name="core.create" title="JACTION_CREATE" description="JACTION_CREATE_COMPONENT_DESC" />
     <action name="core.delete" title="JACTION_DELETE" description="JACTION_DELETE_COMPONENT_DESC" />
     <action name="core.edit" title="JACTION_EDIT" description="JACTION_EDIT_COMPONENT_DESC" />
     <action name="core.edit.state" title="JACTION_EDITSTATE" description="JACTION_EDITSTATE_COMPONENT_DESC" />
     <action name="core.edit.own" title="JACTION_EDITOWN" description="JACTION_EDITOWN_COMPONENT_DESC" />
  </section>
  <section name="category">
     <action name="core.create" title="JACTION_CREATE" description="COM_CATEGORIES_ACCESS_CREATE_DESC" />
     <action name="core.delete" title="JACTION_DELETE" description="COM_CATEGORIES_ACCESS_DELETE_DESC" />
     <action name="core.edit" title="JACTION_EDIT" description="COM_CATEGORIES_ACCESS_EDIT_DESC" />
     <action name="core.edit.state" title="JACTION_EDITSTATE" description="COM_CATEGORIES_ACCESS_EDITSTATE_DESC" />
     <action name="core.edit.own" title="JACTION_EDITOWN" description="COM_CATEGORIES_ACCESS_EDITOWN_DESC" />
  </section>
</access>

Because we use the same actions as Weblinks at both the component and category levels, this file is identical to the Weblinks access.xml file except for the component name on the second line.

Once we finish coding our component, we will be able to click the Options icon to see the permissions screen shown in Figure 9.2.

Image

Figure 9.2. Options screen

Manager View

The view for the manager screen is named JoomproSubsViewSubManager, which follows the naming convention discussed in Chapter 7. The third part of the name (“SubManager”) is just a description of the view. We don’t need to worry about another component having this name because we have already made the name unique to our component with the first segment (“JoomproSubs”). This class is in the file views/submanager/view.html.php, again following the same conventions as the core components.

The code for the first part of this class is as follows:

defined('_JEXEC') or die;

jimport('joomla.application.component.view'),

/**
 * View class for a list of subscriptions.
 *
*/
class JoomproSubsViewSubmanager extends JView
{
  protected $items;
  protected $pagination;
  protected $state;

  /**
   * Display the view
   */
  public function display($tpl = null)
  {
     $this->state = $this->get('State'),
     $this->items = $this->get('Items'),
     $this->pagination = $this->get('Pagination'),

     // Check for errors.
     if (count($errors = $this->get('Errors'))) {
        JError::raiseError(500, implode(" ", $errors));
        return false;
     }

     $this->addToolbar();
     parent::display($tpl);
   }

Again, this code is almost identical to the WeblinksViewWeblinks class, except of course we refer to the joomprosubs component name instead of Weblinks.

The class extends JView, and we have class fields for items, pagination, and state. The only public method for this class is display(). This method gets the state, items, and pagination from the model, checks for errors, adds the toolbar, and then calls the display() method of JView (its parent).

The only other method in this class is addToolbar(), shown here:

/**
 * Add the page title and toolbar.
 *
 * @since 2.5
 */
protected function addToolbar()
{
   JLoader::register('JoomproSubsHelper', JPATH_COMPONENT.'/helpers/joomprosubs.php'),

   $state  = $this->get('State'),
   $canDo  = JoomprosubsHelper::getActions($state->get('filter.category_id'));
   $user   = JFactory::getUser();

   JToolBarHelper::title(JText::_('COM_JOOMPROSUBS_MANAGER_JOOMPROSUBS'), 'newsfeeds.png'),
   if (count($user->getAuthorisedCategories('com_joomprosubs', 'core.create')) > 0) {
      JToolBarHelper::addNew('subscription.add','JTOOLBAR_NEW'),
  }
   if ($canDo->get('core.edit')) {
      JToolBarHelper::editList('subscription.edit','JTOOLBAR_EDIT'),
   }

   if ($canDo->get('core.edit.state')) {

      JToolBarHelper::divider();
      JToolBarHelper::publish('submanager.publish', 'JTOOLBAR_PUBLISH', true);
      JToolBarHelper::unpublish('submanager.unpublish', 'JTOOLBAR_UNPUBLISH', true);

      JToolBarHelper::divider();
      JToolBarHelper::archiveList('submanager.archive'),
      JToolBarHelper::checkin('submanager.checkin'),
     }

   if ($state->get('filter.state') == -2 && $canDo->get('core.delete')) {
      JToolBarHelper::deleteList('', 'submanager.delete', 'JTOOLBAR_EMPTY_TRASH'),
      JToolBarHelper::divider();
   } else if ($canDo->get('core.edit.state')) {
      JToolBarHelper::trash('submanager.trash','JTOOLBAR_TRASH'),
      JToolBarHelper::divider();
  }
  if ($canDo->get('core.admin')) {
      JToolBarHelper::preferences('com_joomprosubs'),
      JToolBarHelper::divider();
  }

  JToolBarHelper::help('', '', JText::_('COM_JOOMPROSUBS_SUBMANAGER_HELP_LINK'));
}

Again, this code is almost identical to the corresponding code in Weblinks. We load the helper file and use it to create an object to hold this user’s authorized actions. We then show the toolbar icons if the user’s permissions authorize that action. Again, recall that checking is for the user interface but is not by itself sufficient to protect from an unauthorized user trying to perform an action. We also need to repeat these checks when we are performing each task.

If the user has permissions for the core.admin action, we add the Options toolbar button. Recall that this adds a link to the com_config component with the options specified in the config.xml file.

The last line of code creates the help icon on the toolbar. In this case, we are specifying that the help button will take the user to the URL specified in the language file for the key “COM_JOOMPROSUBS_SUBMANAGER_HELP_LINK”. By making this value language dependent, we make it easy for someone to create a help screen in a different language. The JToolBarHelper::help() method creates the following HTML code in our page:

<a class="toolbar" rel="help" onclick="popupWindow(
'http://joomlaprogrammingbook.com/joompro-subscriptions-help.html', 'Help', 700, 500, 1)" href="#">
<span class="icon-32-help">
</span>
Help
</a>

The URL comes from the translation of our language key “COM_JOOMPROSUBS_SUBMANAGER_HELP_LINK”. When clicked, this opens the link in a pop-up window, as shown in Figure 9.3.

Image

Figure 9.3. Component help screen

We could also point to a local help file if we preferred. To do that, we would specify the file name in the second argument of the method. We could again use JText::_() to allow for different files for different languages.

Helper Class

Note in the previous file we invoked the JoomprosubsHelper::getActions() method from the file helpers/joomprosubs.php. The first part of the code for this file is as follows:

defined('_JEXEC') or die;

/**
 *Joomprosubs helper.
 *
 */
class JoomproSubsHelper
{
  /**
   * Configure the Linkbar.
   *
   * @param string  The name of the active view.
   */
  public static function addSubmenu($vName = 'submanager')
  {
     JSubMenuHelper::addEntry(
        JText::_('COM_JOOMPROSUBS_SUBMENU_JOOMPROSUBS'),
        'index.php?option=com_joomprosubs&view=submanager',
        $vName == 'submanager'
     );
     JSubMenuHelper::addEntry(
        JText::_('COM_JOOMPROSUBS_SUBMENU_CATEGORIES'),
        'index.php?option=com_categories&extension=com_joomprosubs',
        $vName == 'categories'
     );
     if ($vName=='categories') {
        JToolBarHelper::title(JText::sprintf('COM_CATEGORIES_CATEGORIES_TITLE', JText::_('com_joomprosubs')),
          'joomprosubs-categories'),
    }
  }

This method is used to create the submenus for the manager screen. These allow the user to navigate between the Subscriptions and Categories manager screens. It is important to note that the categories screen is created by the com_categories component, so there is no code in our component for this screen. The previously given URL 'index.php?option=com_categories&extension=com_joomprosubs' tells the com_categories component all it needs to know to create a complete management screen for our categories.

The last method in the helper class is as follows:

/**
 * Gets a list of the actions that can be performed.
 *
 * @param  int     The category ID.
 * @return JObject
 */
public static function getActions($categoryId = 0)
{
 $user = JFactory::getUser();
 $result = new JObject;

 if (empty($categoryId)) {
    $assetName = 'com_joomprosubs';
 } else {
    $assetName = 'com_joomprosubs.category.'.(int) $categoryId;
 }

 $actions = array(
   'core.admin', 'core.manage', 'core.create', 'core.edit', 'core.edit.own', 'core.edit.state', 'core.delete'
 );

 foreach ($actions as $action) {
    $result->set($action,  $user->authorise($action, $assetName));
 }

 return $result;
}

This method creates an object that tells us which actions the current user is authorized to perform. The result is used to tell the view which toolbars to show on the manager screen.

Manager Model

In the display() method of the view, we called three methods from the model: getState(), getItems(), and getPagination(). The model for this view is JoomproSubsModelSubManager (models/submanager.php). The first part of this file is as follows:

defined('_JEXEC') or die;

jimport('joomla.application.component.modellist'),

/**
 * Methods supporting a list of joomprosub records.
 *
*/
class JoomproSubsModelSubManager extends JModelList
{

   /**
    * Constructor.
    *
    * @param   array  An optional associative array of configuration settings.
    * @see     JController
    * @since   2.5
    */
   public function __construct($config = array())
   {
     if (empty($config['filter_fields'])) {
       $config['filter_fields'] = array(
          'id', 'a.id',
          'title', 'a.title',
          'alias', 'a.alias',
          'checked_out', 'a.checked_out',
          'checked_out_time', 'a.checked_out_time',
          'catid', 'a.catid', 'category_title',
          'published', 'a. published ',
          'access', 'a.access', 'access_level',
          'created', 'a.created',
          'created_by', 'a.created_by',
          'publish_up', 'a.publish_up',
          'publish_down', 'a.publish_down',
          'group_title', 'g.title',
          'duration', 'a.duration'
        );
     }

     parent::__construct($config);
   }

Image

As with Weblinks, this model extends JModelList. Also, in the constructor we build an array of valid filter fields and then call the parent’s constructor. Recall that the 'filter_fields' array allows us to guard against SQL injection hacks by filtering out invalid column names in the order-by part of the request. If a hacker tries to put some SQL code into the form or URL, it will be removed because it does not match one of the valid values in this array.

The next method in our model is populateState(), as follows:

/**
 * Method to auto-populate the model state.
 *
 * Note. Calling getState in this method will result in recursion.
 *
 */
protected function populateState($ordering = null, $direction = null)
{
  // Initialise variables.
  $app = JFactory::getApplication('administrator'),

  // Load the filter state.
  $search = $this->getUserStateFromRequest($this->context.'.filter.search', 'filter_search'),
  $this->setState('filter.search', $search);

  $accessId = $this->getUserStateFromRequest($this->context.'.filter.access', 'filter_access', null, 'int'),
  $this->setState('filter.access', $accessId);

  $published = $this->getUserStateFromRequest($this->context.'.filter.state', 'filter_published', '', 'string'),
  $this->setState('filter.state', $published);

  $categoryId = $this->getUserStateFromRequest($this->context.'.filter.category_id', 'filter_category_id', ''),
  $this->setState('filter.category_id', $categoryId);

  // Load the parameters.
  $params = JComponentHelper::getParams('com_joomprosubs'),
  $this->setState('params', $params);

  // List state information.
  parent::populateState('a.title', 'asc'),
}

Recall that in our manager we have filters for the search field, the published state, the category, and the access level. This method reads the different state variables from the request and saves them in the state field of the model. It also saves the parameters for the component. Then it calls the parent’s populateState() method. There we add the list limit and limit start values (for when we are paging through the list). We also use the 'filter_fields' array we created in the constructor to validate the ordering.

The next method in the model is getStoreId(), as follows:

/**
 * Method to get a store id based on model configuration state.
 *
 * This is necessary because the model is used by the component and
 * different modules that might need different sets of data or different
 * ordering requirements.
 *
 * @param      string $id     A prefix for the store id.
 * @return     string         A store id.
 */
protected function getStoreId($id = '')
{
    // Compile the store id.
    $id.= ':' . $this->getState('filter.search'),
    $id.= ':' . $this->getState('filter.access'),
    $id.= ':' . $this->getState('filter.state'),
    $id.= ':' . $this->getState('filter.category_id'),

    return parent::getStoreId($id);
}

Recall that this method is used to create a unique key for a cached version of the list of items, the query, the total count, or the pagination object. In our case, the uniqueness of a list of items is determined by the combination of the four possible filters we use plus the start, limit, ordering, and ordering direction (which are added in the parent’s getStoreID() method). These are converted to an md5 hash and saved. Then, if we are showing exactly the same screen that we already have saved in memory, we can save some processing time by just using the saved copy. Note that most of the work for this is done for us by the JModelList class. All we have to do is add a getStoreId() method that includes all the possible filter values.

The last method in our model is getListQuery(), as follows:

/**
 * Build a SQL query to load the list data.
 *
 * @return    JDatabaseQuery
 */
protected function getListQuery()
{
  // Create a new query object.
  $db    = $this->getDbo();
  $query = $db->getQuery(true);

  // Select the required fields from the table.
  $query->select('a.*'),
  $query->from($db->quoteName('#__joompro_subscriptions').' AS a'),

  // Join over the users for the checked out user.
  $query->select('uc.name AS editor'),
  $query->join('LEFT', $db->quoteName('#__users').' AS uc ON uc.id=a.checked_out'),

  // Join over the user groups to get the group name
  $query->select('g.title as group_title'),
  $query->join('LEFT', $db->quoteName('#__usergroups').' AS g ON a.group_id = g.id'),
  // Join over the categories.
  $query->select('c.title AS category_title'),
  $query->join('LEFT', $db->quoteName('#__categories').' AS c ON c.id = a.catid'),

  // Filter by access level.
  if ($access = $this->getState('filter.access')) {
     $query->where('a.access = '.(int) $access);
  }

  // Filter by published state
  $published = $this->getState('filter.state'),
  if (is_numeric($published)) {
     $query->where('a.published = '.(int) $published);
  } else if ($published === '') {
     $query->where('(a.published IN (0, 1))'),
  }

  // Filter by category.
  $categoryId = $this->getState('filter.category_id'),
  if (is_numeric($categoryId)) {
     $query->where('a.catid = '.(int) $categoryId);
  }

  // Filter by search in title
  $search = $this->getState('filter.search'),
  if (!empty($search)) {
     if (stripos($search, 'id:') === 0) {
         $query->where('a.id = '.(int) substr($search, 3));
     } else {
         $search = $db->Quote('%'.$db->getEscaped($search, true).'%'),
         $query->where('(a.title LIKE '.$search.' OR a.alias LIKE '.$search.')'),
     }
  }

  // Add the list ordering clause.
  $orderCol = $this->state->get('list.ordering'),
  $orderDirn = $this->state->get('list.direction'),
  $query->order($db->getEscaped($orderCol.' '.$orderDirn));

  return $query;
}

Here we build the query to retrieve the rows from the database table. We create a new JDatabaseQuery object using $db->getQuery(true). This is important. Starting in Joomla version 1.7, which uses the Platform version 11.1, JDatabaseQuery is an abstract class. This means that you cannot instantiate a new object of this type, so you cannot use new JDatabaseQuery() to create a new query object. Using $db->getQuery() creates a JDatabaseQuery object specific to your database type (for example, JDatabaseQueryMySQLi). This is needed to allow Joomla to work with multiple databases.

Then we select all the columns for our main table (#__joompro_subscriptions) using the SQL construct 'a.*'. We then join the #__users table so we can get the name of the user who has checked out a subscription for editing. We join the #__usergroups and #__categories tables so we can show the titles for the user group and category for the subscriptions.

It is important that we use the prefix “#__” in our queries because we don’t know what table prefix will be in use in the website. Joomla substitutes the correct table prefix before the query is passed to the database.

Also, notice that we put the table names inside the $db->quoteName() method. This allows for the possibility that different databases use different quote characters to escape table and column names. For example, MySQL uses the back-quote “`” for this. We discuss this more in Chapter 11.

All the joins are LEFT joins, which means that the row from the primary table, #__joompro_subscriptions, will show even if there is no matching row in one of these tables. Note that this should never happen in our case, because we should always have a valid category, user, and user group.

Then we check our access, published state, category, and search filters. If any of these are set, we add the appropriate WHERE clause to the database query to limit the query results. Note that we have special code to allow a search by id number. We check to see if the first three characters in the search field match “id:”. If so, we look for an integer and try to find an id that matches. If not, we assume it is a partial text match on the title. Finally, we add the query ordering according to the state variables for ordering and direction.

Database Tables

In the previous section, we did a query on one of our new database tables. Let’s see where these are created. To create the tables automatically when our component is installed, we create a file called sql/install.mysql.utf8.sql. The file is as follows:

CREATE TABLE IF NOT EXISTS `#__joompro_subscriptions` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'Automatic incrementing key field',
  `catid` int(11) NOT NULL DEFAULT '0' COMMENT 'Foreign key to #__categories=table',
  `title` varchar(250) NOT NULL DEFAULT '' COMMENT 'Title of Subscription',
  `alias` varchar(255) NOT NULL DEFAULT '' COMMENT 'Alias value, used for SEF URLs',
  `description` text NOT NULL COMMENT 'Description (will be edited using editor)',
  `group_id` int(11) NOT NULL DEFAULT '0' COMMENT 'Foreign key to #__usergroups  table',
  `duration` int(11) NOT NULL DEFAULT '0' COMMENT 'Number for days that subscription lasts',
  `published` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Published state (1=published, 0=unpublished, -2=trashed)',
  `checked_out` int(11) NOT NULL DEFAULT '0',
  `checked_out_time` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `access` int(11) NOT NULL DEFAULT '1' COMMENT 'Used to control access to subscriptions',
  `params` text NOT NULL COMMENT 'For possible future use to add item-level parameters (JSON string format)',
  `language` char(7) NOT NULL DEFAULT '' COMMENT 'For possible future use to add language switching',
  `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `created_by` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'Foreign key to #__users table for user who created this item',
  `created_by_alias` varchar(255) NOT NULL DEFAULT '',
  `modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `modified_by` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'Foreign key to #__users table for user who modified this item',
  `publish_up` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT 'Date to start publishing this item',
  `publish_down` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT 'Date to stop publishing this item',
  PRIMARY KEY (`id`),
  KEY `idx_access` (`access`),
  KEY `idx_checkout` (`checked_out`),
  KEY `idx_published` (`published`),
  KEY `idx_catid` (`catid`),
  KEY `idx_createdby` (`created_by`),
  KEY `idx_language` (`language`)
  ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

CREATE TABLE IF NOT EXISTS `#__joompro_sub_mapping` (
  `subscription_id` int(11) NOT NULL DEFAULT '0' COMMENT 'Foreign Key to #_joompro_subscriptions.id',
  `user_id` int(11) NOT NULL DEFAULT '0' COMMENT 'Foreign Key to #__users.id',
  `start_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `end_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (`subscription_id`, `user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

This file creates our two new database tables. We will reference this file in our component’s XML file as an installation file to be run automatically when we install our component. There are several things to note about this file:

• It is not a PHP file. It is a SQL script, meaning that it contains SQL commands in plain text.

• It is specific to one database—MySQL. To support installation for a different database, we would supply a separate file for each one.

• It references the tables with the prefix #__. Therefore, it is designed to be run from within Joomla, where the JDatabase object will convert the prefix to the correct one for each Joomla site. (For testing purposes, you can easily run this script against your test database using a database management program such as phpMyAdmin. Just load the file into a text editor and do a search/replace replacing “#__” with the table prefix for your Joomla site.)

We will discuss creating tables in detail in Chapter 11. For now, you can create the table by installing the component from the archive file or by running this script from phpMyAdmin (after changing the table prefix).

If a user uninstalls our component, we don’t want to keep the database tables in the database. So we provide an uninstall file called sql/uninstall.mysql.utf8.sql with the following code:

DROP TABLE IF EXISTS `#__joompro_subscriptions`;
DROP TABLE IF EXISTS `#__joompro_sub_mapping`;

This deletes our two tables from the database. We discuss this code in Chapter 11.

Manager Screen Layout

The last program for displaying the manager screen is the layout, views/submanager/tmpl/default.php. This file, like the others, is closely modeled on the corresponding file for Weblinks (administrator/components/com_weblinks/views/weblinks/tmpl/default.php). The first part of the layout file is as follows:

defined('_JEXEC') or die;

JHtml::addIncludePath(JPATH_COMPONENT.'/helpers/html'),
JHtml::_('behavior.tooltip'),
JHtml::_('script','system/multiselect.js', false, true);

$user = JFactory::getUser();
$userId = $user->get('id'),
$listOrder = $this->escape($this->state->get('list.ordering'));
$listDirn = $this->escape($this->state->get('list.direction'));
?>

This just does some housekeeping and gets the current ordering for the list. The next part of the file is as follows:

<div class="joomprosubs-manager">
<form action="<?php echo JRoute::_('index.php?option=com_joomprosubs&view=submanager'), ?>"
  method="post" name="adminForm" id="adminForm">
  <fieldset id="filter-bar">
     <div class="filter-search fltlft">
        <label class="filter-search-lbl" for="filter_search"><?php echo JText::_('JSEARCH_FILTER_LABEL'), ?></label>
        <input type="text" name="filter_search" id="filter_search" value="<?php echo $this->escape($this->state->get('filter.search')); ?>" title="<?php echo JText::_('COM_JOOMPROSUBS_SEARCH_IN_TITLE'), ?>" />
        <button type="submit"><?php echo JText::_('JSEARCH_FILTER_SUBMIT'), ?></button>
        <button type="button" onclick= "document.id('filter_search').value='';this.form.submit();"> <?php echo JText::_('JSEARCH_FILTER_CLEAR'), ?></button>
     </div>
     <div class="filter-select fltrt">

        <select name="filter_published" class="inputbox" onchange="this.form.submit()">
          <option value=""><?php echo JText::_('JOPTION_SELECT_PUBLISHED'),?></option>
          <?php echo JHtml::_('select.options', JHtml::_('jgrid.publishedOptions'), 'value', 'text', $this->state->get('filter.state'), true);?>
        </select>

        <select name="filter_category_id" class="inputbox" onchange="this.form.submit()">
          <option value=""><?php echo JText::_('JOPTION_SELECT_CATEGORY'),?></option>
          <?php echo JHtml::_('select.options', JHtml::_('category.options', 'com_joomprosubs'), 'value', 'text', $this->state->get('filter.category_id'));?>
        </select>

            <select name="filter_access" class="inputbox" onchange="this.form.submit()">
          <option value=""><?php echo JText::_('JOPTION_SELECT_ACCESS'),?></option>
          <?php echo JHtml::_('select.options', JHtml::_('access.assetgroups'), 'value', 'text', $this->state->get('filter.access'));?>
       </select>

     </div>
  </fieldset>
  <div class="clr"> </div>

This code creates the search and other filters. It is modeled after the same code in Weblinks. Note that we put a div element with a class of “joomprosubs-manager” around the entire layout. This allows a designer to create CSS styling specific to this screen.

The next part of the file is as follows:

<table class="adminlist">
  <thead>
     <tr>
        <th style="width: 1%;">
         <input type="checkbox" name="checkall-toggle" value="" onclick="checkAll(this)" />
        </th>
        <th class="title">
          <?php echo JHtml::_('grid.sort',  'JGLOBAL_TITLE', 'a.title', $listDirn, $listOrder); ?>
       </th>
       <th style="width: 5%;">
          <?php echo JHtml::_('grid.sort',  'JSTATUS', 'a.published', $listDirn, $listOrder); ?>
       </th>

       <th style="width: 20%;">
          <?php echo JHtml::_('grid.sort',  'JCATEGORY', 'category_title', $listDirn, $listOrder); ?>
       </th>

       <th style="width: 20%;">
                            <?php echo JHtml::_('grid.sort', 'COM_JOOMPROSUBS_FIELD_USERGROUP_LABEL', 'g.title', $listDirn, $listOrder); ?>
       </th>

       <th style="width: 10%;">
          <?php echo JHtml::_('grid.sort', 'COM_JOOMPROSUBS_FIELD_DURATION_LABEL', 'a.duration', $listDirn, $listOrder); ?>
       </th>

       <th style="width: 5%;">
          <?php echo JHtml::_('grid.sort',  'JGRID_HEADING_ACCESS', 'a.access', $listDirn, $listOrder); ?>
       </th>

       <th style="width: 5%;" class="nowrap">
          <?php echo JHtml::_('grid.sort',  'JGRID_HEADING_ID', 'a.id', $listDirn, $listOrder); ?>
       </th>
  </tr>
  </thead>

This creates the sortable column headings, just like in the Weblinks Manager screen. The only difference is that we have used the preferred syntax for the in-line styling of the width.

The next part creates the pagination at the bottom of the screen, as follows:

<tfoot>
   <tr>
     <td colspan="10">
       <?php echo $this->pagination->getListFooter(); ?>
     </td>
   </tr>
</tfoot>

The next section starts a foreach loop to process each subscription in the list, as follows:

<tbody>
<?php foreach ($this->items as $i => $item) :
   $ordering = ($listOrder == 'a.ordering'),
   $item->cat_link = JRoute::_('index.php?option=com_categories&extension=com_joomprosubs &task=edit&type=other&cid[]='. $item->catid);
   $canCreate = $user->authorise('core.create', 'com_joomprosubs.category.'.$item->catid);
   $canEdit = $user->authorise('core.edit', 'com_joomprosubs.category.'.$item->catid);
   $canCheckin = $user->authorise('core.manage', 'com_checkin') || $item->checked_out==$user->get('id') || $item->checked_out==0;
   $canChange = $user->authorise('core.edit.state', 'com_joomprosubs.category.'.$item->catid) && $canCheckin;
  ?>

This checks whether the current user has permission to create, edit, check in, and publish each item.

The next section of code displays the information for each subscription in the list, as follows:

  <tr class="row<?php echo $i % 2; ?>">
     <td class="center">
        <?php echo JHtml::_('grid.id', $i, $item->id); ?>
     </td>
     <td>
        <?php if ($item->checked_out) : ?>
        <?php echo JHtml::_('jgrid.checkedout', $i, $item->editor, $item->checked_out_time, 'submanager.', $canCheckin); ?>
        <?php endif; ?>
        <?php if ($canEdit) : ?>
           <a href="<?php echo JRoute::_('index.php?option=com_joomprosubs&task=subscription.edit&id='.(int) $item->id); ?>">
           <?php echo $this->escape($item->title); ?></a>
        <?php else : ?>
           <?php echo $this->escape($item->title); ?>
        <?php endif; ?>
        <p class="smallsub">
        <?php echo JText::sprintf('JGLOBAL_LIST_ALIAS', $this->escape($item->alias));?></p>
     </td>
     <td class="center">
        <?php echo JHtml::_('jgrid.published', $item->published, $i, 'submanager.', $canChange, 'cb', $item->publish_up, $item->publish_down); ?>
     </td>
     <td class="center">
        <?php echo $this->escape($item->category_title); ?>
     </td>

     <td class="center">
        <?php echo $this->escape($item->group_title); ?>
     </td>

     <td class="center">
        <?php echo $this->escape($item->duration); ?>
     </td>

     <td class="center">
        <?php echo $this->escape($item->access); ?>
     </td>
     <td class="center">
        <?php echo (int) $item->id; ?>
     </td>
     </tr>
     <?php endforeach; ?>
  </tbody>
</table>

This code reads each row from the $item object and displays it inside a table cell (td) HTML element.

The last section of code outputs hidden fields for task, boxchecked, filter_order, and filter_order_dir, as follows:

   <div>
     <input type="hidden" name="task" value="" />
     <input type="hidden" name="boxchecked" value="0" />
     <input type="hidden" name="filter_order" value="<?php echo $listOrder; ?>" />
     <input type="hidden" name="filter_order_Dir" value="<?php echo $listDirn; ?>" />
     <?php echo JHtml::_('form.token'), ?>
   </div>
</form>
</div>

It also closes the initial div element we used for possible styling.

This is a lot of code, but it is closely based on similar files for Weblinks and other core components.

At this point, we have all the files we need to do the default task: display the manager screen.

Subscriptions Manager: Add and Edit

Next let’s look at the code for adding and editing a subscription. This uses the following files, which are discussed in this section:

controllers/subscription.php (JoomprosubsControllerSubscription)

views/subscription/view.html.php (JoomprosubsViewSubscription)

models/subscription.php (JoomproSubsModelSubscription)

views/subscription/tmpl/edit.php

models/forms/subscription.xml

tables/subscription.php (JoomprosubsTableSubscription)

Controller Tasks

In the manager screen, the toolbar task for add and edit are subscription.add and subscription.edit. Recall from Chapter 7 that this means we invoke the add() and edit() methods of the controller class JoomprosubsControllerSubscription (controllers/subscription.php). This class extends JControllerForm and only overrides two methods: allowAdd() and allowEdit(). These are called from the add() and edit() methods of JControllerForm to check whether the user is authorized to perform these actions.

The first part of JoomprosubsControllerSubscription defines the allowAdd() method, as follows:

defined('_JEXEC') or die;

jimport('joomla.application.component.controllerform'),

/**
 * Joomprosubs controller class.
 *
 */
class JoomproSubsControllerSubscription extends JControllerForm
{
 /**
  * The URL view list variable.
  *
  * @var    string
  */
 protected $view_list = 'submanager';

 /**
  * Method override to check if you can add a new record.
  *
  * @param     array       $data   An array of input data.
  * @return boolean
  */
 protected function allowAdd($data = array())
 {
    // Initialise variables.
    $user = JFactory::getUser();
    $categoryId = JArrayHelper::getValue($data, 'catid', JRequest::getInt('filter_category_id'), 'int'),
    $allow = null;

    if ($categoryId) {
       // If the category has been passed in the URL check it.
       $allow  = $user->authorise('core.create', $this->option.'.category.'.$categoryId);
    }

    if ($allow === null) {
      // In the absense of better information, revert to the component permissions.
      return parent::allowAdd($data);
    } else {
      return $allow;
    }
 }

Here we define the class and the first method. Note that we define a field called $view_list, set to “submanager”. We didn’t need this field in the WeblinksControllerWeblink class because it defaulted to the component name without the plural (“weblink”). In our case, that rule doesn’t work. If we called the default view “joomprosubs”, we wouldn’t need this field or the $default_view field we defined in the JoomproSubsController class. However, two lines of code is a small price to pay for having meaningful class and file names, and “submanager” is more meaningful than “joomprosubs”.

The allowAdd() method is very similar to the same method in WeblinksControllerWeblink class. We try to read a category id from the request. If we are filtering on a category, there will be one. If we find a category id, we check to see if the current user is authorized to add an item in this category. If not, we just check the permissions at the component level, which we do by calling the parent’s allowAdd() method.

The rest of the code for this class defines the allowEdit() method, as follows:

   /**
    * Method to check if you can add a new record.
    *
    * @param   array   $data  An array of input data.
    * @param   string  $key   The name of the key for the primary key.
    *
    * @return  boolean
    */
   protected function allowEdit($data = array(), $key = 'id')
   {
      // Initialise variables.
      $recordId = (int) isset($data[$key]) ? $data[$key] : 0;
      $categoryId = 0;

      if ($recordId) {
         $categoryId = (int) $this->getModel()->getItem($recordId)->catid;
      }

      if ($categoryId) {
         // The category has been set. Check the category permissions.
         return JFactory::getUser()->authorise('core.edit', $this->option.'.category.'.$categoryId);
      } else {
         // Since there is no asset tracking, revert to the component permissions.
         return parent::allowEdit($data, $key);
      }
   }
}

Normally, when we are in this method, we should have a valid subscription id. In this case, we read the category id from the subscription and check that the user is authorized to edit subscriptions in this category. If for some reason we don’t have a valid subscription id, we check that the user has edit permission at the component level.

Add and Edit View

Assuming the user is authorized to add or edit, the parent controller sets the redirect to

   administrator/index.php?option=com_joomprosubs&view=subscription
   &layout=edit.

This will execute the display task using the JoomproSubsViewSubscription class (views/subscription/view.html.php).

The first part of this file is as follows:

defined('_JEXEC') or die;
jimport('joomla.application.component.view'),

/**
 * View to edit a contact.
 *
 * @package      Joomla.Administrator
 * @subpackage   com_joomprosubs
 */
class JoomprosubsViewSubscription extends JView
{
 protected $form;
 protected $item;
 protected $state;

 /**
  * Display the view
  */
 public function display($tpl = null)
 {
     // Initialiase variables.
     $this->form      = $this->get('Form'),
     $this->item      = $this->get('Item'),
     $this->state     = $this->get('State'),

     // Check for errors.
     if (count($errors = $this->get('Errors'))) {
        JError::raiseError(500, implode(" ", $errors));
        return false;
     }

     $this->addToolbar();
     parent::display($tpl);
 }

This is similar to the corresponding file for Weblinks. Here, we extend JView and then define our display() method. This method gets the form object, the item, and the state from the model. It then checks for errors. If none are found, it adds the toolbar and then calls the parent’s display method.

The last part of the file is as follows:

  protected function addToolbar()
  {
    JRequest::setVar('hidemainmenu', true);

    $user = JFactory::getUser();
    $isNew = ($this->item->id == 0);
    $checkedOut = !($this->item->checked_out == 0 || $this->item->checked_out == $user->get('id'));
    $canDo = JoomprosubsHelper::getActions($this->state->get('filter.category_id'), $this->item->id);

    JToolBarHelper::title(JText::_('COM_JOOMPROSUBS_MANAGER_JOOMPROSUB'), 'newfeeds.png'),

    // If not checked out, can save the item.
    if (!$checkedOut && ($canDo->get('core.edit')|| (count($user->getAuthorisedCategories('com_joomprosubs', 'core.create')))))
    {
        JToolBarHelper::apply('subscription.apply', 'JTOOLBAR_APPLY'),
        JToolBarHelper::save('subscription.save', 'JTOOLBAR_SAVE'),
    }
    if (!$checkedOut && (count($user->getAuthorisedCategories('com_joomprosubs', 'core.create')))){
        JToolBarHelper::custom('subscription.save2new', 'save-new.png', 'save-new_f2.png', 'JTOOLBAR_SAVE_AND_NEW', false);
    }
    // If an existing item, can save to a copy.
    if (!$isNew && (count($user->getAuthorisedCategories('com_joomprosubs', 'core.create')) > 0)) {
        JToolBarHelper::custom('subscription.save2copy', 'save-copy.png', 'save-copy_f2.png', 'JTOOLBAR_SAVE_AS_COPY', false);
    }
    if (empty($this->item->id)) {
        JToolBarHelper::cancel('subscription.cancel', 'JTOOLBAR_CANCEL'),
    }
    else {
        JToolBarHelper::cancel('subscription.cancel', 'JTOOLBAR_CLOSE'),
    }

    JToolBarHelper::divider();
    JToolBarHelper::help('', '', JText::_('COM_JOOMPROSUBS_SUBSCRIPTION_HELP_LINK'));
  }
}

As with the corresponding method for Weblinks, here we check the user’s permissions before showing the Save, Save & Close, Save & New, and Save as Copy icons. We also show the Cancel/Close and Help icons. Remember that this is to improve the user interface and is not a substitute for rechecking the user’s permissions for these actions after they have been initiated.

Add and Edit Model

In the view, we called the model’s getForm(), getItem(), and getState() methods. Let’s look at the model class next. This is the JoomproSubsModelSubscription class in the file models/subscription.php. Note that the getItem() and getState() methods for this class are inherited from JModelAdmin and JModel, respectively. In addition, this model defines six methods.

The first part of the file defines the class and the canDelete() method, as follows:

defined('_JEXEC') or die;

jimport('joomla.application.component.modeladmin'),

/**
 *Joomprosubs model.
 *
 * @package    Joomla.Administrator
 * @subpackage com_joomprosubs
 * @since      2.5
 */
class JoomproSubsModelSubscription extends JModelAdmin
{
  /**
   * @var  string  The prefix to use with controller messages.
   */
  protected $text_prefix = 'COM_JOOMPROSUBS';

  /**
   * Method to test whether a record can be deleted.
   *
   * @param   object A record object.
   * @retur    Boolean True if allowed to delete the record. Defaults to the permission set in the component.
   */
  protected function canDelete($record)
  {
     if (!empty($record->id)) {
        if ($record->published != -2) {
           return ;
        }
        $user = JFactory::getUser();

        if ($record->catid) {
           return $user->authorise('core.delete', 'com_joomprosubs.category.'.(int) $record->catid);
        }
        else {
           return parent::canDelete($record);
        }
     }
  }

This method is called during the delete task. It simply checks whether a user has delete permission for the item. Note that we only check the permission for the category. Because the Joomla ACL is hierarchical, we only need to check the category permission. If there is no permission for this action set at the category level, it will automatically inherit the component level permission. So we don’t need to check both levels.

The next method in this class is canEditState(), as follows:

/**
 * Method to test whether a record can have its state changed.
 *
 * @param      object A record object.
 * @return     Boolean True if allowed to change the state of the record. Defaults to the permission set in the component.
 */
protected function canEditState($record)
{
   $user = JFactory::getUser();

   if (!empty($record->catid)) {
      return $user->authorise('core.edit.state', 'com_joomprosubs.category.'.(int) $record->catid);
   }
   else {
      return parent::canEditState($record);
   }
}

This method is called whenever the published state is changed. Again, this method just checks the user for this permission for the item’s category.

The next method is getTable(), as follows:

/**
 * Returns a reference to the a Table object, always creating it.
 *
 * @param  type       The table type to instantiate
 * @param  string     A prefix for the table class name. Optional.
 * @param  array      Configuration array for model. Optional.
 * @return JTable     A database object
 */
public function getTable($type = 'subscription', $prefix = 'JoomproSubsTable', $config = array())
{
    return JTable::getInstance($type, $prefix, $config);
}

This method is called whenever we are going to write to the table. It simply provides the correct table name for the table and then gets an instance of the table using the JTable class.

The next method is getForm(), as follows:

/**
 * Method to get the record form.
 *
 * @param  array    $data An optional array of data for the form to interogate.
 * @param  boolean  $loadData  True if the form is to load its own data (default case), false if not.
 * @return  JForm   A JForm object on success, false on failure
 */
public function getForm($data = array(), $loadData = true)
{
 // Initialise variables.
 $app  = JFactory::getApplication();

 // Get the form.
 $form = $this->loadForm('com_joomprosubs.subscription', 'subscription', array('control' => 'jform', 'load_data' => $loadData));
 if (empty($form)) {
    return false;
 }

 // Determine correct permissions to check.
 if ($this->getState('subscription.id')) {
    // Existing record. Can only edit in selected categories.
    $form->setFieldAttribute('catid', 'action', 'core.edit'),
 } else {
    // New record. Can only create in selected categories.
    $form->setFieldAttribute('catid', 'action', 'core.create'),
 }

 // Modify the form based on access controls.
 if (!$this->canEditState((object) $data)) {
    // Disable fields for display.
    $form->setFieldAttribute('published', 'disabled', 'true'),
    $form->setFieldAttribute('publish_up', 'disabled', 'true'),
    $form->setFieldAttribute('publish_down', 'disabled', 'true'),

    // Disable fields while saving.
    // The controller has already verified this is a record you can edit.
    $form->setFieldAttribute('published', 'filter', 'unset'),
    $form->setFieldAttribute('publish_up', 'filter', 'unset'),
    $form->setFieldAttribute('publish_down', 'filter', 'unset'),
 }

 return $form;
}

This method is called at two points in the cycle:

• When we open a form for editing

• When we are saving form data

Here we get the data for the form and check that the user has permission to either edit or create an item. Then, we check if the user has edit state permission. If not, we do two things:

• We disable the fields so they don’t show on the form, by setting the disabled attribute in the form to true.

• We filter out any changes that might have been entered in these fields, by setting the filter attribute to unset.

Image

We previously discussed the fact that we need to check input coming from users before processing the information. Even if a form does not allow a user to enter something, a hacker can easily modify the HTML of the page to change the form and bypass this control. That is why we need to also check form values after the form has been submitted.

JForm makes this very easy to do. When we set the filter attribute to unset, we are telling JForm to remove any data that might have been entered by a user. In this case, because we already disabled those form fields to prevent normal entry, we know this information must have been entered by someone who modified the form in their browser. This code protects us from this type of hacking attempt. No further checking of input is required. This is a great example of using JForm to change things on the fly and to protect against hacking.

The next method is getFormData(), as follows:

/**
 * Method to get the data that should be injected in the form.
 *
 * @return mixed   The data for the form.
 */
protected function loadFormData()
{
 // Check the session for previously entered form data.
 $data = JFactory::getApplication()->getUserState('com_joomprosubs.edit.subscription.data', array());

 if (empty($data)) {
    $data = $this->getItem();

    // Prime some default values.
    if ($this->getState('subscription.id') == 0) {
        $app = JFactory::getApplication();
        $data->set('catid', JRequest::getInt('catid', $app->getUserState('com_joomprosubs.submanager.filter.category_id')));
    }
 }

 return $data;
}

First, we get any data that may have been saved in the session. Recall that we do this so the user doesn’t have to reenter all the form’s data if one field is invalid. If there isn’t data already in the session, we get the data using the getItem() method. If we are entering a new item, we check whether we are filtering on a category. If so, we use that category id as the default for the form.

The last part of the class has the prepareTable() method, as follows:

   /**
   * Prepare and sanitise the table prior to saving.
   *
   */
   protected function prepareTable(&$table)
   {
     $table->alias = JApplication::stringURLSafe($table->alias);
     if (empty($table->alias)) {
        $table->alias = JApplication::stringURLSafe($table->title);
     }
  }
} // end of class

Here, we are making sure the alias column is safe to be in a URL. This is because we use the alias in the URL when we have the SEF URL option set to yes. We also set the URL automatically, based on the title, if it is not already set. This way the user can skip this field if they just want to use the title as the alias.

Add and Edit Form

When we render the form on the screen, the layout for the form is views/subscription/tmpl/edit.php. This is a script, not a class. The first part of the code for this file is as follows:

<?php
/**
 * @copyright  Copyright (C) 2011 Mark Dexter and Louis Landry. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

// no direct access
defined('_JEXEC') or die;

JHtml::addIncludePath(JPATH_COMPONENT.'/helpers/html'),
JHtml::_('behavior.tooltip'),
JHtml::_('behavior.formvalidation'),
?>
<script type="text/javascript">
  Joomla.submitbutton = function(task)
  {
     if (task == 'subscription.cancel' || document.formvalidator.isValid(document.id('subscription-form'))) {
       <?php echo $this->form->getField('description')->save(); ?>
       Joomla.submitform(task, document.getElementById('subscription-form'));
     }
     else {
        alert('<?php echo $this->escape(JText::_('JGLOBAL_VALIDATION_FORM_FAILED'));?>'),
     }
  }
</script>

As with earlier examples of using JForm, we include two JavaScript behaviors, one for tooltips and one for validating our form. Then we define a script that will run the form validation when we submit the form. We discuss how this works in Chapter 12.

The next part of the file is as follows:

<form action="<?php echo JRoute::_('index.php?option=com_joomprosubs&layout=edit&id='.(int) $this->item->id); ?>" method="post" name="adminForm" id="subscription-form" class="form-validate">
  <div class="width-60 fltlft">
    <fieldset class="adminform">
      <legend><?php echo empty($this->item->id) ? JText::_('COM_JOOMPROSUBS_NEW_JOOMPROSUB') : JText::sprintf('COM_JOOMPROSUBS_EDIT_JOOMPROSUB', $this->item->id); ?> </legend>
      <ul class="adminformlist">
      <li><?php echo $this->form->getLabel('title'), ?>
      <?php echo $this->form->getInput('title'), ?></li>

      <li><?php echo $this->form->getLabel('alias'), ?>
      <?php echo $this->form->getInput('alias'), ?></li>

      <li><?php echo $this->form->getLabel('catid'), ?>
      <?php echo $this->form->getInput('catid'), ?></li>

      <li><?php echo $this->form->getLabel('group_id'), ?>
      <?php echo $this->form->getInput('group_id'), ?></li>

      <li><?php echo $this->form->getLabel('duration'), ?>
      <?php echo $this->form->getInput('duration'), ?></li>

      <li><?php echo $this->form->getLabel('published'), ?>
      <?php echo $this->form->getInput('published'), ?></li>

      <li><?php echo $this->form->getLabel('access'), ?>
      <?php echo $this->form->getInput('access'), ?></li>

      <li><?php echo $this->form->getLabel('id'), ?>
      <?php echo $this->form->getInput('id'), ?></li>
      </ul>

      <?php echo $this->form->getLabel('description'), ?>
      <div class="clr"></div>
      <?php echo $this->form->getInput('description'), ?>

    </fieldset>
  </div>

Here we define the form action to load the same URL we are already on, with our current item id. Then we define the post method for the form and define the name, id, and class attributes for the form element. Then we output the first seven form elements as an unordered HTML list. In each case, we just get the form label and input from the form’s XML file. These are the elements that show on the left side of the form.

The last part of this file is as follows:

   <div class="width-40 fltrt">
      <?php echo JHtml::_('sliders.start','joomprosubs-sliders-'.$this->item->id, array('useCookie'=>1)); ?>

      <?php echo JHtml::_('sliders.panel',JText::_('JGLOBAL_FIELDSET_PUBLISHING'), 'publishing-details'), ?>

      <fieldset class="panelform">
         <ul class="adminformlist">
            <li><?php echo $this->form->getLabel('created_by'), ?>
            <?php echo $this->form->getInput('created_by'), ?></li>

            <li><?php echo $this->form->getLabel('created_by_alias'), ?>
            <?php echo $this->form->getInput('created_by_alias'), ?></li>

            <li><?php echo $this->form->getLabel('created'), ?>
            <?php echo $this->form->getInput('created'), ?></li>

            <li><?php echo $this->form->getLabel('publish_up'), ?>
            <?php echo $this->form->getInput('publish_up'), ?></li>

            <li><?php echo $this->form->getLabel('publish_down'), ?>
            <?php echo $this->form->getInput('publish_down'), ?></li>

            <?php if ($this->item->modified_by) : ?>
               <li><?php echo $this->form->getLabel('modified_by'), ?>
               <?php echo $this->form->getInput('modified_by'), ?></li>

               <li><?php echo $this->form->getLabel('modified'), ?>
               <?php echo $this->form->getInput('modified'), ?></li>
            <?php endif; ?>

         </ul>
      </fieldset>

      <?php echo JHtml::_('sliders.end'), ?>

      <input type="hidden" name="task" value="" />
      <?php echo JHtml::_('form.token'), ?>
  </div>
  <div class="clr"></div>
</form>

Here we define a slider for the publishing information. We use the JHtmlSliders::start() and JHtmlSliders::panel() methods to create the slider. Then we use getLabel() and getInput() again to output the seven form fields for publishing information. Then we close the slider and add a hidden field for the form token. Recall that this field is used to check against the token saved in the session to make sure we are saving the correct information.

Next, let’s look at the XML file (models/forms/subscription.xml) that we read the fields from. The first part is as follows:

<?xml version="1.0" encoding="utf-8"?>
<form>
   <fieldset>
      <field name="id" type="text" default="0" label="JGLOBAL_FIELD_ID_LABEL"
         readonly="true" class="readonly"
         description="JGLOBAL_FIELD_ID_DESC"/>

      <field name="title" type="text" class="inputbox"
         size="40" label="JGLOBAL_TITLE"
         description="COM_JOOMPROSUBS_FIELD_TITLE_DESC" required="true" />

      <field name="alias" type="text" class="inputbox"
         size="40" label="JFIELD_ALIAS_LABEL"
         description="COM_JOOMPROSUBS_FIELD_ALIAS_DESC" />

      <field name="catid" type="category" extension="com_joomprosubs"
         label="JCATEGORY"
         description="COM_JOOMPROSUBS_FIELD_CATEGORY_DESC"
         class="inputbox" >
      </field>

      <field name="group_id" type="usergroup"
          label="COM_JOOMPROSUBS_FIELD_USERGROUP_LABEL"
          description="COM_JOOMPROSUBS_FIELD_USERGROUP_DESC"
          default="0" size="1" >
      </field>

      <field name="duration" type="integer" filter="integer"
          first="15" last="90" step="15" default="30"
          label="COM_JOOMPROSUBS_FIELD_DURATION_LABEL"
          description="COM_JOOMPROSUBS_FIELD_DURATION_DESC" />

      <field name="description" type="editor" buttons="true"
          hide="pagebreak,readmore"
          class="inputbox"
          filter="safehtml"
           label="JGLOBAL_DESCRIPTION"
           description="COM_JOOMPROSUBS_FIELD_DESCRIPTION_DESC" />

Here we define the first seven fields for our form. For the catid field, we set the type to category and the extension to our extension name. That provides us with the list of subscription categories in a drop-down list box. Similarly, we specify a type of user group for the user group field.

The duration field uses the “integer” type and also the “integer” filter. This type allows us to specify a lower and upper bound and a step. In this case, we start with 15 days and go by increments of 15 up to 90 days. Note that we set the filter to integer. This means that even if a hacker bypasses our list box and enters in something else in the form, it will be converted to an integer when the data is saved to the table.

Finally, the description has a type of “editor”. This means that the user’s default editor will be used to edit this field. We specify a filter of “safehtml” to filter the user’s input to be safe to render on an HTML page.

The last part of this XML file is as follows:

      <field
        name="published"
        type="list"
        label="JSTATUS"
        description="COM_JOOMPROSUBS_FIELD_STATE_DESC"
        class="inputbox"
        size="1"
        default="1">
        <option
          value="1">JPUBLISHED</option>
        <option
          value="0">JUNPUBLISHED</option>
        <option
          value="-2">JTRASHED</option>
     </field>

     <field name="access" type="accesslevel" label="JFIELD_ACCESS_LABEL"
       description="JFIELD_ACCESS_DESC" class="inputbox" size="1" />

     <field name="created" type="calendar"
       label="JGLOBAL_FIELD_CREATED_LABEL"
       description="JGLOBAL_FIELD_CREATED_DESC"
       class="inputbox" size="22" format="%Y-%m-%d %H:%M:%S"
       filter="user_utc" />

     <field name="created_by" type="user"
       label="JGLOBAL_FIELD_CREATED_BY_LABEL"
       description="JGLOBAL_FIELD_CREATED_BY_Desc" />

     <field name="created_by_alias" type="text"
       label="JGLOBAL_FIELD_CREATED_BY_ALIAS_LABEL"
       description="JGLOBAL_FIELD_CREATED_BY_ALIAS_DESC"
       class="inputbox" size="20" />

     <field name="modified" type="calendar" class="readonly"
       label="JGLOBAL_FIELD_MODIFIED_LABEL"
       description="COM_JOOMPROSUBS_FIELD_MODIFIED_DESC"
       size="22" readonly="true" format="%Y-%m-%d %H:%M:%S"
       filter="user_utc" />

     <field name="modified_by" type="user"
       label="JGLOBAL_FIELD_MODIFIED_BY_LABEL"
       class="readonly" readonly="true" filter="unset"  />

     <field name="checked_out" type="hidden" filter="unset" />

     <field name="checked_out_time" type="hidden" filter="unset" />

     <field name="publish_up" type="calendar"
       label="JGLOBAL_FIELD_PUBLISH_UP_LABEL"
       description="JGLOBAL_FIELD_PUBLISH_UP_DESC"
       class="inputbox" format="%Y-%m-%d %H:%M:%S" size="22"
       filter="user_utc" />

     <field name="publish_down" type="calendar"
       label="JGLOBAL_FIELD_PUBLISH_DOWN_LABEL"
       description="JGLOBAL_FIELD_PUBLISH_DOWN_DESC"
       class="inputbox" format="%Y-%m-%d %H:%M:%S" size="22"
       filter="user_utc" />

  </fieldset>

</form>

This defines the last seven fields in our form. Note that we use the type of “calendar” for our dates. This provides a pop-up calendar widget. We also include two hidden fields for checked_out and checked_out_time. These cannot be entered from the form because we set the filter attribute to unset. These are included specifically to prevent them from being entered by a hacker.

Table Class

The last file to review for the add/edit process is JoomprosubsTableSubscription (tables/subscription.php). This file defines any special processing we need to do when we save a row in our table. The first part of the file is as follows:

defined('_JEXEC') or die;

class JoomprosubsTableSubscription extends JTable
{
  /**
   * Constructor
   *
   * @param JDatabase A database connector object
   */
  public function __construct(&$db)
  {
    parent::__construct('#__joompro_subscriptions', 'id', $db);
  }

This defines the class and gives the database table name as the first argument in the constructor. Note that we use an ampersand (&) in front of the $db in the argument list. Recall that this means that if we change the $db object during this method, the changed object will be passed back to the calling method.

The next part of the class defines the store() method, as follows:

/**
 * Overload the store method for the Subscriptions table.
 *
 * @param  boolean    Toggle whether null values should be updated.
 * @return boolean    True on success, false on failure.
 */
public function store($updateNulls = false)
{
  $date = JFactory::getDate();
  $user = JFactory::getUser();
  if ($this->id) {
     // Existing item
     $this->modified     = $date->toSQL();
     $this->modified_by  = $user->get('id'),
  } else {
     // New subscription. Created and created_by field can be set by the user,
     // so we don't touch either of these if they are set.
     if (!intval($this->created)) {
        $this->created = $date->toSQL();
     }
     if (empty($this->created_by)) {
        $this->created_by = $user->get('id'),
     }
  }

  // Verify that the alias is unique
  $table = JTable::getInstance('subscription', 'JoomprosubsTable'),
  if ($table->load(array('alias'=>$this->alias,'catid'=>$this->catid)) && ($table->id != $this->id || $this->id==0)) {
     $this->setError(JText::_('COM_JOOMPROSUBS_ERROR_UNIQUE_ALIAS'));
     return false;
  }
  // Attempt to store the user data.
  return parent::store($updateNulls);
}

This method is used when we save a row in the table. If we are editing an existing row, we set the modified date to the current time and the modified-by user to the current user. If we are adding a new row, we set the created-by user and time. Then we check that the alias is unique for this category. This is to prevent problems when a user inadvertently creates two items in the same category with the same alias. If that happens, only one of them can be displayed on the front end, because they both have the same URL. This check prevents that from happening by giving the user an error message.

Finally, we call the store() method of the parent class to do the standard table processing. This includes setting the key field and updating the assets table if needed.

The last part of this class defines the check() method, as follows:

  /**
   * Overloaded check method to ensure data integrity.
   *
   * @return   boolean True on success.
   */
  public function check()
  {
     // check for existing name
     $db = $this->_db;
     $query = $db->getQuery(true);
     $query->select('id'),
     $query->from($db->quoteName('#__joompro_subscriptions'));
     $query->where('title = ' . $db->quote($this->title) .
        ' AND catid = ' . (int) $this->catid);
     $db->setQuery($query);

     $xid = intval($db->loadResult());
     if ($xid && $xid != intval($this->id)) {
        $this->setError(JText::_('COM_JOOMPROSUBS_ERR_TABLES_NAME'));
        return false;
     }

     if (empty($this->alias)) {
        $this->alias = $this->title;
     }
     $this->alias = JApplication::stringURLSafe($this->alias);
     if (trim(str_replace('-','',$this->alias)) == '') {
        $this->alias = JFactory::getDate()->format("Y-m-d-H-i-s");
     }

     // Check the publish down date is not earlier than publish up.
     if (intval($this->publish_down) > 0 && $this->publish_down < $this->publish_up) {
       // Swap the dates.
       $temp = $this->publish_up;
       $this->publish_up = $this->publish_down;
       $this->publish_down = $temp;
     }

     return true;
  }
}

Here we check that the name of the subscription is unique, again for this category. This helps prevent duplicate alias values. We also check that the publish start date is before the stop date.

At this point, we have defined all the files required to add or edit a subscription item in the back end of our component.

Language Files

We need two language files for the back end. We have chosen to keep these files in the component’s language folder (administrator/components/com_joomprosubs/language/en-GB/) instead of in the common administrator/language folder. The first file is language/en-GB/en-GB.com_joomprosubs.ini. This contains all the translations for the language keys used for the component. Its contents are as follows:

COM_JOOMPROSUBS_CATEGORY_LIST_HELP_LINK="http://joomlaprogrammingbook.com/joompro-category-list-help.html"
COM_JOOMPROSUBS_CATEGORY_VIEW_DEFAULT_TITLE="Category List"
COM_JOOMPROSUBS_CONFIGURATION="Subscriptions Manager Options"
COM_JOOMPROSUBS_EDIT_JOOMPROSUB="Subscription Edit"
COM_JOOMPROSUBS_FIELD_ALIAS_DESC="Alias for the item. You can leave this blank and the system will create an alias for you."
COM_JOOMPROSUBS_FIELD_CATEGORY_DESC="Select the category from the list."
COM_JOOMPROSUBS_FIELD_DESCRIPTION_DESC="Enter an optional description for the subscription."
COM_JOOMPROSUBS_FIELD_DURATION_DESC="Select the number of days that this subscription will be active."
COM_JOOMPROSUBS_FIELD_DURATION_LABEL="Duration (days)"
COM_JOOMPROSUBS_FIELD_MODIFIED_DESC="Date this subscription was last modified."
COM_JOOMPROSUBS_FIELD_SELECT_CATEGORY_DESC="Select the category from the list."
COM_JOOMPROSUBS_FIELD_SELECT_CATEGORY_LABEL="Category"
COM_JOOMPROSUBS_FIELD_STATE_DESC="Published state for this subscription."
COM_JOOMPROSUBS_FIELD_TITLE_DESC="Title of the subscription."
COM_JOOMPROSUBS_FIELD_USERGROUP_DESC="Select the User Group that is associated with this subscription."
COM_JOOMPROSUBS_FIELD_USERGROUP_LABEL="User Group"
COM_JOOMPROSUBS_MANAGER_JOOMPROSUB="Subscription Manager: Subscription Entry"
COM_JOOMPROSUBS_MANAGER_JOOMPROSUBS="Subscriptions Manager: Subscriptions"
COM_JOOMPROSUBS_N_ITEMS_ARCHIVED_1="%d subscription successfully archived"
COM_JOOMPROSUBS_N_ITEMS_ARCHIVED="%d subscriptions successfully archived"
COM_JOOMPROSUBS_N_ITEMS_CHECKED_IN_0="No subscription successfully checked in"
COM_JOOMPROSUBS_N_ITEMS_CHECKED_IN_1="%d subscription successfully checked in"
COM_JOOMPROSUBS_N_ITEMS_CHECKED_IN_MORE="%d subscriptions successfully checked in"
COM_JOOMPROSUBS_N_ITEMS_DELETED_1="%d subscription successfully deleted"
COM_JOOMPROSUBS_N_ITEMS_DELETED="%d subscriptions successfully deleted"
COM_JOOMPROSUBS_N_ITEMS_PUBLISHED_1="%d subscription successfully published"
COM_JOOMPROSUBS_N_ITEMS_PUBLISHED="%d subscriptions successfully published"
COM_JOOMPROSUBS_N_ITEMS_TRASHED_1="%d subscription successfully trashed"
COM_JOOMPROSUBS_N_ITEMS_TRASHED="%d subscriptions successfully trashed"
COM_JOOMPROSUBS_N_ITEMS_UNPUBLISHED_1="%d subscription successfully unpublished"
COM_JOOMPROSUBS_N_ITEMS_UNPUBLISHED="%d subscriptions successfully unpublished"
COM_JOOMPROSUBS_NEW_JOOMPROSUB="Subscription Add"
COM_JOOMPROSUBS_SEARCH_IN_TITLE="Searh in title."
COM_JOOMPROSUBS_SUBMANAGER_HELP_LINK="http://joomlaprogrammingbook.com/joompro-subscription-manager-help.html"
COM_JOOMPROSUBS_SUBMENU_CATEGORIES="Categories"
COM_JOOMPROSUBS_SUBMENU_JOOMPROSUBS="Subscriptions"
COM_JOOMPROSUBS_SUBSCRIPTION_HELP_LINK="http://joomlaprogrammingbook.com/joompro-subscription-edit-help.html"
COM_JOOMPROSUBS_TOOLBAR_CSVREPORT="Report"
COM_JOOMPROSUBS="Subscriptions"

The second file, language/en-GB/en-GB.com_joomprosubs.sys.ini, contains the language strings needed when we are not in the component. It is as follows:

CATEGORIES="Categories"
COM_JOOMPROSUBS="JoomPro Subscriptions"
COM_JOOMPROSUBS_XML_DESCRIPTION="This is an example component for the Joomla! Programming book."
COM_JOOMPROSUBS_CATEGORY_VIEW_DEFAULT_DESC="Lists all subscriptions in a category."
COM_JOOMPROSUBS_CATEGORY_VIEW_DEFAULT_OPTION="Default Layout"
COM_JOOMPROSUBS_CATEGORY_VIEW_DEFAULT_TITLE="Category List"

Installation and Configuration

The last file in the back end is the joomprosubs.xml file in the administrator/components/com_joomprosubs folder. We are going to build this file in two stages. For now, we will include only the back-end files. This will allow us to install and test the back end of the component before moving on to the front end. We will add the front-end files to the installation file in the next chapter.

The first part of this file is as follows:

<?xml version="1.0" encoding="utf-8"?>
<extension type="component" version="2.5.0" method="upgrade">
   <name>com_joomprosubs</name>
   <author>Mark Dexter and Louis Landry</author>
   <creationDate>January 2012</creationDate>
   <copyright>(C) 2012 Mark Dexter and Louis Landry. All rights reserved.
   </copyright>
   <license>GNU General Public License version 2 or later; see
      LICENSE.txt</license>
   <authorEmail>[email protected]</authorEmail>
   <authorUrl>www.joomla.org</authorUrl>
   <version>2.5.0</version>
   <description>COM_JOOMPROSUBS_XML_DESCRIPTION</description>

   <install> <!-- Runs on install -->
      <sql>
         <file driver="mysql" charset="utf8">sql/install.mysql.utf8.sql</file>
      </sql>
   </install>
   <uninstall> <!-- Runs on uninstall -->
      <sql>
         <file driver="mysql" charset="utf8">sql/uninstall.mysql.utf8.sql</file>
      </sql>
   </uninstall>

First, we define various descriptive elements, including name, author, and so on, down to description. Then we define the install and uninstall elements. These name the database scripts to run when the component is installed or uninstalled. In our example, recall that these scripts create and drop the two tables for our component.

The next section is as follows:

  <administration>
    <menu img="class:newsfeeds">COM_JOOMPROSUBS</menu>
    <submenu>
      <!--
        Note that all & must be escaped to &amp; for the file to be valid
        XML and be parsed by the installer
      -->
      <menu link="option=com_joomprosubs" view="submanager" img="class:newsfeeds"
        alt="Subscriptions/Subscriptions">COM_JOOMPROSUBS</menu>
      <menu link="option=com_categories&amp;extension=com_joomprosubs"
        view="categories" img="class:newsfeeds-cat"
        alt="Subscriptions/Categories">Categories</menu>
    </submenu>

This starts the administrator element, which defines the back-end menus and files. The first part defines the menu and submenu elements. These are exactly the same as discussed in Chapter 7 for Weblinks except that we use our component name. Here we add one top-level option and two submenu options for our component, which will display as shown in Figure 9.4.

Image

Figure 9.4. Subscriptions menu options

The last section of the installation XML file is as follows:

    <files folder="admin">
       <folder>controllers</folder>
       <folder>helpers</folder>
       <folder>language</folder>
       <folder>models</folder>
       <folder>sql</folder>
       <folder>tables</folder>
       <folder>views</folder>
       <filename>access.xml</filename>
       <filename>config.xml</filename>
       <filename>controller.php</filename>
       <filename>index.html</filename>
       <filename>joomprosubs.php</filename>
    </files>
  </administration>
</extension>

This defines the files for the back end of our component. Again, we only have to define the top-level folders and files. Then we close the administrator and extension elements.

At this point, we can use the Discover and Install options from the Extension Manager → Discover screen to install and test the back-end part of our component.

Summary

In this chapter, we created the entire back end of our example component. This included the manager screen, the add/edit screen, installation files, and the miscellaneous files we need. If you understand how this works, you will be able to create your own components using the same techniques covered here.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.51.145