Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.
   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 &amp;.
 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  }