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,62 @@
<?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\Controller;
use Joomla\CMS\MVC\Controller\BaseController;
use Joomla\Component\Finder\Administrator\Helper\LanguageHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Finder Component Controller.
*
* @since 2.5
*/
class DisplayController extends BaseController
{
/**
* Method to display a view.
*
* @param boolean $cachable If true, the view output will be cached. [optional]
* @param array $urlparams An array of safe URL parameters and their variable types. Optional.
* @see \Joomla\CMS\Filter\InputFilter::clean() for valid values.
*
* @return static This object is to support chaining.
*
* @since 2.5
*/
public function display($cachable = false, $urlparams = [])
{
$input = $this->app->getInput();
$cachable = true;
// Load plugin language files.
LanguageHelper::loadPluginLanguage();
// Set the default view name and format from the Request.
$viewName = $input->get('view', 'search', 'word');
$input->set('view', $viewName);
// Don't cache view for search queries
if ($input->get('q', null, 'string') || $input->get('f', null, 'int') || $input->get('t', null, 'array')) {
$cachable = false;
}
$safeurlparams = [
'f' => 'INT',
'lang' => 'CMD',
];
return parent::display($cachable, $safeurlparams);
}
}

View File

@ -0,0 +1,101 @@
<?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\Controller;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\MVC\Controller\BaseController;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Suggestions \JSON controller for Finder.
*
* @since 2.5
*/
class SuggestionsController extends BaseController
{
/**
* Method to find search query suggestions. Uses awesomplete
*
* @return void
*
* @since 3.4
*/
public function suggest()
{
$app = $this->app;
$app->mimeType = 'application/json';
// Ensure caching is disabled as it depends on the query param in the model
$app->allowCache(false);
$suggestions = $this->getSuggestions();
// Send the response.
$app->setHeader('Content-Type', $app->mimeType . '; charset=' . $app->charSet);
$app->sendHeaders();
echo '{ "suggestions": ' . json_encode($suggestions) . ' }';
}
/**
* Method to find search query suggestions for OpenSearch
*
* @return void
*
* @since 4.0.0
*/
public function opensearchsuggest()
{
$app = $this->app;
$app->mimeType = 'application/json';
$result = [];
$result[] = $app->getInput()->request->get('q', '', 'string');
$result[] = $this->getSuggestions();
// Ensure caching is disabled as it depends on the query param in the model
$app->allowCache(false);
// Send the response.
$app->setHeader('Content-Type', $app->mimeType . '; charset=' . $app->charSet);
$app->sendHeaders();
echo json_encode($result);
}
/**
* Method to retrieve the data from the database
*
* @return array The suggested words
*
* @since 3.4
*/
protected function getSuggestions()
{
$return = [];
$params = ComponentHelper::getParams('com_finder');
if ($params->get('show_autosuggest', 1)) {
// Get the suggestions.
$model = $this->getModel('Suggestions');
$return = $model->getItems();
}
// Check the data.
if (empty($return)) {
$return = [];
}
return $return;
}
}

View File

