| Current Path : /var/www/html/components/com_jdownloads/src/Model/ |
| Current File : /var/www/html/components/com_jdownloads/src/Model/SearchModel.php |
<?php
/**
* @package Joomla.Site
* @subpackage com_search
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
/**
* @package jDownloads
* @version 4.0
* Some parts from the search component 3.x (and search content plugin) adapted and modified to can use it in jDownloads 4.x as an internal search function.
*/
namespace JDownloads\Component\JDownloads\Site\Model;
\defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Language\Associations;
use Joomla\CMS\Language\Multilanguage;
use Joomla\CMS\Object\CMSObject;
use Joomla\CMS\Table\Table;
use Joomla\Database\ParameterType;
use Joomla\Registry\Registry;
use Joomla\Utilities\ArrayHelper;
use Joomla\CMS\Router\Route;
use Joomla\CMS\MVC\Model\ListModel;
use Joomla\CMS\Session\Session;
use Joomla\CMS\Pagination\Pagination;
use Joomla\CMS\Language\Text;
use Joomla\Database\DatabaseInterface;
use JDownloads\Component\JDownloads\Site\Helper\JDHelper;
use JDownloads\Component\JDownloads\Site\Helper\RouteHelper;
use JDownloads\Component\JDownloads\Site\Helper\SearchHelper;
/**
* Search Model
*
*/
class SearchModel extends ListModel
{
/**
* Sezrch data array
*
* @var array
*/
var $_data = null;
/**
* Search total
*
* @var integer
*/
var $_total = null;
/**
* Search areas
*
* @var integer
*/
var $_areas = null;
/**
* Category listbox
*
* @var array
*/
var $_cat_listbox = null;
/**
* Pagination object
*
* @var object
*/
var $_pagination = null;
/**
* Constructor
*
* @since 1.5
*/
public function __construct()
{
parent::__construct();
//Get configuration
$app = Factory::getApplication();
$config = Factory::getConfig();
$quoted = false;
// we use a session to store the required data for the pagination
$session = Factory::getApplication()->getSession();
// When '1' we do clean the prior stored session data
$reset = (int)$app->getInput()->get('reset', '', 'string');
if ( $reset == 1){
$session->set('jd_searchword', '');
$session->set('jd_searchphrase', '');
$session->set('jd_ordering', '');
$session->set('jd_searchcat', 0);
} else {
$old_searchword = $session->get('jd_searchword');
$old_searchphrase = $session->get('jd_searchphrase');
$old_ordering = $session->get('jd_ordering');
$old_searchcat = $session->get('jd_searchcat');
}
// Get the pagination request variables
$this->setState('limit', $app->getUserStateFromRequest('com_jdownloads.limit', 'limit', $config->get('list_limit'), 'uint'));
$this->setState('limitstart', $app->getInput()->get('limitstart', 0, 'uint'));
// Set the search parameters
$keyword = urldecode($app->getInput()->getString('searchword') ?? '' );
if ($keyword && !$reset){
// slashes cause errors, <> get stripped anyway later on. # causes problems.
$badchars = array('#', '>', '<', '\\');
$searchword = trim(str_replace($badchars, '', $keyword));
// if searchword enclosed in double quotes, strip quotes and do exact match
if (substr($searchword, 0, 1) === '"' && substr($searchword, -1) === '"') {
$keyword = substr($searchword, 1, -1);
$quoted = true;
} else {
$keyword = $searchword;
}
$session->set('jd_searchword', $keyword);
} else {
if (isset($old_searchword) && $old_searchword != ''){
$keyword = $old_searchword;
} else {
$keyword = '';
}
}
if ($quoted){
$searchphrase = 'exact';
} else {
$searchphrase = $app->getInput()->get('searchphrase', '', 'word');
}
if ($searchphrase && !$reset){
$session->set('jd_searchphrase', $searchphrase);
} else {
if (isset($old_searchphrase) && $old_searchphrase != ''){
$searchphrase = $old_searchphrase;
} else {
$searchphrase = 'all';
}
}
$ordering = $app->getInput()->get('ordering', '', 'word');
if ($ordering && !$reset){
$session->set('jd_ordering', $ordering);
} else {
if (isset($old_ordering) && $old_ordering != ''){
$ordering = $old_ordering;
} else {
$ordering = 'newest';
}
}
$searchcat = $app->getInput()->get('searchcat', 0, 'integer');
if (!$reset){
$session->set('jd_searchcat', $searchcat);
} else {
if (isset($old_searchcat) && $old_searchcat != 0){
$searchcat = $old_searchcat;
} else {
$searchcat = 0;
}
}
$this->setSearch($keyword, $searchphrase, $ordering, $searchcat);
//Set the search areas
if (!$reset){
$areas = $app->getInput()->get('areas', null, 'array');
} else {
$areas = array();
}
$cat_searchbox = self::getCategoryList();
$this->setAreas($areas, null, $cat_searchbox);
}
/**
* Method to set the search parameters
*
* @access public
* @param string search string
* @param string mathcing option, exact|any|all
* @param string ordering option, newest|oldest|popular|alpha|category
*/
public function setSearch($keyword, $match = 'all', $ordering = 'newest', $searchcat = 0)
{
if (isset($keyword)) {
$this->setState('origkeyword', $keyword);
if($match !== 'exact') {
$keyword = preg_replace('#\xE3\x80\x80#s', ' ', $keyword);
}
$this->setState('keyword', $keyword);
}
if (isset($match)) {
$this->setState('match', $match);
}
if (isset($ordering)) {
$this->setState('ordering', $ordering);
}
if (isset($searchcat)) {
$this->setState('searchcat', $searchcat);
}
}
/**
* Method to set the search areas
*
* @access public
* @param array Active areas
* @param array Search areas
*/
public function setAreas($active = array(), $search = array(), $cat_listbox = array())
{
$this->_areas['active'] = $active;
$this->_areas['search'] = $search;
$this->_areas['cat_listbox'] = $cat_listbox;
}
/**
* Method to get item data for the category
*
* @access public
* @return array
*/
public function getData()
{
// Lets get the data if it doesn't already exist
if (empty($this->_data))
{
$areas = $this->getAreas();
$results = self::getSearchResults(
$this->getState('keyword'),
$this->getState('match'),
$this->getState('ordering'),
$areas['active'],
$this->getState('searchcat')
);
$rows = array();
$this->_total = count($results);
if ($this->getState('limit') > 0) {
$this->_data = array_splice($results, $this->getState('limitstart'), $this->getState('limit'));
} else {
$this->_data = $results;
}
}
return $this->_data;
}
/**
* Method to get the total number of download items for the category
*
* @access public
* @return integer
*/
public function getTotal()
{
return $this->_total;
}
/**
* Method to get a pagination object of the download items for the category
*
* @access public
* @return integer
*/
public function getPagination()
{
// Lets load the content if it doesn't already exist
if (empty($this->_pagination))
{
$this->_pagination = new Pagination($this->getTotal(), $this->getState('limitstart'), $this->getState('limit'));
}
return $this->_pagination;
}
/**
* Method to get the search areas
*
* @since 1.5
*/
public function getAreas()
{
// Load the Category data
if (empty($this->_areas['search']))
{
$areas = array();
$areas['title'] = Text::_('COM_JDOWNLOADS_SEARCH_IN_TITLES');
$areas['description'] = Text::_('COM_JDOWNLOADS_SEARCH_IN_DESCRIPTIONS');
$areas['changelog'] = Text::_('COM_JDOWNLOADS_SEARCH_IN_CHANGELOG');
$areas['author'] = Text::_('COM_JDOWNLOADS_SEARCH_IN_AUTHOR_NAME');
$areas['metatags'] = Text::_('COM_JDOWNLOADS_SEARCH_IN_META_TAGS');
$this->_areas['search'] = $areas;
}
return $this->_areas;
}
/**
* Method to get a select box with all categories
*
* @return array The field option objects.
* @since 3.9.8.3
*/
public function getCategoryList()
{
// Initialise variables.
$categories = array();
$app = Factory::getApplication();
$user = Factory::getApplication()->getIdentity();
$db = Factory::getContainer()->get(DatabaseInterface::class);
$query = $db->getQuery(true);
$query->select('a.id AS value, a.title AS text, a.level');
$query->from('#__jdownloads_categories AS a');
$query->join('LEFT', '`#__jdownloads_categories` AS b ON a.lft > b.lft AND a.rgt < b.rgt');
$query->where('a.published IN (0,1)');
$query->where('a.access IN (' . implode(',', $user->getAuthorisedViewLevels()) . ')');
// Filter by language
if ($app->getLanguageFilter()){
$query->where('a.language IN (' . $db->quote(Factory::getLanguage()->getTag()) . ',' . $db->quote('*') . ')');
}
$query->group('a.id');
$query->order('a.lft ASC');
// Get the options.
$db->setQuery($query);
// Check for a database error.
try
{
$categories = $db->loadObjectList();
}
catch (\RuntimeException $e)
{
Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
return false;
}
// Pad the option text with spaces using depth level as a multiplier.
for ($i = 0, $n = count($categories); $i < $n; $i++)
{
// Translate ROOT
if ($categories[$i]->level == 0){
$root = array_shift($categories);
$i++;
$n--;
} else {
if ($categories[$i]->level > 1){
$categories[$i]->text = str_repeat('- ',($categories[$i]->level -1)).$categories[$i]->text;
}
}
}
// Add select hint at top
return $categories;
}
/**
* Get the search result
* The sql must return the following fields that are used in a common display
* routine: href, title, section, created, text, browsernav
* @param string Target search string
* @param string mathcing option, exact|any|all
* @param string ordering option, newest|oldest|popular|alpha|category
* @param mixed An array if the search it to be restricted to areas, null if search all
*/
public function getSearchResults($text, $phrase = '', $ordering = '', $areas = null, $searchcat = 0)
{
$app = Factory::getApplication();
$params = $app->getParams();
$db = Factory::getContainer()->get(DatabaseInterface::class);
$serverType = $db->getServerType();
$app = Factory::getApplication();
$user = Factory::getApplication()->getIdentity();
$groups = implode(',', $user->getAuthorisedViewLevels());
$tag = Factory::getLanguage()->getTag();
$user_rules = JDHelper::getUserRules();
$searchText = $text;
$limit = $this->state->get('search_limit', 250);
$nullDate = $db->getNullDate();
$date = Factory::getDate();
$now = $date->toSql();
$text = trim($text);
if ($text === '') {
return array();
}
$wheres = array();
switch ($phrase) {
case 'exact':
$text = $db->Quote('%'.$db->escape($text, true).'%', false);
$wheres2 = array();
if (!$areas || in_array('title', $areas)){
$wheres2[] = 'a.title LIKE '.$text;
}
if (!$areas || in_array('description', $areas)){
$wheres2[] = 'a.description LIKE '.$text;
$wheres2[] = 'a.description_long LIKE '.$text;
}
if (!$areas || in_array('changelog', $areas)){
$wheres2[] = 'a.changelog LIKE '.$text;
}
if (!$areas || in_array('author', $areas)){
$wheres2[] = 'a.author LIKE '.$text;
}
if (!$areas || in_array('metatags', $areas)){
$wheres2[] = 'a.metakey LIKE '.$text;
$wheres2[] = 'a.metadesc LIKE '.$text;
}
$relevance[] = ' CASE WHEN ' . $wheres2[0] . ' THEN 5 ELSE 0 END ';
if (!$areas){
// Join over Fields when we shall search in all (7) fields. So no special field was selected to search only in this content.
$subQuery = $db->getQuery(true);
$subQuery->select('cfv.item_id')
->from('#__fields_values AS cfv')
->join('LEFT', '#__fields AS f ON f.id = cfv.field_id')
->where('(f.context IS NULL OR f.context = ' . $db->q('com_jdownloads.download') . ')')
->where('(f.state IS NULL OR f.state = 1)')
->where('(f.access IS NULL OR f.access IN (' . $groups . '))')
->where('cfv.value LIKE ' . $text);
// Filter by language.
if ($app->isClient('site') && Multilanguage::isEnabled()){
$subQuery->where('(f.language IS NULL OR f.language in (' . $db->quote($tag) . ',' . $db->quote('*') . '))');
}
if ($serverType == 'mysql'){
// This generates a dependent sub-query so do no use in MySQL prior to version 6.0 !
// $wheres2[] = 'a.id IN( '. (string) $subQuery.')';
$db->setQuery($subQuery);
$fieldids = $db->loadColumn();
if (count($fieldids)){
$wheres2[] = 'a.id IN(' . implode(",", $fieldids) . ')';
}
} else {
$wheres2[] = $subQuery->castAs('CHAR', 'a.id') . ' IN( ' . (string) $subQuery . ')';
}
}
$where = '(' . implode(') OR (', $wheres2) . ')';
break;
case 'all':
case 'any':
default:
$words = explode(' ', $text);
$wheres = array();
$cfwhere = array();
foreach ($words as $word)
{
$word = $db->Quote('%'.$db->escape($word, true).'%', false);
$wheres2 = array();
if (!$areas || in_array('title', $areas)){
$wheres2[] = 'a.title LIKE '.$word;
}
if (!$areas || in_array('description', $areas)){
$wheres2[] = 'a.description LIKE '.$word;
$wheres2[] = 'a.description_long LIKE '.$word;
}
if (!$areas || in_array('changelog', $areas)){
$wheres2[] = 'a.changelog LIKE '.$word;
}
if (!$areas || in_array('author', $areas)){
$wheres2[] = 'a.author LIKE '.$word;
}
if (!$areas || in_array('metatags', $areas)){
$wheres2[] = 'a.metakey LIKE '.$word;
$wheres2[] = 'a.metadesc LIKE '.$word;
}
$relevance[] = ' CASE WHEN ' . $wheres2[0] . ' THEN 5 ELSE 0 END ';
if ($phrase === 'all'){
if (!$areas){
// Join over Fields when we shall search in all possible fields. So no special field was selected to search only in this content.
$subQuery = $db->getQuery(true);
$subQuery->select('cfv.item_id')
->from('#__fields_values AS cfv')
->join('LEFT', '#__fields AS f ON f.id = cfv.field_id')
->where('(f.context IS NULL OR f.context = ' . $db->q('com_jdownloads.download') . ')')
->where('(f.state IS NULL OR f.state = 1)')
->where('(f.access IS NULL OR f.access IN (' . $groups . '))')
->where('LOWER(cfv.value) LIKE LOWER(' . $word . ')');
// Filter by language.
if ($app->isClient('site') && Multilanguage::isEnabled()){
$subQuery->where('(f.language IS NULL OR f.language in (' . $db->quote($tag) . ',' . $db->quote('*') . '))');
}
if ($serverType == 'mysql'){
$db->setQuery($subQuery);
$fieldids = $db->loadColumn();
if (count($fieldids)){
$wheres2[] = 'a.id IN(' . implode(",", $fieldids) . ')';
}
} else {
$wheres2[] = $subQuery->castAs('CHAR', 'a.id') . ' IN( ' . (string) $subQuery . ')';
}
}
} else {
$cfwhere[] = 'LOWER(cfv.value) LIKE LOWER(' . $word . ')';
}
$wheres[] = implode(' OR ', $wheres2);
}
if ($phrase === 'any'){
if (!$areas){
// Join over Fields when we shall search in all possible fields. So no special field was selected to search only in this content.
$subQuery = $db->getQuery(true);
$subQuery->select('cfv.item_id')
->from('#__fields_values AS cfv')
->join('LEFT', '#__fields AS f ON f.id = cfv.field_id')
->where('(f.context IS NULL OR f.context = ' . $db->q('com_jdownloads.download') . ')')
->where('(f.state IS NULL OR f.state = 1)')
->where('(f.access IS NULL OR f.access IN (' . $groups . '))')
->where('(' . implode(($phrase === 'all' ? ') AND (' : ') OR ('), $cfwhere) . ')');
// Filter by language.
if ($app->isClient('site') && Multilanguage::isEnabled()){
$subQuery->where('(f.language IS NULL OR f.language in (' . $db->quote($tag) . ',' . $db->quote('*') . '))');
}
if ($serverType == 'mysql'){
$db->setQuery($subQuery);
$fieldids = $db->loadColumn();
if (count($fieldids)){
$wheres[] = 'a.id IN(' . implode(",", $fieldids) . ')';
}
} else {
$wheres[] = $subQuery->castAs('CHAR', 'a.id') . ' IN( ' . (string) $subQuery . ')';
}
}
}
$where = '(' . implode(($phrase == 'all' ? ') AND (' : ') OR ('), $wheres) . ')';
break;
}
$morder = '';
switch ($ordering) {
case 'oldest':
$order = 'a.created ASC';
break;
case 'popular':
$order = 'a.downloads DESC';
break;
case 'alpha':
$order = 'a.title ASC';
break;
case 'category':
$order = 'c.title ASC, a.title ASC';
$morder = 'a.title ASC';
break;
case 'newest':
default:
$order = 'a.created DESC';
break;
}
$rows = array();
$query = $db->getQuery(true);
// search downloads
if ($limit > 0)
{
$query->clear();
//sqlsrv changes
$case_when = ' CASE WHEN ';
$case_when .= $query->charLength('a.alias');
$case_when .= ' THEN ';
$a_id = $query->castAs('CHAR', 'a.id');
$case_when .= $query->concatenate(array($a_id, 'a.alias'), ':');
$case_when .= ' ELSE ';
$case_when .= $a_id.' END as slug';
$case_when1 = ' CASE WHEN ';
$case_when1 .= $query->charLength('c.alias');
$case_when1 .= ' THEN ';
$c_id = $query->castAs('CHAR', 'c.id');
$case_when1 .= $query->concatenate(array($c_id, 'c.alias'), ':');
$case_when1 .= ' ELSE ';
$case_when1 .= $c_id.' END as catslug';
if (!empty($relevance)){
$query->select(implode(' + ', $relevance) . ' AS relevance');
$order = ' relevance DESC, ' . $order;
}
$where_cat = '';
if ($searchcat > 0){
$where_cat = ' AND a.catid = ' . $db->Quote($searchcat) . ' ';
}
$query->select('a.title AS title, a.metadesc, a.metakey, a.url_download, a.extern_file, a.other_file_id, a.license_agree, a.password, a.author, a.created AS created, a.language');
$query->select($query->concatenate(array('a.description', 'a.description_long', 'a.changelog')).' AS text');
$query->select('c.title AS section, ' . $case_when . ',' . $case_when1 . ', ' . '\'2\' AS browsernav');
// We can only use the single user_access field if a value is present
if ($user->id){
$where_single_user = 'OR a.user_access = '.$db->quote($user->id);
} else {
$where_single_user = '';
}
$query->from('#__jdownloads_files AS a')
->innerJoin('#__jdownloads_categories AS c ON c.id = a.catid')
->where('('. $where .')' . $where_cat . 'AND a.published = 1 AND c.published = 1 AND (a.access IN ('.$groups.') AND a.user_access = '.$db->quote(0).' ' . $where_single_user . ') '
.'AND c.access IN ('.$groups.') '
.'AND (a.publish_up IS NULL OR a.publish_up <= '.$db->Quote($now).') '
.'AND (a.publish_down IS NULL OR a.publish_down >= '.$db->Quote($now).')' )
->group('a.id, a.title, a.metadesc, a.metakey, a.author, a.created, a.description, a.description_long, a.changelog, c.title, a.alias, c.alias, c.id')
->order($order);
// Filter by language
if ($app->isClient('site') && Multilanguage::isEnabled()) {
$query->where('a.language in (' . $db->Quote($tag) . ',' . $db->Quote('*') . ')');
$query->where('c.language in (' . $db->Quote($tag) . ',' . $db->Quote('*') . ')');
}
$db->setQuery($query, 0, $limit);
try {
$list = $db->loadObjectList();
}
catch (RuntimeException $e){
$list = array();
Factory::getApplication()->enqueueMessage(Text::_('JERROR_AN_ERROR_HAS_OCCURRED'), 'error');
}
$limit -= count($list);
if (isset($list)){
foreach($list as $key => $item)
{
$direct_download = $params->get('direct_download');
if ((!$item->url_download && !$item->extern_file && !$item->other_file_id) || $item->password || $item->license_agree || $user_rules->view_captcha){
// this download is a simple document without a file so we can not use 'direct' download option
// or we need the summary page for password, captcha or license agree
$direct_download = 0;
}
if ($params->get('view_detailsite')){
// we must link to the details page
$list[$key]->href = RouteHelper::getDownloadRoute($item->slug, $item->catslug, $item->language);
} else {
if ($direct_download){
// we must start the download process directly
$list[$key]->href = Route::_('index.php?option=com_jdownloads&task=download.send&id='.(int)$item->slug.'&catid='.(int)$item->catslug.'&m=0');
} else {
if (!$item->url_download && !$item->extern_file && !$item->other_file_id){
// Download is only a simple document without a file so we must link to the details page
$list[$key]->href = RouteHelper::getDownloadRoute($item->slug, $item->catslug, $item->language);
} else {
// we must link to the summary page
$list[$key]->href = Route::_('index.php?option=com_jdownloads&view=summary&id='.$item->slug.'&catid='.(int)$item->catslug);
}
}
}
}
}
$rows[] = $list;
}
$results = array();
if (count($rows)){
foreach ($rows as $row){
$new_row = array();
foreach ($row as $download){
// Not efficient to get these ONE Download at a TIME
// Lookup field values so they can be checked, GROUP_CONCAT would work in above queries, but isn't supported by non-MySQL DBs.
$query = $db->getQuery(true);
$query->select('fv.value')
->from('#__fields_values as fv')
->join('left', '#__fields as f on fv.field_id = f.id')
->where('f.context = ' . $db->quote('com_jdownloads.download'))
->where('fv.item_id = ' . $db->quote((int) $download->slug));
$db->setQuery($query);
$download->jcfields = implode(',', $db->loadColumn());
if (SearchHelper::checkNoHtml($download, $searchText, array('text', 'title', 'jcfields', 'metadesc', 'metakey'))){
$new_row[] = $download;
}
}
$results = array_merge($results, (array) $new_row);
}
}
return $results;
}
}