J3.x:Developing an MVC Component/Adding Access
From Joomla! Documentation
Articles in This Series
- Introduction
- Developing a Basic Component
- Adding a View to the Site Part
- Adding a Menu Type to the Site Part
- Adding a Model to the Site Part
- Adding a Variable Request in the Menu Type
- Using the Database
- Basic Backend
- Adding Language Management
- Adding Backend Actions
- Adding Decorations to the Backend
- Adding Verifications
- Adding Categories
- Adding Configuration
- Adding ACL
- Adding an Install/Uninstall/Update Script File
- Adding a Frontend Form
- Adding an Image
- Adding a Map
- Adding AJAX
- Adding an Alias
- Using the Language Filter Facility
- Adding a Modal
- Adding Associations
- Adding Checkout
- Adding Ordering
- Adding Levels
- Adding Versioning
- Adding Tags
- Adding Access
- Adding a Batch Process
- Adding Cache
- Adding a Feed
- Adding an Update Server
- Adding Custom Fields
- Upgrading to Joomla4
This is a multiple-article series of tutorials on how to develop a Model-View-Controller Component for Joomla! Version
.
Begin with the Introduction, and navigate the articles in this series by using the navigation button at the bottom or the box to the right (the Articles in this series).
This tutorial is part of the Developing an MVC Component for Joomla! 3.2 tutorial. You are encouraged to read the previous parts of the tutorial before reading this.
In this step we add access capability to our component, which allows us to restrict the set of users who can view our helloworld data. This step really fits before Adding ACL, but is added belatedly for completeness here.
There is a video accompanying this step (which was originally produced at the same time as the other videos for ACL) available at Access Levels.
Under Construction!
Introduction
If you're not familiar with Access within Joomla, then it's worthwhile playing around with the functionality – setting access on menuitems and articles, setting the accesslevels of user groups (and hence of users), and confirming that users can see only the items to which they have been granted access. This works on both the site front end, and the admin back end.
The diagram shows how Joomla user access levels work.

