Overview

Packages

  • CONTENIDO
  • Core
    • Authentication
    • Backend
    • Cache
    • CEC
    • Chain
    • ContentType
    • Database
    • Debug
    • Exception
    • Frontend
      • Search
      • URI
      • Util
    • GenericDB
      • Model
    • GUI
      • HTML
    • I18N
    • LayoutHandler
    • Log
    • Security
    • Session
    • Util
    • Validation
    • Versioning
    • XML
  • Module
    • ContentSitemapHtml
    • ContentSitemapXml
    • ContentUserForum
    • NavigationTop
    • ScriptCookieDirective
  • mpAutoloaderClassMap
  • None
  • PHP
  • Plugin
    • ContentAllocation
    • CronjobOverview
    • FormAssistant
    • FrontendLogic
    • FrontendUsers
    • Linkchecker
    • ModRewrite
    • Newsletter
    • Repository
      • FrontendNavigation
      • KeywordDensity
    • SmartyWrapper
    • UrlShortener
    • UserForum
    • Workflow
  • PluginManager
  • Setup
    • Form
    • GUI
    • Helper
      • Environment
      • Filesystem
      • MySQL
      • PHP
    • UpgradeJob

Classes

  • cSearch
  • cSearchBaseAbstract
  • cSearchIndex
  • cSearchResult
  • Overview
  • Package
  • Class
  • Tree
  • Deprecated
  • Todo
  1: <?php
  2: 
  3: /**
  4:  * This file contains the class for content search.
  5:  *
  6:  * @package Core
  7:  * @subpackage Frontend_Search
  8:  * @author Willi Man
  9:  * @copyright four for business AG <www.4fb.de>
 10:  * @license http://www.contenido.org/license/LIZENZ.txt
 11:  * @link http://www.4fb.de
 12:  * @link http://www.contenido.org
 13:  */
 14: 
 15: defined('CON_FRAMEWORK') || die('Illegal call: Missing framework initialization - request aborted.');
 16: 
 17: cInclude('includes', 'functions.encoding.php');
 18: 
 19: /**
 20:  * CONTENIDO API - Search Object
 21:  *
 22:  * This object starts a indexed fulltext search.
 23:  *
 24:  * TODO:
 25:  * - The way to set the search options could be done much more better!
 26:  * - The computation of the set of searchable articles should not be
 27:  * treated in this class.
 28:  * - It is better to compute the array of searchable articles from the
 29:  * outside and to pass the array of searchable articles as parameter.
 30:  * - Avoid foreach loops.
 31:  *
 32:  * Use object with
 33:  *
 34:  * $options = array(
 35:  *      // use db function regexp
 36:  *      'db' => 'regexp',
 37:  *      // combine searchwords with or
 38:  *      'combine' => 'or'
 39:  * );
 40:  *
 41:  * The range of searchable articles is by default the complete content
 42:  * which is online and not protected.
 43:  *
 44:  * With option 'searchable_articles' you can define your own set of
 45:  * searchable articles.
 46:  *
 47:  * If parameter 'searchable_articles' is set, the options 'cat_tree',
 48:  * 'categories', 'articles', 'exclude', 'artspecs', 'protected' and
 49:  * 'dontshowofflinearticles' won't have any effect.
 50:  *
 51:  * $options = array(
 52:  *      // use db function regexp
 53:  *      'db' => 'regexp',
 54:  *      // combine searchwords with or
 55:  *      'combine' => 'or',
 56:  *      'searchable_articles' => array(5, 6, 9, 13)
 57:  * );
 58:  *
 59:  * One can define the range of searchable articles by setting the
 60:  * parameter 'exclude' to false which means the range of categories
 61:  * defined by parameter 'cat_tree' or 'categories' and the range of
 62:  * articles defined by parameter 'articles' is included.
 63:  *
 64:  * $options = array(
 65:  *      // use db function regexp
 66:  *      'db' => 'regexp',
 67:  *      // combine searchwords with or
 68:  *      'combine' => 'or',
 69:  *      // searchrange specified in 'cat_tree', 'categories' and
 70:  *      // 'articles' is included
 71:  *      'exclude' => false,
 72:  *      // tree with root 12 included
 73:  *      'cat_tree' => array(12),
 74:  *      // categories 100, 111 included
 75:  *      'categories' => array(100,111),
 76:  *      // article 33 included
 77:  *      'articles' => array(33),
 78:  *      // array of article specifications => search only articles with
 79:  *      // these artspecs
 80:  *      'artspecs' => array(2, 3),
 81:  *      // results per page
 82:  *      'res_per_page' => 2,
 83:  *      // do not search articles or articles in categories which are
 84:  *      // offline or protected
 85:  *      'protected' => true,
 86:  *      // search offline articles or articles in categories which are
 87:  *      // offline
 88:  *      'dontshowofflinearticles' => false
 89:  * );
 90:  *
 91:  * You can build the complement of the range of searchable articles by
 92:  * setting the parameter 'exclude' to true which means the range of
 93:  * categories defined by parameter 'cat_tree' or 'categories' and the
 94:  * range of articles defined by parameter 'articles' is excluded from
 95:  * search.
 96:  *
 97:  * $options = array(
 98:  *      // use db function regexp
 99:  *      'db' => 'regexp',
100:  *      // combine searchwords with or
101:  *      'combine' => 'or',
102:  *      // searchrange specified in 'cat_tree', 'categories' and
103:  *      // 'articles' is excluded
104:  *      'exclude' => true,
105:  *      // tree with root 12 excluded
106:  *      'cat_tree' => array(12),
107:  *      // categories 100, 111 excluded
108:  *      'categories' => array(100,111),
109:  *      // article 33 excluded
110:  *      'articles' => array(33),
111:  *      // array of article specifications => search only articles with
112:  *      // these artspecs
113:  *      'artspecs' => array(2, 3),
114:  *      // results per page
115:  *      'res_per_page' => 2,
116:  *      // do not search articles or articles in categories which are
117:  *      // offline or protected
118:  *      'protected' => true,
119:  *      // search offline articles or articles in categories which are
120:  *      // offline
121:  *      'dontshowofflinearticles' => false
122:  * );
123:  *
124:  * $search = new Search($options);
125:  *
126:  * // search only in these cms-types
127:  * $search->setCmsOptions(array(
128:  *      "htmlhead",
129:  *      "html",
130:  *      "head",
131:  *      "text",
132:  *      "imgdescr",
133:  *      "link",
134:  *      "linkdescr"
135:  * ));
136:  *
137:  * // start search
138:  * $search_result = $search->searchIndex($searchword, $searchwordex);
139:  *
140:  * The search result structure has following form
141:  * Array (
142:  * [20] => Array (
143:  * [CMS_HTML] => Array (
144:  * [0] => 1
145:  * [1] => 1
146:  * [2] => 1
147:  * )
148:  * [keyword] => Array (
149:  * [0] => content
150:  * [1] => contenido
151:  * [2] => wwwcontenidoorg
152:  * )
153:  * [search] => Array (
154:  * [0] => con
155:  * [1] => con
156:  * [2] => con
157:  * )
158:  * [occurence] => Array (
159:  * [0] => 1
160:  * [1] => 5
161:  * [2] => 1
162:  * )
163:  * [similarity] => 60
164:  * )
165:  * )
166:  *
167:  * The keys of the array are the article ID's found by search.
168:  *
169:  * Searching 'con' matches keywords 'content', 'contenido' and
170:  * 'wwwcontenidoorg' in article with ID 20 in content type CMS_HTML[1].
171:  * The search term occurs 7 times.
172:  * The maximum similarity between searchterm and matching keyword is 60%.
173:  *
174:  * // rank and display the results
175:  * $oSearchResults = new cSearchResult($search_result, 10);
176:  *
177:  * @package Core
178:  * @subpackage Frontend_Search
179:  */
180: class cSearch extends cSearchBaseAbstract {
181: 
182:     /**
183:      * Instance of class Index
184:      *
185:      * @var object
186:      */
187:     protected $_index;
188: 
189:     /**
190:      * search words
191:      *
192:      * @var array
193:      */
194:     protected $_searchWords = array();
195: 
196:     /**
197:      * words which should be excluded from search
198:      *
199:      * @var array
200:      */
201:     protected $_searchWordsExclude = array();
202: 
203:     /**
204:      * type of db search
205:      *
206:      * like => 'sql like'
207:      * regexp => 'sql regexp'
208:      *
209:      * @var string
210:      */
211:     protected $_searchOption;
212: 
213:     /**
214:      * logical combination of searchwords (and, or)
215:      *
216:      * @var string
217:      */
218:     protected $_searchCombination;
219: 
220:     /**
221:      * array of searchable articles
222:      *
223:      * @var array
224:      */
225:     protected $_searchableArts = array();
226: 
227:     /**
228:      * article specifications
229:      *
230:      * @var array
231:      */
232:     protected $_articleSpecs = array();
233: 
234:     /**
235:      * If $protected = true => do not search articles which are offline
236:      * or articles in catgeories which are offline (protected) unless
237:      * the user has access to them.
238:      *
239:      * @var bool
240:      */
241:     protected $_protected;
242: 
243:     /**
244:      * If $dontshowofflinearticles = false => search offline articles or
245:      * articles in categories which are offline.
246:      *
247:      * @var bool
248:      */
249:     protected $_dontshowofflinearticles;
250: 
251:     /**
252:      * If $exclude = true => the specified search range is excluded from
253:      * search, otherwise included.
254:      *
255:      * @var bool
256:      */
257:     protected $_exclude;
258: 
259:     /**
260:      * Array of article id's with information about cms-types, occurence
261:      * of keyword/searchword, similarity.
262:      *
263:      * @var array
264:      */
265:     protected $_searchResult = array();
266: 
267:     /**
268:      * Constructor to create an instance of this class.
269:      *
270:      * @param array $options
271:      *                  $options['db']
272:      *                  'regexp' => DB search with REGEXP
273:      *                  'like' => DB search with LIKE
274:      *                  'exact' => exact match;
275:      *                  $options['combine']
276:      *                  'and', 'or' Combination of search words with AND, OR
277:      *                  $options['exclude']
278:      *                  'true' => searchrange specified in 'cat_tree', 'categories'
279:      *                  and 'articles' is excluded;
280:      *                  'false' => searchrange specified in 'cat_tree', 'categories'
281:      *                  and 'articles' is included
282:      *                  $options['cat_tree']
283:      *                  e.g. array(8) => The complete tree with root 8 is in/excluded
284:      *                  from search
285:      *                  $options['categories']
286:      *                  e.g. array(10, 12) => Categories 10, 12 in/excluded
287:      *                  $options['articles']
288:      *                  e.g. array(23) => Article 33 in/excluded
289:      *                  $options['artspecs']
290:      *                  e.g. array(2, 3) => search only articles with certain article
291:      *                  specifications
292:      *                  $options['protected']
293:      *                  'true' => do not search articles which are offline (locked)
294:      *                  or articles in catgeories which are offline (protected)
295:      *                  $options['dontshowofflinearticles']
296:      *                  'false' => search offline articles or articles in categories
297:      *                  which are offline
298:      *                  $options['searchable_articles']
299:      *                  array of article ID's which should be searchable
300:      * @param cDb   $db [optional]
301:      *                  CONTENIDO database object
302:      *
303:      * @throws cDbException
304:      * @throws cInvalidArgumentException
305:      */
306:     public function __construct($options, $db = NULL) {
307:         parent::__construct($db);
308: 
309:         $this->_index = new cSearchIndex($db);
310: 
311:         $this->_searchOption = (array_key_exists('db', $options)) ? cString::toLowerCase($options['db']) : 'regexp';
312:         $this->_searchCombination = (array_key_exists('combine', $options)) ? cString::toLowerCase($options['combine']) : 'or';
313:         $this->_protected = (array_key_exists('protected', $options)) ? $options['protected'] : true;
314:         $this->_dontshowofflinearticles = (array_key_exists('dontshowofflinearticles', $options)) ? $options['dontshowofflinearticles'] : true;
315:         $this->_exclude = (array_key_exists('exclude', $options)) ? $options['exclude'] : true;
316:         $this->_articleSpecs = (array_key_exists('artspecs', $options) && is_array($options['artspecs'])) ? $options['artspecs'] : array();
317: 
318:         if (array_key_exists('searchable_articles', $options) && is_array($options['searchable_articles'])) {
319:             $this->_searchableArts = $options['searchable_articles'];
320:         } else {
321:             $this->_searchableArts = $this->getSearchableArticles($options);
322:         }
323: 
324:         // minimum similarity between searchword and keyword in percent
325:         $this->intMinimumSimilarity = 50;
326:     }
327: 
328:     /**
329:      * indexed fulltext search
330:      *
331:      * @param string $searchwords
332:      *                                    The search words
333:      * @param string $searchwords_exclude [optional]
334:      *                                    The words, which should be excluded from search
335:      *
336:      * @return bool|array
337:      * @throws cDbException
338:      * @throws cException
339:      * @throws cInvalidArgumentException
340:      */
341:     public function searchIndex($searchwords, $searchwords_exclude = '') {
342:         if (cString::getStringLength(trim($searchwords)) > 0) {
343:             $this->_searchWords = $this->stripWords($searchwords);
344:         } else {
345:             return false;
346:         }
347: 
348:         if (cString::getStringLength(trim($searchwords_exclude)) > 0) {
349:             $this->_searchWordsExclude = $this->stripWords($searchwords_exclude);
350:         }
351: 
352:         $tmp_searchwords = array();
353:         foreach ($this->_searchWords as $word) {
354:             $wordEscaped = cSecurity::escapeDB($word, $this->db);
355:             if ($this->_searchOption == 'like') {
356:                 $wordEscaped = "'%" . $wordEscaped . "%'";
357:             } elseif ($this->_searchOption == 'exact') {
358:                 $wordEscaped = "'" . $wordEscaped . "'";
359:             }
360:             $tmp_searchwords[] = $wordEscaped;
361:         }
362: 
363:         if (count($this->_searchWordsExclude) > 0) {
364:             foreach ($this->_searchWordsExclude as $word) {
365:                 $wordEscaped = cSecurity::escapeDB($word, $this->db);
366:                 if ($this->_searchOption == 'like') {
367:                     $wordEscaped = "'%" . $wordEscaped . "%'";
368:                 } elseif ($this->_searchOption == 'exact') {
369:                     $wordEscaped = "'" . $wordEscaped . "'";
370:                 }
371:                 $tmp_searchwords[] = $wordEscaped;
372:                 $this->_searchWords[] = $word;
373:             }
374:         }
375: 
376:         if ($this->_searchOption == 'regexp') {
377:             // regexp search
378:             $kwSql = "keyword REGEXP '" . implode('|', $tmp_searchwords) . "'";
379:         } elseif ($this->_searchOption == 'like') {
380:             // like search
381:             $search_like = implode(" OR keyword LIKE ", $tmp_searchwords);
382:             $kwSql = "keyword LIKE " . $search_like;
383:         } elseif ($this->_searchOption == 'exact') {
384:             // exact match
385:             $search_exact = implode(" OR keyword = ", $tmp_searchwords);
386:             $kwSql = "keyword LIKE " . $search_exact;
387:         }
388: 
389:         $sql = "SELECT keyword, auto FROM " . $this->cfg['tab']['keywords'] . " WHERE idlang=" . cSecurity::toInteger($this->lang) . " AND " . $kwSql . " ";
390:         $this->_debug('sql', $sql);
391:         $this->db->query($sql);
392: 
393:         while ($this->db->nextRecord()) {
394: 
395:             $tmp_index_string = preg_split('/&/', $this->db->f('auto'), -1, PREG_SPLIT_NO_EMPTY);
396: 
397:             $this->_debug('index', $this->db->f('auto'));
398: 
399:             $tmp_index = array();
400:             foreach ($tmp_index_string as $string) {
401:                 $tmp_string = preg_replace('/[=\(\)]/', ' ', $string);
402:                 $tmp_index[] = preg_split('/\s/', $tmp_string, -1, PREG_SPLIT_NO_EMPTY);
403:             }
404:             $this->_debug('tmp_index', $tmp_index);
405: 
406:             foreach ($tmp_index as $string) {
407:                 $artid = $string[0];
408: 
409:                 // filter nonsearchable articles
410:                 if (in_array($artid, $this->_searchableArts)) {
411: 
412:                     $cms_place = $string[2];
413:                     $keyword = $this->db->f('keyword');
414:                     $percent = 0;
415:                     $similarity = 0;
416:                     foreach ($this->_searchWords as $word) {
417:                         // computes similarity between searchword and keyword in
418:                         // percent
419:                         similar_text($word, $keyword, $percent);
420:                         if ($percent > $similarity) {
421:                             $similarity = $percent;
422:                             $searchword = $word;
423:                         }
424:                     }
425: 
426:                     $tmp_cmstype = preg_split('/[,]/', $cms_place, -1, PREG_SPLIT_NO_EMPTY);
427:                     $this->_debug('tmp_cmstype', $tmp_cmstype);
428: 
429:                     $tmp_cmstype2 = array();
430:                     foreach ($tmp_cmstype as $type) {
431:                         $tmp_cmstype2[] = preg_split('/-/', $type, -1, PREG_SPLIT_NO_EMPTY);
432:                     }
433:                     $this->_debug('tmp_cmstype2', $tmp_cmstype2);
434: 
435:                     foreach ($tmp_cmstype2 as $type) {
436:                         if (!$this->_index->checkCmsType($type[0])) {
437:                             // search for specified cms-types
438:                             if ($similarity >= $this->intMinimumSimilarity) {
439:                                 // include article into searchresult set only if
440:                                 // similarity between searchword and keyword is
441:                                 // big enough
442:                                 $this->_searchResult[$artid][$type[0]][] = $type[1];
443:                                 $this->_searchResult[$artid]['keyword'][] = $this->db->f('keyword');
444:                                 $this->_searchResult[$artid]['search'][] = $searchword;
445:                                 $this->_searchResult[$artid]['occurence'][] = $string[1];
446:                                 $this->_searchResult[$artid]['debug_similarity'][] = $percent;
447:                                 if (isset($this->_searchResult[$artid]['similarity']) && $similarity > $this->_searchResult[$artid]['similarity']) {
448:                                     $this->_searchResult[$artid]['similarity'] = $similarity;
449:                                 }
450:                             }
451:                         }
452:                     }
453:                 }
454:             }
455:         }
456: 
457:         if ($this->_searchCombination == 'and') {
458:             // all search words must appear in the article
459:             foreach ($this->_searchResult as $article => $val) {
460:                 if (!count(array_diff($this->_searchWords, $val['search'])) == 0) {
461:                     // $this->rank_structure[$article] = $rank[$article];
462:                     unset($this->_searchResult[$article]);
463:                 }
464:             }
465:         }
466: 
467:         if (count($this->_searchWordsExclude) > 0) {
468:             // search words to be excluded must not appear in article
469:             foreach ($this->_searchResult as $article => $val) {
470:                 if (!count(array_intersect($this->_searchWordsExclude, $val['search'])) == 0) {
471:                     // $this->rank_structure[$article] = $rank[$article];
472:                     unset($this->_searchResult[$article]);
473:                 }
474:             }
475:         }
476: 
477:         $this->_debug('$this->search_result', $this->_searchResult);
478:         $this->_debug('$this->searchable_arts', $this->_searchableArts);
479: 
480:         $searchTracking = new cApiSearchTrackingCollection();
481:         $searchTracking->trackSearch($searchwords, count($this->_searchResult));
482: 
483:         return $this->_searchResult;
484:     }
485: 
486:     /**
487:      *
488:      * @param mixed $cms_options
489:      *         The cms-types (htmlhead, html, ...) which should explicitly be
490:      *         searched.
491:      */
492:     public function setCmsOptions($cms_options) {
493:         if (is_array($cms_options) && count($cms_options) > 0) {
494:             $this->_index->setCmsOptions($cms_options);
495:         }
496:     }
497: 
498:     /**
499:      *
500:      * @param string $searchwords
501:      *         The search-words
502:      * @return array
503:      *         of stripped search-words
504:      */
505:     public function stripWords($searchwords) {
506:         // remove backslash and html tags
507:         $searchwords = trim(strip_tags(stripslashes($searchwords)));
508: 
509:         // split the phrase by any number of commas or space characters
510:         $tmp_words = mb_split('[\s,]+', $searchwords);
511: 
512:         $tmp_searchwords = array();
513: 
514:         foreach ($tmp_words as $word) {
515: 
516:             $word = htmlentities($word, ENT_COMPAT, 'UTF-8');
517:             $word = (trim(cString::toLowerCase($word)));
518:             $word = html_entity_decode($word, ENT_COMPAT, 'UTF-8');
519: 
520:             // $word =(trim(cString::toLowerCase($word)));
521:             if (cString::getStringLength($word) > 1) {
522:                 $tmp_searchwords[] = $word;
523:             }
524:         }
525: 
526:         return array_unique($tmp_searchwords);
527:     }
528: 
529:     /**
530:      * Returns the category tree array.
531:      *
532:      * @todo This is not the job for search, should be outsourced ...
533:      * @param int $cat_start
534:      *         Root of a category tree
535:      * @return array
536:      *         Category Tree
537:      * @throws cDbException
538:      * @throws cInvalidArgumentException
539:      */
540:     public function getSubTree($cat_start) {
541:         $sql = "SELECT
542:                 B.idcat, B.parentid
543:             FROM
544:                 " . $this->cfg['tab']['cat_tree'] . " AS A,
545:                 " . $this->cfg['tab']['cat'] . " AS B,
546:                 " . $this->cfg['tab']['cat_lang'] . " AS C
547:             WHERE
548:                 A.idcat  = B.idcat AND
549:                 B.idcat  = C.idcat AND
550:                 C.idlang = '" . cSecurity::toInteger($this->lang) . "' AND
551:                 B.idclient = '" . cSecurity::toInteger($this->client) . "'
552:             ORDER BY
553:                 idtree";
554:         $this->_debug('sql', $sql);
555:         $this->db->query($sql);
556: 
557:         // $aSubCats = array();
558:         // $i = false;
559:         // while ($this->db->nextRecord()) {
560:         // if ($this->db->f('parentid') < $cat_start) {
561:         // // ending part of tree
562:         // $i = false;
563:         // }
564:         // if ($this->db->f('idcat') == $cat_start) {
565:         // // starting part of tree
566:         // $i = true;
567:         // }
568:         // if ($i == true) {
569:         // $aSubCats[] = $this->db->f('idcat');
570:         // }
571:         // }
572: 
573:         $aSubCats = array(
574:             $cat_start
575:         );
576:         while ($this->db->nextRecord()) {
577:             // ommit if cat is no child of any recognized descendant
578:             if (!in_array($this->db->f('parentid'), $aSubCats)) {
579:                 continue;
580:             }
581:             // ommit if cat is already recognized (happens with $cat_start)
582:             if (in_array($this->db->f('idcat'), $aSubCats)) {
583:                 continue;
584:             }
585:             // add cat as recognized descendant
586:             $aSubCats[] = $this->db->f('idcat');
587:         }
588: 
589:         return $aSubCats;
590:     }
591: 
592:     /**
593:      * Returns list of searchable article ids in given search range.
594:      *
595:      * @param array $search_range
596:      * @return array
597:      * @throws cDbException
598:      * @throws cInvalidArgumentException
599:      */
600:     public function getSearchableArticles($search_range) {
601:         global $auth;
602: 
603:         $aCatRange = array();
604:         if (array_key_exists('cat_tree', $search_range) && is_array($search_range['cat_tree'])) {
605:             if (count($search_range['cat_tree']) > 0) {
606:                 foreach ($search_range['cat_tree'] as $cat) {
607:                     $aCatRange = array_merge($aCatRange, $this->getSubTree($cat));
608:                 }
609:             }
610:         }
611: 
612:         if (array_key_exists('categories', $search_range) && is_array($search_range['categories'])) {
613:             if (count($search_range['categories']) > 0) {
614:                 $aCatRange = array_merge($aCatRange, $search_range['categories']);
615:             }
616:         }
617: 
618:         $aCatRange = array_unique($aCatRange);
619:         $sCatRange = implode("','", $aCatRange);
620:         $sArtRange = '';
621: 
622:         if (array_key_exists('articles', $search_range) && is_array($search_range['articles'])) {
623:             if (count($search_range['articles']) > 0) {
624:                 $sArtRange = implode("','", $search_range['articles']);
625:             }
626:         }
627: 
628:         if ($this->_protected == true) {
629:             // access will be checked later
630:             $sProtected = " C.visible = 1 AND B.online = 1 ";
631:         } else {
632:             if ($this->_dontshowofflinearticles == true) {
633:                 $sProtected = " C.visible = 1 AND B.online = 1 ";
634:             } else {
635:                 $sProtected = " 1 ";
636:             }
637:         }
638: 
639:         if ($this->_exclude == true) {
640:             // exclude searchrange
641:             $sSearchRange = " A.idcat NOT IN ('" . $sCatRange . "') AND B.idart NOT IN ('" . $sArtRange . "') AND ";
642:         } else {
643:             // include searchrange
644:             if (cString::getStringLength($sArtRange) > 0) {
645:                 $sSearchRange = " A.idcat IN ('" . $sCatRange . "') AND B.idart IN ('" . $sArtRange . "') AND ";
646:             } else {
647:                 $sSearchRange = " A.idcat IN ('" . $sCatRange . "') AND ";
648:             }
649:         }
650: 
651:         if (count($this->_articleSpecs) > 0) {
652:             $sArtSpecs = " B.artspec IN ('" . implode("','", $this->_articleSpecs) . "') AND ";
653:         } else {
654:             $sArtSpecs = '';
655:         }
656: 
657:         $sql = "SELECT
658:                     A.idart,
659:                     A.idcat,
660:                     C.public
661:                 FROM
662:                     " . $this->cfg["tab"]["cat_art"] . " as A,
663:                     " . $this->cfg["tab"]["art_lang"] . " as B,
664:                     " . $this->cfg["tab"]["cat_lang"] . " as C
665:                 WHERE
666:                     " . $sSearchRange . "
667:                     B.idlang = '" . cSecurity::toInteger($this->lang) . "' AND
668:                     C.idlang = '" . cSecurity::toInteger($this->lang) . "' AND
669:                     A.idart = B.idart AND
670:                     B.searchable = 1  AND
671:                     A.idcat = C.idcat AND
672:                     " . $sArtSpecs . "
673:                     " . $sProtected . " ";
674:         $this->_debug('sql', $sql);
675:         $this->db->query($sql);
676: 
677:         $aIdArts = array();
678:         while ($this->db->nextRecord()) {
679:             if($this->db->f("idcat") != "" && $this->_protected) {
680:                 if($this->db->f("public") == "0") {
681:                     // CEC to check category access
682:                     // break at 'true', default value 'false'
683:                     cApiCecHook::setBreakCondition(true, false);
684:                     $allow = cApiCecHook::executeWhileBreakCondition('Contenido.Frontend.CategoryAccess', $this->lang, $this->db->f("idcat"), $auth->auth['uid']);
685:                     if (!$allow) {
686:                         continue;
687:                     }
688:                 }
689:             }
690: 
691:             $aIdArts[] = $this->db->f('idart');
692:         }
693:         return $aIdArts;
694:     }
695: 
696:     /**
697:      * Fetch all article specifications which are online.
698:      *
699:      * @return array
700:      *         Array of article specification Ids
701:      * @throws cDbException
702:      * @throws cInvalidArgumentException
703:      */
704:     public function getArticleSpecifications() {
705:         $sql = "SELECT
706:                     idartspec
707:                 FROM
708:                     " . $this->cfg['tab']['art_spec'] . "
709:                 WHERE
710:                     client = " . cSecurity::toInteger($this->client) . " AND
711:                     lang = " . cSecurity::toInteger($this->lang) . " AND
712:                     online = 1 ";
713:         $this->_debug('sql', $sql);
714:         $this->db->query($sql);
715:         $aArtspec = array();
716:         while ($this->db->nextRecord()) {
717:             $aArtspec[] = $this->db->f('idartspec');
718:         }
719:         return $aArtspec;
720:     }
721: 
722:     /**
723:      * Set article specification.
724:      *
725:      * @param int $iArtspecID
726:      */
727:     public function setArticleSpecification($iArtspecID) {
728:         $this->_articleSpecs[] = $iArtspecID;
729:     }
730: 
731:     /**
732:      * Add all article specifications matching name of article
733:      * specification (client dependent but language independent).
734:      *
735:      * @param string $sArtSpecName
736:      * @return bool
737:      * @throws cDbException
738:      * @throws cInvalidArgumentException
739:      */
740:     public function addArticleSpecificationsByName($sArtSpecName) {
741:         if (!isset($sArtSpecName) || cString::getStringLength($sArtSpecName) == 0) {
742:             return false;
743:         }
744: 
745:         $sql = "SELECT
746:                     idartspec
747:                 FROM
748:                     " . $this->cfg['tab']['art_spec'] . "
749:                 WHERE
750:                     client = " . cSecurity::toInteger($this->client) . " AND
751:                     artspec = '" . $this->db->escape($sArtSpecName) . "' ";
752:         $this->_debug('sql', $sql);
753:         $this->db->query($sql);
754:         while ($this->db->nextRecord()) {
755:             $this->_articleSpecs[] = $this->db->f('idartspec');
756:         }
757:     }
758: }
759: 
CMS CONTENIDO 4.10.0 API documentation generated by ApiGen 2.8.0