1<?php 2/* 3 +-----------------------------------------------------------------------------+ 4 | ILIAS open source | 5 +-----------------------------------------------------------------------------+ 6 | Copyright (c) 1998-2001 ILIAS open source, University of Cologne | 7 | | 8 | This program is free software; you can redistribute it and/or | 9 | modify it under the terms of the GNU General Public License | 10 | as published by the Free Software Foundation; either version 2 | 11 | of the License, or (at your option) any later version. | 12 | | 13 | This program is distributed in the hope that it will be useful, | 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of | 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 16 | GNU General Public License for more details. | 17 | | 18 | You should have received a copy of the GNU General Public License | 19 | along with this program; if not, write to the Free Software | 20 | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | 21 +-----------------------------------------------------------------------------+ 22*/ 23 24/** 25* searchResult stores all result of a search query. 26* Offers methods like mergeResults. To merge result sets of different queries. 27* 28* 29* @author Stefan Meyer <meyer@leifos.com> 30* @version Id$ 31* 32* @package ilias-search 33*/ 34include_once('Services/Search/classes/class.ilUserSearchCache.php'); 35 36define('DEFAULT_SEARCH', 0); 37define('ADVANCED_SEARCH', 1); 38define('ADVANCED_MD_SEARCH', 4); 39 40class ilSearchResult 41{ 42 public $permission = 'visible'; 43 44 public $user_id; 45 public $entries = array(); 46 public $results = array(); 47 public $observers = array(); 48 49 protected $search_cache = null; 50 protected $offset = 0; 51 52 // OBJECT VARIABLES 53 public $ilias; 54 public $ilAccess; 55 56 // Stores info if MAX HITS is reached or not 57 public $limit_reached = false; 58 public $result; 59 60 protected $preventOverwritingMaxhits = false; 61 62 /** 63 * Constructor 64 * @access public 65 */ 66 public function __construct($a_user_id = 0) 67 { 68 global $DIC; 69 70 $ilias = $DIC['ilias']; 71 $ilAccess = $DIC['ilAccess']; 72 $ilDB = $DIC['ilDB']; 73 $ilUser = $DIC['ilUser']; 74 75 $this->ilAccess = $ilAccess; 76 if ($a_user_id) { 77 $this->user_id = $a_user_id; 78 } else { 79 $this->user_id = $ilUser->getId(); 80 } 81 $this->__initSearchSettingsObject(); 82 $this->initUserSearchCache(); 83 84 $this->db = $ilDB; 85 } 86 87 /** 88 * Set the required permission for the rbac checks in function 'filter()' 89 */ 90 public function setRequiredPermission($a_permission) 91 { 92 $this->permission = $a_permission; 93 } 94 95 public function getRequiredPermission() 96 { 97 return $this->permission; 98 } 99 100 101 public function setUserId($a_user_id) 102 { 103 $this->user_id = $a_user_id; 104 } 105 public function getUserId() 106 { 107 return $this->user_id; 108 } 109 110 public function getEntries() 111 { 112 return $this->entries ? $this->entries : array(); 113 } 114 115 public function isLimitReached() 116 { 117 return $this->limit_reached ? true : false; 118 } 119 120 public function setMaxHits($a_max_hits) 121 { 122 $this->max_hits = $a_max_hits; 123 } 124 public function getMaxHits() 125 { 126 return $this->max_hits; 127 } 128 129 /** 130 * Check if offset is reached 131 * 132 * @access public 133 * @param int current counter of result 134 * @return bool reached or not 135 */ 136 public function isOffsetReached($a_counter) 137 { 138 return ($a_counter < $this->offset) ? false : true; 139 } 140 141 /** 142 * 143 * add search result entry 144 * Entries are stored with 'obj_id'. This method is typically called to store db query results. 145 * @param integer object object_id 146 * @param string obj_type 'lm' or 'crs' ... 147 * @param array value position of query parser words in query string 148 * @param integer child id e.g id of page or chapter 149 * @access public 150 */ 151 public function addEntry($a_obj_id, $a_type, $found, $a_child_id = 0) 152 { 153 // Create new entry if it not exists 154 if (!$this->entries[$a_obj_id]) { 155 $this->entries[$a_obj_id]['obj_id'] = $a_obj_id; 156 $this->entries[$a_obj_id]['type'] = $a_type; 157 $this->entries[$a_obj_id]['found'] = $found; 158 $this->entries[$a_obj_id]['child'] = []; 159 160 if ($a_child_id and $a_child_id != $a_obj_id) { 161 $this->entries[$a_obj_id]['child'][$a_child_id] = $a_child_id; 162 } 163 } else { 164 // replace or add child ('pg','st') id 165 if ($a_child_id and $a_child_id != $a_obj_id) { 166 $this->entries[$a_obj_id]['child'][$a_child_id] = $a_child_id; 167 } 168 169 // UPDATE FOUND 170 $counter = 0; 171 foreach ($found as $position) { 172 if ($position) { 173 $this->entries[$a_obj_id]['found'][$counter] = $position; 174 } 175 $counter++; 176 } 177 } 178 return true; 179 } 180 181 /** 182 * 183 * Check number of entries 184 * @access public 185 */ 186 public function numEntries() 187 { 188 return count($this->getEntries()); 189 } 190 191 /** 192 * 193 * merge entries of this instance and another result object 194 * @param object result_obj 195 * @access public 196 */ 197 public function mergeEntries(&$result_obj) 198 { 199 foreach ($result_obj->getEntries() as $entry) { 200 $this->addEntry($entry['obj_id'], $entry['type'], $entry['found']); 201 $this->__updateEntryChilds($entry['obj_id'], $entry['child']); 202 } 203 return true; 204 } 205 206 /** 207 * 208 * diff entries of this instance and another result object 209 * Used for search in results 210 * @param object result_obj 211 * @access public 212 */ 213 public function diffEntriesFromResult(&$result_obj) 214 { 215 $new_entries = $this->getEntries(); 216 $this->entries = array(); 217 218 // Get all checked objects 219 foreach ($this->search_cache->getCheckedItems() as $ref_id => $obj_id) { 220 if (isset($new_entries[$obj_id])) { 221 $this->addEntry( 222 $new_entries[$obj_id]['obj_id'], 223 $new_entries[$obj_id]['type'], 224 $new_entries[$obj_id]['found'] 225 ); 226 $this->__updateEntryChilds( 227 $new_entries[$obj_id]['obj_id'], 228 $new_entries[$obj_id]['child'] 229 ); 230 } 231 } 232 } 233 234 /** 235 * 236 * Build intersection of entries (all entries that are present in both result sets) 237 * @param object result_obj 238 * @access public 239 */ 240 public function intersectEntries(&$result_obj) 241 { 242 $new_entries = $this->getEntries(); 243 $this->entries = array(); 244 245 foreach ($result_obj->getEntries() as $entry) { 246 $obj_id = $entry['obj_id']; 247 if (isset($new_entries[$obj_id])) { 248 $this->addEntry( 249 $new_entries[$obj_id]['obj_id'], 250 $new_entries[$obj_id]['type'], 251 $new_entries[$obj_id]['found'] 252 ); 253 254 $this->__updateEntryChilds( 255 $new_entries[$obj_id]['obj_id'], 256 $new_entries[$obj_id]['child'] 257 ); 258 } 259 } 260 } 261 262 263 /** 264 * 265 * add search result 266 * Results are stored with 'ref_id'. This method is typically called after checking access of entries. 267 * @param integer ref_id 268 * @param integer obj_id 269 * @param string obj_type 'lm' or 'crs' ... 270 * @access public 271 */ 272 public function addResult($a_ref_id, $a_obj_id, $a_type) 273 { 274 $this->results[$a_ref_id]['ref_id'] = $a_ref_id; 275 $this->results[$a_ref_id]['obj_id'] = $a_obj_id; 276 $this->results[$a_ref_id]['type'] = $a_type; 277 } 278 279 public function getResults() 280 { 281 return $this->results ? $this->results : array(); 282 } 283 284 /** 285 * get result ids 286 * 287 * @access public 288 * @return array result ids 289 */ 290 public function getResultIds() 291 { 292 foreach ($this->getResults() as $id => $tmp) { 293 $ids[] = $id; 294 } 295 return $ids ? $ids : array(); 296 } 297 298 public function getResultsByObjId() 299 { 300 $tmp_res = array(); 301 foreach ($this->getResults() as $ref_id => $res_data) { 302 $tmp_res[$res_data['obj_id']][] = $ref_id; 303 } 304 return $tmp_res ? $tmp_res : array(); 305 } 306 307 308 /** 309 * 310 * Get unique results. Return an array of obj_id (No multiple results for references) 311 * Results are stored with 'ref_id'. This method is typically called after checking access of entries. 312 * @access public 313 */ 314 public function getUniqueResults() 315 { 316 $obj_ids = array(); 317 foreach ($this->results as $result) { 318 if (in_array($result['obj_id'], $obj_ids)) { 319 continue; 320 } 321 $obj_ids[] = $result['obj_id']; 322 $objects[] = $result; 323 } 324 return $objects ? $objects : array(); 325 } 326 327 public function getResultsForPresentation() 328 { 329 $res = array(); 330 331 foreach ($this->getResults() as $result) { 332 $res[$result['ref_id']] = $result['obj_id']; 333 } 334 return $res; 335 } 336 337 public function getSubitemIds() 338 { 339 $res = array(); 340 foreach ($this->getResults() as $row) { 341 $res[$row['obj_id']] = $row['child']; 342 } 343 return $res ? $res : array(); 344 } 345 346 347 348 /** 349 * Filter search result. 350 * Do RBAC checks. 351 * 352 * Allows paging of results for referenced objects 353 * 354 * @access public 355 * @param int root node id 356 * @param bool check and boolean search 357 * @return bool success status 358 * 359 */ 360 public function filter($a_root_node, $check_and) 361 { 362 global $DIC; 363 364 $tree = $DIC['tree']; 365 366 // get ref_ids and check access 367 $counter = 0; 368 $offset_counter = 0; 369 foreach ($this->getEntries() as $entry) { 370 // boolean and failed continue 371 if ($check_and and in_array(0, $entry['found'])) { 372 continue; 373 } 374 // Types like role, rolt, user do not need rbac checks 375 $type = ilObject::_lookupType($entry['obj_id']); 376 if ($type == 'rolt' or $type == 'usr' or $type == 'role') { 377 if ($this->callListeners($entry['obj_id'], $entry)) { 378 $this->addResult($entry['obj_id'], $entry['obj_id'], $type); 379 if (is_array($entry['child'])) { 380 $counter += count($entry['child']); 381 } 382 // Stop if maximum of hits is reached 383 if (++$counter > $this->getMaxHits()) { 384 $this->limit_reached = true; 385 return true; 386 } 387 } 388 continue; 389 } 390 // Check referenced objects 391 foreach (ilObject::_getAllReferences($entry['obj_id']) as $ref_id) { 392 // Failed check: if ref id check is failed by previous search 393 if ($this->search_cache->isFailed($ref_id)) { 394 continue; 395 } 396 // Offset check 397 if ($this->search_cache->isChecked($ref_id) and !$this->isOffsetReached($offset_counter)) { 398 ++$offset_counter; 399 continue; 400 } 401 402 if (!$this->callListeners($ref_id, $entry)) { 403 continue; 404 } 405 406 407 408 // RBAC check 409 $type = ilObject::_lookupType($ref_id, true); 410 if ($this->ilAccess->checkAccessOfUser( 411 $this->getUserId(), 412 $this->getRequiredPermission(), 413 '', 414 $ref_id, 415 $type, 416 $entry['obj_id'] 417 )) { 418 if ($a_root_node == ROOT_FOLDER_ID or $tree->isGrandChild($a_root_node, $ref_id)) { 419 // Call listeners 420 #if($this->callListeners($ref_id,$entry)) 421 if (1) { 422 $this->addResult($ref_id, $entry['obj_id'], $type); 423 $this->search_cache->appendToChecked($ref_id, $entry['obj_id']); 424 $this->__updateResultChilds($ref_id, $entry['child']); 425 426 $counter++; 427 $offset_counter++; 428 // Stop if maximum of hits is reached 429 430 if ($counter >= $this->getMaxHits()) { 431 $this->limit_reached = true; 432 $this->search_cache->setResults($this->results); 433 return true; 434 } 435 } 436 } 437 continue; 438 } 439 $this->search_cache->appendToFailed($ref_id); 440 } 441 } 442 $this->search_cache->setResults($this->results); 443 return false; 444 } 445 446 /** 447 * 448 * Filter search area of result set 449 * @access public 450 */ 451 public function filterResults($a_root_node) 452 { 453 global $DIC; 454 455 $tree = $DIC['tree']; 456 457 $tmp_results = $this->getResults(); 458 $this->results = array(); 459 foreach ($tmp_results as $result) { 460 if ($tree->isGrandChild($a_root_node, $result['ref_id']) and $tree->isInTree($result['ref_id'])) { 461 $this->addResult($result['ref_id'], $result['obj_id'], $result['type']); 462 $this->__updateResultChilds($result['ref_id'], $result['child']); 463 } 464 } 465 466 return true; 467 } 468 469 470 /** 471 * 472 * Save search results 473 * @param integer DEFAULT_SEARCH or ADVANCED_SEARCH 474 * @access public 475 */ 476 public function save($a_type = DEFAULT_SEARCH) 477 { 478 $this->search_cache->save(); 479 return false; 480 } 481 /** 482 * 483 * read search results 484 * @param integer DEFAULT_SEARCH or ADVANCED_SEARCH 485 * @access public 486 */ 487 public function read($a_type = DEFAULT_SEARCH) 488 { 489 $this->results = $this->search_cache->getResults(); 490 } 491 492 // PRIVATE 493 /** 494 * 495 * Update childs for a specific entry 496 * @param integer object object_id 497 * @param array array of child ids. E.g 'pg', 'st' 498 * @access private 499 */ 500 public function __updateEntryChilds($a_obj_id, $a_childs) 501 { 502 if ($this->entries[$a_obj_id] and is_array($a_childs)) { 503 foreach ($a_childs as $child_id) { 504 if ($child_id) { 505 $this->entries[$a_obj_id]['child'][$child_id] = $child_id; 506 } 507 } 508 return true; 509 } 510 return false; 511 } 512 /** 513 * 514 * Update childs for a specific result 515 * @param integer object ref_id 516 * @param array array of child ids. E.g 'pg', 'st' 517 * @access private 518 */ 519 public function __updateResultChilds($a_ref_id, $a_childs) 520 { 521 if ($this->results[$a_ref_id] and is_array($a_childs)) { 522 foreach ($a_childs as $child_id) { 523 $this->results[$a_ref_id]['child'][$child_id] = $child_id; 524 } 525 return true; 526 } 527 return false; 528 } 529 530 531 532 public function __initSearchSettingsObject() 533 { 534 include_once 'Services/Search/classes/class.ilSearchSettings.php'; 535 536 $this->search_settings = new ilSearchSettings(); 537 if (!$this->preventOverwritingMaxhits()) { 538 $this->setMaxHits($this->search_settings->getMaxHits()); 539 } 540 } 541 542 /** 543 * Init user search cache 544 * 545 * @access private 546 * 547 */ 548 protected function initUserSearchCache() 549 { 550 include_once('Services/Search/classes/class.ilUserSearchCache.php'); 551 $this->search_cache = ilUserSearchCache::_getInstance($this->getUserId()); 552 $this->offset = $this->getMaxHits() * ($this->search_cache->getResultPageNumber() - 1) ; 553 } 554 555 /** 556 * If you call this function and pass "true" the maxhits setting will not be overwritten 557 * in __initSearchSettingsObject() 558 * 559 * @access public 560 * @param boolean $a_flag true or false to set the flag or leave blank to get the status of the flag 561 * @returmn boolean if called without parameter the status of the flag will be returned, otherwise $this 562 * 563 */ 564 public function preventOverwritingMaxhits($a_flag = null) 565 { 566 if (null === $a_flag) { 567 return $this->preventOverwritingMaxhits; 568 } 569 570 $this->preventOverwritingMaxhits = $a_flag; 571 572 return $this; 573 } 574 575 /** 576 * The observer is used to call functions for filtering result. 577 * Every callback function should support the following parameters: 578 * array of ids. E.g: ref_id = 5,array(obj_id = 1,type = 'crs'), 579 * The function should return true or false. 580 * @param object class of callback function 581 * @param string name of callback method 582 * @access public 583 */ 584 public function addObserver(&$a_class, $a_method) 585 { 586 $this->observers[] = array('class' => $a_class, 587 'method' => $a_method); 588 return true; 589 } 590 public function callListeners($a_ref_id, &$a_data) 591 { 592 foreach ($this->observers as $observer) { 593 $class = &$observer['class']; 594 $method = $observer['method']; 595 596 if (!$class->$method($a_ref_id, $a_data)) { 597 return false; 598 } 599 } 600 return true; 601 } 602} // END class.Search 603