2 // This file is part of Moodle - http://moodle.org/
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
18 * Search area base class for areas working at module level.
20 * @package core_search
21 * @copyright 2015 David Monllao {@link http://www.davidmonllao.com}
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 namespace core_search
;
27 defined('MOODLE_INTERNAL') ||
die();
30 * Base implementation for search areas working at module level.
32 * Even if the search area works at multiple levels, if module is one of these levels
33 * it should extend this class, as this class provides helper methods for module level search management.
35 * @package core_search
36 * @copyright 2015 David Monllao {@link http://www.davidmonllao.com}
37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39 abstract class base_mod
extends base
{
42 * The context levels the search area is working on.
44 * This can be overwriten by the search area if it works at multiple
49 protected static $levels = [CONTEXT_MODULE
];
52 * Returns the module name.
56 protected function get_module_name() {
57 return substr($this->componentname
, 4);
61 * Gets the course module for the required instanceid + modulename.
63 * The returned data depends on the logged user, when calling this through
64 * self::get_document the admin user is used so everything would be returned.
66 * No need more internal caching here, modinfo is already cached.
68 * @throws \dml_missing_record_exception
69 * @param string $modulename The module name
70 * @param int $instanceid Module instance id (depends on the module)
71 * @param int $courseid Helps speeding up things
74 protected function get_cm($modulename, $instanceid, $courseid) {
75 $modinfo = get_fast_modinfo($courseid);
77 // Hopefully not many, they are indexed by cmid.
78 $instances = $modinfo->get_instances_of($modulename);
79 foreach ($instances as $cminfo) {
80 if ($cminfo->instance
== $instanceid) {
86 throw new \
dml_missing_record_exception($modulename);
90 * Helper function that gets SQL useful for restricting a search query given a passed-in
93 * The SQL returned will be zero or more JOIN statements, surrounded by whitespace, which act
94 * as restrictions on the query based on the rows in a module table.
96 * You can pass in a null or system context, which will both return an empty string and no
99 * Returns an array with two nulls if there can be no results for the activity within this
100 * context (e.g. it is a block context).
102 * If named parameters are used, these will be named gcrs0, gcrs1, etc. The table aliases used
103 * in SQL also all begin with gcrs, to avoid conflicts.
105 * @param \context|null $context Context to restrict the query
106 * @param string $modname Name of module e.g. 'forum'
107 * @param string $modtable Alias of table containing module id
108 * @param int $paramtype Type of SQL parameters to use (default question mark)
109 * @return array Array with SQL and parameters; both null if no need to query
110 * @throws \coding_exception If called with invalid params
112 protected function get_context_restriction_sql(\context
$context = null, $modname, $modtable,
113 $paramtype = SQL_PARAMS_QM
) {
120 switch ($paramtype) {
129 case SQL_PARAMS_NAMED
:
138 throw new \
coding_exception('Unexpected $paramtype: ' . $paramtype);
142 switch ($context->contextlevel
) {
147 case CONTEXT_COURSECAT
:
148 // Find all activities of this type within the specified category or any
150 $pathmatch = $DB->sql_like('gcrscc2.path', $DB->sql_concat('gcrscc1.path', $param3));
151 $sql = " JOIN {course_modules} gcrscm ON gcrscm.instance = $modtable.id
152 AND gcrscm.module = (SELECT id FROM {modules} WHERE name = $param1)
153 JOIN {course} gcrsc ON gcrsc.id = gcrscm.course
154 JOIN {course_categories} gcrscc1 ON gcrscc1.id = $param2
155 JOIN {course_categories} gcrscc2 ON gcrscc2.id = gcrsc.category AND
156 (gcrscc2.id = gcrscc1.id OR $pathmatch) ";
157 $params[$key1] = $modname;
158 $params[$key2] = $context->instanceid
;
159 // Note: This param is a bit annoying as it obviously never changes, but sql_like
160 // throws a debug warning if you pass it anything with quotes in, so it has to be
161 // a bound parameter.
162 $params[$key3] = '/%';
166 // Find all activities of this type within the course.
167 $sql = " JOIN {course_modules} gcrscm ON gcrscm.instance = $modtable.id
168 AND gcrscm.course = $param1
169 AND gcrscm.module = (SELECT id FROM {modules} WHERE name = $param2) ";
170 $params[$key1] = $context->instanceid
;
171 $params[$key2] = $modname;
175 // Find only the specified activity of this type.
176 $sql = " JOIN {course_modules} gcrscm ON gcrscm.instance = $modtable.id
177 AND gcrscm.id = $param1
178 AND gcrscm.module = (SELECT id FROM {modules} WHERE name = $param2) ";
179 $params[$key1] = $context->instanceid
;
180 $params[$key2] = $modname;
185 // These contexts cannot contain any activities, so return null.
189 throw new \
coding_exception('Unexpected contextlevel: ' . $context->contextlevel
);
192 return [$sql, $params];
196 * This can be used in subclasses to change ordering within the get_contexts_to_reindex
199 * It returns 2 values:
200 * - Extra SQL joins (tables course_modules 'cm' and context 'x' already exist).
201 * - An ORDER BY value which must use aggregate functions, by default 'MAX(cm.added) DESC'.
203 * Note the query already includes a GROUP BY on the context fields, so if your joins result
204 * in multiple rows, you can use aggregate functions in the ORDER BY. See forum for an example.
206 * @return string[] Array with 2 elements; extra joins for the query, and ORDER BY value
208 protected function get_contexts_to_reindex_extra_sql() {
209 return ['', 'MAX(cm.added) DESC'];
213 * Gets a list of all contexts to reindex when reindexing this search area.
215 * For modules, the default is to return all contexts for modules of that type, in order of
216 * time added (most recent first).
218 * @return \Iterator Iterator of contexts to reindex
219 * @throws \moodle_exception If any DB error
221 public function get_contexts_to_reindex() {
224 list ($extrajoins, $dborder) = $this->get_contexts_to_reindex_extra_sql();
226 $selectcolumns = \context_helper
::get_preload_record_columns_sql('x');
227 $groupbycolumns = '';
228 foreach (\context_helper
::get_preload_record_columns('x') as $column => $thing) {
229 if ($groupbycolumns !== '') {
230 $groupbycolumns .= ',';
232 $groupbycolumns .= $column;
234 $rs = $DB->get_recordset_sql("
235 SELECT $selectcolumns
236 FROM {course_modules} cm
237 JOIN {context} x ON x.instanceid = cm.id AND x.contextlevel = ?
239 WHERE cm.module = (SELECT id FROM {modules} WHERE name = ?)
240 GROUP BY $groupbycolumns
241 ORDER BY $dborder", [CONTEXT_MODULE
, $this->get_module_name()]);
242 return new \core\dml\recordset_walk
($rs, function($rec) {
244 \context_helper
::preload_from_record($rec);
245 return \context
::instance_by_id($id);
250 * Indicates whether this search area may restrict access by group.
252 * This should return true if the search area (sometimes) sets the 'groupid' schema field, and
253 * false if it never sets that field.
255 * (If this function returns false, but the field is set, then results may be restricted
258 * If this returns true, the search engine will automatically apply group restrictions in some
259 * cases (by default, where a module is configured to use separate groups). See function
260 * restrict_cm_access_by_group().
264 public function supports_group_restriction() {
269 * Checks whether the content of this search area should be restricted by group for a
270 * specific module. Called at query time.
272 * The default behaviour simply checks if the effective group mode is SEPARATEGROUPS, which
273 * is probably correct for most cases.
275 * If restricted by group, the search query will (where supported by the engine) filter out
276 * results for groups the user does not belong to, unless the user has 'access all groups'
277 * for the activity. This affects only documents which set the 'groupid' field; results with no
278 * groupid will not be restricted.
280 * Even if you return true to this function, you may still need to do group access checks in
281 * check_access, because the search engine may not support group restrictions.
283 * @param \cm_info $cm
284 * @return bool True to restrict by group
286 public function restrict_cm_access_by_group(\cm_info
$cm) {
287 return $cm->effectivegroupmode
== SEPARATEGROUPS
;
291 * Returns an icon instance for the document.
293 * @param \core_search\document $doc
294 * @return \core_search\document_icon
296 public function get_doc_icon(document
$doc) : document_icon
{
297 return new document_icon('icon', $this->get_module_name());
301 * Returns a list of category names associated with the area.
305 public function get_category_names() {
306 return [manager
::SEARCH_AREA_CATEGORY_COURSE_CONTENT
];