first commit

This commit is contained in:
2025-06-17 11:53:18 +02:00
commit 9f0f7ba12b
8804 changed files with 1369176 additions and 0 deletions

View File

@ -0,0 +1,29 @@
<?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
*
* @phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
*/
use Joomla\Component\Finder\Site\Helper\RouteHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Finder route helper class.
*
* @since 2.5
*
* @deprecated 4.3 will be removed in 6.0
* Use \Joomla\Component\Finder\Site\Helper\RouteHelper instead
*/
class FinderHelperRoute extends RouteHelper
{
}

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

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\DatabaseQuery;
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 DatabaseQuery 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->get('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,88 @@
<?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\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');
// 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,340 @@
<?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;
// 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);
}
}
}

View File

@ -0,0 +1,37 @@
<?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
*/
defined('_JEXEC') or die;
$this->document->getWebAssetManager()
->useStyle('com_finder.finder')
->useScript('com_finder.finder');
?>
<div class="com-finder finder">
<?php if ($this->params->get('show_page_heading')) : ?>
<h1>
<?php if ($this->escape($this->params->get('page_heading'))) : ?>
<?php echo $this->escape($this->params->get('page_heading')); ?>
<?php else : ?>
<?php echo $this->escape($this->params->get('page_title')); ?>
<?php endif; ?>
</h1>
<?php endif; ?>
<div id="search-form" class="com-finder__form">
<?php echo $this->loadTemplate('form'); ?>
</div>
<?php // Load the search results layout if we are performing a search. ?>
<?php if ($this->query->search === true) : ?>
<div id="search-results" class="com-finder__results">
<?php echo $this->loadTemplate('results'); ?>
</div>
<?php endif; ?>
</div>

View File

