[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/components/com_finder/src/Model/ -> SearchModel.php (source)

   1  <?php
   2  
   3  /**
   4   * @package     Joomla.Site
   5   * @subpackage  com_finder
   6   *
   7   * @copyright   (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
   8   * @license     GNU General Public License version 2 or later; see LICENSE.txt
   9   */
  10  
  11  namespace Joomla\Component\Finder\Site\Model;
  12  
  13  use Joomla\CMS\Factory;
  14  use Joomla\CMS\Language\Multilanguage;
  15  use Joomla\CMS\MVC\Model\ListModel;
  16  use Joomla\Component\Finder\Administrator\Indexer\Query;
  17  use Joomla\String\StringHelper;
  18  
  19  // phpcs:disable PSR1.Files.SideEffects
  20  \defined('_JEXEC') or die;
  21  // phpcs:enable PSR1.Files.SideEffects
  22  
  23  /**
  24   * Search model class for the Finder package.
  25   *
  26   * @since  2.5
  27   */
  28  class SearchModel extends ListModel
  29  {
  30      /**
  31       * Context string for the model type
  32       *
  33       * @var    string
  34       * @since  2.5
  35       */
  36      protected $context = 'com_finder.search';
  37  
  38      /**
  39       * The query object is an instance of Query which contains and
  40       * models the entire search query including the text input; static and
  41       * dynamic taxonomy filters; date filters; etc.
  42       *
  43       * @var    Query
  44       * @since  2.5
  45       */
  46      protected $searchquery;
  47  
  48      /**
  49       * An array of all excluded terms ids.
  50       *
  51       * @var    array
  52       * @since  2.5
  53       */
  54      protected $excludedTerms = array();
  55  
  56      /**
  57       * An array of all included terms ids.
  58       *
  59       * @var    array
  60       * @since  2.5
  61       */
  62      protected $includedTerms = array();
  63  
  64      /**
  65       * An array of all required terms ids.
  66       *
  67       * @var    array
  68       * @since  2.5
  69       */
  70      protected $requiredTerms = array();
  71  
  72      /**
  73       * Method to get the results of the query.
  74       *
  75       * @return  array  An array of Result objects.
  76       *
  77       * @since   2.5
  78       * @throws  \Exception on database error.
  79       */
  80      public function getItems()
  81      {
  82          $items = parent::getItems();
  83  
  84          // Check the data.
  85          if (empty($items)) {
  86              return null;
  87          }
  88  
  89          $results = array();
  90  
  91          // Convert the rows to result objects.
  92          foreach ($items as $rk => $row) {
  93              // Build the result object.
  94              if (is_resource($row->object)) {
  95                  $result = unserialize(stream_get_contents($row->object));
  96              } else {
  97                  $result = unserialize($row->object);
  98              }
  99  
 100              $result->cleanURL = $result->route;
 101  
 102              // Add the result back to the stack.
 103              $results[] = $result;
 104          }
 105  
 106          // Return the results.
 107          return $results;
 108      }
 109  
 110      /**
 111       * Method to get the query object.
 112       *
 113       * @return  Query  A query object.
 114       *
 115       * @since   2.5
 116       */
 117      public function getQuery()
 118      {
 119          // Return the query object.
 120          return $this->searchquery;
 121      }
 122  
 123      /**
 124       * Method to build a database query to load the list data.
 125       *
 126       * @return  \Joomla\Database\DatabaseQuery  A database query.
 127       *
 128       * @since   2.5
 129       */
 130      protected function getListQuery()
 131      {
 132          // Create a new query object.
 133          $db    = $this->getDatabase();
 134          $query = $db->getQuery(true);
 135  
 136          // Select the required fields from the table.
 137          $query->select(
 138              $this->getState(
 139                  'list.select',
 140                  'l.link_id, l.object'
 141              )
 142          );
 143  
 144          $query->from('#__finder_links AS l');
 145  
 146          $user = Factory::getUser();
 147          $groups = $this->getState('user.groups', $user->getAuthorisedViewLevels());
 148          $query->whereIn($db->quoteName('l.access'), $groups)
 149              ->where('l.state = 1')
 150              ->where('l.published = 1');
 151  
 152          // Get the current date, minus seconds.
 153          $nowDate = $db->quote(substr_replace(Factory::getDate()->toSql(), '00', -2));
 154  
 155          // Add the publish up and publish down filters.
 156          $query->where('(l.publish_start_date IS NULL OR l.publish_start_date <= ' . $nowDate . ')')
 157              ->where('(l.publish_end_date IS NULL OR l.publish_end_date >= ' . $nowDate . ')');
 158  
 159          $query->group('l.link_id');
 160          $query->group('l.object');
 161  
 162          /*
 163           * Add the taxonomy filters to the query. We have to join the taxonomy
 164           * map table for each group so that we can use AND clauses across
 165           * groups. Within each group there can be an array of values that will
 166           * use OR clauses.
 167           */
 168          if (!empty($this->searchquery->filters)) {
 169              // Convert the associative array to a numerically indexed array.
 170              $groups = array_values($this->searchquery->filters);
 171              $taxonomies = call_user_func_array('array_merge', array_values($this->searchquery->filters));
 172  
 173              $query->join('INNER', $db->quoteName('#__finder_taxonomy_map') . ' AS t ON t.link_id = l.link_id')
 174                  ->where('t.node_id IN (' . implode(',', array_unique($taxonomies)) . ')');
 175  
 176              // Iterate through each taxonomy group.
 177              for ($i = 0, $c = count($groups); $i < $c; $i++) {
 178                  $query->having('SUM(CASE WHEN t.node_id IN (' . implode(',', $groups[$i]) . ') THEN 1 ELSE 0 END) > 0');
 179              }
 180          }
 181  
 182          // Add the start date filter to the query.
 183          if (!empty($this->searchquery->date1)) {
 184              // Escape the date.
 185              $date1 = $db->quote($this->searchquery->date1);
 186  
 187              // Add the appropriate WHERE condition.
 188              if ($this->searchquery->when1 === 'before') {
 189                  $query->where($db->quoteName('l.start_date') . ' <= ' . $date1);
 190              } elseif ($this->searchquery->when1 === 'after') {
 191                  $query->where($db->quoteName('l.start_date') . ' >= ' . $date1);
 192              } else {
 193                  $query->where($db->quoteName('l.start_date') . ' = ' . $date1);
 194              }
 195          }
 196  
 197          // Add the end date filter to the query.
 198          if (!empty($this->searchquery->date2)) {
 199              // Escape the date.
 200              $date2 = $db->quote($this->searchquery->date2);
 201  
 202              // Add the appropriate WHERE condition.
 203              if ($this->searchquery->when2 === 'before') {
 204                  $query->where($db->quoteName('l.start_date') . ' <= ' . $date2);
 205              } elseif ($this->searchquery->when2 === 'after') {
 206                  $query->where($db->quoteName('l.start_date') . ' >= ' . $date2);
 207              } else {
 208                  $query->where($db->quoteName('l.start_date') . ' = ' . $date2);
 209              }
 210          }
 211  
 212          // Filter by language
 213          if ($this->getState('filter.language')) {
 214              $query->where('l.language IN (' . $db->quote(Factory::getLanguage()->getTag()) . ', ' . $db->quote('*') . ')');
 215          }
 216  
 217          // Get the result ordering and direction.
 218          $ordering = $this->getState('list.ordering', 'm.weight');
 219          $direction = $this->getState('list.direction', 'DESC');
 220  
 221          /*
 222           * If we are ordering by relevance we have to add up the relevance
 223           * scores that are contained in the ordering field.
 224           */
 225          if ($ordering === 'm.weight') {
 226              // Get the base query and add the ordering information.
 227              $query->select('SUM(' . $db->escape($ordering) . ') AS ordering');
 228          } else {
 229              /**
 230               * If we are not ordering by relevance, we just have to add
 231               * the unique items to the set.
 232               */
 233              // Get the base query and add the ordering information.
 234              $query->select($db->escape($ordering) . ' AS ordering');
 235          }
 236  
 237          $query->order('ordering ' . $db->escape($direction));
 238  
 239          /*
 240           * If there are no optional or required search terms in the query, we
 241           * can get the results in one relatively simple database query.
 242           */
 243          if (empty($this->includedTerms) && $this->searchquery->empty && $this->searchquery->input == '') {
 244              // Return the results.
 245              return $query;
 246          }
 247  
 248          /*
 249           * If there are no optional or required search terms in the query and
 250           * empty searches are not allowed, we return an empty query.
 251           * If the search term is not empty and empty searches are allowed,
 252           * but no terms were found, we return an empty query as well.
 253           */
 254          if (
 255              empty($this->includedTerms)
 256              && (!$this->searchquery->empty || ($this->searchquery->empty && $this->searchquery->input != ''))
 257          ) {
 258              // Since we need to return a query, we simplify this one.
 259              $query->clear('join')
 260                  ->clear('where')
 261                  ->clear('bounded')
 262                  ->clear('having')
 263                  ->clear('group')
 264                  ->where('false');
 265  
 266              return $query;
 267          }
 268  
 269          $included = call_user_func_array('array_merge', array_values($this->includedTerms));
 270          $query->join('INNER', $db->quoteName('#__finder_links_terms') . ' AS m ON m.link_id = l.link_id')
 271              ->where('m.term_id IN (' . implode(',', $included) . ')');
 272  
 273          // Check if there are any excluded terms to deal with.
 274          if (count($this->excludedTerms)) {
 275              $query2 = $db->getQuery(true);
 276              $query2->select('e.link_id')
 277                  ->from($db->quoteName('#__finder_links_terms', 'e'))
 278                  ->where('e.term_id IN (' . implode(',', $this->excludedTerms) . ')');
 279              $query->where('l.link_id NOT IN (' . $query2 . ')');
 280          }
 281  
 282          /*
 283           * The query contains required search terms.
 284           */
 285          if (count($this->requiredTerms)) {
 286              foreach ($this->requiredTerms as $terms) {
 287                  if (count($terms)) {
 288                      $query->having('SUM(CASE WHEN m.term_id IN (' . implode(',', $terms) . ') THEN 1 ELSE 0 END) > 0');
 289                  } else {
 290                      $query->where('false');
 291                      break;
 292                  }
 293              }
 294          }
 295  
 296          return $query;
 297      }
 298  
 299      /**
 300       * Method to get a store id based on model the configuration state.
 301       *
 302       * This is necessary because the model is used by the component and
 303       * different modules that might need different sets of data or different
 304       * ordering requirements.
 305       *
 306       * @param   string   $id    An identifier string to generate the store id. [optional]
 307       * @param   boolean  $page  True to store the data paged, false to store all data. [optional]
 308       *
 309       * @return  string  A store id.
 310       *
 311       * @since   2.5
 312       */
 313      protected function getStoreId($id = '', $page = true)
 314      {
 315          // Get the query object.
 316          $query = $this->getQuery();
 317  
 318          // Add the search query state.
 319          $id .= ':' . $query->input;
 320          $id .= ':' . $query->language;
 321          $id .= ':' . $query->filter;
 322          $id .= ':' . serialize($query->filters);
 323          $id .= ':' . $query->date1;
 324          $id .= ':' . $query->date2;
 325          $id .= ':' . $query->when1;
 326          $id .= ':' . $query->when2;
 327  
 328          if ($page) {
 329              // Add the list state for page specific data.
 330              $id .= ':' . $this->getState('list.start');
 331              $id .= ':' . $this->getState('list.limit');
 332              $id .= ':' . $this->getState('list.ordering');
 333              $id .= ':' . $this->getState('list.direction');
 334          }
 335  
 336          return parent::getStoreId($id);
 337      }
 338  
 339      /**
 340       * Method to auto-populate the model state.  Calling getState in this method will result in recursion.
 341       *
 342       * @param   string  $ordering   An optional ordering field. [optional]
 343       * @param   string  $direction  An optional direction. [optional]
 344       *
 345       * @return  void
 346       *
 347       * @since   2.5
 348       */
 349      protected function populateState($ordering = null, $direction = null)
 350      {
 351          // Get the configuration options.
 352          $app      = Factory::getApplication();
 353          $input    = $app->input;
 354          $params   = $app->getParams();
 355          $user     = Factory::getUser();
 356          $language = Factory::getLanguage();
 357  
 358          $this->setState('filter.language', Multilanguage::isEnabled());
 359  
 360          $request = $input->request;
 361          $options = array();
 362  
 363          // Get the empty query setting.
 364          $options['empty'] = $params->get('allow_empty_query', 0);
 365  
 366          // Get the static taxonomy filters.
 367          $options['filter'] = $request->getInt('f', $params->get('f', ''));
 368  
 369          // Get the dynamic taxonomy filters.
 370          $options['filters'] = $request->get('t', $params->get('t', array()), 'array');
 371  
 372          // Get the query string.
 373          $options['input'] = $request->getString('q', $params->get('q', ''));
 374  
 375          // Get the query language.
 376          $options['language'] = $request->getCmd('l', $params->get('l', $language->getTag()));
 377  
 378          // Set the word match mode
 379          $options['word_match'] = $params->get('word_match', 'exact');
 380  
 381          // Get the start date and start date modifier filters.
 382          $options['date1'] = $request->getString('d1', $params->get('d1', ''));
 383          $options['when1'] = $request->getString('w1', $params->get('w1', ''));
 384  
 385          // Get the end date and end date modifier filters.
 386          $options['date2'] = $request->getString('d2', $params->get('d2', ''));
 387          $options['when2'] = $request->getString('w2', $params->get('w2', ''));
 388  
 389          // Load the query object.
 390          $this->searchquery = new Query($options, $this->getDatabase());
 391  
 392          // Load the query token data.
 393          $this->excludedTerms = $this->searchquery->getExcludedTermIds();
 394          $this->includedTerms = $this->searchquery->getIncludedTermIds();
 395          $this->requiredTerms = $this->searchquery->getRequiredTermIds();
 396  
 397          // Load the list state.
 398          $this->setState('list.start', $input->get('limitstart', 0, 'uint'));
 399          $this->setState('list.limit', $input->get('limit', $params->get('list_limit', $app->get('list_limit', 20)), 'uint'));
 400  
 401          /*
 402           * Load the sort ordering.
 403           * Currently this is 'hard' coded via menu item parameter but may not satisfy a users need.
 404           * More flexibility was way more user friendly. So we allow the user to pass a custom value
 405           * from the pool of fields that are indexed like the 'title' field.
 406           * Also, we allow this parameter to be passed in either case (lower/upper).
 407           */
 408          $order = $input->getWord('o', $params->get('sort_order', 'relevance'));
 409          $order = StringHelper::strtolower($order);
 410          $this->setState('list.raworder', $order);
 411  
 412          switch ($order) {
 413              case 'date':
 414                  $this->setState('list.ordering', 'l.start_date');
 415                  break;
 416  
 417              case 'price':
 418                  $this->setState('list.ordering', 'l.list_price');
 419                  break;
 420  
 421              case ($order === 'relevance' && !empty($this->includedTerms)):
 422                  $this->setState('list.ordering', 'm.weight');
 423                  break;
 424  
 425              case 'title':
 426                  $this->setState('list.ordering', 'l.title');
 427                  break;
 428  
 429              default:
 430                  $this->setState('list.ordering', 'l.link_id');
 431                  $this->setState('list.raworder');
 432                  break;
 433          }
 434  
 435          /*
 436           * Load the sort direction.
 437           * Currently this is 'hard' coded via menu item parameter but may not satisfy a users need.
 438           * More flexibility was way more user friendly. So we allow to be inverted.
 439           * Also, we allow this parameter to be passed in either case (lower/upper).
 440           */
 441          $dirn = $input->getWord('od', $params->get('sort_direction', 'desc'));
 442          $dirn = StringHelper::strtolower($dirn);
 443  
 444          switch ($dirn) {
 445              case 'asc':
 446                  $this->setState('list.direction', 'ASC');
 447                  break;
 448  
 449              default:
 450                  $this->setState('list.direction', 'DESC');
 451                  break;
 452          }
 453  
 454          // Set the match limit.
 455          $this->setState('match.limit', 1000);
 456  
 457          // Load the parameters.
 458          $this->setState('params', $params);
 459  
 460          // Load the user state.
 461          $this->setState('user.id', (int) $user->get('id'));
 462          $this->setState('user.groups', $user->getAuthorisedViewLevels());
 463      }
 464  }


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer