Example #1
0
 public function testSetQueryStringInvalid()
 {
     $query = new Elastica_Query_QueryString();
     try {
         $query->setQueryString(array());
         $this->fail('should throw exception because no string');
     } catch (Elastica_Exception_Invalid $e) {
         $this->assertTrue(true);
     }
 }
Example #2
0
 public function search($query)
 {
     if (is_string($query)) {
         $queryObj = new Query();
         $queryObj->setQuery($query);
         $query = $queryObj;
     }
     if ($query instanceof QueryInterface) {
         $query->setIndexManager($this);
         // Build the Elastica Query
         $elasticaQuery = new \Elastica_Query();
         // Define a Query. We want a string query.
         $elasticaQueryString = new \Elastica_Query_QueryString();
         $elasticaQueryString->setDefaultOperator('AND');
         $elasticaQueryString->setQuery($query->getQuery());
         $elasticaQuery->setQuery($elasticaQueryString);
         $elasticaQuery->setFrom($query->getOffset());
         $elasticaQuery->setLimit($query->getLimit());
         $index = $this->getIndex();
         $elasticaResultSet = $index->search($elasticaQuery);
         return $elasticaResultSet;
     }
     throw new \InvalidArgumentException();
 }
Example #3
0
 /**
  * @return \Elastica_Query_Abstract
  */
 public function getQuery()
 {
     // main query
     $search = $this->parameters->getSearch();
     if ($search) {
         $main = new \Elastica_Query_QueryString();
         $main->setQueryString($search);
         $main->setParam('analyze_wildcard', true);
     } else {
         $main = new \Elastica_Query_MatchAll();
     }
     $main = $this->geo($main);
     $main = $this->access($main);
     return $main;
 }
 public function grab(&$param_pool = NULL)
 {
     $config = (object) Symphony::Configuration()->get('elasticsearch');
     // build an object of runtime parameters
     $params = (object) array('keywords' => isset($_GET['keywords']) ? $_GET['keywords'] : '', 'current-page' => isset($_GET['page']) && is_numeric($_GET['page']) ? (int) $_GET['page'] : 1, 'per-page' => isset($_GET['per-page']) && is_numeric($_GET['per-page']) ? (int) $_GET['per-page'] : $config->{'per-page'}, 'sort' => isset($_GET['sort']) ? $_GET['sort'] : $config->sort, 'direction' => isset($_GET['direction']) && in_array($_GET['direction'], array('asc', 'desc')) ? $_GET['direction'] : $config->direction, 'sections' => isset($_GET['sections']) && !empty($_GET['sections']) ? array_map('trim', explode(',', $_GET['sections'])) : NULL, 'default-sections' => !empty($config->{'default-sections'}) ? explode(',', $config->{'default-sections'}) : NULL, 'language' => isset($_GET['language']) && !empty($_GET['language']) ? array_map('trim', explode(',', $_GET['language'])) : NULL, 'default-language' => !empty($config->{'default-language'}) ? explode(',', $config->{'default-language'}) : NULL);
     $params->{'keywords-raw'} = $params->keywords;
     $params->keywords = ElasticSearch::filterKeywords($params->keywords);
     // don't run search if not searching for anything
     if (empty($params->keywords)) {
         return;
     }
     // check valid page number
     if ($params->{'current-page'} < 1) {
         $params->{'current-page'} = 1;
     }
     // if no language passed but there are defaults, use the defaults
     if ($params->{'language'} === NULL && count($params->{'default-language'})) {
         $params->{'language'} = $params->{'default-language'};
     }
     // include this extension's own library
     ElasticSearch::init();
     // a query_string search type in ES accepts common (Lucene) search syntax such as
     // prefixing terms with +/- and surrounding exact phrases with quotes
     $query_querystring = new Elastica_Query_QueryString();
     // all terms are required
     $query_querystring->setDefaultOperator('AND');
     // pass in keywords
     $query_querystring->setQueryString($params->keywords);
     // only apply the search to fields mapped as multi-type with a sub-type named "symphony_fulltext"
     // this allows us to exclude fields from this generic full-site search but search them elsewhere
     if ($params->{'language'}) {
         $fields = array();
         foreach ($params->{'language'} as $language) {
             $fields[] = '*_' . $language . '.symphony_fulltext';
         }
         $query_querystring->setFields($fields);
     } else {
         $query_querystring->setFields(array('*.symphony_fulltext'));
     }
     // create the parent query object (a factory) into which the query_string is passed
     $query = new Elastica_Query($query_querystring);
     $query->setLimit($params->{'per-page'});
     // TODO: check this. should it be + 1?
     $query->setFrom($params->{'per-page'} * ($params->{'current-page'} - 1));
     $query->setSort(array($params->{'sort'} => $params->{'direction'}));
     // build a search object, this wraps an Elastica_Client and handles requests to and from the ElasticSearch server
     $search = new Elastica_Search(ElasticSearch::getClient());
     // search on our site index only (in case the server is running multiple indexes)
     $search->addIndex(ElasticSearch::getIndex());
     // create a new facet on the entry _type (section handle). this will return a list
     // of sections in which the matching entries reside, and a count of matches in each
     $facet = new Elastica_Facet_Terms('filtered-sections');
     $facet->setField('_type');
     $query->addFacet($facet);
     // we also want a list of _all_ sections and their total entry counts. facets run within the context
     // of the query they are attached to, so we want a new query that searches within the specified sections
     // but doesn't search on the keywords (so it finds everything). ES supports this with a match_all query
     // which Elastica creates by default when you create a plain query object
     $query_all = new Elastica_Query();
     $facet = new Elastica_Facet_Terms('all-sections');
     $facet->setField('_type');
     $query_all->addFacet($facet);
     // build an array of all valid section handles that have mappings
     $all_mapped_sections = array();
     $section_full_names = array();
     foreach (ElasticSearch::getAllTypes() as $type) {
         // if using default config sections, check that the type exists in the default
         if (count($params->{'default-sections'}) > 0 && !in_array($type->section->get('handle'), $params->{'default-sections'})) {
             continue;
         }
         $all_mapped_sections[] = $type->section->get('handle');
         // cache an array of section names indexed by their handles, quick lookup later
         $section_full_names[$type->section->get('handle')] = $type->section->get('name');
     }
     $sections = array();
     // no specified sections were sent in the params, so default to all available sections
     if ($params->sections === NULL) {
         $sections = $all_mapped_sections;
     } else {
         foreach ($params->sections as $handle) {
             if (!in_array($handle, $all_mapped_sections)) {
                 continue;
             }
             $sections[] = $handle;
         }
     }
     // a filter is an additional set of filtering that can be added to a query. filters are run
     // after the query has executed, so run over the resultset and remove documents that don't
     // match the criteria. they are fast and are cached by ES. we want to restrict the search
     // results to within the specified sections only, so we add a filter on the _type (section handle)
     // field. the filter is of type "terms" (an array of exact-match strings)
     $filter = new Elastica_Filter_Terms('_type');
     // build an array of field handles which should be highlighted in search results, used for building
     // the excerpt on results pages. a field is marked as highlightable by giving it a "symphony_fulltext"
     // field in the section mappings
     $highlights = array();
     // iterate over each valid section, adding it as a filter and finding any highlighted fields within
     foreach ($sections as $section) {
         // add these sections to the entry search
         $filter->addTerm($section);
         // read the section's mapping JSON from disk
         $mapping = json_decode(ElasticSearch::getTypeByHandle($section)->mapping_json, FALSE);
         // find fields that have symphony_highlight
         foreach ($mapping->{$section}->properties as $field => $properties) {
             if (!$properties->fields->symphony_fulltext) {
                 continue;
             }
             $highlights[] = array($field => (object) array());
         }
     }
     // add the section filter to both queries (keyword search and the all entries facet search)
     $query->setFilter($filter);
     $query_all->setFilter($filter);
     // configure highlighting for the keyword search
     $query->setHighlight(array('fields' => $highlights, 'encoder' => 'html', 'fragment_size' => $config->{'highlight-fragment-size'}, 'number_of_fragments' => $config->{'highlight-per-field'}, 'pre_tags' => array('<strong class="highlight">'), 'post_tags' => array('</strong>')));
     // run both queries!
     $query_result = $search->search($query);
     $query_all_result = $search->search($query_all);
     // build root XMK element
     $xml = new XMLElement($this->dsParamROOTELEMENT, NULL, array('took' => $query_result->getResponse()->getEngineTime() . 'ms', 'max-score' => round($query_result->getMaxScore(), 4)));
     // append keywords to the XML
     $xml_keywords = new XMLElement('keywords');
     $xml_keywords->appendChild(new XMLElement('raw', General::sanitize($params->{'keywords-raw'})));
     $xml_keywords->appendChild(new XMLElement('filtered', General::sanitize($params->{'keywords'})));
     $xml->appendChild($xml_keywords);
     // build pagination
     $xml->appendChild(General::buildPaginationElement($query_result->getTotalHits(), ceil($query_result->getTotalHits() * (1 / $params->{'per-page'})), $params->{'per-page'}, $params->{'current-page'}));
     // build facets
     $xml_facets = new XMLElement('facets');
     // merge the facets from both queries so they appear as one
     $facets = array_merge($query_result->getFacets(), $query_all_result->getFacets());
     foreach ($facets as $handle => $facet) {
         $xml_facet = new XMLElement('facet', NULL, array('handle' => $handle));
         foreach ($facet['terms'] as $term) {
             // only show sections that are in default config, if it is being used
             if (!in_array($term['term'], $all_mapped_sections)) {
                 continue;
             }
             $xml_facet_term = new XMLElement('term', $section_full_names[$term['term']], array('handle' => $term['term'], 'entries' => $term['count'], 'active' => in_array($term['term'], $sections) ? 'yes' : 'no'));
             $xml_facet->appendChild($xml_facet_term);
         }
         $xml_facets->appendChild($xml_facet);
     }
     $xml->appendChild($xml_facets);
     // if each entry is to have its full XML built and appended to the result,
     // create a new EntryManager for using later on
     if ($config->{'build-entry-xml'} === 'yes') {
         $em = new EntryManager(Frontend::instance());
         $field_pool = array();
     }
     // append entries
     $xml_entries = new XMLElement('entries');
     foreach ($query_result->getResults() as $data) {
         $entry = new XMLElement('entry', NULL, array('id' => $data->getId(), 'section' => $data->getType(), 'score' => is_array($data->getScore()) ? reset($data->getScore()) : round($data->getScore(), 4)));
         // append field highlights
         foreach ($data->getHighlights() as $field => $highlight) {
             foreach ($highlight as $html) {
                 $entry->appendChild(new XMLElement('highlight', $html, array('field' => $field)));
             }
         }
         // build and append entry data
         // this was pinched from Symphony's datasource class
         if ($config->{'build-entry-xml'} === 'yes') {
             $e = reset($em->fetch($data->getId()));
             $field_data = $e->getData();
             foreach ($field_data as $field_id => $values) {
                 if (!isset($field_pool[$field_id]) || !is_object($field_pool[$field_id])) {
                     $field_pool[$field_id] = FieldManager::fetch($field_id);
                 }
                 $field_pool[$field_id]->appendFormattedElement($entry, $values, FALSE, NULL, $e->get('id'));
             }
         }
         $xml_entries->appendChild($entry);
         // put each entry ID into the param pool for chaining
         $param_pool['ds-elasticsearch'][] = $data->getId();
     }
     $xml->appendChild($xml_entries);
     // log query if logging is enabled
     if ($config->{'log-searches'} === 'yes') {
         ElasticSearchLogs::save($params->keywords, $params->{'keywords-raw'}, $sections, $params->{'current-page'}, $query_result->getTotalHits());
     }
     return $xml;
 }
 public function grab(&$param_pool = NULL)
 {
     $config = (object) Symphony::Configuration()->get('elasticsearch');
     // build an object of runtime parameters
     $params = (object) array('keywords' => isset($_GET['keywords']) ? trim($_GET['keywords']) : '', 'per-page' => isset($_GET['per-page']) ? $_GET['per-page'] : $config->{'per-page'}, 'sections' => isset($_GET['sections']) ? array_map('trim', explode(',', $_GET['sections'])) : NULL, 'default-sections' => !empty($config->{'default-sections'}) ? explode(',', $config->{'default-sections'}) : NULL, 'language' => isset($_GET['language']) && !empty($_GET['language']) ? array_map('trim', explode(',', $_GET['language'])) : NULL, 'default-language' => !empty($config->{'default-language'}) ? explode(',', $config->{'default-language'}) : NULL);
     $params->keywords = ElasticSearch::filterKeywords($params->keywords);
     if (empty($params->keywords)) {
         return;
     }
     // add trailing wildcard if it's not already there
     if (end(str_split($params->keywords)) !== '*') {
         $params->keywords = $params->keywords . '*';
     }
     // if no language passed but there are defaults, use the defaults
     if ($params->{'language'} === NULL && count($params->{'default-language'})) {
         $params->{'language'} = $params->{'default-language'};
     }
     ElasticSearch::init();
     $query_querystring = new Elastica_Query_QueryString();
     $query_querystring->setDefaultOperator('AND');
     $query_querystring->setQueryString($params->keywords);
     if ($params->{'language'}) {
         $fields = array();
         foreach ($params->{'language'} as $language) {
             $fields[] = '*_' . $language . '.symphony_fulltext';
         }
         $query_querystring->setFields($fields);
     } else {
         $query_querystring->setFields(array('*.symphony_fulltext'));
     }
     $query = new Elastica_Query($query_querystring);
     // returns loads. let's say we search for "romeo" and there are hundreds of lines that contain
     // romeo but also the play title "romeo and juliet", the first 10 or 20 results might just be script lines
     // containing "romeo", so the play title will not be included. so return a big chunk of hits to give a
     // better chance of more different terms being in the result. a tradeoff of speed/hackiness over usefulness.
     $query->setLimit(1000);
     $search = new Elastica_Search(ElasticSearch::getClient());
     $search->addIndex(ElasticSearch::getIndex());
     $filter = new Elastica_Filter_Terms('_type');
     // build an array of all valid section handles that have mappings
     $all_mapped_sections = array();
     $section_full_names = array();
     foreach (ElasticSearch::getAllTypes() as $type) {
         if (count($params->{'default-sections'}) > 0 && !in_array($type->section->get('handle'), $params->{'default-sections'})) {
             continue;
         }
         $all_mapped_sections[] = $type->section->get('handle');
         // cache an array of section names indexed by their handles, quick lookup later
         $section_full_names[$type->section->get('handle')] = $type->section->get('name');
     }
     $sections = array();
     // no specified sections were sent in the params, so default to all available sections
     if ($params->sections === NULL) {
         $sections = $all_mapped_sections;
     } else {
         foreach ($params->sections as $handle) {
             if (!in_array($handle, $all_mapped_sections)) {
                 continue;
             }
             $sections[] = $handle;
         }
     }
     //$autocomplete_fields = array();
     $highlights = array();
     foreach ($sections as $section) {
         $filter->addTerm($section);
         $mapping = json_decode(ElasticSearch::getTypeByHandle($section)->mapping_json, FALSE);
         // find fields that have symphony_highlight
         foreach ($mapping->{$section}->properties as $field => $properties) {
             if (!$properties->fields->symphony_autocomplete) {
                 continue;
             }
             //$autocomplete_fields[] = $field;
             $highlights[] = array($field => (object) array());
         }
     }
     //$autocomplete_fields = array_unique($autocomplete_fields);
     $query->setFilter($filter);
     $query->setHighlight(array('fields' => $highlights, 'encoder' => 'html', 'fragment_size' => 100, 'number_of_fragments' => 3, 'pre_tags' => array('<strong>'), 'post_tags' => array('</strong>')));
     // run the entry search
     $entries_result = $search->search($query);
     $xml = new XMLElement($this->dsParamROOTELEMENT, NULL, array('took' => $entries_result->getResponse()->getEngineTime() . 'ms'));
     $words = array();
     foreach ($entries_result->getResults() as $data) {
         foreach ($data->getHighlights() as $field => $highlight) {
             foreach ($highlight as $html) {
                 $words[] = $html;
             }
         }
     }
     $words = array_unique($words);
     $xml_words = new XMLElement('words');
     foreach ($words as $word) {
         $raw = General::sanitize(strip_tags($word));
         $highlighted = General::sanitize($word);
         $xml_word = new XMLElement('word');
         $xml_word->appendChild(new XMLElement('raw', $raw));
         $xml_word->appendChild(new XMLElement('highlighted', $highlighted));
         $xml_words->appendChild($xml_word);
     }
     $xml->appendChild($xml_words);
     return $xml;
 }
