primo commit

This commit is contained in:
2024-12-17 17:34:10 +01:00
commit e650f8df99
16435 changed files with 2451012 additions and 0 deletions

View File

@ -0,0 +1,530 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Finder\Administrator\Service\HTML;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Multilanguage;
use Joomla\CMS\Language\Text;
use Joomla\Component\Finder\Administrator\Helper\LanguageHelper;
use Joomla\Component\Finder\Administrator\Indexer\Query;
use Joomla\Database\DatabaseAwareTrait;
use Joomla\Database\ParameterType;
use Joomla\Filter\OutputFilter;
use Joomla\Registry\Registry;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Filter HTML Behaviors for Finder.
*
* @since 2.5
*/
class Filter
{
use DatabaseAwareTrait;
/**
* Method to generate filters using the slider widget and decorated
* with the FinderFilter JavaScript behaviors.
*
* @param array $options An array of configuration options. [optional]
*
* @return mixed A rendered HTML widget on success, null otherwise.
*
* @since 2.5
*/
public function slider($options = [])
{
$db = $this->getDatabase();
$query = $db->getQuery(true);
$user = Factory::getUser();
$groups = implode(',', $user->getAuthorisedViewLevels());
$html = '';
$filter = null;
// Get the configuration options.
$filterId = $options['filter_id'] ?? null;
$activeNodes = \array_key_exists('selected_nodes', $options) ? $options['selected_nodes'] : [];
$classSuffix = \array_key_exists('class_suffix', $options) ? $options['class_suffix'] : '';
// Load the predefined filter if specified.
if (!empty($filterId)) {
$query->select('f.data, f.params')
->from($db->quoteName('#__finder_filters') . ' AS f')
->where('f.filter_id = ' . (int) $filterId);
// Load the filter data.
$db->setQuery($query);
try {
$filter = $db->loadObject();
} catch (\RuntimeException $e) {
return null;
}
// Initialize the filter parameters.
if ($filter) {
$filter->params = new Registry($filter->params);
}
}
// Build the query to get the branch data and the number of child nodes.
$query->clear()
->select('t.*, count(c.id) AS children')
->from($db->quoteName('#__finder_taxonomy') . ' AS t')
->join('INNER', $db->quoteName('#__finder_taxonomy') . ' AS c ON c.parent_id = t.id')
->where('t.parent_id = 1')
->where('t.state = 1')
->where('t.access IN (' . $groups . ')')
->group('t.id, t.parent_id, t.state, t.access, t.title, c.parent_id')
->order('t.lft, t.title');
// Limit the branch children to a predefined filter.
if ($filter) {
$query->where('c.id IN(' . $filter->data . ')');
}
// Load the branches.
$db->setQuery($query);
try {
$branches = $db->loadObjectList('id');
} catch (\RuntimeException $e) {
return null;
}
// Check that we have at least one branch.
if (\count($branches) === 0) {
return null;
}
$branch_keys = array_keys($branches);
$html .= HTMLHelper::_('bootstrap.startAccordion', 'accordion', ['active' => 'accordion-' . $branch_keys[0]]);
// Load plugin language files.
LanguageHelper::loadPluginLanguage();
// Iterate through the branches and build the branch groups.
foreach ($branches as $bk => $bv) {
// If the multi-lang plugin is enabled then drop the language branch.
if ($bv->title === 'Language' && Multilanguage::isEnabled()) {
continue;
}
// Build the query to get the child nodes for this branch.
$query->clear()
->select('t.*')
->from($db->quoteName('#__finder_taxonomy') . ' AS t')
->where('t.lft > ' . (int) $bv->lft)
->where('t.rgt < ' . (int) $bv->rgt)
->where('t.state = 1')
->where('t.access IN (' . $groups . ')')
->order('t.lft, t.title');
// Self-join to get the parent title.
$query->select('e.title AS parent_title')
->join('LEFT', $db->quoteName('#__finder_taxonomy', 'e') . ' ON ' . $db->quoteName('e.id') . ' = ' . $db->quoteName('t.parent_id'));
// Load the branches.
$db->setQuery($query);
try {
$nodes = $db->loadObjectList('id');
} catch (\RuntimeException $e) {
return null;
}
// Translate node titles if possible.
$lang = Factory::getLanguage();
foreach ($nodes as $nv) {
if (trim($nv->parent_title, '*') === 'Language') {
$title = LanguageHelper::branchLanguageTitle($nv->title);
} else {
$key = LanguageHelper::branchPlural($nv->title);
$title = $lang->hasKey($key) ? Text::_($key) : $nv->title;
}
$nv->title = $title;
}
// Adding slides
$html .= HTMLHelper::_(
'bootstrap.addSlide',
'accordion',
Text::sprintf(
'COM_FINDER_FILTER_BRANCH_LABEL',
Text::_(LanguageHelper::branchSingular($bv->title)) . ' - ' . \count($nodes)
),
'accordion-' . $bk
);
// Populate the toggle button.
$html .= '<button class="btn btn-secondary js-filter" type="button" data-id="tax-' . $bk . '"><span class="icon-square" aria-hidden="true"></span> '
. Text::_('JGLOBAL_SELECTION_INVERT') . '</button><hr>';
// Populate the group with nodes.
foreach ($nodes as $nk => $nv) {
// Determine if the node should be checked.
$checked = \in_array($nk, $activeNodes) ? ' checked="checked"' : '';
// Build a node.
$html .= '<div class="form-check">';
$html .= '<label class="form-check-label">';
$html .= '<input type="checkbox" class="form-check-input selector filter-node' . $classSuffix
. ' tax-' . $bk . '" value="' . $nk . '" name="t[]"' . $checked . '> ' . str_repeat('&mdash;', $nv->level - 2) . $nv->title;
$html .= '</label>';
$html .= '</div>';
}
$html .= HTMLHelper::_('bootstrap.endSlide');
}
$html .= HTMLHelper::_('bootstrap.endAccordion');
return $html;
}
/**
* Method to generate filters using select box dropdown controls.
*
* @param Query $idxQuery A Query object.
* @param Registry $options An array of options.
*
* @return string|null A rendered HTML widget on success, null otherwise.
*
* @since 2.5
*/
public function select($idxQuery, $options)
{
$user = Factory::getUser();
$groups = implode(',', $user->getAuthorisedViewLevels());
$filter = null;
// Get the configuration options.
$classSuffix = $options->get('class_suffix', null);
$showDates = $options->get('show_date_filters', false);
// Try to load the results from cache.
$cache = Factory::getCache('com_finder', '');
$cacheId = 'filter_select_' . serialize([$idxQuery->filter, $options, $groups, Factory::getLanguage()->getTag()]);
// Check the cached results.
if ($cache->contains($cacheId)) {
$branches = $cache->get($cacheId);
} else {
$db = $this->getDatabase();
$query = $db->getQuery(true);
// Load the predefined filter if specified.
if (!empty($idxQuery->filter)) {
$query->select('f.data, ' . $db->quoteName('f.params'))
->from($db->quoteName('#__finder_filters') . ' AS f')
->where('f.filter_id = ' . (int) $idxQuery->filter);
// Load the filter data.
$db->setQuery($query);
try {
$filter = $db->loadObject();
} catch (\RuntimeException $e) {
return null;
}
// Initialize the filter parameters.
if ($filter) {
$filter->params = new Registry($filter->params);
}
}
// Build the query to get the branch data and the number of child nodes.
$query->clear()
->select('t.*, count(c.id) AS children')
->from($db->quoteName('#__finder_taxonomy') . ' AS t')
->join('INNER', $db->quoteName('#__finder_taxonomy') . ' AS c ON c.parent_id = t.id')
->where('t.parent_id = 1')
->where('t.state = 1')
->where('t.access IN (' . $groups . ')')
->where('c.state = 1')
->where('c.access IN (' . $groups . ')')
->group($db->quoteName('t.id'))
->group($db->quoteName('t.parent_id'))
->group('t.title, t.state, t.access, t.lft')
->order('t.lft, t.title');
// Limit the branch children to a predefined filter.
if (!empty($filter->data)) {
$query->where('c.id IN(' . $filter->data . ')');
}
// Load the branches.
$db->setQuery($query);
try {
$branches = $db->loadObjectList('id');
} catch (\RuntimeException $e) {
return null;
}
// Check that we have at least one branch.
if (\count($branches) === 0) {
return null;
}
// Iterate through the branches and build the branch groups.
foreach ($branches as $bk => $bv) {
// If the multi-lang plugin is enabled then drop the language branch.
if ($bv->title === 'Language' && Multilanguage::isEnabled()) {
continue;
}
// Build the query to get the child nodes for this branch.
$query->clear()
->select('t.*')
->from($db->quoteName('#__finder_taxonomy') . ' AS t')
->where('t.lft > :lft')
->where('t.rgt < :rgt')
->where('t.state = 1')
->whereIn('t.access', $user->getAuthorisedViewLevels())
->order('t.level, t.parent_id, t.title')
->bind(':lft', $bv->lft, ParameterType::INTEGER)
->bind(':rgt', $bv->rgt, ParameterType::INTEGER);
// Apply multilanguage filter
if (Multilanguage::isEnabled()) {
$language = [Factory::getLanguage()->getTag(), '*'];
$query->whereIn($db->quoteName('t.language'), $language, ParameterType::STRING);
}
// Self-join to get the parent title.
$query->select('e.title AS parent_title')
->join('LEFT', $db->quoteName('#__finder_taxonomy', 'e') . ' ON ' . $db->quoteName('e.id') . ' = ' . $db->quoteName('t.parent_id'));
// Limit the nodes to a predefined filter.
if (!empty($filter->data)) {
$query->whereIn('t.id', explode(",", $filter->data));
}
// Load the branches.
$db->setQuery($query);
try {
$bv->nodes = $db->loadObjectList('id');
} catch (\RuntimeException $e) {
return null;
}
// Translate branch nodes if possible.
$language = Factory::getLanguage();
$root = [];
foreach ($bv->nodes as $node_id => $node) {
if (trim($node->parent_title, '*') === 'Language') {
$title = LanguageHelper::branchLanguageTitle($node->title);
} else {
$key = LanguageHelper::branchPlural($node->title);
$title = $language->hasKey($key) ? Text::_($key) : $node->title;
}
if ($node->level > 2) {
$node->title = str_repeat('-', $node->level - 2) . $title;
} else {
$node->title = $title;
$root[] = $branches[$bk]->nodes[$node_id];
}
if ($node->parent_id && isset($branches[$bk]->nodes[$node->parent_id])) {
if (!isset($branches[$bk]->nodes[$node->parent_id]->children)) {
$branches[$bk]->nodes[$node->parent_id]->children = [];
}
$branches[$bk]->nodes[$node->parent_id]->children[] = $node;
}
}
$branches[$bk]->nodes = $this->reduce($root);
// Add the Search All option to the branch.
array_unshift($bv->nodes, ['id' => null, 'title' => Text::_('COM_FINDER_FILTER_SELECT_ALL_LABEL')]);
}
// Store the data in cache.
$cache->store($branches, $cacheId);
}
$html = '';
// Add the dates if enabled.
if ($showDates) {
$html .= HTMLHelper::_('filter.dates', $idxQuery, $options);
}
$html .= '<div class="filter-branch' . $classSuffix . '">';
// Iterate through all branches and build code.
foreach ($branches as $bv) {
// If the multi-lang plugin is enabled then drop the language branch.
if ($bv->title === 'Language' && Multilanguage::isEnabled()) {
continue;
}
$active = null;
// Check if the branch is in the filter.
if (\array_key_exists($bv->title, $idxQuery->filters)) {
// Get the request filters.
$temp = Factory::getApplication()->getInput()->request->get('t', [], 'array');
// Search for active nodes in the branch and get the active node.
$active = array_intersect($temp, $idxQuery->filters[$bv->title]);
$active = \count($active) === 1 ? array_shift($active) : null;
}
// Build a node.
$html .= '<div class="control-group">';
$html .= '<div class="control-label">';
$html .= '<label for="tax-' . OutputFilter::stringURLSafe($bv->title) . '">';
$html .= Text::sprintf('COM_FINDER_FILTER_BRANCH_LABEL', Text::_(LanguageHelper::branchSingular($bv->title)));
$html .= '</label>';
$html .= '</div>';
$html .= '<div class="controls">';
$html .= HTMLHelper::_(
'select.genericlist',
$bv->nodes,
't[]',
'class="form-select advancedSelect"',
'id',
'title',
$active,
'tax-' . OutputFilter::stringURLSafe($bv->title)
);
$html .= '</div>';
$html .= '</div>';
}
$html .= '</div>';
return $html;
}
/**
* Method to generate fields for filtering dates
*
* @param Query $idxQuery A Query object.
* @param Registry $options An array of options.
*
* @return string A rendered HTML widget.
*
* @since 2.5
*/
public function dates($idxQuery, $options)
{
$html = '';
// Get the configuration options.
$classSuffix = $options->get('class_suffix', null);
$loadMedia = $options->get('load_media', true);
$showDates = $options->get('show_date_filters', false);
if (!empty($showDates)) {
// Build the date operators options.
$operators = [];
$operators[] = HTMLHelper::_('select.option', 'before', Text::_('COM_FINDER_FILTER_DATE_BEFORE'));
$operators[] = HTMLHelper::_('select.option', 'exact', Text::_('COM_FINDER_FILTER_DATE_EXACTLY'));
$operators[] = HTMLHelper::_('select.option', 'after', Text::_('COM_FINDER_FILTER_DATE_AFTER'));
// Load the CSS/JS resources.
if ($loadMedia) {
/** @var \Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->useStyle('com_finder.dates');
}
// Open the widget.
$html .= '<ul id="finder-filter-select-dates">';
// Start date filter.
$attribs['class'] = 'input-medium';
$html .= '<li class="filter-date float-start' . $classSuffix . '">';
$html .= '<label for="filter_date1" class="hasTooltip" title ="' . Text::_('COM_FINDER_FILTER_DATE1_DESC') . '">';
$html .= Text::_('COM_FINDER_FILTER_DATE1');
$html .= '</label>';
$html .= '<br>';
$html .= '<label for="finder-filter-w1" class="visually-hidden">';
$html .= Text::_('COM_FINDER_FILTER_DATE1_OPERATOR');
$html .= '</label>';
$html .= HTMLHelper::_(
'select.genericlist',
$operators,
'w1',
'class="inputbox filter-date-operator advancedSelect form-select w-auto mb-2"',
'value',
'text',
$idxQuery->when1,
'finder-filter-w1'
);
$html .= HTMLHelper::_('calendar', $idxQuery->date1, 'd1', 'filter_date1', '%Y-%m-%d', $attribs);
$html .= '</li>';
// End date filter.
$html .= '<li class="filter-date float-end' . $classSuffix . '">';
$html .= '<label for="filter_date2" class="hasTooltip" title ="' . Text::_('COM_FINDER_FILTER_DATE2_DESC') . '">';
$html .= Text::_('COM_FINDER_FILTER_DATE2');
$html .= '</label>';
$html .= '<br>';
$html .= '<label for="finder-filter-w2" class="visually-hidden">';
$html .= Text::_('COM_FINDER_FILTER_DATE2_OPERATOR');
$html .= '</label>';
$html .= HTMLHelper::_(
'select.genericlist',
$operators,
'w2',
'class="inputbox filter-date-operator advancedSelect form-select w-auto mb-2"',
'value',
'text',
$idxQuery->when2,
'finder-filter-w2'
);
$html .= HTMLHelper::_('calendar', $idxQuery->date2, 'd2', 'filter_date2', '%Y-%m-%d', $attribs);
$html .= '</li>';
// Close the widget.
$html .= '</ul>';
}
return $html;
}
/**
* Method to flatten a tree to a sorted array
*
* @param \stdClass[] $array
*
* @return \stdClass[] Flat array of all nodes of a tree with the children after each parent
*
* @since 5.1.0
*/
private function reduce(array $array)
{
$return = [];
foreach ($array as $item) {
$return[] = $item;
if (isset($item->children)) {
$return = array_merge($return, $this->reduce($item->children));
}
}
return $return;
}
}

View File

@ -0,0 +1,131 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Finder\Administrator\Service\HTML;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\Component\Finder\Administrator\Helper\LanguageHelper;
use Joomla\Database\DatabaseAwareTrait;
use Joomla\Utilities\ArrayHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* HTML behavior class for Finder.
*
* @since 2.5
*/
class Finder
{
use DatabaseAwareTrait;
/**
* Creates a list of types to filter on.
*
* @return array An array containing the types that can be selected.
*
* @since 2.5
*/
public function typeslist()
{
// Load the finder types.
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('DISTINCT t.title AS text, t.id AS value')
->from($db->quoteName('#__finder_types') . ' AS t')
->join('LEFT', $db->quoteName('#__finder_links') . ' AS l ON l.type_id = t.id')
->order('t.title ASC');
$db->setQuery($query);
try {
$rows = $db->loadObjectList();
} catch (\RuntimeException $e) {
return [];
}
// Compile the options.
$options = [];
$lang = Factory::getLanguage();
foreach ($rows as $row) {
$key = $lang->hasKey(LanguageHelper::branchPlural($row->text)) ? LanguageHelper::branchPlural($row->text) : $row->text;
$options[] = HTMLHelper::_('select.option', $row->value, Text::sprintf('COM_FINDER_ITEM_X_ONLY', Text::_($key)));
}
return $options;
}
/**
* Creates a list of maps.
*
* @return array An array containing the maps that can be selected.
*
* @since 2.5
*/
public function mapslist()
{
// Load the finder types.
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select($db->quoteName('title', 'text'))
->select($db->quoteName('id', 'value'))
->from($db->quoteName('#__finder_taxonomy'))
->where($db->quoteName('parent_id') . ' = 1');
$db->setQuery($query);
try {
$branches = $db->loadObjectList();
} catch (\RuntimeException $e) {
Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
}
// Translate.
$lang = Factory::getLanguage();
foreach ($branches as $branch) {
$key = LanguageHelper::branchPlural($branch->text);
$branch->translatedText = $lang->hasKey($key) ? Text::_($key) : $branch->text;
}
// Order by title.
$branches = ArrayHelper::sortObjects($branches, 'translatedText', 1, true, true);
// Compile the options.
$options = [];
$options[] = HTMLHelper::_('select.option', '', Text::_('COM_FINDER_MAPS_SELECT_BRANCH'));
// Convert the values to options.
foreach ($branches as $branch) {
$options[] = HTMLHelper::_('select.option', $branch->value, $branch->translatedText);
}
return $options;
}
/**
* Creates a list of published states.
*
* @return array An array containing the states that can be selected.
*
* @since 2.5
*/
public static function statelist()
{
return [
HTMLHelper::_('select.option', '1', Text::sprintf('COM_FINDER_ITEM_X_ONLY', Text::_('JPUBLISHED'))),
HTMLHelper::_('select.option', '0', Text::sprintf('COM_FINDER_ITEM_X_ONLY', Text::_('JUNPUBLISHED'))),
];
}
}

View File

@ -0,0 +1,156 @@
<?php
/**
* @package Joomla.Administrator
* @subpackage com_finder
*
* @copyright (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Component\Finder\Administrator\Service\HTML;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\Component\Finder\Administrator\Helper\LanguageHelper;
use Joomla\Component\Finder\Administrator\Indexer\Query as IndexerQuery;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Query HTML behavior class for Finder.
*
* @since 2.5
*/
class Query
{
/**
* Method to get the explained (human-readable) search query.
*
* @param IndexerQuery $query A IndexerQuery object to explain.
*
* @return mixed String if there is data to explain, null otherwise.
*
* @since 2.5
*/
public static function explained(IndexerQuery $query)
{
$parts = [];
// Process the required tokens.
foreach ($query->included as $token) {
if ($token->required && (!isset($token->derived) || $token->derived == false)) {
$parts[] = '<span class="query-required">' . Text::sprintf('COM_FINDER_QUERY_TOKEN_REQUIRED', $token->term) . '</span>';
}
}
// Process the optional tokens.
foreach ($query->included as $token) {
if (!$token->required && (!isset($token->derived) || $token->derived == false)) {
$parts[] = '<span class="query-optional">' . Text::sprintf('COM_FINDER_QUERY_TOKEN_OPTIONAL', $token->term) . '</span>';
}
}
// Process the excluded tokens.
foreach ($query->excluded as $token) {
if (!isset($token->derived) || $token->derived === false) {
$parts[] = '<span class="query-excluded">' . Text::sprintf('COM_FINDER_QUERY_TOKEN_EXCLUDED', $token->term) . '</span>';
}
}
// Process the start date.
if ($query->date1) {
$date = Factory::getDate($query->date1)->format(Text::_('DATE_FORMAT_LC'));
$datecondition = Text::_('COM_FINDER_QUERY_DATE_CONDITION_' . strtoupper($query->when1));
$parts[] = '<span class="query-start-date">' . Text::sprintf('COM_FINDER_QUERY_START_DATE', $datecondition, $date) . '</span>';
}
// Process the end date.
if ($query->date2) {
$date = Factory::getDate($query->date2)->format(Text::_('DATE_FORMAT_LC'));
$datecondition = Text::_('COM_FINDER_QUERY_DATE_CONDITION_' . strtoupper($query->when2));
$parts[] = '<span class="query-end-date">' . Text::sprintf('COM_FINDER_QUERY_END_DATE', $datecondition, $date) . '</span>';
}
// Process the taxonomy filters.
if (!empty($query->filters)) {
// Get the filters in the request.
$t = Factory::getApplication()->getInput()->request->get('t', [], 'array');
// Process the taxonomy branches.
foreach ($query->filters as $branch => $nodes) {
// Process the taxonomy nodes.
$lang = Factory::getLanguage();
foreach ($nodes as $title => $id) {
// Translate the title for Types
$key = LanguageHelper::branchPlural($title);
if ($lang->hasKey($key)) {
$title = Text::_($key);
}
// Don't include the node if it is not in the request.
if (!\in_array($id, $t)) {
continue;
}
// Add the node to the explanation.
$parts[] = '<span class="query-taxonomy">'
. Text::sprintf('COM_FINDER_QUERY_TAXONOMY_NODE', $title, Text::_(LanguageHelper::branchSingular($branch)))
. '</span>';
}
}
}
// Build the interpreted query.
return \count($parts) ? implode(Text::_('COM_FINDER_QUERY_TOKEN_GLUE'), $parts) : null;
}
/**
* Method to get the suggested search query.
*
* @param IndexerQuery $query A IndexerQuery object.
*
* @return mixed String if there is a suggestion, false otherwise.
*
* @since 2.5
*/
public static function suggested(IndexerQuery $query)
{
$suggested = false;
// Check if the query input is empty.
if (empty($query->input)) {
return $suggested;
}
// Check if there were any ignored or included keywords.
if (\count($query->ignored) || \count($query->included)) {
$suggested = $query->input;
// Replace the ignored keyword suggestions.
foreach (array_reverse($query->ignored) as $token) {
if (isset($token->suggestion)) {
$suggested = str_ireplace($token->term, $token->suggestion, $suggested);
}
}
// Replace the included keyword suggestions.
foreach (array_reverse($query->included) as $token) {
if (isset($token->suggestion)) {
$suggested = str_ireplace($token->term, $token->suggestion, $suggested);
}
}
// Check if we made any changes.
if ($suggested == $query->input) {
$suggested = false;
}
}
return $suggested;
}
}