1<?php 2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project 3// 4// All Rights Reserved. See copyright.txt for details and a complete list of authors. 5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details. 6// $Id$ 7 8// Controller to process requests from the custom search plugin using the list plugin to display results 9// Refactored from customsearch_ajax.php for Tiki 10 11 12class Services_Search_CustomSearchController 13{ 14 private $textranges = []; 15 private $dateranges = []; 16 private $distances = []; 17 private $contentFields; 18 19 function setUp() 20 { 21 Services_Exception_Disabled::check('wikiplugin_list'); 22 Services_Exception_Disabled::check('wikiplugin_customsearch'); 23 Services_Exception_Disabled::check('feature_search'); 24 25 $this->contentFields = TikiLib::lib('tiki')->get_preference('unified_default_content', ['contents'], true); 26 } 27 28 function action_customsearch($input) 29 { 30 global $prefs; 31 32 $this->textranges = []; 33 $this->dateranges = []; 34 $this->distances = []; 35 36 $cachelib = TikiLib::lib('cache'); 37 $definition = $input->definition->word(); 38 if (empty($definition) || ! $definition = $cachelib->getSerialized($definition, 'customsearch')) { 39 $smarty = \TikiLib::lib('smarty'); 40 $smarty->assign('url', $_SERVER['HTTP_REFERER']); 41 $value = $smarty->fetch('search_customsearch/cache_expired.tpl'); 42 return ['html' => $value]; 43 } 44 45 /** @var Search_Query $query */ 46 $query = $definition['query']; 47 /** @var Search_Formatter_Builder $builder */ 48 $builder = $definition['builder']; 49 /** @var Search_Elastic_FacetBuilder $facetsBuilder */ 50 $facetsBuilder = $definition['facets']; 51 52 $tsettings = $definition['tsettings']; 53 $tsret = $definition['tsret']; 54 55 $matches = WikiParser_PluginMatcher::match($definition['data']); 56 $builder->apply($matches); 57 58 $adddata = json_decode($input->adddata->text(), true); 59 60 $recalllastsearch = $input->recalllastsearch->int() ? true : false; 61 62 $id = $input->searchid->text(); 63 if (empty($id)) { 64 $id = '0'; 65 } 66 67 if ($recalllastsearch && isset($_SESSION["customsearch_$id"])) { 68 unset($_SESSION["customsearch_$id"]); 69 } 70 if ($input->sort_mode->text()) { 71 if ($recalllastsearch) { 72 $_SESSION["customsearch_$id"]["sort_mode"] = $input->sort_mode->text(); 73 } 74 $query->setOrder($input->sort_mode->text()); 75 } 76 if ($input->maxRecords->int()) { 77 if ($recalllastsearch) { 78 $_SESSION["customsearch_$id"]["maxRecords"] = $input->maxRecords->int(); 79 } 80 $maxRecords = $input->maxRecords->int(); // pass request data required by list 81 } else { 82 $maxRecords = $prefs['maxRecords']; 83 } 84 if ($input->offset->int()) { 85 if ($recalllastsearch) { 86 $_SESSION["customsearch_$id"]["offset"] = $input->offset->int(); 87 } 88 $offset = $input->offset->int(); 89 } else { 90 $offset = 0; 91 } 92 $query->setRange($offset, $maxRecords); 93 94 if ($adddata) { 95 foreach ($adddata as $fieldid => $d) { 96 $config = $d['config']; 97 $name = $d['name']; 98 $value = $d['value']; 99 $group = empty($config['_group']) ? null : $config['_group']; 100 101 // save values entered as defaults while session lasts 102 if (empty($value) && $value != 0) { 103 $value = ''; // remove false or null 104 } 105 if ($recalllastsearch) { 106 $_SESSION["customsearch_$id"][$fieldid] = $value; 107 } 108 109 if (empty($config['type'])) { 110 $config['type'] = $name; 111 } 112 113 $filter = 'content'; //default 114 if (isset($config['_filter']) || $name == 'categories' || $name == 'daterange' || $name == 'distance') { 115 if ($config['_filter'] == 'language') { 116 $filter = 'language'; 117 } elseif ($config['_filter'] == 'type') { 118 $filter = 'type'; 119 } elseif ($config['_filter'] == 'categories' || $name == 'categories') { 120 $filter = 'categories'; 121 } elseif ($name == 'daterange') { 122 $filter = 'daterange'; 123 } elseif ($name == 'distance') { 124 $filter = 'distance'; 125 if (! $input->sort_mode->text()) { 126 $config['sort'] = true; 127 } 128 } 129 } 130 131 if (is_array($value) && count($value) > 1) { 132 $value = implode(' ', $value); 133 } elseif (is_array($value)) { 134 $value = current($value); 135 } 136 137 $function = "cs_dataappend_{$filter}"; 138 if (method_exists($this, $function)) { 139 $this->$function($query->getSubQuery($group), $config, $value); 140 } 141 } 142 143 foreach ($this->textranges as $info) { 144 if (count($info['values']) == 2) { 145 $from = array_shift($info['values']); 146 $to = array_shift($info['values']); 147 $info['query']->filterTextRange($from, $to, $info['config']['_field']); 148 } 149 } 150 151 foreach ($this->dateranges as $info) { 152 if (count($info['values']) == 2) { 153 $from = array_shift($info['values']); 154 $to = array_shift($info['values']); 155 $info['query']->filterRange($from, $to, $info['config']['_field']); 156 } 157 } 158 } 159 160 if ($prefs['storedsearch_enabled'] == 'y' && $queryId = $input->store_query->int()) { 161 // Store prior to adding 162 $storedsearchlib = TikiLib::lib('storedsearch'); 163 $storeResult = $storedsearchlib->storeUserQuery($queryId, $query); 164 165 if (! $storeResult) { 166 throw new Services_Exception('Failed to store the query.', 500); 167 } 168 } 169 170 $unifiedsearchlib = TikiLib::lib('unifiedsearch'); 171 $unifiedsearchlib->initQuery($query); // Done after cache because permissions vary 172 173 174 if ($prefs['unified_highlight_results'] === 'y') { 175 $query->applyTransform( 176 new \Search\ResultSet\UrlHighlightTermsTransform( 177 $query->getTerms() 178 ) 179 ); 180 } 181 182 $facetsBuilder->build($query, $unifiedsearchlib->getFacetProvider()); 183 184 $index = $unifiedsearchlib->getIndex(); 185 $resultSet = $query->search($index); 186 if (! empty($_SESSION['tikifeedback']) && $_SESSION['tikifeedback'][0]['type'] === 'error') { 187 Feedback::send_headers(); 188 } else { 189 $resultSet->setTsSettings($builder->getTsSettings()); 190 $resultSet->setId('wpcs-' . $id); 191 $resultSet->setTsOn($tsret['tsOn']); 192 193 $formatter = $builder->getFormatter(); 194 $results = $formatter->format($resultSet); 195 196 $parserLib = TikiLib::lib('parser'); 197 $results = $parserLib->searchFilePreview($results, true); 198 $results = $parserLib->parse_data($results, ['is_html' => true, 'skipvalidation' => true]); 199 200 return ['html' => $results]; 201 } 202 } 203 204 private function cs_dataappend_language(Search_Query $query, $config, $value) 205 { 206 if ($config['type'] != 'text') { 207 if (! empty($config['_value'])) { 208 $value = $config['_value']; 209 $query->filterLanguage($value); 210 } elseif ($value) { 211 $query->filterLanguage($value); 212 } 213 } 214 } 215 216 private function cs_dataappend_type(Search_Query $query, $config, $value) 217 { 218 if ($config['type'] != 'text') { 219 if (! empty($config['_value'])) { 220 $value = $config['_value']; 221 $query->filterType($value); 222 } elseif ($value) { 223 $query->filterType($value); 224 } 225 } 226 } 227 228 private function cs_dataappend_content(Search_Query $query, $config, $value) 229 { 230 if (( isset($config['_textrange']) || isset($config['_daterange']) ) && ( isset($config['_emptyfrom']) || isset($config['_emptyto']) ) && $value <= '') { 231 $value = isset($config['_emptyfrom']) ? $config['_emptyfrom'] : $config['_emptyto']; 232 } 233 if ($value > '') { 234 if (isset($config['_textrange'])) { 235 $this->cs_handle_textrange($config['_textrange'], $query, $config, $value); 236 } elseif (isset($config['_daterange'])) { 237 $this->cs_handle_daterange($config['_daterange'], $query, $config, $value); 238 } elseif ($config['type'] == 'checkbox') { 239 if (empty($config['_field'])) { 240 return; 241 } 242 if (! empty($config['_value'])) { 243 if ($config['_value'] == 'n') { 244 $config['_value'] = 'NOT y'; 245 } 246 $query->filterContent($config['_value'], $config['_field']); 247 } else { 248 $query->filterContent('y', $config['_field']); 249 } 250 } elseif ($config['type'] == 'radio' && ! empty($config['_value'])) { 251 if (empty($config['_field'])) { 252 $query->filterContent($config['_value']); 253 } else { 254 $query->filterContent($config['_value'], $config['_field']); 255 } 256 } else { 257 if ($config['type'] == 'select' && ! empty($config['multiple']) && ! empty($config['_operator'])) { 258 $value = str_replace(' ', " {$config['_operator']} ", $value); 259 } 260 // covers everything else including radio that have no _value set (use sent value) 261 if (empty($config['_field'])) { 262 $query->filterContent($value, $this->contentFields); 263 } else { 264 $query->filterContent($value, $config['_field']); 265 } 266 } 267 } 268 } 269 270 private function cs_handle_textrange($rangeName, Search_Query $query, $config, $value) 271 { 272 if (! isset($this->textranges[$rangeName])) { 273 $this->textranges[$rangeName] = [ 274 'query' => $query, 275 'config' => $config, 276 'values' => [], 277 ]; 278 } 279 280 if (isset($config['_emptyother']) && isset($config['_emptyfrom'])) { 281 // is "from" value 282 if (count($this->textranges[$rangeName]['values']) == 0) { 283 $this->textranges[$rangeName]['values'][] = $config['_emptyother']; 284 } elseif (count($this->textranges[$rangeName]['values']) == 2) { 285 array_shift($this->textranges[$rangeName]['values']); 286 } 287 array_unshift($this->textranges[$rangeName]['values'], $value); 288 } elseif (isset($config['_emptyother']) && isset($config['_emptyto'])) { 289 // is "to" value 290 if (count($this->textranges[$rangeName]['values']) == 0) { 291 $this->textranges[$rangeName]['values'][] = $config['_emptyother']; 292 } elseif (count($this->textranges[$rangeName]['values']) == 2) { 293 array_pop($this->textranges[$rangeName]['values']); 294 } 295 $this->textranges[$rangeName]['values'][] = $value; 296 } else { 297 $this->textranges[$rangeName]['values'][] = $value; 298 } 299 } 300 301 private function cs_handle_daterange($rangeName, Search_Query $query, $config, $value) 302 { 303 if (! isset($this->dateranges[$rangeName])) { 304 $this->dateranges[$rangeName] = [ 305 'query' => $query, 306 'config' => $config, 307 'values' => [], 308 ]; 309 } 310 311 if (isset($config['_emptyother']) && isset($config['_emptyfrom'])) { 312 // is "from" value 313 if (count($this->dateranges[$rangeName]['values']) == 0) { 314 $this->dateranges[$rangeName]['values'][] = $config['_emptyother']; 315 } elseif (count($this->dateranges[$rangeName]['values']) == 2) { 316 array_shift($this->dateranges[$rangeName]['values']); 317 } 318 array_unshift($this->dateranges[$rangeName]['values'], $value); 319 } elseif (isset($config['_emptyother']) && isset($config['_emptyto'])) { 320 // is "to" value 321 if (count($this->dateranges[$rangeName]['values']) == 0) { 322 $this->dateranges[$rangeName]['values'][] = $config['_emptyother']; 323 } elseif (count($this->dateranges[$rangeName]['values']) == 2) { 324 array_pop($this->dateranges[$rangeName]['values']); 325 } 326 $this->dateranges[$rangeName]['values'][] = $value; 327 } else { 328 $this->dateranges[$rangeName]['values'][] = $value; 329 } 330 } 331 332 private function cs_dataappend_categories(Search_Query $query, $config, $value) 333 { 334 if (isset($config['_filter']) && $config['_filter'] == 'categories' && $config['type'] != 'text') { 335 if (! empty($config['_value'])) { 336 $value = $config['_value']; 337 } 338 } elseif (! isset($config['_style'])) { 339 return; 340 } 341 if ($value) { 342 $deep = (isset($config['_showdeep']) && $config['_showdeep'] != 'n') || (isset($config['_deep']) && $config['_deep'] != 'n'); 343 $query->filterCategory($value, $deep); 344 } 345 } 346 347 private function cs_dataappend_daterange(Search_Query $query, $config, $value) 348 { 349 if ($vals = explode(',', $value)) { 350 if (count($vals) == 2) { 351 $from = $vals[0]; 352 $to = $vals[1]; 353 if ((empty($config['_showtime']) || $config['_showtime'] === 'n') && 354 (empty($config['_toendofday']) || $config['_toendofday'] === 'y')) { 355 $to += (24 * 3600) - 1; // end date should be the end of the day, not the beginning 356 } 357 if (! empty($config['_field'])) { 358 $field = $config['_field']; 359 } else { 360 $field = 'modification_date'; 361 } 362 $query->filterRange($from, $to, $field); 363 } 364 } 365 } 366 367 private function cs_dataappend_distance(Search_Query $query, $config, $value) 368 { 369 if ($vals = array_filter(preg_split('/,/', $value))) { // ignore if dist, lat or lon is missing 370 if (count($vals) == 3) { 371 $distance = $vals[0]; 372 $lat = $vals[1]; 373 $lon = $vals[2]; 374 if (! empty($config['_field'])) { 375 $field = $config['_field']; 376 } else { 377 $field = 'geo_point'; 378 } 379 $query->filterDistance($distance, $lat, $lon, $field); 380 381 if (! empty($config['sort']) || ! empty($config['_mode'])) { 382 $order = empty($config['_mode']) ? 'asc' : $config['_mode']; 383 $sortOrder = new Search_Query_Order( 384 $field, 385 'distance', 386 $order, 387 [ 388 'distance' => $distance, 389 'lat' => $lat, 390 'lon' => $lon, 391 ] 392 ); 393 $query->setOrder($sortOrder); 394 } 395 } 396 } 397 } 398} 399