@ -0,0 +1,312 @@
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<layout title="COM_FINDER_MENU_SEARCH_VIEW_DEFAULT_TITLE">
<help
key = "Menu_Item:_Search"
/>
<message>
<![CDATA[COM_FINDER_MENU_SEARCH_VIEW_DEFAULT_TEXT]]>
</message>
</layout>
<fields name="request" addfieldprefix="Joomla\Component\Finder\Administrator\Field" >
<fieldset name="request">
<field
name="q"
type="text"
label="COM_FINDER_PREFILL_SEARCH_LABEL"
/>
<field
name="f"
type="searchfilter"
label="COM_FINDER_SELECT_FILTER_LABEL"
default=""
/>
</fieldset>
</fields>
<fields name="params">
<fieldset name="basic">
<field
name="show_date_filters"
type="list"
label="COM_FINDER_CONFIG_SHOW_DATE_FILTERS_LABEL"
default=""
useglobal="true"
class="form-select-color-state"
validate="options"
>
<option value="1">JSHOW</option>
<option value="0">JHIDE</option>
</field>
<field
name="show_advanced"
type="list"
label="COM_FINDER_CONFIG_SHOW_ADVANCED_LABEL"
default=""
useglobal="true"
class="form-select-color-state"
validate="options"
>
<option value="1">JSHOW</option>
<option value="0">JHIDE</option>
</field>
<field
name="expand_advanced"
type="list"
label="COM_FINDER_CONFIG_EXPAND_ADVANCED_LABEL"
default=""
useglobal="true"
class="form-select-color-state"
validate="options"
>
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field type="spacer" />
<field
name="show_taxonomy"
type="list"
label="COM_FINDER_CONFIG_SHOW_TAXONOMY_LABEL"
default=""
useglobal="true"
class="form-select-color-state"
validate="options"
>
<option value="1">JSHOW</option>
<option value="0">JHIDE</option>
</field>
<field
name="show_description"
type="list"
label="COM_FINDER_CONFIG_SHOW_DESCRIPTION_LABEL"
default=""
useglobal="true"
class="form-select-color-state"
validate="options"
>
<option value="1">JSHOW</option>
<option value="0">JHIDE</option>
</field>
<field
name="description_length"
type="number"
label="COM_FINDER_CONFIG_DESCRIPTION_LENGTH_LABEL"
filter="integer"
default=""
useglobal="true"
/>
<field
name="show_image"
type="list"
label="COM_FINDER_CONFIG_SHOW_IMAGE_LABEL"
default=""
useglobal="true"
class="form-select-color-state"
validate="options"
>
<option value="1">JSHOW</option>
<option value="0">JHIDE</option>
</field>
<field
name="image_class"
type="text"
label="COM_FINDER_CONFIG_IMAGE_CLASS_LABEL"
default=""
useglobal="true"
validate="CssIdentifier"
showon="show_image!:0"
/>
<field
name="link_image"
type="list"
label="COM_FINDER_CONFIG_LINKED_IMAGE_LABEL"
default=""
useglobal="true"
class="form-select-color-state"
validate="options"
showon="show_image!:0"
>
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
<field
name="show_date"
type="list"
label="COM_FINDER_CONFIG_SHOW_DATE_LABEL"
default=""
useglobal="true"
class="form-select-color-state"
validate="options"
>
<option value="1">JSHOW</option>
<option value="0">JHIDE</option>
</field>
<field
name="show_url"
type="list"
label="COM_FINDER_CONFIG_SHOW_URL_LABEL"
default=""
useglobal="true"
class="form-select-color-state"
validate="options"
>
<option value="1">JSHOW</option>
<option value="0">JHIDE</option>
</field>
<field type="spacer" />
</fieldset>
<fieldset name="advanced">
<field
name="show_pagination_limit"
type="list"
label="JGLOBAL_DISPLAY_SELECT_LABEL"
validate="options"
class="form-select-color-state"
>
<option value="">JGLOBAL_USE_GLOBAL</option>
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
</field>
<field
name="show_pagination"
type="list"
label="JGLOBAL_PAGINATION_LABEL"
validate="options"
class="form-select-color-state"
>
<option value="">JGLOBAL_USE_GLOBAL</option>
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
<option value="2">JGLOBAL_AUTO</option>
</field>
<field
name="show_pagination_results"
type="list"
label="JGLOBAL_PAGINATION_RESULTS_LABEL"
validate="options"
class="form-select-color-state"
>
<option value="">JGLOBAL_USE_GLOBAL</option>
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
</field>
<field
name="list_limit"
type="list"
label="JGLOBAL_LIST_LIMIT"
default="20"
validate="options"
>
<option value="5">J5</option>
<option value="10">J10</option>
<option value="15">J15</option>
<option value="20">J20</option>
<option value="25">J25</option>
<option value="30">J30</option>
<option value="50">J50</option>
<option value="100">J100</option>
<option value="0">JALL</option>
</field>
<field
name="allow_empty_query"
type="list"
label="COM_FINDER_ALLOW_EMPTY_QUERY_LABEL"
description="COM_FINDER_ALLOW_EMPTY_QUERY_DESC"
default=""
useglobal="true"
class="form-select-color-state"
validate="options"
>
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field
name="show_suggested_query"
type="list"
label="COM_FINDER_CONFIG_SHOW_SUGGESTED_QUERY_LABEL"
default=""
useglobal="true"
class="form-select-color-state"
validate="options"
>
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field
name="show_explained_query"
type="list"
label="COM_FINDER_CONFIG_SHOW_EXPLAINED_QUERY_LABEL"
default=""
useglobal="true"
class="form-select-color-state"
validate="options"
>
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field
name="sort_order"
type="list"
label="COM_FINDER_CONFIG_SORT_ORDER_LABEL"
default=""
useglobal="true"
validate="options"
>
<option value="relevance">COM_FINDER_CONFIG_SORT_OPTION_RELEVANCE</option>
<option value="title">COM_FINDER_CONFIG_SORT_OPTION_TITLE</option>
<option value="date">COM_FINDER_CONFIG_SORT_OPTION_START_DATE</option>
<option value="price">COM_FINDER_CONFIG_SORT_OPTION_LIST_PRICE</option>
<option value="sale_price">COM_FINDER_CONFIG_SORT_OPTION_SALES_PRICE</option>
</field>
<field
name="show_sort_order"
type="radio"
label="COM_FINDER_CONFIG_SHOW_SORT_ORDER_LABEL"
layout="joomla.form.field.radio.switcher"
default="0"
>
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
</field>
<field
name="shown_sort_order"
type="list"
label="COM_FINDER_CONFIG_SORT_ORDER_FIELDS_LABEL"
layout="joomla.form.field.list-fancy-select"
multiple="true"
validate="options"
showon="show_sort_order:1"
>
<option value="relevance">COM_FINDER_CONFIG_SORT_OPTION_RELEVANCE</option>
<option value="title">COM_FINDER_CONFIG_SORT_OPTION_TITLE</option>
<option value="date">COM_FINDER_CONFIG_SORT_OPTION_START_DATE</option>
<option value="price">COM_FINDER_CONFIG_SORT_OPTION_LIST_PRICE</option>
<option value="sale_price">COM_FINDER_CONFIG_SORT_OPTION_SALES_PRICE</option>
</field>
<field
name="sort_direction"
type="list"
label="COM_FINDER_CONFIG_SORT_DIRECTION_LABEL"
default=""
useglobal="true"
validate="options"
>
<option value="desc">COM_FINDER_CONFIG_SORT_OPTION_DESCENDING</option>
<option value="asc">COM_FINDER_CONFIG_SORT_OPTION_ASCENDING</option>
</field>
</fieldset>
<fieldset name="integration">
<field
name="show_feed_link"
type="list"
label="JGLOBAL_SHOW_FEED_LINK_LABEL"
validate="options"
class="form-select-color-state"
>
<option value="">JGLOBAL_USE_GLOBAL</option>
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
</field>
</fieldset>
</fields>
</metadata>