@ -0,0 +1,101 @@
<?php
/**
* @package Joomla.Site
* @subpackage com_finder
*
* @copyright (C) 2018 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\Helper;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\Component\Finder\Administrator\Indexer\Query;
use Joomla\Database\ParameterType;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Helper class for Joomla! Finder components
*
* @since 4.0.0
*/
class FinderHelper
{
/**
* Method to log searches to the database
*
* @param Query $searchquery The search query
* @param integer $resultCount The number of results for this search
*
* @return void
*
* @since 4.0.0
*/
public static function logSearch(Query $searchquery, $resultCount = 0)
{
if (!ComponentHelper::getParams('com_finder')->get('gather_search_statistics', 0)) {
return;
}
if (trim($searchquery->input) == '' && !$searchquery->empty) {
return;
}
// Initialise our variables
$db = Factory::getDbo();
$query = $db->getQuery(true);
// Sanitise the term for the database
$temp = new \stdClass();
$temp->input = trim(strtolower((string) $searchquery->input));
$entry = new \stdClass();
$entry->searchterm = $temp->input;
$entry->query = serialize($temp);
$entry->md5sum = md5($entry->query);
$entry->hits = 1;
$entry->results = $resultCount;
// Query the table to determine if the term has been searched previously
$query->select($db->quoteName('hits'))
->from($db->quoteName('#__finder_logging'))
->where($db->quoteName('md5sum') . ' = ' . $db->quote($entry->md5sum));
$db->setQuery($query);
$hits = (int) $db->loadResult();
// Reset the $query object
$query->clear();
// Update the table based on the results
if ($hits) {
$query->update($db->quoteName('#__finder_logging'))
->set('hits = (hits + 1)')
->where($db->quoteName('md5sum') . ' = ' . $db->quote($entry->md5sum));
$db->setQuery($query);
$db->execute();
} else {
$query->insert($db->quoteName('#__finder_logging'))
->columns(
[
$db->quoteName('searchterm'),
$db->quoteName('query'),
$db->quoteName('md5sum'),
$db->quoteName('hits'),
$db->quoteName('results'),
]
)
->values('?, ?, ?, ?, ?')
->bind(1, $entry->searchterm)
->bind(2, $entry->query, ParameterType::LARGE_OBJECT)
->bind(3, $entry->md5sum)
->bind(4, $entry->hits, ParameterType::INTEGER)
->bind(5, $entry->results, ParameterType::INTEGER);
$db->setQuery($query);
$db->execute();
}
}
}

View File

@ -0,0 +1,154 @@
<?php
/**
* @package Joomla.Site
* @subpackage com_finder
*
* @copyright (C) 2020 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\Helper;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Uri\Uri;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Finder route helper class.
*
* @since 2.5
*/
class RouteHelper
{
/**
* Method to get the route for a search page.
*
* @param integer $f The search filter id. [optional]
* @param string $q The search query string. [optional]
*
* @return string The search route.
*
* @since 2.5
*/
public static function getSearchRoute($f = null, $q = null)
{
// Get the menu item id.
$query = ['view' => 'search', 'q' => $q, 'f' => $f];
$item = self::getItemid($query);
// Get the base route.
$uri = clone Uri::getInstance('index.php?option=com_finder&view=search');
// Add the pre-defined search filter if present.
if ($f !== null) {
$uri->setVar('f', $f);
}
// Add the search query string if present.
if ($q !== null) {
$uri->setVar('q', $q);
}
// Add the menu item id if present.
if ($item !== null) {
$uri->setVar('Itemid', $item);
}
return $uri->toString(['path', 'query']);
}
/**
* Method to get the route for an advanced search page.
*
* @param integer $f The search filter id. [optional]
* @param string $q The search query string. [optional]
*
* @return string The advanced search route.
*
* @since 2.5
*/
public static function getAdvancedRoute($f = null, $q = null)
{
// Get the menu item id.
$query = ['view' => 'advanced', 'q' => $q, 'f' => $f];
$item = self::getItemid($query);
// Get the base route.
$uri = clone Uri::getInstance('index.php?option=com_finder&view=advanced');
// Add the pre-defined search filter if present.
if ($q !== null) {
$uri->setVar('f', $f);
}
// Add the search query string if present.
if ($q !== null) {
$uri->setVar('q', $q);
}
// Add the menu item id if present.
if ($item !== null) {
$uri->setVar('Itemid', $item);
}
return $uri->toString(['path', 'query']);
}
/**
* Method to get the most appropriate menu item for the route based on the
* supplied query needles.
*
* @param array $query An array of URL parameters.
*
* @return mixed An integer on success, null otherwise.
*
* @since 2.5
*/
public static function getItemid($query)
{
static $items, $active;
// Get the menu items for com_finder.
if (!$items || !$active) {
$app = Factory::getApplication();
$com = ComponentHelper::getComponent('com_finder');
$menu = $app->getMenu();
$active = $menu->getActive();
$items = $menu->getItems('component_id', $com->id);
$items = \is_array($items) ? $items : [];
}
// Try to match the active view and filter.
if ($active && @$active->query['view'] == @$query['view'] && @$active->query['f'] == @$query['f']) {
return $active->id;
}
// Try to match the view, query, and filter.
foreach ($items as $item) {
if (@$item->query['view'] == @$query['view'] && @$item->query['q'] == @$query['q'] && @$item->query['f'] == @$query['f']) {
return $item->id;
}
}
// Try to match the view and filter.
foreach ($items as $item) {
if (@$item->query['view'] == @$query['view'] && @$item->query['f'] == @$query['f']) {
return $item->id;
}
}
// Try to match the view.
foreach ($items as $item) {
if (@$item->query['view'] == @$query['view']) {
return $item->id;
}
}
return null;
}
}

