Actions

J2.5

Difference between revisions of "Developing a MVC Component/Adding ACL"

From Joomla! Documentation

< J2.5:Developing a MVC Component
m (Displaying only the right toolbar buttons)
(rollback to original)
(41 intermediate revisions by 5 users not shown)
Line 1: Line 1:
This tutorial is for {{JVer|2.5}}
+
{{version/tutor|2.5}}
 
{{Chunk:Developing a Model-View-Controller (MVC) Component for Joomla!2.5 - Contents}}
 
{{Chunk:Developing a Model-View-Controller (MVC) Component for Joomla!2.5 - Contents}}
  
 
== Introduction ==
 
== Introduction ==
 
This tutorial is part of the [[Developing a Model-View-Controller (MVC) Component for Joomla!2.5]] tutorial. You are encouraged to read the previous parts of the tutorial before reading this.
 
This tutorial is part of the [[Developing a Model-View-Controller (MVC) Component for Joomla!2.5]] tutorial. You are encouraged to read the previous parts of the tutorial before reading this.
 
TODO: remove deprecated JError-references.
 
  
 
== Adding Access Control ==
 
== Adding Access Control ==
With Joomla!'s Access Control we can define which user groups are allowed or denied to do which actions in your component. In this example we use actions that are defined in the core. For the component as a whole: core.admin (access to the configuration) and core.manage (access to the back-end). And at various levels actions like create, delete and edit. Besides those core actions you can define your own actions, but that is often not necessary and is not shown in this example. View/Read Access is not managed via those actions but with View Access Levels; see general documentation about Joomla!'s ACL for that.  
+
With Joomla!'s Access Control we can define which user groups are allowed or denied to do which actions in your component. In this example we use actions that are defined in the core. For the component as a whole: core.admin (access to the configuration) and core.manage (access to the backend). And at various levels actions like create, delete and edit. Besides those core actions you can define your own actions, but that is often not necessary and is not shown in this example. View/Read Access is not managed via those actions but with View Access Levels; see general documentation about Joomla!'s ACL for that.  
  
 
In the #__assets table the actual list is stored: which user groups are allowed or denied to do which actions on which resources (assets). This is the implementation of the Access Control List (ACL).  
 
In the #__assets table the actual list is stored: which user groups are allowed or denied to do which actions on which resources (assets). This is the implementation of the Access Control List (ACL).  
Line 17: Line 15:
 
There are 2 actions that need to be defined at the component level for a Joomla! 2.5 component to offer basic ACL support:
 
There are 2 actions that need to be defined at the component level for a Joomla! 2.5 component to offer basic ACL support:
 
* '''Configure''' (core.admin): which groups are allowed to configure the component level permissions via the 'Options' toolbar button?
 
* '''Configure''' (core.admin): which groups are allowed to configure the component level permissions via the 'Options' toolbar button?
* '''Access Component''' (core.manage): which groups are allowed to access the component's back-end?
+
* '''Access Component''' (core.manage): which groups are allowed to access the component's backend?
  
 
This basic ACL support is done in 4 simple steps:
 
This basic ACL support is done in 4 simple steps:
 +
