Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
search_manager.inc
1 <?php
17 require_once SQ_INCLUDE_PATH.'/asset.inc';
18 require_once SQ_DATA_PATH.'/private/conf/tools.inc';
19 
31 class Search_Manager extends Asset
32 {
33  var $standard_date_fields = NULL;
34  var $standard_text_fields = NULL;
35 
36 
43  var $_db_plugin = NULL;
44 
45 
52  function __construct($assetid=0)
53  {
54  $this->_ser_attrs = TRUE;
55  parent::__construct($assetid);
56  $this->standard_date_fields = Array(
57  'created' => translate('asset_created_date'),
58  'updated' => translate('asset_updated_date'),
59  'published' => translate('asset_published_date'),
60  'status_changed' => translate('asset_status_changed_date'),
61  );
62  $this->standard_text_fields = Array(
63  'assetid' => translate('asset_id'),
64  'name' => translate('asset_name'),
65  'short_name' => translate('asset_name_short'),
66  'contents' => translate('asset_contents'),
67  );
68 
69  }//end constructor
70 
71 
81  function create(Array &$link)
82  {
83  require_once SQ_CORE_PACKAGE_PATH.'/system/system_asset_fns.inc';
84  if (!system_asset_fns_create_pre_check($this)) {
85  return FALSE;
86  }
87  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
88  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
89 
90  if ($linkid = parent::create($link)) {
91  if (!system_asset_fns_create_cleanup($this)) {
92  $linkid = FALSE;
93  }
94  }
95 
96  if ($linkid) {
97  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
98  } else {
99  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
100  }
101 
102  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
103  return $linkid;
104 
105  }//end create()
106 
107 
117  function _getName($short_name=FALSE)
118  {
119  return $GLOBALS['SQ_SYSTEM']->am->getTypeInfo($this->type(), 'name');
120 
121  }//end _getName()
122 
123 
130  function canDelete()
131  {
132  return FALSE;
133 
134  }//end canDelete()
135 
136 
143  function canClone()
144  {
145  return FALSE;
146 
147  }//end canClone()
148 
149 
150 //-- EVENT STUBS --//
151 
152 
163  function onAssetUpdate(&$broadcaster, $vars=Array())
164  {
165  if (!$this->attr('indexing')) return FALSE;
166  $this->reindexAsset($broadcaster, $vars);
167 
168  return TRUE;
169 
170  }//end onAssetUpdate()
171 
172 
183  function onAssetCreate(&$broadcaster, $vars=Array())
184  {
185  if (!$this->attr('indexing')) return FALSE;
186  $this->reindexAsset($broadcaster, $vars);
187  $this->reindexAttributes($broadcaster, $GLOBALS['SQ_SYSTEM']->am->getAssetTypeAttributes($broadcaster->type(), Array('name')), TRUE);
188 
189  return TRUE;
190 
191  }//end onAssetCreate()
192 
193 
204  function onAssetStatusUpdate(&$broadcaster, $vars=Array())
205  {
206  if (!$this->attr('indexing')) return FALSE;
207  $this->reindexAsset($broadcaster, Array('status_changed', 'published'));
208 
209  return TRUE;
210 
211  }//end onAssetStatusUpdate()
212 
213 
223  function onAttributeChange(&$broadcaster, $vars=Array())
224  {
225  if (!$this->attr('indexing')) return FALSE;
226  $this->reindexAttributes($broadcaster, $vars);
227 
228  return TRUE;
229 
230  }//end onAttributeChange()
231 
232 
243  function onContentsUpdated(&$broadcaster, $vars=Array())
244  {
245  if (!$this->attr('indexing')) return FALSE;
246  $this->reindexContents($broadcaster, $vars);
247 
248  return TRUE;
249 
250  }//end onContentsUpdated()
251 
252 
263  function onAssetDeleted(&$broadcaster, $vars=Array())
264  {
265  if (!$this->attr('indexing')) return FALSE;
266  return $this->flushIndexableContent($broadcaster->id);
267 
268  }//end onAssetDeleted()
269 
270 
285  function onContextDelete(&$broadcaster, $vars=Array())
286  {
287  if (!$this->attr('indexing')) return FALSE;
288  MatrixDAL::executeQuery('search_manager', 'deleteDeadContextIndexableContent', $vars);
289 
290  }//end onContextDelete()
291 
292 
293 //-- ASSET INDEXING --//
294 
295 
310  function reindexAsset(&$asset, $vars=Array())
311  {
312  // if search manager is reindexed then there is infinite recursion!!!
313  if ($asset->id == $this->id) return FALSE;
314 
315  // only delete the vars that have changed
316  $flush_vars = Array();
317  if (!in_array('all', $vars)) {
318  foreach ($vars as $var) {
319  $flush_vars[] = '__'.$var.'__';
320  }
321  } else {
322  // flush all the assets vars
323  $flush_vars = array_keys($this->getIndexableAssetComponents());
324  }
325  if (!empty($flush_vars)) {
326  $this->flushIndexableContent($asset->id, $flush_vars);
327  }
328 
329  // check that we want to index this asset first
330  if (!$this->isAssetIndexable($asset->id)) return;
331 
332  $index_content = $this->getIndexableAssetContent($asset, $vars);
333  $this->addIndexableContent($asset->id, $index_content);
334 
335  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($asset);
336 
337  }//end reindexAsset()
338 
339 
358  function reindexAttributes(&$asset, $vars=Array(), $all_contexts=FALSE)
359  {
360  // if search manager is reindexed then there is infinite recursion!!!
361  if ($asset->id == $this->id) return FALSE;
362 
363  // We can't reindex attributes if this asset does not have any attributes
364  if (empty($asset->vars)) return FALSE;
365 
366  $contextid = (int)$GLOBALS['SQ_SYSTEM']->getContextId();
367  $all_contextids = array_keys($GLOBALS['SQ_SYSTEM']->getAllContexts());
368 
369  // only delete the attributes that have changed
370  $flush_vars = Array();
371  if (!in_array('all', $vars)) {
372  $flush_vars = $vars;
373  } else {
374  // flush all the indexable attributes for this asset type
375  $flush_vars = array_keys($this->getIndexableAttributes($asset->type()));
376  }
377 
378  if ($all_contexts === TRUE) {
379  $contexts_todo = array_keys($GLOBALS['SQ_SYSTEM']->getAllContexts());
380  } else {
381  $contexts_todo = Array($contextid);
382  }
383 
384  if (count($flush_vars) > 0) {
385  foreach ($flush_vars as &$flush_var) {
386  $flush_var = 'attr:'.$flush_var;
387  }
388  unset($flush_var);
389  }
390 
391  $all_indexable_content = Array();
392 
393  if ($this->isAssetIndexable($asset->id) === FALSE) {
394  // Asset is not indexable.
395  // Flush the vars in all passed contexts, plus default
396  $this->flushIndexableContent($asset->id, $flush_vars, array_merge($all_contextids, Array('default')));
397  } else {
398  // Asset is indexable. Yay.
399  $indexable_content = $this->getIndexableAttributeContent($asset, $vars, $all_contexts);
400 
401  if ($all_contexts === TRUE) {
402  // If all contexts, we'll consider it a fresh start.
403  $this->flushIndexableContent($asset->id, $flush_vars, array_merge($all_contextids, Array('default')));
404  } else {
405  if ($contextid === 0) {
406  // Flush out all the default values for these
407  $this->flushDefaultIndexableContent($asset->id, $flush_vars);
408  } else {
409  // We only have to flush the indexable content for this context,
410  // because we are only adding values for this context after all,
411  // which won't be defaults
412  $this->flushIndexableContent($asset->id, $flush_vars, Array($contextid));
413 
414  // Also flush the non-contextable attributes updated in this context
415  // This will be indexed as the general "default context" value (see bug #5832)
416  $flush_non_contextable_vars = Array();
417  foreach($indexable_content as $attr_idx_data) {
418  if (isset($attr_idx_data['contextid']) && $attr_idx_data['contextid'] == 'default' && isset($attr_idx_data['component'])) {
419  $flush_non_contextable_vars[$attr_idx_data['component']] = 1;
420  }//end if
421  }//end foreach
422  if (!empty($flush_non_contextable_vars)) {
423  $this->flushIndexableContent($asset->id, array_keys($flush_non_contextable_vars), Array('default'));
424  }
425  }
426  }
427 
428  // Indexable content includes contextid/use default information. Now
429  // we can add it.
430  $this->addIndexableContent($asset->id, $indexable_content);
431  }
432 
433  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($asset);
434 
435  }//end reindexAttributes()
436 
437 
456  function reindexContents(&$asset, $vars=Array(), $all_contexts=FALSE)
457  {
458  // Find the asset that we want to index the content under.
459  // this will be the top most dependant asset above this asset
460  if (!$indexing_assetid = $this->getIndexingAssetid($asset)) {
461  return FALSE;
462  }
463  $contextid = $GLOBALS['SQ_SYSTEM']->getContextId();
464 
465  $indexing_asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($indexing_assetid);
466 
467  // check that we want to index this asset first
468  if (!$this->isAssetIndexable($indexing_assetid)) {
469  return;
470  }
471 
472  if ($all_contexts === TRUE) {
473  $contexts_todo = array_keys($GLOBALS['SQ_SYSTEM']->getAllContexts());
474  } else {
475  $contexts_todo = Array($contextid);
476  }
477 
478  $this->flushIndexableContent($indexing_assetid, Array('__contents__'), $contexts_todo);
479 
480  foreach ($contexts_todo as $processed_contextid) {
481  $other_context = FALSE;
482  if (((int)$contextid === (int)$processed_contextid)) {
483  // Same context as was passed, OR, asset is being created
484  // and therefore all context values will be the same
485  $contexted_asset =& $indexing_asset;
486  } else {
487  $other_context = TRUE;
488  // Grab the contexted version of the asset
489  $GLOBALS['SQ_SYSTEM']->changeContext($processed_contextid);
490  $contexted_asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($asset->id);
491  }
492 
493  $index_content = $this->getIndexableContent($contexted_asset);
494 
495  foreach ($index_content as &$index_content_item) {
496  $index_content_item['contextid'] = $processed_contextid;
497  }
498  $this->addIndexableContent($indexing_assetid, $index_content);
499 
500  if ($other_context === TRUE) {
501  unset($contexted_asset);
502  $GLOBALS['SQ_SYSTEM']->restoreContext();
503  }
504  }
505 
506  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($indexing_asset);
507 
508  }//end reindexContents()
509 
510 
519  function getIndexingAssetid(&$asset)
520  {
521  $links = $GLOBALS['SQ_SYSTEM']->am->getLinks($asset->id, SQ_SC_LINK_ALL, '', FALSE, 'minor');
522  if (empty($links)) return FALSE;
523 
524  $parents = $GLOBALS['SQ_SYSTEM']->am->getDependantParents($asset->id);
525  $parents = $GLOBALS['SQ_SYSTEM']->am->getAssetInfo($parents, Array(), FALSE, 'type_code');
526 
527  if (empty($parents)) return $asset->id;
528 
529  // the first dependant parent will be the first element on the array
530  $array_keys = array_keys($parents);
531  $parent = array_shift($array_keys);
532 
533  return $parent;
534 
535  }//end getIndexingAssetid()
536 
537 
547  function getIndexableAssetContent(&$asset, $changed_vars)
548  {
549  require_once SQ_FUDGE_PATH.'/general/text.inc';
550 
551  $plugin =& $this->_getDBPlugin();
552  $weightings = $this->getWeightings($asset->id);
553  $type_code = $asset->type();
554  $index_content = Array();
555  $reindex_all = in_array('all', $changed_vars);
556  $indexed_components = $this->getIndexedComponents($asset->id);
557 
558  // index the name and the short name
559  foreach (Array('name', 'short_name') as $name_type) {
560 
561  if (!in_array($name_type, $changed_vars) && !$reindex_all) {
562  continue;
563  }
564 
565  $name_index = '__'.$name_type.'__';
566  // if there are no weightings, we dont want to index this name
567  if (!isset($weightings[$name_index])) continue;
568 
569  $name = ($name_type == 'name') ? $asset->name : $asset->short_name;
570  $index_content = array_merge($index_content, $plugin->splitIndexableContent($name, $type_code, 'text', $name_index, $weightings[$name_index], 'default', TRUE));
571  }
572 
573  $dates = Array(
574  'created' => $asset->created,
575  'updated' => $asset->updated,
576  );
577  if (!is_null($asset->published)) {
578  $dates['published'] = $asset->published;
579  }
580  if (!is_null($asset->status_changed)) {
581  $dates['status_changed'] = $asset->status_changed;
582  }
583 
584  foreach ($dates as $type => $date) {
585 
586  if (!in_array($type, $changed_vars) && !$reindex_all) {
587  continue;
588  }
589 
590  // if there are no weightings, we don't want to index this date
591  if (!isset($weightings['__'.$type.'__'])) continue;
592 
593  $index_content[] = Array(
594  'value' => ts_iso8601($date),
595  'type_code' => $type_code,
596  'type' => 'datetime',
597  'component' => '__'.$type.'__',
598  'score' => $weightings['__'.$type.'__'],
599  'contextid' => 'default',
600  'use_default' => '1',
601  );
602  }
603 
604  if (isset($weightings['__assetid__'])) {
605 
606  if (in_array('assetid', $changed_vars) || $reindex_all) {
607 
608  $index_content[] = Array(
609  'value' => $asset->id,
610  'type_code' => $type_code,
611  'type' => 'int',
612  'component' => '__assetid__',
613  'score' => $weightings['__assetid__'],
614  'contextid' => 'default',
615  'use_default' => '1',
616  );
617  }
618  }
619 
620  foreach ($index_content as &$index_content_item) {
621  $index_content_item['contextid'] = NULL;
622  }
623 
624  return $index_content;
625 
626  }//end getIndexableAssetContent()
627 
628 
638  function getIndexableAttributeContent(&$asset, $changed_vars, $all_contexts=TRUE)
639  {
640  $plugin =& $this->_getDBPlugin();
641  $weightings = $this->getWeightings($asset->id);
642  $index_content = Array();
643  $type_code = $asset->type();
644  $reindex_all = in_array('all', $changed_vars);
645  $contextid = $GLOBALS['SQ_SYSTEM']->getContextId();
646 
647  if ($all_contexts === TRUE) {
648  $asset_vars = Array();
649  $context_list = array_keys($GLOBALS['SQ_SYSTEM']->getAllContexts());
650  foreach ($context_list as $this_contextid) {
651  if ($this_contextid !== $contextid) {
652  $GLOBALS['SQ_SYSTEM']->changeContext($this_contextid);
653  $context_asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($asset->id);
654  } else {
655  $context_asset = $asset;
656  }
657 
658  $asset_vars[$this_contextid] = $context_asset->vars;
659 
660  if ($this_contextid !== $contextid) {
661  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($context_asset->id);
662  unset($context_asset);
663  $GLOBALS['SQ_SYSTEM']->restoreContext();
664  }
665  }
666  } else {
667  $context_list = Array();
668  // We might need the "default" context values for non-contextable attributes
669  if ($contextid !== 0) {
670  $context_list[] = 0;
671  $GLOBALS['SQ_SYSTEM']->changeContext(0);
672  $context_asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($asset->id);
673  $asset_vars[0] = $context_asset->vars;
674  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($context_asset->id);
675  unset($context_asset);
676  $GLOBALS['SQ_SYSTEM']->restoreContext();
677  }
678  $context_list[] = $contextid;
679  $asset_vars[$contextid] = $asset->vars;
680  }
681 
682  // We cannot use isset() because we have to handle the possibility of a
683  // NULL default value, so will try and fudge with an array and
684  // array_key_exists(). Hold onto your hats, folks...
685  $default_value = Array();
686  foreach ($asset->vars as $var_name => $var_data) {
687 
688  // only reindex the attributes that have been changed
689  if (!in_array($var_name, $changed_vars) && !$reindex_all) {
690  continue;
691  }
692 
693  // if there is no weightings for this attribute, then its not indexable
694  if (!isset($weightings[$var_name])) continue;
695 
696  // don't index passwords or serialise attributes
697  if ($var_data['type'] == 'password' || $var_data['type'] == 'serialise') {
698  continue;
699  }
700 
701  $attrid = $var_data['attrid'];
702  $values = Array();
703  if ((int)$var_data['is_contextable'] === 0) {
704  $attr = $GLOBALS['SQ_SYSTEM']->am->getAttribute($attrid);
705  $attr->setValue($asset_vars[0][$var_name]['value']);
706  $values['default'] = $attr->getContent();
707  } else {
708  foreach (array_keys($asset_vars) as $this_contextid) {
709  $attr = $GLOBALS['SQ_SYSTEM']->am->getAttribute($attrid);
710  $attr->setValue($asset_vars[$this_contextid][$var_name]['value']);
711  // Need to check if the 'use_default' key is set to NULL
712  if (array_key_exists('use_default', $asset_vars[$this_contextid][$var_name]) && (int)$asset_vars[$this_contextid][$var_name]['use_default'] === 1) {
713  // Is the default value for this attribute found yet?
714  // Otherwise, don't bother reloading the attribute.
715  if (isset($default_value[$var_name]) === FALSE) {
716  $default_value[$var_name] = $attr->getContent();
717  }
718 
719  } else {
720  $values[$this_contextid] = $attr->getContent();
721  }
722  }
723  }
724 
725  if ((int)$var_data['is_contextable'] === 0) {
726  $list_to_test = Array('default');
727  } else {
728  $list_to_test = $context_list;
729  }
730 
731  foreach ($list_to_test as $this_contextid) {
732  if ($this_contextid === 'default') {
733  // Non-contextable attribute.
734  $use_default = TRUE;
735  } else {
736  $use_default = ($this_contextid === 0 ? TRUE : FALSE);
737  if (array_key_exists($this_contextid, $values) === FALSE) {
738  $values[$this_contextid] = $default_value[$var_name];
739  $use_default = TRUE;
740  }
741  }
742 
743  $value = $values[$this_contextid];
744 
745  //strip the formatting tags first because they should not be replace with space (' '), e.g. <b>H</b>ello => Hello
746  $formatting_tags = Array('span', 'b', 'big', 'em', 'i', 'small', 'strong', 'sub', 'sup', 'ins', 'del');
747  $value = $this->_strip_tags($formatting_tags, $value);
748  // strip tags and replace with whitespace so as to preserve word boundaries
749  $value = preg_replace('/<\/?[^>]+>/i', ' ', $value);
750  if (empty($value)) continue;
751 
752  $comp_name = 'attr:'.$var_name;
753 
754  // if the type is text and its just a number representation
755  // index the number as a whole (remove spaces so that the number does
756  // not get indexed as two separate entries)
757 
758  if ($var_data['type'] == 'text') {
759  $value = $this->_getRawNumberInText($value);
760  }
761 
762  if ($var_data['type'] == 'datetime') {
763  // At least there's something we can agree on - datetimes should
764  // never be split apart
765  $index_content[] = Array(
766  'value' => $value,
767  'type_code' => $type_code,
768  'type' => $var_data['type'],
769  'component' => $comp_name,
770  'score' => $weightings[$var_name],
771  'contextid' => $this_contextid,
772  'use_default' => $use_default,
773  );
774  } else {
775  $index_content = array_merge($index_content, $plugin->splitIndexableContent($value, $type_code, $var_data['type'], $comp_name, $weightings[$var_name], $this_contextid, $use_default));
776  }
777  }//end foreach context in value list
778 
779  }//end foreach
780 
781  unset($default_value);
782 
783  return $index_content;
784 
785  }//end getIndexableAttributeContent()
786 
787 
796  function getIndexableContent(&$asset)
797  {
798  $plugin =& $this->_getDBPlugin();
799  $index_content = Array();
800  $weightings = $this->getWeightings($asset->id);
801  $type_code = $asset->type();
802  $contextid = $GLOBALS['SQ_SYSTEM']->getContextId();
803 
804  // only index this if there is a weighting set
805  if (isset($weightings['__contents__'])) {
806 
807  // ask the asset for its content, and store it under a contents component
808  // we don't want to index any tags so strip them
809 
810  $contents = $asset->getContent();
811 
812  //strip the formatting tags first because they should not be replace with space (' '), e.g. <b>H</b>ello => Hello
813  $formatting_tags = Array('span', 'b', 'big', 'em', 'i', 'small', 'strong', 'sub', 'sup', 'ins', 'del');
814  $contents = $this->_strip_tags($formatting_tags, $contents);
815  // strip tags and replace with whitespace so as to preserve word boundaries
816  $contents = preg_replace('/<\/?[^>]+>/i', ' ', $contents);
817 
818  if (trim($contents) != '') {
819  $index_content = $plugin->splitIndexableContent($contents, $type_code, 'text', '__contents__', $weightings['__contents__'], $contextid, FALSE);
820  }
821  }
822 
823  return $index_content;
824 
825  }//end getIndexableContent()
826 
827 
837  function addIndexableContent($assetid, &$index_content)
838  {
839  // don't run if indexing turned OFF
840  if (!$this->attr('indexing')) return FALSE;
841  if (!is_array($index_content)) return FALSE;
842 
843  $index_content_found = FALSE;
844  $keys = array_keys($index_content);
845 
846  // Array to keep track of which context we are using for a certain
847  // assetid/component combination. We must track this separately; to use
848  // $defaults for this would result in only one item of split content
849  // to be indexed.
850  $defaults_context = Array();
851 
852  // List of values that will be added at the end using the 'use default'
853  // insert, after all custom values are entered.
854  $defaults = Array();
855  $add_content = Array();
856  // Pre-pack the custom indexable content query, so it doesn't have to
857  // prepare itself over and over again
858  if (!empty($keys)) {
859  foreach ($keys as $key) {
860  $data =& $index_content[$key];
861  $value = $data['value'];
862 
863  if (trim($value) == '') continue;
864 
865  switch ($data['type']) {
866  case 'datetime':
867  require_once SQ_FUDGE_PATH.'/general/datetime.inc';
868  if (!is_iso8601($value)) {
869  $value = ts_iso8601(strtotime($value));
870  }
871  break;
872 
873  //wysiwyg type would have the same characteristics to text type, e.g. a value which is longer than 255 characters will not be indexed
874  case 'wysiwyg':
875  case 'text':
876  $value = $this->_getRawNumberInText($value);
877  // filter noise words
878  if (!$this->isWordIndexable($value)) continue 2; // continue the foreach loop
879  break;
880 
881  default:
882  // Selection and thesaurus types do not need massaging.
883  // (splitIndexableContent() in DB plugin lowercases words
884  // already if required)
885  break;
886  }
887 
888  // work out whether we've already added the default for this
889  $default_key = $assetid.' '.$data['component'].' '.sha1($value);
890 
891  $bind_vars = Array(
892  'value' => $value,
893  'assetid' => $assetid,
894  'type_code' => $data['type_code'],
895  'component' => $data['component'],
896  'contextid' => ($data['contextid'] === NULL) ? 'default' : (string)$data['contextid'],
897  'type' => $data['type'],
898  'score' => $data['score'],
899  'use_default' => $data['use_default'] ? '1' : '0',
900  );
901 
902  // Save up the default values so we can put them in after custom
903  // values... but only if we have not seen them before (lock one
904  // context as the one we saw first, and only accept values from it)
905  if (((int)$bind_vars['use_default'] === 1) && ($bind_vars['contextid'] !== 'default')) {
906  if (array_key_exists($default_key, $defaults_context) === FALSE) {
907  $defaults_context[$default_key] = $bind_vars['contextid'];
908  }
909  if ($bind_vars['contextid'] === $defaults_context[$default_key]) {
910  $defaults[] = $bind_vars;
911  }
912  } else {
913  // Don't perform database transactions here as it can fail silently here without warning
914  $add_content[] = $bind_vars;
915  }
916 
917  }//end foreach
918  }//end if
919 
920  // This could be out of a transaction at this point, and in db1 (the
921  // read-only db). Change to db2, and wrap in a transaction so it doesn't
922  // autocommit all over the place
923  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
924  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
925 
926 
927  // And finally, add the indexable content
928  // This code was moved outside the loop below which creates both the default and now the content.
929  // This was due to a bug in PHP (<5.2) and Postgres 8.1 dropping transactions silently
930  // when a prepared statement is not executed.
931 
932  if (!empty($add_content)) {
933  $dal_query = MatrixDAL::getQuery('search_manager', 'addIndexableContent');
934  $query = $dal_query->prepare();
935 
936  foreach ($add_content as $bind_vars) {
937 
938  try {
939  // Non-default value, or non-contextable. Put it in straight away.
940  foreach ($bind_vars as $bind_var => $bind_value) {
941  MatrixDAL::bindValueToPdo($query, $bind_var, $bind_value);
942  }
943  MatrixDAL::execPdoQuery($query);
944  $index_content_found = TRUE;
945  } catch (Exception $e) {
946  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
947  throw new Exception('Unable to add indexable content due to database error: '.$e->getMessage());
948  }
949  }//end foreach
950  }//end if
951 
952  // Okay, shove in the default values, which will be determined by what
953  // context values are not already in there.
954  // Pre-pack the query first though, so it doesn't potentially get
955  // prepared 1000's of times over.
956  if (!empty($defaults)) {
957  if (DAL::getDbType() === 'oci') {
958 
959  // For Oracle system, we'll use two queries to add content into search index table
960  // First query to the get the contextids which doesn't have context values already there
961  // and second to insert the contextid along with other indexable content into index table
962  // See bug #4136
963  $dal_get_context_query = MatrixDAL::getQuery('search_manager', 'getDefaultContext');
964  $get_context_query = $dal_get_context_query->prepare();
965 
966  $dal_insert_query = MatrixDAL::getQuery('search_manager', 'addIndexableContent');
967  $insert_query = $dal_insert_query->prepare();
968 
969 
970  foreach ($defaults as $bind_vars) {
971  try {
972  MatrixDAL::bindValueToPdo($get_context_query, 'assetid', $bind_vars['assetid']);
973  MatrixDAL::bindValueToPdo($get_context_query, 'component', $bind_vars['component']);
974  $result = MatrixDAL::executePdoAll($get_context_query);
975 
976  foreach($result as $asset_context) {
977  $bind_vars['contextid'] = $asset_context['contextid'];
978  foreach ($bind_vars as $bind_var => $bind_value) {
979  MatrixDAL::bindValueToPdo($insert_query, $bind_var, $bind_value);
980  }
981  MatrixDAL::execPdoQuery($insert_query);
982  }
983 
984  $index_content_found = TRUE;
985  } catch (Exception $e) {
986  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
987  throw new Exception('Unable to add indexable content due to database error: '.$e->getMessage());
988  }
989  }//end foreach
990  } else {
991 
992  $dal_query = MatrixDAL::getQuery('search_manager', 'addDefaultIndexableContent');
993  $query = $dal_query->prepare();
994 
995  foreach ($defaults as $bind_vars) {
996  try {
997  foreach ($bind_vars as $bind_var => $bind_value) {
998  if ($bind_var !== 'contextid') {
999  MatrixDAL::bindValueToPdo($query, $bind_var, $bind_value);
1000  }
1001  }
1002  MatrixDAL::bindValueToPdo($query, 'assetid_1', $bind_vars['assetid']);
1003  MatrixDAL::bindValueToPdo($query, 'component_1', $bind_vars['component']);
1004  MatrixDAL::bindValueToPdo($query, 'value_1', $bind_vars['value']);
1005  MatrixDAL::execPdoQuery($query);
1006 
1007  $index_content_found = TRUE;
1008  } catch (Exception $e) {
1009  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1010  throw new Exception('Unable to add indexable content due to database error: '.$e->getMessage());
1011  }
1012  }
1013  }//end else
1014 
1015  }//end if
1016 
1017  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
1018  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1019 
1020  return $index_content_found;
1021 
1022  }//end addIndexableContent()
1023 
1024 
1041  function flushIndexableContent($assetid, $components=Array(), $contextids=NULL)
1042  {
1043  // don't run if indexing turned OFF
1044  if (!$this->attr('indexing')) return;
1045 
1046  // This could be out of a transaction at this point, and in db1 (the
1047  // read-only db). Change to db2, and wrap in a transaction so it doesn't
1048  // autocommit all over the place
1049  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
1050  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
1051 
1052  try {
1053  if ($contextids === NULL) {
1054  $contextids = array_keys($GLOBALS['SQ_SYSTEM']->getAllContexts());
1055  $contextids[] = 'default';
1056  }
1057 
1058  if (is_array($contextids) === FALSE) {
1059  $contextids = Array($contextids);
1060  }
1061 
1062 
1063  $bind_vars = Array(
1064  'assetid' => $assetid,
1065  'contextids' => $contextids,
1066  );
1067 
1068  if (!empty($components)) {
1069  $bind_vars['components'] = $components;
1070  } else {
1071  $bind_vars['components'] = NULL;
1072  }
1073 
1074  $affected_rows = MatrixDAL::executeQuery('search_manager', 'flushIndexableContent', $bind_vars);
1075  } catch (DALException $e) {
1076  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1077  throw new Exception('Unable to flush indexable content due to database error: '.$e->getMessage());
1078  }
1079 
1080  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
1081  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1082 
1083  }//end flushIndexableContent()
1084 
1085 
1105  function flushDefaultIndexableContent($assetid, $components=Array())
1106  {
1107  // don't run if indexing turned OFF
1108  if (!$this->attr('indexing')) return;
1109 
1110  // This could be out of a transaction at this point, and in db1 (the
1111  // read-only db). Change to db2, and wrap in a transaction so it doesn't
1112  // autocommit all over the place
1113  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
1114  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
1115 
1116  try {
1117  $bind_vars = Array(
1118  'assetid' => $assetid,
1119  'use_default' => '1',
1120  );
1121 
1122  if (!empty($components)) {
1123  $bind_vars['components'] = $components;
1124  } else {
1125  $bind_vars['components'] = NULL;
1126  }
1127 
1128  $affected_rows = MatrixDAL::executeQuery('search_manager', 'flushDefaultIndexableContent', $bind_vars);
1129  } catch (DALException $e) {
1130  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
1131  throw new Exception('Unable to flush default indexable content due to database error: '.$e->getMessage());
1132  }
1133 
1134  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
1135  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
1136 
1137  }//end flushIndexableContent()
1138 
1139 
1148  function getIndexedComponents($assetid)
1149  {
1150  $db = MatrixDAL::getDb();
1151 
1152  $sql = 'SELECT
1153  component
1154  FROM
1155  sq_sch_idx
1156  WHERE
1157  assetid = :assetid';
1158 
1159  try {
1160  $query = MatrixDAL::preparePdoQuery($sql);
1161  MatrixDAL::bindValueToPdo($query, 'assetid', $assetid);
1162  $components = MatrixDAL::executePdoAssoc($query, 0);
1163  } catch (Exception $e) {
1164  throw new Exception('Unable to get indexed components due to database error: '.$e->getMessage());
1165  }
1166 
1167  return $components;
1168 
1169  }//end getIndexedComponents()
1170 
1171 
1181  function getAssetidsByWordIntersection($source_id, $type=NULL)
1182  {
1183  $plugin =& $this->_getDBPlugin();
1184  $result = $plugin->getAssetidsByWordIntersection($source_id, $type);
1185 
1186  return $result;
1187 
1188  }//end getAssetidsByWordIntersection()
1189 
1190 
1198  {
1199  $components = Array(
1200  '__assetid__' => 'Asset ID',
1201  '__name__' => 'Asset Name',
1202  '__short_name__' => 'Asset Short Name',
1203  '__created__' => 'Created Date',
1204  '__updated__' => 'Updated Date',
1205  '__published__' => 'Published Date',
1206  '__status_changed__' => 'Status Changed Date',
1207  '__contents__' => 'Asset Contents',
1208  );
1209 
1210  return $components;
1211 
1212  }//end getIndexableAssetComponents()
1213 
1214 
1223  function getIndexableAttributes($type_code)
1224  {
1225  $attrs = $GLOBALS['SQ_SYSTEM']->am->getAssetTypeAttributes($type_code, Array('name', 'type', 'is_admin'));
1226 
1227  $indexable_attrs = Array();
1228  foreach ($attrs as $attr => $info) {
1229  if (!$attrs[$attr]['is_admin'] || $attrs[$attr]['is_admin'] == 'f') {
1230  $indexable_attrs[$attr] = $info;
1231  }
1232  }
1233  return $indexable_attrs;
1234 
1235  }//end getIndexableAttributes()
1236 
1237 
1246  function isAttributeIndexable($attrid)
1247  {
1248  $info = $GLOBALS['SQ_SYSTEM']->am->getAttributeInfo($attrid);
1249  return !$info[$attrid]['is_admin'];
1250 
1251  }//end isAttributeIndexable()
1252 
1253 
1262  function isAssetIndexable($assetid)
1263  {
1264  //check asset weight first because it has highest priority (asset weight > asset tree weight > global weight)
1265  $weights = $this->getAssetWeightings($assetid);
1266 
1267  if (empty($weights)) {
1268  //check asset tree weight and global weight (because global weight is also a type of tree weight with the treeid is root id (usually has assetid 1))
1269  $weights = $this->getAssetTreeWeightings($assetid, FALSE);
1270  }
1271 
1272  // if there is no indexed value in the array then we can assume
1273  // that indexing is on for this asset
1274  return (!isset($weights['indexed']) || $weights['indexed']);
1275 
1276  }//end isAssetIndexable()
1277 
1278 
1289  function isWordIndexable($word)
1290  {
1291  $plugin =& $this->_getDBPlugin();
1292 
1293  // Make sure db constraints aren't breached
1294  $max_length = $plugin->getMaxWordLength();
1295  if (($max_length > 0) && (strlen($word) > $max_length)) {
1296  return FALSE;
1297  }
1298 
1299  $min_length = $this->attr('min_word_length');
1300  return $this->isWhiteWord($word) || (strlen($word) >= $min_length && !$this->isNoiseWord($word));
1301 
1302  }//end isWordIndexable()
1303 
1304 
1316  function _getRawNumberInText($value)
1317  {
1318  if (is_array($value)) return $value;
1319  // check to see if this value is a raw number
1320  if (!preg_match('/[a-zA-Z]+/', $value)) {
1321  $pattern = Array(',', '+');
1322  $new_value = str_replace($pattern, '', $value);
1323  $new_value = preg_replace('/\s+/', '', $new_value);
1324 
1325  // if after removal of whitespace and , and + characters
1326  // this is just a raw number, index it without spaces
1327  if (preg_match('/^\d+$/', $new_value)) {
1328  $value = $new_value;
1329  }
1330  }
1331  return $value;
1332 
1333  }//end _getRawNumberInText()
1334 
1335 
1336 //-- METADATA INDEXING --//
1337 
1338 
1350  function onMetadataUpdate(&$broadcaster, $vars=Array())
1351  {
1352  if (!$this->attr('indexing')) return FALSE;
1353  // We've been told not to do this right now
1354  if ($broadcaster->shouldFastTrack('search_manager_reindex_metadata')) return FALSE;
1355 
1356  $this->reindexMetadata($broadcaster->id, $vars, TRUE);
1357 
1358  }//end onMetadataUpdate()
1359 
1360 
1372  function onMetadataDeleted(&$broadcaster, $vars=Array())
1373  {
1374  $mm = $GLOBALS['SQ_SYSTEM']->getMetadataManager();
1375  $fieldids = array_keys($mm->getMetadataFields(Array($vars['schemaid'])));
1376  for (reset($fieldids); NULL !== ($key = key($fieldids)); next($fieldids)) {
1377  $fieldids[$key] = 'metadata:'.$fieldids[$key];
1378  }
1379 
1380  $this->flushIndexableContent($broadcaster->id, $fieldids);
1381 
1382  }//end onMetadataDeleted()
1383 
1384 
1395  function reindexMetadata($assetid, $vars=Array(), $all_contexts=FALSE)
1396  {
1397  assert_valid_assetid($assetid);
1398  $mm = $GLOBALS['SQ_SYSTEM']->getMetadataManager();
1399 
1400  $contextid = (int)$GLOBALS['SQ_SYSTEM']->getContextId();
1401  $all_contextids = array_keys($GLOBALS['SQ_SYSTEM']->getAllContexts());
1402 
1403  if (in_array('all', $vars)) {
1404  $schema_ids = array_keys($mm->getSchemas($assetid));
1405  $fieldids = $mm->getMetadataFields($schema_ids);
1406  } else if (in_array('schemaid', array_keys($vars))) {
1407  $fieldids = $mm->getMetadataFields($vars['schemaid'], TRUE);
1408  } else {
1409  $fieldids = $GLOBALS['SQ_SYSTEM']->am->getAssetInfo($vars['fieldids'], Array(), TRUE, 'type_code');
1410  }
1411 
1412  if (empty($fieldids)) {
1413  $fieldids = $mm->getMetadataFields($mm->getSchemas($assetid, TRUE));
1414  }
1415 
1416  $flush_fieldids = Array();
1417  foreach (array_keys($fieldids) as $fieldid) {
1418  $flush_fieldids[] = 'metadata:'.$fieldid;
1419  }
1420 
1421  if (!empty($flush_fieldids)) {
1422  if ($all_contexts) {
1423  $this->flushIndexableContent($assetid, $flush_fieldids);
1424  } else {
1425  if ($contextid === 0) {
1426  $this->flushDefaultIndexableContent($assetid, $flush_fieldids);
1427  } else {
1428  $this->flushIndexableContent($assetid, $flush_fieldids, Array($contextid));
1429  }
1430  }
1431  }
1432 
1433  if (!$this->isAssetIndexable($assetid)) return;
1434 
1435  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
1436 
1437  $indexable = $this->getIndexableMetadataContent($asset, $fieldids, $all_contexts);
1438 
1439  $this->addIndexableContent($assetid, $indexable);
1440 
1441  }//end reindexMetadata()
1442 
1443 
1454  function getIndexableMetadataContent(&$asset, $fieldids=Array(), $all_contexts=TRUE)
1455  {
1456  $index_content = Array();
1457  $mm = $GLOBALS['SQ_SYSTEM']->getMetadataManager();
1458  $plugin =& $this->_getDBPlugin();
1459  $type_code = $asset->type();
1460 
1461  if (is_null($asset)) return $index_content;
1462 
1463  // Get the current contextid
1464  $contextid = $GLOBALS['SQ_SYSTEM']->getContextId();
1465  $context_list = $all_contexts ? array_keys($GLOBALS['SQ_SYSTEM']->getAllContexts()) : Array($contextid);
1466 
1467  if (empty($fieldids)) {
1468  $fieldids = $mm->getMetadataFields($mm->getSchemas($asset->id, TRUE));
1469  }
1470 
1471  // Get all metadata for each context in advance. This is quicker
1472  // than getting it for each context for each field later..
1473  $all_metadata = Array();
1474  foreach ($context_list as $c) {
1475  $all_metadata[$c] = $mm->getMetadata($asset->id, 0, $c);
1476  }
1477 
1478  foreach ($fieldids as $fieldid => $field_type) {
1479 
1480  $flag_match_found = FALSE;
1481  $current_links = $GLOBALS['SQ_SYSTEM']->am->getLinks($fieldid, SQ_LINK_TYPE_2, 'metadata_section', FALSE, 'minor', NULL, TRUE);
1482 
1483  foreach ($current_links as $link) {
1484  $section = $GLOBALS['SQ_SYSTEM']->am->getAsset($link['majorid'], $link['major_type_code']);
1485  $restrictions = $section->attr('restrict');
1486  if (!empty($restrictions)) {
1487  foreach($restrictions as $type_code_restricted => $inherit_it) {
1488  if ($inherit_it && !$flag_match_found) {
1489  $asset_type_with_parents = $GLOBALS['SQ_SYSTEM']->am->getAssetTypeInfo(Array($asset->id));
1490  foreach($asset_type_with_parents[$asset->id] as $index => $asset_type) {
1491  if (array_key_exists($asset_type[0], $restrictions) && !$flag_match_found) {
1492  $flag_match_found = TRUE;
1493  }
1494  }
1495  } else {
1496  if (array_key_exists($asset->type(), $restrictions)) {
1497  $flag_match_found = TRUE;
1498  }
1499  }
1500  }
1501  } else {
1502  // There is at least one section without any restrictions where this field is linked to
1503  $flag_match_found = TRUE;
1504  }
1505 
1506  // No need to check futher restrictions on other metadata sections
1507  if ($flag_match_found) break;
1508  }
1509 
1510  if (!$flag_match_found) {
1511  continue;
1512  }
1513 
1514  // dont index this fieldid if getMetadataWeighting return false
1515  if (FALSE === ($weighting = $this->getMetadataWeighting($asset->id, $fieldid))) {
1516  continue;
1517  }
1518 
1519  // new condition for 3.18 because of the result format changes
1520  if (is_array($field_type)) {
1521  $field_type = $field_type[0]['type_code'];
1522  }//end if
1523  $field = $GLOBALS['SQ_SYSTEM']->am->getAsset($fieldid, $field_type);
1524 
1525  // If metadata field is non-contextable then it means it will have same value for all contexts
1526  $is_contextable = (int)$field->attr('is_contextable') === 0 ? FALSE : TRUE;
1527  $context_to_index = $is_contextable ? $context_list : Array($context_list[0]);
1528 
1529  foreach($context_to_index as $this_contextid) {
1530 
1531  // Get metadata for this context (here's one we prepared earlier)
1532  $metadata = $all_metadata[$this_contextid];
1533 
1534  if (isset($metadata[$field->id][0]['value'])) {
1535  $value = $metadata[$field->id][0]['value'];
1536  } else if ($field instanceof Metadata_Field_WYSIWYG) {
1537  $value = $field->attr('default_html');
1538  } else {
1539  $value = $field->attr('default');
1540  }
1541 
1542  $use_default = empty($value) || $this_contextid === 'default' || $this_contextid === 0 ? TRUE : FALSE;
1543 
1544  $value_components = Array();
1545  Metadata_Field::decodeValueString($value, $value, $value_components);
1546 
1547  for($i = 0; $i < 3; $i++) { //attempt to replace nested keywords max 3 levels. See Bug #5295
1548  $keywords = retrieve_keywords_replacements($value, '.');
1549 
1550  $the_replacement = $mm->generateKeywordReplacements($asset, $keywords, FALSE);
1551 
1552  foreach($keywords as $keyword) {
1553 
1554  if (!isset($the_replacement[$keyword])) {
1555  // Looking for further replacements
1556  $the_replacement[$keyword] = str_replace('"', '\"', $asset->getKeywordReplacement($keyword));
1557 
1558  } else if ($the_replacement[$keyword] == '%'.$keyword.'%' && substr($keyword, 0, 15) == 'metadata_field_') {
1559 
1560  // Remove any modifiers from keyword
1561  $full_keyword = $keyword;
1562  $keyword = parse_keyword($keyword, $modifiers);
1563 
1564  // searching for keywords that replace the content of another field
1565  $fieldid = $mm->getFieldAssetIdFromName($asset->id, substr($keyword, 15));
1566  $keyword_replacement = $mm->getMetadataValueByAssetid($asset->id, $fieldid, TRUE, FALSE);
1567 
1568  if ($keyword_replacement !== NULL) {
1569  if (count($modifiers) > 0) {
1570  apply_keyword_modifiers($keyword_replacement, $modifiers, Array('assetid' => $asset->id));
1571  }
1572  $the_replacement[$full_keyword] = $keyword_replacement;
1573  } else {
1574  $the_replacement[$full_keyword] = '%'.$full_keyword.'%';
1575  }
1576 
1577  }//end if
1578 
1579  }//end foreach
1580 
1581  replace_keywords($value, $the_replacement);
1582  }//end for
1583 
1584  // check field_type to define the indexing way
1585  if ($field_type == 'metadata_field_date') {
1586  $index_content[] = Array(
1587  'value' => $value,
1588  'type_code' => $type_code,
1589  'type' => 'datetime',
1590  'component' => 'metadata:'.$field->id,
1591  'score' => $weighting,
1592  'contextid' => $this_contextid,
1593  'use_default' => $use_default,
1594  );
1595  } else {
1596 
1597  switch ($field_type) {
1598  case 'metadata_field_hierarchy':
1599  $key_type = 'selection';
1600  break;
1601 
1602  case 'metadata_field_select':
1603  if ($field->attr('multiple')) {
1604  $plugin =& $this->_getDBPlugin();
1605  $value = $plugin->handleMultipleMetadataSelect($value);
1606  }
1607 
1608  $key_type = 'selection';
1609  break;
1610 
1611  case 'metadata_field_multiple_text':
1612  $plugin =& $this->_getDBPlugin();
1613  $value = $plugin->handleMultipleMetadataSelect($value);
1614  $key_type = 'selection';
1615  break;
1616 
1617  case 'metadata_field_thesaurus':
1618  $key_type = 'thesaurus';
1619  // Replace comma with spaces so the values can be indexed correctly. This should be done here and not in get_word_counts/remove_silent_chars.
1620  $value = str_replace(',',' ', $value);
1621  break;
1622 
1623  case 'metadata_field_wysiwyg':
1624  $key_type = 'metadata_field_wysiwyg';
1625  $plugin =& $this->_getDBPlugin();
1626  $value = $plugin->handleMultipleMetadataSelect($value);
1627  break;
1628 
1629  default:
1630  $key_type = 'text';
1631  $value = $this->_getRawNumberInText($value);
1632  }
1633 
1634  $index_content = array_merge($index_content, $plugin->splitIndexableContent($value, $type_code, $key_type, 'metadata:'.$field->id, $weighting, $this_contextid, $use_default));
1635  }//end else
1636 
1637  }// end foreach contexts_to_use
1638 
1639  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($field);
1640  unset($field);
1641 
1642  }//end foreach fieldids
1643 
1644  return $index_content;
1645 
1646  }//end getIndexableMetadataContent()
1647 
1648 
1649 //-- WEIGHTINGS --//
1650 
1651 
1675  function getWeightings($assetid)
1676  {
1677  if (isset($this->_tmp['asset_weightings'][$assetid]) === FALSE) {
1678 
1679  // firstly, check if there are any weightings specific to this asset
1680  $weightings = $this->getAssetWeightings($assetid);
1681 
1682  if (empty($weightings)) {
1683 
1684  // if we can't find any weightings specific to this asset, check
1685  // to see if there are any tree weightings for this asset
1686  $weightings = $this->getAssetTreeWeightings($assetid, FALSE);
1687 
1688  // if we can't find any tree weightings that this asset exists under
1689  // check the asset type weightings.
1690  if (empty($weightings)) {
1691  $type_code = $GLOBALS['SQ_SYSTEM']->am->getAssetInfo(Array($assetid), Array(), TRUE, 'type_code');
1692  $type_code = array_get_index($type_code, $assetid, '');
1693  if (!empty($type_code)) {
1694  $weightings = $this->getAssetTypeWeightings($type_code);
1695  }
1696 
1697  // if there are no type weightings, then we will have to create
1698  // some default values for this assetid
1699  if (empty($weightings)) {
1700  $weightings = Array();
1701  $asset_comps = $this->getIndexableAssetComponents();
1702  $attr_comps = $this->getIndexableAttributes($type_code);
1703  $components = array_merge($asset_comps, $attr_comps);
1704 
1705  foreach (array_keys($components) as $component) {
1706  $weightings[$component] = 1;
1707  }
1708  $this->_tmp['asset_weightings'][$assetid] = $weightings;
1709  }
1710  }
1711  }
1712 
1713  // if not yet cached using default values, reorder array and cache
1714  if (isset($this->_tmp['asset_weightings'][$assetid]) === FALSE) {
1715  $indexed_weightings = Array();
1716  // only add the weightings if the individual components are indexed
1717  foreach (Array('attr_weights', 'asset_weights') as $type) {
1718  foreach ($weightings[$type] as $component => $info) {
1719  if ($info['indexed']) {
1720  $indexed_weightings[$component] = $info['weight'];
1721  }
1722  }
1723  }
1724 
1725  $this->_tmp['asset_weightings'][$assetid] = $indexed_weightings;
1726  }
1727 
1728  }//end if already cached
1729 
1730  return $this->_tmp['asset_weightings'][$assetid];
1731 
1732  }//end getWeightings()
1733 
1734 
1754  function getMetadataWeighting($assetid, $fieldid)
1755  {
1756  $mm = $GLOBALS['SQ_SYSTEM']->getMetadataManager();
1757 
1758  $section_link = $GLOBALS['SQ_SYSTEM']->am->getLink($fieldid, SQ_LINK_TYPE_2, 'metadata_section', TRUE, NULL, 'minor');
1759  if (empty($section_link)) {
1760  // the field is not attached to a section, so return default weighting
1761  return 1;
1762  }
1763 
1764  $schema_links = $GLOBALS['SQ_SYSTEM']->am->getLinks($section_link['majorid'], SQ_LINK_TYPE_2, 'metadata_schema', TRUE, 'minor');
1765  if (empty($schema_links)) {
1766  // the section is not attached to a schema, so return default weighting
1767  return 1;
1768  }
1769 
1770  $sectionid = $section_link['majorid'];
1771  $schemaids = Array();
1772 
1773  foreach ($schema_links as $schema_info) {
1774  $schemaids[] = $schema_info['majorid'];
1775  }
1776 
1777  $info = $GLOBALS['SQ_SYSTEM']->am->getAssetInfo(Array($assetid), Array(), TRUE, 'type_code');
1778  $type_code = $info[$assetid];
1779 
1780  $weight_sources = Array(
1781  // check metadata weightings in asset weight customisation
1782  0 => Array('function_name' => 'Asset' , 'params' => Array($assetid)),
1783  // check metadata weightings in asset type of asset tree customisation
1784  1 => Array('function_name' => 'AssetTree' , 'params' => Array($assetid, FALSE, FALSE, TRUE, FALSE)),
1785  // check generic metadata weightings of asset tree customisation
1786  2 => Array('function_name' => 'AssetTree' , 'params' => Array($assetid, FALSE, FALSE, FALSE, FALSE)),
1787  // check metadata weightings in asset type of global weight customisation
1788  3 => Array('function_name' => 'AssetType' , 'params' => Array($type_code)),
1789  // check generic metadata weightings of global weight customisation
1790  4 => Array('function_name' => 'AssetTree' , 'params' => Array($GLOBALS['SQ_SYSTEM']->am->getSystemAssetid('root_folder'))),
1791  );
1792 
1793  foreach ($weight_sources as $source) {
1794  $fn = 'get'.$source['function_name'].'Weightings';
1795  $weightings = call_user_func_array(Array($this, $fn), $source['params']);
1796  if (!empty($weightings) && isset($weightings['metadata_weights'])) {
1797  $weight = $this->_getLocalMetadataWeightings($weightings['metadata_weights'], $fieldid, $sectionid, $schemaids);
1798 
1799  // if the weight is false then we don't want to index this field
1800  if ($weight === FALSE) return FALSE;
1801 
1802  // if the weight is not -1 then this is the weight to use
1803  // otherwise continue to the next source
1804  if ($weight != -1) return $weight;
1805  }
1806  }
1807 
1808  // return a default weight of 1 for this field
1809  return 1;
1810 
1811  }//end getMetadataWeighting()
1812 
1813 
1830  function _getLocalMetadataWeightings($weightings, $fieldid, $sectionid, $schemaids)
1831  {
1832  if (!empty($weightings)) {
1833  // check to see if there is an explicit weighting for the field
1834  if (isset($weightings[$fieldid])) {
1835  if (!$weightings[$fieldid]['indexed']) return FALSE;
1836  return $weightings[$fieldid]['weight'];
1837  } else {
1838  // check for a weighting for the section that the field is under
1839  if (isset($weightings[$sectionid])) {
1840  if (!$weightings[$sectionid]['indexed']) return FALSE;
1841  return $weightings[$sectionid]['weight'];
1842  } else {
1843  // check all the schemas that the section belongs under
1844  // its possible that this field is in multiple schemas, so find the
1845  // hightest weighting from all the schemas that it is under
1846  $highest_weight = -1;
1847 
1848  // check the index flag on all the schemas, and only turn off indexing
1849  // for this fieldid if all schemas have indexing off
1850  $index = FALSE;
1851 
1852  foreach ($schemaids as $schemaid) {
1853  if (isset($weightings[$schemaid])) {
1854  if ($weightings[$schemaid]['indexed']) $index = TRUE;
1855  if ($weightings[$schemaid]['weight'] > $highest_weight) {
1856  $highest_weight = $weightings[$schemaid]['weight'];
1857  }
1858  }
1859  }
1860  if (!$index && $highest_weight !== -1) {
1861  return FALSE;
1862  }
1863  if ($highest_weight !== -1) return $highest_weight;
1864  }
1865  }
1866  }//end if !empty($weightings
1867  // no weight could be found
1868  return -1;
1869 
1870  }//end _getLocalMetadataWeightings()
1871 
1872 
1885  function getAssetTypeWeightings($type_code=NULL, $strict_type_code=FALSE)
1886  {
1887  $root_folder_assetid = $GLOBALS['SQ_SYSTEM']->am->getSystemAssetid('root_folder');
1888  // type weightings are stored under the root folder tree weightings
1889  $weights = $this->getAssetTreeWeightings($root_folder_assetid, TRUE, TRUE);
1890 
1891  $type_weights = array_get_index($weights, 'type_weights', Array());
1892 
1893  if (is_null($type_code)) return $type_weights;
1894 
1895  if (!$strict_type_code) {
1896  $type_hier = $GLOBALS['SQ_SYSTEM']->am->getTypeAncestors($type_code, TRUE);
1897  array_unshift($type_hier, $type_code);
1898  } else {
1899  $type_hier = Array($type_code);
1900  }
1901 
1902  $customised_type_codes = array_keys($type_weights);
1903  foreach ($type_hier as $type_code) {
1904  if (in_array($type_code, $customised_type_codes)) {
1905  return $type_weights[$type_code];
1906  }
1907  }
1908 
1909  return Array();
1910 
1911  }//end getAssetTypeWeightings()
1912 
1913 
1936  function getAssetTreeWeightings($assetid=NULL, $strict_assetid=TRUE, $strict_type_code=FALSE, $type_weights_only=TRUE, $include_global_weights=TRUE)
1937  {
1938  $weightings_file = $this->data_path.'/asset_tree_weightings.inc';
1939  if (file_exists($weightings_file)) {
1940  require $weightings_file;
1941 
1942  if (!$include_global_weights) {
1943  $root_folder_assetid = $GLOBALS['SQ_SYSTEM']->am->getSystemAssetid('root_folder');
1944  if (isset($asset_tree_weightings[$root_folder_assetid])) {
1945  unset($asset_tree_weightings[$root_folder_assetid]);
1946  }
1947  }
1948 
1949  if (!is_null($assetid)) {
1950 
1951  if ($strict_assetid) {
1952  if (isset($asset_tree_weightings[$assetid])) {
1953  return $asset_tree_weightings[$assetid];
1954  }
1955  } else {
1956  // there is no explicit weighting customisation for the assetid
1957  // so do a tree lookup for the closest one. getParents ORDER's by treeid
1958  // by default so we can be garunteed that the closest assetids are at the top of the array
1959  $parents = $GLOBALS['SQ_SYSTEM']->am->getParents($assetid);
1960 
1961  if ($type_weights_only){
1962  $type_code = $GLOBALS['SQ_SYSTEM']->am->getAssetInfo(Array($assetid), Array(), TRUE, 'type_code');
1963  $type_code = array_get_index($type_code, $assetid, '');
1964 
1965  // firstly, check to see if there is an explicit entry for this assetid
1966  if (isset($asset_tree_weightings[$assetid]['type_weights'][$type_code])) {
1967  return $asset_tree_weightings[$assetid]['type_weights'][$type_code];
1968  }
1969 
1970  // check to see if there are customisations for all asset types
1971  if (isset($asset_tree_weightings[$assetid]['type_weights']['asset'])) {
1972  return $asset_tree_weightings[$assetid]['type_weights']['asset'];
1973  }
1974 
1975  // if type code not found for some unknown reason, default to base asset
1976  if (empty($type_code)) $type_code = 'asset';
1977 
1978  // find the parent asset types of this type code
1979  if (!$strict_type_code) {
1980  $type_hier = $GLOBALS['SQ_SYSTEM']->am->getTypeAncestors($type_code, TRUE);
1981  // add the current type code so that we can also find
1982  // weightings for this type, not just its parent types
1983  array_unshift($type_hier, $type_code);
1984  } else {
1985  $type_hier = Array($type_code);
1986  }
1987 
1988  // Search by parents first; that way we will always find the
1989  // closest asset to what we are using, otherwise having a tree weight and global weight,
1990  // the tree weight does not get used when using all asset types
1991  $customised_assetids = array_keys($asset_tree_weightings);
1992  foreach (array_keys($parents) as $parentid) {
1993  if (in_array($parentid, $customised_assetids)) {
1994  if (isset($asset_tree_weightings[$parentid]['type_weights'])) {
1995  $customised_type_codes = array_keys($asset_tree_weightings[$parentid]['type_weights']);
1996  foreach ($type_hier as $parent_type_code) {
1997  if (in_array($parent_type_code, $customised_type_codes)) {
1998  return $asset_tree_weightings[$parentid]['type_weights'][$parent_type_code];
1999  }
2000  }//end foreach
2001  }
2002  }//end if
2003  }//end foreach
2004  } else {
2005  if (isset($asset_tree_weightings[$assetid])) {
2006  return $asset_tree_weightings[$assetid];
2007  }
2008 
2009  foreach (array_keys($parents) as $parentid) {
2010  if (isset($asset_tree_weightings[$parentid])) {
2011  return $asset_tree_weightings[$parentid];
2012  }//end if
2013  }//end foreach
2014  }//end else
2015  }//end else strict_assetid
2016  } else {
2017  return $asset_tree_weightings;
2018  }//end if !is_null(assetid)
2019  }//end if file_exists
2020 
2021  return Array();
2022 
2023  }//end getAssetTreeWeightings()
2024 
2025 
2034  function saveAssetTreeWeightings($asset_tree_weightings)
2035  {
2036  return $this->_saveWeightings($asset_tree_weightings, 'asset_tree');
2037 
2038  }//end saveAssetTreeWeightings()
2039 
2040 
2051  function getAssetWeightings($assetid=NULL)
2052  {
2053  $weightings_file = $this->data_path.'/asset_weightings.inc';
2054  if (file_exists($weightings_file)) {
2055  require $weightings_file;
2056  if (!is_null($assetid)) {
2057  if (isset($asset_weightings[$assetid])) {
2058  return $asset_weightings[$assetid];
2059  }
2060  } else {
2061  return $asset_weightings;
2062  }
2063  }
2064  return Array();
2065 
2066  }//end getAssetWeightings()
2067 
2068 
2078  function saveAssetWeightings($asset_weightings)
2079  {
2080  return $this->_saveWeightings($asset_weightings, 'asset');
2081 
2082  }//end saveAssetWeightings()
2083 
2084 
2095  function getMetadataWeightings($assetid=NULL)
2096  {
2097  $weightings_file = $this->data_path.'/metadata_weightings.inc';
2098  if (file_exists($weightings_file)) {
2099  require $weightings_file;
2100  if (!is_null($assetid)) {
2101  if (isset($metadata_weightings[$assetid])) {
2102  return $metadata_weightings[$assetid];
2103  }
2104  } else {
2105  return $metadata_weightings;
2106  }
2107  }
2108  return Array();
2109 
2110  }//end getMetadataWeightings()
2111 
2112 
2121  function saveMetadataWeightings($metadata_weightings)
2122  {
2123  return $this->_saveWeightings($metadata_weightings, 'metadata');
2124 
2125  }//end saveMetadataWeightings()
2126 
2127 
2138  function _saveWeightings($weightings, $type)
2139  {
2140  if (!in_array($type, Array('asset_type', 'asset', 'asset_tree', 'metadata'))) {
2141  trigger_error('Invalid weightings type "'.$type.'"', E_USER_ERROR);
2142  return FALSE;
2143  }
2144  $weightings_file = $this->data_path.'/'.$type.'_weightings.inc';
2145 
2146  $output = '<'.'?php'."\n".' $'.$type.'_weightings = ';
2147  $output .= var_export($weightings, TRUE);
2148  $output .= "\n?".'>';
2149 
2150  require_once SQ_FUDGE_PATH.'/general/file_system.inc';
2151 
2152  create_directory($this->data_path);
2153  if (!string_to_file($output, $weightings_file)) {
2154  trigger_error('Could not write out the '.str_replace('_', ' ', $type).' weightings to file', E_USER_WARNING);
2155  return FALSE;
2156  }
2157 
2158  // Flush the asset weightings cache
2159  unset($this->_tmp['asset_weightings']);
2160 
2161  return TRUE;
2162 
2163  }//end _saveWeightings()
2164 
2165 
2166 //-- KEYWORD EXTRACTION --//
2167 
2168 
2180  function extractKeywords(&$asset, $include_metadata=FALSE, $include_scores=FALSE)
2181  {
2182  $plugin =& $this->_getDBPlugin();
2183  return $plugin->extractKeywords($asset, $include_metadata, $include_scores);
2184 
2185  }//end extractKeywords()
2186 
2187 
2188 //-- TAG STRIP --//
2189 
2199  function _strip_tags($tags, $contents) {
2200  if (!is_array($tags)) {
2201  $tags = Array($tags);
2202  }
2203 
2204  foreach ($tags as $tag) {
2205  //a tag will start with <, there can be one / if it is close tag, and then the tag name,
2206  //to make sure that the tags with tag name are removed, not the tags start with that tag name,
2207  //e.g. remove <b> tag, not <br> tag. The tag must end with >, but before that, it can have / (e.g. <br/> ) or space followed by
2208  //other characters (to form attributes <h1 align="center">, or the / itself (<br />) )
2209  $contents = preg_replace('/<\/?'.$tag.'(\/|(\s+[^>]*))?>/i', '', $contents);
2210  }
2211 
2212  return $contents;
2213 
2214  }//end _strip_tags() function
2215 
2216 
2217 //-- SEARCH PROCESSING --//
2218 
2219 
2228  function generateWordList($words)
2229  {
2230  $plugin =& $this->_getDBPlugin();
2231  return $plugin->generateWordList($words);
2232 
2233  }//end generateWordList()
2234 
2235 
2280  function processSearch($search_info, $include_context=FALSE)
2281  {
2282 
2283  // start performance mode timer
2284  $GLOBALS['SQ_SYSTEM']->pm->startTimer($this, 'processSearch');
2285 
2286  require_once SQ_INCLUDE_PATH.'/general_occasional.inc'; // will definitely need this later.
2287  $contextid = $GLOBALS['SQ_SYSTEM']->getContextId();
2288 
2290  // CONSTRUCT CACHING KEY //
2292  // we need the cache manager to cache the results of queries
2293  $cm = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('cache_manager');
2294  if (empty($search_info['roots'])) {
2295  $search_cache_key = 'systemWide';
2296  } else {
2297  $search_cache_key = implode(',', $search_info['roots']);
2298  }
2299  if (isset($search_info['statuses'])) {
2300  $search_cache_key .= implode(':', $search_info['statuses']);
2301  }
2302  if (isset($search_info['asset_types'])) {
2303  foreach ($search_info['asset_types'] as $type => $inherit) {
2304  $search_cache_key .= $type.($inherit ? 'i' : 'n').':';
2305  }
2306  }
2307  $search_cache_key .= 'RL='.array_get_index($search_info, 'root_logic', 'OR');
2308  if ($include_context) {
2309  $search_cache_key .= 'includeContext';
2310  }
2311 
2312  // Add the context to the cache key, because the results will differ
2313  // depending on the context we are in (since we only search the current one)
2314  $search_cache_key .= '-ctx'.$contextid;
2315  $this->_tmp['cache_key'] = $search_cache_key;
2316  $this->_tmp['cm'] =& $cm;
2317 
2319  // PREPARE FIELDS //
2321  foreach ($search_info['fields'] as $field_name => $field_details) {
2322  $this->_prepareSearchField($search_info['fields'][$field_name]);
2323  }
2324 
2325  if (isset($search_info['exclude'])) {
2326  foreach ($search_info['exclude'] as $field_name => $field_details) {
2327  $this->_prepareSearchField($search_info['exclude'][$field_name]);
2328  }//end foreach
2329  }//end if
2330 
2332  // HANDLE ANY SHADOW ASSET OR BRIDGE ROOT NODES //
2334  if (!empty($search_info['roots'])) {
2335  $root_info = $GLOBALS['SQ_SYSTEM']->am->getAssetInfo($search_info['roots']);
2336  $shadow_results = Array();
2337  foreach ($search_info['roots'] as $i => $assetid) {
2338  if (array_key_exists($assetid, $root_info)) {
2339  $root = $root_info[$assetid];
2340  $GLOBALS['SQ_SYSTEM']->am->includeAsset($root['type_code']);
2341  // assert class implements bridge and contains method
2342  if (implements_interface($root['type_code'], 'bridge') && is_callable(Array($root['type_code'], 'processSearch'))) {
2343  $shadow_asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
2344  $shadow_results = $this->_combineResults($shadow_results, $shadow_asset->processSearch($search_info, $include_context), array_get_index($search_info, 'root_logic', 'OR'));
2345  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($shadow_asset);
2346  unset($search_info['roots'][$i]);
2347  $shadow_processed = TRUE;
2348  }
2349  }
2350  }
2351  if (empty($search_info['roots'])) {
2352  return $shadow_results;
2353  }
2354  }
2355 
2357  // CONSTRUCT BASE SEARCH QUERY //
2359  $base_query = $this->constructBaseSearchQuery($search_info);
2360  $base_query['select'][] = 'ai.assetid';
2361  $base_query['select'][] = 'SUM(ai.score) as search_score';
2362 
2363  if (!in_array('ai.assetid', array_get_index($base_query, 'group_by', Array()))) {
2364  $base_query['group_by'][] = 'ai.assetid';
2365  }
2366 
2367  if ($include_context) {
2368  $base_query['select'][] = 'ai.component as source';
2369  $base_query['group_by'][] = 'ai.component';
2370  }
2371 
2372  $base_query['where'][] = '(ai.contextid = '.MatrixDAL::quote($contextid).' OR ai.contextid = '.MatrixDAL::quote('default').')';
2373 
2375  // PROCESS EACH FIELD //
2377  $term_totals = Array();
2378 
2379  // Search fields types
2380  $search_field_types = Array('search' => $search_info['fields']);
2381  if (isset($search_info['exclude'])) {
2382  $search_field_types['exclude'] = $search_info['exclude'];
2383  }
2384 
2385  // Search result for 'search' and 'exclude' fields
2386  $search_results = Array();
2387  foreach($search_field_types as $search_field_type => $search_fields) {
2388  $final_search_type_results = NULL;
2389 
2390  // As per existing behaviour, differnet exclude fields are always combined using "OR"
2391  $field_logic = $search_field_type == 'exclude' ? 'OR' : array_get_index($search_info, 'field_logic', 'OR');
2392 
2393  foreach ($search_fields as $field_name => $field_details) {
2394  $field_results = NULL;
2395  $word_logic = array_get_index($field_details, 'word_logic', 'AND');
2396  $data_source_logic = array_get_index($field_details, 'data_source_logic', 'OR');
2397 
2398  if (empty($field_details['words'])) continue;
2399 
2400  if (isset($field_details['words']['from']) && isset($field_details['words']['to'])) {
2401  $search_type = 'date';
2402  } else if (array_key_exists('lower', $field_details['words']) && array_key_exists('upper', $field_details['words'])) {
2403  // need to use array_key_exists()
2404  $search_type = 'numeric';
2405  } else {
2406  $search_type = 'word';
2407  }
2408 
2409  $all_source_results = Array();
2410  $all_source_assetids = Array();
2411 
2412  foreach ($field_details['data_sources'] as $data_source) {
2413  if ($search_type == 'numeric') {
2414  $data_source_results = $this->_processNumericSearch($field_details['words'], $data_source, $base_query);
2415  } else if ($search_type == 'date') {
2416  $data_source_results = $this->_processDateSearch($field_details['words'], $data_source, $base_query);
2417  } else {
2418  $data_source_results = NULL;
2419  foreach ($field_details['words'] as $search_term) {
2420  //THE SILENT CHARACTERS WERE REMOVED BEFORE IN _prepareSearchField() FUNCTION
2421  // $info = remove_silent_chars($search_term, strpos(DAL::getDbType(), 'oci') !== FALSE ? TRUE : FALSE);
2422  $term_results = $this->_processWordSearch($search_term, $data_source, $base_query, $word_logic);
2423 
2424  if (!is_null($term_results)) {
2425  if (!isset($all_source_results[$search_term])) {
2426  $all_source_results[$search_term] = Array();
2427  }
2428  $all_source_results[$search_term] = $all_source_results[$search_term] + $term_results;
2429 
2430  // Records all the assets in which we find the term.
2431  $all_source_assetids = $all_source_assetids + $term_results;
2432 
2433  $term_totals[$search_term] = array_get_index($term_totals, $search_term, 0) + count($term_results);
2434  $data_source_results = $this->_combineResults($data_source_results, $term_results, $word_logic);
2435  }
2436  }
2437  }
2438  $field_results = $this->_combineResults($field_results, $data_source_results, $data_source_logic);
2439  }
2440 
2441  $cross_source_results = $all_source_assetids;
2442  foreach ($all_source_results as $term => $results) {
2443  $cross_source_results = array_intersect_assoc($cross_source_results, $results);
2444  }
2445 
2446  if (is_null($field_results)) {
2447  $field_results = $cross_source_results;
2448  } else {
2449  $field_results = $field_results + $cross_source_results;
2450  }
2451  // if this search field has its own logic, use it first
2452  if(isset($field_details['specific_field_logic'])) {
2453  $final_search_type_results = $this->_combineResults($final_search_type_results, $field_results, $field_details['specific_field_logic']);
2454  }
2455  else {
2456  $final_search_type_results = $this->_combineResults($final_search_type_results, $field_results, $field_logic);
2457  }
2458 
2459  }//end foreach
2460 
2461  $search_results[$search_field_type] = $final_search_type_results;
2462 
2463  }//end foreach
2464 
2465  // Final search results for 'search' terms
2466  $final_results = array_get_index($search_results, 'search', NULL);
2467 
2469  // PROCESS EXCLUDED TERMS //
2471  if (!empty($search_results['exclude'])) {
2472 
2473  foreach (array_keys($search_results['exclude']) as $exclude_assetid) {
2474  if (isset($final_results[$exclude_assetid])) {
2475  unset($final_results[$exclude_assetid]);
2476  }
2477  }
2478  }
2479 
2480  // join any results from bridges
2481  if (!empty($shadow_results)) {
2482  $final_results = $this->_combineResults($final_results, $shadow_results, array_get_index($search_info, 'root_logic', 'OR'));
2483  }
2484 
2485  if (is_null($final_results)) {
2486  // If this is the case then we didn't find anything to actually search on. Maybe no
2487  // words were long enough. Anyway, we should at least break the news in the correct format.
2488  $final_results = Array();
2489  }
2490 
2492  // LOG THIS SEARCH //
2494  if (!empty($search_info['requester'])) {
2495  $log_contents['terms'] = $term_totals;
2496  $log_contents['results'] = count($final_results);
2497  $log_contents['assetid'] = $search_info['requester'];
2498  log_write($log_contents, 'search');
2499  }
2500 
2501  // stop performance mode timer
2502  $GLOBALS['SQ_SYSTEM']->pm->stopTimer($this, 'processSearch');
2503 
2504  return $final_results;
2505 
2506  }//end processSearch()
2507 
2508 
2521  function _prepareSearchField(&$field_details)
2522  {
2523  if (empty($field_details['data_sources'])) {
2524  $field_details['data_sources'] = Array();
2525  return;
2526  }
2527 
2528  // figure out if we should treat this field as a date/time range
2529  $is_datetime = TRUE;
2530  $is_numeric = FALSE;
2531  foreach ($field_details['data_sources'] as $i => $data_source) {
2532  switch ($data_source['type']) {
2533 
2534  case 'asset_attrib':
2535  $asset_type = array_get_index($data_source['params'], 'asset_type');
2536  $attrid = array_get_index($data_source['params'], 'attrid');
2537  if (empty($asset_type) || empty($attrid)) {
2538  unset($field_details['data_sources'][$i]);
2539  continue;
2540  }
2541  $attribute = $GLOBALS['SQ_SYSTEM']->am->getAttribute($attrid);
2542  if (is_null($attribute)) {
2543  unset($field_details['data_sources'][$i]);
2544  continue;
2545  }
2546  if (!($attribute instanceof Asset_Attribute_Datetime)) {
2547  $is_datetime = FALSE;
2548  }
2549  if (($attribute instanceof Asset_Attribute_Int) || ($attribute instanceof Asset_Attribute_Float)) {
2550  $is_numeric = TRUE;
2551  }
2552  if (!$is_datetime || $is_numeric) {
2553  break 2;
2554  }
2555  break;
2556 
2557  case 'metadata':
2558  if (empty($data_source['params']['assetid'])) {
2559  unset($field_details['data_sources'][$i]);
2560  continue;
2561  }
2562  $metadata_field = $GLOBALS['SQ_SYSTEM']->am->getAsset($data_source['params']['assetid']);
2563  if (is_null($metadata_field)) {
2564  unset($field_details['data_sources'][$i]);
2565  continue;
2566  }
2567  if (!($metadata_field instanceof Metadata_Field_Date)) {
2568  $is_datetime = FALSE;
2569  break(2);
2570  }
2571  break;
2572 
2573  case 'standard':
2574  $asset_field = array_get_index($data_source['params'], 'field');
2575  if (empty($asset_field)) {
2576  unset($field_details['data_sources'][$i]);
2577  continue;
2578  }
2579  if (isset($this->standard_text_fields[$asset_field])) {
2580  $is_datetime = FALSE;
2581  break(2);
2582  } else if (!isset($this->standard_date_fields[$asset_field])) {
2583  trigger_error('Invalid standard field "'.$asset_field.'"', E_USER_WARNING);
2584  break(2);
2585  }
2586  break;
2587 
2588  case 'include_all':
2589  $is_datetime = FALSE;
2590  break(2);
2591  break;
2592 
2593  default:
2594  trigger_error('Unknown data source type '.$data_source['type'], E_USER_WARNING);
2595  break;
2596 
2597  }//end switch
2598  }//end foreach data source
2599 
2600  if ($is_datetime) {
2601  $from = array_get_index($field_details['words'], 'from', '---------- --:--:--');
2602  $to = array_get_index($field_details['words'], 'to', '---------- --:--:--');
2603  if ($from != '---------- --:--:--') {
2604  $from = str_replace('--:--:--', '00:00:00', $from);
2605  }
2606  if ($to != '---------- --:--:--') {
2607  $to = str_replace('--:--:--', '23:59:59', $to);
2608  }
2609  $field_details['words'] = Array('from' => $from, 'to' => $to);
2610  } else if ($is_numeric) {
2611  $lower = array_get_index($field_details['words'], 'lower', NULL);
2612  $upper = array_get_index($field_details['words'], 'upper', NULL);
2613  $field_details['words'] = Array('lower' => $lower, 'upper' => $upper);
2614  } else {
2615  // DONT remove unindexable words (then you cannot search for part of it)
2616  $words = Array();
2617 
2618  //only use mb_strtolower() function if it is supported
2619  $mb_support = FALSE;
2620  if (function_exists('mb_strtolower')) {
2621  $mb_support = TRUE;
2622  }
2623 
2624  // Multiple selections are passed in as an array, so filter those out to a string
2625  $word_list = '';
2626  if (is_array($field_details['words'])) {
2627  $count = 0;
2628  foreach ($field_details['words'] as $selected_word) {
2629  $count++ ;
2630  $word_list .= $selected_word;
2631  // bug fix #4047 Oracle search fails multiple word keys search with select/multiselect
2632  // if OR logic is used we will need to make sure there is a OR in between the search terms or else oracle will consider it to be AND
2633  // i.e - "search something like this" is considered to be "search AND something AND like AND this"
2634  $word_list .= ( DAL::getDbType() === 'oci' && $field_details['word_logic'] === 'OR' && count($field_details['words']) > $count ) ? ' OR ' : ' ';
2635  }//end foreach
2636  $word_list = trim($word_list);
2637  } else {
2638  $word_list = $field_details['words'];
2639  }//end if
2640 
2641  foreach ($this->generateWordList($word_list) as $word) {
2642  if ($mb_support) {
2643  $words[] = mb_strtolower($word, SQ_CONF_DEFAULT_CHARACTER_SET);
2644  } else {
2645  //lowercase the string which has non-ASCII characters by encoding them to HTML entities
2646  $word = htmlentities($word, ENT_NOQUOTES, SQ_CONF_DEFAULT_CHARACTER_SET);
2647  $word = strtolower($word);
2648  $word = html_entity_decode($word, ENT_NOQUOTES, SQ_CONF_DEFAULT_CHARACTER_SET);
2649  $words[] = $word;
2650  }
2651  }
2652 
2653  // our OR's have to strtolower'd :( oracle wont like it, get it back to OR
2654  if (DAL::getDbType() === 'oci' && $field_details['word_logic'] === 'OR' ) {
2655  foreach ($words as $index => $word) {
2656  $words[$index] = str_replace(' or ', ' OR ' , $word);
2657  }
2658  }
2659  $field_details['words'] = $words;
2660  }
2661 
2662  }//end _prepareSearchField()
2663 
2664 
2677  function _processWordSearch($search_term, $data_source, $base_query, $word_logic='AND')
2678  {
2679  $cache_key = $this->_tmp['cache_key'].$data_source['type'];
2680  switch ($data_source['type']) {
2681  case 'asset_attrib':
2682  $cache_key .= $data_source['params']['attrid'];
2683  break;
2684 
2685  case 'metadata':
2686  $cache_key .= $data_source['params']['assetid'];
2687  break;
2688 
2689  case 'standard':
2690  $cache_key .= $data_source['params']['field'];
2691  break;
2692  }
2693  $cache_key .= $search_term;
2694 
2695  $result = $this->_tmp['cm']->loadFromCache($this->id, $this->type(), $cache_key);
2696  if ($result === FALSE) {
2697  $plugin =& $this->_getDBPlugin();
2698  $result = $plugin->processWordSearch($this, $search_term, $data_source, $base_query, $word_logic);
2699  $r = $this->_tmp['cm']->saveToCache($this->id, $this->type(), $cache_key, serialize($result));
2700  } else {
2701  $result = unserialize($result);
2702  }
2703  return $result;
2704 
2705  }//end _processWordSearch()
2706 
2707 
2718  function _processDateSearch($date_range, $data_source, $base_query)
2719  {
2720  $cache_key = $this->_tmp['cache_key'].$data_source['type'];
2721  switch ($data_source['type']) {
2722  case 'asset_attrib':
2723  $cache_key .= $data_source['params']['attrid'];
2724  break;
2725 
2726  case 'metadata':
2727  $cache_key .= $data_source['params']['assetid'];
2728  break;
2729 
2730  case 'standard':
2731  $cache_key .= $data_source['params']['field'];
2732  break;
2733  }
2734  $cache_key .= 'from'.$date_range['from'].'to'.$date_range['to'];
2735 
2736  $result = $this->_tmp['cm']->loadFromCache($this->id, $this->type(), $cache_key);
2737  if ($result === FALSE) {
2738  $plugin =& $this->_getDBPlugin();
2739  $result = $plugin->processDateSearch($this, $date_range, $data_source, $base_query);
2740  $r = $this->_tmp['cm']->saveToCache($this->id, $this->type(), $cache_key, serialize($result));
2741  } else {
2742  $result = unserialize($result);
2743  }
2744  return $result;
2745 
2746  }//end _processDateSearch()
2747 
2748 
2759  function _processNumericSearch($numeric_range, $data_source, $base_query)
2760  {
2761  $cache_key = $this->_tmp['cache_key'].$data_source['type'];
2762  switch ($data_source['type']) {
2763  case 'asset_attrib':
2764  $cache_key .= $data_source['params']['attrid'];
2765  break;
2766 
2767  case 'metadata':
2768  $cache_key .= $data_source['params']['assetid'];
2769  break;
2770 
2771  case 'standard':
2772  $cache_key .= $data_source['params']['field'];
2773  break;
2774  }
2775  $cache_key .= 'l'.$numeric_range['lower'].'u'.$numeric_range['upper'];
2776 
2777  $result = $this->_tmp['cm']->loadFromCache($this->id, $this->type(), $cache_key);
2778  if ($result === FALSE) {
2779  $plugin =& $this->_getDBPlugin();
2780  $result = $plugin->processNumericSearch($this, $numeric_range, $data_source, $base_query);
2781  $r = $this->_tmp['cm']->saveToCache($this->id, $this->type(), $cache_key, serialize($result));
2782  } else {
2783  $result = unserialize($result);
2784  }
2785  return $result;
2786 
2787  }//end _processNumericSearch()
2788 
2789 
2806  function _combineResults($results_so_far, $new_results, $logic)
2807  {
2808  if (is_null($results_so_far)) {
2809  return $new_results;
2810  } else {
2811  if ($logic == 'OR') {
2812  $res_results = $results_so_far;
2813  } else {
2814  $res_results = Array();
2815  }
2816  if (isset($results_so_far[0])) {
2817  // getAll format, rather than getAssoc format
2818  foreach ($new_results as $new_result) {
2819  $added = FALSE;
2820  $found = FALSE;
2821  foreach ($results_so_far as $i => $old_result) {
2822  if ($old_result['assetid'] == $new_result['assetid']) {
2823  $found = TRUE;
2824  if ($old_result['source'] == $new_result['source']) {
2825  if ($logic == 'AND') {
2826  $old_result['search_score'] += $new_result['search_score'];
2827  $res_results[] = $old_result;
2828  } else {
2829  $res_results[$i]['search_score'] += $new_result['search_score'];
2830  }
2831  $added = TRUE;
2832  break;
2833  } else if ($logic == 'AND') {
2834  // the assetid is common even though the source isn't, so the
2835  // existing entry deserves to stay
2836  $res_results[] = $old_result;
2837  }
2838  }
2839  }
2840  if (!$added && ($found || ($logic == 'OR'))) {
2841  // we haven't added it yet and either the logic is "OR" or the assetid is common
2842  // so we add the new result now
2843  $res_results[] = $new_result;
2844  }
2845  }
2846  } else {
2847  foreach ($new_results as $assetid => $score) {
2848  if ($logic == 'OR') {
2849  $res_results[$assetid] = $score + array_get_index($results_so_far, $assetid);
2850  } else if (isset($results_so_far[$assetid])) {
2851  $res_results[$assetid] = $score + $results_so_far[$assetid];
2852  }
2853  }
2854  }
2855  return $res_results;
2856  }//end else
2857 
2858  }//end _combineResults()
2859 
2860 
2872  function processBasicSearch($search_info)
2873  {
2874  require_once SQ_INCLUDE_PATH.'/general_occasional.inc'; // will definitely need this later.
2875  $query_comps = $this->constructBaseSearchQuery($search_info);
2876 
2877  if (empty($query_comps)) return Array();
2878  $query_comps['select'][] = 'distinct ai.assetid';
2879  $query_comps['select'][] = '1 as weight';
2880 
2881  $bind_vars = array_get_index($query_comps, 'bind_vars', Array());
2882  $search_sql = implode_sql($query_comps);
2883  $query = MatrixDAL::preparePDOQuery($search_sql);
2884  foreach ($bind_vars as $bind_var => $value) {
2885  MatrixDAL::bindValueToPdo($query, $bind_var, $value);
2886  }//end foreach
2887 
2888  $search_results = Array();
2889 
2890  try {
2891  $search_results = MatrixDAL::executePdoGroupedAssoc($query);
2892  } catch (Exception $e) {
2893  throw new Exception('Unable to process basic search due to database error: '.$e->getMessage());
2894  }
2895 
2896  if (isset($search_info['limit'])) {
2897  // limiting the search results
2898  $search_results = array_slice($search_results, 0, $search_info['limit'], TRUE);
2899  }
2900 
2901  // get results from any shadow asset root nodes
2902  $root_info = $GLOBALS['SQ_SYSTEM']->am->getAssetInfo($search_info['roots']);
2903 
2904  foreach ($root_info as $assetid => $root) {
2905  $shadow_results = Array();
2906  $GLOBALS['SQ_SYSTEM']->am->includeAsset($root['type_code']);
2907 
2908  // assert class implements bridge and contains method
2909  if (implements_interface($root['type_code'], 'bridge') && is_callable(Array($root['type_code'], 'processBasicSearch'))) {
2910  $shadow_asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
2911  $shadow_results += $shadow_asset->processBasicSearch($search_info);
2912  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($shadow_asset);
2913  }
2914  }
2915 
2916  if (!empty($shadow_results)) {
2917  $search_results += $shadow_results;
2918  }
2919 
2920  return $search_results;
2921 
2922  }//end processBasicSearch()
2923 
2924 
2938  function spellCheckWord($word, $language=SQ_TOOL_SPELL_CHECKER_LANG)
2939  {
2940  if (!$this->spellCheckAvailable()) {
2941  // spell check not available, bugger
2942  return Array();
2943  }
2944 
2945  $pspell_config = pspell_config_create($language);
2946  pspell_config_mode($pspell_config, PSPELL_FAST);
2947  $pspell_instance = pspell_new_config($pspell_config);
2948 
2949  if (!$pspell_instance) return Array();
2950 
2951  $check_spelling = TRUE;
2952 
2953  // ignore numbers
2954  if ($check_spelling) {
2955  $converted_word = (int)$word;
2956  if ((string)$converted_word == $word) {
2957  $check_spelling = FALSE;
2958  }
2959  }
2960 
2961  // ignore words in all uppercase
2962  if ($check_spelling) {
2963  $converted_word = strtoupper($word);
2964  if ($converted_word == $word) $check_spelling = FALSE;
2965  }
2966 
2967  // if we aren't checking spelling OR word is correct, return no suggestions
2968  if (!$check_spelling || pspell_check($pspell_instance, $word)) {
2969  return Array();
2970  } else {
2971  // word incorrect, if any suggestions then return them
2972  $suggestions = pspell_suggest($pspell_instance, $word);
2973  return $suggestions;
2974  }
2975 
2976  }//end spellCheckWord()
2977 
2978 
2990  {
2991  return extension_loaded('pspell');
2992 
2993  }//end spellCheckAvailable()
2994 
2995 
3021  function constructBaseSearchQuery($search_info)
3022  {
3023  $db = MatrixDAL::getDb();
3024 
3031  $query = Array(
3032  'select' => Array(),
3033  'from' => Array('sq_sch_idx ai'),
3034  'join' => Array(),
3035  'where' => Array(),
3036  'where_joiner' => 'AND',
3037  'order_by' => Array(),
3038  );
3039 
3046  $subquery = Array(
3047  'select' => Array('a.assetid'),
3048  'from' => Array('sq_ast a'),
3049  'join' => Array(),
3050  'where' => Array(),
3051  'where_joiner' => 'AND',
3052  'order_by' => Array(),
3053  );
3054 
3059  $permission_check_query = Array(
3060  'select' => Array('p.assetid'),
3061  'from' => Array('sq_ast_perm p'),
3062  'join' => Array(),
3063  'where' => Array(),
3064  'where_joiner' => 'AND',
3065  'order_by' => Array(),
3066  );
3067 
3068  // TREE LOCATIONS
3069  if (!empty($search_info['roots'])) {
3070  $root_logic = array_get_index($search_info, 'root_logic', 'OR');
3071 
3072  // get the treeids of our search roots
3073  try {
3074  $bind_vars = Array(
3075  'minorids' => $search_info['roots'],
3076  );
3077  $root_tree_ids = MatrixDAL::executeGroupedAssoc('search_manager', 'getSearchRootTreeids', $bind_vars);
3078  } catch (Exception $e) {
3079  throw new Exception('Unable to root tree id due to database error: '.$e->getMessage());
3080  }
3081 
3082  if ($root_logic == 'AND') {
3083  foreach (array_values($search_info['roots']) as $i => $rootid) {
3084  $subquery['join'][] = 'INNER JOIN sq_ast_lnk l'.$i.' ON l'.$i.'.minorid = a.assetid'; // join the link tree and link tables
3085  $subquery['join'][] = 'INNER JOIN sq_ast_lnk_tree t'.$i.' ON l'.$i.'.linkid = t'.$i.'.linkid'; // join them to the asset table
3086 
3087  $treeid = $root_tree_ids[$rootid][0]['treeid'];
3088  $subquery['where'][] = 't'.$i.'.treeid LIKE '.MatrixDAL::quote($treeid.'%');
3089  }
3090  } else {
3091  $subquery['join'][] = 'INNER JOIN sq_ast_lnk l ON l.minorid = a.assetid';
3092  $subquery['join'][] = 'INNER JOIN sq_ast_lnk_tree t ON t.linkid = l.linkid';
3093 
3094  $treeid_wheres = Array();
3095  foreach ($root_tree_ids as $treeid) {
3096  $treeid_wheres[] = '(t.treeid LIKE '.MatrixDAL::quote($treeid[0]['treeid'].'%').')';
3097  }
3098  if (!empty($treeid_wheres)) {
3099  if (count($treeid_wheres) > 1 ) {
3100  $subquery['where'][] = '('.implode(' OR ', $treeid_wheres).')';
3101  } else {
3102  $subquery['where'][] = implode(' OR ', $treeid_wheres);
3103  }
3104  }
3105  }
3106  }//end if !empty($search_info['roots'])
3107 
3108  // ACCESS RESTRICTIONS
3109  $user_restrictions = (!$GLOBALS['SQ_SYSTEM']->userRoot() && !$GLOBALS['SQ_SYSTEM']->userSystemAdmin());
3110  if ($user_restrictions) {
3111  $no_roles = ((isset($search_info['no_roles_check']) && $search_info['no_roles_check'] == 1) || (SQ_CONF_ENABLE_ROLES_PERM_SYSTEM == '0')) ? TRUE : FALSE;
3112  $no_group_access_check = (isset($search_info['no_group_access_check']) && $search_info['no_group_access_check'] == 1) ? TRUE : FALSE;
3113  if (!$no_roles) {
3114  $permission_check_query['join'][] = 'LEFT JOIN sq_vw_ast_role r ON (p.userid = r.roleid AND r.assetid=p.assetid)';
3115  }
3116 
3117  $public_user = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('public_user');
3118  $current_user =& $GLOBALS['SQ_SYSTEM']->user;
3119  if (isset($search_info['search_as_public_user'])) {
3120  $search_as_public_user = $search_info['search_as_public_user'];
3121  } else {
3122  $search_as_public_user = 0;
3123  }
3124  $public_user_access = (empty($current_user) || $search_as_public_user);
3125  if ($public_user_access) {
3126  if (!$no_group_access_check) {
3127  $userids = $public_user->getUserGroups();
3128  }//end if
3129  } else {
3130  if (!$no_group_access_check) {
3131  $userids = $current_user->getUserGroups();
3132  }//end if
3133  $userids[] = $current_user->id;
3134  }
3135 
3136  $userids[] = $public_user->id;
3137 
3138  $userids = array_unique($userids);
3139 
3140  $query['bind_vars'] = Array();
3141  $bind_vars = Array();
3142  for (reset($userids); NULL !== ($i = key($userids)); next($userids)) {
3143  $bind_vars[] = ':user'.$i;
3144  $query['bind_vars']['user'.$i] = $userids[$i];
3145  }
3146 
3147  $usrids_str = implode(',', $bind_vars);
3148 
3149  $userid_check = '(
3150  p.userid IN ('. $usrids_str . ')';
3151  if (!$no_roles) {
3152  $userid_check .= ' OR r.userid IN (' . $usrids_str . ')';
3153  }
3154  $userid_check .= ') ';
3155  $permission_check_query['where'][] = $userid_check;
3156 
3157  // If we are searching as public user, we don't want to have group and roles check with permission granted.
3158  // We want to get all denied access as well. But if we are logged in, then we only want public granted access rows.
3159  $check_group_role = '';
3160  if (!$public_user_access) {
3161  $check_group_role = '(
3162  (p.permission = '.MatrixDAL::quote(SQ_PERMISSION_READ).'
3163  AND (
3164  (p.userid <> '.MatrixDAL::quote($public_user->id);
3165  if (!$no_roles) {
3166  if ($public_user_access) {
3167  $check_group_role .= ' OR ';
3168  } else {
3169  $check_group_role .= ' AND ';
3170  }
3171  $check_group_role .= ' (r.userid IS NULL OR r.userid <> '.MatrixDAL::quote($public_user->id).')';
3172  }
3173  $check_group_role .= ')
3174  OR (p.userid = '.MatrixDAL::quote($public_user->id).' AND granted = \'1\')';
3175  if (!$no_roles) {
3176  $check_group_role .= ' OR (r.userid = '.MatrixDAL::quote($public_user->id).' AND granted = \'1\')';
3177  }
3178 
3179  $check_group_role .= '
3180  )
3181  )
3182  OR
3183  (p.permission > '.MatrixDAL::quote(SQ_PERMISSION_READ).' AND p.granted = \'1\')
3184  )';
3185  }
3186 
3187  if (!empty($check_group_role)) {
3188  $permission_check_query['where'][] = $check_group_role;
3189  }//end if
3190  }//end if
3191 
3192  // STATUS RESTRICTIONS
3193  if (!empty($search_info['statuses'])) {
3194  $statuses = $search_info['statuses'];
3195  if (array_sum($statuses) != SQ_SC_STATUS_ALL) {
3196  foreach ($statuses as $i => $status) {
3197  $statuses[$i] = MatrixDAL::quote($status);
3198  }
3199  $subquery['where'][] = 'a.status IN ('.implode(', ', $statuses).')';
3200  }
3201  } else {
3202  // if there are no status restrictions, default to LIVE assets only
3203  $subquery['where'][] = 'a.status >= '.MatrixDAL::quote(SQ_STATUS_LIVE);
3204  }
3205 
3206  // ASSET TYPE RESTRICTIONS
3207  if (!empty($search_info['asset_types'])) {
3208  $inherited_types = Array();
3209  $normal_types = Array();
3210  for (reset($search_info['asset_types']); NULL !== ($i = key($search_info['asset_types'])); next($search_info['asset_types'])) {
3211  if ($search_info['asset_types'][$i] == 1) {
3212  $inherited_types[] = MatrixDAL::quote($i);
3213  } else {
3214  $normal_types[] = MatrixDAL::quote($i);
3215  }
3216  }
3217 
3218  $type_code_cond = Array();
3219 
3220  // if we have inherited types and/or normal types
3221  if (!empty($inherited_types)) {
3222  $type_code_cond[] = 'inhd_type_code IN ('.implode(', ', $inherited_types).')';
3223  if (!empty($normal_types)) {
3224  $type_code_cond[] = 'type_code IN ('.implode(', ', $normal_types).')';
3225  }
3226  $type_code_cond = implode(' OR ', $type_code_cond);
3227  $subquery['where'][] = 'a.type_code IN (
3228  SELECT type_code
3229  FROM sq_ast_typ_inhd
3230  WHERE '.$type_code_cond.'
3231  )';
3232  } else {
3233  // if we only got normal type instead we are not using the subquery
3234  $subquery['where'][] = 'a.type_code IN ('.implode(', ', $normal_types).')';
3235  }
3236  }
3237 
3246  $subquery_string = 'SELECT ' . implode(',', $subquery['select']) . ' FROM ' . implode(',', $subquery['from']) . ' ' . implode(' ', $subquery['join']);
3247  if (!empty($subquery['where'])) {
3248  $subquery_string .= ' WHERE ' . implode(' ' . $subquery['where_joiner'] . ' ', $subquery['where']);
3249  }
3250 
3251  $db_type = DAL::getDbType();
3252 
3258  if (!empty($permission_check_query['where'])) {
3265  $permission_check_query['where'][] = 'a.assetid=p.assetid';
3266 
3273  $permission_check_query_string = 'SELECT ' . implode(',', $permission_check_query['select']) . ' FROM ' . implode(',', $permission_check_query['from']) . ' ' . implode(' ', $permission_check_query['join']);
3274  $permission_check_query_string .= ' WHERE ' . implode(' ' . $permission_check_query['where_joiner'] . ' ', $permission_check_query['where']);
3275  $permission_check_query_string .= ' GROUP BY ' . implode(',', $permission_check_query['select']);
3276  $permission_check_query_string .= ' HAVING MIN(p.granted) <> \'0\'';
3277 
3278  if (!empty($subquery['where'])) {
3279  $subquery_string .= ' AND ';
3280  } else {
3281  $subquery_string .= ' WHERE ';
3282  }
3283  $subquery_string .= ' a.assetid IN (';
3284  $subquery_string .= $permission_check_query_string;
3285  $subquery_string .= ')';
3286  }
3287 
3293  if ($db_type == 'oci') {
3294  $query['where'][] = 'ai.assetid IN (' . $subquery_string . ')';
3295  }
3296 
3301  if ($db_type == 'pgsql') {
3302  $subquery_string = '(' . $subquery_string . ') asset_check';
3303  $query['from'][] = $subquery_string;
3304  $query['where'][] = 'ai.assetid=asset_check.assetid';
3305  }
3306 
3307  return $query;
3308 
3309  }//end constructBaseSearchQuery()
3310 
3311 
3325  function combineAssetScores($asset_scores1, $asset_scores2, $logic='AND', $word=NULL)
3326  {
3327  $result_scores = NULL;
3328  if (!is_array($asset_scores1) && !is_array($asset_scores2)) {
3329  $result_scores = Array();
3330  } else if (!is_array($asset_scores2)) {
3331  $result_scores = $asset_scores1;
3332  } else if (!is_array($asset_scores1)) {
3333  $result_scores = $asset_scores2;
3334  }
3335 
3336  if (!is_null($result_scores)) {
3337  if (is_array($result_scores) && !empty($result_scores) && !empty($word)) {
3338  foreach ($result_scores as $assetid => $data) {
3339  $this->_tmp['term_totals'][$assetid][$word] = $word;
3340  }
3341  }
3342  return $result_scores;
3343  }
3344 
3345  // find the intersection/union (depending on logic) of the assetids in field_asset_scores and results
3346  $assets1 = array_keys($asset_scores1);
3347  $assets2 = array_keys($asset_scores2);
3348 
3349  $out_asset_scores = $asset_scores1;
3350 
3351  if ($logic == 'AND') {
3352  $common_assets = array_intersect($assets1, $assets2);
3353 
3354  // get the assets that are to be removed in field_asset_scores
3355  $removed_assets = array_diff($assets1, $assets2);
3356  foreach ($removed_assets as $assetid) {
3357  unset($out_asset_scores[$assetid]);
3358  if (isset($this->_tmp['term_totals'][$assetid])) {
3359  unset($this->_tmp['term_totals'][$assetid]);
3360  }
3361  }
3362 
3363  foreach ($common_assets as $assetid) {
3364  if (isset($out_asset_scores[$assetid])) {
3365  $out_asset_scores[$assetid]['search_score'] = $out_asset_scores[$assetid]['search_score'] + $asset_scores2[$assetid]['search_score'];
3366  } else {
3367  $out_asset_scores[$assetid] = $asset_scores2[$assetid];
3368  }
3369 
3370  if (!empty($word)) {
3371  $this->_tmp['term_totals'][$assetid][$word] = $word;
3372  }
3373 
3374  }
3375 
3376  } else if ($logic == 'OR') {
3377 
3378  foreach ($asset_scores2 as $assetid => $data) {
3379  if (isset($out_asset_scores[$assetid])) {
3380  $out_asset_scores[$assetid]['search_score'] += $asset_scores2[$assetid]['search_score'];
3381  } else {
3382  $out_asset_scores[$assetid] = $asset_scores2[$assetid];
3383  }
3384 
3385  if (!empty($word)) {
3386  $this->_tmp['term_totals'][$assetid][$word] = $word;
3387  }
3388 
3389  }
3390  }
3391  return $out_asset_scores;
3392 
3393  }//end combineAssetScores()
3394 
3395 
3402  function getNoiseWords()
3403  {
3404  return $this->attr('noise_word_list');
3405 
3406  }//end getNoiseWords()
3407 
3408 
3415  function getWhiteWords()
3416  {
3417  return $this->attr('white_word_list');
3418 
3419  }//end getWhiteWords()
3420 
3421 
3430  function isNoiseWord($word)
3431  {
3432  $words = $this->attr('noise_word_list');
3433  foreach ($words as $key => $value) {
3434  if (strcasecmp($key, $word) == 0) return TRUE;
3435  }
3436  return FALSE;
3437 
3438  }//end isNoiseWord()
3439 
3440 
3449  function isWhiteWord($word)
3450  {
3451  $words = $this->attr('white_word_list');
3452  foreach ($words as $key => $value) {
3453  if (strcasecmp($key, $word) == 0) return TRUE;
3454  }
3455  return FALSE;
3456 
3457  }//end isWhiteWord()
3458 
3459 
3466  function getMinWordLength()
3467  {
3468  return $this->attr('min_word_length');
3469 
3470  }//end getMinWordLength()
3471 
3472 
3482  function &_getDBPlugin()
3483  {
3484  if (is_null($this->_db_plugin)) {
3485  $db_type = DAL::getDbType();
3486 
3487  $dir = dirname(__FILE__).'/plugins/'.strtolower($db_type);
3488  $class_name = 'search_manager_plugin_'.$db_type;
3489  require_once $dir.'/'.$class_name.'.inc';
3490  $this->_db_plugin = new $class_name();
3491  }
3492 
3493  return $this->_db_plugin;
3494 
3495  }//end _getDBPlugin()
3496 
3497 
3506  function getCondensedResults($results)
3507  {
3508  $searched_results = Array();
3509 
3510  // transform the array into a better working format
3511  foreach ($results as $result) {
3512  if (empty($searched_results[$result['assetid']])) {
3513  $searched_results[$result['assetid']]['score'] = 0;
3514  }
3515  $searched_results[$result['assetid']]['sources'][] = $result['source'];
3516  $searched_results[$result['assetid']]['score'] += $result['search_score'];
3517  }
3518 
3519  uasort($searched_results, create_function('$a,$b', 'return $b["score"] - $a["score"];'));
3520 
3521  return $searched_results;
3522 
3523  }//end getCondensedResults()
3524 
3525 
3541  function getFormattedResults($results, $search_string, $full_content = TRUE)
3542  {
3543  $search_string = preg_replace('/([\.\\\!\+\*\?\[\]\^\$\(\)\=\!<>\|\:]+)/i', '\\\${1}',$search_string);
3544  $search_string = trim(preg_replace('/[\~\&\|\/]/i', ' ', $search_string));
3545 
3546  $mm = $GLOBALS['SQ_SYSTEM']->getMetadataManager();
3547  $plugin =& $this->_getDBPlugin();
3548 
3549  $formatted_results = Array();
3550 
3551  $words = $plugin->getWords($search_string);
3552 
3553  // populate the results array as required
3554  // note we dont include fields that dont have matches
3555  foreach ($results as $assetid => $content) {
3556  $content = $content['sources'];
3557  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid);
3558 
3559  foreach ($content as $content_type) {
3560  switch (TRUE) {
3561 
3562  // asset contents containing search string
3563  case preg_match('/contents/', $content_type) > 0:
3564  // not need to retrieve full content which could be time wasting
3565  if(!$full_content) {
3566  $formatted_results[$assetid]['contents'][0]='...'.$search_string.'...';
3567  break;
3568  }
3569  $asset_contents = $GLOBALS['SQ_SYSTEM']->am->getEditableContents($assetid);
3570 
3571  // If there is no editable contents for the asset, see if the content was generated (eg; for PDF files)
3572  if (!$asset_contents) {
3573  $asset_contents = Array(
3574  $assetid => $asset->getContent(),
3575  );
3576  }
3577 
3578  foreach ($asset_contents as $contents_id => $contents_value) {
3579 
3580  foreach ($words as $word) {
3581  if (preg_match('/'.htmlentities($word).'/i', $contents_value) || preg_match('/'.($word).'/i', $contents_value)) {
3582  $formatted_results[$assetid]['contents'][$contents_id] = $contents_value;
3583  break;
3584  }
3585  }
3586  }
3587  break;
3588 
3589  // attributes containing search string
3590  // attributes come in 2 formats, __attribute__ and attr:attrname
3591  case preg_match('/__(.+)__/', $content_type, $matches) > 0:
3592  case preg_match('/attr:(.+)$/', $content_type, $matches) > 0:
3593  if (isset($asset->vars[$matches[1]])) {
3594  $attribute = $asset->attr($matches[1]);
3595 
3596  foreach ($words as $word) {
3597  if (preg_match("/$word/i", $attribute)) {
3598  $formatted_results[$assetid]['attributes'][$matches[1]] = $attribute;
3599  break;
3600  }
3601  }
3602  }
3603  break;
3604 
3605  // metadata containing search string
3606  case preg_match('/metadata/', $content_type) > 0:
3607  // we've already set metadata for this asset
3608  if (isset($formatted_results[$assetid]['metadata'])) {
3609  break;
3610  }
3611 
3612  $metadata = $mm->getMetadata($assetid);
3613 
3614  foreach ($metadata as $fieldid => $data) {
3615  foreach ($words as $word) {
3616  if (preg_match("/$word/i", $data[0]['value'])) {
3617  $formatted_results[$assetid]['metadata'][$fieldid] = $data;
3618  break;
3619  }
3620  }
3621  }
3622 
3623  // if no metadata was set, then we were looking for the schema
3624  if (!isset($formatted_results[$assetid]['metadata'])) {
3625  $schemas = $mm->getSchemas($assetid, TRUE);
3626 
3627  foreach ($schemas as $schemaid) {
3628  $schema = $GLOBALS['SQ_SYSTEM']->am->getAsset($schemaid);
3629  $schema_data = $mm->getSchemaDefaultValues($schema->id);
3630  foreach ($schema_data as $fieldid => $data) {
3631  foreach ($words as $word) {
3632  if (preg_match("/$word/i", $data['value'])) {
3633  $formatted_results[$schema->id]['schema'][$fieldid] = $data;
3634  break;
3635  }
3636  }
3637  }
3638 
3639  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($schema);
3640  }
3641  }
3642  break;
3643 
3644  default:
3645  break;
3646 
3647  }//end switch content type
3648  }//end foreach $assetid
3649 
3650  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($asset);
3651 
3652  }//end foreach $results
3653 
3654  return $formatted_results;
3655 
3656  }//end getFormattedResults()
3657 
3658 
3659 }//end class
3660 
3661 ?>