開發一個 Model-View-Controller 元件/新增快取
From Joomla! Documentation
< J3.x:Developing an MVC Component
在選單類型中新增一個變數請求
使用資料庫
基本的後台
新增語言管理
新增後台行為
新增後台的佈置
新增表單驗證
新增類別
新增設定介面
新增一個安裝/移除/更新程式碼檔案
新增一個前台表單
使用語言過濾功能
新增更新伺服器
這是一系列的多篇文章,旨在介紹如何開發一個 Joomla! Version Model-View-Controller 元件
讓我們從簡介開始,您可以使用底下的導覽按鈕來瀏覽文章,或是右側的方塊中的連結(列出所有的文章)。
- 本教學是 開發Joomla! 3.x MVC 元件 系列文章的一部分,閱讀本文之前,您應該先看過前面幾篇文章。
In this step we add cache capability to our component. There are 3 videos associated with step, covering page cache, progressive and conservative cache and a walkthrough of the tutorial code.
介紹
If you're not familiar with Joomla caching capabilities then you may find reading this cache page useful.
In this step we add 2 caching aspects to our component:
- a view cache which caches the output of our helloworld component
- a cache which caches the results of our Ajax query when the user clicks on Search Here in the map.
To exercise the functionality in this step you should:
- ensure that the System Page Cache plugin is disabled
- set the Global Configuration / System / Cache Settings to have System Cache set to ON – Conservative caching or ON – Progressive cahing. It doesn't matter which.
Approach
We implement our view cache by following the pattern of the core Joomla components:
Caching conditions
In our site controller.php we specify the conditions under which we allow the helloworld output to be cached. Generally it's fine to cache output which is simply displayed, however, it's not appropriate to cache forms (such as our front-end form for capturing a new greeting), for a couple of reasons:
- if the user makes a mistake on the form and then presses the submit button, then we would want to provide feedback from the server validation which failed, and then re-display the form pre-filled with the data the user entered previously. We wouldn't want to just display the same anonymous cached form again.
- in our forms we insert a token which is checked on the server. If we display a cached form and a different user submits the form then the token check will fail.
As it is, the Ajax call which we send has a token which is checked in our controller mapsearch() function, so for the purposes of the tutorial we will remove the checking of this token.
Specify unique cache id
In addition to our front-end form (which we're not going to cache) we have a number of views within our helloworld component
- the 'helloworld' view, which displays an individual greeting
- the 'category' view, which displays the records associated with a specific category. Also on this view we have pagination implemented, so different pages can be shown, with different numbers of records and different ordering.
So if we're caching using files, for example, then we need to make sure that we have different cache files for these different cases, and that the filename is unique for the combination of view name / helloworld record id / pagination page / ordering / language etc.
Joomla generates the cache id (which goes into the filename) by performing an md5 hash of the values of URL parameters. What we have to do is specify which URL parameters we want to have as part of that hash. The display() method in our controller has the signature public function display($cachable = false, $urlparams = array()), so when we call parent::display() in our code we now pass the parameters
- $cachable – indicates whether or not we allow this view output to be stored in the cache. (Remember, even if we specify true here, the view will be cached only if the administrator has set the Cache option within the Global Configuration).
- $urlparams – these are URL parameters whose values we want to use within the hash to generate the cache id. We specify them as an array of URL parameter name and type.
Note that there's nowhere here to indicate which user is logged on. We have to be careful as different users have different permissions, and so may not be able to view certain helloworld records which have the Access set. If we were to store and serve cached views without considering the user then it would be possible for guests to the site to see records to which they shouldn't have access. For this reason, we include in our conditions for setting $cachable a check to see if the user is logged on. As a general rule, Joomla doesn't cache pages, views or modules if a user is logged on.
Clearing the Cache
If we change any helloworld record then we clear all the cache associated with our com_helloworld component. Otherwise webpages would be shown to visitors which didn't reflect the current state of helloworld records in the database.
Caching the Ajax query
Independent of the view caching, we implement a cache for our results of the query associated with the Ajax message. This code is in our helloworld model, and we implement a callback cache, as outlined here.
We include this within the com_helloworld cache area, so that clearing the com_helloworld cache will result in clearing the cached query results as well.
We'll enable this cache to be used by logged-in users as well, but we'll include within the cache id hash the value of a call to JFactory::getUser()->getAuthorisedViewLevels() so that two users will use the same cache file only if they have the same Access permissions.
Patching Joomla
Whenever the view is stored in cache it's not just the visible html which is stored but also indications of what needs to be inserted into the html <head>, for example, links to javascript or CSS files. In the step Adding a map we passed the values of variables down to the javascript code using the line $document->addScriptOptions('params', $params);. This results in some <script> code being inserted into the html <head> so that those values are accessible within the javascript code. Unfortunately at time of writing (Joomla version 3.9.2) there is a bug in Joomla which means that these script options aren't included in the cache, so to make this tutorial step work you may need to patch the libraries/src/Document/HtmlDocument.php source file as shown in this issue.
Site Controller
Here we implement the changes to specify the conditions for com_helloworld views being cached, the URL parameters whose values will be used in the md5 hash to form the cache id, and remove the verification of the token, thus allowing our Ajax call to work even if the view is cached.
site/controller.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');
/**
* Hello World Component Controller
*
* @since 0.0.1
*/
class HelloWorldController extends JControllerLegacy
{
public function display($cachable = false, $urlparams = array())
{
$viewName = $this->input->get('view', '');
$cachable = true;
if ($viewName == 'form' || JFactory::getUser()->get('id'))
{
$cachable = false;
}
$safeurlparams = array(
'id' => 'ARRAY',
'catid' => 'ARRAY',
'list' => 'ARRAY',
'limitstart' => 'UINT',
'Itemid' => 'INT',
'view' => 'CMD',
'lang' => 'CMD',
);
parent::display($cachable, $safeurlparams);
}
public function mapsearch()
{
// if (!JSession::checkToken('get'))
// {
// echo new JResponseJson(null, JText::_('JINVALID_TOKEN'), true);
// }
// else
// {
parent::display();
// }
}
}
Cleaning the Cache
In the admin helloworld model and in the site form model we need to clear the cache whenever a change is made to a helloworld database record. To enable this we just have to include a cleanCache() method as shown below. This method gets called from the JModelAdmin (aka AdminModel) methods which involve database changes.
admin/models/helloworld.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;
/**
* HelloWorld Model
*
* @since 0.0.1
*/
class HelloWorldModelHelloWorld extends JModelAdmin
{
// JModelAdmin needs to know this for storing the associations
protected $associationsContext = 'com_helloworld.item';
// Contenthistory needs to know this for restoring previous versions
public $typeAlias = 'com_helloworld.helloworld';
// batch processes supported by helloworld (over and above the standard batch processes)
protected $helloworld_batch_commands = array(
'position' => 'batchPosition',
);
/**
* Method overriding batch in JModelAdmin so that we can include the additional batch processes
* which the helloworld component supports.
*/
public function batch($commands, $pks, $contexts)
{
$this->batch_commands = array_merge($this->batch_commands, $this->helloworld_batch_commands);
return parent::batch($commands, $pks, $contexts);
}
/**
* Method implementing the batch setting of lat/long values
*/
protected function batchPosition($value, $pks, $contexts)
{
$app = JFactory::getApplication();
$app->enqueueMessage("In batchPosition");
if (isset($value['setposition']) && ($value['setposition'] === 'changePosition'))
{
if (empty($this->batchSet))
{
// Set some needed variables.
$this->user = JFactory::getUser();
$this->table = $this->getTable();
$this->tableClassName = get_class($this->table);
$this->contentType = new JUcmType;
$this->type = $this->contentType->getTypeByTable($this->tableClassName);
}
foreach ($pks as $pk)
{
if ($this->user->authorise('core.edit', $contexts[$pk]))
{
$this->table->reset();
$this->table->load($pk);
if (isset($value['latitude']))
{
$latitude = floatval($value['latitude']);
if ($latitude <= 90 && $latitude >= -90)
{
$this->table->latitude = $latitude;
}
}
if (isset($value['longitude']))
{
$longitude = floatval($value['longitude']);
if ($longitude <= 180 && $longitude >= -180)
{
$this->table->longitude = $longitude;
}
}
if (!$this->table->store())
{
$this->setError($this->table->getError());
return false;
}
}
else
{
$this->setError(JText::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));
return false;
}
}
}
return true;
}
/**
* Method to override generateTitle() because the helloworld component uses 'greeting' as the title field
*/
public function generateTitle($categoryId, $table)
{
// Alter the title & alias
$data = $this->generateNewTitle($categoryId, $table->alias, $table->greeting);
$table->greeting = $data['0'];
$table->alias = $data['1'];
}
/**
* Method to override getItem to allow us to convert the JSON-encoded image information
* in the database record into an array for subsequent prefilling of the edit form
* We also use this method to prefill the tags and associations
*/
public function getItem($pk = null)
{
$item = parent::getItem($pk);
if ($item AND property_exists($item, 'image'))
{
$registry = new Registry($item->image);
$item->imageinfo = $registry->toArray();
}
if (!empty($item->id))
{
$tagsHelper = new JHelperTags;
$item->tags = $tagsHelper->getTagIds($item->id, 'com_helloworld.helloworld');
}
// Load associated items
if (JLanguageAssociations::isEnabled())
{
$item->associations = array();
if ($item->id != null)
{
$associations = JLanguageAssociations::getAssociations('com_helloworld', '#__helloworld', 'com_helloworld.item', (int)$item->id);
foreach ($associations as $tag => $association)
{
$item->associations[$tag] = $association->id;
}
}
}
return $item;
}
/**
* Method to get a table object, load it if necessary.
*
* @param string $type The table name. Optional.
* @param string $prefix The class prefix. Optional.
* @param array $config Configuration array for model. Optional.
*
* @return JTable A JTable object
*
* @since 1.6
*/
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 1.6
*/
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 preprocess the form to add the association fields dynamically
*
* @return none
*/
protected function preprocessForm(JForm $form, $data, $group = 'helloworld')
{
// Association content items
if (JLanguageAssociations::isEnabled())
{
$languages = JLanguageHelper::getContentLanguages(false, true, null, 'ordering', 'asc');
if (count($languages) > 1)
{
$addform = new SimpleXMLElement('<form />');
$fields = $addform->addChild('fields');
$fields->addAttribute('name', 'associations');
$fieldset = $fields->addChild('fieldset');
$fieldset->addAttribute('name', 'item_associations');
foreach ($languages as $language)
{
$field = $fieldset->addChild('field');
$field->addAttribute('name', $language->lang_code);
$field->addAttribute('type', 'modal_helloworld');
$field->addAttribute('language', $language->lang_code);
$field->addAttribute('label', $language->title);
$field->addAttribute('translate_label', 'false');
}
$form->load($addform, false);
}
}
parent::preprocessForm($form, $data, $group);
}
/**
* Method to get the script 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 1.6
*/
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;
}
/**
* Method to override the JModelAdmin save() function to handle Save as Copy correctly
*
* @param The helloworld record data submitted from the form.
*
* @return parent::save() return value
*/
public function save($data)
{
$input = JFactory::getApplication()->input;
JLoader::register('CategoriesHelper', JPATH_ADMINISTRATOR . '/components/com_categories/helpers/categories.php');
// Validate the category id
// validateCategoryId() returns 0 if the catid can't be found
if ((int) $data['catid'] > 0)
{
$data['catid'] = CategoriesHelper::validateCategoryId($data['catid'], 'com_helloworld');
}
// Alter the greeting and alias for save as copy
if ($input->get('task') == 'save2copy')
{
$origTable = clone $this->getTable();
$origTable->load($input->getInt('id'));
if ($data['greeting'] == $origTable->greeting)
{
list($greeting, $alias) = $this->generateNewTitle($data['catid'], $data['alias'], $data['greeting']);
$data['greeting'] = $greeting;
$data['alias'] = $alias;
}
else
{
if ($data['alias'] == $origTable->alias)
{
$data['alias'] = '';
}
}
// standard Joomla practice is to set the new record as unpublished
$data['published'] = 0;
}
$result = parent::save($data);
if ($result)
{
$this->getTable()->rebuild(1);
}
return $result;
}
/**
* Method to check if it's OK to delete a message. Overrides JModelAdmin::canDelete
*/
protected function canDelete($record)
{
if( !empty( $record->id ) )
{
return JFactory::getUser()->authorise( "core.delete", "com_helloworld.helloworld." . $record->id );
}
}
/**
* Prepare a helloworld record for saving in the database
*/
protected function prepareTable($table)
{
}
/**
* Save the record reordering after a record is dragged to a new position in the helloworlds view
*/
public function saveorder($idArray = null, $lft_array = null)
{
// Get an instance of the table object.
$table = $this->getTable();
if (!$table->saveorder($idArray, $lft_array))
{
$this->setError($table->getError());
return false;
}
return true;
}
protected function cleanCache($group = null, $client_id = 0)
{
parent::cleanCache('com_helloworld');
}
}
site/models/form.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');
/**
* HelloWorld Model
*
* @since 0.0.1
*/
class HelloWorldModelForm extends JModelAdmin
{
/**
* Method to get a table object, load it if necessary.
*
* @param string $type The table name. Optional.
* @param string $prefix The class prefix. Optional.
* @param array $config Configuration array for model. Optional.
*
* @return JTable A JTable object
*
* @since 1.6
*/
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 1.6
*/
public function getForm($data = array(), $loadData = true)
{
// Get the form.
$form = $this->loadForm(
'com_helloworld.form',
'add-form',
array(
'control' => 'jform',
'load_data' => $loadData
)
);
if (empty($form))
{
$errors = $this->getErrors();
throw new Exception(implode("\n", $errors), 500);
}
return $form;
}
/**
* Method to get the data that should be injected in the form.
* As this form is for add, we're not prefilling the form with an existing record
* But if the user has previously hit submit and the validation has found an error,
* then we inject what was previously entered.
*
* @return mixed The data for the form.
*
* @since 1.6
*/
protected function loadFormData()
{
// Check the session for previously entered form data.
$data = JFactory::getApplication()->getUserState(
'com_helloworld.edit.helloworld.data',
array()
);
return $data;
}
/**
* Method to get the script that have to be included on the form
* This returns the script associated with helloworld field greeting validation
*
* @return string Script files
*/
public function getScript()
{
return 'administrator/components/com_helloworld/models/forms/helloworld.js';
}
/**
* Prepare a helloworld record for saving in the database
*/
protected function prepareTable($table)
{
}
protected function cleanCache($group = null, $client_id = 0)
{
parent::cleanCache('com_helloworld');
}
}
Site Helloworld Model
In the site helloworld model we change the getMapSearchResults() method which is called whenever the ajax request is received. We have to do the following
- get a cache object via the JFactory::getCache() call
- form a cache id which is unique enough – to do this we use the values which feed into the SQL WHERE clause, including the Access levels which the user has access to, and the language.
- call $cache->get() passing the cache id. This function will look to see if there's a cache with the given cache id, and if so, will use that data. If it can't then it will use the first 2 parameters we pass, which are the name of the function which provides the data in the event that no cache is present, and an array of the parameters to pass to that function. The cache code then calls that function, passing those parameters, and captures the echoed output and results returned, and stores those in the cache for handling a future request.
site/models/helloworld.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');
JLoader::register('HelloworldHelperRoute', JPATH_ROOT . '/components/com_helloworld/helpers/route.php');
/**
* HelloWorld Model
*
* @since 0.0.1
*/
class HelloWorldModelHelloWorld extends JModelItem
{
/**
* @var object item
*/
protected $item;
/**
* Method to auto-populate the model state.
*
* This method should only be called once per instantiation and is designed
* to be called on the first call to the getState() method unless the model
* configuration flag to ignore the request is set.
*
* Note. Calling getState in this method will result in recursion.
*
* @return void
* @since 2.5
*/
protected function populateState()
{
// Get the message id
$jinput = JFactory::getApplication()->input;
$id = $jinput->get('id', 1, 'INT');
$this->setState('message.id', $id);
// Load the parameters.
$this->setState('params', JFactory::getApplication()->getParams());
parent::populateState();
}
/**
* Method to get a table object, load it if necessary.
*
* @param string $type The table name. Optional.
* @param string $prefix The class prefix. Optional.
* @param array $config Configuration array for model. Optional.
*
* @return JTable A JTable object
*
* @since 1.6
*/
public function getTable($type = 'HelloWorld', $prefix = 'HelloWorldTable', $config = array())
{
return JTable::getInstance($type, $prefix, $config);
}
/**
* Get the message
* @return object The message to be displayed to the user
*/
public function getItem($id = null)
{
if (!isset($this->item) || !is_null($id))
{
$id = is_null($id) ? $this->getState('message.id') : $id;
$db = JFactory::getDbo();
$query = $db->getQuery(true);
$query->select('h.greeting, h.params, h.image as image, c.title as category, c.access as catAccess,
h.latitude as latitude, h.longitude as longitude, h.access as access,
h.id as id, h.alias as alias, h.catid as catid, h.parent_id as parent_id, h.level as level, h.description as description')
->from('#__helloworld as h')
->leftJoin('#__categories as c ON h.catid=c.id')
->where('h.id=' . (int)$id);
if (JLanguageMultilang::isEnabled())
{
$lang = JFactory::getLanguage()->getTag();
$query->where('h.language IN ("*","' . $lang . '")');
}
$db->setQuery((string)$query);
if ($this->item = $db->loadObject())
{
// Load the JSON string
$params = new JRegistry;
$params->loadString($this->item->params, 'JSON');
$this->item->params = $params;
// Merge global params with item params
$params = clone $this->getState('params');
$params->merge($this->item->params);
$this->item->params = $params;
// Convert the JSON-encoded image info into an array
$image = new JRegistry;
$image->loadString($this->item->image, 'JSON');
$this->item->imageDetails = $image;
// Check if the user can access this record (and category)
$user = JFactory::getUser();
$userAccessLevels = $user->getAuthorisedViewLevels();
if ($user->authorise('core.admin')) // ie superuser
{
$this->item->canAccess = true;
}
else
{
if ($this->item->catid == 0)
{
$this->item->canAccess = in_array($this->item->access, $userAccessLevels);
}
else
{
$this->item->canAccess = in_array($this->item->access, $userAccessLevels) && in_array($this->item->catAccess, $userAccessLevels);
}
}
}
else
{
throw new Exception('Helloworld id not found', 404);
}
}
return $this->item;
}
public function getMapParams()
{
if ($this->item)
{
$url = HelloworldHelperRoute::getAjaxURL();
$this->mapParams = array(
'latitude' => $this->item->latitude,
'longitude' => $this->item->longitude,
'zoom' => 10,
'greeting' => $this->item->greeting,
'ajaxurl' => $url
);
return $this->mapParams;
}
else
{
throw new Exception('No helloworld details available for map', 500);
}
}
public function getMapSearchResults($mapbounds)
{
if (JFactory::getConfig()->get('caching') >= 1)
{
// Build a cache ID based on the conditions for the SQL where clause
$groups = implode(',', JFactory::getUser()->getAuthorisedViewLevels());
$cacheId = $groups . '.' . $mapbounds['minlat'] . '.' . $mapbounds['maxlat'] . '.' .
$mapbounds['minlng'] . '.' . $mapbounds['maxlng'];
if (JLanguageMultilang::isEnabled())
{
$lang = JFactory::getLanguage()->getTag();
$cacheId .= $lang;
}
$cache = JFactory::getCache('com_helloworld', 'callback');
$results = $cache->get(array($this, '_getMapSearchResults'), array($mapbounds), md5($cacheId), false);
return $results;
}
else
{
return $this->_getMapSearchResults($mapbounds);
}
}
public function _getMapSearchResults($mapbounds)
{
try
{
$db = JFactory::getDbo();
$query = $db->getQuery(true);
$query->select('h.id, h.alias, h.catid, h.greeting, h.latitude, h.longitude, h.access')
->from('#__helloworld as h')
->where('h.latitude > ' . $mapbounds['minlat'] .
' AND h.latitude < ' . $mapbounds['maxlat'] .
' AND h.longitude > ' . $mapbounds['minlng'] .
' AND h.longitude < ' . $mapbounds['maxlng']);
if (JLanguageMultilang::isEnabled())
{
$lang = JFactory::getLanguage()->getTag();
$query->where('h.language IN ("*","' . $lang . '")');
}
$user = JFactory::getUser();
$loggedIn = $user->get('guest') != 1;
if ($loggedIn && !$user->authorise('core.admin'))
{
$userAccessLevels = $user->getAuthorisedViewLevels();
$query->where('h.access IN (' . implode(",", $userAccessLevels) . ')');
$query->join('LEFT', $db->quoteName('#__categories', 'c') . ' ON c.id = h.catid');
$query->where('(c.access IN (' . implode(",", $userAccessLevels) . ') OR h.catid = 0)');
}
$db->setQuery($query);
$results = $db->loadObjectList();
}
catch (Exception $e)
{
$msg = $e->getMessage();
JFactory::getApplication()->enqueueMessage($msg, 'error');
$results = null;
}
if (JLanguageMultilang::isEnabled())
{
$query_lang = "&lang={$lang}";
}
else
{
$query_lang = "";
}
for ($i = 0; $i < count($results); $i++)
{
$results[$i]->url = JRoute::_('index.php?option=com_helloworld&view=helloworld&id=' . $results[$i]->id .
":" . $results[$i]->alias . '&catid=' . $results[$i]->catid . $query_lang);
}
return $results;
}
public function getChildren($id)
{
$table = $this->getTable();
$children = $table->getTree($id);
return $children;
}
}
包裝元件
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/default_batch_body.php
- admin/views/helloworlds/tmpl/default_batch_footer.php
- admin/views/helloworlds/tmpl/modal.php
- admin/layouts/index.html
- admin/layouts/position.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.31</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>
<!-- layout files section -->
<folder>layouts</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>