* [[#Add_the_2_minimal_component_level_actions_to_access.xml|Add the 2 minimal component level actions to access.xml]]
 +
* [[#Add_the_permissions_fieldset_to_config.xml|Add the permissions fieldset to config.xml]]
 +
* [[#Add_the_.27Options.27_toolbar_button_when_user_is_authorised_for_it|Add the 'Options' toolbar button]]
 +
* [[#Restrict_the_access_to_the_component.27s_backend_to_authorised_usergroups|Restrict the access to the component's backend]]
 +
  
 
=== Add the 2 minimal component level actions to access.xml ===
 
=== Add the 2 minimal component level actions to access.xml ===
Line 38: Line 41:
 
Add the following permissions fieldset to ''admin/config.xml'' in order to be able to set our component level permissions
 
Add the following permissions fieldset to ''admin/config.xml'' in order to be able to set our component level permissions
  
<source lang="php">
+
<source lang="xml">
 
<fieldset
 
<fieldset
 
name="permissions"
 
name="permissions"
Line 70: Line 73:
 
</source>
 
</source>
  
See further downwards for a [[#admin/views/helloworlds/view.html.php|more elaborated example of ''admin/views/helloworlds/view.html.php'']] where this ''JToolBarHelper::preferences('com_helloworld')'' is done in an addToolBar()-method and the ''JUser->authorise()''-check in the admin/helpers/helloworld.php helper file.
+
See further downwards for a [[#admin/views/helloworlds/view.html.php|more elaborated example of ''admin/views/helloworlds/view.html.php'']] where this ''JToolBarHelper::preferences('com_helloworld')'' is done in an addToolBar()-method together with the other toolbar buttons and the ''JUser->authorise()''-check is done in the admin/helpers/helloworld.php helper file, resulting in the $canDo-property.
  
=== Restrict the access to the component's back-end to authorised usergroups ===
+
=== Restrict the access to the component's backend to authorised usergroups ===
To control the access to the back-end of the component add the following lines to the ''admin/helloworld.php'' entry-file:
+
To control the access to the backend of the component add the following lines to the ''admin/helloworld.php'' entry-file:
  
 
<source lang="php">
 
<source lang="php">
// Access check: is this user allowed to access the back-end of this component?
+
// Access check: is this user allowed to access the backend of this component?
 
if (!JFactory::getUser()->authorise('core.manage', 'com_helloworld'))  
 
if (!JFactory::getUser()->authorise('core.manage', 'com_helloworld'))  
 
{
 
{
Line 87: Line 90:
 
== Adding more actions, also at category level and item level ==
 
== Adding more actions, also at category level and item level ==
 
When adding more actions and more levels, the above described 4 steps are done too:
 
When adding more actions and more levels, the above described 4 steps are done too:
* Add the actions to access.xml; here we can add more actions and levels
+
* [[#Describing_the_actions_you_want_to_control_the_access_to|Add the actions to access.xml; here we can add more actions and levels]]
* Add the permissions-fieldset to config.xml
+
* [[#Adding_the_setting_of_permissions_in_the_component.27s_Preferences|Add the permissions-fieldset to config.xml]]
* Add the 'Options' toolbar button
+
* [[#Displaying_only_the_right_toolbar_buttons|Add the 'Options' toolbar button]]
* Restrict the access to the component's back-end
+
* [[#Restricting_access_to_the_component|Restrict the access to the component's backend
 
+
]]
 
In addition we also have to do the following steps:
 
In addition we also have to do the following steps:
* Add an asset_id to the item's database table for item level access control.
+
* [[#Add_the_asset_id_column_to_the_database_table|Add an asset_id to the item's database table for item level access control]]
* Store the permissions in the assets table. Especially take care of setting the asset_id of the parent-asset.
+
* [[#Setting_the_permission_values_in_the_assets_table|Store the permissions in the assets table. Especially take care of setting the asset_id of the parent-asset]]
* Make the settings of the permissions at the item level editable
+
* [[#Showing_the_setting_of_permissions_on_the_item_level|Make the settings of the permissions at the item level editable]]
* Add some language strings.
+
* [[#Adding_language_strings|Add some language strings]]
  
 
=== Describing the actions you want to control the access to ===
 
=== Describing the actions you want to control the access to ===
Line 129: Line 132:
  
 
=== Adding the setting of permissions in the component's Preferences ===
 
=== Adding the setting of permissions in the component's Preferences ===
Since we now use Access Control permissions in our component, we need to be able to set them at the component level. That is done in the Preferences of this component: the screen you see after clicking the 'Options' button. The config.xml-file is a form-definition for those Preferences. We could define the component level actions here too, as a child of the "rules" field-tag (see for example step 1 in http://www.aclmanager.net/blog/general/31-how-to-add-basic-acl-support-to-your-extension), but it is now preferred to also put those actions in access.xml: in that way all access rules for this component are on one spot.
+
Since we now use Access Control permissions in our component, we need to be able to set them at the component level. That is done in the Preferences of this component: the screen you see after clicking the 'Options' button. The config.xml-file is a form-definition for those Preferences. We could define the component level actions here too, as a child of the "rules" field-tag, but it is now preferred to also put those actions in access.xml: in that way all access rules for this component are on one spot.
  
 
<span id="admin/config.xml">
 
<span id="admin/config.xml">
 
''admin/config.xml''
 
''admin/config.xml''
<source lang="php" highlight="19-34">
+
<source lang="xml" highlight="19-34">
 
<?xml version="1.0" encoding="utf-8"?>
 
<?xml version="1.0" encoding="utf-8"?>
 
<config>
 
<config>
Line 174: Line 177:
 
=== Displaying only the right toolbar buttons ===
 
=== Displaying only the right toolbar buttons ===
  
Which toolbar buttons to display depends on the Access Control permissions of the user. We put all permissions for this user in the $canDo property of the view; so we can eventually refer to it in layouts (in the edit-form for example).
+
Which toolbar buttons to display depends on the Access Control permissions for the user. We put all permissions for this user in the $canDo property of the view; so we can eventually refer to it in layouts (in the edit-form for example).
  
 
In the ''admin/views/helloworlds/view.html.php'', put this code
 
In the ''admin/views/helloworlds/view.html.php'', put this code
Line 180: Line 183:
 
<span id="admin/views/helloworlds/view.html.php">
 
<span id="admin/views/helloworlds/view.html.php">
 
''admin/views/helloworlds/view.html.php''
 
''admin/views/helloworlds/view.html.php''
<source lang="php" highlight="15,27-28,54-55,57-59,61-63,65-67,70" >
+
<source lang="php" highlight="15,27-28,53-54,56-58,60-62,64-66,69" >
 
<?php
 
<?php
 
// No direct access to this file
 
// No direct access to this file
Line 191: Line 194:
 
  * HelloWorlds View
 
  * HelloWorlds View
 
  */
 
  */
class HelloWorldViewHelloWorlds extends JView
+
class HelloWorldViewHelloWorlds extends JViewLegacy
 
{
 
{
 
protected $items;
 
protected $items;
Line 210: Line 213:
 
$this->canDo = HelloWorldHelper::getActions();
 
$this->canDo = HelloWorldHelper::getActions();
  
// Check for errors.
+
// Check for errors
//TODO: JError is deprecated; use appropriate error reporting
+
 
if (count($errors = $this->get('Errors')))  
 
if (count($errors = $this->get('Errors')))  
 
{
 
{
Line 270: Line 272:
 
<span id="admin/views/helloworld/view.html.php">
 
<span id="admin/views/helloworld/view.html.php">
 
''admin/views/helloworld/view.html.php''
 
''admin/views/helloworld/view.html.php''
<source lang="php" highlight="16,29-30,66-67,72,77-78,85,86,89-92,95">
+
<source lang="php" highlight="16,29-30,65-66,71,76-77,84,85,88-91,94">
 
<?php
 
<?php
 
// No direct access to this file
 
// No direct access to this file
Line 281: Line 283:
 
  * HelloWorld View
 
  * HelloWorld View
 
  */
 
  */
class HelloWorldViewHelloWorld extends JView
+
class HelloWorldViewHelloWorld extends JViewLegacy
 
{
 
{
 
protected $form;
 
protected $form;
Line 302: Line 304:
 
$this->canDo = HelloWorldHelper::getActions($this->item->id);
 
$this->canDo = HelloWorldHelper::getActions($this->item->id);
  
// Check for errors.
+
// Check for errors
//TODO: JError is deprecated; use appropriate error reporting
+
 
if (count($errors = $this->get('Errors')))  
 
if (count($errors = $this->get('Errors')))  
 
{
 
{
Line 332: Line 333:
 
JToolBarHelper::title($isNew ? JText::_('COM_HELLOWORLD_MANAGER_HELLOWORLD_NEW')
 
JToolBarHelper::title($isNew ? JText::_('COM_HELLOWORLD_MANAGER_HELLOWORLD_NEW')
 
                            : JText::_('COM_HELLOWORLD_MANAGER_HELLOWORLD_EDIT'), 'helloworld');
 
                            : JText::_('COM_HELLOWORLD_MANAGER_HELLOWORLD_EDIT'), 'helloworld');
// Built the actions for new and existing records.
+
// Build the actions for new and existing records.
 
if ($isNew)  
 
if ($isNew)  
 
{
 
{
Line 455: Line 456:
 
=== Restricting access to the component ===
 
=== Restricting access to the component ===
  
The main idea in ACL is to restrict actions to groups of users. The first action to be restricted is access to the administrative back-end of the component itself. With your favorite file editor, edit the ''admin/helloworld.php'' file and add the lines with the access check.
+
The main idea in ACL is to restrict actions to groups of users. The first action to be restricted is access to the administrative backend of the component itself. With your favorite file editor, edit the ''admin/helloworld.php'' file and add the lines with the access check.
  
 
<span id="admin/helloworld.php">
 
<span id="admin/helloworld.php">
Line 464: Line 465:
 
defined('_JEXEC') or die('Restricted access');
 
defined('_JEXEC') or die('Restricted access');
  
// Access check: is this user allowed to access the back-end of this component?
+
// Access check: is this user allowed to access the backend of this component?
 
if (!JFactory::getUser()->authorise('core.manage', 'com_helloworld'))  
 
if (!JFactory::getUser()->authorise('core.manage', 'com_helloworld'))  
 
{
 
{
Line 471: Line 472:
  
 
// require helper file
 
// require helper file
JLoader::register('HelloWorldHelper', dirname(__FILE__) . DS . 'helpers' . DS . 'helloworld.php');
+
JLoader::register('HelloWorldHelper', dirname(__FILE__) . '/helpers/helloworld.php');
  
 
// import joomla controller library
 
// import joomla controller library
Line 497: Line 498:
 
<span id="admin/sql/install.mysql.utf8.sql">
 
<span id="admin/sql/install.mysql.utf8.sql">
 
''admin/sql/install.mysql.utf8.sql''
 
''admin/sql/install.mysql.utf8.sql''
<source lang="php" highlight="5">
+
<source lang="mysql" highlight="5">
 
DROP TABLE IF EXISTS `#__helloworld`;
 
DROP TABLE IF EXISTS `#__helloworld`;
 
   
 
   
Line 507: Line 508:
 
   `params` TEXT NOT NULL DEFAULT '',
 
   `params` TEXT NOT NULL DEFAULT '',
 
   PRIMARY KEY  (`id`)
 
   PRIMARY KEY  (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
+
);
 
   
 
   
 
INSERT INTO `#__helloworld` (`greeting`) VALUES
 
INSERT INTO `#__helloworld` (`greeting`) VALUES
Line 520: Line 521:
 
<span id="admin/sql/updates/mysql/0.0.14.sql">
 
<span id="admin/sql/updates/mysql/0.0.14.sql">
 
''admin/sql/updates/mysql/0.0.14.sql''
 
''admin/sql/updates/mysql/0.0.14.sql''
<source lang="php">
+
<source lang="mysql">
 
ALTER TABLE`#__helloworld` ADD COLUMN `asset_id` INT(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `id`;
 
ALTER TABLE`#__helloworld` ADD COLUMN `asset_id` INT(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `id`;
 
</source>
 
</source>
 
</span>
 
</span>
  
=== Setting the value in the assets table ===
+
=== Restricting access to the messages ===
  
In order to set asset for each message, we have to do two things.
+
So far we have restricted access to the component itself, but we also need to do that at ''message'' level.
  
First, the 'core.edit' ACL "right" is taken from the message itself, then from the component.
+
To check the '''"core.delete"''' permission you need to modify the model class: ''admin/models/helloworld.php'' by adding these lines:
  
<span id="admin/models/helloworld.php">
+
<source lang='php'>
''admin/models/helloworld.php''
+
        /**
<source lang="php">
+
        * Method to check if it's OK to delete a message. Overwrites JModelAdmin::canDelete
<?php
+
        */
// No direct access to this file
+
        protected function canDelete($record)
defined('_JEXEC') or die('Restricted access');
+
        {
 +
            if( !empty( $record->id ) ){
 +
                $user = JFactory::getUser();
 +
                return $user->authorise( "core.delete", "com_helloworld.message." . $record->id );
 +
            }
 +
        }
 +
</source>
  
// import Joomla modelform library
+
To check '''"core.edit"''' (and '''core.add''' if you wish) you need to update the sub-controller (''not'' the model). I am not sure why this is so, but that's how other standard Joomla components do it. You need to add the following lines in the file: ''/admin/controllers/helloworld.php''
jimport('joomla.application.component.modeladmin');
+
 
 +
<source lang='php'>
 +
 
 +
    /**
 +
    * Implement to allowAdd or not
 +
    *
 +
    * Not used at this time (but you can look at how other components use it....)
 +
    * Overwrites: JControllerForm::allowAdd
 +
    *
 +
    * @param array $data
 +
    * @return bool
 +
    */
 +
    protected function allowAdd($data = array())
 +
    {
 +
        return parent::allowAdd($data);
 +
    }
 +
 
 +
    /**
 +
    * Implement to allow edit or not
 +
    * Overwrites: JControllerForm::allowEdit
 +
    *
 +
    * @param array $data
 +
    * @param string $key
 +
    * @return bool
 +
    */
 +
    protected function allowEdit($data = array(), $key = 'id')
 +
    {
 +
        $id = isset( $data[ $key ] ) ? $data[ $key ] : 0;
 +
        if( !empty( $id ) ){
 +
            $user = JFactory::getUser();
 +
            return $user->authorise( "core.edit", "com_helloworld.message." . $id );
 +
        }
 +
    }
  
/**
 
* HelloWorld Model
 
*/
 
class HelloWorldModelHelloWorld extends JModelAdmin
 
{
 
/**
 
* Method override to check if you can edit an existing record.
 
*
 
* @param array $data An array of input data.
 
* @param string $key The name of the key for the primary key.
 
*
 
* @return boolean
 
* @since 2.5
 
*/
 
protected function allowEdit($data = array(), $key = 'id')
 
{
 
// Check specific edit permission then general edit permission.
 
return JFactory::getUser()->authorise('core.edit', 'com_helloworld.message.'.
 
                                      ((int) isset($data[$key]) ? $data[$key] : 0))
 
      or parent::allowEdit($data, $key);
 
}
 
/**
 
* 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
 
* @since 2.5
 
*/
 
public function getTable($type = 'HelloWorld', $prefix = 'HelloWorldTable', $config = array())
 
{
 
return JTable::getInstance($type, $prefix, $config);
 
}
 
/**
 
* Method to get the record form.
 
*
 
* @param array $data Data for the form.
 
* @param boolean $loadData True if the form is to load its own data (default case), false if not.
 
* @return mixed A JForm object on success, false on failure
 
* @since 2.5
 
*/
 
public function getForm($data = array(), $loadData = true)
 
{
 
// Get the form.
 
$form = $this->loadForm('com_helloworld.helloworld', 'helloworld',
 
                        array('control' => 'jform', 'load_data' => $loadData));
 
if (empty($form))
 
{
 
return false;
 
}
 
return $form;
 
}
 
/**
 
* Method to get the script that have to be included on the form
 
*
 
* @return string Script files
 
*/
 
public function getScript()
 
{
 
return 'administrator/components/com_helloworld/models/forms/helloworld.js';
 
}
 
/**
 
* Method to get the data that should be injected in the form.
 
*
 
* @return mixed The data for the form.
 
* @since 2.5
 
*/
 
protected function loadFormData()
 
{
 
// Check the session for previously entered form data.
 
$data = JFactory::getApplication()->getUserState('com_helloworld.edit.helloworld.data', array());
 
if (empty($data))
 
{
 
$data = $this->getItem();
 
}
 
return $data;
 
}
 
}
 
 
</source>
 
</source>
</span>
+
 
+
Please note that ''allowAdd'' simply calls its parent. I've put it here in case you want to actually use it in your component. If you look at your ''admin/access.xml'' file, you will notice there is no ''core.add'' action defined for "messages", so you will need to add it there as well if you want to be able to configure it in the interface.
Second, set the asset name, asset title and asset parent in the helloworld table.
+
 
 +
=== Setting the permission values in the assets table ===
 +
 
 +
In order to store permissions for each message in the assets database table, we have to instruct the table class associated with the model to save those permissions in the assets table.
 +
 
 +
JTable not only provides an interface for storing the data of the record itself in the item's database table, but also for storing the permissions for that record in the assets database table. Therefore we must add information to the bind()-method about the permission-values. We also  have to provide the asset name, asset title and the id of the asset parent via the helloworld JTable. Therefore we override 3 methods:
 +
* _getAssetName(): a unique name for this asset, by which it can be retrieved
 +
* _getAssetTitle(): a more human-friendly way to identify the asset (not necessary unique)
 +
* _getAssetParentId(): the asset_id of the parent in the asset database table (from whom permissions are inherited)
  
 
<span id="admin/tables/helloworld.php">
 
<span id="admin/tables/helloworld.php">
 
''admin/tables/helloworld.php''
 
''admin/tables/helloworld.php''
<source lang="php">
+
<source lang="php" highlight="41-48,75-130">
 
<?php
 
<?php
 
// No direct access
 
// No direct access
Line 649: Line 617:
 
parent::__construct('#__helloworld', 'id', $db);
 
parent::__construct('#__helloworld', 'id', $db);
 
}
 
}
 +
 
/**
 
/**
* Overloaded bind function
+
* Overridden bind function
 
*
 
*
 
* @param      array          named array
 
* @param      array          named array
* @return      null|string    null is operation was satisfactory, otherwise returns an error
+
* @return      null|string    null if operation was satisfactory, otherwise returns an error
 
* @see JTable:bind
 
* @see JTable:bind
 
* @since 1.5
 
* @since 1.5
Line 678: Line 647:
  
 
/**
 
/**
* Overloaded load function
+
* Overridden load function
 
*
 
*
 
* @param      int $pk primary key
 
* @param      int $pk primary key
Line 700: Line 669:
 
}
 
}
 
}
 
}
 +
 
/**
 
/**
 
* Method to compute the default name of the asset.
 
* Method to compute the default name of the asset.
Line 726: Line 696:
  
 
/**
 
/**
* Get the parent asset id for the record
+
* Method to get the asset-parent-id of the item
 
*
 
*
 
* @return int
 
* @return int
* @since 2.5
 
 
*/
 
*/
 
protected function _getAssetParentId()
 
protected function _getAssetParentId()
 
{
 
{
// Initialise variables.
+
// We will retrieve the parent-asset from the Asset-table
$parentAssetId = null;
+
$assetParent = JTable::getInstance('Asset');
 +
// Default: if no asset-parent can be found we take the global asset
 +
$assetParentId = $assetParent->getRootId();
  
// This message is under a category
+
// Find the parent-asset
 
if (($this->catid)&& !empty($this->catid))
 
if (($this->catid)&& !empty($this->catid))
 
{
 
{
// Build the query to get the asset id for the parent category.
+
// The item has a category as asset-parent
$query = $this->_db->getQuery(true);
+
$assetParent->loadByName('com_helloworld.category.' . (int) $this->catid);
$query->select($this->_db->quoteName('asset_id'));
+
$query->from($this->_db->quoteName('#__categories'));
+
$query->where($this->_db->quoteName('id') . ' = ' . (int) $this->catid);
+
 
+
// Get the asset id from the database.
+
$this->_db->setQuery($query);
+
if ($result = $this->_db->loadResult())
+
{
+
$parentAssetId = (int) $result;
+
}
+
 
}
 
}
 
+
else
// Return the parentAssetId
+
if ($parentAssetId )
+
 
{
 
{
return $parentAssetId ;
+
// The item has the component as asset-parent
 +
$assetParent->loadByName('com_helloworld');
 
}
 
}
else
+
 
 +
// Return the found asset-parent-id
 +
if ($assetParent->id)
 
{
 
{
// Otherwise: the parentAssetId is the asset_id of the component
+
$assetParentId=$assetParent->id;
$asset = JTable::getInstance('Asset');
+
$asset->loadByName('com_helloworld');
+
return $asset->id;
+
 
}
 
}
 +
return $assetParentId;
 
}
 
}
 
}
 
}
 
</source>
 
</source>
 
</span>
 
</span>
 +
 +
This code for _getAssetParentId() above uses JTableAsset to retrieve the asset_id of the asset-parent. This is different from the code in the current version of com_content, where the asset_id of the category is retrieved from the #__categories database table. That is another possibility; many ways leading to Rome. In com_content however, if an item would not be under a category, then the asset_id of the global asset is returned. That would of course not be right, but is fixed there by providing a default category "uncategorised", so that an article is ''always'' under a category. That is why you cannot just copy the code of _getAssetParentId() in com_content to your own component. The code above is more general.
  
 
=== Showing the setting of permissions on the item level ===
 
=== Showing the setting of permissions on the item level ===
Line 776: Line 738:
 
<span id="admin/models/forms/helloworld.xml">
 
<span id="admin/models/forms/helloworld.xml">
 
''admin/models/forms/helloworld.xml''
 
''admin/models/forms/helloworld.xml''
<source lang="php">
+
<source lang="xml">
 
     <fieldset name="accesscontrol">
 
     <fieldset name="accesscontrol">
 
     <field name="asset_id" type="hidden" filter="unset" />
 
     <field name="asset_id" type="hidden" filter="unset" />
Line 797: Line 759:
 
<span id="admin/views/helloworld/tmpl/edit.php">
 
<span id="admin/views/helloworld/tmpl/edit.php">
 
''admin/views/helloworld/tmpl/edit.php''
 
''admin/views/helloworld/tmpl/edit.php''
<source lang="php">
+
<source lang="php" highlight="41-59">
 
<?php
 
<?php
 
// No direct access
 
// No direct access
Line 837: Line 799:
 
       <?php echo JHtml::_('sliders.end'); ?>
 
       <?php echo JHtml::_('sliders.end'); ?>
 
   </div>
 
   </div>
 
  
 
   <!-- begin ACL definition-->
 
   <!-- begin ACL definition-->
Line 866: Line 827:
 
</source>
 
</source>
 
</span>
 
</span>
 
  
 
=== Adding language strings ===
 
=== Adding language strings ===
We used 3 language strings that have to be added to the back-end language-file.
+
We used 3 language strings that have to be added to the backend language-file.
  
 
<span id="admin/language/en-GB/en-GB.com_helloworld.ini">
 
<span id="admin/language/en-GB/en-GB.com_helloworld.ini">
 
''admin/language/en-GB/en-GB.com_helloworld.ini''
 
''admin/language/en-GB/en-GB.com_helloworld.ini''
<source lang="php">
+
<source lang="text">
 
COM_HELLOWORLD_FIELDSET_RULES="Message Permissions"
 
COM_HELLOWORLD_FIELDSET_RULES="Message Permissions"
 
COM_HELLOWORLD_ACCESS_DELETE_DESC="Is this group allowed to edit this message?"
 
COM_HELLOWORLD_ACCESS_DELETE_DESC="Is this group allowed to edit this message?"
Line 882: Line 842:
 
== Further reading ==
 
== Further reading ==
 
More information on actions, assets and ACL can be found on the following pages:
 
More information on actions, assets and ACL can be found on the following pages:
* [[ACL Tutorial for Joomla 1.6]]
+
* General information and use: [[Access Control List/1.6-2.5/Tutorial]]
 +
* Technical information, under construction: [[ACL Technique in Joomla!]]
 
* [[How to implement actions in your code]]
 
* [[How to implement actions in your code]]
TODO: add more references; split into general ACL and using it in your code.
+
* [[Adding ACL rules to your component]]
 +
 
 +
== Deprecated classes ==
 +
For the moment we leave the '''deprecated JError'''-references as they are. They will probably still be available in Joomla! 3.x. We cannot just change them to JLog::add() statements because in Joomla! 2.5 the messages will then not be enqueued (as there is no messagequeue-logger added as is in /libraries/cms.php in Joomla! 3.0). Other solutions, like using $app->enqueueMessage() or directly throwing PHP-exceptions as showstopper are also possible, but then there would still be numerous references to JError throughout the application. For instance in the view, we now check for errors raised in the model with: <span style="color:#900">count($errors = $this->get('Errors'))</span>, which uses JError from the JOBject that was the base for JModel. To get the same functionality without using JError at all, we would have to change the way the Model raises those errors and warnings now. If we want to make an application that would work in Joomla! 2.5 and 3.x we can continue using JError. The moment we want to use our 3.x extensions also in 4.x we will have to change this (and probably a lot more too). This tutorial is now primarily focussed on Joomla! 2.5. So: we notice the upcoming change, but leave it there for the moment.
 +
 
 +
Since Joomla! 2.5.5 the MVC-base-classes JController, JModel and JView got proxies JControllerLegacy, JModelLegacy and JViewLegacy. You are recommended to use those proxies instead of the original classes to be forward compatibility with Joomla! CMS 3.x legacy classes.
  
 
== Packaging the component ==
 
== Packaging the component ==
Line 956: Line 922:
 
* ''media/images/tux-48x48.png''
 
* ''media/images/tux-48x48.png''
  
Create a compressed file of this directory or directly download the [http://joomlacode.org/gf/download/frsrelease/11394/58412/com_helloworld-1.6-part14.zip archive] (TODO: zip has to be updated! Will be done coming days...) and install it using the extension manager of Joomla. You can add a menu item of this component using the menu manager in the back-end.
+
Create a compressed file of this directory or directly download the [http://joomlacode.org/gf/download/frsrelease/11394/58412/com_helloworld-1.6-part14.zip archive] (TODO: zip has to be updated! Will be done coming days...) and install it using the extension manager of Joomla. You can add a menu item of this component using the menu manager in the backend.
  
 
<span id="helloworld.xml">
 
<span id="helloworld.xml">
Line 1,048: Line 1,014:
 
</source>
 
</source>
 
</span>
 
</span>
 
== Zips ==
 
Download the zip file for this Part:
 
[http://www.leyar.com/joomlaorg/part14.zip]= 404!!!, TODO: make new zip available
 
  
 
== Navigate ==
 
== Navigate ==
Line 1,062: Line 1,024:
 
*[[User:florian_denizot|Florian Denizot]]
 
*[[User:florian_denizot|Florian Denizot]]
 
*[[User:HermanPeeren|Herman Peeren]]
 
*[[User:HermanPeeren|Herman Peeren]]
 
+
* TDZweb (Ståle Rolf Sæbøe), Elin Waring, Neil via https://groups.google.com/forum/#!topic/joomla-dev-general/aCH3GNA91m4
TODO: add
+
  
 
[[Category:Development]]
 
[[Category:Development]]
[[category:Joomla! 1.6]]
+
[[Category:Joomla! 1.6]]
[[category:Joomla! 1.7]]
+
[[Category:Joomla! 1.7]]
[[category:Joomla! 2.5]]
+
[[Category:Joomla! 2.5]]
[[category:Manual]]
+

Revision as of 07:11, 11 October 2012

Contents


Introduction

This tutorial is part of the Developing a Model-View-Controller (MVC) Component for Joomla!2.5 tutorial. You are encouraged to read the previous parts of the tutorial before reading this.

Adding Access Control

With Joomla!'s Access Control we can define which user groups are allowed or denied to do which actions in your component. In this example we use actions that are defined in the core. For the component as a whole: core.admin (access to the configuration) and core.manage (access to the backend). And at various levels actions like create, delete and edit. Besides those core actions you can define your own actions, but that is often not necessary and is not shown in this example. View/Read Access is not managed via those actions but with View Access Levels; see general documentation about Joomla!'s ACL for that.

In the #__assets table the actual list is stored: which user groups are allowed or denied to do which actions on which resources (assets). This is the implementation of the Access Control List (ACL).

In this article we will show how to add and use access permissions at several levels of granularity: for your component as a whole, for the categories and for the individual items.

Minimal ACL requirements at the component level

There are 2 actions that need to be defined at the component level for a Joomla! 2.5 component to offer basic ACL support:

  • Configure (core.admin): which groups are allowed to configure the component level permissions via the 'Options' toolbar button?
  • Access Component (core.manage): which groups are allowed to access the component's backend?

This basic ACL support is done in 4 simple steps:


Add the 2 minimal component level actions to access.xml

Add an access.xml file to the root of the admin folder. Put the 2 basic actions for the com_helloworld component in this file.

admin/access.xml

<?xml version="1.0" encoding="utf-8" ?>
<access component="com_helloworld">
        <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" />
        </section>
</access>

Add the permissions fieldset to config.xml

Add the following permissions fieldset to admin/config.xml in order to be able to set our component level permissions

        <fieldset
                name="permissions"
                label="JCONFIG_PERMISSIONS_LABEL"
                description="JCONFIG_PERMISSIONS_DESC"
        >
                <field
                        name="rules"
                        type="rules"
                        label="JCONFIG_PERMISSIONS_LABEL"
                        class="inputbox"
                        validate="rules"
                        filter="rules"
                        component="com_helloworld"
                        section="component"
                />
        </fieldset>

See the more elaborate config.xml example further downwards for the exact place where to insert this code.

Add the 'Options' toolbar button when user is authorised for it

Add the following code to admin/views/helloworlds/view.html.php:

    // Options button.
    if (JFactory::getUser()->authorise('core.admin', 'com_helloworld'))     {        JToolBarHelper::preferences('com_helloworld');
    }

See further downwards for a more elaborated example of admin/views/helloworlds/view.html.php where this JToolBarHelper::preferences('com_helloworld') is done in an addToolBar()-method together with the other toolbar buttons and the JUser->authorise()-check is done in the admin/helpers/helloworld.php helper file, resulting in the $canDo-property.

Restrict the access to the component's backend to authorised usergroups

To control the access to the backend of the component add the following lines to the admin/helloworld.php entry-file:

// Access check: is this user allowed to access the backend of this component?
if (!JFactory::getUser()->authorise('core.manage', 'com_helloworld')) 
{
        return JError::raiseWarning(404, JText::_('JERROR_ALERTNOAUTHOR'));
}

See further downwards for the whole code of the admin/helloworld.php file.

Adding more actions, also at category level and item level

When adding more actions and more levels, the above described 4 steps are done too:

In addition we also have to do the following steps:

Describing the actions you want to control the access to

Each component (or part of it) has its own set of permissions that can be controlled. They are described in an access.xml file located at the root of the admin folder. In this helloworld-example the actions to which access is controlled are divided in three sections: at the component level, the category level and the item level. An 'item' is called a 'message' in our example component, hence the name of the third section.

admin/access.xml

<?xml version="1.0" encoding="utf-8" ?>
<access component="com_helloworld">
        <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" />
        </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>
        <section name="message">
                <action name="core.delete" title="JACTION_DELETE" description="COM_HELLOWORLD_ACCESS_DELETE_DESC" />
                <action name="core.edit" title="JACTION_EDIT" description="COM_HELLOWORLD_ACCESS_EDIT_DESC" />
        </section>
</access>

Adding the setting of permissions in the component's Preferences

Since we now use Access Control permissions in our component, we need to be able to set them at the component level. That is done in the Preferences of this component: the screen you see after clicking the 'Options' button. The config.xml-file is a form-definition for those Preferences. We could define the component level actions here too, as a child of the "rules" field-tag, but it is now preferred to also put those actions in access.xml: in that way all access rules for this component are on one spot.

admin/config.xml

<?xml version="1.0" encoding="utf-8"?>
<config>
        <fieldset
                name="greetings"
                label="COM_HELLOWORLD_CONFIG_GREETING_SETTINGS_LABEL"
                description="COM_HELLOWORLD_CONFIG_GREETING_SETTINGS_DESC"
        >
                <field
                        name="show_category"
                        type="radio"
                        label="COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_LABEL"
                        description="COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_DESC"
                        default="0"
                >
                        <option value="0">JHIDE</option>
                        <option value="1">JSHOW</option>
                </field>
        </fieldset>
        <fieldset                name="permissions"                label="JCONFIG_PERMISSIONS_LABEL"                description="JCONFIG_PERMISSIONS_DESC"        >                <field                        name="rules"                        type="rules"                        label="JCONFIG_PERMISSIONS_LABEL"                        class="inputbox"                        validate="rules"                        filter="rules"                        component="com_helloworld"                        section="component"                />        </fieldset></config>

Displaying only the right toolbar buttons

Which toolbar buttons to display depends on the Access Control permissions for the user. We put all permissions for this user in the $canDo property of the view; so we can eventually refer to it in layouts (in the edit-form for example).

In the admin/views/helloworlds/view.html.php, put this code

admin/views/helloworlds/view.html.php

<?php
// No direct access to this file
defined('_JEXEC') or die('Restricted access');
 
// import Joomla view library
jimport('joomla.application.component.view');
 
/**
 * HelloWorlds View
 */
class HelloWorldViewHelloWorlds extends JViewLegacy
{
        protected $items;
        protected $pagination;
        protected $canDo; 
        /**
         * HelloWorlds view display method
         * @return void
         */
        function display($tpl = null) 
        {
                // Get data from the model
                $this->items = $this->get('Items');
                $this->pagination = $this->get('Pagination');
 
                // What Access Permissions does this user have? What can (s)he do?                $this->canDo = HelloWorldHelper::getActions(); 
                // Check for errors
                if (count($errors = $this->get('Errors'))) 
                {
                        JError::raiseError(500, implode('<br />', $errors));
                        return false;
                }
 
                // Set the toolbar
                $this->addToolBar();
 
                // Display the template
                parent::display($tpl);
 
                // Set the document
                $this->setDocument();
        }
 
        /**
         * Setting the toolbar
         */
        protected function addToolBar() 
        {
                JToolBarHelper::title(JText::_('COM_HELLOWORLD_MANAGER_HELLOWORLDS'), 'helloworld');
                if ($this->canDo->get('core.create'))                 {                        JToolBarHelper::addNew('helloworld.add', 'JTOOLBAR_NEW');
                }                if ($this->canDo->get('core.edit'))                 {                        JToolBarHelper::editList('helloworld.edit', 'JTOOLBAR_EDIT');
                }                if ($this->canDo->get('core.delete'))                 {                        JToolBarHelper::deleteList('', 'helloworlds.delete', 'JTOOLBAR_DELETE');
                }                if ($this->canDo->get('core.admin'))                 {                        JToolBarHelper::divider();
                        JToolBarHelper::preferences('com_helloworld');
                }        }
        /**
         * Method to set up the document properties
         *
         * @return void
         */
        protected function setDocument() 
        {
                $document = JFactory::getDocument();
                $document->setTitle(JText::_('COM_HELLOWORLD_ADMINISTRATION'));
        }
}

In the admin/views/helloworld/view.html.php, put this code

admin/views/helloworld/view.html.php

<?php
// No direct access to this file
defined('_JEXEC') or die('Restricted access');
 
// import Joomla view library
jimport('joomla.application.component.view');
 
/**
 * HelloWorld View
 */
class HelloWorldViewHelloWorld extends JViewLegacy
{
        protected $form;
        protected $item;
        protected $script;
        protected $canDo; 
        /**
         * display method of Hello view
         * @return void
         */
        public function display($tpl = null) 
        {
                // get the Data
                $this->form = $this->get('Form');
                $this->item = $this->get('Item');
                $this->script = $this->get('Script');
 
                // What Access Permissions does this user have? What can (s)he do?                $this->canDo = HelloWorldHelper::getActions($this->item->id); 
                // Check for errors
                if (count($errors = $this->get('Errors'))) 
                {
                        JError::raiseError(500, implode('<br />', $errors));
                        return false;
                }
 
                // Set the toolbar
                $this->addToolBar();
 
                // Display the template
                parent::display($tpl);
 
                // Set the document
                $this->setDocument();
        }
 
        /**
         * Setting the toolbar
         */
        protected function addToolBar() 
        {
                $input = JFactory::getApplication()->input;
                $input->set('hidemainmenu', true);
                $user = JFactory::getUser();
                $userId = $user->id;
                $isNew = $this->item->id == 0;
                JToolBarHelper::title($isNew ? JText::_('COM_HELLOWORLD_MANAGER_HELLOWORLD_NEW')
                                             : JText::_('COM_HELLOWORLD_MANAGER_HELLOWORLD_EDIT'), 'helloworld');
                // Build the actions for new and existing records.
                if ($isNew) 
                {
                        // For new records, check the create permission.
                        if ($this->canDo->get('core.create'))                         {                                JToolBarHelper::apply('helloworld.apply', 'JTOOLBAR_APPLY');
                                JToolBarHelper::save('helloworld.save', 'JTOOLBAR_SAVE');
                                JToolBarHelper::custom('helloworld.save2new', 'save-new.png', 'save-new_f2.png',
                                                       'JTOOLBAR_SAVE_AND_NEW', false);
                        }                        JToolBarHelper::cancel('helloworld.cancel', 'JTOOLBAR_CANCEL');
                }
                else
                {
                        if ($this->canDo->get('core.edit'))                        {                                // We can save the new record
                                JToolBarHelper::apply('helloworld.apply', 'JTOOLBAR_APPLY');
                                JToolBarHelper::save('helloworld.save', 'JTOOLBAR_SAVE');
 
                                // We can save this record, but check the create permission to see
                                // if we can return to make a new one.
                                if ($this->canDo->get('core.create'))                                 {                                        JToolBarHelper::custom('helloworld.save2new', 'save-new.png', 'save-new_f2.png',
                                                               'JTOOLBAR_SAVE_AND_NEW', false);
                                }                        }                        if ($this->canDo->get('core.create'))                         {                                JToolBarHelper::custom('helloworld.save2copy', 'save-copy.png', 'save-copy_f2.png',
                                                       'JTOOLBAR_SAVE_AS_COPY', false);
                        }                        JToolBarHelper::cancel('helloworld.cancel', 'JTOOLBAR_CLOSE');
                }
        }
        /**
         * Method to set up the document properties
         *
         * @return void
         */
        protected function setDocument() 
        {
                $isNew = $this->item->id == 0;
                $document = JFactory::getDocument();
                $document->setTitle($isNew ? JText::_('COM_HELLOWORLD_HELLOWORLD_CREATING')
                                           : JText::_('COM_HELLOWORLD_HELLOWORLD_EDITING'));
                $document->addScript(JURI::root() . $this->script);
                $document->addScript(JURI::root() . "/administrator/components/com_helloworld"
                                                  . "/views/helloworld/submitbutton.js");
                JText::script('COM_HELLOWORLD_HELLOWORLD_ERROR_UNACCEPTABLE');
        }
}

These two files use the getActions method defined in the admin/helpers/helloworld.php file

In the helper-file, put this code:

admin/helpers/helloworld.php

<?php
// No direct access to this file
defined('_JEXEC') or die;
 
/**
 * HelloWorld component helper.
 */
abstract class HelloWorldHelper
{
        /**
         * Configure the Linkbar.
         */
        public static function addSubmenu($submenu) 
        {
                JSubMenuHelper::addEntry(JText::_('COM_HELLOWORLD_SUBMENU_MESSAGES'),
                                         'index.php?option=com_helloworld', $submenu == 'messages');
                JSubMenuHelper::addEntry(JText::_('COM_HELLOWORLD_SUBMENU_CATEGORIES'),
                                         'index.php?option=com_categories&view=categories&extension=com_helloworld',
                                         $submenu == 'categories');
                // set some global property
                $document = JFactory::getDocument();
                $document->addStyleDeclaration('.icon-48-helloworld ' .
                                               '{background-image: url(../media/com_helloworld/images/tux-48x48.png);}');
                if ($submenu == 'categories') 
                {
                        $document->setTitle(JText::_('COM_HELLOWORLD_ADMINISTRATION_CATEGORIES'));
                }
        }
 
        /**         * Get the actions         */        public static function getActions($messageId = 0)        {                       jimport('joomla.access.access');                $user   = JFactory::getUser();                $result = new JObject;                 if (empty($messageId)) {                        $assetName = 'com_helloworld';                }                else {                        $assetName = 'com_helloworld.message.'.(int) $messageId;                }                 $actions = JAccess::getActions('com_helloworld', 'component');                 foreach ($actions as $action) {                        $result->set($action->name, $user->authorise($action->name, $assetName));                }                 return $result;        }}

Restricting access to the component

The main idea in ACL is to restrict actions to groups of users. The first action to be restricted is access to the administrative backend of the component itself. With your favorite file editor, edit the admin/helloworld.php file and add the lines with the access check.

admin/helloworld.php

<?php
// No direct access to this file
defined('_JEXEC') or die('Restricted access');
 
// Access check: is this user allowed to access the backend of this component?if (!JFactory::getUser()->authorise('core.manage', 'com_helloworld')) {        return JError::raiseWarning(404, JText::_('JERROR_ALERTNOAUTHOR'));} 
// require helper file
JLoader::register('HelloWorldHelper', dirname(__FILE__) . '/helpers/helloworld.php');
 
// import joomla controller library
jimport('joomla.application.component.controller');
 
// Get an instance of the controller prefixed by HelloWorld
$controller = JController::getInstance('HelloWorld');
 
// Perform the Request task
$input = JFactory::getApplication()->input;
$controller->execute($input->getCmd('task'));
 
// Redirect if set by the controller
$controller->redirect();

Add the asset_id column to the database table

In order to be able to work with JTable an asset_id column has to be added to the database #__helloworld table.

So, admin/sql/install.mysql.utf8.sql becomes:

admin/sql/install.mysql.utf8.sql

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

For updates we add a sql-update-file:

admin/sql/updates/mysql/0.0.14.sql

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

Restricting access to the messages

So far we have restricted access to the component itself, but we also need to do that at message level.

To check the "core.delete" permission you need to modify the model class: admin/models/helloworld.php by adding these lines:

        /**
         * Method to check if it's OK to delete a message. Overwrites JModelAdmin::canDelete
         */
        protected function canDelete($record)
        {
            if( !empty( $record->id ) ){
                $user = JFactory::getUser();
                return $user->authorise( "core.delete", "com_helloworld.message." . $record->id );
            }
        }

To check "core.edit" (and core.add if you wish) you need to update the sub-controller (not the model). I am not sure why this is so, but that's how other standard Joomla components do it. You need to add the following lines in the file: /admin/controllers/helloworld.php

    /**
     * Implement to allowAdd or not
     *
     * Not used at this time (but you can look at how other components use it....)
     * Overwrites: JControllerForm::allowAdd
     *
     * @param array $data
     * @return bool
     */
    protected function allowAdd($data = array())
    {
        return parent::allowAdd($data);
    }
 
    /**
     * Implement to allow edit or not
     * Overwrites: JControllerForm::allowEdit
     *
     * @param array $data
     * @param string $key
     * @return bool
     */
    protected function allowEdit($data = array(), $key = 'id')
    {
        $id = isset( $data[ $key ] ) ? $data[ $key ] : 0;
        if( !empty( $id ) ){
            $user = JFactory::getUser();
            return $user->authorise( "core.edit", "com_helloworld.message." . $id );
        }
    }

Please note that allowAdd simply calls its parent. I've put it here in case you want to actually use it in your component. If you look at your admin/access.xml file, you will notice there is no core.add action defined for "messages", so you will need to add it there as well if you want to be able to configure it in the interface.

Setting the permission values in the assets table

In order to store permissions for each message in the assets database table, we have to instruct the table class associated with the model to save those permissions in the assets table.

JTable not only provides an interface for storing the data of the record itself in the item's database table, but also for storing the permissions for that record in the assets database table. Therefore we must add information to the bind()-method about the permission-values. We also have to provide the asset name, asset title and the id of the asset parent via the helloworld JTable. Therefore we override 3 methods:

  • _getAssetName(): a unique name for this asset, by which it can be retrieved
  • _getAssetTitle(): a more human-friendly way to identify the asset (not necessary unique)
  • _getAssetParentId(): the asset_id of the parent in the asset database table (from whom permissions are inherited)

admin/tables/helloworld.php

<?php
// No direct access
defined('_JEXEC') or die('Restricted access');
 
// import Joomla table library
jimport('joomla.database.table');
 
/**
 * Hello Table class
 */
class HelloWorldTableHelloWorld extends JTable
{
        /**
         * Constructor
         *
         * @param object Database connector object
         */
        function __construct(&$db) 
        {
                parent::__construct('#__helloworld', 'id', $db);
        }
 
        /**
         * Overridden bind function
         *
         * @param       array           named array
         * @return      null|string     null if operation was satisfactory, otherwise returns an error
         * @see JTable:bind
         * @since 1.5
         */
        public function bind($array, $ignore = '') 
        {
                if (isset($array['params']) && is_array($array['params'])) 
                {
                        // Convert the params field to a string.
                        $parameter = new JRegistry;
                        $parameter->loadArray($array['params']);
                        $array['params'] = (string)$parameter;
                }
 
                // Bind the rules.                if (isset($array['rules']) && is_array($array['rules']))                {                        $rules = new JAccessRules($array['rules']);                        $this->setRules($rules);                }                 return parent::bind($array, $ignore);        }
 
        /**
         * Overridden load function
         *
         * @param       int $pk primary key
         * @param       boolean $reset reset data
         * @return      boolean
         * @see JTable:load
         */
        public function load($pk = null, $reset = true) 
        {
                if (parent::load($pk, $reset)) 
                {
                        // Convert the params field to a registry.
                        $params = new JRegistry;
                        $params->loadJSON($this->params);
                        $this->params = $params;
                        return true;
                }
                else
                {
                        return false;
                }
        }
 
        /**         * Method to compute the default name of the asset.         * The default name is in the form `table_name.id`         * where id is the value of the primary key of the table.         *         * @return      string         * @since       2.5         */        protected function _getAssetName()        {                $k = $this->_tbl_key;                return 'com_helloworld.message.'.(int) $this->$k;        }         /**         * Method to return the title to use for the asset table.         *         * @return      string         * @since       2.5         */        protected function _getAssetTitle()        {                return $this->greeting;        }         /**         * Method to get the asset-parent-id of the item         *         * @return      int         */        protected function _getAssetParentId()        {                // We will retrieve the parent-asset from the Asset-table                $assetParent = JTable::getInstance('Asset');                // Default: if no asset-parent can be found we take the global asset                $assetParentId = $assetParent->getRootId();                 // Find the parent-asset                if (($this->catid)&& !empty($this->catid))                {                        // The item has a category as asset-parent                        $assetParent->loadByName('com_helloworld.category.' . (int) $this->catid);                }                else                {                        // The item has the component as asset-parent                        $assetParent->loadByName('com_helloworld');                }                 // Return the found asset-parent-id                if ($assetParent->id)                {                        $assetParentId=$assetParent->id;                }                return $assetParentId;        }}

This code for _getAssetParentId() above uses JTableAsset to retrieve the asset_id of the asset-parent. This is different from the code in the current version of com_content, where the asset_id of the category is retrieved from the #__categories database table. That is another possibility; many ways leading to Rome. In com_content however, if an item would not be under a category, then the asset_id of the global asset is returned. That would of course not be right, but is fixed there by providing a default category "uncategorised", so that an article is always under a category. That is why you cannot just copy the code of _getAssetParentId() in com_content to your own component. The code above is more general.

Showing the setting of permissions on the item level

Adding the rules field to the form-definition of the edit-form

admin/models/forms/helloworld.xml

    <fieldset name="accesscontrol">
        <field name="asset_id" type="hidden" filter="unset" />
        <field name="rules"
                type="rules"
                label="JFIELD_RULES_LABEL"
                translate_label="false"
                filter="rules"
                validate="rules"
                class="inputbox"
                component="com_helloworld"
                section="message"
        />
    </fieldset>

And display the ACL interface at the bottom of your Helloworld editform

admin/views/helloworld/tmpl/edit.php

<?php
// No direct access
defined('_JEXEC') or die('Restricted access');
JHtml::_('behavior.tooltip');
JHtml::_('behavior.formvalidation');
$params = $this->form->getFieldsets('params');
?>
<form action="<?php echo JRoute::_('index.php?option=com_helloworld&layout=edit&id='.(int) $this->item->id); ?>"
      method="post" name="adminForm" id="helloworld-form" class="form-validate">
 
   <div class="width-60 fltlft">
      <fieldset class="adminform">
         <legend><?php echo JText::_( 'COM_HELLOWORLD_HELLOWORLD_DETAILS' ); ?></legend>
         <ul class="adminformlist">
            <?php foreach($this->form->getFieldset('details') as $field): ?>
               <li><?php echo $field->label;echo $field->input;?></li>
            <?php endforeach; ?>
         </ul>
      </fieldset>
   </div>
 
   <div class="width-40 fltrt">
      <?php echo JHtml::_('sliders.start', 'helloworld-slider');
         foreach ($params as $name => $fieldset):
            echo JHtml::_('sliders.panel', JText::_($fieldset->label), $name.'-params');
            if (isset($fieldset->description) && trim($fieldset->description)): ?>
               <p class="tip"><?php echo $this->escape(JText::_($fieldset->description));?></p>
            <?php endif;?>
            <fieldset class="panelform" >
               <ul class="adminformlist">
                  <?php foreach ($this->form->getFieldset($name) as $field) : ?>
                     <li><?php echo $field->label; ?><?php echo $field->input; ?></li>
                  <?php endforeach; ?>
               </ul>
            </fieldset>
         <?php endforeach; ?>
 
      <?php echo JHtml::_('sliders.end'); ?>
   </div>
 
   <!-- begin ACL definition-->    <div class="clr"></div>    <?php if ($this->canDo->get('core.admin')): ?>      <div class="width-100 fltlft">         <?php echo JHtml::_('sliders.start', 'permissions-sliders-'.$this->item->id, array('useCookie'=>1)); ?>             <?php echo JHtml::_('sliders.panel', JText::_('COM_HELLOWORLD_FIELDSET_RULES'), 'access-rules'); ?>            <fieldset class="panelform">               <?php echo $this->form->getLabel('rules'); ?>               <?php echo $this->form->getInput('rules'); ?>            </fieldset>          <?php echo JHtml::_('sliders.end'); ?>      </div>   <?php endif; ?>    <!-- end ACL definition--> 
   <div>
      <input type="hidden" name="task" value="helloworld.edit" />
      <?php echo JHtml::_('form.token'); ?>
   </div>
</form>

Adding language strings

We used 3 language strings that have to be added to the backend language-file.

admin/language/en-GB/en-GB.com_helloworld.ini

COM_HELLOWORLD_FIELDSET_RULES="Message Permissions"
COM_HELLOWORLD_ACCESS_DELETE_DESC="Is this group allowed to edit this message?"
COM_HELLOWORLD_ACCESS_DELETE_DESC="Is this group allowed to delete this message?"

Further reading

More information on actions, assets and ACL can be found on the following pages:

Deprecated classes

For the moment we leave the deprecated JError-references as they are. They will probably still be available in Joomla! 3.x. We cannot just change them to JLog::add() statements because in Joomla! 2.5 the messages will then not be enqueued (as there is no messagequeue-logger added as is in /libraries/cms.php in Joomla! 3.0). Other solutions, like using $app->enqueueMessage() or directly throwing PHP-exceptions as showstopper are also possible, but then there would still be numerous references to JError throughout the application. For instance in the view, we now check for errors raised in the model with: count($errors = $this->get('Errors')), which uses JError from the JOBject that was the base for JModel. To get the same functionality without using JError at all, we would have to change the way the Model raises those errors and warnings now. If we want to make an application that would work in Joomla! 2.5 and 3.x we can continue using JError. The moment we want to use our 3.x extensions also in 4.x we will have to change this (and probably a lot more too). This tutorial is now primarily focussed on Joomla! 2.5. So: we notice the upcoming change, but leave it there for the moment.

Since Joomla! 2.5.5 the MVC-base-classes JController, JModel and JView got proxies JControllerLegacy, JModelLegacy and JViewLegacy. You are recommended to use those proxies instead of the original classes to be forward compatibility with Joomla! CMS 3.x legacy classes.

Packaging the component

Content of your code directory

Create a compressed file of this directory or directly download the archive (TODO: zip has to be updated! Will be done coming days...) and install it using the extension manager of Joomla. You can add a menu item of this component using the menu manager in the backend.

helloworld.xml

<?xml version="1.0" encoding="utf-8"?>
<extension type="component" version="2.5.0" method="upgrade">
 
        <name>COM_HELLOWORLD</name>
        <!-- The following elements are optional and free of formatting constraints -->
        <creationDate>November 2009</creationDate>
        <author>John Doe</author>
        <authorEmail>john.doe@example.org</authorEmail>
        <authorUrl>http://www.example.org</authorUrl>
        <copyright>Copyright Info</copyright>
        <license>License Info</license>
        <!--  The version string is recorded in the components table -->
        <version>0.0.14</version>
        <!-- The description is optional and defaults to the name -->
        <description>COM_HELLOWORLD_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>
        <update> <!-- Runs on update; New in 2.5 -->
                <schemas>
                        <schemapath type="mysql">sql/updates/mysql</schemapath>
                </schemas>
        </update>
 
        <!-- Site Main File Copy Section -->
        <!-- Note the folder attribute: This attribute describes the folder
                to copy FROM in the package to install therefore files copied
                in this section are copied from /site/ in the package -->
        <files folder="site">
                <filename>index.html</filename>
                <filename>helloworld.php</filename>
                <filename>controller.php</filename>
                <folder>views</folder>
                <folder>models</folder>
                <folder>language</folder>
        </files>
 
        <media destination="com_helloworld" folder="media">
                <filename>index.html</filename>
                <folder>images</folder>
        </media>
 
        <administration>
                <!-- Administration Menu Section -->
                <menu img="../media/com_helloworld/images/tux-16x16.png">COM_HELLOWORLD_MENU</menu>
                <!-- Administration Main File Copy Section -->
                <!-- Note the folder attribute: This attribute describes the folder
                        to copy FROM in the package to install therefore files copied
                        in this section are copied from /admin/ in the package -->
                <files folder="admin">
                        <!-- Admin Main File Copy Section -->
                        <filename>index.html</filename>
                        <filename>config.xml</filename>
                        <filename>access.xml</filename>
                        <filename>helloworld.php</filename>
                        <filename>controller.php</filename>
                        <!-- SQL files section -->
                        <folder>sql</folder>
                        <!-- tables files section -->
                        <folder>tables</folder>
                        <!-- models files section -->
                        <folder>models</folder>
                        <!-- views files section -->
                        <folder>views</folder>
                        <!-- controllers files section -->
                        <folder>controllers</folder>
                        <!-- helpers files section -->
                        <folder>helpers</folder>
                </files>
 
                <languages folder="admin">
                        <language tag="en-GB">language/en-GB/en-GB.com_helloworld.ini</language>
                        <language tag="en-GB">language/en-GB/en-GB.com_helloworld.sys.ini</language>
                </languages>
        </administration>
 
</extension>

Navigate

Prev: Adding configuration Next: Adding an install/uninstall/update script file

Contributors