Example #6
0
 function search($query, $facets = array(), $offset = false, $limit = false, $indexName = false, $indexType = false)
 {
     $global_query = $query;
     $query = preg_replace("/[^a-zA-Z 0-9]+/", "", $global_query['s']);
     // Define a Query. We want a string query.
     $elasticaQueryString = new Elastica_Query_QueryString();
     $elasticaQueryString->setQuery($query);
     $elasticaQueryString->setFields(array('title', 'content'));
     $elasticaFilterAnd = new Elastica_Filter_And();
     $elasticaFilterBool = new Elastica_Filter_Bool();
     $tagTerm = $global_query['tags'];
     $authorTerm = $global_query['author'];
     $catTerm = $global_query['cats'];
     $dateFrom = $global_query['datefrom'];
     $dateTo = $global_query['dateto'];
     if (!empty($tagTerm)) {
         $tagTermArr = explode(",", $tagTerm);
         $filterTag = new Elastica_Filter_Terms();
         $filterTag->setTerms('tag', $tagTermArr);
         $elasticaFilterAnd->addFilter($filterTag);
     }
     if (!empty($authorTerm)) {
         $authorTermArr = explode(",", $authorTerm);
         $filterAuthor = new Elastica_Filter_Terms();
         $filterAuthor->setTerms('author', $authorTermArr);
         $elasticaFilterAnd->addFilter($filterAuthor);
     }
     if (!empty($catTerm)) {
         $catTermArr = explode(",", $catTerm);
         $filterCat = new Elastica_Filter_Terms();
         $filterCat->setTerms('cat', $catTermArr);
         $elasticaFilterAnd->addFilter($filterCat);
     }
     if (!empty($dateFrom) && !empty($dateTo)) {
         $filterDate = new Elastica_Filter_Range('date', array('from' => strftime('%F %T', strtotime($dateFrom)), 'to' => strftime('%F %T', strtotime($dateTo))));
         // $dates = new Elastica_Filter_Terms();
         // $dates->setTerms('date', array($dateFrom." -> ".$dateTo));
         // $elasticaFilterAnd->addFilter($dates);
         $elasticaFilterBool->addMust($filterDate);
     }
     // Create the actual search object with some data.
     $elasticaQuery = new Elastica_Query();
     $elasticaQuery->setQuery($elasticaQueryString);
     if (!empty($tagTerm) || !empty($authorTerm) || !empty($catTerm) || !empty($dateFrom) || !empty($dateTo)) {
         // $elasticaQuery->setFilter($elasticaFilterAnd);
         $elasticaQuery->setFilter($elasticaFilterBool);
     }
     if ($offset) {
         $elasticaQuery->setFrom($offset);
     }
     if ($limit) {
         $elasticaQuery->setLimit($limit);
     }
     //Check facet fields
     if (!empty($facets)) {
         $facet_arr = array();
         foreach ($facets as $facet) {
             ${$facet . "_facet"} = new Elastica_Facet_Terms($facet);
             ${$facet . "_facet"}->setField($facet);
             ${$facet . "_facet"}->setSize(10);
             ${$facet . "_facet"}->setOrder('reverse_count');
             array_push($facet_arr, ${$facet . "_facet"});
         }
         $elasticaQuery->setFacets($facet_arr);
     }
     //Search on the index.
     if ($indexType) {
         $elasticaResultSet = $this->elastic_search_client->getIndex($indexName)->getType($indexType)->search($elasticaQuery);
     } else {
         $elasticaResultSet = $this->elastic_search_client->getIndex($indexName)->search($elasticaQuery);
     }
     return $elasticaResultSet;
 }
