Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/vendors/Textpattern/Search/Filter.php - 348 lines - 10795 bytes - Summary - Text - Print

Description: Admin-side search.

   1  <?php
   2  
   3  /*
   4   * Textpattern Content Management System
   5   * http://textpattern.com
   6   *
   7   * Copyright (C) 2016 The Textpattern Development Team
   8   *
   9   * This file is part of Textpattern.
  10   *
  11   * Textpattern is free software; you can redistribute it and/or
  12   * modify it under the terms of the GNU General Public License
  13   * as published by the Free Software Foundation, version 2.
  14   *
  15   * Textpattern is distributed in the hope that it will be useful,
  16   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18   * GNU General Public License for more details.
  19   *
  20   * You should have received a copy of the GNU General Public License
  21   * along with Textpattern. If not, see <http://www.gnu.org/licenses/>.
  22   */
  23  
  24  /**
  25   * Admin-side search.
  26   *
  27   * A collection of search-related features that allow search forms to be output
  28   * and permit the DB to be queried using sets of pre-defined criteria-to-DB-field
  29   * mappings.
  30   *
  31   * @since   4.6.0
  32   * @package Search
  33   */
  34  
  35  namespace Textpattern\Search;
  36  
  37  class Filter
  38  {
  39      /**
  40       * The filter's event.
  41       *
  42       * @var string
  43       */
  44  
  45      public $event;
  46  
  47      /**
  48       * The available search methods as an array of Textpattern\Search\Method.
  49       *
  50       * @var array
  51       */
  52  
  53      protected $methods;
  54  
  55      /**
  56       * The filter's in-use search method(s).
  57       *
  58       * @var string[]
  59       */
  60  
  61      protected $search_method;
  62  
  63      /**
  64       * The filter's user-supplied search criteria.
  65       *
  66       * @var string
  67       */
  68  
  69      protected $crit;
  70  
  71      /**
  72       * The SQL-safe (escaped) filter's search criteria.
  73       *
  74       * @var string
  75       */
  76  
  77      protected $crit_escaped;
  78  
  79      /**
  80       * Whether the user-supplied search criteria is to be considered verbatim (quoted) or not.
  81       *
  82       * @var bool
  83       */
  84  
  85      protected $verbatim;
  86  
  87      /**
  88       * General constructor for searches.
  89       *
  90       * @param string    $event      The admin-side event to which this search relates
  91       * @param string    $methods    Available search methods
  92       * @param string    $crit       Criteria to be used in filter. If omitted, uses GET/POST value
  93       * @param string[]  $method     Search method(s) to filter by. If omitted, uses GET/POST value or last-used method
  94       */
  95  
  96      public function __construct($event, $methods, $crit = null, $method = null)
  97      {
  98          $this->event = $event;
  99  
 100          callback_event_ref('search_criteria', $event, 0, $methods);
 101  
 102          $this->setMethods($methods);
 103  
 104          if ($crit === null) {
 105              $this->crit = gps('crit');
 106          }
 107  
 108          $this->setSearchMethod($method);
 109          $this->verbatim = (bool) preg_match('/^"(.*)"$/', $this->crit, $m);
 110          $this->crit_escaped = ($this->verbatim) ? doSlash($m[1]) : doLike($this->crit);
 111      }
 112  
 113      /**
 114       * Sets filter's search methods.
 115       *
 116       * @param array $methods Array of column indices and their human-readable names/types
 117       */
 118  
 119      private function setMethods($methods)
 120      {
 121          foreach ($methods as $key => $atts) {
 122              $this->methods[$key] = new \Textpattern\Search\Method($key, $atts);
 123          }
 124      }
 125  
 126      /**
 127       * Sets filter's options.
 128       *
 129       * @param array $options Array of method indices and their corresponding array of attributes
 130       */
 131  
 132      private function setOptions($options)
 133      {
 134          foreach ($options as $method => $opts) {
 135              if (isset($this->methods[$method])) {
 136                  $this->methods[$method]->setOptions($opts);
 137              }
 138          }
 139      }
 140  
 141      /**
 142       * Sets a method's aliases.
 143       *
 144       * @param string $method Method index to which the aliases should apply
 145       * @param array  $tuples DB criteria => comma-separated list of user criteria values that are equivalent to it
 146       */
 147  
 148      public function setAliases($method, $tuples)
 149      {
 150          if (isset($this->methods[$method])) {
 151              foreach ($tuples as $key => $value) {
 152                  $columns = $this->methods[$method]->getInfo('column');
 153  
 154                  if (!$this->verbatim) {
 155                      $value = strtolower($value);
 156                  }
 157  
 158                  foreach ($columns as $column) {
 159                      $this->methods[$method]->setAlias($column, $key, do_list($value));
 160                  }
 161              }
 162          }
 163      }
 164  
 165      /**
 166       * Generates SQL statements from the current criteria and search_method.
 167       *
 168       * @param  array $options Options
 169       * @return array The criteria SQL, searched value and the search locations
 170       */
 171  
 172      public function getFilter($options = array())
 173      {
 174          $out = array('criteria' => 1);
 175  
 176          if ($this->search_method && $this->crit !== '') {
 177              $this->setOptions($options);
 178  
 179              $search_criteria = array();
 180  
 181              foreach ($this->search_method as $selected_method) {
 182                  if (array_key_exists($selected_method, $this->methods)) {
 183                      $search_criteria[] = join(' or ', $this->methods[$selected_method]->getCriteria($this->crit_escaped, $this->verbatim));
 184                  }
 185              }
 186  
 187              if ($search_criteria) {
 188                  $out['crit'] = $this->crit;
 189                  $out['criteria'] = join(' or ', $search_criteria);
 190  
 191                  if (is_array($this->search_method)) {
 192                      $out['search_method'] = join(',', $this->search_method);
 193                      $this->saveDefaultSearchMethod();
 194                  }
 195              } else {
 196                  $out['crit'] = '';
 197                  $out['search_method'] = join(',', $this->loadDefaultSearchMethod());
 198              }
 199          } else {
 200              $out['crit'] = '';
 201              $out['search_method'] =  join(',', $this->loadDefaultSearchMethod());
 202          }
 203  
 204          $out['criteria'] .= callback_event('admin_criteria', $this->event.'_list', 0, $out['criteria']);
 205  
 206          return array_values($out);
 207      }
 208  
 209      /**
 210       * Renders an admin-side search form.
 211       *
 212       * @param  string $step    Textpattern Step for the form submission
 213       * @param  array  $options Options
 214       * @return string HTML
 215       */
 216  
 217      public function renderForm($step, $options = array())
 218      {
 219          static $id_counter = 0;
 220  
 221          $event = $this->event;
 222          $methods = $this->getMethods();
 223          $selected = $this->search_method;
 224  
 225          extract(lAtts(array(
 226              'default_method' => 'all',
 227              'submit_as'      => 'get', // or 'post'
 228              'placeholder'    => '',
 229              'label_all'      => 'search_all',
 230              'class'          => '',
 231          ), (array) $options));
 232  
 233          $selected = ($selected) ? $selected : $default_method;
 234          $submit_as = (in_array($submit_as, array('get', 'post')) ? $submit_as : 'get');
 235  
 236          if (!is_array($selected)) {
 237              $selected = do_list($selected);
 238          }
 239  
 240          $set_all = ((count($selected) === 1 && $selected[0] === 'all') || (count($selected) === count($methods)) || (count($selected) === 0));
 241  
 242          if ($label_all) {
 243              $methods = array('all' => gTxt($label_all)) + $methods;
 244          }
 245  
 246          $method_list = array();
 247  
 248          foreach ($methods as $key => $value) {
 249              $name = ($key === 'all') ? 'select_all' : 'search_method[]';
 250              $method_list[] = tag(
 251                  n.tag(
 252                      checkbox($name, $key, ($set_all || in_array($key, $selected)), 0, 'search-'.$key.$id_counter).
 253                      n.tag($value, 'label', array('for' => 'search-'.$key.$id_counter)).n,
 254                      'div').n,
 255                  'li'
 256              );
 257          }
 258  
 259          $button_set = n.'<button class="txp-search-button">'.gTxt('search').'</button>';
 260  
 261          if (count($method_list) > 1) {
 262              $button_set .= n.'<button class="txp-search-options">'.gTxt('search_options').'</button>'.n;
 263          }
 264  
 265          $buttons = n.tag($button_set, 'span', array('class' => 'txp-search-buttons')).n;
 266  
 267          // So the search can be used multiple times on a page without id clashes.
 268          $id_counter++;
 269  
 270          // TODO: consider moving Route.add() to textpattern.js, but that involves adding one
 271          // call per panel that requires search, instead of auto-adding it when invoked here.
 272          return form(
 273              (
 274                  $this->crit
 275                      ? span(
 276                          href(gTxt('search_clear'), array('event' => $event)),
 277                          array('class' => 'txp-search-clear'))
 278                      : ''
 279              ).
 280              fInput('search', 'crit', $this->crit, 'txp-search-input', '', '', 24, 0, '', false, false, gTxt($placeholder)).
 281              eInput($event).
 282              sInput($step).
 283              $buttons.
 284              n.tag(join(n, $method_list), 'ul', array('class' => 'txp-dropdown')), '', '', $submit_as, 'txp-search'.($class ? ' '.$class : ''), '', '', 'search'
 285              ).
 286              script_js(<<<EOJS
 287  textpattern.Route.add('{$event}', txp_search);
 288  EOJS
 289              );
 290      }
 291  
 292      /**
 293       * Returns all methods as a simple id->label array.
 294       *
 295       * @return array
 296       */
 297  
 298      public function getMethods()
 299      {
 300          $out = array();
 301  
 302          foreach ($this->methods as $key => $method) {
 303              $out[$key] = $this->methods[$key]->getInfo('label');
 304          }
 305  
 306          return $out;
 307      }
 308  
 309      /**
 310       * Search method(s) to filter by. If omitted, uses GET/POST value or last-used method.
 311       *
 312       * @param string[]|string   $method  The method key(s) as either an array of strings or a comma-separated list.
 313       */
 314      public function setSearchMethod($method = null)
 315      {
 316          $this->search_method = empty($method) ? gps('search_method'): $method;
 317  
 318          if ($this->search_method === '') {
 319              $this->loadDefaultSearchMethod($this->event);
 320          }
 321          // Normalise to an array of trimmed trueish strings, containing keys of known $methods.
 322          $this->search_method = array_filter(do_list(join(',', (array)$this->search_method)));
 323          $this->search_method = array_intersect($this->search_method, array_keys($this->methods));
 324      }
 325  
 326      /**
 327       * Load default search method from a private preference.
 328       *
 329       * @return  string[]    The default search method key(s).
 330       */
 331      public function loadDefaultSearchMethod()
 332      {
 333          assert_string($this->event);
 334          $this->search_method = array_filter(do_list(get_pref('search_options_'.$this->event)));
 335          $this->search_method = array_intersect($this->search_method, array_keys($this->methods));
 336          return $this->search_method;
 337      }
 338  
 339      /**
 340       * Save default search method to a private preference.
 341       */
 342      public function saveDefaultSearchMethod()
 343      {
 344          assert_string($this->event);
 345          assert_array($this->search_method);
 346          set_pref('search_options_'.$this->event, join(', ', $this->search_method), $this->event, PREF_HIDDEN, 'text_input', 0, PREF_PRIVATE);
 347      }
 348  }

title

Description

title

Description

title

Description

title

title

Body