View File

@ -0,0 +1,82 @@
<?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
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
/*
* This segment of code sets up the autocompleter.
*/
if ($this->params->get('show_autosuggest', 1)) {
$this->document->getWebAssetManager()->usePreset('awesomplete');
$this->document->addScriptOptions('finder-search', ['url' => Route::_('index.php?option=com_finder&task=suggestions.suggest&format=json&tmpl=component', false)]);
Text::script('JLIB_JS_AJAX_ERROR_OTHER');
Text::script('JLIB_JS_AJAX_ERROR_PARSE');
}
?>
<form action="<?php echo Route::_($this->query->toUri()); ?>" method="get" class="js-finder-searchform">
<?php echo $this->getFields(); ?>
<fieldset class="com-finder__search word mb-3">
<legend class="com-finder__search-legend visually-hidden">
<?php echo Text::_('COM_FINDER_SEARCH_FORM_LEGEND'); ?>
</legend>
<div class="form-inline">
<label for="q" class="me-2">
<?php echo Text::_('COM_FINDER_SEARCH_TERMS'); ?>
</label>
<div class="input-group">
<input type="text" name="q" id="q" class="js-finder-search-query form-control" value="<?php echo $this->escape($this->query->input); ?>">
<button type="submit" class="btn btn-primary">
<span class="icon-search icon-white" aria-hidden="true"></span>
<?php echo Text::_('JSEARCH_FILTER_SUBMIT'); ?>
</button>
<?php if ($this->params->get('show_advanced', 1)) : ?>
<?php HTMLHelper::_('bootstrap.collapse'); ?>
<button class="btn btn-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#advancedSearch" aria-expanded="<?php echo ($this->params->get('expand_advanced', 0) ? 'true' : 'false'); ?>">
<span class="icon-search-plus" aria-hidden="true"></span>
<?php echo Text::_('COM_FINDER_ADVANCED_SEARCH_TOGGLE'); ?></button>
<?php endif; ?>
</div>
</div>
</fieldset>
<?php if ($this->params->get('show_advanced', 1)) : ?>
<fieldset id="advancedSearch" class="com-finder__advanced js-finder-advanced collapse<?php if ($this->params->get('expand_advanced', 0)) {
echo ' show';
} ?>">
<legend class="com-finder__search-advanced visually-hidden">
<?php echo Text::_('COM_FINDER_SEARCH_ADVANCED_LEGEND'); ?>
</legend>
<?php if ($this->params->get('show_advanced_tips', 1)) : ?>
<div class="com-finder__tips card card-outline-secondary mb-3">
<div class="card-body">
<?php echo Text::_('COM_FINDER_ADVANCED_TIPS_INTRO'); ?>
<?php echo Text::_('COM_FINDER_ADVANCED_TIPS_AND'); ?>
<?php echo Text::_('COM_FINDER_ADVANCED_TIPS_NOT'); ?>
<?php echo Text::_('COM_FINDER_ADVANCED_TIPS_OR'); ?>
<?php if ($this->params->get('tuplecount', 1) > 1) : ?>
<?php echo Text::_('COM_FINDER_ADVANCED_TIPS_PHRASE'); ?>
<?php endif; ?>
<?php echo Text::_('COM_FINDER_ADVANCED_TIPS_OUTRO'); ?>
</div>
</div>
<?php endif; ?>
<div id="finder-filter-window" class="com-finder__filter">
<?php echo HTMLHelper::_('filter.select', $this->query, $this->params); ?>
</div>
</fieldset>
<?php endif; ?>
</form>

