MDL-46868 glossary / filter: performance, check modinfo sooner.
[moodle.git] / mod / glossary / classes / local / concept_cache.php
blobcd5adc8046b5fc9dd9e9837678d43c3d437d3e17
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
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.
8 //
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/>.
17 /**
18 * Entry caching for glossary filter.
20 * @package mod_glossary
21 * @copyright 2014 Petr Skoda
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 namespace mod_glossary\local;
26 defined('MOODLE_INTERNAL') || die();
28 /**
29 * Concept caching for glossary filter.
31 * @package mod_glossary
32 * @copyright 2014 Petr Skoda
33 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35 class concept_cache {
36 /**
37 * Event observer, do not call directly.
38 * @param \core\event\course_module_updated $event
40 public static function cm_updated(\core\event\course_module_updated $event) {
41 if ($event->other['modulename'] !== 'glossary') {
42 return;
44 // We do not know what changed exactly, so let's reset everything that might be affected.
45 concept_cache::reset_course_muc($event->courseid);
46 concept_cache::reset_global_muc();
49 /**
50 * Reset concept related caches.
51 * @param bool $phpunitreset
53 public static function reset_caches($phpunitreset = false) {
54 if ($phpunitreset) {
55 return;
57 $cache = \cache::make('mod_glossary', 'concepts');
58 $cache->purge();
61 /**
62 * Reset the cache for course concepts.
63 * @param int $courseid
65 public static function reset_course_muc($courseid) {
66 if (empty($courseid)) {
67 return;
69 $cache = \cache::make('mod_glossary', 'concepts');
70 $cache->delete((int)$courseid);
73 /**
74 * Reset the cache for global concepts.
76 public static function reset_global_muc() {
77 $cache = \cache::make('mod_glossary', 'concepts');
78 $cache->delete(0);
81 /**
82 * Utility method to purge caches related to given glossary.
83 * @param \stdClass $glossary
85 public static function reset_glossary($glossary) {
86 if (!$glossary->usedynalink) {
87 return;
89 self::reset_course_muc($glossary->course);
90 if ($glossary->globalglossary) {
91 self::reset_global_muc();
95 /**
96 * Fetch concepts for given glossaries.
97 * @param int[] $glossaries
98 * @return array
100 protected static function fetch_concepts(array $glossaries) {
101 global $DB;
103 $glossarylist = implode(',', $glossaries);
105 $sql = "SELECT id, glossaryid, concept, casesensitive, 0 AS category, fullmatch
106 FROM {glossary_entries}
107 WHERE glossaryid IN ($glossarylist) AND usedynalink = 1 AND approved = 1
109 UNION
111 SELECT id, glossaryid, name AS concept, 1 AS casesensitive, 1 AS category, 1 AS fullmatch
112 FROM {glossary_categories}
113 WHERE glossaryid IN ($glossarylist) AND usedynalink = 1
115 UNION
117 SELECT ge.id, ge.glossaryid, ga.alias AS concept, ge.casesensitive, 0 AS category, ge.fullmatch
118 FROM {glossary_alias} ga
119 JOIN {glossary_entries} ge ON (ga.entryid = ge.id)
120 WHERE ge.glossaryid IN ($glossarylist) AND ge.usedynalink = 1 AND ge.approved = 1";
122 $concepts = array();
123 $rs = $DB->get_recordset_sql($sql);
124 foreach ($rs as $concept) {
125 $currentconcept = trim(strip_tags($concept->concept));
127 // Concept must be HTML-escaped, so do the same as format_string to turn ampersands into &amp;.
128 $currentconcept = replace_ampersands_not_followed_by_entity($currentconcept);
130 if (empty($currentconcept)) {
131 continue;
134 // Rule out any small integers, see MDL-1446.
135 if (is_number($currentconcept) and $currentconcept < 1000) {
136 continue;
139 $concept->concept = $currentconcept;
141 $concepts[$concept->glossaryid][] = $concept;
143 $rs->close();
145 return $concepts;
149 * Get all linked concepts from course.
150 * @param int $courseid
151 * @return array
153 protected static function get_course_concepts($courseid) {
154 global $DB;
156 if (empty($courseid)) {
157 return array(array(), array());
160 $courseid = (int)$courseid;
162 // Get info on any glossaries in this course.
163 $modinfo = get_fast_modinfo($courseid);
164 $cminfos = $modinfo->get_instances_of('glossary');
165 if (!$cminfos) {
166 // No glossaries in this course, so don't do any work.
167 return array(array(), array());
170 $cache = \cache::make('mod_glossary', 'concepts');
171 $data = $cache->get($courseid);
172 if (is_array($data)) {
173 list($glossaries, $allconcepts) = $data;
175 } else {
176 // Find all course glossaries.
177 $sql = "SELECT g.id, g.name
178 FROM {glossary} g
179 JOIN {course_modules} cm ON (cm.instance = g.id)
180 JOIN {modules} m ON (m.name = 'glossary' AND m.id = cm.module)
181 WHERE g.usedynalink = 1 AND g.course = :course AND cm.visible = 1 AND m.visible = 1
182 ORDER BY g.globalglossary, g.id";
183 $glossaries = $DB->get_records_sql_menu($sql, array('course' => $courseid));
184 if (!$glossaries) {
185 $data = array(array(), array());
186 $cache->set($courseid, $data);
187 return $data;
189 foreach ($glossaries as $id => $name) {
190 $name = str_replace(':', '-', $name);
191 $glossaries[$id] = replace_ampersands_not_followed_by_entity(strip_tags($name));
194 $allconcepts = self::fetch_concepts(array_keys($glossaries));
195 foreach ($glossaries as $gid => $unused) {
196 if (!isset($allconcepts[$gid])) {
197 unset($glossaries[$gid]);
200 if (!$glossaries) {
201 // This means there are no interesting concepts in the existing glossaries.
202 $data = array(array(), array());
203 $cache->set($courseid, $data);
204 return $data;
206 $cache->set($courseid, array($glossaries, $allconcepts));
209 $concepts = $allconcepts;
211 // Verify access control to glossary instances.
212 foreach ($concepts as $modid => $unused) {
213 if (!isset($cminfos[$modid])) {
214 // This should not happen.
215 unset($concepts[$modid]);
216 unset($glossaries[$modid]);
217 continue;
219 if (!$cminfos[$modid]->uservisible) {
220 unset($concepts[$modid]);
221 unset($glossaries[$modid]);
222 continue;
226 return array($glossaries, $concepts);
230 * Get all linked global concepts.
231 * @return array
233 protected static function get_global_concepts() {
234 global $DB;
236 $cache = \cache::make('mod_glossary', 'concepts');
237 $data = $cache->get(0);
238 if (is_array($data)) {
239 list($glossaries, $allconcepts) = $data;
241 } else {
242 // Find all global glossaries - no access control here.
243 $sql = "SELECT g.id, g.name
244 FROM {glossary} g
245 JOIN {course_modules} cm ON (cm.instance = g.id)
246 JOIN {modules} m ON (m.name = 'glossary' AND m.id = cm.module)
247 WHERE g.usedynalink = 1 AND g.globalglossary = 1 AND cm.visible = 1 AND m.visible = 1
248 ORDER BY g.globalglossary, g.id";
249 $glossaries = $DB->get_records_sql_menu($sql);
250 if (!$glossaries) {
251 $data = array(array(), array());
252 $cache->set(0, $data);
253 return $data;
255 foreach ($glossaries as $id => $name) {
256 $name = str_replace(':', '-', $name);
257 $glossaries[$id] = replace_ampersands_not_followed_by_entity(strip_tags($name));
259 $allconcepts = self::fetch_concepts(array_keys($glossaries));
260 foreach ($glossaries as $gid => $unused) {
261 if (!isset($allconcepts[$gid])) {
262 unset($glossaries[$gid]);
265 $cache->set(0, array($glossaries, $allconcepts));
268 // NOTE: no access control is here because it would be way too expensive to check access
269 // to all courses that contain the global glossaries.
270 return array($glossaries, $allconcepts);
274 * Get all concepts that should be linked in the given course.
275 * @param int $courseid
276 * @return array with two elements - array of glossaries and concepts for each glossary
278 public static function get_concepts($courseid) {
279 list($glossaries, $concepts) = self::get_course_concepts($courseid);
280 list($globalglossaries, $globalconcepts) = self::get_global_concepts();
282 foreach ($globalconcepts as $gid => $cs) {
283 if (!isset($concepts[$gid])) {
284 $concepts[$gid] = $cs;
287 foreach ($globalglossaries as $gid => $name) {
288 if (!isset($glossaries[$gid])) {
289 $glossaries[$gid] = $name;
293 return array($glossaries, $concepts);