Source of file ResultList.php
Size: 14,704 Bytes - Last Modified: 2021-12-23T10:58:34+00:00
/var/www/docs.ssmods.com/process/src/src/SilverStripe/Elastica/ResultList.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481 | <?php namespace SilverStripe\Elastica; use Elastica\Index; use Elastica\Query; /** * A list wrapper around the results from a query. Note that not all operations are implemented. */ class ResultList extends \ViewableData implements \SS_list { /** * @var \Elastica\Index */ private $service; /** * @var \Elastica\Query */ private $query; /** * List of types to search for, default (blank) returns all. * * @var string */ private $types = ''; /** * Filters, i.e. selected aggregations, to apply to the search. */ private $filters = array(); /** * An array list of aggregations from this search. * * @var ArrayList */ private $aggregations; /** * Create a search and then optionally tweak it. Actual search is only performed against * Elasticsearch when the getResults() method is called. * * @param ElasticaService $service object used to communicate with Elasticsearch * @param Query $query Elastica query object, created via QueryGenerator * @param string $queryText the text from the query * @param array $filters Selected filters, used for aggregation purposes only * (i.e. query already filtered prior to this) */ public function __construct(ElasticaService $service, Query $query, $queryText, $filters = array()) { $this->service = $service; $this->query = $query; $this->originalQueryText = $queryText; $this->filters = $filters; } public function __clone() { $this->query = clone $this->query; } /** * @return \Elastica\Index */ public function getService() { return $this->service; } /** * Set a new list of types (SilverStripe classes) to search for. * * @param string $newTypes comma separated list of types to search for */ public function setTypes($newTypes) { $this->types = $newTypes; } /** * @return \Elastica\Query */ public function getQuery() { return $this->query; } /** * Get the aggregation results for this query. Should only be called * after $this->getResults() has been executed. * Note this will be an empty array list if there is no aggregation. * * @return ArrayList ArrayList of the aggregated results for this query */ public function getAggregations() { return $this->aggregations; } /** * @return array */ public function getResults() { if (!isset($this->_cachedResults)) { $ers = $this->service->search($this->query, $this->types); if (isset($ers->MoreLikeThisTerms)) { $this->MoreLikeThisTerms = $ers->MoreLikeThisTerms; } $suggestions = $ers->getSuggests(); if (isset($suggestions['query-phrase-suggestions'])) { $suggest = $suggestions['query-phrase-suggestions']; $suggestedPhraseAndHL = ElasticaUtil::getPhraseSuggestion($suggest); if ($suggestedPhraseAndHL) { $this->SuggestedQuery = $suggestedPhraseAndHL['suggestedQuery']; $this->SuggestedQueryHighlighted = $suggestedPhraseAndHL['suggestedQueryHighlighted']; } } $this->TotalItems = $ers->getTotalHits(); $this->TotalTime = $ers->getTotalTime(); $this->_cachedResults = $ers->getResults(); // make the aggregations available to the templating, title casing // to be consistent with normal templating conventions $aggs = $ers->getAggregations(); // array of index field name to human readable title $indexedFieldTitleMapping = array(); // optionally remap keys and store chosen aggregations from get params if (isset($this->SearchHelper)) { $manipulator = \Injector::inst()->create($this->SearchHelper); $manipulator->query = $this->query; $manipulator->updateAggregation($aggs); $indexedFieldTitleMapping = $manipulator->getIndexFieldTitleMapping(); } $aggsTemplate = new \ArrayList(); // Convert the buckets into a form suitable for SilverStripe templates $queryText = $this->originalQueryText; // if not search term remove it and aggregate with a blank query if ($queryText == '' && sizeof($aggs) > 0) { $params = $this->query->getParams(); unset($params['query']); $this->query->setParams($params); $queryText = ''; } // get the base URL for the current facets selected $baseURL = \Controller::curr()->Link().'?'; $prefixAmp = false; if ($queryText !== '') { $baseURL .= 'q='.urlencode($queryText); $prefixAmp = true; } // now add the selected facets foreach ($this->filters as $key => $value) { if ($prefixAmp) { $baseURL .= '&'; } else { $prefixAmp = true; } $baseURL .= $key.'='.urlencode($value); } foreach (array_keys($aggs) as $key) { $aggDO = new \DataObject(); //FIXME - Camel case separate here if (isset($indexedFieldTitleMapping[$key])) { $aggDO->Name = $indexedFieldTitleMapping[$key]; } else { $aggDO->Name = $key; } $aggDO->Slug = preg_replace( '/[^a-zA-Z0-9_]/', '_', strtolower($aggDO->Name) ); // now the buckets if (isset($aggs[$key]['buckets'])) { $bucketsAL = new \ArrayList(); foreach ($aggs[$key]['buckets'] as $value) { $ct = new \DataObject(); $ct->Key = $value['key']; $ct->DocumentCount = $value['doc_count']; $query[$key] = $value; if ($prefixAmp) { $url = $baseURL.'&'; } else { $url = $baseURL; $prefixAmp = true; } // check if currently selected if (isset($this->filters[$key])) { if ($this->filters[$key] === (string) $value['key']) { $ct->IsSelected = true; // mark this facet as having been selected, so optional toggling // of the display of the facet can be done via the template. $aggDO->IsSelected = true; $urlParam = $key.'='.urlencode($this->filters[$key]); // possible ampersand combos to remove $v2 = '&'.$urlParam; $v3 = $urlParam.'&'; $url = str_replace($v2, '', $url); $url = str_replace($v3, '', $url); $url = str_replace($urlParam, '', $url); $ct->URL = $url; } } else { $url .= $key.'='.urlencode($value['key']); $prefixAmp = true; } $url = rtrim($url, '&'); $ct->URL = $url; $bucketsAL->push($ct); } // in the case of range queries we wish to remove the non selected ones if ($aggDO->IsSelected) { $newList = new \ArrayList(); foreach ($bucketsAL->getIterator() as $bucket) { if ($bucket->IsSelected) { $newList->push($bucket); break; } } $bucketsAL = $newList; } $aggDO->Buckets = $bucketsAL; } $aggsTemplate->push($aggDO); } $this->aggregations = $aggsTemplate; } return $this->_cachedResults; } public function getTotalItems() { $this->getResults(); return $this->TotalItems; } public function getTotalTime() { return $this->TotalTime; } public function getIterator() { return $this->toArrayList()->getIterator(); } public function limit($limit, $offset = 0) { $list = clone $this; $list->getQuery()->setSize($limit); $list->getQuery()->setFrom($offset); return $list; } /** * Converts results of type {@link \Elastica\Result} * into their respective {@link DataObject} counterparts. * * @return array DataObject[] */ public function toArray() { $result = array(); /** @var $found \Elastica\Result[] */ $found = $this->getResults(); $needed = array(); $retrieved = array(); foreach ($found as $item) { $type = $item->getType(); if (!array_key_exists($type, $needed)) { $needed[$type] = array($item->getId()); $retrieved[$type] = array(); } else { $needed[$type][] = $item->getId(); } } foreach ($needed as $class => $ids) { foreach ($class::get()->byIDs($ids) as $record) { $retrieved[$class][$record->ID] = $record; } } // Title and Link are special cases $ignore = array('Title', 'Link', 'Title.standard', 'Link.standard'); foreach ($found as $item) { // Safeguards against indexed items which might no longer be in the DB if (array_key_exists($item->getId(), $retrieved[$item->getType()])) { $data_object = $retrieved[$item->getType()][$item->getId()]; $data_object->setElasticaResult($item); $highlights = $item->getHighlights(); //$snippets will contain the highlights shown in the body of the search result //$namedSnippets will be used to add highlights to the Link and Title $snippets = new \ArrayList(); $namedSnippets = new \ArrayList(); foreach (array_keys($highlights) as $fieldName) { $fieldSnippets = new \ArrayList(); foreach ($highlights[$fieldName] as $snippet) { $do = new \DataObject(); $do->Snippet = $snippet; // skip title and link in the summary of highlights if (!in_array($fieldName, $ignore)) { $snippets->push($do); } $fieldSnippets->push($do); } if ($fieldSnippets->count() > 0) { //Fields may have a dot in their name, e.g. Title.standard - take this into account //As dots are an issue with template syntax, store as Title_standard $splits = explode('.', $fieldName); if (sizeof($splits) == 1) { $namedSnippets->$fieldName = $fieldSnippets; } else { // The Title.standard case, for example $splits = explode('.', $fieldName); $compositeFielddName = $splits[0].'_'.$splits[1]; $namedSnippets->$compositeFielddName = $fieldSnippets; } } } $data_object->SearchHighlights = $snippets; $data_object->SearchHighlightsByField = $namedSnippets; $result[] = $data_object; } } return $result; } public function toArrayList() { return new \ArrayList($this->toArray()); } public function toNestedArray() { $result = array(); foreach ($this as $record) { $result[] = $record->toMap(); } return $result; } public function first() { // TODO throw new \Exception('Not implemented'); } public function last() { // TODO: Implement last() method throw new \Exception('Not implemented'); } public function map($key = 'ID', $title = 'Title') { return $this->toArrayList()->map($key, $title); } public function column($col = 'ID') { if ($col == 'ID') { $ids = array(); foreach ($this->getResults() as $result) { $ids[] = $result->getId(); } return $ids; } else { return $this->toArrayList()->column($col); } } public function each($callback) { return $this->toArrayList()->each($callback); } public function count() { return count($this->toArray()); } /** * @ignore */ public function offsetExists($offset) { throw new \Exception('Not implemented'); } /** * @ignore */ public function offsetGet($offset) { throw new \Exception('Not implemented'); } /** * @ignore */ public function offsetSet($offset, $value) { throw new \Exception('Not implemented'); } /** * @ignore */ public function offsetUnset($offset) { throw new \Exception('Not implemented'); } /** * @ignore */ public function add($item) { throw new \Exception('Not implemented'); } /** * @ignore */ public function remove($item) { throw new \Exception('Not implemented'); } /** * @ignore */ public function find($key, $value) { throw new \Exception('Not implemented'); } } |