View File

@ -0,0 +1,129 @@
<?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
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Multilanguage;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
use Joomla\Component\Finder\Administrator\Helper\LanguageHelper;
use Joomla\Component\Finder\Administrator\Indexer\Helper;
use Joomla\Component\Finder\Administrator\Indexer\Taxonomy;
use Joomla\String\StringHelper;
$user = $this->getCurrentUser();
$show_description = $this->params->get('show_description', 1);
if ($show_description) {
// Calculate number of characters to display around the result
$term_length = StringHelper::strlen($this->query->input);
$desc_length = $this->params->get('description_length', 255);
$pad_length = $term_length < $desc_length ? (int) floor(($desc_length - $term_length) / 2) : 0;
// Make sure we highlight term both in introtext and fulltext
$full_description = $this->result->description;
if (!empty($this->result->summary) && !empty($this->result->body)) {
$full_description = Helper::parse($this->result->summary . $this->result->body);
}
// Find the position of the search term
$pos = $term_length ? StringHelper::strpos(StringHelper::strtolower($full_description), StringHelper::strtolower($this->query->input)) : false;
// Find a potential start point
$start = ($pos && $pos > $pad_length) ? $pos - $pad_length : 0;
// Find a space between $start and $pos, start right after it.
$space = StringHelper::strpos($full_description, ' ', $start > 0 ? $start - 1 : 0);
$start = ($space && $space < $pos) ? $space + 1 : $start;
$description = HTMLHelper::_('string.truncate', StringHelper::substr($full_description, $start), $desc_length, true);
}
$showImage = $this->params->get('show_image', 0);
$imageClass = $this->params->get('image_class', '');
$extraAttr = [];
if ($showImage && !empty($this->result->imageUrl) && $imageClass !== '') {
$extraAttr['class'] = $imageClass;
}
$icon = '';
if (!empty($this->result->mime)) {
$icon = '<span class="icon-file-' . $this->result->mime . '" aria-hidden="true"></span> ';
}
$show_url = '';
if ($this->params->get('show_url', 1)) {
$show_url = '<cite class="result__title-url">' . $this->baseUrl . Route::_($this->result->cleanURL) . '</cite>';
}
?>
<li class="result__item">
<?php if ($showImage && isset($this->result->imageUrl)) : ?>
<figure class="<?php echo htmlspecialchars($imageClass, ENT_COMPAT, 'UTF-8'); ?> result__image">
<?php if ($this->params->get('link_image') && $this->result->route) : ?>
<a href="<?php echo Route::_($this->result->route); ?>">
<?php echo HTMLHelper::_('image', $this->result->imageUrl, $this->result->imageAlt, $extraAttr); ?>
</a>
<?php else : ?>
<?php echo HTMLHelper::_('image', $this->result->imageUrl, $this->result->imageAlt, $extraAttr); ?>
<?php endif; ?>
</figure>
<?php endif; ?>
<p class="result__title">
<?php if ($this->result->route) : ?>
<?php echo HTMLHelper::link(
Route::_($this->result->route),
'<span class="result__title-text">' . $icon . $this->result->title . '</span>' . $show_url,
[
'class' => 'result__title-link'
]
); ?>
<?php else : ?>
<?php echo $this->result->title; ?>
<?php endif; ?>
</p>
<?php if ($show_description && $description !== '') : ?>
<p class="result__description">
<?php if ($this->result->start_date && $this->params->get('show_date', 1)) : ?>
<time class="result__date" datetime="<?php echo HTMLHelper::_('date', $this->result->start_date, 'c'); ?>">
<?php echo HTMLHelper::_('date', $this->result->start_date, Text::_('DATE_FORMAT_LC3')); ?>
</time>
<?php endif; ?>
<?php echo $description; ?>
</p>
<?php endif; ?>
<?php $taxonomies = $this->result->getTaxonomy(); ?>
<?php if (count($taxonomies) && $this->params->get('show_taxonomy', 1)) : ?>
<ul class="result__taxonomy">
<?php foreach ($taxonomies as $type => $taxonomy) : ?>
<?php if ($type == 'Language' && (!Multilanguage::isEnabled() || (isset($taxonomy[0]) && $taxonomy[0]->title == '*'))) : ?>
<?php continue; ?>
<?php endif; ?>
<?php $branch = Taxonomy::getBranch($type); ?>
<?php if ($branch->state == 1 && in_array($branch->access, $user->getAuthorisedViewLevels())) : ?>
<?php $taxonomy_text = []; ?>
<?php foreach ($taxonomy as $node) : ?>
<?php if ($node->state == 1 && in_array($node->access, $user->getAuthorisedViewLevels())) : ?>
<?php $taxonomy_text[] = $node->title; ?>
<?php endif; ?>
<?php endforeach; ?>
<?php if (count($taxonomy_text)) : ?>
<li class="result__taxonomy-item result__taxonomy--<?php echo $type; ?>">
<span><?php echo Text::_(LanguageHelper::branchSingular($type)); ?>:</span>
<?php echo Text::_(LanguageHelper::branchSingular(implode(',', $taxonomy_text))); ?>
</li>
<?php endif; ?>
<?php endif; ?>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</li>

