Spade

Mini Shell

Directory:~$ /proc/self/root/home/lmsyaran/www/joomla5/libraries/src/Helper/
Upload File

[Home] [System Details] [Kill Me]
Current File:~$ //proc/self/root/home/lmsyaran/www/joomla5/libraries/src/Helper/TagsHelper.php

<?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;
    }
}