Spade
Mini Shell
| Directory:~$ /proc/self/root/home/lmsyaran/www/joomla5/libraries/src/Helper/ |
| [Home] [System Details] [Kill Me] |
<?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2013 Open Source Matters, Inc.
<https://www.joomla.org>
* @license GNU General Public License version 2 or later; see
LICENSE.txt
*/
namespace Joomla\CMS\Helper;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Table\CoreContent;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\TableInterface;
use Joomla\CMS\UCM\UCMContent;
use Joomla\CMS\UCM\UCMType;
use Joomla\Database\DatabaseInterface;
use Joomla\Database\ParameterType;
use Joomla\Utilities\ArrayHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Tags helper class, provides methods to perform various tasks relevant
* tagging of content.
*
* @since 3.1
*/
class TagsHelper extends CMSHelper
{
/**
* Helper object for storing and deleting tag information.
*
* @var boolean
* @since 3.1
*/
protected $tagsChanged = false;
/**
* Whether up replace all tags or just add tags
*
* @var boolean
* @since 3.1
*/
protected $replaceTags = false;
/**
* Alias for querying mapping and content type table.
*
* @var string
* @since 3.1
*/
public $typeAlias;
/**
* Array of item tags.
*
* @var array
* @since 3.1
*/
public $itemTags;
/**
* The tags as comma separated string or array.
*
* @var mixed
* @since 4.3.0
*/
public $tags;
/**
* The new tags as comma separated string or array.
*
* @var mixed
* @since 4.3.0
*/
public $newTags;
/**
* The old tags as comma separated string or array.
*
* @var mixed
* @since 4.3.0
*/
public $oldTags;
/**
* Method to add tag rows to mapping table.
*
* @param integer $ucmId ID of the #__ucm_content item being
tagged
* @param TableInterface $table Table object being tagged
* @param array $tags Array of tags to be applied.
*
* @return boolean true on success, otherwise false.
*
* @since 3.1
*/
public function addTagMapping($ucmId, TableInterface $table, $tags =
[])
{
$db = $table->getDbo();
$key = $table->getKeyName();
$item = $table->$key;
$ucm = new UCMType($this->typeAlias, $db);
$typeId = $ucm->getTypeId();
// Insert the new tag maps
if (strpos(implode(',', $tags), '#') !== false)
{
$tags = self::createTagsFromField($tags);
}
// 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([$this->typeAlias],
ParameterType::STRING),
[$query->currentTimestamp()]
)
)
);
}
$db->setQuery($query);
return (bool) $db->execute();
}
/**
* Function that converts tags paths into paths of names
*
* @param array $tags Array of tags
*
* @return array
*
* @since 3.1
*/
public static function convertPathsToNames($tags)
{
// We will replace path aliases with tag names
if ($tags) {
// Create an array with all the aliases of the results
$aliases = [];
foreach ($tags as $tag) {
if (!empty($tag->path)) {
if ($pathParts = explode('/', $tag->path))
{
$aliases = array_merge($aliases, $pathParts);
}
}
}
// Get the aliases titles in one single query and map the
results
if ($aliases) {
// Remove duplicates
$aliases = array_values(array_unique($aliases));
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select(
[
$db->quoteName('alias'),
$db->quoteName('title'),
]
)
->from($db->quoteName('#__tags'))
->whereIn($db->quoteName('alias'),
$aliases, ParameterType::STRING);
$db->setQuery($query);
try {
$aliasesMapper =
$db->loadAssocList('alias');
} catch (\RuntimeException $e) {
return false;
}
// Rebuild the items path
if ($aliasesMapper) {
foreach ($tags as $tag) {
$namesPath = [];
if (!empty($tag->path)) {
if ($pathParts = explode('/',
$tag->path)) {
foreach ($pathParts as $alias) {
if (isset($aliasesMapper[$alias])) {
$namesPath[] =
$aliasesMapper[$alias]['title'];
} else {
$namesPath[] = $alias;
}
}
$tag->text = implode('/',
$namesPath);
}
}
}
}
}
}
return $tags;
}
/**
* Create any new tags by looking for #new# in the strings
*
* @param array $tags Tags text array from the field
*
* @return mixed If successful, metadata with new tag titles
replaced by tag ids. Otherwise false.
*
* @since 3.1
*/
public function createTagsFromField($tags)
{
if (empty($tags) || $tags[0] == '') {
return;
}
// We will use the tags table to store them
$tagTable =
Factory::getApplication()->bootComponent('com_tags')->getMVCFactory()->createTable('Tag',
'Administrator');
$newTags = [];
$canCreate =
Factory::getUser()->authorise('core.create',
'com_tags');
foreach ($tags as $key => $tag) {
// User is not allowed to create tags, so don't create.
if (!$canCreate && strpos($tag, '#new#') !==
false) {
continue;
}
// Remove the #new# prefix that identifies new tags
$tagText = str_replace('#new#', '', $tag);
if ($tagText === $tag) {
$newTags[] = (int) $tag;
} else {
// Clear old data if exist
$tagTable->reset();
// Try to load the selected tag
if ($tagTable->load(['title' => $tagText]))
{
$newTags[] = (int) $tagTable->id;
} else {
// Prepare tag data
$tagTable->id = 0;
$tagTable->title = $tagText;
$tagTable->published = 1;
$tagTable->description = '';
// $tagTable->language = property_exists ($item,
'language') ? $item->language : '*';
$tagTable->language = '*';
$tagTable->access = 1;
// Make this item a child of the root tag
$tagTable->setLocation($tagTable->getRootId(),
'last-child');
// Try to store tag
if ($tagTable->check()) {
// Assign the alias as path (autogenerated tags
have always level 1)
$tagTable->path = $tagTable->alias;
if ($tagTable->store()) {
$newTags[] = (int) $tagTable->id;
}
}
}
}
}
// At this point $tags is an array of all tag ids
$this->tags = $newTags;
$result = $newTags;
return $result;
}
/**
* Method to delete the tag mappings and #__ucm_content record for an
item
*
* @param TableInterface $table Table object of content
table where delete occurred
* @param integer|array $contentItemId ID of the content item. Or
an array of key/value pairs with array key
* being a primary key name
and value being the content item ID. Note
* multiple primary keys are
not supported
*
* @return boolean true on success, false on failure
*
* @since 3.1
* @throws \InvalidArgumentException
*/
public function deleteTagData(TableInterface $table, $contentItemId)
{
$key = $table->getKeyName();
if (!\is_array($contentItemId)) {
$contentItemId = [$key => $contentItemId];
}
// If we have multiple items for the content item primary key we
currently don't support this so
// throw an InvalidArgumentException for now
if (\count($contentItemId) != 1) {
throw new \InvalidArgumentException('Multiple primary keys
are not supported as a content item id');
}
$result = $this->unTagItem($contentItemId[$key], $table);
/** @var CoreContent $ucmContentTable */
$ucmContentTable = Table::getInstance('CoreContent');
return $result &&
$ucmContentTable->deleteByContentId($contentItemId[$key],
$this->typeAlias);
}
/**
* Method to get a list of tags for an item, optionally with the tag
data.
*
* @param string $contentType Content type alias. Dot separated.
* @param integer $id Id of the item to retrieve tags for.
* @param boolean $getTagData If true, data from the tags table
will be included, defaults to true.
*
* @return array Array of tag objects
*
* @since 3.1
*/
public function getItemTags($contentType, $id, $getTagData = true)
{
// Cast as integer until method is typehinted.
$id = (int) $id;
// Initialize some variables.
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('m.tag_id'))
->from($db->quoteName('#__contentitem_tag_map',
'm'))
->where(
[
$db->quoteName('m.type_alias') . ' =
:contentType',
$db->quoteName('m.content_item_id') .
' = :id',
$db->quoteName('t.published') . ' =
1',
]
)
->bind(':contentType', $contentType)
->bind(':id', $id, ParameterType::INTEGER);
$user = Factory::getUser();
$groups = $user->getAuthorisedViewLevels();
$query->whereIn($db->quoteName('t.access'),
$groups);
// Optionally filter on language
$language =
ComponentHelper::getParams('com_tags')->get('tag_list_language_filter',
'all');
if ($language !== 'all') {
if ($language === 'current_language') {
$language = $this->getCurrentLanguage();
}
$query->whereIn($db->quoteName('language'),
[$language, '*'], ParameterType::STRING);
}
if ($getTagData) {
$query->select($db->quoteName('t') .
'.*');
}
$query->join('INNER',
$db->quoteName('#__tags', 't'),
$db->quoteName('m.tag_id') . ' = ' .
$db->quoteName('t.id'));
$db->setQuery($query);
$this->itemTags = $db->loadObjectList();
return $this->itemTags;
}
/**
* Method to get a list of tags for multiple items, optionally with the
tag data.
*
* @param string $contentType Content type alias. Dot separated.
* @param array $ids Id of the item to retrieve tags for.
* @param boolean $getTagData If true, data from the tags table
will be included, defaults to true.
*
* @return array Array of tag objects grouped by Id.
*
* @since 4.2.0
*/
public function getMultipleItemTags($contentType, array $ids,
$getTagData = true)
{
$data = [];
$ids = array_map('intval', $ids);
/** @var DatabaseInterface $db */
$db = Factory::getContainer()->get(DatabaseInterface::class);
$query = $db->getQuery(true)
->select($db->quoteName(['m.tag_id',
'm.content_item_id']))
->from($db->quoteName('#__contentitem_tag_map',
'm'))
->where(
[
$db->quoteName('m.type_alias') . ' =
:contentType',
$db->quoteName('t.published') . ' =
1',
]
)
->whereIn($db->quoteName('m.content_item_id'),
$ids)
->bind(':contentType', $contentType);
$query->join('INNER',
$db->quoteName('#__tags', 't'),
$db->quoteName('m.tag_id') . ' = ' .
$db->quoteName('t.id'));
$groups = Factory::getUser()->getAuthorisedViewLevels();
$query->whereIn($db->quoteName('t.access'),
$groups);
// Optionally filter on language
$language =
ComponentHelper::getParams('com_tags')->get('tag_list_language_filter',
'all');
if ($language !== 'all') {
if ($language === 'current_language') {
$language = $this->getCurrentLanguage();
}
$query->whereIn($db->quoteName('language'),
[$language, '*'], ParameterType::STRING);
}
if ($getTagData) {
$query->select($db->quoteName('t') .
'.*');
}
$db->setQuery($query);
$rows = $db->loadObjectList();
// Group data by item Id.
foreach ($rows as $row) {
$data[$row->content_item_id][] = $row;
unset($row->content_item_id);
}
return $data;
}
/**
* Method to get a list of tags for a given item.
* Normally used for displaying a list of tags within a layout
*
* @param mixed $ids The id or array of ids (primary key) of
the item to be tagged.
* @param string $prefix Dot separated string with the option and
view to be used for a url.
*
* @return string Comma separated list of tag Ids.
*
* @since 3.1
*/
public function getTagIds($ids, $prefix)
{
if (empty($ids)) {
return;
}
/**
* Ids possible formats:
* ---------------------
* $id = 1;
* $id = array(1,2);
* $id = array('1,3,4,19');
* $id = '1,3';
*/
$ids = (array) $ids;
$ids = implode(',', $ids);
$ids = explode(',', $ids);
$ids = ArrayHelper::toInteger($ids);
$db = Factory::getDbo();
// Load the tags.
$query = $db->getQuery(true)
->select($db->quoteName('t.id'))
->from($db->quoteName('#__tags',
't'))
->join('INNER',
$db->quoteName('#__contentitem_tag_map', 'm'),
$db->quoteName('m.tag_id') . ' = ' .
$db->quoteName('t.id'))
->where($db->quoteName('m.type_alias') . '
= :prefix')
->whereIn($db->quoteName('m.content_item_id'),
$ids)
->bind(':prefix', $prefix);
$db->setQuery($query);
// Add the tags to the content data.
$tagsList = $db->loadColumn();
$this->tags = implode(',', $tagsList);
return $this->tags;
}
/**
* Method to get a query to retrieve a detailed list of items for a
tag.
*
* @param mixed $tagId Tag or array of tags to be
matched
* @param mixed $typesr Null, type or array of type
aliases for content types to be included in the results
* @param boolean $includeChildren True to include the results from
child tags
* @param string $orderByOption Column to order the results by
* @param string $orderDir Direction to sort the results in
* @param boolean $anyOrAll True to include items matching
at least one tag, false to include
* items all tags in the array.
* @param string $languageFilter Optional filter on language.
Options are 'all', 'current' or any string.
* @param string $stateFilter Optional filtering on
publication state, defaults to published or unpublished.
*
* @return \Joomla\Database\DatabaseQuery Query to retrieve a list of
tags
*
* @since 3.1
*/
public function getTagItemsQuery(
$tagId,
$typesr = null,
$includeChildren = false,
$orderByOption = 'c.core_title',
$orderDir = 'ASC',
$anyOrAll = true,
$languageFilter = 'all',
$stateFilter = '0,1'
) {
// Create a new query object.
$db = Factory::getDbo();
$query = $db->getQuery(true);
$user = Factory::getUser();
$nullDate = $db->getNullDate();
$nowDate = Factory::getDate()->toSql();
// Force ids to array and sanitize
$tagIds = (array) $tagId;
$tagIds = implode(',', $tagIds);
$tagIds = explode(',', $tagIds);
$tagIds = ArrayHelper::toInteger($tagIds);
$ntagsr = \count($tagIds);
// If we want to include children we have to adjust the list of
tags.
// We do not search child tags when the match all option is
selected.
if ($includeChildren) {
$tagTreeArray = [];
foreach ($tagIds as $tag) {
$this->getTagTreeArray($tag, $tagTreeArray);
}
$tagIds = array_values(array_unique(array_merge($tagIds,
$tagTreeArray)));
}
// Sanitize filter states
$stateFilters = explode(',', $stateFilter);
$stateFilters = ArrayHelper::toInteger($stateFilters);
// M is the mapping table. C is the core_content table. Ct is the
content_types table.
$query->select(
[
$db->quoteName('m.type_alias'),
$db->quoteName('m.content_item_id'),
$db->quoteName('m.core_content_id'),
'COUNT(' .
$db->quoteName('m.tag_id') . ') AS ' .
$db->quoteName('match_count'),
'MAX(' .
$db->quoteName('m.tag_date') . ') AS ' .
$db->quoteName('tag_date'),
'MAX(' .
$db->quoteName('c.core_title') . ') AS ' .
$db->quoteName('core_title'),
'MAX(' .
$db->quoteName('c.core_params') . ') AS ' .
$db->quoteName('core_params'),
'MAX(' .
$db->quoteName('c.core_alias') . ') AS ' .
$db->quoteName('core_alias'),
'MAX(' .
$db->quoteName('c.core_body') . ') AS ' .
$db->quoteName('core_body'),
'MAX(' .
$db->quoteName('c.core_state') . ') AS ' .
$db->quoteName('core_state'),
'MAX(' .
$db->quoteName('c.core_access') . ') AS ' .
$db->quoteName('core_access'),
'MAX(' .
$db->quoteName('c.core_metadata') . ') AS ' .
$db->quoteName('core_metadata'),
'MAX(' .
$db->quoteName('c.core_created_user_id') . ') AS ' .
$db->quoteName('core_created_user_id'),
'MAX(' .
$db->quoteName('c.core_created_by_alias') . ') AS' .
$db->quoteName('core_created_by_alias'),
'MAX(' .
$db->quoteName('c.core_created_time') . ') AS ' .
$db->quoteName('core_created_time'),
'MAX(' .
$db->quoteName('c.core_images') . ') AS ' .
$db->quoteName('core_images'),
'CASE WHEN ' .
$db->quoteName('c.core_modified_time') . ' = :nullDate
THEN ' . $db->quoteName('c.core_created_time')
. ' ELSE ' .
$db->quoteName('c.core_modified_time') . ' END AS '
. $db->quoteName('core_modified_time'),
'MAX(' .
$db->quoteName('c.core_language') . ') AS ' .
$db->quoteName('core_language'),
'MAX(' .
$db->quoteName('c.core_catid') . ') AS ' .
$db->quoteName('core_catid'),
'MAX(' .
$db->quoteName('c.core_publish_up') . ') AS ' .
$db->quoteName('core_publish_up'),
'MAX(' .
$db->quoteName('c.core_publish_down') . ') AS ' .
$db->quoteName('core_publish_down'),
'MAX(' .
$db->quoteName('ct.type_title') . ') AS ' .
$db->quoteName('content_type_title'),
'MAX(' . $db->quoteName('ct.router')
. ') AS ' . $db->quoteName('router'),
'CASE WHEN ' .
$db->quoteName('c.core_created_by_alias') . ' > '
. $db->quote(' ')
. ' THEN ' .
$db->quoteName('c.core_created_by_alias') . ' ELSE '
. $db->quoteName('ua.name') . ' END AS ' .
$db->quoteName('author'),
$db->quoteName('ua.email',
'author_email'),
]
)
->bind(':nullDate', $nullDate)
->from($db->quoteName('#__contentitem_tag_map',
'm'))
->join(
'INNER',
$db->quoteName('#__ucm_content',
'c'),
$db->quoteName('m.type_alias') . ' =
' . $db->quoteName('c.core_type_alias')
. ' AND ' .
$db->quoteName('m.core_content_id') . ' = ' .
$db->quoteName('c.core_content_id')
)
->join('INNER',
$db->quoteName('#__content_types', 'ct'),
$db->quoteName('ct.type_alias') . ' = ' .
$db->quoteName('m.type_alias'));
// Join over categories to get only tags from published categories
$query->join('LEFT',
$db->quoteName('#__categories', 'tc'),
$db->quoteName('tc.id') . ' = ' .
$db->quoteName('c.core_catid'));
// Join over the users for the author and email
$query->join('LEFT',
$db->quoteName('#__users', 'ua'),
$db->quoteName('ua.id') . ' = ' .
$db->quoteName('c.core_created_user_id'))
->whereIn($db->quoteName('c.core_state'),
$stateFilters)
->whereIn($db->quoteName('m.tag_id'), $tagIds)
->extendWhere(
'AND',
[
$db->quoteName('c.core_catid') . ' =
0',
$db->quoteName('tc.published') . ' =
1',
],
'OR'
);
// Get the type data, limited to types in the request if there are
any specified.
$typesarray = self::getTypes('assocList', $typesr,
false);
$typeAliases = array_column($typesarray, 'type_alias');
$query->whereIn($db->quoteName('m.type_alias'),
$typeAliases, ParameterType::STRING);
$groups =
array_values(array_unique($user->getAuthorisedViewLevels()));
$groups[] = 0;
$query->whereIn($db->quoteName('c.core_access'),
$groups);
if (!\in_array(0, $stateFilters, true)) {
$query->extendWhere(
'AND',
[
$db->quoteName('c.core_publish_up') .
' = :nullDate1',
$db->quoteName('c.core_publish_up') .
' IS NULL',
$db->quoteName('c.core_publish_up') .
' <= :nowDate1',
],
'OR'
)
->extendWhere(
'AND',
[
$db->quoteName('c.core_publish_down')
. ' = :nullDate2',
$db->quoteName('c.core_publish_down')
. ' IS NULL',
$db->quoteName('c.core_publish_down')
. ' >= :nowDate2',
],
'OR'
)
->bind([':nullDate1', ':nullDate2'],
$nullDate)
->bind([':nowDate1', ':nowDate2'],
$nowDate);
}
// Optionally filter on language
if ($languageFilter !== 'all') {
if ($languageFilter === 'current_language') {
$languageFilter = $this->getCurrentLanguage();
}
$query->whereIn($db->quoteName('c.core_language'),
[$languageFilter, '*'], ParameterType::STRING);
}
$query->group(
[
$db->quoteName('m.type_alias'),
$db->quoteName('m.content_item_id'),
$db->quoteName('m.core_content_id'),
$db->quoteName('core_modified_time'),
$db->quoteName('core_created_time'),
$db->quoteName('core_created_by_alias'),
$db->quoteName('author'),
$db->quoteName('author_email'),
]
);
// Use HAVING if matching all tags and we are matching more than
one tag.
if ($ntagsr > 1 && $anyOrAll != 1 &&
$includeChildren != 1) {
// The number of results should equal the number of tags
requested.
$query->having('COUNT(' .
$db->quoteName('m.tag_id') . ') = :ntagsr')
->bind(':ntagsr', $ntagsr,
ParameterType::INTEGER);
}
// Set up the order by using the option chosen
if ($orderByOption === 'match_count') {
$orderBy = 'COUNT(' .
$db->quoteName('m.tag_id') . ')';
} else {
$orderBy = 'MAX(' . $db->quoteName($orderByOption)
. ')';
}
$query->order($orderBy . ' ' . $orderDir);
return $query;
}
/**
* Function that converts tag ids to their tag names
*
* @param array $tagIds Array of integer tag ids.
*
* @return array An array of tag names.
*
* @since 3.1
*/
public function getTagNames($tagIds)
{
$tagNames = [];
if (\is_array($tagIds) && \count($tagIds) > 0) {
$tagIds = ArrayHelper::toInteger($tagIds);
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('title'))
->from($db->quoteName('#__tags'))
->whereIn($db->quoteName('id'), $tagIds)
->order($db->quoteName('title'));
$db->setQuery($query);
$tagNames = $db->loadColumn();
}
return $tagNames;
}
/**
* Method to get an array of tag ids for the current tag and its
children
*
* @param integer $id An optional ID
* @param array &$tagTreeArray Array containing the tag tree
*
* @return mixed
*
* @since 3.1
*/
public function getTagTreeArray($id, &$tagTreeArray = [])
{
// Get a level row instance.
$table =
Factory::getApplication()->bootComponent('com_tags')->getMVCFactory()->createTable('Tag',
'Administrator');
if ($table->isLeaf($id)) {
$tagTreeArray[] = $id;
return $tagTreeArray;
}
$tagTree = $table->getTree($id);
// Attempt to load the tree
if ($tagTree) {
foreach ($tagTree as $tag) {
$tagTreeArray[] = $tag->id;
}
return $tagTreeArray;
}
}
/**
* Method to get a list of types with associated data.
*
* @param string $arrayType Optionally specify that the returned
list consist of objects, associative arrays, or arrays.
* Options are: rowList, assocList, and
objectList
* @param array $selectTypes Optional array of type ids or
aliases to limit the results to. Often from a request.
* @param boolean $useAlias If true, the alias is used to match,
if false the type_id is used.
*
* @return array Array of types
*
* @since 3.1
*/
public static function getTypes($arrayType = 'objectList',
$selectTypes = null, $useAlias = true)
{
// Initialize some variables.
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('*');
if (!empty($selectTypes)) {
$selectTypes = (array) $selectTypes;
if ($useAlias) {
$query->whereIn($db->quoteName('type_alias'), $selectTypes,
ParameterType::STRING);
} else {
$selectTypes = ArrayHelper::toInteger($selectTypes);
$query->whereIn($db->quoteName('type_id'),
$selectTypes);
}
}
$query->from($db->quoteName('#__content_types'));
$db->setQuery($query);
switch ($arrayType) {
case 'assocList':
$types = $db->loadAssocList();
break;
case 'rowList':
$types = $db->loadRowList();
break;
case 'objectList':
default:
$types = $db->loadObjectList();
break;
}
return $types;
}
/**
* Function that handles saving tags used in a table class after a
store()
*
* @param TableInterface $table Table being processed
* @param array $newTags Array of new tags
* @param boolean $replace Flag indicating if all existing
tags should be replaced
*
* @return boolean
*
* @since 3.1
*/
public function postStoreProcess(TableInterface $table, $newTags = [],
$replace = true)
{
if (!empty($table->newTags) && empty($newTags)) {
$newTags = $table->newTags;
}
// If existing row, check to see if tags have changed.
$newTable = clone $table;
$newTable->reset();
$result = true;
// Process ucm_content and ucm_base if either tags have changed or
we have some tags.
if ($this->tagsChanged || (!empty($newTags) &&
$newTags[0] != '')) {
if (!$newTags && $replace == true) {
// Delete all tags data
$key = $table->getKeyName();
$result = $this->deleteTagData($table, $table->$key);
} else {
// Process the tags
$data = $this->getRowData($table);
$ucmContentTable =
Table::getInstance('CoreContent');
$ucm = new UCMContent($table, $this->typeAlias);
$ucmData = $data ? $ucm->mapData($data) :
$ucm->ucmData;
$primaryId =
$ucm->getPrimaryKey($ucmData['common']['core_type_id'],
$ucmData['common']['core_content_item_id']);
$result = $ucmContentTable->load($primaryId);
$result = $result &&
$ucmContentTable->bind($ucmData['common']);
$result = $result &&
$ucmContentTable->check();
$result = $result &&
$ucmContentTable->store();
$ucmId = $ucmContentTable->core_content_id;
// Store the tag data if the article data was saved and run
related methods.
$result = $result && $this->tagItem($ucmId,
$table, $newTags, $replace);
}
}
return $result;
}
/**
* Function that preProcesses data from a table prior to a store() to
ensure proper tag handling
*
* @param TableInterface $table Table being processed
* @param array $newTags Array of new tags
*
* @return null
*
* @since 3.1
*/
public function preStoreProcess(TableInterface $table, $newTags = [])
{
if ($newTags != []) {
$this->newTags = $newTags;
}
// If existing row, check to see if tags have changed.
$oldTable = clone $table;
$oldTable->reset();
$key = $oldTable->getKeyName();
$typeAlias = $this->typeAlias;
if ($oldTable->$key && $oldTable->load()) {
$this->oldTags = $this->getTagIds($oldTable->$key,
$typeAlias);
}
// New items with no tags bypass this step.
if ((!empty($newTags) && \is_string($newTags) ||
(isset($newTags[0]) && $newTags[0] != '')) ||
isset($this->oldTags)) {
if (\is_array($newTags)) {
$newTags = implode(',', $newTags);
}
// We need to process tags if the tags have changed or if we
have a new row
$this->tagsChanged = (empty($this->oldTags) &&
!empty($newTags)) || (!empty($this->oldTags) &&
$this->oldTags != $newTags) || !$table->$key;
}
}
/**
* Function to search tags
*
* @param array $filters Filter to apply to the search
*
* @return array
*
* @since 3.1
*/
public static function searchTags($filters = [])
{
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select(
[
$db->quoteName('a.id', 'value'),
$db->quoteName('a.path',
'text'),
$db->quoteName('a.path'),
]
)
->from($db->quoteName('#__tags',
'a'))
->join(
'LEFT',
$db->quoteName('#__tags', 'b'),
$db->quoteName('a.lft') . ' > ' .
$db->quoteName('b.lft') . ' AND ' .
$db->quoteName('a.rgt') . ' < ' .
$db->quoteName('b.rgt')
);
// Do not return root
$query->where($db->quoteName('a.alias') . '
<> ' . $db->quote('root'));
// Filter language
if (!empty($filters['flanguage'])) {
$query->whereIn($db->quoteName('a.language'),
[$filters['flanguage'], '*'], ParameterType::STRING);
}
// Search in title or path
if (!empty($filters['like'])) {
$search = '%' . $filters['like'] .
'%';
$query->extendWhere(
'AND',
[
$db->quoteName('a.title') . ' LIKE
:search1',
$db->quoteName('a.path') . ' LIKE
:search2',
],
'OR'
)
->bind([':search1', ':search2'],
$search);
}
// Filter title
if (!empty($filters['title'])) {
$query->where($db->quoteName('a.title') .
' = :title')
->bind(':title', $filters['title']);
}
// Filter on the published state
if (isset($filters['published']) &&
is_numeric($filters['published'])) {
$published = (int) $filters['published'];
$query->where($db->quoteName('a.published') .
' = :published')
->bind(':published', $published,
ParameterType::INTEGER);
}
// Filter on the access level
if (isset($filters['access']) &&
\is_array($filters['access']) &&
\count($filters['access'])) {
$groups = ArrayHelper::toInteger($filters['access']);
$query->whereIn($db->quoteName('a.access'),
$groups);
}
// Filter by parent_id
if (isset($filters['parent_id']) &&
is_numeric($filters['parent_id'])) {
$tagTable =
Factory::getApplication()->bootComponent('com_tags')->getMVCFactory()->createTable('Tag',
'Administrator');
if ($children =
$tagTable->getTree($filters['parent_id'])) {
$childrenIds = array_column($children, 'id');
$query->whereIn($db->quoteName('a.id'),
$childrenIds);
}
}
$query->group(
[
$db->quoteName('a.id'),
$db->quoteName('a.title'),
$db->quoteName('a.level'),
$db->quoteName('a.lft'),
$db->quoteName('a.rgt'),
$db->quoteName('a.parent_id'),
$db->quoteName('a.published'),
$db->quoteName('a.path'),
]
)
->order($db->quoteName('a.lft') . '
ASC');
// Get the options.
$db->setQuery($query);
try {
$results = $db->loadObjectList();
} catch (\RuntimeException $e) {
return [];
}
// We will replace path aliases with tag names
return self::convertPathsToNames($results);
}
/**
* Method to delete all instances of a tag from the mapping table.
Generally used when a tag is deleted.
*
* @param integer $tagId The tag_id (primary key) for the deleted
tag.
*
* @return void
*
* @since 3.1
*/
public function tagDeleteInstances($tagId)
{
// Cast as integer until method is typehinted.
$tag_id = (int) $tagId;
// Delete the old tag maps.
$db = Factory::getDbo();
$query = $db->getQuery(true)
->delete($db->quoteName('#__contentitem_tag_map'))
->where($db->quoteName('tag_id') . ' =
:id')
->bind(':id', $tagId, ParameterType::INTEGER);
$db->setQuery($query);
$db->execute();
}
/**
* Method to add or update tags associated with an item.
*
* @param integer $ucmId Id of the #__ucm_content item
being tagged
* @param TableInterface $table Table object being tagged
* @param array $tags Array of tags to be applied.
* @param boolean $replace Flag indicating if all existing
tags should be replaced
*
* @return boolean true on success, otherwise false.
*
* @since 3.1
*/
public function tagItem($ucmId, TableInterface $table, $tags = [],
$replace = true)
{
$key = $table->getKeyName();
$oldTags = $this->getTagIds((int) $table->$key,
$this->typeAlias);
$oldTags = explode(',', $oldTags);
$result = $this->unTagItem($ucmId, $table);
if ($replace) {
$newTags = $tags;
} else {
if ($tags == []) {
$newTags = $table->newTags;
} else {
$newTags = $tags;
}
if ($oldTags[0] != '') {
$newTags = array_unique(array_merge($newTags, $oldTags));
}
}
if (\is_array($newTags) && \count($newTags) > 0
&& $newTags[0] != '') {
$result = $result && $this->addTagMapping($ucmId,
$table, $newTags);
}
return $result;
}
/**
* Method to untag an item
*
* @param integer $contentId ID of the content item being
untagged
* @param TableInterface $table Table object being untagged
* @param array $tags Array of tags to be untagged.
Use an empty array to untag all existing tags.
*
* @return boolean true on success, otherwise false.
*
* @since 3.1
*/
public function unTagItem($contentId, TableInterface $table, $tags =
[])
{
$key = $table->getKeyName();
$id = (int) $table->$key;
$db = Factory::getDbo();
$query = $db->getQuery(true)
->delete($db->quoteName('#__contentitem_tag_map'))
->where(
[
$db->quoteName('type_alias') . ' =
:type',
$db->quoteName('content_item_id') . '
= :id',
]
)
->bind(':type', $this->typeAlias)
->bind(':id', $id, ParameterType::INTEGER);
if (\is_array($tags) && \count($tags) > 0) {
$tags = ArrayHelper::toInteger($tags);
$query->whereIn($db->quoteName('tag_id'),
$tags);
}
$db->setQuery($query);
return (bool) $db->execute();
}
/**
* Function that converts tag ids to their tag id and tag names
*
* @param array $tagIds Array of integer tag ids.
*
* @return array An array of tag id and name.
*
* @since 4.4.0
*/
public function getTags($tagIds)
{
$tagNames = [];
if (\is_array($tagIds) && \count($tagIds) > 0) {
$tagIds = ArrayHelper::toInteger($tagIds);
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select([$db->quoteName('id'),
$db->quoteName('title')])
->from($db->quoteName('#__tags'))
->whereIn($db->quoteName('id'), $tagIds)
->order($db->quoteName('title'));
$db->setQuery($query);
$tagNames = $db->loadAssocList('id',
'title');
}
return $tagNames;
}
}