View File

@ -0,0 +1,96 @@
<?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
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Uri\Uri;
?>
<?php // Display the suggested search if it is different from the current search. ?>
<?php if (($this->suggested && $this->params->get('show_suggested_query', 1)) || ($this->explained && $this->params->get('show_explained_query', 1))) : ?>
<div id="search-query-explained" class="com-finder__explained">
<?php // Display the suggested search query. ?>
<?php if ($this->suggested && $this->params->get('show_suggested_query', 1)) : ?>
<?php // Replace the base query string with the suggested query string. ?>
<?php $uri = Uri::getInstance($this->query->toUri()); ?>
<?php $uri->setVar('q', $this->suggested); ?>
<?php // Compile the suggested query link. ?>
<?php $linkUrl = Route::_($uri->toString(['path', 'query'])); ?>
<?php $link = '<a href="' . $linkUrl . '">' . $this->escape($this->suggested) . '</a>'; ?>
<?php echo Text::sprintf('COM_FINDER_SEARCH_SIMILAR', $link); ?>
<?php elseif ($this->explained && $this->params->get('show_explained_query', 1)) : ?>
<?php // Display the explained search query. ?>
<p role="alert">
<?php echo Text::plural('COM_FINDER_QUERY_RESULTS', $this->total, $this->explained); ?>
</p>
<?php endif; ?>
</div>
<?php endif; ?>
<?php // Display the 'no results' message and exit the template. ?>
<?php if (($this->total === 0) || ($this->total === null)) : ?>
<div id="search-result-empty" class="com-finder__empty">
<h2><?php echo Text::_('COM_FINDER_SEARCH_NO_RESULTS_HEADING'); ?></h2>
<?php $multilang = Factory::getApplication()->getLanguageFilter() ? '_MULTILANG' : ''; ?>
<p><?php echo Text::sprintf('COM_FINDER_SEARCH_NO_RESULTS_BODY' . $multilang, $this->escape($this->query->input)); ?></p>
</div>
<?php // Exit this template. ?>
<?php return; ?>
<?php endif; ?>
<?php // Display the 'Sort By' drop-down. ?>
<?php if ($this->params->get('show_sort_order', 0) && !empty($this->sortOrderFields) && !empty($this->results)) : ?>
<div id="search-sorting" class="com-finder__sorting">
<?php echo $this->loadTemplate('sorting'); ?>
</div>
<?php endif; ?>
<?php // Activate the highlighter if enabled. ?>
<?php if (!empty($this->query->highlight) && $this->params->get('highlight_terms', 1)) : ?>
<?php
// Allow a maximum of 10 tokens to be highlighted. Otherwise the URL can get too long.
$this->document->getWebAssetManager()->useScript('highlight');
$this->document->addScriptOptions(
'highlight',
[[
'class' => 'js-highlight',
'highLight' => array_slice($this->query->highlight, 0, 10),
]]
);
?>
<?php endif; ?>
<?php // Display a list of results ?>
<ul id="search-result-list" class="js-highlight com-finder__results-list" start="<?php echo (int) $this->pagination->limitstart + 1; ?>">
<?php $this->baseUrl = Uri::getInstance()->toString(['scheme', 'host', 'port']); ?>
<?php foreach ($this->results as $i => $result) : ?>
<?php $this->result = &$result; ?>
<?php $this->result->counter = $i + 1; ?>
<?php $layout = $this->getLayoutFile($this->result->layout); ?>
<?php echo $this->loadTemplate($layout); ?>
<?php endforeach; ?>
</ul>
<?php // Display the pagination ?>
<div class="com-finder__navigation search-pagination">
<?php if ($this->params->get('show_pagination', 1) > 0) : ?>
<div class="com-finder__pagination w-100">
<?php echo $this->pagination->getPagesLinks(); ?>
</div>
<?php endif; ?>
<?php if ($this->params->get('show_pagination_results', 1) > 0) : ?>
<div class="com-finder__counter search-pages-counter">
<?php // Prepare the pagination string. Results X - Y of Z ?>
<?php $start = (int) $this->pagination->limitstart + 1; ?>
<?php $total = (int) $this->pagination->total; ?>
<?php $limit = (int) $this->pagination->limit * $this->pagination->pagesCurrent; ?>
<?php $limit = (int) min($limit, $total); ?>
<?php echo Text::sprintf('COM_FINDER_SEARCH_RESULTS_OF', $start, $limit, $total); ?>
</div>
<?php endif; ?>
</div>

