public function __construct()
 {
     parent::__construct();
     $this->init();
     foreach ($this->getClasses() as $class => $options) {
         SearchVariant::with($class, $options['include_children'])->call('alterDefinition', $class, $this);
     }
     $this->buildDependancyList();
 }
 /**
  * Fulltextsearch module doesn't yet support facets very well, so I've just copied this function here so
  * we have access to the results. I'd prefer to modify it minimally so we can eventually get rid of it
  * once they add faceting or hooks to get directly at the returned response.
  *
  * @param SearchQuery $query
  * @param integer $offset
  * @param integer $limit
  * @param  Array $params Extra request parameters passed through to Solr
  * @param array $facetSpec - Added for ShopSearch so we can process the facets
  * @return ArrayData Map with the following keys:
  *  - 'Matches': ArrayList of the matched object instances
  */
 public function search(SearchQuery $query, $offset = -1, $limit = -1, $params = array(), $facetSpec = array())
 {
     $service = $this->getService();
     SearchVariant::with(count($query->classes) == 1 ? $query->classes[0]['class'] : null)->call('alterQuery', $query, $this);
     $q = array();
     $fq = array();
     // Build the search itself
     foreach ($query->search as $search) {
         $text = $search['text'];
         preg_match_all('/"[^"]*"|\\S+/', $text, $parts);
         $fuzzy = $search['fuzzy'] ? '~' : '';
         foreach ($parts[0] as $part) {
             $fields = isset($search['fields']) ? $search['fields'] : array();
             if (isset($search['boost'])) {
                 $fields = array_merge($fields, array_keys($search['boost']));
             }
             if ($fields) {
                 $searchq = array();
                 foreach ($fields as $field) {
                     $boost = isset($search['boost'][$field]) ? '^' . $search['boost'][$field] : '';
                     $searchq[] = "{$field}:" . $part . $fuzzy . $boost;
                 }
                 $q[] = '+(' . implode(' OR ', $searchq) . ')';
             } else {
                 $q[] = '+' . $part . $fuzzy;
             }
         }
     }
     // Filter by class if requested
     $classq = array();
     foreach ($query->classes as $class) {
         if (!empty($class['includeSubclasses'])) {
             $classq[] = 'ClassHierarchy:' . $class['class'];
         } else {
             $classq[] = 'ClassName:' . $class['class'];
         }
     }
     if ($classq) {
         $fq[] = '+(' . implode(' ', $classq) . ')';
     }
     // Filter by filters
     foreach ($query->require as $field => $values) {
         $requireq = array();
         foreach ($values as $value) {
             if ($value === SearchQuery::$missing) {
                 $requireq[] = "(*:* -{$field}:[* TO *])";
             } elseif ($value === SearchQuery::$present) {
                 $requireq[] = "{$field}:[* TO *]";
             } elseif ($value instanceof SearchQuery_Range) {
                 $start = $value->start;
                 if ($start === null) {
                     $start = '*';
                 }
                 $end = $value->end;
                 if ($end === null) {
                     $end = '*';
                 }
                 $requireq[] = "{$field}:[{$start} TO {$end}]";
             } else {
                 $requireq[] = $field . ':"' . $value . '"';
             }
         }
         $fq[] = '+(' . implode(' ', $requireq) . ')';
     }
     foreach ($query->exclude as $field => $values) {
         $excludeq = array();
         $missing = false;
         foreach ($values as $value) {
             if ($value === SearchQuery::$missing) {
                 $missing = true;
             } elseif ($value === SearchQuery::$present) {
                 $excludeq[] = "{$field}:[* TO *]";
             } elseif ($value instanceof SearchQuery_Range) {
                 $start = $value->start;
                 if ($start === null) {
                     $start = '*';
                 }
                 $end = $value->end;
                 if ($end === null) {
                     $end = '*';
                 }
                 $excludeq[] = "{$field}:[{$start} TO {$end}]";
             } else {
                 $excludeq[] = $field . ':"' . $value . '"';
             }
         }
         $fq[] = ($missing ? "+{$field}:[* TO *] " : '') . '-(' . implode(' ', $excludeq) . ')';
     }
     //		if(!headers_sent()) {
     //			if ($q) header('X-Query: '.implode(' ', $q));
     //			if ($fq) header('X-Filters: "'.implode('", "', $fq).'"');
     //		}
     if ($offset == -1) {
         $offset = $query->start;
     }
     if ($limit == -1) {
         $limit = $query->limit;
     }
     if ($limit == -1) {
         $limit = SearchQuery::$default_page_size;
     }
     $params = array_merge($params, array('fq' => implode(' ', $fq)));
     $res = $service->search($q ? implode(' ', $q) : '*:*', $offset, $limit, $params, Apache_Solr_Service::METHOD_POST);
     //Debug::dump($res);
     $results = new ArrayList();
     if ($res->getHttpStatus() >= 200 && $res->getHttpStatus() < 300) {
         foreach ($res->response->docs as $doc) {
             $result = DataObject::get_by_id($doc->ClassName, $doc->ID);
             if ($result) {
                 $results->push($result);
                 // Add highlighting (optional)
                 $docId = $doc->_documentid;
                 if ($res->highlighting && $res->highlighting->{$docId}) {
                     // TODO Create decorator class for search results rather than adding arbitrary object properties
                     // TODO Allow specifying highlighted field, and lazy loading
                     // in case the search API needs another query (similar to SphinxSearchable->buildExcerpt()).
                     $combinedHighlights = array();
                     foreach ($res->highlighting->{$docId} as $field => $highlights) {
                         $combinedHighlights = array_merge($combinedHighlights, $highlights);
                     }
                     // Remove entity-encoded U+FFFD replacement character. It signifies non-displayable characters,
                     // and shows up as an encoding error in browsers.
                     $result->Excerpt = DBField::create_field('HTMLText', str_replace('&#65533;', '', implode(' ... ', $combinedHighlights)));
                 }
             }
         }
         $numFound = $res->response->numFound;
     } else {
         $numFound = 0;
     }
     $ret = array();
     $ret['Matches'] = new PaginatedList($results);
     $ret['Matches']->setLimitItems(false);
     // Tell PaginatedList how many results there are
     $ret['Matches']->setTotalItems($numFound);
     // Results for current page start at $offset
     $ret['Matches']->setPageStart($offset);
     // Results per page
     $ret['Matches']->setPageLength($limit);
     // Facets
     //Debug::dump($res);
     if (isset($res->facet_counts->facet_fields)) {
         $ret['Facets'] = $this->buildFacetResults($res->facet_counts->facet_fields, $facetSpec);
     }
     // Suggestions (requires custom setup, assumes spellcheck.collate=true)
     if (isset($res->spellcheck->suggestions->collation)) {
         $ret['Suggestion'] = $res->spellcheck->suggestions->collation;
     }
     return new ArrayData($ret);
 }
 /**
  * @param SearchQuery $query
  * @param integer $offset
  * @param integer $limit
  * @param array $params Extra request parameters passed through to Solr
  * @return ArrayData Map with the following keys: 
  *  - 'Matches': ArrayList of the matched object instances
  */
 public function search(SearchQuery $query, $offset = -1, $limit = -1, $params = array())
 {
     $service = $this->getService();
     $searchClass = count($query->classes) == 1 ? $query->classes[0]['class'] : null;
     SearchVariant::with($searchClass)->call('alterQuery', $query, $this);
     $q = array();
     // Query
     $fq = array();
     // Filter query
     $qf = array();
     // Query fields
     $hlq = array();
     // Highlight query
     // Build the search itself
     $q = $this->getQueryComponent($query, $hlq);
     // If using boosting, set the clean term separately for highlighting.
     // See https://issues.apache.org/jira/browse/SOLR-2632
     if (array_key_exists('hl', $params) && !array_key_exists('hl.q', $params)) {
         $params['hl.q'] = implode(' ', $hlq);
     }
     // Filter by class if requested
     $classq = array();
     foreach ($query->classes as $class) {
         if (!empty($class['includeSubclasses'])) {
             $classq[] = 'ClassHierarchy:' . $class['class'];
         } else {
             $classq[] = 'ClassName:' . $class['class'];
         }
     }
     if ($classq) {
         $fq[] = '+(' . implode(' ', $classq) . ')';
     }
     // Filter by filters
     $fq = array_merge($fq, $this->getFiltersComponent($query));
     // Prepare query fields unless specified explicitly
     if (isset($params['qf'])) {
         $qf = $params['qf'];
     } else {
         $qf = $this->getQueryFields();
     }
     if (is_array($qf)) {
         $qf = implode(' ', $qf);
     }
     if ($qf) {
         $params['qf'] = $qf;
     }
     if (!headers_sent() && !Director::isLive()) {
         if ($q) {
             header('X-Query: ' . implode(' ', $q));
         }
         if ($fq) {
             header('X-Filters: "' . implode('", "', $fq) . '"');
         }
         if ($qf) {
             header('X-QueryFields: ' . $qf);
         }
     }
     if ($offset == -1) {
         $offset = $query->start;
     }
     if ($limit == -1) {
         $limit = $query->limit;
     }
     if ($limit == -1) {
         $limit = SearchQuery::$default_page_size;
     }
     $params = array_merge($params, array('fq' => implode(' ', $fq)));
     $res = $service->search($q ? implode(' ', $q) : '*:*', $offset, $limit, $params, Apache_Solr_Service::METHOD_POST);
     $results = new ArrayList();
     if ($res->getHttpStatus() >= 200 && $res->getHttpStatus() < 300) {
         foreach ($res->response->docs as $doc) {
             $result = DataObject::get_by_id($doc->ClassName, $doc->ID);
             if ($result) {
                 $results->push($result);
                 // Add highlighting (optional)
                 $docId = $doc->_documentid;
                 if ($res->highlighting && $res->highlighting->{$docId}) {
                     // TODO Create decorator class for search results rather than adding arbitrary object properties
                     // TODO Allow specifying highlighted field, and lazy loading
                     // in case the search API needs another query (similar to SphinxSearchable->buildExcerpt()).
                     $combinedHighlights = array();
                     foreach ($res->highlighting->{$docId} as $field => $highlights) {
                         $combinedHighlights = array_merge($combinedHighlights, $highlights);
                     }
                     // Remove entity-encoded U+FFFD replacement character. It signifies non-displayable characters,
                     // and shows up as an encoding error in browsers.
                     $result->Excerpt = DBField::create_field('HTMLText', str_replace('&#65533;', '', implode(' ... ', $combinedHighlights)));
                 }
             }
         }
         $numFound = $res->response->numFound;
     } else {
         $numFound = 0;
     }
     $ret = array();
     $ret['Matches'] = new PaginatedList($results);
     $ret['Matches']->setLimitItems(false);
     // Tell PaginatedList how many results there are
     $ret['Matches']->setTotalItems($numFound);
     // Results for current page start at $offset
     $ret['Matches']->setPageStart($offset);
     // Results per page
     $ret['Matches']->setPageLength($limit);
     // Include spellcheck and suggestion data. Requires spellcheck=true in $params
     if (isset($res->spellcheck)) {
         // Expose all spellcheck data, for custom handling.
         $ret['Spellcheck'] = $res->spellcheck;
         // Suggestions. Requires spellcheck.collate=true in $params
         if (isset($res->spellcheck->suggestions->collation)) {
             // Extract string suggestion
             $suggestion = $this->getCollatedSuggestion($res->spellcheck->suggestions->collation);
             // The collation, including advanced query params (e.g. +), suitable for making another query programmatically.
             $ret['Suggestion'] = $suggestion;
             // A human friendly version of the suggestion, suitable for 'Did you mean $SuggestionNice?' display.
             $ret['SuggestionNice'] = $this->getNiceSuggestion($suggestion);
             // A string suitable for appending to an href as a query string.
             // For example <a href="http://example.com/search?q=$SuggestionQueryString">$SuggestionNice</a>
             $ret['SuggestionQueryString'] = $this->getSuggestionQueryString($suggestion);
         }
     }
     return new ArrayData($ret);
 }
 /**
  * Clear all records of the given class in the current state ONLY.
  *
  * Optionally delete from a given group (where the group is defined as the ID % total groups)
  *
  * @param SolrIndex $indexInstance Index instance
  * @param string $class Class name
  * @param int $groups Number of groups, if clearing from a striped group
  * @param int $group Group number, if clearing from a striped group
  */
 protected function clearRecords(SolrIndex $indexInstance, $class, $groups = null, $group = null)
 {
     // Clear by classname
     $conditions = array("+(ClassHierarchy:{$class})");
     // If grouping, delete from this group only
     if ($groups) {
         $conditions[] = "+_query_:\"{!frange l={$group} u={$group}}mod(ID, {$groups})\"";
     }
     // Also filter by state (suffix on document ID)
     $query = new SearchQuery();
     SearchVariant::with($class)->call('alterQuery', $query, $indexInstance);
     if ($query->isfiltered()) {
         $conditions = array_merge($conditions, $indexInstance->getFiltersComponent($query));
     }
     // Invoke delete on index
     $deleteQuery = implode(' ', $conditions);
     $indexInstance->getService()->deleteByQuery($deleteQuery);
 }