The shortest possible working version of this code is this as I tested this:
use Joomla\CMS\Factory;
use Joomla\CMS\Helper\TagsHelper;
function addTagsToArticle($ucmId, $articleId, $tags) {
$tagsHelper = new TagsHelper();
$app = Factory::getApplication();
$factory = $app->bootComponent('com_content')->getMVCFactory();
$table = $factory->createTable('Article', 'Table');
$table->load($articleId);
$tagsHelper->typeAlias = $table->typeAlias;
$tagsHelper->tagItem($ucmId, $table, $tags, false);
}
And you call this function like:
$newTags = ['#new#Millions', '#new#Worldwide'];
$ucmId = 5; // core_content_id of the article from ucm_content table
$itemID = 11; // article ID in #__content table
addTagsToArticle($ucmId, $itemID, $newTags);
This way the code works and save new tags to an article. without the store()
method at the end, it is not needed, and the tags has to be with #new#
prefix in the array.
THE LONG WAY OF DOING THE SAME
I have tested some codes more deeply regarding tagging content items in Joomla 4. I am not sure that too many people want to tag articles/content programmatically and I do not think that there is any official way of doing that but this is one way of doing that. Someone really worked out a quite good and complex tagging system for Joomla and I just took some parts from that core code and you can use it in your own custom methods.
First I created a function createNewTags()
using the Joomla4 core createTagsFromField()
from the TagsHelper
class. I used that because it is not worthy to create similar function and it is sure that it works well on saving new tags. If a tag was already in the tag table, then it does not save it again. However, we have to modify the core addTagMapping()
method for our custom code.
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Helper\TagsHelper;
use Joomla\CMS\UCM\UCMType;
use Joomla\Database\DatabaseDriver;
use Joomla\Database\ParameterType;
use Joomla\Utilities\ArrayHelper;
/**
* create new tags by array of strings (texts)
* @param array $tags Array of tags to be applied.
*
* @return array -- created tags
*/
function createNewTags($tags = []) {
$tagsHelper = new TagsHelper();
$new_tags = array_map(fn($tag) => "#new#$tag", $tags);
// create tags first and save it
$tags_ids = $tagsHelper->createTagsFromField($new_tags);
return $tags_ids;
}
// we have to create our own custom function for mapping
function addTagMapping($ucmId, $contentId, $table, $tags = [])
{
$db = $table->getDbo();
$item = $contentId;
$ucm = new UCMType($table->typeAlias, $db);
$typeId = $ucm->getTypeId();
// Prevent saving duplicate tags
$tags = array_values(array_unique($tags));
if (!$tags) {
return true;
}
$query = $db->getQuery(true);
$query->insert('#__contentitem_tag_map');
$query->columns(
[
$db->quoteName('core_content_id'),
$db->quoteName('content_item_id'),
$db->quoteName('tag_id'),
$db->quoteName('type_id'),
$db->quoteName('type_alias'),
$db->quoteName('tag_date'),
]
);
foreach ($tags as $tag) {
$query->values(
implode(
',',
array_merge(
$query->bindArray([(int) $ucmId, (int) $item, (int) $tag, (int) $typeId]),
$query->bindArray([$table->typeAlias], ParameterType::STRING),
[$query->currentTimestamp()]
)
)
);
}
$db->setQuery($query);
return (bool) $db->execute();
}
function tagThoseArticleItems($ucmId, $itemID, $ourTags = [], $replace = true) {
$tagsHelper = new TagsHelper();
$tags = $tagsHelper->createNewTags($ourTags);
$app = Factory::getApplication();
$factory = $app->bootComponent('com_content')->getMVCFactory();
$table = $factory->createTable('Article', 'Table');
$tagsHelper->typeAlias = $table->typeAlias;
// $table->setTagsHelper($tagsHelper); // only if the table does not have it
foreach ($tags as $tagId) {
$tagsHelper->tagDeleteInstances($tagId);
}
// we have to use our own custom function on this
// with added itemID (content item ID) to the call
$result = addTagMapping($ucmId, $itemID, $table, $tags, $replace = true);
return $result;
}
And you call this whole thing like:
$ourTags = ['getty', 'Roma', 'New York'];
// this is an existing article ID from the #__ucm_content table
$ucmId = 5;
$itemID = 11; // The article ID in #__content table
$our_tags_created = tagThoseArticleItems($ucmId, $itemID, $ourTags, $replace = true);
The second function is just doing the tag mapping correctly. I hardcoded the content Table in this, but you can refactor this for using other tables too, if not content tagging.
Important to note that $ucmId
is the core_content_id
from the #__ucm_content
table. so you should use that ID as the first argument and NOT a type like com_content.article
and not the article ID from the #__content table
. In our custom code we have to provide the article ID too from the content table.
The challenge
I tried but I have not found a short, straightforward solution on this task in Joomla on tagging content articles from an outside extension for example. Anybody else on this? The question is interesting because if someone came with a Wordpress developer background, rightfully would ask and look for a one line solution on this, like WP has on tagging a Post:
wp_set_post_tags( int $post_id, string|array $tags = '', bool $append = false )
Unfortunately at many of these small points we could realize the big difference on developer friendliness of the Content Management Systems.