Tags or Keywords are a flexible solution to organise content in Joomla A keyword can be assigned to many different elements of different content types. Each element can have unlimited tags.
Joomla's tagging system is used in all core extensions. It is designed to be easily integrated into other extensions that use standard Joomla design patterns. Using tags in a third-party extension is quite simple. Using it in your own extension requires the modifications explained in this section.
For impatient people: View the changed program code in the Diff View[^codeberg.org/astrid/j4examplecode/compare/t28...t29] and copy these changes into your development version.
Step by step
New files
No new files.
Modified files
administrator/components/com_foos/forms/filter_foos.xml
The form through which the search tools are managed receives an entry for the keywords.
administrator/components/com_foos/forms/filter_foos.xml
<option value="*">JALL</option>
</field>
<field
name="tag"
type="tag"
label="JTAG"
multiple="true"
mode="nested"
custom="false"
hint="JOPTION_SELECT_TAG"
onchange="this.form.submit();"
/>
</fields>
<fields name="list">
administrator/components/com_foos/forms/foo.xml
In the XML form, we add the form field that contains the information about the tag. Since we use Joomla Standard, we can use many ready-made functions out-of-the-box.
administrator/components/com_foos/forms/foo.xml
label="JFIELD_ORDERING_LABEL"
content_type="com_foos.foo"
/>
<field
name="tags"
type="tag"
label="JTAG"
class="advancedSelect"
multiple="true"
/>
</fieldset>
<fields name="params" label="JGLOBAL_FIELDSET_DISPLAY_OPTIONS">
<fieldset name="display" label="JGLOBAL_FIELDSET_DISPLAY_OPTIONS">
administrator/components/com_foos/script.php
In the installation script, we make sure that our extension is recognised as a separate content type in Joomla.
administrator/components/com_foos/script.php
{
echo Text::_('COM_FOOS_INSTALLERSCRIPT_POSTFLIGHT');
$this->saveContentTypes();
return true;
}
return $id;
}
private function saveContentTypes()
{
$table = Table::getInstance('Contenttype', 'JTable');
$table->load(['type_alias' => 'com_foos.foo']);
$tablestring = '{
"special": {
"dbtable": "#__foos",
"key": "id",
"type": "FooTable",
"prefix": "Joomla\\\\Component\\\\Foos\\\\Administrator\\\\Table\\\\",
"config": "array()"
},
"common": {
"dbtable": "#__ucm_content",
"key": "ucm_id",
"type": "Corecontent",
"prefix": "JTable",
"config": "array()"
}
}';
$fieldmapping = '{
"common": {
"core_content_item_id": "id",
"core_title": "name",
"core_state": "published",
"core_alias": "alias",
"core_publish_up": "publish_up",
"core_publish_down": "publish_down",
"core_access": "access",
"core_params": "params",
"core_featured": "featured",
"core_language": "language",
"core_ordering": "ordering",
"core_catid": "catid",
"asset_id": "null"
},
"special": {
}
}';
$contenttype = [];
$contenttype['type_id'] = ($table->type_id) ? $table->type_id : 0;
$contenttype['type_title'] = 'Foos';
$contenttype['type_alias'] = 'com_foos.foo';
$contenttype['table'] = $tablestring;
$contenttype['rules'] = '';
$contenttype['router'] = 'RouteHelper::getFooRoute';
$contenttype['field_mappings'] = $fieldmapping;
$contenttype['content_history_options'] = '';
$table->save($contenttype);
return;
}
}
administrator/components/com_foos/src/Model/FooModel.php
In the model of the element, we insert the tags into the batch processing batch and ensure that the associated tags are loaded.
administrator/components/com_foos/src/Model/FooModel.php
use Joomla\CMS\Language\LanguageHelper;
use Joomla\Database\ParameterType;
use Joomla\Utilities\ArrayHelper;
use Joomla\CMS\Helper\TagsHelper;
/**
* Item Model for a Foo.
... class FooModel extends AdminModel
protected $batch_commands = [
'assetgroup_id' => 'batchAccess',
'language_id' => 'batchLanguage',
'tag' => 'batchTag',
'user_id' => 'batchUser',
];
... public function getItem($pk = null)
}
}
// Load item tags
if (!empty($item->id)) {
$item->tags = new TagsHelper;
$item->tags->getTagIds($item->id, 'com_foos.foo');
}
return $item;
}
administrator/components/com_foos/src/Model/FoosModel.php
We change the model of the overview list of our extension in the backend regarding the filters and the database query.
administrator/components/com_foos/src/Model/FoosModel.php
use Joomla\CMS\Language\Associations;
use Joomla\CMS\Factory;
use Joomla\Utilities\ArrayHelper;
use Joomla\Database\ParameterType;
/**
* Methods supporting a list of foo records.
... protected function getListQuery()
$query->where($db->quoteName('a.language') . ' = ' . $db->quote($language));
}
// Filter by a single or group of tags.
$tag = $this->getState('filter.tag');
// Run simplified query when filtering by one tag.
if (\is_array($tag) && \count($tag) === 1) {
$tag = $tag[0];
}
if ($tag && \is_array($tag)) {
$tag = ArrayHelper::toInteger($tag);
$subQuery = $db->getQuery(true)
->select('DISTINCT ' . $db->quoteName('content_item_id'))
->from($db->quoteName('#__contentitem_tag_map'))
->where(
[
$db->quoteName('tag_id') . ' IN (' . implode(',', $query->bindArray($tag)) . ')',
$db->quoteName('type_alias') . ' = ' . $db->quote('com_foos.foo'),
]
);
$query->join(
'INNER',
'(' . $subQuery . ') AS ' . $db->quoteName('tagmap'),
$db->quoteName('tagmap.content_item_id') . ' = ' . $db->quoteName('a.id')
);
} else if ($tag = (int) $tag) {
$query->join(
'INNER',
$db->quoteName('#__contentitem_tag_map', 'tagmap'),
$db->quoteName('tagmap.content_item_id') . ' = ' . $db->quoteName('a.id')
)
->where(
[
$db->quoteName('tagmap.tag_id') . ' = :tag',
$db->quoteName('tagmap.type_alias') . ' = ' . $db->quote('com_foos.foo'),
]
)
->bind(':tag', $tag, ParameterType::INTEGER);
}
// Filter by access level.
if ($access = $this->getState('filter.access')) {
$query->where($db->quoteName('a.access') . ' = ' . (int) $access);
administrator/components/com_foos/src/View/Foo/HtmlView.php
In the view, we ensure that the keywords matching the language are loaded.
administrator/components/com_foos/src/View/Foo/HtmlView.php
// Only allow to select categories with All language or with the forced language.
$this->form->setFieldAttribute('catid', 'language', '*,' . $forcedLanguage);
// Only allow to select tags with All language or with the forced language.
$this->form->setFieldAttribute('tags', 'language', '*,' . $forcedLanguage);
}
$this->addToolbar();
administrator/components/com_foos/tmpl/foos/default_batch_body.php
So that the batch processing can also be used for the tags, we insert a form field. With the help of this field it is possible to select a keyword that will be assigned to all selected items.
administrator/components/com_foos/tmpl/foos/default_batch_body.php
</div>
</div>
<?php endif; ?>
<div class="form-group col-md-6">
<div class="controls">
<?php echo LayoutHelper::render('joomla.html.batch.tag', []); ?>
</div>
</div>
</div>
</div>
Test your Joomla component
- install your component in Joomla version 4 to test it:
Copy the files in the administrator
folder into the administrator
folder of your Joomla 4 installation.
-
install your component as described in part one, after copying all files. As we have changed things in the installation script, this is necessary.
-
create a tag using the keyword component.
- set the just created keyword at a Foo element.
- convince yourself that filtering by keywords works.
- create a menu item that shows all elements that are assigned to a certain keyword and see the display in the frontend.
If you have tagged a Foo element and are now surprised that it is not displayed, first check whether the Foo element is published. Only published elements are displayed in the frontend.
- Create a new tag and assign it to several Foo items by batch processing.
- think about how and where you show the keywords in the frontend of your own extension. com_contact' provides a parameter that allows the website owner to set whether tags are displayed. The display is done with the help of the layout
joomla.content.tags
.
<?php if ($tparams->get('show_tags', 1) && !empty($this->item->tags->itemTags)) : ?>
<div class="com-contact__tags">
<?php $this->item->tagLayout = new FileLayout('joomla.content.tags'); ?>
<?php echo $this->item->tagLayout->render($this->item->tags->itemTags); ?>
</div>
<?php endif; ?>
You want to tag Joomla elements in order to build a keyword directory. You make the assignment by opening the element (article, category ...) and entering the keywords on the right. Do you have any problems? Are not all keywords saved. Do you also notice that not all keywords are displayed in the selection menu of other components? The reason is that the display of the keywords is limited to 30. I have searched for documentation. I have not found anything. Maybe it's because I'm not searching properly. I sometimes find it easier to look directly in the code: The place that answers the question is in the file libraries/src/Form/Field/TagField.php[^github.com/joomla/joomla-cms/blob/ba5fc69400c2fb2a27e56d0b8bec0db10c8705df/libraries/src/form/field/tagfield.php#l136]. The PR, where you can also read out the reasons for the introduction, is PR 31481[^github.com/joomla/joomla-cms/pull/31481]. The workaround is to change the display mode in the Tags component.
Links
Using Tags in a Extension[^docs.joomla.org/j3.x:using_tags_in_an_extension]
Webmentions