Developers
Configuration
A component has two XML files in its config directory. config.xml declares
the settings an administrator can change; access.xml declares the things a
group can be allowed to do. Neither is required, and a component with no
settings and no permissions of its own needs neither.
app/components/com_bookings/
config/
config.xml
access.xml
Declare a parameter when a hub will reasonably want a different value from
yours — how far ahead com_bookings lets people book, whether a reservation
needs approval. Do not declare one for every constant in the component: each
is a screen an administrator has to understand, and a value your code has to
keep working for.
Permissions are the other half. Anything a lab manager may do and a user may
not is an action in access.xml, checked with User::authorise(). A
component that decides by checking User::get('username') against a list has
made a rule no hub can change.
config.xml
The root element is <config>, with <fieldset> children directly inside it
— not wrapped in a <fields> element the way a module or plugin manifest
wraps them. Each <fieldset> becomes a tab on the component's Options screen,
and each <field> becomes a control. com_kb's is short enough to read
whole:
<config>
<fieldset name="basic">
<field name="show_date" type="list" default="2" label="COM_KB_SHOW_DATE_LABEL" description="COM_KB_SHOW_DATE_DESC">
<option value="0">COM_KB_SHOW_DATE_HIDE</option>
<option value="1">COM_KB_SHOW_DATE_CREATED</option>
<option value="2">COM_KB_SHOW_DATE_MODIFIED</option>
</field>
<field name="allow_comments" type="list" default="1" label="COM_KB_ALLOW_COMMENTS_LABEL" description="COM_KB_ALLOW_COMMENTS_DESC">
<option value="0">COM_KB_DISALLOW</option>
<option value="1">COM_KB_ALLOW</option>
</field>
<field name="close_comments" type="list" default="year" label="COM_KB_CLOSE_COMMENTS_LABEL" description="COM_KB_CLOSE_COMMENTS_DESC">
<option value="never">COM_KB_FEED_CLOSE_NEVER</option>
<option value="now">COM_KB_FEED_CLOSE_NOW</option>
<option value="day">COM_KB_FEED_CLOSE_DAY</option>
<option value="week">COM_KB_FEED_CLOSE_WEEK</option>
<option value="month">COM_KB_FEED_CLOSE_MONTH</option>
<option value="6months">COM_KB_FEED_CLOSE_6MONTHS</option>
<option value="year">COM_KB_FEED_CLOSE_YEAR</option>
</field>
<field name="feeds_enabled" type="list" default="1" label="COM_KB_FEED_ENABLED_LABEL" description="COM_KB_FEED_ENABLED_DESC">
<option value="0">COM_KB_DISABLED</option>
<option value="1">COM_KB_ENABLED</option>
</field>
<field name="feed_entries" type="list" default="partial" label="COM_KB_FEED_ENTRIES_LABEL" description="COM_KB_FEED_ENTRIES_DESC">
<option value="full">COM_KB_FEED_ENTRIES_FULL</option>
<option value="partial">COM_KB_FEED_ENTRIES_PARTIAL</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_kb" section="component" />
</fieldset>
</config>
label and description are language keys, resolved from the component's
administrator language file — the Options screen runs in the administrator
client. Write them as keys, not as English; a hub running in another language
sees whatever you put there. A key defined only in site/language/ renders on
the Options screen as itself. See Languages.
Field type names a class in core/libraries/Hubzero/Form/Fields —
text, textarea, radio, checkboxes, number, password, filelist,
folderlist, accesslevel, usergroup, editor, tags, and about thirty
more. list is the exception: PHP will not allow a class called List, so the
form aliases it to Select. A list carries <option> children whose bodies
are language keys too.
The last fieldset is the same in every component that has 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_bookings" section="component" />
</fieldset>
The rules field reads access.xml for the component named in its
component attribute and renders the permission grid. Change the component
name when you copy this block — leave com_kb in it and the administrator
edits the knowledge base's permissions from your Options screen, which is a
bug nobody notices until the wrong people can do the wrong thing.
com_config reads config.xml from {component path}/config/config.xml, and
the administrator reaches the form from the toolbar:
Toolbar::preferences($this->option, '550');
access.xml
Actions are grouped into sections. The component section applies to the
component as a whole; further sections apply to individual records.
<access component="com_kb">
<section name="component">
<action name="core.admin" title="JACTION_ADMIN" description="JACTION_ADMIN_COMPONENT_DESC" />
<action name="core.manage" title="JACTION_MANAGE" description="JACTION_MANAGE_COMPONENT_DESC" />
<action name="core.create" title="JACTION_CREATE" description="JACTION_CREATE_COMPONENT_DESC" />
<action name="core.delete" title="JACTION_DELETE" description="JACTION_DELETE_COMPONENT_DESC" />
<action name="core.edit" title="JACTION_EDIT" description="JACTION_EDIT_COMPONENT_DESC" />
<action name="core.edit.state" title="JACTION_EDITSTATE" description="JACTION_EDITSTATE_COMPONENT_DESC" />
<action name="core.edit.own" title="JACTION_EDITOWN" description="JACTION_EDITOWN_COMPONENT_DESC" />
</section>
<section name="category">
<action name="core.create" title="JACTION_CREATE" description="COM_CATEGORIES_ACCESS_CREATE_DESC" />
<action name="core.delete" title="JACTION_DELETE" description="COM_CATEGORIES_ACCESS_DELETE_DESC" />
<action name="core.edit" title="JACTION_EDIT" description="COM_CATEGORIES_ACCESS_EDIT_DESC" />
<action name="core.edit.state" title="JACTION_EDITSTATE" description="COM_CATEGORIES_ACCESS_EDITSTATE_DESC" />
<action name="core.edit.own" title="JACTION_EDITOWN" description="COM_CATEGORIES_ACCESS_EDITOWN_DESC" />
</section>
<section name="article">
<action name="core.create" title="JACTION_CREATE" description="COM_CATEGORIES_ACCESS_CREATE_DESC" />
<action name="core.delete" title="JACTION_DELETE" description="COM_CATEGORIES_ACCESS_DELETE_DESC" />
<action name="core.edit" title="JACTION_EDIT" description="COM_CATEGORIES_ACCESS_EDIT_DESC" />
<action name="core.edit.state" title="JACTION_EDITSTATE" description="COM_CATEGORIES_ACCESS_EDITSTATE_DESC" />
<action name="core.edit.own" title="JACTION_EDITOWN" description="COM_CATEGORIES_ACCESS_EDITOWN_DESC" />
</section>
</access>
title and description are language keys again; the JACTION_* and
COM_CATEGORIES_ACCESS_* ones shown here are defined by the platform, so a
component that only needs the standard actions writes no new strings.
The action names are conventions the platform relies on:
| Action | Means |
|---|---|
core.admin |
may change this component's permissions |
core.manage |
may open this component in the administrator |
core.create |
may add a record |
core.edit |
may edit any record |
core.edit.own |
may edit a record they created |
core.edit.state |
may publish, unpublish, archive, trash |
core.delete |
may delete a record |
Check them with User::authorise(), against the component or against one
record:
User::authorise('core.manage', 'com_bookings'); // the component
User::authorise('core.edit', 'com_bookings.instrument.42'); // one instrument
The component-level asset those checks hang off is created by
addComponentEntry() in the migration. Without it there is no row to consult
and no permissions to edit — see Migrations. Per-record
assets are a component's own business; most components only check at
component level and keep record ownership in a created_by column.
com_kb wraps the component-level checks in a helper so its views can ask
once and reuse the answer — see Helpers.
Reading settings back
Component::params() returns a Hubzero\Config\Registry:
$params = Component::params('com_bookings');
echo $params->get('max_days_ahead', 30);
Inside a controller the same registry is already on hand as $this->config,
set by the constructor.
Per-record settings
A record may carry its own parameters in a params column, overriding the
component's for that record alone. com_kb's Article merges them in
setup(), so a view reads one registry and never has to know which level a
value came from:
$params = new Registry($this->get('params'));
$this->params = Component::params('com_bookings');
$this->params->merge($params);
Merge in that direction — component first, record over the top — or a record
with an empty params column wipes out the component's settings for that
record.
The manifest <params> block
kb.xml and other component manifests still carry a <params> element
listing settings in an older format. It is inherited, and it is not what the
Options screen reads — that is config.xml. A setting added to <params>
never appears anywhere. com_kb's administrator edit form does still parse the
manifest, through Hubzero\Html\Parameter, to render the per-article
parameter controls. Do not add one to a new component; put the settings in
config.xml and the per-record form fields in models/forms/.
Rewritten and checked against 2.4-main @ 348f0057c2 on 2026-09-10.