primo commit
This commit is contained in:
62
components/com_finder/src/Controller/DisplayController.php
Normal file
62
components/com_finder/src/Controller/DisplayController.php
Normal 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);
|
||||
}
|
||||
}
|
||||
101
components/com_finder/src/Controller/SuggestionsController.php
Normal file
101
components/com_finder/src/Controller/SuggestionsController.php
Normal 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;
|
||||
}
|
||||
}
|
||||
101
components/com_finder/src/Helper/FinderHelper.php
Normal file
101
components/com_finder/src/Helper/FinderHelper.php
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
154
components/com_finder/src/Helper/RouteHelper.php
Normal file
154
components/com_finder/src/Helper/RouteHelper.php
Normal 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;
|
||||
}
|
||||
}
|
||||
589
components/com_finder/src/Model/SearchModel.php
Normal file
589
components/com_finder/src/Model/SearchModel.php
Normal 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());
|
||||
}
|
||||
}
|
||||
181
components/com_finder/src/Model/SuggestionsModel.php
Normal file
181
components/com_finder/src/Model/SuggestionsModel.php
Normal 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);
|
||||
}
|
||||
}
|
||||
49
components/com_finder/src/Service/Router.php
Normal file
49
components/com_finder/src/Service/Router.php
Normal 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));
|
||||
}
|
||||
}
|
||||
94
components/com_finder/src/View/Search/FeedView.php
Normal file
94
components/com_finder/src/View/Search/FeedView.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
366
components/com_finder/src/View/Search/HtmlView.php
Normal file
366
components/com_finder/src/View/Search/HtmlView.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
89
components/com_finder/src/View/Search/OpensearchView.php
Normal file
89
components/com_finder/src/View/Search/OpensearchView.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user