1<?php 2/** 3 * @package tikiwiki 4 */ 5// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project 6// 7// All Rights Reserved. See copyright.txt for details and a complete list of authors. 8// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details. 9// $Id$ 10 11$inputConfiguration = [ 12 [ 'staticKeyFilters' => [ 13 'date' => 'digits', 14 'maxRecords' => 'digits', 15 'highlight' => 'text', 16 'where' => 'text', 17 'find' => 'text', 18 'searchLang' => 'word', 19 'words' => 'text', 20 'boolean' => 'word', 21 'storeAs' => 'int', 22 ] 23 ] 24]; 25 26$section = 'search'; 27require_once('tiki-setup.php'); 28$access->check_feature('feature_search'); 29$access->check_permission('tiki_p_search'); 30$smarty->assign('headtitle', tr('Search')); 31 32//get_strings tra("Searchindex") 33//ini_set('display_errors', true); 34//error_reporting(E_ALL); 35 36foreach (['find', 'highlight', 'where'] as $possibleKey) { 37 if (empty($_REQUEST['filter']) && ! empty($_REQUEST[$possibleKey])) { 38 $_REQUEST['filter']['content'] = $_REQUEST[$possibleKey]; 39 } 40} 41$filter = isset($_REQUEST['filter']) ? $_REQUEST['filter'] : []; 42$postfilter = isset($_REQUEST['postfilter']) ? $_REQUEST['postfilter'] : []; 43$facets = []; 44 45if (count($filter) || count($postfilter)) { 46 if (isset($_REQUEST['save_query'])) { 47 $_SESSION['quick_search'][(int) $_REQUEST['save_query']] = $_REQUEST; 48 } 49 $offset = isset($_REQUEST['offset']) ? $_REQUEST['offset'] : 0; 50 $maxRecords = empty($_REQUEST['maxRecords']) ? $prefs['maxRecords'] : $_REQUEST['maxRecords']; 51 52 if ($access->is_serializable_request(true)) { 53 $jitRequest->replaceFilter('fields', 'word'); 54 $fetchFields = array_merge(['title', 'modification_date', 'url'], $jitRequest->asArray('fields', ',')); 55 ; 56 57 $results = tiki_searchindex_get_results($filter, $postfilter, $offset, $maxRecords); 58 59 $smarty->loadPlugin('smarty_function_object_link'); 60 $smarty->loadPlugin('smarty_modifier_sefurl'); 61 foreach ($results as &$res) { 62 foreach ($fetchFields as $f) { 63 if (isset($res[$f])) { 64 $res[$f]; // Dynamic load if applicable 65 } 66 } 67 $res['link'] = smarty_function_object_link( 68 [ 69 'type' => $res['object_type'], 70 'id' => $res['object_id'], 71 'title' => $res['title'], 72 ], 73 $smarty->getEmptyInternalTemplate() 74 ); 75 $res = array_filter( 76 $res, 77 function ($v) { 78 return ! is_null($v); 79 } 80 ); // strip out null values 81 } 82 // add facet/aggregations to the serialised outout 83 if ($prefs['search_use_facets'] == 'y') { 84 $facets = array_map( 85 function ($facet) { 86 return $facet->getOptions(); 87 }, 88 $results->getFacets() 89 ); 90 $resultArray = [ 91 'count' => $results->count(), 92 'maxRecords' => $results->getMaxRecords(), 93 'offset' => $results->getOffset(), 94 'result' => (array) $results, 95 'facets' => $facets, 96 ]; 97 $results = $resultArray; 98 } 99 100 $access->output_serialized( 101 $results, 102 [ 103 'feedTitle' => tr('%0: Results for "%1"', $prefs['sitetitle'], isset($filter['content']) ? $filter['content'] : ''), 104 'feedDescription' => tr('Search Results'), 105 'entryTitleKey' => 'title', 106 'entryUrlKey' => 'url', 107 'entryModificationKey' => 'modification_date', 108 'entryObjectDescriptors' => ['object_type', 'object_id'], 109 ] 110 ); 111 exit; 112 } else { 113 $cachelib = TikiLib::lib('cache'); 114 $cacheType = 'search'; 115 $cacheName = $user . '/' . $offset . '/' . $maxRecords . '/' . serialize($filter); 116 $isCached = false; 117 if (! empty($prefs['unified_user_cache']) && $cachelib->isCached($cacheName, $cacheType)) { 118 list($date, $html) = $cachelib->getSerialized($cacheName, $cacheType); 119 if ($date > $tikilib->now - $prefs['unified_user_cache'] * 60) { 120 $isCached = true; 121 } 122 } 123 $excludedFacets = tiki_searchindex_get_excluded_facets(); 124 if (! $isCached) { 125 $results = tiki_searchindex_get_results($filter, $postfilter, $offset, $maxRecords); 126 $facets = array_filter(array_map( 127 function ($facet) use ($excludedFacets) { 128 $name = $facet->getName(); 129 if (! in_array($name, $excludedFacets)) { 130 return $name; 131 } else { 132 return ''; 133 } 134 }, 135 $results->getFacets() 136 )); 137 138 $plugin = new Search_Formatter_Plugin_SmartyTemplate('searchresults-plain.tpl'); 139 $plugin->setData( 140 [ 141 'prefs' => $prefs, 142 ] 143 ); 144 $fields = [ 145 'title' => null, 146 'url' => null, 147 'modification_date' => null, 148 'highlight' => null, 149 ]; 150 if ($prefs['feature_search_show_visit_count'] === 'y') { 151 $fields['visits'] = null; 152 } 153 $plugin->setFields($fields); 154 155 $formatter = Search_Formatter_Factory::newFormatter($plugin); 156 157 $wiki = $formatter->format($results); 158 $parserLib = TikiLib::lib('parser'); 159 $wiki = $parserLib->searchFilePreview($wiki); 160 $html = $parserLib->parse_data( 161 $wiki, 162 [ 163 'is_html' => true, 164 ] 165 ); 166 if (! empty($prefs['unified_user_cache'])) { 167 $cachelib->cacheItem($cacheName, serialize([$tikilib->now, $html]), $cacheType); 168 } 169 } 170 $smarty->assign('results', $html); 171 } 172} 173 174$smarty->assign('filter', $filter); 175$smarty->assign('postfilter', $postfilter); 176$smarty->assign('facets', $facets); 177 178// disallow robots to index page: 179$smarty->assign('metatag_robots', 'NOINDEX, NOFOLLOW'); 180 181if ($prefs['search_use_facets'] == 'y' && $prefs['unified_engine'] === 'elastic') { 182 $smarty->display("tiki-searchfacets.tpl"); 183} else { 184 $smarty->display("tiki-searchindex.tpl"); 185} 186 187/** 188 * @param array $filter 189 * @param array $postfilter 190 * @param int $offset 191 * @param int $maxRecords 192 * 193 * @return Search_ResultSet 194 * @throws Exception 195 */ 196function tiki_searchindex_get_results($filter, $postfilter, $offset, $maxRecords) 197{ 198 global $prefs; 199 200 $unifiedsearchlib = TikiLib::lib('unifiedsearch'); 201 202 $query = new Search_Query; 203 $unifiedsearchlib->initQueryBase($query); 204 $query = $unifiedsearchlib->buildQuery($filter, $query); 205 $query->filterContent('y', 'searchable'); 206 207 if (count($postfilter)) { 208 $unifiedsearchlib->buildQuery($postfilter, $query->getPostFilter()); 209 } 210 211 if (isset($_REQUEST['sort_mode']) && $order = Search_Query_Order::parse($_REQUEST['sort_mode'])) { 212 $query->setOrder($order); 213 } 214 215 if ($prefs['storedsearch_enabled'] == 'y' && ! empty($_POST['storeAs'])) { 216 $storedsearch = TikiLib::lib('storedsearch'); 217 $storedsearch->storeUserQuery($_POST['storeAs'], $query); 218 TikiLib::lib('smarty')->assign('display_msg', tr('Your query was stored.')); 219 } 220 221 $unifiedsearchlib->initQueryPermissions($query); 222 223 $query->setRange($offset, $maxRecords); 224 225 if ($prefs['feature_search_stats'] == 'y') { 226 $stats = TikiLib::lib('searchstats'); 227 foreach ($query->getTerms() as $term) { 228 $stats->register_term_hit($term); 229 } 230 } 231 232 if ($prefs['search_use_facets'] == 'y') { 233 $provider = $unifiedsearchlib->getFacetProvider(); 234 $facetLabels = []; 235 236 if ($prefs['search_avoid_duplicated_facet_labels'] === 'y') { 237 foreach ($provider->getFacets() as $facet) { 238 $facetLabels[] = $facet->getLabel(); 239 } 240 } 241 $duplicateLabels = array_filter( 242 array_count_values($facetLabels), 243 function ($value) { 244 return $value > 1; 245 } 246 ); 247 248 foreach ($provider->getFacets() as $facet) { 249 $name = $facet->getName(); 250 if (! in_array($name, tiki_searchindex_get_excluded_facets())) { 251 if ($prefs['search_avoid_duplicated_facet_labels'] === 'y') { 252 $label = $facet->getLabel(); 253 if (key_exists($label, $duplicateLabels)) { 254 // it's almost always tracker fields that are duplicated, so just them for now 255 if (strpos($name, 'tracker_field_') === 0) { 256 $field =TikiLib::lib('trk')->get_tracker_field(substr($name, 14)); 257 $definition = \Tracker_Definition::get($field['trackerId']); 258 $facet->setLabel($label . ' (' . $definition->getConfiguration('name') . ')'); 259 } 260 } 261 } 262 $query->requestFacet($facet); 263 } 264 } 265 } 266 267 268 if ($prefs['unified_highlight_results'] === 'y') { 269 $query->applyTransform( 270 new \Search\ResultSet\UrlHighlightTermsTransform( 271 $query->getTerms() 272 ) 273 ); 274 } 275 276 try { 277 if ($prefs['federated_enabled'] == 'y' && ! empty($filter['content'])) { 278 $fed = TikiLib::lib('federatedsearch'); 279 $fed->augmentSimpleQuery($query, $filter['content']); 280 } 281 282 $resultset = $query->search($unifiedsearchlib->getIndex()); 283 284 return $resultset; 285 } catch (Search_Elastic_TransportException $e) { 286 Feedback::error(tr('Search functionality currently unavailable.')); 287 } catch (Exception $e) { 288 Feedback::error($e->getMessage()); 289 } 290 291 return new Search_ResultSet([], 0, 0, -1); 292} 293 294/** 295 * Temporarily exclude the two date range facets from tiki-searchindex.php as they don't work 296 * 297 * @return array 298 */ 299function tiki_searchindex_get_excluded_facets() 300{ 301 global $prefs; 302 303 return array_merge( 304 $prefs['search_excluded_facets'], 305 [ 306 'date_histogram', 307 'date_range', 308 ] 309 ); 310} 311 312