View File

@ -0,0 +1,589 @@
<?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\Database\QueryInterface;
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 QueryInterface 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.
foreach ($groups as $group) {
$query->having('SUM(CASE WHEN t.node_id IN (' . implode(',', $group) . ') 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));
/*
* Prevent invalid records from being returned in the final query.
* This can happen if the search results are queried while the indexer is running.
*/
$query->where($db->quoteName('object') . ' != ' . $db->quote(''));
/*
* 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();
$options = [];
$this->setState('filter.language', Multilanguage::isEnabled());
// Get the empty query setting.
$options['empty'] = $params->get('allow_empty_query', 0);
// Get the static taxonomy filters.
$options['filter'] = $input->getInt('f', $params->get('f', ''));
// Get the dynamic taxonomy filters.
$options['filters'] = $input->get('t', $params->get('t', []), 'array');
// Get the query string.
$options['input'] = $input->getString('q', $params->get('q', ''));
// Get the query language.
$options['language'] = $input->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'] = $input->getString('d1', $params->get('d1', ''));
$options['when1'] = $input->getString('w1', $params->get('w1', ''));
// Get the end date and end date modifier filters.
$options['date2'] = $input->getString('d2', $params->get('d2', ''));
$options['when2'] = $input->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->id);
$this->setState('user.groups', $user->getAuthorisedViewLevels());
}
}

View File

@ -0,0 +1,181 @@
<?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\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Multilanguage;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Component\Finder\Administrator\Indexer\Helper;
use Joomla\Database\QueryInterface;
use Joomla\String\StringHelper;
use Joomla\Utilities\ArrayHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Suggestions model class for the Finder package.
*
* @since 2.5
*/
class SuggestionsModel extends ListModel
{
/**
* Context string for the model type.
*
* @var string
* @since 2.5
*/
protected $context = 'com_finder.suggestions';
/**
* Method to get an array of data items.
*
* @return array An array of data items.
*
* @since 2.5
*/
public function getItems()
{
// Get the items.
$items = parent::getItems();
// Convert them to a simple array.
foreach ($items as $k => $v) {
$items[$k] = $v->term;
}
return $items;
}
/**
* Method to build a database query to load the list data.
*
* @return QueryInterface A database query
*
* @since 2.5
*/
protected function getListQuery()
{
$user = $this->getCurrentUser();
$groups = ArrayHelper::toInteger($user->getAuthorisedViewLevels());
$lang = Helper::getPrimaryLanguage($this->getState('language'));
// Create a new query object.
$db = $this->getDatabase();
$termIdQuery = $db->getQuery(true);
$termQuery = $db->getQuery(true);
// Limit term count to a reasonable number of results to reduce main query join size
$termIdQuery->select('ti.term_id')
->from($db->quoteName('#__finder_terms', 'ti'))
->where('ti.term LIKE ' . $db->quote($db->escape(StringHelper::strtolower($this->getState('input')), true) . '%', false))
->where('ti.common = 0')
->where('ti.language IN (' . $db->quote($lang) . ', ' . $db->quote('*') . ')')
->order('ti.links DESC')
->order('ti.weight DESC');
$termIds = $db->setQuery($termIdQuery, 0, 100)->loadColumn();
// Early return on term mismatch
if (!\count($termIds)) {
return $termIdQuery;
}
// Select required fields
$termQuery->select('DISTINCT(t.term)')
->from($db->quoteName('#__finder_terms', 't'))
->whereIn('t.term_id', $termIds)
->order('t.links DESC')
->order('t.weight DESC');
// Join mapping table for term <-> link relation
$mappingTable = $db->quoteName('#__finder_links_terms', 'tm');
$termQuery->join('INNER', $mappingTable . ' ON tm.term_id = t.term_id');
// Join links table
$termQuery->join('INNER', $db->quoteName('#__finder_links', 'l') . ' ON (tm.link_id = l.link_id)')
->where('l.access IN (' . implode(',', $groups) . ')')
->where('l.state = 1')
->where('l.published = 1');
return $termQuery;
}
/**
* 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]
*
* @return string A store id.
*
* @since 2.5
*/
protected function getStoreId($id = '')
{
// Add the search query state.
$id .= ':' . $this->getState('input');
$id .= ':' . $this->getState('language');
// Add the list state.
$id .= ':' . $this->getState('list.start');
$id .= ':' . $this->getState('list.limit');
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.
* @param string $direction An optional direction (asc|desc).
*
* @return void
*
* @since 2.5
*/
protected function populateState($ordering = null, $direction = null)
{
// Get the configuration options.
$app = Factory::getApplication();
$input = $app->getInput();
$params = ComponentHelper::getParams('com_finder');
$user = $this->getCurrentUser();
// Get the query input.
$this->setState('input', $input->request->get('q', '', 'string'));
// Set the query language
if (Multilanguage::isEnabled()) {
$lang = Factory::getLanguage()->getTag();
} else {
$lang = Helper::getDefaultLanguage();
}
$this->setState('language', $lang);
// Load the list state.
$this->setState('list.start', 0);
$this->setState('list.limit', 10);
// Load the parameters.
$this->setState('params', $params);
// Load the user state.
$this->setState('user.id', (int) $user->id);
}
}

View File

@ -0,0 +1,49 @@
<?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\Service;
use Joomla\CMS\Application\SiteApplication;
use Joomla\CMS\Component\Router\RouterView;
use Joomla\CMS\Component\Router\RouterViewConfiguration;
use Joomla\CMS\Component\Router\Rules\MenuRules;
use Joomla\CMS\Component\Router\Rules\NomenuRules;
use Joomla\CMS\Component\Router\Rules\StandardRules;
use Joomla\CMS\Menu\AbstractMenu;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Routing class from com_finder
*
* @since 3.3
*/
class Router extends RouterView
{
/**
* Finder Component router constructor
*
* @param SiteApplication $app The application object
* @param AbstractMenu $menu The menu object to work with
*/
public function __construct(SiteApplication $app, AbstractMenu $menu)
{
$search = new RouterViewConfiguration('search');
$this->registerView($search);
parent::__construct($app, $menu);
$this->attachRule(new MenuRules($this));
$this->attachRule(new StandardRules($this));
$this->attachRule(new NomenuRules($this));
}
}

View File

@ -0,0 +1,94 @@
<?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\View\Search;
use Joomla\CMS\Document\Feed\FeedItem;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Router\Route;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Search feed view class for the Finder package.
*
* @since 2.5
*/
class FeedView extends BaseHtmlView
{
/**
* Method to display the view.
*
* @param string $tpl A template file to load. [optional]
*
* @return void
*
* @since 2.5
*/
public function display($tpl = null)
{
// Get the application
$app = Factory::getApplication();
// Adjust the list limit to the feed limit.
$app->getInput()->set('limit', $app->get('feed_limit'));
// Get view data.
$state = $this->get('State');
$params = $state->get('params');
$query = $this->get('Query');
$results = $this->get('Items');
$total = $this->get('Total');
// If the feed has been disabled, we want to bail out here
if ($params->get('show_feed_link', 1) == 0) {
throw new \Exception(Text::_('JGLOBAL_RESOURCE_NOT_FOUND'), 404);
}
// Push out the query data.
$explained = HTMLHelper::_('query.explained', $query);
// Set the document title.
$this->setDocumentTitle($params->get('page_title', ''));
// Configure the document description.
if (!empty($explained)) {
$this->getDocument()->setDescription(html_entity_decode(strip_tags($explained), ENT_QUOTES, 'UTF-8'));
}
// Set the document link.
$this->getDocument()->link = Route::_($query->toUri());
// If we don't have any results, we are done.
if (empty($results)) {
return;
}
// Convert the results to feed entries.
foreach ($results as $result) {
// Convert the result to a feed entry.
$item = new FeedItem();
$item->title = $result->title;
$item->link = Route::_($result->route);
$item->description = $result->description;
// Use Unix date to cope for non-english languages
$item->date = (int) $result->start_date ? HTMLHelper::_('date', $result->start_date, 'U') : $result->indexdate;
// Loads item info into RSS array
$this->getDocument()->addItem($item);
}
}
}

View File

@ -0,0 +1,366 @@
<?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\View\Search;
use Joomla\CMS\Event\Finder\ResultEvent;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\GenericDataException;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Pagination\Pagination;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Profiler\Profiler;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Router\SiteRouterAwareInterface;
use Joomla\CMS\Router\SiteRouterAwareTrait;
use Joomla\CMS\Uri\Uri;
use Joomla\Component\Finder\Administrator\Indexer\Query;
use Joomla\Component\Finder\Site\Helper\FinderHelper;
use Joomla\Filesystem\Path;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Search HTML view class for the Finder package.
*
* @since 2.5
*/
class HtmlView extends BaseHtmlView implements SiteRouterAwareInterface
{
use SiteRouterAwareTrait;
/**
* The query indexer object
*
* @var Query
*
* @since 4.0.0
*/
protected $query;
/**
* The page parameters
*
* @var \Joomla\Registry\Registry|null
*/
protected $params = null;
/**
* The model state
*
* @var \Joomla\Registry\Registry
*/
protected $state;
/**
* The logged in user
*
* @var \Joomla\CMS\User\User|null
*/
protected $user = null;
/**
* The suggested search query
*
* @var string|false
*
* @since 4.0.0
*/
protected $suggested = false;
/**
* The explained (human-readable) search query
*
* @var string|null
*
* @since 4.0.0
*/
protected $explained = null;
/**
* The page class suffix
*
* @var string
*
* @since 4.0.0
*/
protected $pageclass_sfx = '';
/**
* An array of results
*
* @var array
*
* @since 3.8.0
*/
protected $results;
/**
* The total number of items
*
* @var integer
*
* @since 3.8.0
*/
protected $total;
/**
* The pagination object
*
* @var Pagination
*
* @since 3.8.0
*/
protected $pagination;
/**
* Method to display the view.
*
* @param string $tpl A template file to load. [optional]
*
* @return void
*
* @since 2.5
*/
public function display($tpl = null)
{
$app = Factory::getApplication();
$this->params = $app->getParams();
// Get view data.
$this->state = $this->get('State');
$this->query = $this->get('Query');
\JDEBUG ? Profiler::getInstance('Application')->mark('afterFinderQuery') : null;
$this->results = $this->get('Items');
\JDEBUG ? Profiler::getInstance('Application')->mark('afterFinderResults') : null;
$this->sortOrderFields = $this->get('sortOrderFields');
\JDEBUG ? Profiler::getInstance('Application')->mark('afterFinderSortOrderFields') : null;
$this->total = $this->get('Total');
\JDEBUG ? Profiler::getInstance('Application')->mark('afterFinderTotal') : null;
$this->pagination = $this->get('Pagination');
\JDEBUG ? Profiler::getInstance('Application')->mark('afterFinderPagination') : null;
// Flag indicates to not add limitstart=0 to URL
$this->pagination->hideEmptyLimitstart = true;
$input = $app->getInput()->get;
// Add additional parameters
$queryParameterList = [
'f' => 'int',
't' => 'array',
'q' => 'string',
'l' => 'cmd',
'd1' => 'string',
'd2' => 'string',
'w1' => 'string',
'w2' => 'string',
'o' => 'word',
'od' => 'word',
];
foreach ($queryParameterList as $parameter => $filter) {
$value = $input->get($parameter, null, $filter);
if (\is_null($value)) {
continue;
}
$this->pagination->setAdditionalUrlParam($parameter, $value);
}
// Check for errors.
if (\count($errors = $this->get('Errors'))) {
throw new GenericDataException(implode("\n", $errors), 500);
}
// Configure the pathway.
if (!empty($this->query->input)) {
$app->getPathway()->addItem($this->escape($this->query->input));
}
// Check for a double quote in the query string.
if (strpos($this->query->input, '"')) {
$router = $this->getSiteRouter();
// Fix the q variable in the URL.
if ($router->getVar('q') !== $this->query->input) {
$router->setVar('q', $this->query->input);
}
}
// Run an event on each result item
if (\is_array($this->results)) {
$dispatcher = $this->getDispatcher();
// Import Finder plugins
PluginHelper::importPlugin('finder', null, true, $dispatcher);
foreach ($this->results as $result) {
$dispatcher->dispatch('onFinderResult', new ResultEvent('onFinderResult', [
'subject' => $result,
'query' => $this->query,
]));
}
}
// Log the search
FinderHelper::logSearch($this->query, $this->total);
// Push out the query data.
$this->suggested = HTMLHelper::_('query.suggested', $this->query);
$this->explained = HTMLHelper::_('query.explained', $this->query);
// Escape strings for HTML output
$this->pageclass_sfx = htmlspecialchars($this->params->get('pageclass_sfx', ''));
// Check for layout override only if this is not the active menu item
// If it is the active menu item, then the view and category id will match
$active = $app->getMenu()->getActive();
if (isset($active->query['layout'])) {
// We need to set the layout in case this is an alternative menu item (with an alternative layout)
$this->setLayout($active->query['layout']);
}
$this->prepareDocument();
\JDEBUG ? Profiler::getInstance('Application')->mark('beforeFinderLayout') : null;
parent::display($tpl);
\JDEBUG ? Profiler::getInstance('Application')->mark('afterFinderLayout') : null;
}
/**
* Method to get hidden input fields for a get form so that control variables
* are not lost upon form submission
*
* @return string A string of hidden input form fields
*
* @since 2.5
*/
protected function getFields()
{
$fields = null;
// Get the URI.
$uri = Uri::getInstance(Route::_($this->query->toUri()));
$uri->delVar('q');
$uri->delVar('o');
$uri->delVar('t');
$uri->delVar('d1');
$uri->delVar('d2');
$uri->delVar('w1');
$uri->delVar('w2');
$elements = $uri->getQuery(true);
// Create hidden input elements for each part of the URI.
foreach ($elements as $n => $v) {
if (\is_scalar($v)) {
$fields .= '<input type="hidden" name="' . $n . '" value="' . $v . '">';
}
}
return $fields;
}
/**
* Method to get the layout file for a search result object.
*
* @param string $layout The layout file to check. [optional]
*
* @return string The layout file to use.
*
* @since 2.5
*/
protected function getLayoutFile($layout = null)
{
// Create and sanitize the file name.
$file = $this->_layout . '_' . preg_replace('/[^A-Z0-9_\.-]/i', '', $layout);
// Check if the file exists.
$filetofind = $this->_createFileName('template', ['name' => $file]);
$exists = Path::find($this->_path['template'], $filetofind);
return ($exists ? $layout : 'result');
}
/**
* Prepares the document
*
* @return void
*
* @since 2.5
*/
protected function prepareDocument()
{
$app = Factory::getApplication();
// Because the application sets a default page title,
// we need to get it from the menu item itself
$menu = $app->getMenu()->getActive();
if ($menu) {
$this->params->def('page_heading', $this->params->get('page_title', $menu->title));
} else {
$this->params->def('page_heading', Text::_('COM_FINDER_DEFAULT_PAGE_TITLE'));
}
$this->setDocumentTitle($this->params->get('page_title', ''));
if ($layout = $this->params->get('article_layout')) {
$this->setLayout($layout);
}
// Configure the document meta-description.
if (!empty($this->explained)) {
$explained = $this->escape(html_entity_decode(strip_tags($this->explained), ENT_QUOTES, 'UTF-8'));
$this->getDocument()->setDescription($explained);
} elseif ($this->params->get('menu-meta_description')) {
$this->getDocument()->setDescription($this->params->get('menu-meta_description'));
}
if ($this->params->get('robots')) {
$this->getDocument()->setMetaData('robots', $this->params->get('robots'));
}
// Check for OpenSearch
if ($this->params->get('opensearch', 1)) {
$ostitle = $this->params->get(
'opensearch_name',
Text::_('COM_FINDER_OPENSEARCH_NAME') . ' ' . $app->get('sitename')
);
$this->getDocument()->addHeadLink(
Uri::getInstance()->toString(['scheme', 'host', 'port']) . Route::_('index.php?option=com_finder&view=search&format=opensearch'),
'search',
'rel',
['title' => $ostitle, 'type' => 'application/opensearchdescription+xml']
);
}
// Add feed link to the document head.
if ($this->params->get('show_feed_link', 1) == 1) {
// Add the RSS link.
$props = ['type' => 'application/rss+xml', 'title' => htmlspecialchars($this->getDocument()->getTitle())];
$route = Route::_($this->query->toUri() . '&format=feed&type=rss');
$this->getDocument()->addHeadLink($route, 'alternate', 'rel', $props);
// Add the ATOM link.
$props = ['type' => 'application/atom+xml', 'title' => htmlspecialchars($this->getDocument()->getTitle())];
$route = Route::_($this->query->toUri() . '&format=feed&type=atom');
$this->getDocument()->addHeadLink($route, 'alternate', 'rel', $props);
}
}
}

View File

@ -0,0 +1,89 @@
<?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\View\Search;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Document\Opensearch\OpensearchUrl;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\View\AbstractView;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Uri\Uri;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* OpenSearch View class for Finder
*
* @since 2.5
*/
class OpensearchView extends AbstractView
{
/**
* Method to display the view.
*
* @param string $tpl A template file to load. [optional]
*
* @return void
*
* @since 2.5
*/
public function display($tpl = null)
{
$app = Factory::getApplication();
$params = ComponentHelper::getParams('com_finder');
$this->getDocument()->setShortName($params->get('opensearch_name', $app->get('sitename', '')));
$this->getDocument()->setDescription($params->get('opensearch_description', $app->get('MetaDesc', '')));
// Prevent any output when OpenSearch Support is disabled
if (!$params->get('opensearch', 1)) {
return;
}
// Add the URL for the search
$searchUri = 'index.php?option=com_finder&view=search&q={searchTerms}';
$suggestionsUri = 'index.php?option=com_finder&task=suggestions.opensearchsuggest&format=json&q={searchTerms}';
$baseUrl = Uri::getInstance()->toString(['host', 'port', 'scheme']);
$active = $app->getMenu()->getActive();
if ($active->component == 'com_finder') {
$searchUri .= '&Itemid=' . $active->id;
$suggestionsUri .= '&Itemid=' . $active->id;
}
// Add the HTML result view
$htmlSearch = new OpensearchUrl();
$htmlSearch->template = $baseUrl . Route::_($searchUri, false);
$this->getDocument()->addUrl($htmlSearch);
// Add the RSS result view
$htmlSearch = new OpensearchUrl();
$htmlSearch->template = $baseUrl . Route::_($searchUri . '&format=feed&type=rss', false);
$htmlSearch->type = 'application/rss+xml';
$this->getDocument()->addUrl($htmlSearch);
// Add the Atom result view
$htmlSearch = new OpensearchUrl();
$htmlSearch->template = $baseUrl . Route::_($searchUri . '&format=feed&type=atom', false);
$htmlSearch->type = 'application/atom+xml';
$this->getDocument()->addUrl($htmlSearch);
// Add suggestions URL
if ($params->get('show_autosuggest', 1)) {
$htmlSearch = new OpensearchUrl();
$htmlSearch->template = $baseUrl . Route::_($suggestionsUri, false);
$htmlSearch->type = 'application/x-suggestions+json';
$this->getDocument()->addUrl($htmlSearch);
}
}
}