Spade
Mini Shell
| Directory:~$ /proc/self/root/home/lmsyaran/public_html/joomla5/components/com_finder/src/Model/ |
| [Home] [System Details] [Kill Me] |
<?php
/**
* @package Joomla.Site
* @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\Site\Model;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Multilanguage;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Uri\Uri;
use Joomla\Component\Finder\Administrator\Indexer\Query;
use Joomla\String\StringHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Search model class for the Finder package.
*
* @since 2.5
*/
class SearchModel extends ListModel
{
/**
* Context string for the model type
*
* @var string
* @since 2.5
*/
protected $context = 'com_finder.search';
/**
* The query object is an instance of Query which contains and
* models the entire search query including the text input; static and
* dynamic taxonomy filters; date filters; etc.
*
* @var Query
* @since 2.5
*/
protected $searchquery;
/**
* Maps each sorting field with a text label.
*
* @var string[]
*
* @since 4.3.0
*/
protected $sortOrderFieldsLabels = [
'relevance.asc' =>
'COM_FINDER_SORT_BY_RELEVANCE_ASC',
'relevance.desc' =>
'COM_FINDER_SORT_BY_RELEVANCE_DESC',
'title.asc' => 'JGLOBAL_TITLE_ASC',
'title.desc' => 'JGLOBAL_TITLE_DESC',
'date.asc' => 'JDATE_ASC',
'date.desc' => 'JDATE_DESC',
'price.asc' =>
'COM_FINDER_SORT_BY_PRICE_ASC',
'price.desc' =>
'COM_FINDER_SORT_BY_PRICE_DESC',
'sale_price.asc' =>
'COM_FINDER_SORT_BY_SALES_PRICE_ASC',
'sale_price.desc' =>
'COM_FINDER_SORT_BY_SALES_PRICE_DESC',
];
/**
* An array of all excluded terms ids.
*
* @var array
* @since 2.5
*/
protected $excludedTerms = [];
/**
* An array of all included terms ids.
*
* @var array
* @since 2.5
*/
protected $includedTerms = [];
/**
* An array of all required terms ids.
*
* @var array
* @since 2.5
*/
protected $requiredTerms = [];
/**
* Method to get the results of the query.
*
* @return array An array of Result objects.
*
* @since 2.5
* @throws \Exception on database error.
*/
public function getItems()
{
$items = parent::getItems();
// Check the data.
if (empty($items)) {
return null;
}
$results = [];
// Convert the rows to result objects.
foreach ($items as $rk => $row) {
// Build the result object.
if (\is_resource($row->object)) {
$result =
unserialize(stream_get_contents($row->object));
} else {
$result = unserialize($row->object);
}
$result->cleanURL = $result->route;
// Add the result back to the stack.
$results[] = $result;
}
// Return the results.
return $results;
}
/**
* Method to get the query object.
*
* @return Query A query object.
*
* @since 2.5
*/
public function getQuery()
{
// Return the query object.
return $this->searchquery;
}
/**
* Method to build a database query to load the list data.
*
* @return \Joomla\Database\DatabaseQuery A database query.
*
* @since 2.5
*/
protected function getListQuery()
{
// Create a new query object.
$db = $this->getDatabase();
$query = $db->getQuery(true);
// Select the required fields from the table.
$query->select(
$this->getState(
'list.select',
'l.link_id, l.object'
)
);
$query->from('#__finder_links AS l');
$user = $this->getCurrentUser();
$groups = $this->getState('user.groups',
$user->getAuthorisedViewLevels());
$query->whereIn($db->quoteName('l.access'),
$groups)
->where('l.state = 1')
->where('l.published = 1');
// Get the current date, minus seconds.
$nowDate =
$db->quote(substr_replace(Factory::getDate()->toSql(),
'00', -2));
// Add the publish up and publish down filters.
$query->where('(l.publish_start_date IS NULL OR
l.publish_start_date <= ' . $nowDate . ')')
->where('(l.publish_end_date IS NULL OR
l.publish_end_date >= ' . $nowDate . ')');
$query->group('l.link_id');
$query->group('l.object');
/*
* Add the taxonomy filters to the query. We have to join the
taxonomy
* map table for each group so that we can use AND clauses across
* groups. Within each group there can be an array of values that
will
* use OR clauses.
*/
if (!empty($this->searchquery->filters)) {
// Convert the associative array to a numerically indexed
array.
$groups = array_values($this->searchquery->filters);
$taxonomies = \call_user_func_array('array_merge',
array_values($this->searchquery->filters));
$query->join('INNER',
$db->quoteName('#__finder_taxonomy_map') . ' AS t ON
t.link_id = l.link_id')
->where('t.node_id IN (' .
implode(',', array_unique($taxonomies)) . ')');
// Iterate through each taxonomy group.
for ($i = 0, $c = \count($groups); $i < $c; $i++) {
$query->having('SUM(CASE WHEN t.node_id IN ('
. implode(',', $groups[$i]) . ') THEN 1 ELSE 0 END) >
0');
}
}
// Add the start date filter to the query.
if (!empty($this->searchquery->date1)) {
// Escape the date.
$date1 = $db->quote($this->searchquery->date1);
// Add the appropriate WHERE condition.
if ($this->searchquery->when1 === 'before') {
$query->where($db->quoteName('l.start_date') . ' <=
' . $date1);
} elseif ($this->searchquery->when1 ===
'after') {
$query->where($db->quoteName('l.start_date') . ' >=
' . $date1);
} else {
$query->where($db->quoteName('l.start_date') . ' =
' . $date1);
}
}
// Add the end date filter to the query.
if (!empty($this->searchquery->date2)) {
// Escape the date.
$date2 = $db->quote($this->searchquery->date2);
// Add the appropriate WHERE condition.
if ($this->searchquery->when2 === 'before') {
$query->where($db->quoteName('l.start_date') . ' <=
' . $date2);
} elseif ($this->searchquery->when2 ===
'after') {
$query->where($db->quoteName('l.start_date') . ' >=
' . $date2);
} else {
$query->where($db->quoteName('l.start_date') . ' =
' . $date2);
}
}
// Filter by language
if ($this->getState('filter.language')) {
$query->where('l.language IN (' .
$db->quote(Factory::getLanguage()->getTag()) . ', ' .
$db->quote('*') . ')');
}
// Get the result ordering and direction.
$ordering = $this->getState('list.ordering',
'm.weight');
$direction = $this->getState('list.direction',
'DESC');
/*
* If we are ordering by relevance we have to add up the relevance
* scores that are contained in the ordering field.
*/
if ($ordering === 'm.weight') {
// Get the base query and add the ordering information.
$query->select('SUM(' . $db->escape($ordering)
. ') AS ordering');
} else {
/**
* If we are not ordering by relevance, we just have to add
* the unique items to the set.
*/
// Get the base query and add the ordering information.
$query->select($db->escape($ordering) . ' AS
ordering');
}
$query->order('ordering ' .
$db->escape($direction));
/*
* If there are no optional or required search terms in the query,
we
* can get the results in one relatively simple database query.
*/
if (empty($this->includedTerms) &&
$this->searchquery->empty && $this->searchquery->input
== '') {
// Return the results.
return $query;
}
/*
* If there are no optional or required search terms in the query
and
* empty searches are not allowed, we return an empty query.
* If the search term is not empty and empty searches are allowed,
* but no terms were found, we return an empty query as well.
*/
if (
empty($this->includedTerms)
&& (!$this->searchquery->empty ||
($this->searchquery->empty && $this->searchquery->input
!= ''))
) {
// Since we need to return a query, we simplify this one.
$query->clear('join')
->clear('where')
->clear('bounded')
->clear('having')
->clear('group')
->where('false');
return $query;
}
$included = \call_user_func_array('array_merge',
array_values($this->includedTerms));
$query->join('INNER',
$db->quoteName('#__finder_links_terms') . ' AS m ON
m.link_id = l.link_id')
->where('m.term_id IN (' . implode(',',
$included) . ')');
// Check if there are any excluded terms to deal with.
if (\count($this->excludedTerms)) {
$query2 = $db->getQuery(true);
$query2->select('e.link_id')
->from($db->quoteName('#__finder_links_terms',
'e'))
->where('e.term_id IN (' .
implode(',', $this->excludedTerms) . ')');
$query->where('l.link_id NOT IN (' . $query2 .
')');
}
/*
* The query contains required search terms.
*/
if (\count($this->requiredTerms)) {
foreach ($this->requiredTerms as $terms) {
if (\count($terms)) {
$query->having('SUM(CASE WHEN m.term_id IN
(' . implode(',', $terms) . ') THEN 1 ELSE 0 END) >
0');
} else {
$query->where('false');
break;
}
}
}
return $query;
}
/**
* Method to get the available sorting fields.
*
* @return array The sorting field objects.
*
* @throws \Exception
*
* @since 4.3.0
*/
public function getSortOrderFields()
{
$sortOrderFields = [];
$directions = ['asc', 'desc'];
$app = Factory::getApplication();
$params = $app->getParams();
$sortOrderFieldValues =
$params->get('shown_sort_order', [], 'array');
if ($params->get('show_sort_order', 0,
'uint') && !empty($sortOrderFieldValues)) {
$defaultSortFieldValue =
$params->get('sort_order', '', 'cmd');
$queryUri =
Uri::getInstance($this->getQuery()->toUri());
// If the default field is not included in the shown sort
fields, add it.
if (!\in_array($defaultSortFieldValue, $sortOrderFieldValues))
{
array_unshift($sortOrderFieldValues,
$defaultSortFieldValue);
}
foreach ($sortOrderFieldValues as $sortOrderFieldValue) {
foreach ($directions as $direction) {
// The relevance has only descending direction. Except
if ascending is set in the parameters.
if ($sortOrderFieldValue === 'relevance'
&& $direction === 'asc' &&
$app->getParams()->get('sort_direction', 'desc')
=== 'desc') {
continue;
}
$sortOrderFields[] =
$this->getSortField($sortOrderFieldValue, $direction, $queryUri);
}
}
}
// Import Finder plugins
PluginHelper::importPlugin('finder');
// Trigger an event, in case a plugin wishes to change the order
fields.
$app->triggerEvent('onFinderSortOrderFields',
[&$sortOrderFields]);
return $sortOrderFields;
}
/**
* Method to generate and return a sorting field
*
* @param string $value The value based on which the results
will be sorted.
* @param string $direction The sorting direction ('asc'
or 'desc').
* @param Uri $queryUri The uri of the search query.
*
* @return \stdClass The sorting field object.
*
* @throws \Exception
*
* @since 4.3.0
*/
protected function getSortField(string $value, string $direction, Uri
$queryUri)
{
$sortField = new \stdClass();
$app = Factory::getApplication();
// We have to clone the query uri. Otherwise the next elements will
use the same.
$queryUri = clone $queryUri;
$queryUri->setVar('o', $value);
$currentOrderingDirection =
$app->getInput()->getWord('od',
$app->getParams()->get('sort_direction',
'desc'));
// Validate the sorting direction and add it only if it is
different than the set in the params.
if (\in_array($direction, ['asc', 'desc'])
&& $direction !=
$app->getParams()->get('sort_direction', 'desc'))
{
$queryUri->setVar('od',
StringHelper::strtolower($direction));
}
$label = '';
if (isset($this->sortOrderFieldsLabels[$value . '.' .
$direction])) {
$label = Text::_($this->sortOrderFieldsLabels[$value .
'.' . $direction]);
}
$sortField->label = $label;
$sortField->url = $queryUri->toString();
$currentSortOrderField =
$app->getInput()->getWord('o',
$app->getParams()->get('sort_order',
'relevance'));
$sortField->active = false;
if ($value === StringHelper::strtolower($currentSortOrderField)
&& $direction === $currentOrderingDirection) {
$sortField->active = true;
}
return $sortField;
}
/**
* Method to get a store id based on model the configuration state.
*
* This is necessary because the model is used by the component and
* different modules that might need different sets of data or
different
* ordering requirements.
*
* @param string $id An identifier string to generate the store
id. [optional]
* @param boolean $page True to store the data paged, false to
store all data. [optional]
*
* @return string A store id.
*
* @since 2.5
*/
protected function getStoreId($id = '', $page = true)
{
// Get the query object.
$query = $this->getQuery();
// Add the search query state.
$id .= ':' . $query->input;
$id .= ':' . $query->language;
$id .= ':' . $query->filter;
$id .= ':' . serialize($query->filters);
$id .= ':' . $query->date1;
$id .= ':' . $query->date2;
$id .= ':' . $query->when1;
$id .= ':' . $query->when2;
if ($page) {
// Add the list state for page specific data.
$id .= ':' .
$this->getState('list.start');
$id .= ':' .
$this->getState('list.limit');
$id .= ':' .
$this->getState('list.ordering');
$id .= ':' .
$this->getState('list.direction');
}
return parent::getStoreId($id);
}
/**
* Method to auto-populate the model state. Calling getState in this
method will result in recursion.
*
* @param string $ordering An optional ordering field. [optional]
* @param string $direction An optional direction. [optional]
*
* @return void
*
* @since 2.5
*/
protected function populateState($ordering = null, $direction = null)
{
// Get the configuration options.
$app = Factory::getApplication();
$input = $app->getInput();
$params = $app->getParams();
$user = $this->getCurrentUser();
$language = $app->getLanguage();
$this->setState('filter.language',
Multilanguage::isEnabled());
$request = $input->request;
$options = [];
// Get the empty query setting.
$options['empty'] =
$params->get('allow_empty_query', 0);
// Get the static taxonomy filters.
$options['filter'] = $request->getInt('f',
$params->get('f', ''));
// Get the dynamic taxonomy filters.
$options['filters'] = $request->get('t',
$params->get('t', []), 'array');
// Get the query string.
$options['input'] = $request->getString('q',
$params->get('q', ''));
// Get the query language.
$options['language'] = $request->getCmd('l',
$params->get('l', $language->getTag()));
// Set the word match mode
$options['word_match'] =
$params->get('word_match', 'exact');
// Get the start date and start date modifier filters.
$options['date1'] =
$request->getString('d1', $params->get('d1',
''));
$options['when1'] =
$request->getString('w1', $params->get('w1',
''));
// Get the end date and end date modifier filters.
$options['date2'] =
$request->getString('d2', $params->get('d2',
''));
$options['when2'] =
$request->getString('w2', $params->get('w2',
''));
// Load the query object.
$this->searchquery = new Query($options,
$this->getDatabase());
// Load the query token data.
$this->excludedTerms =
$this->searchquery->getExcludedTermIds();
$this->includedTerms =
$this->searchquery->getIncludedTermIds();
$this->requiredTerms =
$this->searchquery->getRequiredTermIds();
// Load the list state.
$this->setState('list.start',
$input->get('limitstart', 0, 'uint'));
$this->setState('list.limit',
$input->get('limit', $params->get('list_limit',
$app->get('list_limit', 20)), 'uint'));
/*
* Load the sort ordering.
* Currently this is 'hard' coded via menu item parameter
but may not satisfy a users need.
* More flexibility was way more user friendly. So we allow the
user to pass a custom value
* from the pool of fields that are indexed like the
'title' field.
* Also, we allow this parameter to be passed in either case
(lower/upper).
*/
$order = $input->getWord('o',
$params->get('sort_order', 'relevance'));
$order = StringHelper::strtolower($order);
$this->setState('list.raworder', $order);
switch ($order) {
case 'date':
$this->setState('list.ordering',
'l.start_date');
break;
case 'price':
$this->setState('list.ordering',
'l.list_price');
break;
case 'sale_price':
$this->setState('list.ordering',
'l.sale_price');
break;
case ($order === 'relevance' &&
!empty($this->includedTerms)):
$this->setState('list.ordering',
'm.weight');
break;
case 'title':
$this->setState('list.ordering',
'l.title');
break;
default:
$this->setState('list.ordering',
'l.link_id');
$this->setState('list.raworder');
break;
}
/*
* Load the sort direction.
* Currently this is 'hard' coded via menu item parameter
but may not satisfy a users need.
* More flexibility was way more user friendly. So we allow to be
inverted.
* Also, we allow this parameter to be passed in either case
(lower/upper).
*/
$dirn = $input->getWord('od',
$params->get('sort_direction', 'desc'));
$dirn = StringHelper::strtolower($dirn);
switch ($dirn) {
case 'asc':
$this->setState('list.direction',
'ASC');
break;
default:
$this->setState('list.direction',
'DESC');
break;
}
// Set the match limit.
$this->setState('match.limit', 1000);
// Load the parameters.
$this->setState('params', $params);
// Load the user state.
$this->setState('user.id', (int)
$user->get('id'));
$this->setState('user.groups',
$user->getAuthorisedViewLevels());
}
}