Example #7
0
 /**
  * Handles search and facets.
  *
  * @param string $q
  * @param array $params
  * @param string $type
  * @return Elastica_ResultSet
  * @throws Exception
  */
 public function search($q, $params = array(), $type = 'product')
 {
     if ($this->getStatus()->indexExists($this->_index)) {
         if (empty($params['filters'])) {
             $params['filters'] = '*';
         }
         $queryFilter = new Elastica_Filter_Query(new Elastica_Query_QueryString($params['filters']));
         if (isset($params['range_filters']) && !empty($params['range_filters'])) {
             $andFilter = new Elastica_Filter_And();
             $andFilter->addFilter($queryFilter);
             $filter = new Elastica_Filter_Range();
             foreach ($params['range_filters'] as $field => $rangeFilter) {
                 $filter->addField($field, $rangeFilter);
             }
             $andFilter->addFilter($filter);
             $queryFilter = $andFilter;
         }
         if (empty($q)) {
             $baseQuery = new Elastica_Query_MatchAll();
         } else {
             $baseQuery = new Elastica_Query_Bool();
             if ($this->isFuzzyQueryEnabled()) {
                 $fields = $this->_getSearchFields(true, $q);
                 $queryFuzzy = new Elastica_Query_FuzzyLikeThis();
                 $queryFuzzy->addFields($fields);
                 $queryFuzzy->setLikeText($q);
                 $queryFuzzy->setMinSimilarity($this->getFuzzyMinSimilarity());
                 $queryFuzzy->setPrefixLength($this->getFuzzyPrefixLength());
                 $queryFuzzy->setMaxQueryTerms($this->getFuzzyMaxQueryTerms());
                 $queryFuzzy->setBoost($this->getFuzzyQueryBoost());
                 $baseQuery->addShould($queryFuzzy);
             }
             $queryString = new Elastica_Query_QueryString($q);
             $queryString->setFields($this->_getSearchFields(false, $q));
             $baseQuery->addShould($queryString);
         }
         $filteredQuery = new Elastica_Query_Filtered($baseQuery, $queryFilter);
         $query = Elastica_Query::create($filteredQuery)->setFrom($params['offset'])->setLimit($params['limit']);
         if (isset($params['facets']['queries']) && !empty($params['facets']['queries'])) {
             foreach ($params['facets']['queries'] as $facetQuery) {
                 $facet = new Elastica_Facet_Query($facetQuery);
                 $facet->setParam('query_string', array('query' => $facetQuery));
                 $query->addFacet($facet);
             }
         }
         if (isset($params['stats']['fields']) && !empty($params['stats']['fields'])) {
             foreach ($params['stats']['fields'] as $field) {
                 $facet = new Elastica_Facet_Statistical($field);
                 $facet->setParam('field', $field);
                 $query->addFacet($facet);
             }
         } else {
             if (isset($params['facets']['fields']) && !empty($params['facets']['fields'])) {
                 $properties = $this->_getIndexProperties();
                 foreach ($params['facets']['fields'] as $field) {
                     if (array_key_exists($field, $properties)) {
                         $facet = new Elastica_Facet_Terms($field);
                         if ($properties[$field]['type'] == 'multi_field') {
                             $field .= '.untouched';
                         }
                         $facet->setField($field);
                         $facet->setParam('all_terms', true);
                         $facet->setSize($this->getFacetsMaxSize());
                         $query->addFacet($facet);
                     }
                 }
             }
             if (isset($params['facets']['ranges']) && !empty($params['facets']['ranges'])) {
                 foreach ($params['facets']['ranges'] as $field => $ranges) {
                     $facet = new Elastica_Facet_Range($field);
                     $facet->setField($field);
                     $facet->setRanges($ranges);
                     $query->addFacet($facet);
                 }
             }
         }
         if (isset($params['sort']) && !empty($params['sort'])) {
             foreach ($params['sort'] as $sort) {
                 $query->addSort($sort);
             }
         }
         $result = $this->getIndex($this->_index)->getType($type)->search($query);
         return $result;
     }
     return array();
 }