// If there is any problem we trigger the exception as soon as possible.
$client = $this->get_search_client();
- $serverstatus = $this->is_server_ready();
- if ($serverstatus !== true) {
- throw new \core_search\engine_exception('engineserverstatus', 'search');
- }
- $query = new \SolrDisMaxQuery();
- $maxrows = \core_search\manager::MAX_RESULTS;
- if ($this->file_indexing_enabled()) {
- // When using file indexing and grouping, we are going to collapse results, so we want extra results.
- $maxrows *= 2;
+ // Create the query object.
+ $query = $this->create_user_query($filters, $usercontexts);
+
+ // We expect good match rates, so for our first get, we will get a small number of records.
+ // This significantly speeds solr response time for first few pages.
+ $query->setRows(min($limit * 3, static::QUERY_SIZE));
+ $response = $this->get_query_response($query);
+
+ // Get count data out of the response, and reset our counters.
+ list($included, $found) = $this->get_response_counts($response);
+ $this->totalenginedocs = $found;
+ $this->processeddocs = 0;
+ $this->skippeddocs = 0;
+ if ($included == 0 || $this->totalenginedocs == 0) {
+ // No results.
+ return array();
+ }
+
+ // Get valid documents out of the response.
+ $results = $this->process_response($response, $limit);
+
+ // We have processed all the docs in the response at this point.
+ $this->processeddocs += $included;
+
+ // If we haven't reached the limit, and there are more docs left in Solr, lets keep trying.
+ while (count($results) < $limit && ($this->totalenginedocs - $this->processeddocs) > 0) {
+ // Offset the start of the query, and since we are making another call, get more per call.
+ $query->setStart($this->processeddocs);
+ $query->setRows(static::QUERY_SIZE);
+
+ $response = $this->get_query_response($query);
+ list($included, $found) = $this->get_response_counts($response);
+ if ($included == 0 || $found == 0) {
+ // No new results were found. Found being empty would be weird, so we will just return.
+ return $results;
+ }
+ $this->totalenginedocs = $found;
+
+ // Get the new response docs, limiting to remaining we need, then add it to the end of the results array.
+ $newdocs = $this->process_response($response, $limit - count($results));
+ $results = array_merge($results, $newdocs);
+
+ // Add to our processed docs count.
+ $this->processeddocs += $included;
+ }
+
+ return $results;
+ }
+
+ /**
+ * Takes a query and returns the response in SolrObject format.
+ *
+ * @param SolrQuery $query Solr query object.
+ * @return SolrObject|false Response document or false on error.
+ */
+ protected function get_query_response($query) {
+ try {
+ return $this->get_search_client()->query($query)->getResponse();
+ } catch (\SolrClientException $ex) {
+ debugging('Error executing the provided query: ' . $ex->getMessage(), DEBUG_DEVELOPER);
+ $this->queryerror = $ex->getMessage();
+ return false;
+ } catch (\SolrServerException $ex) {
+ debugging('Error executing the provided query: ' . $ex->getMessage(), DEBUG_DEVELOPER);
+ $this->queryerror = $ex->getMessage();
+ return false;
+ }
+ }
+
+ /**
+ * Returns the total number of documents available for the most recently call to execute_query.
+ *
+ * @return int
+ */
+ public function get_query_total_count() {
+ // Return the total engine count minus the docs we have determined are bad.
+ return $this->totalenginedocs - $this->skippeddocs;
+ }
+
+ /**
+ * Returns count information for a provided response. Will return 0, 0 for invalid or empty responses.
+ *
+ * @param SolrDocument $response The response document from Solr.
+ * @return array A two part array. First how many response docs are in the response.
+ * Second, how many results are vailable in the engine.
+ */
+ protected function get_response_counts($response) {
+ $found = 0;
+ $included = 0;
+
+ if (isset($response->grouped->solr_filegroupingid->ngroups)) {
+ // Get the number of results for file grouped queries.
+ $found = $response->grouped->solr_filegroupingid->ngroups;
+ $included = count($response->grouped->solr_filegroupingid->groups);
+ } else if (isset($response->response->numFound)) {
+ // Get the number of results for standard queries.
+ $found = $response->response->numFound;
+ $included = count($response->response->docs);
}
- $this->set_query($query, $data->q, $maxrows);
+
+ return array($included, $found);
+ }
+
+ /**
+ * Prepares a new query object with needed limits, filters, etc.
+ *
+ * @param stdClass $filters Containing query and filters.
+ * @param array $usercontexts Contexts where the user has access. True if the user can access all contexts.
+ * @return SolrDisMaxQuery
+ */
+ protected function create_user_query($filters, $usercontexts) {
+ global $USER;
+
+ // Let's keep these changes internal.
+ $data = clone $filters;
+
+ $query = new \SolrDisMaxQuery();
+
+ $this->set_query($query, $data->q);
$this->add_fields($query);
// Search filters applied, we don't cache these filters as we don't want to pollute the cache with tmp filters
// If the user can access all contexts $usercontexts value is just true, we don't need to filter
// in that case.
if ($usercontexts && is_array($usercontexts)) {
- if (!empty($data->areaid)) {
- $query->addFilterQuery('contextid:(' . implode(' OR ', $usercontexts[$data->areaid]) . ')');
- } else {
- // Join all area contexts into a single array and implode.
- $allcontexts = array();
- foreach ($usercontexts as $areacontexts) {
- foreach ($areacontexts as $contextid) {
- // Ensure they are unique.
- $allcontexts[$contextid] = $contextid;
- }
+ // Join all area contexts into a single array and implode.
+ $allcontexts = array();
+ foreach ($usercontexts as $areaid => $areacontexts) {
+ if (!empty($data->areaids) && !in_array($areaid, $data->areaids)) {
+ // Skip unused areas.
+ continue;
}
- $query->addFilterQuery('contextid:(' . implode(' OR ', $allcontexts) . ')');
+ foreach ($areacontexts as $contextid) {
+ // Ensure they are unique.
+ $allcontexts[$contextid] = $contextid;
+ }
+ }
+ if (empty($allcontexts)) {
+ // This means there are no valid contexts for them, so they get no results.
+ return array();
}
+ $query->addFilterQuery('contextid:(' . implode(' OR ', $allcontexts) . ')');
}
- try {
- if ($this->file_indexing_enabled()) {
- // Now group records by solr_filegroupingid. Limit to 3 results per group.
- $query->setGroup(true);
- $query->setGroupLimit(3);
- $query->addGroupField('solr_filegroupingid');
- return $this->grouped_files_query_response($client->query($query));
- } else {
- return $this->query_response($client->query($query));
- }
- } catch (\SolrClientException $ex) {
- debugging('Error executing the provided query: ' . $ex->getMessage(), DEBUG_DEVELOPER);
- $this->queryerror = $ex->getMessage();
- return array();
- } catch (\SolrServerException $ex) {
- debugging('Error executing the provided query: ' . $ex->getMessage(), DEBUG_DEVELOPER);
- $this->queryerror = $ex->getMessage();
- return array();
+ if ($this->file_indexing_enabled()) {
+ // Now group records by solr_filegroupingid. Limit to 3 results per group.
+ $query->setGroup(true);
+ $query->setGroupLimit(3);
+ $query->setGroupNGroups(true);
+ $query->addGroupField('solr_filegroupingid');
}
+ return $query;
}
/**
// Title.
unset($querydata->timeend);
- $querydata->title = 'moodle/course:renameroles roleid 1';
+ $querydata->title = 'Special title';
$this->assertCount(1, $this->search->search($querydata));
+ // Course IDs.
+ unset($querydata->title);
+ $querydata->courseids = array(SITEID + 1);
+ $this->assertCount(0, $this->search->search($querydata));
+
+ $querydata->courseids = array(SITEID);
+ $this->assertCount(3, $this->search->search($querydata));
+
+ // Now try some area-id combinations.
+ unset($querydata->courseids);
+ $forumpostareaid = \core_search\manager::generate_areaid('mod_forum', 'post');
+ $mockareaid = \core_search\manager::generate_areaid('core_mocksearch', 'role_capabilities');
+
+ $querydata->areaids = array($forumpostareaid);
+ $this->assertCount(0, $this->search->search($querydata));
+
+ $querydata->areaids = array($forumpostareaid, $mockareaid);
+ $this->assertCount(3, $this->search->search($querydata));
+
+ $querydata->areaids = array($mockareaid);
+ $this->assertCount(3, $this->search->search($querydata));
+
+ $querydata->areaids = array();
+ $this->assertCount(3, $this->search->search($querydata));
+
// Check that index contents get updated.
- $DB->delete_records('role_capabilities', array('capability' => 'moodle/course:renameroles'));
+ $this->generator->delete_all();
$this->search->index(true);
unset($querydata->title);
- $querydata->q = '*renameroles*';
+ $querydata->q = '*';
$this->assertCount(0, $this->search->search($querydata));
}