Textpattern | PHP Cross Reference | Content Management Systems |
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
Body
title
Description
Body
title
Description
Body
title
Body
title