示例#1
0
 /**
  * Performs a search by calling the search() method on the underlying search engine plugin
  * Information about all searches is logged to ca_search_log
  *
  * @param string $ps_search The search to perform; engine takes Lucene syntax query
  * @param SearchResult $po_result  A newly instantiated sub-class of SearchResult to place search results into and return. If this is not set, then a generic SearchResults object will be returned.
  * @param array $pa_options Optional array of options for the search. Options include:
  *
  *		sort = field or attribute to sort on in <table name>.<field or attribute name> format (eg. ca_objects.idno); default is to sort on relevance (aka. sort='_natural')
  *		sortDirection = direction to sort results by, either 'asc' for ascending order or 'desc' for descending order; default is 'asc'
  *		no_cache = if true, search is performed regardless of whether results for the search are already cached; default is false
  *		limit = if set then search results will be limited to the quantity specified. If not set then all results are returned.
  *		form_id = optional form identifier string to record in log for search
  *		log_details = optional form description to record in log for search
  *		search_source = optional source indicator text to record in log for search
  *		checkAccess = optional array of access values to filter results on
  *		showDeleted = if set to true, related items that have been deleted are returned. Default is false.
  *		deletedOnly = if set to true, only deleted items are returned. Default is false.
  *		limitToModifiedOn = if set returned results will be limited to rows modified within the specified date range. The value should be a date/time expression parse-able by TimeExpressionParser
  *		sets = if value is a list of set_ids, only rows that are members of those sets will be returned
  *		user_id = If set item level access control is performed relative to specified user_id, otherwise defaults to logged in user
  *		dontFilterByACL = if true ACL checking is not performed on results
  *		appendToSearch = 
  *		restrictSearchToFields = 
  *
  * @return SearchResult Results packages in a SearchResult object, or sub-class of SearchResult if an instance was passed in $po_result
  * @uses TimeExpressionParser::parse
  */
 public function doSearch($ps_search, $po_result = null, $pa_options = null)
 {
     $t = new Timer();
     global $AUTH_CURRENT_USER_ID;
     if ($vs_append_to_search = isset($pa_options['appendToSearch']) ? ' ' . $pa_options['appendToSearch'] : '') {
         $ps_search .= $vs_append_to_search;
     }
     $ps_search = str_replace("[BLANK]", '"[BLANK]"', $ps_search);
     // the special [BLANK] search term, which returns records that have *no* content in a specific fields, has to be quoted in order to protect the square brackets from the parser.
     if (!is_array($pa_options)) {
         $pa_options = array();
     }
     if (($vn_limit = caGetOption('limit', $pa_options, null, array('castTo' => 'int'))) < 0) {
         $vn_limit = null;
     }
     $vs_sort = caGetOption('sort', $pa_options, null);
     $vs_sort_direction = strtolower(caGetOption('sortDirection', $pa_options, caGetOption('sort_direction', $pa_options, null)));
     //print "QUERY=$ps_search<br>";
     //
     // Note that this is *not* misplaced code that should be in the Lucene plugin!
     //
     // We are using the Lucene syntax as our query syntax regardless the of back-end search engine.
     // The Lucene calls below just parse the query and then rewrite access points as-needed; the result
     // is a Lucene-compliant query ready-to-roll that is passed to the engine plugin. Of course, the Lucene
     // plugin just uses the string as-is... other plugins my choose to parse it however they wish to.
     //
     //
     // Process suffixes list... if search conforms to regex then we append a suffix.
     // This is useful, for example, to allow auto-wildcarding of accession numbers: if the search looks like an accession regex-wise we can append a "*"
     //
     $va_suffixes = $this->opo_search_config->getAssoc('search_suffixes');
     if (is_array($va_suffixes) && sizeof($va_suffixes) && !preg_match('!"!', $ps_search)) {
         // don't add suffix wildcards when quoting
         foreach ($va_suffixes as $vs_preg => $vs_suffix) {
             if (preg_match("!{$vs_preg}!", $ps_search)) {
                 $ps_search = preg_replace("!({$vs_preg})[\\*]*!", "\$1{$vs_suffix}", $ps_search);
             }
         }
     }
     $vb_no_cache = isset($pa_options['no_cache']) ? $pa_options['no_cache'] : false;
     unset($pa_options['no_cache']);
     $vn_cache_timeout = (int) $this->opo_search_config->get('cache_timeout');
     if ($vn_cache_timeout == 0) {
         $vb_no_cache = true;
     }
     // don't try to cache if cache timeout is 0 (0 means disabled)
     $t_table = $this->opo_datamodel->getInstanceByTableName($this->ops_tablename, true);
     $vs_cache_key = md5($ps_search . "/" . print_R($this->getTypeRestrictionList(), true));
     $o_cache = new SearchCache();
     $vb_from_cache = false;
     if (!$vb_no_cache && $o_cache->load($vs_cache_key, $this->opn_tablenum, $pa_options)) {
         $vn_created_on = $o_cache->getParameter('created_on');
         if (time() - $vn_created_on < $vn_cache_timeout) {
             Debug::msg('SEARCH cache hit for ' . $vs_cache_key);
             $va_hits = $o_cache->getResults();
             if ($vs_sort != '_natural') {
                 $va_hits = $this->sortHits($va_hits, $this->ops_tablename, $vs_sort, $vs_sort_direction);
             } elseif ($vs_sort == '_natural' && $vs_sort_direction == 'desc') {
                 $va_hits = array_reverse($va_hits);
             }
             $o_res = new WLPlugSearchEngineCachedResult($va_hits, $this->opn_tablenum);
             $vb_from_cache = true;
         } else {
             Debug::msg('cache expire for ' . $vs_cache_key);
             $o_cache->remove();
         }
     }
     if (!$vb_from_cache) {
         Debug::msg('SEARCH cache miss for ' . $vs_cache_key);
         $vs_char_set = $this->opo_app_config->get('character_set');
         $o_query_parser = new LuceneSyntaxParser();
         $o_query_parser->setEncoding($vs_char_set);
         $o_query_parser->setDefaultOperator(LuceneSyntaxParser::B_AND);
         $ps_search = preg_replace('![\']+!', '', $ps_search);
         try {
             $o_parsed_query = $o_query_parser->parse($ps_search, $vs_char_set);
         } catch (Exception $e) {
             // Retry search with all non-alphanumeric characters removed
             try {
                 $o_parsed_query = $o_query_parser->parse(preg_replace("![^A-Za-z0-9 ]+!", " ", $ps_search), $vs_char_set);
             } catch (Exception $e) {
                 $o_parsed_query = $o_query_parser->parse("", $vs_char_set);
             }
         }
         $va_rewrite_results = $this->_rewriteQuery($o_parsed_query);
         $o_rewritten_query = new Zend_Search_Lucene_Search_Query_Boolean($va_rewrite_results['terms'], $va_rewrite_results['signs']);
         $vs_search = $this->_queryToString($o_rewritten_query);
         //print "<div style='background:#FFFFFF; padding: 5px; border: 1px dotted #666666;'><strong>DEBUG: </strong>".$ps_search.'/'.$vs_search."</div>";
         // Filter deleted records out of final result
         if (isset($pa_options['deletedOnly']) && $pa_options['deletedOnly'] && $t_table->hasField('deleted')) {
             $this->addResultFilter($this->ops_tablename . '.deleted', '=', '1');
         } else {
             if ((!isset($pa_options['showDeleted']) || !$pa_options['showDeleted']) && $t_table->hasField('deleted')) {
                 $this->addResultFilter($this->ops_tablename . '.deleted', '=', '0');
             }
         }
         if (isset($pa_options['checkAccess']) && (is_array($pa_options['checkAccess']) && sizeof($pa_options['checkAccess']))) {
             $va_access_values = $pa_options['checkAccess'];
             $this->addResultFilter($this->ops_tablename . '.access', 'IN', join(",", $va_access_values));
         }
         $vb_no_types = false;
         if (is_array($va_type_ids = $this->getTypeRestrictionList()) && sizeof($va_type_ids) > 0) {
             if ($t_table->getFieldInfo('type_id', 'IS_NULL')) {
                 $va_type_ids[] = 'NULL';
             }
             $this->addResultFilter($this->ops_tablename . '.type_id', 'IN', join(",", $va_type_ids));
         } elseif (is_array($va_type_ids) && sizeof($va_type_ids) == 0) {
             $vb_no_types = true;
         }
         if (!$vb_no_types) {
             // Filter on source
             if (is_array($va_source_ids = $this->getSourceRestrictionList())) {
                 $this->addResultFilter($this->ops_tablename . '.source_id', 'IN', join(",", $va_source_ids));
             }
             if (in_array($t_table->getHierarchyType(), array(__CA_HIER_TYPE_SIMPLE_MONO__, __CA_HIER_TYPE_MULTI_MONO__))) {
                 $this->addResultFilter($this->ops_tablename . '.parent_id', 'IS NOT', NULL);
             }
             if (is_array($va_restrict_to_fields = caGetOption('restrictSearchToFields', $pa_options, null)) && $this->opo_engine->can('restrict_to_fields')) {
                 $this->opo_engine->setOption('restrictSearchToFields', $va_restrict_to_fields);
             }
             $o_res = $this->opo_engine->search($this->opn_tablenum, $vs_search, $this->opa_result_filters, $o_rewritten_query);
             // cache the results
             $va_hits = $o_res->getPrimaryKeyValues($vn_limit);
             $o_res->seek(0);
         } else {
             $va_hits = array();
         }
         if (isset($pa_options['sets']) && $pa_options['sets']) {
             $va_hits = $this->filterHitsBySets($va_hits, $pa_options['sets'], array('search' => $vs_search));
         }
         $vn_user_id = isset($pa_options['user_id']) && (int) $pa_options['user_id'] ? (int) $pa_options['user_id'] : (int) $AUTH_CURRENT_USER_ID;
         if ((!isset($pa_options['dontFilterByACL']) || !$pa_options['dontFilterByACL']) && $this->opo_app_config->get('perform_item_level_access_checking') && method_exists($t_table, "supportsACL") && $t_table->supportsACL()) {
             $va_hits = $this->filterHitsByACL($va_hits, $this->opn_tablenum, $vn_user_id, __CA_ACL_READONLY_ACCESS__);
         }
         if ($vs_sort != '_natural') {
             $va_hits = $this->sortHits($va_hits, $t_table->tableName(), $pa_options['sort'], isset($pa_options['sort_direction']) ? $pa_options['sort_direction'] : null);
         } elseif ($vs_sort == '_natural' && $vs_sort_direction == 'desc') {
             $va_hits = array_reverse($va_hits);
         }
         $o_res = new WLPlugSearchEngineCachedResult($va_hits, $this->opn_tablenum);
         // cache for later use
         if (!$vb_no_cache) {
             $o_cache->save($vs_cache_key, $this->opn_tablenum, $va_hits, array('created_on' => time()), null, $pa_options);
         }
         // log search
         $o_log = new Searchlog();
         $vn_search_form_id = isset($pa_options['form_id']) ? $pa_options['form_id'] : null;
         $vs_log_details = isset($pa_options['log_details']) ? $pa_options['log_details'] : '';
         $vs_search_source = isset($pa_options['search_source']) ? $pa_options['search_source'] : '';
         $vn_execution_time = $t->getTime(4);
         $o_log->log(array('user_id' => $vn_user_id, 'table_num' => $this->opn_tablenum, 'search_expression' => $ps_search, 'num_hits' => sizeof($va_hits), 'form_id' => $vn_search_form_id, 'ip_addr' => $_SERVER['REMOTE_ADDR'] ? $_SERVER['REMOTE_ADDR'] : null, 'details' => $vs_log_details, 'search_source' => $vs_search_source, 'execution_time' => $vn_execution_time));
     }
     if ($po_result) {
         $po_result->init($o_res, $this->opa_tables, $pa_options);
         return $po_result;
     } else {
         return new SearchResult($o_res, $this->opa_tables);
     }
 }