View File

@ -0,0 +1,46 @@
<?php
/**
* @package Joomla.Site
* @subpackage com_finder
*
* @copyright (C) 2021 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
?>
<?php HTMLHelper::_('bootstrap.dropdown', '.dropdown-toggle'); ?>
<div class="sorting">
<label id="sorting_label" for="sorting_btn"><?php echo Text::_('COM_FINDER_SORT_BY'); ?></label>
<div class="sorting__select btn-group">
<?php foreach ($this->sortOrderFields as $sortOrderField) : ?>
<?php if ($sortOrderField->active) : ?>
<button id="sorting_btn" class="btn btn-secondary dropdown-toggle" type="button"
data-bs-toggle="dropdown"
aria-haspopup="listbox"
aria-expanded="false" aria-controls="finder_sorting_list">
<?php echo $this->escape($sortOrderField->label); ?>
</button>
<?php
break;
endif; ?>
<?php endforeach; ?>
<ul id="finder_sorting_list" class="sorting__list block dropdown-menu" role="listbox" aria-labelledby="finder_sorting_desc">
<?php foreach ($this->sortOrderFields as $sortOrderField) : ?>
<li class="sorting__list-li <?php echo $sortOrderField->active ? 'sorting__list-li-active' : ''; ?>">
<a class="dropdown-item" role="option" href="<?php echo Route::_($sortOrderField->url);?>" <?php echo $sortOrderField->active ? 'aria-current="true"' : ''; ?>>
<?php echo $this->escape($sortOrderField->label); ?>
</a>
</li>
<?php endforeach; ?>
</ul>
</div>
<div class="clearfix"></div>
</div>