1<?php 2 3define('EXACT_TAG_MATCH', getOption('exact_tag_match')); 4define('SEARCH_DURATION', 3000); 5define('SEARCH_CACHE_DURATION', getOption('search_cache_duration')); 6 7/** 8 * search class 9 * @package core 10 * @subpackage classes\objects 11 */ 12class SearchEngine { 13 14 public $fieldList = NULL; 15 public $page = 1; 16 public $images = NULL; 17 public $albums = NULL; 18 public $articles = NULL; 19 public $pages = NULL; 20 public $pattern; 21 public $tagPattern; 22 private $exact = false; 23 protected $dynalbumname = NULL; 24 protected $album = NULL; 25 protected $words; 26 protected $dates; 27 protected $whichdates = 'date'; // for zenpage date searches, which date field to search 28 protected $search_no_albums = false; // omit albums 29 protected $search_no_images = false; // omit images 30 protected $search_no_pages = false; // omit pages 31 protected $search_no_news = false; // omit news 32 protected $search_unpublished = false; // will override the loggedin checks with respect to unpublished items 33 protected $search_structure; // relates translatable names to search fields 34 protected $iteration = 0; // used by apply_filter('search_statistics') to indicate sequential searches of different objects 35 protected $processed_search = NULL; 36 protected $album_list = NULL; // list of albums to search 37 protected $album_list_exclude = null; // list of albums to exclude from search 38 protected $category_list = NULL; // list of categories for a news search 39 protected $searches = NULL; // remember the criteria for past searches 40 protected $extraparams = array(); // allow plugins to add to search parameters 41// mimic album object 42 public $loaded = false; 43 public $table = 'albums'; 44 public $transient = true; 45 46 /** 47 * Constuctor 48 * 49 * @param bool $dynamic_album set true for dynamic albums (limits the search fields) 50 * @return SearchEngine 51 */ 52 function __construct($dynamic_album = false) { 53 global $_zp_exifvars, $_zp_gallery; 54 switch ((int) getOption('exact_tag_match')) { 55 case 0: 56 // partial 57 $this->tagPattern = array('type' => 'like', 'open' => '%', 'close' => '%'); 58 break; 59 case 1: 60 // exact 61 $this->tagPattern = array('type' => '=', 'open' => '', 'close' => ''); 62 break; 63 case 2: 64 //word 65 $this->tagPattern = array('type' => 'regexp', 'open' => '[[:<:]]', 'close' => '[[:>:]]'); 66 break; 67 } 68 69 switch ((int) getOption('exact_string_match')) { 70 case 0: 71 // pattern 72 $this->pattern = array('type' => 'like', 'open' => '%', 'close' => '%'); 73 break; 74 case 1: 75 // partial start 76 $this->pattern = array('type' => 'regexp', 'open' => '[[:<:]]', 'close' => ''); 77 break; 78 case 2: 79 //word 80 $this->pattern = array('type' => 'regexp', 'open' => '[[:<:]]', 'close' => '[[:>:]]'); 81 break; 82 } 83 84 $this->extraparams['albumssorttype'] = getOption('search_album_sort_type'); 85 $this->extraparams['albumssortdirection'] = getOption('search_album_sort_direction') ? 'DESC' : ''; 86 $this->extraparams['imagessorttype'] = getOption('search_image_sort_type'); 87 $this->extraparams['imagessortdirection'] = getOption('search_image_sort_direction') ? 'DESC' : ''; 88 $this->extraparams['newssorttype'] = getOption('search_newsarticle_sort_type'); 89 $this->extraparams['newssortdirection'] = getOption('search_newsarticle_sort_direction') ? 'DESC' : ''; 90 $this->extraparams['pagessorttype'] = getOption('search_page_sort_type'); 91 $this->extraparams['pagessortdirection'] = getOption('search_page_sort_direction') ? 'DESC' : ''; 92 93//image/album fields 94 $this->search_structure['title'] = gettext('Title'); 95 $this->search_structure['desc'] = gettext('Description'); 96 $this->search_structure['tags'] = gettext('Tags'); 97 $this->search_structure['tags_exact'] = ''; // internal use only field 98 $this->search_structure['filename'] = gettext('File/Folder name'); 99 $this->search_structure['date'] = gettext('Date'); 100 $this->search_structure['custom_data'] = gettext('Custom data'); 101 $this->search_structure['location'] = gettext('Location/Place'); 102 $this->search_structure['city'] = gettext('City'); 103 $this->search_structure['state'] = gettext('State'); 104 $this->search_structure['country'] = gettext('Country'); 105 $this->search_structure['copyright'] = gettext('Copyright'); 106 $this->search_structure['owner'] = gettext('Owner'); 107 $this->search_structure['credit'] = gettext('Credit'); 108 $this->search_structure['lastchangeuser'] = gettext('Last change user'); 109 if (extensionEnabled('zenpage') && !$dynamic_album) { 110//zenpage fields 111 $this->search_structure['content'] = gettext('Content'); 112 $this->search_structure['extracontent'] = gettext('ExtraContent'); 113 $this->search_structure['author'] = gettext('Author'); 114 $this->search_structure['titlelink'] = gettext('TitleLink'); 115 $this->search_structure['news_categories'] = gettext('Categories'); 116 } 117//metadata fields 118 foreach ($_zp_exifvars as $field => $row) { 119 if ($row[4] && $row[5]) { // only those that are "real" and "processed" 120 $this->search_structure[strtolower($field)] = $row[2]; 121 } 122 } 123 $this->search_structure = zp_apply_filter('searchable_fields', $this->search_structure); 124 if (isset($_REQUEST['words'])) { 125 $this->words = removeTrailingSlash(strtr(sanitize($_REQUEST['words'], 4), array('__23__' => '#', '__25__' => '%', '__26__' => '&', '__2F__' => '/'))); 126 } else { 127 $this->words = NULL; 128 if (isset($_REQUEST['date'])) { // words & dates are mutually exclusive 129 $this->dates = removeTrailingSlash(sanitize($_REQUEST['date'], 3)); 130 if (isset($_REQUEST['whichdate'])) { 131 $this->whichdates = sanitize($_REQUEST['whichdate']); 132 } 133 } else { 134 $this->dates = NULL; 135 } 136 } 137 $this->fieldList = $this->parseQueryFields(); 138 if (isset($_REQUEST['inalbums'])) { 139 $list = trim(sanitize($_REQUEST['inalbums'], 3)); 140 if (strlen($list) > 0) { 141 switch ($list) { 142 case "0": 143 $this->search_no_albums = true; 144 setOption('search_no_albums', 1, false); 145 break; 146 case "1": 147 $this->search_no_albums = false; 148 setOption('search_no_albums', 0, false); 149 break; 150 default: 151 $this->album_list = explode(',', $list); 152 break; 153 } 154 } 155 } 156 157 if (isset($_REQUEST['excludealbums'])) { 158 $list = trim(sanitize($_REQUEST['excludealbums'], 3)); 159 if (!empty($list)) { 160 $this->album_list_exclude = explode(',', $list); 161 } 162 } 163 164 if (isset($_REQUEST['inimages'])) { 165 $list = trim(sanitize($_REQUEST['inimages'], 3)); 166 if (strlen($list) > 0) { 167 switch ($list) { 168 case "0": 169 $this->search_no_images = true; 170 setOption('search_no_images', 1, false); 171 break; 172 case "1": 173 $this->search_no_images = false; 174 setOption('search_no_images', 0, false); 175 break; 176 } 177 } 178 } 179 if (isset($_REQUEST['inpages'])) { 180 $list = trim(sanitize($_REQUEST['inpages'], 3)); 181 if (strlen($list) > 0) { 182 switch ($list) { 183 case "0": 184 $this->search_no_pages = true; 185 setOption('search_no_pages', 1, false); 186 break; 187 } 188 } 189 } 190 if (isset($_REQUEST['innews'])) { 191 $list = trim(sanitize($_REQUEST['innews'], 3)); 192 if (strlen($list) > 0) { 193 switch ($list) { 194 case "0": 195 $this->search_no_news = true; 196 setOption('search_no_news', 1, false); 197 break; 198 case "1": 199 break; 200 default: 201 $this->category_list = explode(',', $list); 202 break; 203 } 204 } 205 } 206 $this->images = NULL; 207 $this->albums = NULL; 208 $this->searches = array('images' => NULL, 'albums' => NULL, 'pages' => NULL, 'news' => NULL); 209 zp_apply_filter('search_instantiate', $this); 210 } 211 212 /** 213 * mimic an album object 214 * @return number 215 */ 216 function getID() { 217 return 0; 218 } 219 220 /** 221 * Returns a list of search fields display names indexed by the search mask 222 * 223 * @return array 224 */ 225 function getSearchFieldList() { 226 $list = array(); 227 foreach ($this->search_structure as $key => $display) { 228 if ($display) { 229 $list[$display] = $key; 230 } 231 } 232 return $list; 233 } 234 235 /** 236 * Returns an array of the enabled search fields 237 * 238 * @return array 239 */ 240 function allowedSearchFields() { 241 $setlist = array(); 242 $fields = strtolower(getOption('search_fields')); 243 if (is_numeric($fields)) { 244 $setlist = $this->numericFields($fields); 245 } else { 246 $list = explode(',', $fields); 247 foreach ($this->search_structure as $key => $display) { 248 if (in_array($key, $list)) { 249 $setlist[$display] = $key; 250 } 251 } 252 } 253 return $setlist; 254 } 255 256 /** 257 * converts old style bitmask field spec into field list array 258 * 259 * @param bit $fields 260 * @return array 261 */ 262 protected function numericFields($fields) { 263 if ($fields == 0) 264 $fields = 0x0fff; 265 if ($fields & 0x01) 266 $list[$this->search_structure['title']] = 'title'; 267 if ($fields & 0x02) 268 $list[$this->search_structure['desc']] = 'desc'; 269 if ($fields & 0x04) 270 $list[$this->search_structure['tags']] = 'tags'; 271 if ($fields & 0x08) 272 $list[$this->search_structure['filename']] = 'filename'; 273 return $list; 274 } 275 276 /** 277 * creates a search query from the search words 278 * 279 * @param bool $long set to false to omit albumname and page parts 280 * 281 * @return string 282 */ 283 function getSearchParams($long = true) { 284 global $_zp_page; 285 $r = ''; 286 $w = urlencode(trim($this->codifySearchString())); 287 if (!empty($w)) { 288 $r .= '&words=' . $w; 289 } 290 $d = trim($this->dates); 291 if (!empty($d)) { 292 $r .= '&date=' . $d; 293 $d = trim($this->whichdates); 294 if ($d != 'date') { 295 $r.= '&whichdates=' . $d; 296 } 297 } 298 $r .= $this->getSearchFieldsText($this->fieldList); 299 if ($long) { 300 $a = $this->dynalbumname; 301 if ($a) { 302 $r .= '&albumname=' . $a; 303 } 304 if (empty($this->album_list)) { 305 if ($this->search_no_albums) { 306 $r .= '&inalbums=0'; 307 } 308 } else { 309 $r .= '&inalbums=' . implode(',', $this->album_list); 310 } 311 312 if (empty($this->album_list_exclude)) { 313 if ($this->search_no_albums) { 314 $r .= '&inalbums=0'; 315 } 316 } else { 317 $r .= '&excludealbums=' . implode(',', $this->album_list_exclude); 318 } 319 320 if ($this->search_no_images) { 321 $r .= '&inimages=0'; 322 } 323 if ($this->search_no_pages) { 324 $r .= '&inpages=0'; 325 } 326 if (empty($this->categories)) { 327 if ($this->search_no_news) { 328 $r .= '&innews=0'; 329 } 330 } else { 331 $r .= '&innews=' . implode(',', $this->categories); 332 } 333 if ($_zp_page > 1) { 334 $this->page = $_zp_page; 335 $r .= '&page=' . $_zp_page; 336 } 337 } 338 foreach ($this->extraparams as $p => $v) { 339 $r .= '&' . $p . '=' . $v; 340 } 341 return $r; 342 } 343 344 /** 345 * 346 * Retrieves search extra parameters 347 * @return array 348 */ 349 function getSearchExtra() { 350 return $this->extraparams; 351 } 352 353 /** 354 * 355 * Stores extra search params for plugin use 356 * @param array $extra 357 */ 358 function setSearchExtra($extra) { 359 $this->extraparams = $extra; 360 } 361 362 /** 363 * sets sort directions 364 * 365 * @param bool $val the direction 366 * @param string $what 'images' if you want the image direction, 367 * 'albums' if you want it for the album 368 */ 369 function setSortDirection($val, $what = 'images') { 370 if ($val) { 371 $this->extraparams[$what . 'sortdirection'] = 'DESC'; 372 } else { 373 $this->extraparams[$what . 'sortdirection'] = 'ASC'; 374 } 375 } 376 377 /** 378 * Stores the sort type 379 * 380 * @param string $sorttype the sort type 381 * @param string $what 'images' or 'albums' 382 */ 383 function setSortType($sorttype, $what = 'images') { 384 $this->extraparams[$what . 'sorttype'] = $sorttype; 385 } 386 387 /** 388 * Returns the "searchstring" element of a query parameter set 389 * 390 * @param array $fields the fields required 391 * @param string $param the query parameter (possibly with the intro character 392 * @return string 393 */ 394 function getSearchFieldsText($fields, $param = '&searchfields=') { 395 $default = $this->allowedSearchFields(); 396 $diff = array_diff($default, $fields); 397 if (count($diff) > 0) { 398 foreach ($fields as $field) { 399 $param .= $field . ','; 400 } 401 return substr($param, 0, -1); 402 } 403 return ''; 404 } 405 406 /** 407 * Parses and stores a search string 408 * NOTE!! this function assumes that the 'words' part of the list has been urlencoded!!! 409 * 410 * @param string $paramstr the string containing the search words 411 */ 412 function setSearchParams($paramstr) { 413 $params = explode('&', $paramstr); 414 foreach ($params as $param) { 415 $e = strpos($param, '='); 416 $p = substr($param, 0, $e); 417 $v = substr($param, $e + 1); 418 switch ($p) { 419 case 'words': 420 $this->words = urldecode($v); 421 break; 422 case 'date': 423 $this->dates = $v; 424 break; 425 case 'whichdates': 426 $this->whichdates = $v; 427 break; 428 case 'searchfields': 429 if (is_numeric($v)) { 430 $this->fieldList = $this->numericFields($v); 431 } else { 432 $this->fieldList = array(); 433 $list = explode(',', strtolower($v)); 434 foreach ($this->search_structure as $key => $row) { 435 if (in_array(strtolower($key), $list)) { 436 $this->fieldList[] = $key; 437 } 438 } 439 } 440 break; 441 case 'page': 442 $this->page = $v; 443 break; 444 case 'albumname': 445 $alb = newAlbum($v, true, true); 446 if ($alb->loaded) { 447 $this->album = $alb; 448 $this->dynalbumname = $v; 449 $this->setSortType($this->album->getSortType('album'), 'albums'); 450 $this->setSortDirection($this->album->getSortDirection('album'), 'albums'); 451 $this->setSortType($this->album->getSortType(), 'images'); 452 $this->setSortDirection($this->album->getSortDirection('image'), 'images'); 453 } 454 break; 455 case 'inimages': 456 if (strlen($v) > 0) { 457 switch ($v) { 458 case "0": 459 $this->search_no_images = true; 460 setOption('search_no_images', 1, false); 461 break; 462 case "1": 463 $this->search_no_images = false; 464 setOption('search_no_images', 0, false); 465 break; 466 } 467 } 468 break; 469 case 'inalbums': 470 if (strlen($v) > 0) { 471 switch ($v) { 472 case "0": 473 $this->search_no_albums = true; 474 setOption('search_no_albums', 1, false); 475 break; 476 case "1": 477 $this->search_no_albums = false; 478 setOption('search_no_albums', 0, false); 479 break; 480 default: 481 $this->album_list = explode(',', $v); 482 break; 483 } 484 } 485 break; 486 case 'excludealbums': 487 if (strlen($v) > 0) { 488 $this->album_list_exclude = explode(',', $v); 489 } 490 break; 491 case 'unpublished': 492 $this->search_unpublished = (bool) $v; 493 break; 494 default: 495 $this->extraparams[$p] = $v; 496 break; 497 } 498 } 499 if (!empty($this->words)) { 500 $this->dates = ''; // words and dates are mutually exclusive 501 } 502 } 503 504// call to always return unpublished items 505 function setSearchUnpublished() { 506 $this->search_unpublished = true; 507 } 508 509 /** 510 * Returns the search words variable 511 * 512 * @return string 513 */ 514 function getSearchWords() { 515 return $this->words; 516 } 517 518 /** 519 * Returns the search dates variable 520 * 521 * @return string 522 */ 523 function getSearchDate() { 524 return $this->dates; 525 } 526 527 /** 528 * Returns the search fields variable 529 * 530 * @param bool $array set to true to return the fields as array elements. Otherwise 531 * a comma delimited string is returned 532 * 533 * @return mixed 534 */ 535 function getSearchFields($array = false) { 536 if ($array) 537 return $this->fieldList; 538 return implode(',', $this->fieldList); 539 } 540 541 /** 542 * Parses a search string 543 * Items within quotations are treated as atomic 544 * AND, OR and NOT are converted to &, |, and ! 545 * 546 * Returns an array of search elements 547 * 548 * @return array 549 */ 550 function getSearchString() { 551 if ($this->processed_search) { 552 return $this->processed_search; 553 } 554 $searchstring = trim($this->words); 555 $space_is = getOption('search_space_is'); 556 $opChars = array('&' => 1, '|' => 1, '!' => 1, ',' => 1, '(' => 2); 557 if ($space_is) { 558 $opChars[' '] = 1; 559 } 560 $c1 = ' '; 561 $result = array(); 562 $target = ""; 563 $i = 0; 564 do { 565 $c = substr($searchstring, $i, 1); 566 $op = ''; 567 switch ($c) { 568 case "'": 569 case '"': 570 case '`': 571 $j = strpos(str_replace('\\' . $c, '__', $searchstring), $c, $i + 1); 572 if ($j !== false) { 573 $target .= stripcslashes(substr($searchstring, $i + 1, $j - $i - 1)); 574 $i = $j; 575 } else { 576 $target .= $c; 577 } 578 $c1 = $c; 579 break; 580 case ' ': 581 $j = $i + 1; 582 while ($j < strlen($searchstring) && $searchstring[$j] == ' ') { 583 $j++; 584 } 585 switch ($space_is) { 586 case 'OR': 587 case 'AND': 588 if ($j < strlen($searchstring)) { 589 $c3 = $searchstring[$j]; 590 if (array_key_exists($c3, $opChars) && $opChars[$c3] == 1) { 591 $nextop = $c3 != '!'; 592 } else if (substr($searchstring . ' ', $j, 4) == 'AND ') { 593 $nextop = true; 594 } else if (substr($searchstring . ' ', $j, 3) == 'OR ') { 595 $nextop = true; 596 } else { 597 $nextop = false; 598 } 599 } 600 if (!$nextop) { 601 if (!empty($target)) { 602 $r = trim($target); 603 if (!empty($r)) { 604 $last = $result[] = $r; 605 $target = ''; 606 } 607 } 608 if ($space_is == 'AND') { 609 $c1 = '&'; 610 } else { 611 $c1 = '|'; 612 } 613 $target = ''; 614 $last = $result[] = $c1; 615 } 616 break; 617 default: 618 $c1 = $c; 619 $target .= str_pad('', $j - $i); 620 break; 621 } 622 $i = $j - 1; 623 break; 624 case ',': 625 if (!empty($target)) { 626 $r = trim($target); 627 if (!empty($r)) { 628 switch ($r) { 629 case 'AND': 630 $r = '&'; 631 break; 632 case 'OR': 633 $r = '|'; 634 break; 635 case 'NOT': 636 $r = '!'; 637 break; 638 } 639 $last = $result[] = $r; 640 $target = ''; 641 } 642 } 643 $c2 = substr($searchstring, $i + 1, 1); 644 switch ($c2) { 645 case 'A': 646 if (substr($searchstring . ' ', $i + 1, 4) == 'AND ') 647 $c2 = '&'; 648 break; 649 case 'O': 650 if (substr($searchstring . ' ', $i + 1, 3) == 'OR ') 651 $c2 = '|'; 652 break; 653 case 'N': 654 if (substr($searchstring . ' ', $i + 1, 4) == 'NOT ') 655 $c2 = '!'; 656 break; 657 } 658 if (!((isset($opChars[$c2]) && $opChars[$c2] == 1) || (isset($opChars[$last]) && $opChars[$last] == 1))) { 659 $last = $result[] = '|'; 660 $c1 = $c; 661 } 662 break; 663 case '!': 664 case '&': 665 case '|': 666 case '(': 667 case ')': 668 if (!empty($target)) { 669 $r = trim($target); 670 if (!empty($r)) { 671 $last = $result[] = $r; 672 $target = ''; 673 } 674 } 675 $c1 = $c; 676 $target = ''; 677 $last = $result[] = $c; 678 $j = $i + 1; 679 break; 680 case 'A': 681 if (substr($searchstring . ' ', $i, 4) == 'AND ') { 682 $op = '&'; 683 $skip = 3; 684 } 685 case 'O': 686 if (substr($searchstring . ' ', $i, 3) == 'OR ') { 687 $op = '|'; 688 $skip = 2; 689 } 690 case 'N': 691 if (substr($searchstring . ' ', $i, 4) == 'NOT ') { 692 $op = '!'; 693 $skip = 3; 694 } 695 if ($op) { 696 if (!empty($target)) { 697 $r = trim($target); 698 if (!empty($r)) { 699 $last = $result[] = $r; 700 $target = ''; 701 } 702 } 703 $c1 = $op; 704 $target = ''; 705 $last = $result[] = $op; 706 $j = $i + $skip; 707 while ($j < strlen($searchstring) && substr($searchstring, $j, 1) == ' ') { 708 $j++; 709 } 710 $i = $j - 1; 711 } else { 712 $c1 = $c; 713 $target .= $c; 714 } 715 break; 716 717 default: 718 $c1 = $c; 719 $target .= $c; 720 break; 721 } 722 } while ($i++ < strlen($searchstring)); 723 if (!empty($target)) { 724 $last = $result[] = trim($target); 725 } 726 $lasttoken = ''; 727 foreach ($result as $key => $token) { 728 if ($token == '|' && $lasttoken == '|') { // remove redundant OR ops 729 unset($result[$key]); 730 } 731 $lasttoken = $token; 732 } 733 if (array_key_exists($lasttoken, $opChars) && $opChars[$lasttoken] == 1) { 734 array_pop($result); 735 } 736 737 $this->processed_search = zp_apply_filter('search_criteria', $result); 738 return $this->processed_search; 739 } 740 741 /** 742 * recodes the search words replacing the boolean operators with text versions 743 * 744 * @param string $quote how to represent quoted strings 745 * 746 * @return string 747 * 748 */ 749 function codifySearchString() { 750 $searchstring = $this->getSearchString(); 751 $sanitizedwords = ''; 752 if (is_array($searchstring)) { 753 foreach ($searchstring as $singlesearchstring) { 754 switch ($singlesearchstring) { 755 case '&': 756 $sanitizedwords .= " AND "; 757 break; 758 case '|': 759 $sanitizedwords .= " OR "; 760 break; 761 case '!': 762 $sanitizedwords .= " NOT "; 763 break; 764 case '(': 765 case ')': 766 $sanitizedwords .= $singlesearchstring; 767 break; 768 default: 769 $sanitizedwords .= search_quote(sanitize($singlesearchstring, 3)); 770 break; 771 } 772 } 773 } 774 775 $sanitizedwords = trim(str_replace(array(' ', ' ',), ' ', $sanitizedwords)); 776 $sanitizedwords = trim(str_replace('( ', '(', $sanitizedwords)); 777 $sanitizedwords = trim(str_replace(' )', ')', $sanitizedwords)); 778 return $sanitizedwords; 779 } 780 781 /** 782 * Returns the number of albums found in a search 783 * 784 * @return int 785 */ 786 function getNumAlbums() { 787 if (is_null($this->albums)) { 788 $this->getAlbums(0, NULL, NULL, false); 789 } 790 return count($this->albums); 791 } 792 793 /** 794 * Returns the set of fields from the url query/post 795 * @return int 796 * @since 1.1.3 797 */ 798 function parseQueryFields() { 799 $fields = array(); 800 if (isset($_REQUEST['searchfields'])) { 801 $fs = sanitize($_REQUEST['searchfields']); 802 if (is_numeric($fs)) { 803 $fields = array_flip($this->numericFields($fs)); 804 } else { 805 $fields = explode(',', $fs); 806 } 807 } else { 808 foreach ($_REQUEST as $key => $value) { 809 if (strpos($key, 'SEARCH_') !== false) { 810 $fields[substr($key, 7)] = $value; 811 } 812 } 813 } 814 return $fields; 815 } 816 817 /** 818 * 819 * Returns an array of News article IDs belonging to the search categories 820 */ 821 protected function subsetNewsCategories() { 822 global $_zp_zenpage; 823 if (!is_array($this->category_list)) 824 return false; 825 $cat = ''; 826 $list = $_zp_zenpage->getAllCategories(); 827 if (!empty($list)) { 828 foreach ($list as $category) { 829 if (in_array($category['title'], $this->category_list)) { 830 $catobj = new ZenpageCategory($category['titlelink']); 831 $cat .= ' `cat_id`=' . $catobj->getID() . ' OR'; 832 $subcats = $catobj->getCategories(); 833 if ($subcats) { 834 foreach ($subcats as $subcat) { 835 $catobj = new ZenpageCategory($subcat); 836 $cat .= ' `cat_id`=' . $catobj->getID() . ' OR'; 837 } 838 } 839 } 840 } 841 if ($cat) { 842 $cat = ' WHERE ' . substr($cat, 0, -3); 843 } 844 } 845 $sql = 'SELECT DISTINCT `news_id` FROM ' . prefix('news2cat') . $cat; 846 $result = query($sql); 847 $list = array(); 848 if ($result) { 849 while ($row = db_fetch_assoc($result)) { 850 $list[] = $row['news_id']; 851 } 852 db_free_result($result); 853 } 854 return $list; 855 } 856 857 /** 858 * Takes a list of IDs and makes a where clause 859 * 860 * @param array $idlist list of IDs for a where clause 861 */ 862 protected static function compressedIDList($idlist) { 863 $idlist = array_unique($idlist); 864 asort($idlist); 865 return '`id` IN (' . implode(',', $idlist) . ')'; 866 } 867 868 /** 869 * get connical sort key and direction parameters. 870 * @param type $sorttype sort field desired 871 * @param type $sortdirection DESC or ASC 872 * @param type $defaulttype if no sort type otherwise selected use this one 873 * @param type $table the database table being searched 874 * @return array 875 */ 876 protected function sortKey($sorttype, $sortdirection, $defaulttype, $table) { 877 if (is_null($sorttype)) { 878 if (array_key_exists($table . 'sorttype', $this->extraparams)) { 879 $sorttype = $this->extraparams[$table . 'sorttype']; 880 } else if (array_key_exists('sorttype', $this->extraparams)) { 881 $sorttype = $this->extraparams['sorttype']; 882 } 883 } 884 $sorttype = lookupSortKey($sorttype, $defaulttype, $table); 885 if (is_null($sortdirection)) { 886 if (array_key_exists($table . 'sortdirection', $this->extraparams)) { 887 $sortdirection = $this->extraparams[$table . 'sortdirection']; 888 } else if (array_key_exists('sortdirection', $this->extraparams)) { 889 $sortdirection = $this->extraparams['sortdirection']; 890 } 891 } 892 return array($sorttype, $sortdirection); 893 } 894 895 /** 896 * returns the results of a date search 897 * @param string $searchstring the search target 898 * @param string $searchdate the date target 899 * @param string $tbl the database table to search 900 * @param string $sorttype what to sort on 901 * @param string $sortdirection what direction 902 * @return string 903 * @since 1.1.3 904 */ 905 function searchDate($searchstring, $searchdate, $tbl, $sorttype, $sortdirection, $whichdate = 'date') { 906 global $_zp_current_album, $_zp_gallery; 907 $sql = 'SELECT DISTINCT `id`, `show`,`title`'; 908 switch ($tbl) { 909 case 'pages': 910 case 'news': 911 $sql .= ',`titlelink` '; 912 break; 913 case 'albums': 914 $sql .= ",`desc`,`folder` "; 915 break; 916 default: 917 $sql .= ",`desc`,`albumid`,`filename`,`location`,`city`,`state`,`country` "; 918 break; 919 } 920 $sql .= "FROM " . prefix($tbl) . " WHERE "; 921 if (!zp_loggedin()) { 922 $sql .= "`show` = 1 AND ("; 923 } 924 925 if (!empty($searchdate)) { 926 if ($searchdate == "0000-00") { 927 $sql .= "`$whichdate`=\"0000-00-00 00:00:00\""; 928 } else { 929 $datesize = sizeof(explode('-', $searchdate)); 930// search by day 931 if ($datesize == 3) { 932 $d1 = $searchdate . " 00:00:00"; 933 $d2 = $searchdate . " 23:59:59"; 934 $sql .= "`$whichdate` >= \"$d1\" AND `$whichdate` < \"$d2\""; 935 } 936// search by month 937 else if ($datesize == 2) { 938 $d1 = $searchdate . "-01 00:00:00"; 939 $d = strtotime($d1); 940 $d = strtotime('+ 1 month', $d); 941 $d2 = substr(date('Y-m-d H:m:s', $d), 0, 7) . "-01 00:00:00"; 942 $sql .= "`$whichdate` >= \"$d1\" AND `$whichdate` < \"$d2\""; 943 } else { 944 $sql .= "`$whichdate`<\"0000-00-00 00:00:00\""; 945 } 946 } 947 } 948 if (!zp_loggedin()) { 949 $sql .= ")"; 950 } 951 952 switch ($tbl) { 953 case 'news': 954 case 'pages': 955 if (empty($sorttype)) { 956 $key = '`date` DESC'; 957 } else { 958 $key = trim($sorttype . ' ' . $sortdirection); 959 } 960 break; 961 case 'albums': 962 if (is_null($sorttype)) { 963 if (empty($this->album)) { 964 list($key, $sortdirection) = $this->sortKey($_zp_gallery->getSortType(), $sortdirection, 'title', 'albums'); 965 if ($key != '`sort_order`') { 966 if ($_zp_gallery->getSortDirection()) { 967 $key .= " DESC"; 968 } 969 } 970 } else { 971 $key = $this->album->getAlbumSortKey(); 972 if ($key != '`sort_order`' && $key != 'RAND()') { 973 if ($this->album->getSortDirection('album')) { 974 $key .= " DESC"; 975 } 976 } 977 } 978 } else { 979 list($key, $sortdirection) = $this->sortKey($sorttype, $sortdirection, 'title', 'albums'); 980 $key = trim($key . ' ' . $sortdirection); 981 } 982 break; 983 default: 984 $hidealbums = getNotViewableAlbums(); 985 if (!is_null($hidealbums)) { 986 foreach ($hidealbums as $id) { 987 $sql .= ' AND `albumid`!=' . $id; 988 } 989 } 990 if (is_null($sorttype)) { 991 if (empty($this->album)) { 992 list($key, $sortdirection) = $this->sortKey(IMAGE_SORT_TYPE, $sortdirection, 'title', 'images'); 993 if ($key != '`sort_order`') { 994 if (IMAGE_SORT_DIRECTION) { 995 $key .= " DESC"; 996 } 997 } 998 } else { 999 $key = $thie->album->getImageSortKey(); 1000 if ($key != '`sort_order`' && $key != 'RAND()') { 1001 if ($this->album->getSortDirection('image')) { 1002 $key .= " DESC"; 1003 } 1004 } 1005 } 1006 } else { 1007 list($key, $sortdirection) = $this->sortKey($sorttype, $sortdirection, 'title', 'images'); 1008 $key = trim($key . ' ' . $sortdirection); 1009 } 1010 break; 1011 } 1012 $sql .= " ORDER BY " . $key; 1013 return $sql; 1014 } 1015 1016 /** 1017 * Searches the table for tags 1018 * Returns an array of database records. 1019 * 1020 * @param array $searchstring 1021 * @param string $tbl set DB table name to be searched 1022 * @param string $sorttype what to sort on 1023 * @param string $sortdirection what direction 1024 * @return array 1025 */ 1026 protected function searchFieldsAndTags($searchstring, $tbl, $sorttype, $sortdirection) { 1027 global $_zp_gallery; 1028 $weights = $idlist = array(); 1029 $sql = $allIDs = NULL; 1030 $tagPattern = $this->tagPattern; 1031// create an array of [tag, objectid] pairs for tags 1032 $tag_objects = array(); 1033 $fields = $this->fieldList; 1034 if (count($fields) == 0) { // then use the default ones 1035 $fields = $this->allowedSearchFields(); 1036 } 1037 foreach ($fields as $key => $field) { 1038 switch ($field) { 1039 case 'news_categories': 1040 if ($tbl != 'news') { 1041 break; 1042 } 1043 unset($fields[$key]); 1044 query('SET @serachfield="news_categories"'); 1045 $tagsql = 'SELECT @serachfield AS field, t.`title` AS name, o.`news_id` AS `objectid` FROM ' . prefix('news_categories') . ' AS t, ' . prefix('news2cat') . ' AS o WHERE t.`id`=o.`cat_id` AND ('; 1046 foreach ($searchstring as $singlesearchstring) { 1047 switch ($singlesearchstring) { 1048 case '&': 1049 case '!': 1050 case '|': 1051 case '(': 1052 case ')': 1053 break; 1054 case '*': 1055 $targetfound = true; 1056 $tagsql .= "COALESCE(title, '') != '' OR "; 1057 break; 1058 default: 1059 $targetfound = true; 1060 $tagsql .= '`title` = ' . db_quote($singlesearchstring) . ' OR '; 1061 } 1062 } 1063 $tagsql = substr($tagsql, 0, strlen($tagsql) - 4) . ') ORDER BY t.`id`'; 1064 $objects = query_full_array($tagsql, false); 1065 if (is_array($objects)) { 1066 $tag_objects = $objects; 1067 } 1068 break; 1069 case 'tags_exact': 1070 $tagPattern = array('type' => '=', 'open' => '', 'close' => ''); 1071 case 'tags': 1072 unset($fields[$key]); 1073 query('SET @serachfield="tags"'); 1074 $tagsql = 'SELECT @serachfield AS field, t.`name`, o.`objectid` FROM ' . prefix('tags') . ' AS t, ' . prefix('obj_to_tag') . ' AS o WHERE t.`id`=o.`tagid` AND o.`type`="' . $tbl . '" AND ('; 1075 foreach ($searchstring as $singlesearchstring) { 1076 switch ($singlesearchstring) { 1077 case '&': 1078 case '!': 1079 case '|': 1080 case '(': 1081 case ')': 1082 break; 1083 case '*': 1084 query('SET @emptyfield="*"'); 1085 $tagsql = str_replace('t.`name`', '@emptyfield as name', $tagsql); 1086 $tagsql .= "t.`name` IS NOT NULL OR "; 1087 break; 1088 default: 1089 $targetfound = true; 1090 if ($tagPattern['type'] == 'like') { 1091 $target = db_LIKE_escape($singlesearchstring); 1092 } else { 1093 $target = $singlesearchstring; 1094 } 1095 $tagsql .= 't.`name` ' . strtoupper($tagPattern['type']) . ' ' . db_quote($tagPattern['open'] . $target . $tagPattern['close']) . ' OR '; 1096 } 1097 } 1098 $tagsql = substr($tagsql, 0, strlen($tagsql) - 4) . ') ORDER BY t.`id`'; 1099 $objects = query_full_array($tagsql, false); 1100 if (is_array($objects)) { 1101 $tag_objects = array_merge($tag_objects, $objects); 1102 } 1103 break; 1104 default: 1105 break; 1106 } 1107 } 1108 1109 1110// create an array of [name, objectid] pairs for the search fields. 1111 $field_objects = array(); 1112 if (count($fields) > 0) { 1113 $columns = array(); 1114 $dbfields = db_list_fields($tbl); 1115 if (is_array($dbfields)) { 1116 foreach ($dbfields as $row) { 1117 $columns[] = strtolower($row['Field']); 1118 } 1119 } 1120 foreach ($searchstring as $singlesearchstring) { 1121 switch ($singlesearchstring) { 1122 case '!': 1123 case '&': 1124 case '|': 1125 case '(': 1126 case ')': 1127 break; 1128 default: 1129 $targetfound = true; 1130 query('SET @serachtarget=' . db_quote($singlesearchstring)); 1131 foreach ($fields as $fieldname) { 1132 if ($tbl == 'albums' && strtolower($fieldname) == 'filename') { 1133 $fieldname = 'folder'; 1134 } else { 1135 $fieldname = strtolower($fieldname); 1136 } 1137 if ($fieldname && in_array($fieldname, $columns)) { 1138 query('SET @serachfield=' . db_quote($fieldname)); 1139 switch ($singlesearchstring) { 1140 case '*': 1141 $sql = 'SELECT @serachtarget AS name, @serachfield AS field, `id` AS `objectid` FROM ' . prefix($tbl) . ' WHERE (' . "COALESCE(`$fieldname`, '') != ''" . ') ORDER BY `id`'; 1142 break; 1143 default: 1144 if ($this->pattern['type'] == 'like') { 1145 $target = db_LIKE_escape($singlesearchstring); 1146 } else { 1147 $target = $singlesearchstring; 1148 } 1149 $fieldsql = ' `' . $fieldname . '` ' . strtoupper($this->pattern['type']) . ' ' . db_quote($this->pattern['open'] . $target . $this->pattern['close']); 1150 $sql = 'SELECT @serachtarget AS name, @serachfield AS field, `id` AS `objectid` FROM ' . prefix($tbl) . ' WHERE (' . $fieldsql . ') ORDER BY `id`'; 1151 } 1152 $objects = query_full_array($sql, false); 1153 if (is_array($objects)) { 1154 $field_objects = array_merge($field_objects, $objects); 1155 } 1156 } 1157 } 1158 } 1159 } 1160 } 1161 1162// now do the boolean logic of the search string 1163 $exact = $tagPattern['type'] == '='; 1164 $objects = array_merge($tag_objects, $field_objects); 1165 if (count($objects) != 0) { 1166 $tagid = ''; 1167 $taglist = array(); 1168 1169 foreach ($objects as $object) { 1170 $tagid = strtolower($object['name']); 1171 if (!isset($taglist[$tagid]) || !is_array($taglist[$tagid])) { 1172 $taglist[$tagid] = array(); 1173 } 1174 $taglist[$tagid][] = $object['objectid']; 1175 } 1176 $op = ''; 1177 $idstack = array(); 1178 $opstack = array(); 1179 while (count($searchstring) > 0) { 1180 $singlesearchstring = array_shift($searchstring); 1181 switch ($singlesearchstring) { 1182 case '&': 1183 case '!': 1184 case '|': 1185 $op = $op . $singlesearchstring; 1186 break; 1187 case '(': 1188 array_push($idstack, $idlist); 1189 array_push($opstack, $op); 1190 $idlist = array(); 1191 $op = ''; 1192 break; 1193 case ')': 1194 $objectid = $idlist; 1195 $idlist = array_pop($idstack); 1196 $op = array_pop($opstack); 1197 switch ($op) { 1198 case '&': 1199 if (is_array($objectid)) { 1200 $idlist = array_intersect($idlist, $objectid); 1201 } else { 1202 $idlist = array(); 1203 } 1204 break; 1205 case '!': 1206 break; // Paren followed by NOT is nonsensical? 1207 case '&!': 1208 if (is_array($objectid)) { 1209 $idlist = array_diff($idlist, $objectid); 1210 } 1211 break; 1212 case ''; 1213 case '|': 1214 if (is_array($objectid)) { 1215 $idlist = array_merge($idlist, $objectid); 1216 } 1217 break; 1218 } 1219 $op = ''; 1220 break; 1221 default: 1222 $lookfor = strtolower($singlesearchstring); 1223 $objectid = NULL; 1224 foreach ($taglist as $key => $objlist) { 1225 if (($exact && $lookfor == $key) || (!$exact && preg_match('|' . preg_quote($lookfor) . '|', $key))) { 1226 if (is_array($objectid)) { 1227 $objectid = array_merge($objectid, $objlist); 1228 } else { 1229 $objectid = $objlist; 1230 } 1231 } 1232 } 1233 switch ($op) { 1234 case '&': 1235 if (is_array($objectid)) { 1236 $idlist = array_intersect($idlist, $objectid); 1237 } else { 1238 $idlist = array(); 1239 } 1240 break; 1241 case '!': 1242 if (is_null($allIDs)) { 1243 $allIDs = array(); 1244 $result = query("SELECT `id` FROM " . prefix($tbl)); 1245 if ($result) { 1246 while ($row = db_fetch_assoc($result)) { 1247 $allIDs[] = $row['id']; 1248 } 1249 db_free_result($result); 1250 } 1251 } 1252 if (is_array($objectid)) { 1253 $idlist = array_merge($idlist, array_diff($allIDs, $objectid)); 1254 } 1255 break; 1256 case '&!': 1257 if (is_array($objectid)) { 1258 $idlist = array_diff($idlist, $objectid); 1259 } 1260 break; 1261 case ''; 1262 case '|': 1263 if (is_array($objectid)) { 1264 $idlist = array_merge($idlist, $objectid); 1265 } 1266 break; 1267 } 1268 $op = ''; 1269 break; 1270 } 1271 } 1272 } 1273 1274// we now have an id list of the items that were found and will create the SQL Search to retrieve their records 1275 if (count($idlist) > 0) { 1276 $weights = array_count_values($idlist); 1277 arsort($weights, SORT_NUMERIC); 1278 $sql = 'SELECT DISTINCT `id`,`show`,'; 1279 1280 switch ($tbl) { 1281 case 'news': 1282 if ($this->search_unpublished || zp_loggedin(MANAGE_ALL_NEWS_RIGHTS)) { 1283 $show = ''; 1284 } else { 1285 $show = "`show` = 1 AND "; 1286 } 1287 $sql .= '`title`, `titlelink` '; 1288 if (is_array($this->category_list)) { 1289 $news_list = $this->subsetNewsCategories(); 1290 $idlist = array_intersect($news_list, $idlist); 1291 if (count($idlist) == 0) { 1292 return array(false, array()); 1293 } 1294 } 1295 if (empty($sorttype)) { 1296 $key = '`date` DESC'; 1297 } else { 1298 $key = trim($sorttype . $sortdirection); 1299 } 1300 if ($show) { 1301 $show .= '`date`<=' . db_quote(date('Y-m-d H:i:s')) . ' AND '; 1302 } 1303 break; 1304 case 'pages': 1305 if (zp_loggedin(MANAGE_ALL_PAGES_RIGHTS)) { 1306 $show = ''; 1307 } else { 1308 $show = "`show` = 1 AND "; 1309 } 1310 $sql .= '`title`, `titlelink` '; 1311 if (empty($sorttype)) { 1312 $key = '`date` DESC'; 1313 } else { 1314 $key = trim($sorttype . $sortdirection); 1315 } 1316 if ($show) { 1317 $show .= '`date`<=' . db_quote(date('Y-m-d H:i:s')) . ' AND '; 1318 } 1319 break; 1320 case 'albums': 1321 if ($this->search_unpublished || zp_loggedin()) { 1322 $show = ''; 1323 } else { 1324 $show = "`show` = 1 AND "; 1325 } 1326 $sql .= "`folder`, `title` "; 1327 if (is_null($sorttype)) { 1328 if (empty($this->album)) { 1329 list($key, $sortdirection) = $this->sortKey($_zp_gallery->getSortType(), $sortdirection, 'title', 'albums'); 1330 if ($_zp_gallery->getSortDirection()) { 1331 $key .= " DESC"; 1332 } 1333 } else { 1334 $key = $this->album->getAlbumSortKey(); 1335 if ($key != '`sort_order`' && $key != 'RAND()') { 1336 if ($this->album->getSortDirection('album')) { 1337 $key .= " DESC"; 1338 } 1339 } 1340 } 1341 } else { 1342 list($key, $sortdirection) = $this->sortKey($sorttype, $sortdirection, 'title', 'albums'); 1343 $key = trim($key . ' ' . $sortdirection); 1344 } 1345 break; 1346 default: // images 1347 if ($this->search_unpublished || zp_loggedin()) { 1348 $show = ''; 1349 } else { 1350 $show = "`show` = 1 AND "; 1351 } 1352 $sql .= "`albumid`, `filename`, `title` "; 1353 if (is_null($sorttype)) { 1354 if (empty($this->album)) { 1355 list($key, $sortdirection) = $this->sortKey($sorttype, $sortdirection, 'title', 'images'); 1356 if ($sortdirection) { 1357 $key .= " DESC"; 1358 } 1359 } else { 1360 $key = $this->album->getImageSortKey(); 1361 if ($key != '`sort_order`') { 1362 if ($this->album->getSortDirection('image')) { 1363 $key .= " DESC"; 1364 } 1365 } 1366 } 1367 } else { 1368 list($key, $sortdirection) = $this->sortKey($sorttype, $sortdirection, 'title', 'images'); 1369 $key = trim($key . ' ' . $sortdirection); 1370 } 1371 break; 1372 } 1373 $sql .= "FROM " . prefix($tbl) . " WHERE " . $show; 1374 $sql .= '(' . self::compressedIDList($idlist) . ')'; 1375 $sql .= " ORDER BY " . $key; 1376 return array($sql, $weights); 1377 } 1378 return array(false, array()); 1379 } 1380 1381 /** 1382 * Returns an array of albums found in the search 1383 * @param string $sorttype what to sort on 1384 * @param string $sortdirection what direction 1385 * @param bool $mine set true/false to override ownership 1386 * 1387 * @return array 1388 */ 1389 private function getSearchAlbums($sorttype, $sortdirection, $mine = NULL) { 1390 if (getOption('search_no_albums') || $this->search_no_albums) { 1391 return array(); 1392 } 1393 list($sorttype, $sortdirection) = $this->sortKey($sorttype, $sortdirection, 'title', 'albums'); 1394 $albums = array(); 1395 $searchstring = $this->getSearchString(); 1396 if (empty($searchstring)) { 1397 return array(); 1398 } // nothing to find 1399 $criteria = $this->getCacheTag('albums', serialize($searchstring), $sorttype . ' ' . $sortdirection . ' '. $mine); 1400 if ($this->albums && $criteria == $this->searches['albums']) { 1401 return $this->albums; 1402 } 1403 $albums = $this->getCachedSearch($criteria); 1404 if (is_null($albums)) { 1405 if (is_null($mine) && zp_loggedin(MANAGE_ALL_ALBUM_RIGHTS)) { 1406 $mine = true; 1407 } 1408 $result = $albums = array(); 1409 list ($search_query, $weights) = $this->searchFieldsAndTags($searchstring, 'albums', $sorttype, $sortdirection); 1410 if (!empty($search_query)) { 1411 $search_result = query($search_query); 1412 if ($search_result) { 1413 while ($row = db_fetch_assoc($search_result)) { 1414 $albumname = $row['folder']; 1415 if ($albumname != $this->dynalbumname) { 1416 if (file_exists(ALBUM_FOLDER_SERVERPATH . internalToFilesystem($albumname))) { 1417 $album = newAlbum($albumname); 1418 $uralbum = getUrAlbum($album); 1419 $viewUnpublished = ($this->search_unpublished || zp_loggedin() && $uralbum->albumSubRights() & (MANAGED_OBJECT_RIGHTS_EDIT | MANAGED_OBJECT_RIGHTS_VIEW)); 1420 switch (themeObject::checkScheduledPublishing($row)) { 1421 case 1: 1422 $album->setShow(0); 1423 $album->save(); 1424 case 2: 1425 $row['show'] = 0; 1426 } 1427 if ($mine || (is_null($mine) && $album->isMyItem(LIST_RIGHTS)) || (checkAlbumPassword($albumname) && (($album->checkAccess() && $album->isPublic()) || $viewUnpublished))) { 1428 if ((empty($this->album_list) || in_array($albumname, $this->album_list)) && !$this->excludeAlbum($albumname)) { 1429 $result[] = array('title' => $row['title'], 'name' => $albumname, 'weight' => $weights[$row['id']]); 1430 } 1431 } 1432 } 1433 } 1434 } 1435 db_free_result($search_result); 1436 $sortdir = self::getSortdirBool($sortdirection); 1437 if (is_null($sorttype)) { 1438 $result = sortMultiArray($result, 'weight', $sortdir, true, false, false, array('weight')); 1439 } 1440 if ($sorttype == '`title`') { 1441 $result = sortByMultilingual($result, 'title', $sortdir); 1442 } 1443 foreach ($result as $album) { 1444 $albums[] = $album['name']; 1445 } 1446 } 1447 } 1448 zp_apply_filter('search_statistics', $searchstring, 'albums', !empty($albums), $this->dynalbumname, $this->iteration++); 1449 $this->cacheSearch($criteria, $albums); 1450 } 1451 $this->albums = $albums; 1452 $this->searches['albums'] = $criteria; 1453 return $albums; 1454 } 1455 1456 /** 1457 * Returns an array of album names found in the search. 1458 * If $page is not zero, it returns the current page's albums 1459 * 1460 * @param int $page the page number we are on 1461 * @param string $sorttype what to sort on 1462 * @param string $sortdirection what direction 1463 * @param bool $care set to false if the order of the albums does not matter 1464 * @param bool $mine set true/false to override ownership 1465 * 1466 * @return array 1467 */ 1468 function getAlbums($page = 0, $sorttype = NULL, $sortdirection = NULL, $care = true, $mine = NULL) { 1469 $this->albums = $this->getSearchAlbums($sorttype, $sortdirection, $mine); 1470 if ($page == 0) { 1471 return $this->albums; 1472 } else { 1473 $albums_per_page = max(1, getOption('albums_per_page')); 1474 return array_slice($this->albums, $albums_per_page * ($page - 1), $albums_per_page); 1475 } 1476 } 1477 1478 /** 1479 * Checks if the album should be excluded from results 1480 * Subalbums and their contents inherit the exclusion. 1481 * 1482 * @since ZenphotoCMS 1.5.8 1483 * 1484 * @param string $albumname 1485 * @return boolean 1486 */ 1487 function excludeAlbum($albumname) { 1488 $exclude = false; 1489 if (!is_null($this->album_list_exclude)) { 1490 if (in_array($albumname, $this->album_list_exclude)) { 1491 return true; 1492 } else { 1493 foreach ($this->album_list_exclude as $excludealbum) { 1494 if (strpos($albumname, $excludealbum) === 0) { 1495 return true; 1496 } 1497 } 1498 } 1499 } 1500 return $exclude; 1501 } 1502 1503 /** 1504 * Returns the index of the album within the search albums 1505 * 1506 * @param string $curalbum The album sought 1507 * @return int 1508 */ 1509 function getAlbumIndex($curalbum) { 1510 $albums = $this->getAlbums(0); 1511 return array_search($curalbum, $albums); 1512 } 1513 1514 /** 1515 * Returns the album following the current one 1516 * 1517 * @param string $curalbum the name of the current album 1518 * @return object 1519 */ 1520 function getNextAlbum($curalbum) { 1521 global $_zp_gallery; 1522 $albums = $this->getAlbums(0); 1523 $inx = array_search($curalbum, $albums) + 1; 1524 if ($inx >= 0 && $inx < count($albums)) { 1525 return newAlbum($albums[$inx]); 1526 } 1527 return null; 1528 } 1529 1530 /** 1531 * Returns the album preceding the current one 1532 * 1533 * @param string $curalbum the name of the current album 1534 * @return object 1535 */ 1536 function getPrevAlbum($curalbum) { 1537 global $_zp_gallery; 1538 $albums = $this->getAlbums(0); 1539 $inx = array_search($curalbum, $albums) - 1; 1540 if ($inx >= 0 && $inx < count($albums)) { 1541 return newAlbum($albums[$inx]); 1542 } 1543 return null; 1544 } 1545 1546 /** 1547 * Returns the number of images found in the search 1548 * 1549 * @return int 1550 */ 1551 function getNumImages() { 1552 if (is_null($this->images)) { 1553 $this->getImages(0); 1554 } 1555 return count($this->images); 1556 } 1557 1558 /** 1559 * Returns an array of image names found in the search 1560 * 1561 * @param string $sorttype what to sort on 1562 * @param string $sortdirection what direction 1563 * @param bool $mine set true/false to overried ownership 1564 * @return array 1565 */ 1566 private function getSearchImages($sorttype, $sortdirection, $mine = NULL) { 1567 if (getOption('search_no_images') || $this->search_no_images) { 1568 return array(); 1569 } 1570 list($sorttype, $sortdirection) = $this->sortKey($sorttype, $sortdirection, 'title', 'images'); 1571 if (is_null($mine) && zp_loggedin(MANAGE_ALL_ALBUM_RIGHTS)) { 1572 $mine = true; 1573 } 1574 $searchstring = $this->getSearchString(); 1575 $searchdate = $this->dates; 1576 if (empty($searchstring) && empty($searchdate)) { 1577 return array(); 1578 } // nothing to find 1579 $criteria = $this->getCacheTag('images', serialize($searchstring) . ' ' . $searchdate, $sorttype . ' ' . $sortdirection . ' '.$mine); 1580 if ($criteria == $this->searches['images']) { 1581 return $this->images; 1582 } 1583 $images = $this->getCachedSearch($criteria); 1584 if (is_null($images)) { 1585 if (empty($searchdate)) { 1586 list ($search_query, $weights) = $this->searchFieldsAndTags($searchstring, 'images', $sorttype, $sortdirection); 1587 } else { 1588 $search_query = $this->searchDate($searchstring, $searchdate, 'images', $sorttype, $sortdirection); 1589 } 1590 if (empty($search_query)) { 1591 $search_result = false; 1592 } else { 1593 $search_result = query($search_query); 1594 } 1595 $albums_seen = $images = array(); 1596 if ($search_result) { 1597 while ($row = db_fetch_assoc($search_result)) { 1598 $albumid = $row['albumid']; 1599 if (array_key_exists($albumid, $albums_seen)) { 1600 $albumrow = $albums_seen[$albumid]; 1601 } else { 1602 $query = "SELECT folder, `show` FROM " . prefix('albums') . " WHERE id = $albumid"; 1603 $row2 = query_single_row($query); // id is unique 1604 if ($row2) { 1605 $albumname = $row2['folder']; 1606 $allow = false; 1607 $album = newAlbum($albumname); 1608 $uralbum = getUrAlbum($album); 1609 $viewUnpublished = ($this->search_unpublished || zp_loggedin() && $uralbum->albumSubRights() & (MANAGED_OBJECT_RIGHTS_EDIT | MANAGED_OBJECT_RIGHTS_VIEW)); 1610 switch (themeObject::checkScheduledPublishing($row)) { 1611 case 1: 1612 $imageobj = newImage($album, $row['filename']); 1613 $imageobj->setShow(0); 1614 $imageobj->save(); 1615 case 2: 1616 $row['show'] = 0; 1617 break; 1618 } 1619 $viewUnpublished = ($mine || is_null($mine)) && ($album->isMyItem(LIST_RIGHTS) || checkAlbumPassword($albumname) && ($album->isPublic() || $viewUnpublished)); 1620 if ($viewUnpublished) { 1621 $allow = (empty($this->album_list) || in_array($albumname, $this->album_list)) && !$this->excludeAlbum($albumname); 1622 } 1623 $albums_seen[$albumid] = $albumrow = array('allow' => $allow, 'viewUnpublished' => $viewUnpublished, 'folder' => $albumname, 'localpath' => ALBUM_FOLDER_SERVERPATH . internalToFilesystem($albumname) . '/'); 1624 } else { 1625 $albums_seen[$albumid] = $albumrow = array('allow' => false, 'viewUnpublished' => false, 'folder' => '', 'localpath' => ''); 1626 } 1627 } 1628 if ($albumrow['allow'] && ($row['show'] || $albumrow['viewUnpublished'])) { 1629 if (file_exists($albumrow['localpath'] . internalToFilesystem($row['filename']))) { // still exists 1630 $data = array('title' => $row['title'], 'filename' => $row['filename'], 'folder' => $albumrow['folder']); 1631 if (isset($weights)) { 1632 $data['weight'] = $weights[$row['id']]; 1633 } 1634 $images[] = $data; 1635 } 1636 } 1637 } 1638 db_free_result($search_result); 1639 $sortdir = self::getSortdirBool($sortdirection); 1640 if (is_null($sorttype) && isset($weights)) { 1641 $images = sortMultiArray($images, 'weight', $sortdir, true, false, false, array('weight')); 1642 } 1643 if ($sorttype == '`title`') { 1644 $images = sortByMultilingual($images, 'title', $sortdir); 1645 } 1646 } 1647 if (empty($searchdate)) { 1648 zp_apply_filter('search_statistics', $searchstring, 'images', !empty($images), $this->dynalbumname, $this->iteration++); 1649 } 1650 $this->cacheSearch($criteria, $images); 1651 } 1652 $this->searches['images'] = $criteria; 1653 return $images; 1654 } 1655 1656 /** 1657 * Returns an array of images found in the search 1658 * It will return a "page's worth" if $page is non zero 1659 * 1660 * @param int $page the page number desired 1661 * @param int $firstPageCount count of images that go on the album/image transition page 1662 * @param string $sorttype what to sort on 1663 * @param string $sortdirection what direction 1664 * @param bool $care placeholder to make the getImages methods all the same. 1665 * @param bool $mine set true/false to overried ownership 1666 * @return array 1667 */ 1668 function getImages($page = 0, $firstPageCount = 0, $sorttype = NULL, $sortdirection = NULL, $care = true, $mine = NULL) { 1669 $this->images = $this->getSearchImages($sorttype, $sortdirection, $mine); 1670 if ($page == 0) { 1671 return $this->images; 1672 } else { 1673 if (empty($this->images)) { 1674 return array(); 1675 } 1676 // Only return $firstPageCount images if we are on the first page and $firstPageCount > 0 1677 if (($page == 1) && ($firstPageCount > 0)) { 1678 $pageStart = 0; 1679 $images_per_page = $firstPageCount; 1680 } else { 1681 if ($firstPageCount > 0) { 1682 $fetchPage = $page - 2; 1683 } else { 1684 $fetchPage = $page - 1; 1685 } 1686 $images_per_page = max(1, getOption('images_per_page')); 1687 $pageStart = $firstPageCount + $images_per_page * $fetchPage; 1688 } 1689 $slice = array_slice($this->images, $pageStart, $images_per_page); 1690 return $slice; 1691 } 1692 } 1693 1694 /** 1695 * Returns the index of this image in the search images 1696 * 1697 * @param string $album The folder name of the image 1698 * @param string $filename the filename of the image 1699 * @return int 1700 */ 1701 function getImageIndex($album, $filename) { 1702 $images = $this->getImages(); 1703 $c = 0; 1704 foreach ($images as $image) { 1705 if (($album == $image['folder']) && ($filename == $image['filename'])) { 1706 return $c; 1707 } 1708 $c++; 1709 } 1710 return false; 1711 } 1712 1713 /** 1714 * Returns a specific image 1715 * 1716 * @param int $index the index of the image desired 1717 * @return object 1718 */ 1719 function getImage($index) { 1720 global $_zp_gallery; 1721 if (!is_null($this->images)) { 1722 $this->getImages(); 1723 } 1724 if ($index >= 0 && $index < $this->getNumImages()) { 1725 $img = $this->images[$index]; 1726 return newImage(newAlbum($img['folder']), $img['filename']); 1727 } 1728 return false; 1729 } 1730 1731 function getDynamicAlbum() { 1732 return $this->album; 1733 } 1734 1735 /** 1736 * 1737 * return the list of albums found 1738 */ 1739 function getAlbumList() { 1740 return $this->album_list; 1741 } 1742 1743 /** 1744 * 1745 * return the list of categories found 1746 */ 1747 function getCategoryList() { 1748 return $this->category_list; 1749 } 1750 1751 /** 1752 * 1753 * Returns pages from a search 1754 * @param bool $published ignored, left for parameter compatibility 1755 * @param bool $toplevel ignored, left for parameter compatibility 1756 * @param int $number ignored, left for parameter compatibility 1757 * @param string $sorttype the sort key 1758 * @param strng $sortdirection the sort order 1759 * 1760 * @return array 1761 */ 1762 function getPages($published = NULL, $toplevel = false, $number = NULL, $sorttype = NULL, $sortdirection = NULL) { 1763 return $this->getSearchPages($sorttype, $sortdirection); 1764 } 1765 1766 /** 1767 * Returns a list of Pages Titlelinks found in the search 1768 * 1769 * @parm string $sorttype optional sort field 1770 * @param string $sortdirection optional ordering 1771 * 1772 * @return array 1773 */ 1774 private function getSearchPages($sorttype, $sortdirection) { 1775 if (!extensionEnabled('zenpage') || getOption('search_no_pages') || $this->search_no_pages) 1776 return array(); 1777 list($sorttype, $sortdirection) = $this->sortKey($sorttype, $sortdirection, 'title', 'pages'); 1778 $searchstring = $this->getSearchString(); 1779 $searchdate = $this->dates; 1780 if (empty($searchstring) && empty($searchdate)) { 1781 return array(); 1782 } // nothing to find 1783 $criteria = $this->getCacheTag('pages', serialize($searchstring) . ' ' . $searchdate, $sorttype . ' ' . $sortdirection); 1784 if ($this->pages && $criteria == $this->searches['pages']) { 1785 return $this->pages; 1786 } 1787 $pages = $this->getCachedSearch($criteria); 1788 if (is_null($pages)) { 1789 $pages = $result = array(); 1790 if (empty($searchdate)) { 1791 list ($search_query, $weights) = $this->searchFieldsAndTags($searchstring, 'pages', $sorttype, $sortdirection); 1792 if (empty($search_query)) { 1793 $search_result = false; 1794 } else { 1795 $search_result = query($search_query); 1796 } 1797 zp_apply_filter('search_statistics', $searchstring, 'pages', !$search_result, false, $this->iteration++); 1798 } else { 1799 $search_query = $this->searchDate($searchstring, $searchdate, 'pages', NULL, NULL); 1800 $search_result = query($search_query); 1801 } 1802 if ($search_result) { 1803 while ($row = db_fetch_assoc($search_result)) { 1804 $pageobj = new ZenpagePage($row['titlelink']); 1805 if((zp_loggedin() && $pageobj->isMyItem(LIST_RIGHTS)) || ($pageobj->isPublic() || $this->search_unpublished)) { 1806 $data = array('title' => $row['title'], 'titlelink' => $row['titlelink']); 1807 if (isset($weights)) { 1808 $data['weight'] = $weights[$row['id']]; 1809 } 1810 $result[] = $data; 1811 } 1812 } 1813 db_free_result($search_result); 1814 } 1815 $sortdir = self::getSortdirBool($sortdirection); 1816 if (is_null($sorttype) && isset($weights)) { 1817 $result = sortMultiArray($result, 'weight', $sortdir, true, false, false, array('weight')); 1818 } 1819 if ($sorttype == '`title`') { 1820 $result = sortByMultilingual($result, 'title', $sortdir); 1821 } 1822 foreach ($result as $page) { 1823 $pages[] = $page['titlelink']; 1824 } 1825 $this->cacheSearch($criteria, $pages); 1826 } 1827 $this->pages = $pages; 1828 $this->searches['pages'] = $criteria; 1829 return $this->pages; 1830 } 1831 1832 /** 1833 * Returns a list of News Titlelinks found in the search 1834 * 1835 * @param int $articles_per_page The number of articles to get 1836 * @param bool $published placeholder for consistent parameter list 1837 * @param bool $ignorepagination ignore pagination 1838 * @param string $sorttype field to sort on 1839 * @param string $sortdirection sort order 1840 * 1841 * @return array 1842 */ 1843 function getArticles($articles_per_page = 0, $published = NULL, $ignorepagination = false, $sorttype = NULL, $sortdirection = NULL) { 1844 $articles = $this->getSearchArticles($sorttype, $sortdirection); 1845 if (empty($articles)) { 1846 return array(); 1847 } else { 1848 if ($ignorepagination || !$articles_per_page) { 1849 return $articles; 1850 } 1851 return array_slice($articles, Zenpage::getOffset($articles_per_page), $articles_per_page); 1852 } 1853 } 1854 1855 /** 1856 * Returns a list of News Titlelinks found in the search 1857 * 1858 * @param string $sorttype field to sort on 1859 * @param string $sortdirection sort order 1860 * 1861 * @return array 1862 */ 1863 private function getSearchArticles($sorttype, $sortdirection) { 1864 if (!extensionEnabled('zenpage') || getOption('search_no_news') || $this->search_no_news) { 1865 return array(); 1866 } 1867 list($sorttype, $sortdirection) = $this->sortKey($sorttype, $sortdirection, 'title', 'news'); 1868 $searchstring = $this->getSearchString(); 1869 $searchdate = $this->dates; 1870 if (empty($searchstring) && empty($searchdate)) { 1871 return array(); 1872 } // nothing to find 1873 $criteria = $this->getCacheTag('news', serialize($searchstring) . ' ' . $searchdate, $sorttype . ' ' . $sortdirection); 1874 if ($this->articles && $criteria == $this->searches['news']) { 1875 return $this->articles; 1876 } 1877 $result = $this->getCachedSearch($criteria); 1878 if (is_null($result)) { 1879 $result = array(); 1880 if (empty($searchdate)) { 1881 list ($search_query, $weights) = $this->searchFieldsAndTags($searchstring, 'news', $sorttype, $sortdirection); 1882 } else { 1883 $search_query = $this->searchDate($searchstring, $searchdate, 'news', $sorttype, $sortdirection, $this->whichdates); 1884 } 1885 if (empty($search_query)) { 1886 $search_result = false; 1887 } else { 1888 $search_result = query($search_query); 1889 } 1890 zp_apply_filter('search_statistics', $searchstring, 'news', !empty($search_result), false, $this->iteration++); 1891 if ($search_result) { 1892 while ($row = db_fetch_assoc($search_result)) { 1893 $articleobj = new ZenpageNews($row['titlelink']); 1894 if((zp_loggedin() && $articleobj->isMyItem(LIST_RIGHTS)) || ($articleobj->isPublic() || $this->search_unpublished)) { 1895 $data = array('title' => $row['title'], 'titlelink' => $row['titlelink']); 1896 if (isset($weights)) { 1897 $data['weight'] = $weights[$row['id']]; 1898 } 1899 $result[] = $data; 1900 } 1901 } 1902 db_free_result($search_result); 1903 } 1904 $sortdir = self::getSortdirBool($sortdirection); 1905 if (is_null($sorttype) && isset($weights)) { 1906 $result = sortMultiArray($result, 'weight', $sortdir, true, false, false, array('weight')); 1907 } 1908 if ($sorttype == '`title`') { 1909 $result = sortByMultilingual($result, 'title', $sortdir); 1910 } 1911 $this->cacheSearch($criteria, $result); 1912 } 1913 $this->articles = $result; 1914 $this->searches['news'] = $criteria; 1915 return $this->articles; 1916 } 1917 1918 function clearSearchWords() { 1919 $this->processed_search = ''; 1920 $this->words = ''; 1921 } 1922 1923 /** 1924 * 1925 * creates a unique id for a search 1926 * @param string $table Database table 1927 * @param string $search Search string 1928 * @param string $sort Sort criteria 1929 */ 1930 protected function getCacheTag($table, $search, $sort) { 1931 $user = 'guest'; 1932 $authCookies = Zenphoto_Authority::getAuthCookies(); 1933 if (!empty($authCookies)) { // some sort of password exists, play it safe and make the tag unique 1934 $user = getUserIP(); 1935 } 1936 $array = array('item' => $table, 'fields' => implode(', ', $this->fieldList), 'search' => $search, 'sort' => $sort, 'user' => $user); 1937 $dynalbum = $this->getDynamicAlbum(); 1938 if($dynalbum) { 1939 $array['dynalbum'] = $dynalbum->name; 1940 } 1941 return $array; 1942 } 1943 1944 /** 1945 * 1946 * Caches a search 1947 * @param string $criteria 1948 * @param string $found reslts of the search 1949 */ 1950 private function cacheSearch($criteria, $found) { 1951 if (SEARCH_CACHE_DURATION) { 1952 $criteria = serialize($criteria); 1953 $sql = 'SELECT `id`, `data`, `date` FROM ' . prefix('search_cache') . ' WHERE `criteria` = ' . db_quote($criteria); 1954 $result = query_single_row($sql); 1955 if ($result) { 1956 $sql = 'UPDATE ' . prefix('search_cache') . ' SET `data` = ' . db_quote(serialize($found)) . ', `date` = ' . db_quote(date('Y-m-d H:m:s')) . ' WHERE `id` = ' . $result['id']; 1957 query($sql); 1958 } else { 1959 $sql = 'INSERT INTO ' . prefix('search_cache') . ' (criteria, data, date) VALUES (' . db_quote($criteria) . ', ' . db_quote(serialize($found)) . ', ' . db_quote(date('Y-m-d H:m:s')) . ')'; 1960 query($sql); 1961 } 1962 } 1963 } 1964 1965 /** 1966 * 1967 * Fetches a search from the cache if it exists and has not expired 1968 * @param string $criteria 1969 */ 1970 private function getCachedSearch($criteria) { 1971 if (SEARCH_CACHE_DURATION) { 1972 $sql = 'SELECT `id`, `date`, `data` FROM ' . prefix('search_cache') . ' WHERE `criteria` = ' . db_quote(serialize($criteria)); 1973 $result = query_single_row($sql); 1974 if ($result) { 1975 if ((time() - strtotime($result['date'])) > SEARCH_CACHE_DURATION * 60) { 1976 query('DELETE FROM ' . prefix('search_cache') . ' WHERE `id` = ' . $result['id']); 1977 } else { 1978 if ($result = getSerializedArray($result['data'])) { 1979 return $result; 1980 } 1981 } 1982 } 1983 } 1984 return NULL; 1985 } 1986 1987 /** 1988 * Clears the entire search cache table 1989 */ 1990 static function clearSearchCache() { 1991 $check = query_single_row('SELECT id FROM ' . prefix('search_cache'). ' LIMIT 1'); 1992 if($check) { 1993 query('TRUNCATE TABLE ' . prefix('search_cache')); 1994 } 1995 } 1996 1997 /** 1998 * Returns true if $sortdir is set to "DESC", otherwise false, for use with sorting functions 1999 * @param string $sortdirection Traditional speaking values "ASC" or "DESC" 2000 * 2001 * @since ZenphotoCMS 1.5.8 2002 * 2003 * @return boolean 2004 */ 2005 static function getSortdirBool($sortdirection = 'asc') { 2006 $dir = false; // ascending default 2007 if (strtolower($sortdirection) == 'desc') { 2008 $dir = true; 2009 } 2010 return $dir; 2011 } 2012 2013} 2014 2015// search class end 2016 2017/** 2018 * 2019 * encloses search word in quotes if needed 2020 * @param string $word 2021 * @return string 2022 */ 2023function search_quote($word) { 2024 if (is_numeric($word) || preg_match("/[ &|!'\"`,()]/", $word)) { 2025 $word = '"' . str_replace("\\'", "'", addslashes($word)) . '"'; 2026 } 2027 return $word; 2028} 2029 2030?>