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. 13 // 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/>. 16 17 /** 18 * Entry caching for glossary filter. 19 * 20 * @package mod_glossary 21 * @copyright 2014 Petr Skoda 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 namespace mod_glossary\local; 26 defined('MOODLE_INTERNAL') || die(); 27 28 /** 29 * Concept caching for glossary filter. 30 * 31 * @package mod_glossary 32 * @copyright 2014 Petr Skoda 33 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 34 */ 35 class concept_cache { 36 /** 37 * Event observer, do not call directly. 38 * @param \core\event\course_module_updated $event 39 */ 40 public static function cm_updated(\core\event\course_module_updated $event) { 41 if ($event->other['modulename'] !== 'glossary') { 42 return; 43 } 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(); 47 } 48 49 /** 50 * Reset concept related caches. 51 * @param bool $phpunitreset 52 */ 53 public static function reset_caches($phpunitreset = false) { 54 if ($phpunitreset) { 55 return; 56 } 57 $cache = \cache::make('mod_glossary', 'concepts'); 58 $cache->purge(); 59 } 60 61 /** 62 * Reset the cache for course concepts. 63 * @param int $courseid 64 */ 65 public static function reset_course_muc($courseid) { 66 if (empty($courseid)) { 67 return; 68 } 69 $cache = \cache::make('mod_glossary', 'concepts'); 70 $cache->delete((int)$courseid); 71 } 72 73 /** 74 * Reset the cache for global concepts. 75 */ 76 public static function reset_global_muc() { 77 $cache = \cache::make('mod_glossary', 'concepts'); 78 $cache->delete(0); 79 } 80 81 /** 82 * Utility method to purge caches related to given glossary. 83 * @param \stdClass $glossary 84 */ 85 public static function reset_glossary($glossary) { 86 if (!$glossary->usedynalink) { 87 return; 88 } 89 self::reset_course_muc($glossary->course); 90 if ($glossary->globalglossary) { 91 self::reset_global_muc(); 92 } 93 } 94 95 /** 96 * Fetch concepts for given glossaries. 97 * @param int[] $glossaries 98 * @return array 99 */ 100 protected static function fetch_concepts(array $glossaries) { 101 global $DB; 102 103 $glossarylist = implode(',', $glossaries); 104 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 108 109 UNION 110 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 114 115 UNION 116 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"; 121 122 $concepts = array(); 123 $rs = $DB->get_recordset_sql($sql); 124 foreach ($rs as $concept) { 125 $currentconcept = trim(strip_tags($concept->concept)); 126 127 // Concept must be HTML-escaped, so do the same as format_string to turn ampersands into &. 128 $currentconcept = replace_ampersands_not_followed_by_entity($currentconcept); 129 130 if (empty($currentconcept)) { 131 continue; 132 } 133 134 // Rule out any small integers, see MDL-1446. 135 if (is_number($currentconcept) and $currentconcept < 1000) { 136 continue; 137 } 138 139 $concept->concept = $currentconcept; 140 141 $concepts[$concept->glossaryid][] = $concept; 142 } 143 $rs->close(); 144 145 return $concepts; 146 } 147 148 /** 149 * Get all linked concepts from course. 150 * @param int $courseid 151 * @return array 152 */ 153 protected static function get_course_concepts($courseid) { 154 global $DB; 155 156 if (empty($courseid)) { 157 return array(array(), array()); 158 } 159 160 $courseid = (int)$courseid; 161 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()); 168 } 169 170 $cache = \cache::make('mod_glossary', 'concepts'); 171 $data = $cache->get($courseid); 172 if (is_array($data)) { 173 list($glossaries, $allconcepts) = $data; 174 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; 188 } 189 foreach ($glossaries as $id => $name) { 190 $name = str_replace(':', '-', $name); 191 $glossaries[$id] = replace_ampersands_not_followed_by_entity(strip_tags($name)); 192 } 193 194 $allconcepts = self::fetch_concepts(array_keys($glossaries)); 195 foreach ($glossaries as $gid => $unused) { 196 if (!isset($allconcepts[$gid])) { 197 unset($glossaries[$gid]); 198 } 199 } 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; 205 } 206 $cache->set($courseid, array($glossaries, $allconcepts)); 207 } 208 209 $concepts = $allconcepts; 210 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; 218 } 219 if (!$cminfos[$modid]->uservisible) { 220 unset($concepts[$modid]); 221 unset($glossaries[$modid]); 222 continue; 223 } 224 } 225 226 return array($glossaries, $concepts); 227 } 228 229 /** 230 * Get all linked global concepts. 231 * @return array 232 */ 233 protected static function get_global_concepts() { 234 global $DB; 235 236 $cache = \cache::make('mod_glossary', 'concepts'); 237 $data = $cache->get(0); 238 if (is_array($data)) { 239 list($glossaries, $allconcepts) = $data; 240 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; 254 } 255 foreach ($glossaries as $id => $name) { 256 $name = str_replace(':', '-', $name); 257 $glossaries[$id] = replace_ampersands_not_followed_by_entity(strip_tags($name)); 258 } 259 $allconcepts = self::fetch_concepts(array_keys($glossaries)); 260 foreach ($glossaries as $gid => $unused) { 261 if (!isset($allconcepts[$gid])) { 262 unset($glossaries[$gid]); 263 } 264 } 265 $cache->set(0, array($glossaries, $allconcepts)); 266 } 267 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); 271 } 272 273 /** 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 277 */ 278 public static function get_concepts($courseid) { 279 list($glossaries, $concepts) = self::get_course_concepts($courseid); 280 list($globalglossaries, $globalconcepts) = self::get_global_concepts(); 281 282 foreach ($globalconcepts as $gid => $cs) { 283 if (!isset($concepts[$gid])) { 284 $concepts[$gid] = $cs; 285 } 286 } 287 foreach ($globalglossaries as $gid => $name) { 288 if (!isset($glossaries[$gid])) { 289 $glossaries[$gid] = $name; 290 } 291 } 292 293 return array($glossaries, $concepts); 294 } 295 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body