Each item – eg an article, a menuitem, or in our case a helloworld record – has an access level assigned.
Each user is assigned one or more user groups, and user groups have a hierarchical (tree) structure, so that if a user belongs to a user group which is a child of another, then that user is also a member of the parent user group.
Each access level has one more associated user groups, who will then have view access to items which have that access level.
When the user requests to view a particular item, then the functionality
- obtains the set of accesslevels that the user has
- checks if the accesslevel of the requested item is within the set of accesslevels of that user
- if it is, then the item is presented to the user
- if it isn't, then an HTTP response with status 403 (forbidden) is returned.
Approach
Database Changes
To enable us to have an Access Level against a helloworld record, we need to specify a new field in the database, which we'll call "access". We'll set the database default value of this access field to be 1, which means that by default everyone can see the record.
We also have a change to our content_types field mapping, as described below.
Admin back end changes
To enable the administrator to set this field, we will add an Access field within the back-end edit form, and we use the "accesslevel" field type within the Joomla standard form field types. As is often the case, this field will get automatically saved to the "access" field in the database by our existing code.
On our administrator helloworlds view we'll add a column to display the Access Level assigned to that item. As this should be displayed as a string rather than the int value in the helloworld record, we have to do a SQL JOIN find the associated title of the access level from where they're stored in the viewlevels record.
We will make this column sortable, so we must include options for it in the filter fields xml file and in the helloworlds model.
We also want to restrict the administrator to only those helloworld records that he/she has access to view, so this will involve a filter within the SQL query in our model.
The key Joomla method which we use to find the accesslevels which a user has is JUser::getAuthorisedViewLevels(), and for a user to be allowed to view an item, the item's accesslevel must be within the array of accesslevels returned by getAuthorisedViewLevels(). This is true for both front end and back end.
We will also follow the pattern in Joomla, which specifies that to view an item the user must have access to both the item itself and to the category (if any) associated with the item. Joomla already allows us to set an accesslevel against a category, and where categories are being displayed (both on front end and back end) it filters them to show only those which the user is entitled to see. However, we must write the code in the case where we're displaying helloworld records, to check that the user is entitled to view the associated category as well.
Front end changes
On the front end, whenever a single helloworld record is displayed, we will have to check that the user has access to that record, and if not, return an HTTP status 403.
If a number of helloworld records are being displayed, then we will change the SQL query to filter by the set of accesslevels which the user has.
Finally, as helloworld records may be viewed (to a certain extent) via the tags functionality, we update our field mappings so that our access field is copied into the ucm_content table. This means that the same restrictions will apply to viewing both the helloworld record and its copy in the ucm_content table.
Database
We add our new access field to the helloworld record, and include this in the set of fields which are mapped to the ucm_content copy.
admin/sql/updates/mysql/0.0.29.sql
ALTER TABLE `#__helloworld` ADD COLUMN `access` tinyint(4) NOT NULL DEFAULT '1' AFTER `published`;
UPDATE `#__content_types` SET
`field_mappings` =
'{"common": {
"core_content_item_id": "id",
"core_title": "greeting",
"core_state": "published",
"core_alias": "alias",
"core_created_time": "created",
"core_body": "description",
"core_access": "access",
"core_catid": "catid"
}}'
WHERE `type_alias` = 'com_helloworld.helloworld';
admin/sql/install.mysql.utf8.sql
DROP TABLE IF EXISTS `#__helloworld`;
CREATE TABLE `#__helloworld` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`asset_id` INT(10) NOT NULL DEFAULT '0',
`created` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
`created_by` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`checked_out` INT(10) NOT NULL DEFAULT '0',
`checked_out_time` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
`greeting` VARCHAR(25) NOT NULL,
`description` VARCHAR(4000) NOT NULL DEFAULT '',
`alias` VARCHAR(40) NOT NULL DEFAULT '',
`language` CHAR(7) NOT NULL DEFAULT '*',
`parent_id` int(10) NOT NULL DEFAULT '1',
`level` int(10) NOT NULL DEFAULT '0',
`path` VARCHAR(400) NOT NULL DEFAULT '',
`lft` int(11) NOT NULL DEFAULT '0',
`rgt` int(11) NOT NULL DEFAULT '0',
`published` tinyint(4) NOT NULL DEFAULT '1',
`access` tinyint(4) NOT NULL DEFAULT '1',
`catid` int(11) NOT NULL DEFAULT '0',
`params` VARCHAR(1024) NOT NULL DEFAULT '',
`image` VARCHAR(1024) NOT NULL DEFAULT '',
`latitude` DECIMAL(9,7) NOT NULL DEFAULT 0.0,
`longitude` DECIMAL(10,7) NOT NULL DEFAULT 0.0,
PRIMARY KEY (`id`)
)
ENGINE =MyISAM
AUTO_INCREMENT =0
DEFAULT CHARSET =utf8;
CREATE UNIQUE INDEX `aliasindex` ON `#__helloworld` (`alias`, `catid`);
INSERT INTO `#__helloworld` (`greeting`,`alias`,`language`, `parent_id`, `level`, `path`, `lft`, `rgt`) VALUES
('helloworld root','helloworld-root-alias','en-GB', 0, 0, '', 0, 5),
('Hello World!','hello-world','en-GB', 1, 1, 'hello-world', 1, 2),
('Goodbye World!','goodbye-world','en-GB', 1, 1, 'goodbye-world', 3, 4);
INSERT INTO `#__content_types` (`type_title`, `type_alias`, `content_history_options`, `table`, `field_mappings`, `router`)
VALUES
('Helloworld', 'com_helloworld.helloworld',
'{"formFile":"administrator\\/components\\/com_helloworld\\/models\\/forms\\/helloworld.xml",
"hideFields":["asset_id","checked_out","checked_out_time","version","lft","rgt","level","path"],
"ignoreChanges":["checked_out", "checked_out_time", "path"],
"convertToInt":[],
"displayLookup":[
{"sourceColumn":"created_by","targetTable":"#__users","targetColumn":"id","displayColumn":"name"},
{"sourceColumn":"parent_id","targetTable":"#__helloworld","targetColumn":"id","displayColumn":"greeting"},
{"sourceColumn":"catid","targetTable":"#__categories","targetColumn":"id","displayColumn":"title"}]}',
'{"special":{"dbtable":"#__helloworld","key":"id","type":"Helloworld","prefix":"HelloworldTable","config":"array()"},
"common":{"dbtable":"#__ucm_content","key":"ucm_id","type":"Corecontent","prefix":"JTable","config":"array()"}}',
'{"common": {
"core_content_item_id": "id",
"core_title": "greeting",
"core_state": "published",
"core_alias": "alias",
"core_created_time": "created",
"core_body": "description",
"core_access": "access",
"core_catid": "catid"
}}',
'HelloworldHelperRoute::getHelloworldRoute'),
('Helloworld Category', 'com_helloworld.category',
'{"formFile":"administrator\\/components\\/com_categories\\/models\\/forms\\/category.xml",
"hideFields":["asset_id","checked_out","checked_out_time","version","lft","rgt","level","path","extension"],
"ignoreChanges":["modified_user_id", "modified_time", "checked_out", "checked_out_time", "version", "hits", "path"],
"convertToInt":["publish_up", "publish_down"],
"displayLookup":[
{"sourceColumn":"created_user_id","targetTable":"#__users","targetColumn":"id","displayColumn":"name"},
{"sourceColumn":"access","targetTable":"#__viewlevels","targetColumn":"id","displayColumn":"title"},
{"sourceColumn":"modified_user_id","targetTable":"#__users","targetColumn":"id","displayColumn":"name"},
{"sourceColumn":"parent_id","targetTable":"#__categories","targetColumn":"id","displayColumn":"title"}]}',
'{"special":{"dbtable":"#__categories","key":"id","type":"Category","prefix":"JTable","config":"array()"},
"common":{"dbtable":"#__ucm_content","key":"ucm_id","type":"Corecontent","prefix":"JTable","config":"array()"}}',
'{"common": {
"core_content_item_id":"id",
"core_title":"title",
"core_state":"published",
"core_alias":"alias",
"core_created_time":"created_time",
"core_modified_time":"modified_time",
"core_body":"description",
"core_hits":"hits",
"core_publish_up":"null",
"core_publish_down":"null",
"core_access":"access",
"core_params":"params",
"core_featured":"null",
"core_metadata":"metadata",
"core_language":"language",
"core_images":"null",
"core_urls":"null",
"core_version":"version",
"core_ordering":"null",
"core_metakey":"metakey",
"core_metadesc":"metadesc",
"core_catid":"parent_id",
"core_xreference":"null",
"asset_id":"asset_id"},
"special":{
"parent_id":"parent_id",
"lft":"lft",
"rgt":"rgt",
"level":"level",
"path":"path",
"extension":"extension",
"note":"note"}}',
'HelloworldHelperRoute::getCategoryRoute');
Admin Edit Form
We add the access field into the form xml definition. That's all we need to do to enable the administrator to view and set that field, and to get it written to the database.
admin/models/forms/helloworld.xml
<?xml version="1.0" encoding="utf-8"?>
<form
addrulepath="/administrator/components/com_helloworld/models/rules"
>
<fieldset
name="details"
label="COM_HELLOWORLD_HELLOWORLD_DETAILS"
>
<field
name="id"
type="hidden"
/>
<field
name="greeting"
type="text"
label="COM_HELLOWORLD_HELLOWORLD_GREETING_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_GREETING_DESC"
size="40"
class="inputbox validate-greeting"
validate="greeting"
required="true"
default=""
/>
<field
name="alias"
type="text"
label="JFIELD_ALIAS_LABEL"
description="JFIELD_ALIAS_DESC"
hint="JFIELD_ALIAS_PLACEHOLDER"
size="40"
/>
<field
name="catid"
type="category"
extension="com_helloworld"
class="inputbox"
default=""
label="COM_HELLOWORLD_HELLOWORLD_FIELD_CATID_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_FIELD_CATID_DESC"
required="true"
>
<option value="0">JOPTION_SELECT_CATEGORY</option>
</field>
<field
name="latitude"
type="number"
label="COM_HELLOWORLD_HELLOWORLD_FIELD_LATITUDE_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_FIELD_LATITUDE_DESC"
min="-90.0"
max="90.0"
class="inputbox"
required="true"
default="0.0"
/>
<field
name="longitude"
type="number"
label="COM_HELLOWORLD_HELLOWORLD_FIELD_LONGITUDE_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_FIELD_LONGITUDE_DESC"
min="-180.0"
max="180.0"
class="inputbox"
required="true"
default="0.0"
/>
<field
name="language"
type="contentlanguage"
label="JFIELD_LANGUAGE_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_FIELD_LANGUAGE_DESC"
>
<option value="*">JALL</option>
</field>
<field name="published"
type="list"
label="JSTATUS"
description="JFIELD_PUBLISHED_DESC"
class="chzn-color-state"
filter="intval"
size="1"
default="1"
>
<option value="1">
JPUBLISHED</option>
<option value="0">
JUNPUBLISHED</option>
</field>
<field
name="tags"
type="tag"
label="JTAG"
description="JTAG_DESC"
multiple="true"
>
</field>
<field
name="access"
type="accesslevel"
label="JFIELD_ACCESS_LABEL"
description="JFIELD_ACCESS_DESC"
>
</field>
<field
name="parent_id"
type="helloworldparent"
label="COM_HELLOWORLD_HELLOWORLD_FIELD_PARENT_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_FIELD_PARENT_DESC"
default="1"
filter="int">
</field>
<field
name="helloworldordering"
type="helloworldordering"
label="JFIELD_ORDERING_LABEL"
description="JFIELD_ORDERING_DESC"
filter="int"
size="1">
</field>
<field name="version_note"
type="text"
label="JGLOBAL_FIELD_VERSION_NOTE_LABEL"
description="JGLOBAL_FIELD_VERSION_NOTE_DESC"
class="inputbox"
size="45"
labelclass="control-label">
</field>
</fieldset>
<field name="description"
type="editor"
label="COM_HELLOWORLD_HELLOWORLD_FIELD_DESCRIPTION_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_FIELD_DESCRIPTION_DESC"
filter="JComponentHelper::filterText"
buttons="true"
/>
<fields name="imageinfo">
<fieldset
name="image-info"
label="COM_HELLOWORLD_IMAGE_FIELDS"
>
<field
name="image"
type="media"
preview="tooltip"
label="COM_HELLOWORLD_HELLOWORLD_FIELD_IMAGE_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_FIELD_IMAGE_DESC" />
<field name="alt"
type="text"
label="COM_HELLOWORLD_HELLOWORLD_FIELD_ALT_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_FIELD_ALT_DESC"
size="30"/>
<field name="caption"
type="text"
label="COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTION_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTION_DESC"
size="30"/>
</fieldset>
</fields>
<fields name="params">
<fieldset
name="params"
label="JGLOBAL_FIELDSET_DISPLAY_OPTIONS"
>
<field
name="show_category"
type="list"
label="COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_LABEL"
description="COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_DESC"
default=""
>
<option value="">JGLOBAL_USE_GLOBAL</option>
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
</field>
</fieldset>
</fields>
<fieldset
name="accesscontrol"
label="COM_HELLOWORLD_FIELDSET_RULES"
>
<field
name="asset_id"
type="hidden"
filter="unset"
/>
<field
name="rules"
type="rules"
label="COM_HELLOWORLD_FIELD_RULES_LABEL"
filter="rules"
validate="rules"
class="inputbox"
component="com_helloworld"
section="message"
/>
</fieldset>
</form>
Admin Helloworlds Display
Changes in 4 files are required to implement the necessary changes:
- the layout file
- the filter fields xml definition
- the helloworlds model
- the admin language strings
In the layout file we replace the 4 columns associated with the nested table structure (lft, rgt, level, parent) with the access column, and make it a sortable column.
admin/views/helloworlds/tmpl/default.php
<?php
/**
* @package Joomla.Administrator
* @subpackage com_helloworld
*
* @copyright Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
// No direct access to this file
defined('_JEXEC') or die('Restricted Access');
use Joomla\Registry\Registry;
JHtml::_('formbehavior.chosen', 'select');
$listOrder = $this->escape($this->state->get('list.ordering'));
$listDirn = $this->escape($this->state->get('list.direction'));
$user = JFactory::getUser();
$userId = $user->get('id');
$saveOrder = ($listOrder == 'lft' && strtolower($listDirn) == 'asc');
if ($saveOrder)
{
$saveOrderingUrl = 'index.php?option=com_helloworld&task=helloworlds.saveOrderAjax&tmpl=component';
// pass true as parameter 7 to indicate that we have a nested set
JHtml::_('sortablelist.sortable', 'helloworldList', 'adminForm', strtolower($listDirn), $saveOrderingUrl, false, true);
}
$assoc = JLanguageAssociations::isEnabled();
$authorFieldwidth = $assoc ? "10%" : "25%";
JLoader::register('JHtmlHelloworlds', JPATH_ADMINISTRATOR . '/components/com_helloworld/helpers/html/helloworlds.php');
?>
<form action="index.php?option=com_helloworld&view=helloworlds" method="post" id="adminForm" name="adminForm">
<div id="j-sidebar-container" class="span2">
<?php echo JHtmlSidebar::render(); ?>
</div>
<div id="j-main-container" class="span10">
<div class="row-fluid">
<div class="span6">
<?php echo JText::_('COM_HELLOWORLD_HELLOWORLDS_FILTER'); ?>
<?php
echo JLayoutHelper::render(
'joomla.searchtools.default',
array('view' => $this)
);
?>
</div>
</div>
<table class="table table-striped table-hover" id="helloworldList">
<thead>
<tr>
<th width="1%">
<?php echo JHtml::_('searchtools.sort', '', 'lft', $listDirn, $listOrder, null, 'asc', 'JGRID_HEADING_ORDERING', 'icon-menu-2'); ?>
</th>
<th width="1%"><?php echo JText::_('COM_HELLOWORLD_NUM'); ?></th>
<th width="1%">
<?php echo JHtml::_('grid.checkall'); ?>
</th>
<th width="10%">
<?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_HELLOWORLDS_NAME', 'greeting', $listDirn, $listOrder); ?>
</th>
<th width="10%">
<?php echo JText::_('COM_HELLOWORLD_HELLOWORLDS_POSITION'); ?>
</th>
<th width="10%">
<?php echo JText::_('COM_HELLOWORLD_HELLOWORLDS_IMAGE'); ?>
</th>
<th width="20%">
<?php echo JHtml::_('searchtools.sort', 'JGRID_HEADING_ACCESS', 'access', $listDirn, $listOrder); ?>
</th>
<?php if ($assoc) : ?>
<th width="10%">
<?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_HELLOWORLDS_ASSOCIATIONS', 'association', $listDirn, $listOrder); ?>
</th>
<?php endif; ?>
<th width="<?php echo $authorFieldwidth; ?>">
<?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_AUTHOR', 'author', $listDirn, $listOrder); ?>
</th>
<th width="10%">
<?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_LANGUAGE', 'language', $listDirn, $listOrder); ?>
</th>
<th width="10%">
<?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_CREATED_DATE', 'created', $listDirn, $listOrder); ?>
</th>
<th width="5%">
<?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_PUBLISHED', 'published', $listDirn, $listOrder); ?>
</th>
<th width="2%">
<?php echo JHtml::_('searchtools.sort', 'COM_HELLOWORLD_ID', 'id', $listDirn, $listOrder); ?>
</th>
</tr>
</thead>
<tfoot>
<tr>
<td colspan="5">
<?php echo $this->pagination->getListFooter(); ?>
</td>
</tr>
</tfoot>
<tbody>
<?php if (!empty($this->items)) : ?>
<?php foreach ($this->items as $i => $row) :
$link = JRoute::_('index.php?option=com_helloworld&task=helloworld.edit&id=' . $row->id);
$row->image = new Registry;
$row->image->loadString($row->imageInfo);
// create a list of the parents up the hierarchy to the root
if ($row->level > 1)
{
$parentsStr = '';
$_currentParentId = $row->parent_id;
$parentsStr = ' ' . $_currentParentId;
for ($j = 0; $j < $row->level; $j++)
{
foreach ($this->ordering as $k => $v)
{
$v = implode('-', $v);
$v = '-' . $v . '-';
if (strpos($v, '-' . $_currentParentId . '-') !== false)
{
$parentsStr .= ' ' . $k;
$_currentParentId = $k;
break;
}
}
}
}
else
{
$parentsStr = '';
}
?>
<tr class="row<?php echo $i % 2; ?>" sortable-group-id="<?php echo $row->parent_id; ?>" item-id="<?php echo $row->id; ?>" parents="<?php echo $parentsStr; ?>" level="<?php echo $row->level; ?>">
<td><?php
$iconClass = '';
$canReorder = $user->authorise('core.edit.state', 'com_helloworld.helloworld.' . $row->id);
if (!$canReorder)
{
$iconClass = ' inactive';
}
elseif (!$saveOrder)
{
$iconClass = ' inactive tip-top hasTooltip" title="' . JHtml::_('tooltipText', 'JORDERINGDISABLED');
}
?>
<span class="sortable-handler<?php echo $iconClass ?>">
<span class="icon-menu" aria-hidden="true"></span>
</span>
<?php if ($canReorder && $saveOrder) : ?>
<input type="text" style="display:none" name="order[]" size="5" value="<?php echo $row->lft; ?>" class="width-20 text-area-order" />
<?php endif; ?>
</td>
<td><?php echo $this->pagination->getRowOffset($i); ?></td>
<td>
<?php echo JHtml::_('grid.id', $i, $row->id); ?>
</td>
<td>
<?php $prefix = JLayoutHelper::render('joomla.html.treeprefix', array('level' => $row->level)); ?>
<?php echo $prefix; ?>
<?php if ($row->checked_out) : ?>
<?php $canCheckin = $user->authorise('core.manage', 'com_checkin') || $row->checked_out == $userId; ?>
<?php echo JHtml::_('jgrid.checkedout', $i, $row->editor, $row->checked_out_time, 'helloworlds.', $canCheckin); ?>
<?php endif; ?>
<a href="<?php echo $link; ?>" title="<?php echo JText::_('COM_HELLOWORLD_EDIT_HELLOWORLD'); ?>">
<?php echo $row->greeting; ?>
</a>
<span class="small break-word">
<?php echo JText::sprintf('JGLOBAL_LIST_ALIAS', $this->escape($row->alias)); ?>
</span>
<div class="small">
<?php echo JText::_('JCATEGORY') . ': ' . $this->escape($row->category_title); ?>
</div>
<div class="small">
<?php echo 'Path: ' . $this->escape($row->path); ?>
</div>
</td>
<td align="center">
<?php echo "[" . $row->latitude . ", " . $row->longitude . "]"; ?>
</td>
<td align="center">
<?php
$caption = $row->image->get('caption') ? : '' ;
$src = JURI::root() . ($row->image->get('image') ? : '' );
$html = '<p class="hasTooltip" style="display: inline-block" data-html="true" data-toggle="tooltip" data-placement="right" title="<img width=\'100px\' height=\'100px\' src=\'%s\'>">%s</p>';
echo sprintf($html, $src, $caption); ?>
</td>
<td align="center">
<?php echo $this->escape($row->access_level); ?>
</td>
<?php if ($assoc) : ?>
<td align="center">
<?php if ($row->association) : ?>
<?php echo JHtml::_('helloworlds.association', $row->id); ?>
<?php endif; ?>
</td>
<?php endif; ?>
<td align="center">
<?php echo $row->author; ?>
</td>
<td align="center">
<?php echo JLayoutHelper::render('joomla.content.language', $row); ?>
</td>
<td align="center">
<?php echo substr($row->created, 0, 10); ?>
</td>
<td align="center">
<?php echo JHtml::_('jgrid.published', $row->published, $i, 'helloworlds.', true, 'cb'); ?>
</td>
<td align="center">
<?php echo $row->id; ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<input type="hidden" name="task" value=""/>
<input type="hidden" name="boxchecked" value="0"/>
<?php echo JHtml::_('form.token'); ?>
</div>
</form>
Secondly, in the filter helloworlds form definition we add the 2 options for sorting by the access column.
admin/models/forms/filter_helloworlds.xml
<?xml version="1.0" encoding="utf-8"?>
<form>
<fields name="filter">
<field
name="search"
type="text"
label="COM_BANNERS_SEARCH_IN_TITLE"
hint="JSEARCH_FILTER"
class="js-stools-search-string"
/>
<field
name="published"
type="status"
label="JOPTION_SELECT_PUBLISHED"
description="JOPTION_SELECT_PUBLISHED_DESC"
onchange="this.form.submit();"
>
<option value="">JOPTION_SELECT_PUBLISHED</option>
</field>
<field
name="language"
type="contentlanguage"
label="JOPTION_FILTER_LANGUAGE"
description="JOPTION_FILTER_LANGUAGE_DESC"
onchange="this.form.submit();"
>
<option value="">JOPTION_SELECT_LANGUAGE</option>
<option value="*">JALL</option>
</field>
<field
name="category_id"
type="category"
label="JOPTION_FILTER_CATEGORY"
extension="com_helloworld"
onchange="this.form.submit();"
published="0,1,2"
>
<option value="">JOPTION_SELECT_CATEGORY</option>
</field>
</fields>
<fields name="list">
<field
name="fullordering"
type="list"
label="COM_HELLOWORLD_LIST_FULL_ORDERING"
description="COM_HELLOWORLD_LIST_FULL_ORDERING_DESC"
onchange="this.form.submit();"
default="greeting ASC"
>
<option value="">JGLOBAL_SORT_BY</option>
<option value="ordering ASC">COM_HELLOWORLD_ORDERING_ASC</option>
<option value="ordering DESC">COM_HELLOWORLD_ORDERING_DESC</option>
<option value="greeting ASC">COM_HELLOWORLD_GREETING_ASC</option>
<option value="greeting DESC">COM_HELLOWORLD_GREETING_DESC</option>
<option value="id ASC">JGRID_HEADING_ID_ASC</option>
<option value="id DESC">JGRID_HEADING_ID_DESC</option>
<option value="published ASC">COM_HELLOWORLD_PUBLISHED_ASC</option>
<option value="published DESC">COM_HELLOWORLD_PUBLISHED_DESC</option>
<option value="author ASC">COM_HELLOWORLD_AUTHOR_ASC</option>
<option value="author DESC">COM_HELLOWORLD_AUTHOR_DESC</option>
<option value="created ASC">COM_HELLOWORLD_CREATED_ASC</option>
<option value="created DESC">COM_HELLOWORLD_CREATED_DESC</option>
<option value="language ASC">COM_HELLOWORLD_LANGUAGE_ASC</option>
<option value="language DESC">COM_HELLOWORLD_LANGUAGE_DESC</option>
<option value="access ASC">COM_HELLOWORLD_ACCESS_ASC</option>
<option value="access DESC">COM_HELLOWORLD_ACCESS_DESC</option>
<option value="association ASC">COM_HELLOWORLD_ASSOCIATION_ASC</option>
<option value="association DESC">COM_HELLOWORLD_ASSOCIATION_DESC</option>
</field>
<field
name="limit"
type="limitbox"
class="input-mini"
default="25"
label="COM_CONTENT_LIST_LIMIT"
description="COM_HELLOWORLD_LIST_LIMIT_DESC"
onchange="this.form.submit();"
/>
</fields>
</form>
Thirdly we must change the helloworlds model
- to include the access field in the query, and translate the access value into the name of that access level,
- to include the access field within the list of sort fields ($config array),
- to restrict the record returned to those which the user has access to see, by adding an additional SQL WHERE clause.
admin/models/helloworlds.php
<?php
/**
* @package Joomla.Administrator
* @subpackage com_helloworld
*
* @copyright Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
// No direct access to this file
defined('_JEXEC') or die('Restricted access');
/**
* HelloWorldList Model
*
* @since 0.0.1
*/
class HelloWorldModelHelloWorlds extends JModelList
{
/**
* Constructor.
*
* @param array $config An optional associative array of configuration settings.
*
* @see JController
* @since 1.6
*/
public function __construct($config = array())
{
if (empty($config['filter_fields']))
{
$config['filter_fields'] = array(
'id',
'greeting',
'author',
'created',
'language',
'lft',
'category_id',
'access',
'association',
'published'
);
}
parent::__construct($config);
}
protected function populateState($ordering = 'lft', $direction = 'asc')
{
$app = JFactory::getApplication();
// Adjust the context to support modal layouts.
if ($layout = $app->input->get('layout'))
{
$this->context .= '.' . $layout;
}
// Adjust the context to support forced languages.
$forcedLanguage = $app->input->get('forcedLanguage', '', 'CMD');
if ($forcedLanguage)
{
$this->context .= '.' . $forcedLanguage;
}
parent::populateState($ordering, $direction);
// If there's a forced language then define that filter for the query where clause
if (!empty($forcedLanguage))
{
$this->setState('filter.language', $forcedLanguage);
}
}
/**
* Method to build an SQL query to load the list data.
*
* @return string An SQL query
*/
protected function getListQuery()
{
// Initialize variables.
$db = JFactory::getDbo();
$query = $db->getQuery(true);
$user = JFactory::getUser();
// Create the base select statement.
$query->select('a.id as id, a.greeting as greeting, a.published as published, a.created as created, a.access as access,
a.checked_out as checked_out, a.checked_out_time as checked_out_time, a.catid as catid,
a.lft as lft, a.rgt as rgt, a.parent_id as parent_id, a.level as level, a.path as path,
a.image as imageInfo, a.latitude as latitude, a.longitude as longitude, a.alias as alias, a.language as language')
->from($db->quoteName('#__helloworld', 'a'));
// Join over the categories.
$query->select($db->quoteName('c.title', 'category_title'))
->join('LEFT', $db->quoteName('#__categories', 'c') . ' ON c.id = a.catid');
// Join with users table to get the username of the author
$query->select($db->quoteName('u.username', 'author'))
->join('LEFT', $db->quoteName('#__users', 'u') . ' ON u.id = a.created_by');
// Join with users table to get the username of the person who checked the record out
$query->select($db->quoteName('u2.username', 'editor'))
->join('LEFT', $db->quoteName('#__users', 'u2') . ' ON u2.id = a.checked_out');
// Join with languages table to get the language title and image to display
// Put these into fields called language_title and language_image so that
// we can use the little com_content layout to display the map symbol
$query->select($db->quoteName('l.title', 'language_title') . "," .$db->quoteName('l.image', 'language_image'))
->join('LEFT', $db->quoteName('#__languages', 'l') . ' ON l.lang_code = a.language');
// Join over the associations - we just want to know if there are any, at this stage
if (JLanguageAssociations::isEnabled())
{
$query->select('COUNT(asso2.id)>1 as association')
->join('LEFT', '#__associations AS asso ON asso.id = a.id AND asso.context=' . $db->quote('com_helloworld.item'))
->join('LEFT', '#__associations AS asso2 ON asso2.key = asso.key')
->group('a.id');
}
// Join over the access levels, to get the name of the access level
$query->select('v.title AS access_level')
->join('LEFT', '#__viewlevels AS v ON v.id = a.access');
// Filter: like / search
$search = $this->getState('filter.search');
if (!empty($search))
{
$like = $db->quote('%' . $search . '%');
$query->where('greeting LIKE ' . $like);
}
// Filter by published state
$published = $this->getState('filter.published');
if (is_numeric($published))
{
$query->where('a.published = ' . (int) $published);
}
elseif ($published === '')
{
$query->where('(a.published IN (0, 1))');
}
// Filter by language, if the user has set that in the filter field
$language = $this->getState('filter.language');
if ($language)
{
$query->where('a.language = ' . $db->quote($language));
}
// Filter by categories
$catid = $this->getState('filter.category_id');
if ($catid)
{
$query->where("a.catid = " . $db->quote($db->escape($catid)));
}
// Display only records to which the user has access
if (!$user->authorise('core.admin')) // ie if not SuperUser
{
$userAccessLevels = implode(',', $user->getAuthorisedViewLevels());
$query->where('a.access IN (' . $userAccessLevels . ')');
$query->where('c.access IN (' . $userAccessLevels . ')');
}
// exclude root helloworld record
$query->where('a.id > 1');
// Add the list ordering clause.
$orderCol = $this->state->get('list.ordering', 'lft');
$orderDirn = $this->state->get('list.direction', 'asc');
$query->order($db->escape($orderCol) . ' ' . $db->escape($orderDirn));
return $query;
}
}
Fourthly we include the 2 new admin language strings.
admin/language/en-GB/en-GB.com_helloworld.ini
; Joomla! Project
; Copyright (C) 2005 - 2018 Open Source Matters. All rights reserved.
; License GNU General Public License version 2 or later; see LICENSE.txt, see LICENSE.php
; Note : All ini files need to be saved as UTF-8
COM_HELLOWORLD_ADMINISTRATION="HelloWorld - Administration"
COM_HELLOWORLD_ADMINISTRATION_CATEGORIES="HelloWorld - Categories"
COM_HELLOWORLD_NUM="#"
COM_HELLOWORLD_HELLOWORLDS_FILTER="Filters"
COM_HELLOWORLD_AUTHOR="Author"
COM_HELLOWORLD_LANGUAGE="Language"
COM_HELLOWORLD_CREATED_DATE="Created"
COM_HELLOWORLD_PUBLISHED="Published"
COM_HELLOWORLD_HELLOWORLDS_NAME="Name"
COM_HELLOWORLD_HELLOWORLDS_POSITION="Position"
COM_HELLOWORLD_HELLOWORLDS_IMAGE="Image"
COM_HELLOWORLD_HELLOWORLDS_ASSOCIATIONS="Associations"
COM_HELLOWORLD_ID="Id"
COM_HELLOWORLD_HELLOWORLD_CREATING="HelloWorld - Creating"
COM_HELLOWORLD_HELLOWORLD_DETAILS="Details"
COM_HELLOWORLD_HELLOWORLD_EDITING="HelloWorld - Editing"
COM_HELLOWORLD_HELLOWORLD_ERROR_UNACCEPTABLE="Some values are unacceptable"
COM_HELLOWORLD_HELLOWORLD_FIELD_CATID_DESC="The category the messages belongs to"
COM_HELLOWORLD_HELLOWORLD_FIELD_CATID_LABEL="Category"
COM_HELLOWORLD_HELLOWORLD_FIELD_GREETING_DESC="This message will be displayed"
COM_HELLOWORLD_HELLOWORLD_FIELD_GREETING_LABEL="Message"
COM_HELLOWORLD_HELLOWORLD_FIELD_DESCRIPTION_DESC="Message description"
COM_HELLOWORLD_HELLOWORLD_FIELD_DESCRIPTION_LABEL="Description"
COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_LABEL="Show category"
COM_HELLOWORLD_HELLOWORLD_FIELD_SHOW_CATEGORY_DESC="If set to Show, the title of the message’s category will show."
COM_HELLOWORLD_HELLOWORLD_FIELD_LATITUDE_LABEL="Latitude"
COM_HELLOWORLD_HELLOWORLD_FIELD_LATITUDE_DESC="Enter the position latitude, between -90 and +90 degrees"
COM_HELLOWORLD_HELLOWORLD_FIELD_LONGITUDE_LABEL="Longitude"
COM_HELLOWORLD_HELLOWORLD_FIELD_LONGITUDE_DESC="Enter the position longitude, between -180 and +180 degrees"
COM_HELLOWORLD_HELLOWORLD_FIELD_PARENT_LABEL="Parent"
COM_HELLOWORLD_HELLOWORLD_FIELD_PARENT_DESC="Select the parent record"
COM_HELLOWORLD_ITEM_FIELD_ORDERING_VALUE_FIRST="-- First record"
COM_HELLOWORLD_ITEM_FIELD_ORDERING_VALUE_LAST="-- Last record"
COM_HELLOWORLD_ITEM_FIELD_ORDERING_TEXT="Ordering will be available after saving."
COM_HELLOWORLD_HELLOWORLD_FIELD_LANGUAGE_DESC="Select the appropriate language"
COM_HELLOWORLD_IMAGE_FIELDS="Image details"
COM_HELLOWORLD_HELLOWORLD_FIELD_IMAGE_LABEL="Select image"
COM_HELLOWORLD_HELLOWORLD_FIELD_IMAGE_DESC="Select an image from the library, or upload a new one"
COM_HELLOWORLD_HELLOWORLD_FIELD_ALT_LABEL="Alt text"
COM_HELLOWORLD_HELLOWORLD_FIELD_ALT_DESC="Alternative text (if image cannot be displayed)"
COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTION_LABEL="Caption"
COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTION_DESC="Provide a caption for the image"
COM_HELLOWORLD_HELLOWORLD_HEADING_GREETING="Greeting"
COM_HELLOWORLD_HELLOWORLD_HEADING_ID="Id"
COM_HELLOWORLD_MANAGER_HELLOWORLD_EDIT="HelloWorld manager: Edit Message"
COM_HELLOWORLD_MANAGER_HELLOWORLD_NEW="HelloWorld manager: New Message"
COM_HELLOWORLD_MANAGER_HELLOWORLDS="HelloWorld manager"
COM_HELLOWORLD_EDIT_HELLOWORLD="Edit message"
COM_HELLOWORLD_N_ITEMS_DELETED_1="One message deleted"
COM_HELLOWORLD_N_ITEMS_DELETED_MORE="%d messages deleted"
COM_HELLOWORLD_N_ITEMS_PUBLISHED="%d message(s) published"
COM_HELLOWORLD_N_ITEMS_UNPUBLISHED="%d message(s) unpublished"
COM_HELLOWORLD_HELLOWORLD_GREETING_LABEL="Greeting"
COM_HELLOWORLD_HELLOWORLD_GREETING_DESC="Add Hello World Greeting"
COM_HELLOWORLD_SUBMENU_MESSAGES="Messages"
COM_HELLOWORLD_SUBMENU_CATEGORIES="Categories"
COM_HELLOWORLD_CONFIGURATION="HelloWorld Configuration"
COM_HELLOWORLD_CONFIG_GREETING_SETTINGS_LABEL="Messages settings"
COM_HELLOWORLD_CONFIG_GREETING_SETTINGS_DESC="Settings that will be applied to all messages by default"
COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTCHA_LABEL="Captcha"
COM_HELLOWORLD_HELLOWORLD_FIELD_CAPTCHA_DESC="Select Captcha to use on front end form"
COM_HELLOWORLD_HELLOWORLD_FIELD_USER_TO_EMAIL_LABEL="User to email"
COM_HELLOWORLD_HELLOWORLD_FIELD_USER_TO_EMAIL_DESC="Select user to email when a new message is entered on front end"
COM_HELLOWORLD_FIELDSET_RULES="Message Permissions"
COM_HELLOWORLD_FIELD_RULES_LABEL="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?"
COM_HELLOWORLD_TAB_NEW_MESSAGE="New Message"
COM_HELLOWORLD_TAB_EDIT_MESSAGE="Message Details"
COM_HELLOWORLD_TAB_PARAMS="Parameters"
COM_HELLOWORLD_TAB_ASSOCIATIONS="Associations"
COM_HELLOWORLD_TAB_PERMISSIONS="Permissions"
COM_HELLOWORLD_TAB_IMAGE="Image"
COM_HELLOWORLD_LEGEND_DETAILS="Message Details"
COM_HELLOWORLD_LEGEND_PARAMS="Message Parameters"
COM_HELLOWORLD_LEGEND_ASSOCIATIONS="Message Associations"
COM_HELLOWORLD_LEGEND_PERMISSIONS="Message Permissions"
COM_HELLOWORLD_LEGEND_IMAGE="Image info"
; Column ordering in the Helloworlds view
COM_HELLOWORLD_ORDERING_ASC="Ordering ascending"
COM_HELLOWORLD_ORDERING_DESC="Ordering descending"
COM_HELLOWORLD_GREETING_ASC="Greeting ascending"
COM_HELLOWORLD_GREETING_DESC="Greeting descending"
COM_HELLOWORLD_AUTHOR_ASC="Author ascending"
COM_HELLOWORLD_AUTHOR_DESC="Author descending"
COM_HELLOWORLD_CREATED_ASC="Creation date ascending"
COM_HELLOWORLD_CREATED_DESC="Creation date descending"
COM_HELLOWORLD_PUBLISHED_ASC="Unpublished first"
COM_HELLOWORLD_PUBLISHED_DESC="Published first"
COM_HELLOWORLD_LANGUAGE_ASC="Language ascending"
COM_HELLOWORLD_LANGUAGE_DESC="Language descending"
COM_HELLOWORLD_ASSOCIATION_ASC="Association ascending"
COM_HELLOWORLD_ASSOCIATION_DESC="Association descending"
COM_HELLOWORLD_ACCESS_ASC="Access ascending"
COM_HELLOWORLD_ACCESS_DESC="Access descending"
; Helloworld menuitem - selecting a greeting via modal
COM_HELLOWORLD_MENUITEM_SELECT_MODAL_TITLE="Select greeting"
COM_HELLOWORLD_MENUITEM_SELECT_HELLOWORLD="Select"
COM_HELLOWORLD_MENUITEM_SELECT_BUTTON_TOOLTIP="Select a helloworld greeting"
; Checking in records
COM_HELLOWORLD_N_ITEMS_CHECKED_IN_0="No records checked in."
COM_HELLOWORLD_N_ITEMS_CHECKED_IN_1="%d record successfully checked in."
COM_HELLOWORLD_N_ITEMS_CHECKED_IN_MORE="%d records successfully checked in."
Packaging the Component
Contents of your code directory. Each file link below takes you to the step in the tutorial which has the latest version of that source code file.
- helloworld.xml
- script.php
- site/router.php
- site/helloworld.php
- site/index.html
- site/controller.php
- site/controllers/helloworld.php
- site/views/index.html
- site/views/helloworld/index.html
- site/views/helloworld/view.html.php
- site/views/helloworld/view.json.php
- site/views/helloworld/tmpl/index.html
- site/views/helloworld/tmpl/default.xml
- site/views/helloworld/tmpl/default.php
- site/views/form/index.html
- site/views/form/view.html.php
- site/views/form/tmpl/index.html
- site/views/form/tmpl/edit.php
- site/views/form/tmpl/edit.xml
- site/views/category/index.html
- site/views/category/view.html.php
- site/views/category/tmpl/index.html
- site/views/category/tmpl/default.php
- site/views/category/tmpl/default.xml
- site/models/index.html
- site/models/helloworld.php
- site/models/form.php
- site/models/category.php
- site/models/forms/index.html
- site/models/forms/add-form.xml
- site/models/forms/filter_category.xml
- site/language/index.html
- site/language/en-GB/index.html
- site/language/en-GB/en-GB.com_helloworld.ini
- site/helpers/index.html
- site/helpers/route.php
- site/helpers/category.php
- site/helpers/association.php
- admin/index.html
- admin/helloworld.php
- admin/config.xml
- admin/controller.php
- admin/access.xml
- admin/helpers/helloworld.php
- admin/helpers/associations.php
- admin/helpers/index.html
- admin/helpers/html/helloworlds.php
- admin/helpers/html/index.html
- admin/sql/index.html
- admin/sql/install.mysql.utf8.sql
- admin/sql/uninstall.mysql.utf8.sql
- admin/sql/updates/index.html
- admin/sql/updates/mysql/index.html
- admin/sql/updates/mysql/0.0.1.sql
- admin/sql/updates/mysql/0.0.6.sql
- admin/sql/updates/mysql/0.0.12.sql
- admin/sql/updates/mysql/0.0.13.sql
- admin/sql/updates/mysql/0.0.14.sql
- admin/sql/updates/mysql/0.0.16.sql
- admin/sql/updates/mysql/0.0.17.sql
- admin/sql/updates/mysql/0.0.18.sql
- admin/sql/updates/mysql/0.0.20.sql
- admin/sql/updates/mysql/0.0.21.sql
- admin/sql/updates/mysql/0.0.24.sql
- admin/sql/updates/mysql/0.0.25.sql
- admin/sql/updates/mysql/0.0.26.sql
- admin/sql/updates/mysql/0.0.27.sql
- admin/sql/updates/mysql/0.0.28.sql
- admin/sql/updates/mysql/0.0.29.sql
- admin/models/index.html
- admin/models/fields/index.html
- admin/models/fields/helloworld.php
- admin/models/fields/helloworldordering.php
- admin/models/fields/helloworldparent.php
- admin/models/fields/modal/index.html
- admin/models/fields/modal/helloworld.php
- admin/models/helloworlds.php
- admin/models/helloworld.php
- admin/models/forms/filter_helloworlds.xml
- admin/models/forms/index.html
- admin/models/forms/helloworld.js
- admin/models/forms/helloworld.xml
- admin/models/rules/greeting.php
- admin/models/rules/index.html
- admin/controllers/helloworld.php
- admin/controllers/helloworlds.php
- admin/controllers/index.html
- admin/views/index.html
- admin/views/helloworld/index.html
- admin/views/helloworld/view.html.php
- admin/views/helloworld/tmpl/index.html
- admin/views/helloworld/tmpl/edit.php
- admin/views/helloworld/submitbutton.js
- admin/views/helloworlds/index.html
- admin/views/helloworlds/view.html.php
- admin/views/helloworlds/tmpl/index.html
- admin/views/helloworlds/tmpl/default.php
- admin/views/helloworlds/tmpl/modal.php
- admin/tables/index.html
- admin/tables/helloworld.php
- admin/language/index.html
- admin/language/en-GB/index.html
- admin/language/en-GB/en-GB.com_helloworld.ini
- admin/language/en-GB/en-GB.com_helloworld.sys.ini
- media/index.html
- media/images/index.html
- media/images/tux-16x16.png
- media/images/tux-48x48.png
- media/js/index.html
- media/js/openstreetmap.js
- media/js/admin-helloworlds-modal.js
- media/css/index.html
- media/css/openstreetmap.css
helloworld.xml
<?xml version="1.0" encoding="utf-8"?>
<extension type="component" version="3.0" method="upgrade">
<name>COM_HELLOWORLD</name>
<!-- The following elements are optional and free of formatting constraints -->
<creationDate>January 2018</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.29</version>
<!-- The description is optional and defaults to the name -->
<description>COM_HELLOWORLD_DESCRIPTION</description>
<!-- Runs on install/uninstall/update; New in 2.5 -->
<scriptfile>script.php</scriptfile>
<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 since J2.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>
<filename>router.php</filename>
<folder>controllers</folder>
<folder>views</folder>
<folder>models</folder>
<folder>helpers</folder>
</files>
<languages folder="site/language">
<language tag="en-GB">en-GB/en-GB.com_helloworld.ini</language>
<language tag="fr-FR">fr-FR/fr-FR.com_helloworld.ini</language>
</languages>
<media destination="com_helloworld" folder="media">
<filename>index.html</filename>
<folder>images</folder>
<folder>js</folder>
<folder>css</folder>
</media>
<administration>
<!-- Administration Menu Section -->
<menu link='index.php?option=com_helloworld' 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>helloworld.php</filename>
<filename>controller.php</filename>
<filename>access.xml</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">
<language tag="en-GB">en-GB/en-GB.com_helloworld.ini</language>
<language tag="en-GB">en-GB/en-GB.com_helloworld.sys.ini</language>
<language tag="fr-FR">fr-FR/fr-FR.com_helloworld.ini</language>
<language tag="fr-FR">fr-FR/fr-FR.com_helloworld.sys.ini</language>
</languages>
</administration>
</extension>