Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]

   1  <?php
   2  
   3  // This file is part of Moodle - http://moodle.org/
   4  //
   5  // Moodle is free software: you can redistribute it and/or modify
   6  // it under the terms of the GNU General Public License as published by
   7  // the Free Software Foundation, either version 3 of the License, or
   8  // (at your option) any later version.
   9  //
  10  // Moodle is distributed in the hope that it will be useful,
  11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13  // GNU General Public License for more details.
  14  //
  15  // You should have received a copy of the GNU General Public License
  16  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  17  
  18  /**
  19   * Library of functions and constants for module glossary
  20   * (replace glossary with the name of your module and delete this line)
  21   *
  22   * @package   mod_glossary
  23   * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
  24   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  require_once($CFG->libdir . '/completionlib.php');
  27  
  28  define("GLOSSARY_SHOW_ALL_CATEGORIES", 0);
  29  define("GLOSSARY_SHOW_NOT_CATEGORISED", -1);
  30  
  31  define("GLOSSARY_NO_VIEW", -1);
  32  define("GLOSSARY_STANDARD_VIEW", 0);
  33  define("GLOSSARY_CATEGORY_VIEW", 1);
  34  define("GLOSSARY_DATE_VIEW", 2);
  35  define("GLOSSARY_AUTHOR_VIEW", 3);
  36  define("GLOSSARY_ADDENTRY_VIEW", 4);
  37  define("GLOSSARY_IMPORT_VIEW", 5);
  38  define("GLOSSARY_EXPORT_VIEW", 6);
  39  define("GLOSSARY_APPROVAL_VIEW", 7);
  40  
  41  // Glossary tabs.
  42  define('GLOSSARY_STANDARD', 'standard');
  43  define('GLOSSARY_AUTHOR', 'author');
  44  define('GLOSSARY_CATEGORY', 'category');
  45  define('GLOSSARY_DATE', 'date');
  46  
  47  // Glossary displayformats.
  48  define('GLOSSARY_CONTINUOUS', 'continuous');
  49  define('GLOSSARY_DICTIONARY', 'dictionary');
  50  define('GLOSSARY_FULLWITHOUTAUTHOR', 'fullwithoutauthor');
  51  
  52  require_once (__DIR__ . '/deprecatedlib.php');
  53  
  54  /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
  55  /**
  56   * @global object
  57   * @param object $glossary
  58   * @return int
  59   */
  60  function glossary_add_instance($glossary) {
  61      global $DB;
  62  /// Given an object containing all the necessary data,
  63  /// (defined by the form in mod_form.php) this function
  64  /// will create a new instance and return the id number
  65  /// of the new instance.
  66  
  67      if (empty($glossary->ratingtime) or empty($glossary->assessed)) {
  68          $glossary->assesstimestart  = 0;
  69          $glossary->assesstimefinish = 0;
  70      }
  71  
  72      if (empty($glossary->globalglossary) ) {
  73          $glossary->globalglossary = 0;
  74      }
  75  
  76      if (!has_capability('mod/glossary:manageentries', context_system::instance())) {
  77          $glossary->globalglossary = 0;
  78      }
  79  
  80      $glossary->timecreated  = time();
  81      $glossary->timemodified = $glossary->timecreated;
  82  
  83      //Check displayformat is a valid one
  84      $formats = get_list_of_plugins('mod/glossary/formats','TEMPLATE');
  85      if (!in_array($glossary->displayformat, $formats)) {
  86          print_error('unknowformat', '', '', $glossary->displayformat);
  87      }
  88  
  89      $returnid = $DB->insert_record("glossary", $glossary);
  90      $glossary->id = $returnid;
  91      glossary_grade_item_update($glossary);
  92  
  93      $completiontimeexpected = !empty($glossary->completionexpected) ? $glossary->completionexpected : null;
  94      \core_completion\api::update_completion_date_event($glossary->coursemodule,
  95          'glossary', $glossary->id, $completiontimeexpected);
  96  
  97      return $returnid;
  98  }
  99  
 100  /**
 101   * Given an object containing all the necessary data,
 102   * (defined by the form in mod_form.php) this function
 103   * will update an existing instance with new data.
 104   *
 105   * @global object
 106   * @global object
 107   * @param object $glossary
 108   * @return bool
 109   */
 110  function glossary_update_instance($glossary) {
 111      global $CFG, $DB;
 112  
 113      if (empty($glossary->globalglossary)) {
 114          $glossary->globalglossary = 0;
 115      }
 116  
 117      if (!has_capability('mod/glossary:manageentries', context_system::instance())) {
 118          // keep previous
 119          unset($glossary->globalglossary);
 120      }
 121  
 122      $glossary->timemodified = time();
 123      $glossary->id           = $glossary->instance;
 124  
 125      if (empty($glossary->ratingtime) or empty($glossary->assessed)) {
 126          $glossary->assesstimestart  = 0;
 127          $glossary->assesstimefinish = 0;
 128      }
 129  
 130      //Check displayformat is a valid one
 131      $formats = get_list_of_plugins('mod/glossary/formats','TEMPLATE');
 132      if (!in_array($glossary->displayformat, $formats)) {
 133          print_error('unknowformat', '', '', $glossary->displayformat);
 134      }
 135  
 136      $DB->update_record("glossary", $glossary);
 137      if ($glossary->defaultapproval) {
 138          $DB->execute("UPDATE {glossary_entries} SET approved = 1 where approved <> 1 and glossaryid = ?", array($glossary->id));
 139      }
 140      glossary_grade_item_update($glossary);
 141  
 142      $completiontimeexpected = !empty($glossary->completionexpected) ? $glossary->completionexpected : null;
 143      \core_completion\api::update_completion_date_event($glossary->coursemodule,
 144          'glossary', $glossary->id, $completiontimeexpected);
 145  
 146      return true;
 147  }
 148  
 149  /**
 150   * Given an ID of an instance of this module,
 151   * this function will permanently delete the instance
 152   * and any data that depends on it.
 153   *
 154   * @global object
 155   * @param int $id glossary id
 156   * @return bool success
 157   */
 158  function glossary_delete_instance($id) {
 159      global $DB, $CFG;
 160  
 161      if (!$glossary = $DB->get_record('glossary', array('id'=>$id))) {
 162          return false;
 163      }
 164  
 165      if (!$cm = get_coursemodule_from_instance('glossary', $id)) {
 166          return false;
 167      }
 168  
 169      if (!$context = context_module::instance($cm->id, IGNORE_MISSING)) {
 170          return false;
 171      }
 172  
 173      $fs = get_file_storage();
 174  
 175      if ($glossary->mainglossary) {
 176          // unexport entries
 177          $sql = "SELECT ge.id, ge.sourceglossaryid, cm.id AS sourcecmid
 178                    FROM {glossary_entries} ge
 179                    JOIN {modules} m ON m.name = 'glossary'
 180                    JOIN {course_modules} cm ON (cm.module = m.id AND cm.instance = ge.sourceglossaryid)
 181                   WHERE ge.glossaryid = ? AND ge.sourceglossaryid > 0";
 182  
 183          if ($exported = $DB->get_records_sql($sql, array($id))) {
 184              foreach ($exported as $entry) {
 185                  $entry->glossaryid = $entry->sourceglossaryid;
 186                  $entry->sourceglossaryid = 0;
 187                  $newcontext = context_module::instance($entry->sourcecmid);
 188                  if ($oldfiles = $fs->get_area_files($context->id, 'mod_glossary', 'attachment', $entry->id)) {
 189                      foreach ($oldfiles as $oldfile) {
 190                          $file_record = new stdClass();
 191                          $file_record->contextid = $newcontext->id;
 192                          $fs->create_file_from_storedfile($file_record, $oldfile);
 193                      }
 194                      $fs->delete_area_files($context->id, 'mod_glossary', 'attachment', $entry->id);
 195                      $entry->attachment = '1';
 196                  } else {
 197                      $entry->attachment = '0';
 198                  }
 199                  $DB->update_record('glossary_entries', $entry);
 200              }
 201          }
 202      } else {
 203          // move exported entries to main glossary
 204          $sql = "UPDATE {glossary_entries}
 205                     SET sourceglossaryid = 0
 206                   WHERE sourceglossaryid = ?";
 207          $DB->execute($sql, array($id));
 208      }
 209  
 210      // Delete any dependent records
 211      $entry_select = "SELECT id FROM {glossary_entries} WHERE glossaryid = ?";
 212      $DB->delete_records_select('comments', "contextid=? AND commentarea=? AND itemid IN ($entry_select)", array($id, 'glossary_entry', $context->id));
 213      $DB->delete_records_select('glossary_alias',    "entryid IN ($entry_select)", array($id));
 214  
 215      $category_select = "SELECT id FROM {glossary_categories} WHERE glossaryid = ?";
 216      $DB->delete_records_select('glossary_entries_categories', "categoryid IN ($category_select)", array($id));
 217      $DB->delete_records('glossary_categories', array('glossaryid'=>$id));
 218      $DB->delete_records('glossary_entries', array('glossaryid'=>$id));
 219  
 220      // delete all files
 221      $fs->delete_area_files($context->id);
 222  
 223      glossary_grade_item_delete($glossary);
 224  
 225      \core_completion\api::update_completion_date_event($cm->id, 'glossary', $glossary->id, null);
 226  
 227      $DB->delete_records('glossary', array('id'=>$id));
 228  
 229      // Reset caches.
 230      \mod_glossary\local\concept_cache::reset_glossary($glossary);
 231  
 232      return true;
 233  }
 234  
 235  /**
 236   * Return a small object with summary information about what a
 237   * user has done with a given particular instance of this module
 238   * Used for user activity reports.
 239   * $return->time = the time they did it
 240   * $return->info = a short text description
 241   *
 242   * @param object $course
 243   * @param object $user
 244   * @param object $mod
 245   * @param object $glossary
 246   * @return object|null
 247   */
 248  function glossary_user_outline($course, $user, $mod, $glossary) {
 249      global $CFG;
 250  
 251      require_once("$CFG->libdir/gradelib.php");
 252      $grades = grade_get_grades($course->id, 'mod', 'glossary', $glossary->id, $user->id);
 253      if (empty($grades->items[0]->grades)) {
 254          $grade = false;
 255      } else {
 256          $grade = reset($grades->items[0]->grades);
 257      }
 258  
 259      if ($entries = glossary_get_user_entries($glossary->id, $user->id)) {
 260          $result = new stdClass();
 261          $result->info = count($entries) . ' ' . get_string("entries", "glossary");
 262  
 263          $lastentry = array_pop($entries);
 264          $result->time = $lastentry->timemodified;
 265  
 266          if ($grade) {
 267              if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
 268                  $result->info .= ', ' . get_string('gradenoun') . ': ' . $grade->str_long_grade;
 269              } else {
 270                  $result->info = get_string('gradenoun') . ': ' . get_string('hidden', 'grades');
 271              }
 272          }
 273          return $result;
 274      } else if ($grade) {
 275          $result = (object) [
 276              'time' => grade_get_date_for_user_grade($grade, $user),
 277          ];
 278          if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
 279              $result->info = get_string('gradenoun') . ': ' . $grade->str_long_grade;
 280          } else {
 281              $result->info = get_string('gradenoun') . ': ' . get_string('hidden', 'grades');
 282          }
 283  
 284          return $result;
 285      }
 286      return NULL;
 287  }
 288  
 289  /**
 290   * @global object
 291   * @param int $glossaryid
 292   * @param int $userid
 293   * @return array
 294   */
 295  function glossary_get_user_entries($glossaryid, $userid) {
 296  /// Get all the entries for a user in a glossary
 297      global $DB;
 298  
 299      return $DB->get_records_sql("SELECT e.*, u.firstname, u.lastname, u.email, u.picture
 300                                     FROM {glossary} g, {glossary_entries} e, {user} u
 301                               WHERE g.id = ?
 302                                 AND e.glossaryid = g.id
 303                                 AND e.userid = ?
 304                                 AND e.userid = u.id
 305                            ORDER BY e.timemodified ASC", array($glossaryid, $userid));
 306  }
 307  
 308  /**
 309   * Print a detailed representation of what a  user has done with
 310   * a given particular instance of this module, for user activity reports.
 311   *
 312   * @global object
 313   * @param object $course
 314   * @param object $user
 315   * @param object $mod
 316   * @param object $glossary
 317   */
 318  function glossary_user_complete($course, $user, $mod, $glossary) {
 319      global $CFG, $OUTPUT;
 320      require_once("$CFG->libdir/gradelib.php");
 321  
 322      $grades = grade_get_grades($course->id, 'mod', 'glossary', $glossary->id, $user->id);
 323      if (!empty($grades->items[0]->grades)) {
 324          $grade = reset($grades->items[0]->grades);
 325          if (!$grade->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
 326              echo $OUTPUT->container(get_string('gradenoun') . ': ' . $grade->str_long_grade);
 327              if ($grade->str_feedback) {
 328                  echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
 329              }
 330          } else {
 331              echo $OUTPUT->container(get_string('gradenoun') . ': ' . get_string('hidden', 'grades'));
 332          }
 333      }
 334  
 335      if ($entries = glossary_get_user_entries($glossary->id, $user->id)) {
 336          echo '<table width="95%" border="0"><tr><td>';
 337          foreach ($entries as $entry) {
 338              $cm = get_coursemodule_from_instance("glossary", $glossary->id, $course->id);
 339              glossary_print_entry($course, $cm, $glossary, $entry,"","",0);
 340              echo '<p>';
 341          }
 342          echo '</td></tr></table>';
 343      }
 344  }
 345  
 346  /**
 347   * Returns all glossary entries since a given time for specified glossary
 348   *
 349   * @param array $activities sequentially indexed array of objects
 350   * @param int   $index
 351   * @param int   $timestart
 352   * @param int   $courseid
 353   * @param int   $cmid
 354   * @param int   $userid defaults to 0
 355   * @param int   $groupid defaults to 0
 356   * @return void adds items into $activities and increases $index
 357   */
 358  function glossary_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid = 0, $groupid = 0) {
 359      global $COURSE, $USER, $DB;
 360  
 361      if ($COURSE->id == $courseid) {
 362          $course = $COURSE;
 363      } else {
 364          $course = $DB->get_record('course', array('id' => $courseid));
 365      }
 366  
 367      $modinfo = get_fast_modinfo($course);
 368      $cm = $modinfo->cms[$cmid];
 369      $context = context_module::instance($cm->id);
 370  
 371      if (!$cm->uservisible) {
 372          return;
 373      }
 374  
 375      $viewfullnames = has_capability('moodle/site:viewfullnames', $context);
 376      // Groups are not yet supported for glossary. See MDL-10728 .
 377      /*
 378      $accessallgroups = has_capability('moodle/site:accessallgroups', $context);
 379      $groupmode = groups_get_activity_groupmode($cm, $course);
 380       */
 381  
 382      $params['timestart'] = $timestart;
 383  
 384      if ($userid) {
 385          $userselect = "AND u.id = :userid";
 386          $params['userid'] = $userid;
 387      } else {
 388          $userselect = '';
 389      }
 390  
 391      if ($groupid) {
 392          $groupselect = 'AND gm.groupid = :groupid';
 393          $groupjoin   = 'JOIN {groups_members} gm ON  gm.userid=u.id';
 394          $params['groupid'] = $groupid;
 395      } else {
 396          $groupselect = '';
 397          $groupjoin   = '';
 398      }
 399  
 400      $approvedselect = "";
 401      if (!has_capability('mod/glossary:approve', $context)) {
 402          $approvedselect = " AND ge.approved = 1 ";
 403      }
 404  
 405      $params['timestart'] = $timestart;
 406      $params['glossaryid'] = $cm->instance;
 407  
 408      $userfieldsapi = \core_user\fields::for_userpic();
 409      $ufields = $userfieldsapi->get_sql('u', false, '', 'userid', false)->selects;
 410      $entries = $DB->get_records_sql("
 411                SELECT ge.id AS entryid, ge.glossaryid, ge.concept, ge.definition, ge.approved,
 412                       ge.timemodified, $ufields
 413                  FROM {glossary_entries} ge
 414                  JOIN {user} u ON u.id = ge.userid
 415                       $groupjoin
 416                 WHERE ge.timemodified > :timestart
 417                   AND ge.glossaryid = :glossaryid
 418                       $approvedselect
 419                       $userselect
 420                       $groupselect
 421              ORDER BY ge.timemodified ASC", $params);
 422  
 423      if (!$entries) {
 424          return;
 425      }
 426  
 427      foreach ($entries as $entry) {
 428          // Groups are not yet supported for glossary. See MDL-10728 .
 429          /*
 430          $usersgroups = null;
 431          if ($entry->userid != $USER->id) {
 432              if ($groupmode == SEPARATEGROUPS and !$accessallgroups) {
 433                  if (is_null($usersgroups)) {
 434                      $usersgroups = groups_get_all_groups($course->id, $entry->userid, $cm->groupingid);
 435                      if (is_array($usersgroups)) {
 436                          $usersgroups = array_keys($usersgroups);
 437                      } else {
 438                          $usersgroups = array();
 439                      }
 440                  }
 441                  if (!array_intersect($usersgroups, $modinfo->get_groups($cm->groupingid))) {
 442                      continue;
 443                  }
 444              }
 445          }
 446           */
 447  
 448          $tmpactivity                       = new stdClass();
 449          $tmpactivity->user                 = user_picture::unalias($entry, null, 'userid');
 450          $tmpactivity->user->fullname       = fullname($tmpactivity->user, $viewfullnames);
 451          $tmpactivity->type                 = 'glossary';
 452          $tmpactivity->cmid                 = $cm->id;
 453          $tmpactivity->glossaryid           = $entry->glossaryid;
 454          $tmpactivity->name                 = format_string($cm->name, true);
 455          $tmpactivity->sectionnum           = $cm->sectionnum;
 456          $tmpactivity->timestamp            = $entry->timemodified;
 457          $tmpactivity->content              = new stdClass();
 458          $tmpactivity->content->entryid     = $entry->entryid;
 459          $tmpactivity->content->concept     = $entry->concept;
 460          $tmpactivity->content->definition  = $entry->definition;
 461          $tmpactivity->content->approved    = $entry->approved;
 462  
 463          $activities[$index++] = $tmpactivity;
 464      }
 465  
 466      return true;
 467  }
 468  
 469  /**
 470   * Outputs the glossary entry indicated by $activity
 471   *
 472   * @param object $activity      the activity object the glossary resides in
 473   * @param int    $courseid      the id of the course the glossary resides in
 474   * @param bool   $detail        not used, but required for compatibilty with other modules
 475   * @param int    $modnames      not used, but required for compatibilty with other modules
 476   * @param bool   $viewfullnames not used, but required for compatibilty with other modules
 477   * @return void
 478   */
 479  function glossary_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
 480      global $OUTPUT;
 481  
 482      echo html_writer::start_tag('div', array('class'=>'glossary-activity clearfix'));
 483      if (!empty($activity->user)) {
 484          echo html_writer::tag('div', $OUTPUT->user_picture($activity->user, array('courseid'=>$courseid)),
 485              array('class' => 'glossary-activity-picture'));
 486      }
 487  
 488      echo html_writer::start_tag('div', array('class'=>'glossary-activity-content'));
 489      echo html_writer::start_tag('div', array('class'=>'glossary-activity-entry'));
 490  
 491      if (isset($activity->content->approved) && !$activity->content->approved) {
 492          $urlparams = array('g' => $activity->glossaryid, 'mode' => 'approval', 'hook' => $activity->content->concept);
 493          $class = array('class' => 'dimmed_text');
 494      } else {
 495          $urlparams = array('g' => $activity->glossaryid, 'mode' => 'entry', 'hook' => $activity->content->entryid);
 496          $class = array();
 497      }
 498      echo html_writer::link(new moodle_url('/mod/glossary/view.php', $urlparams),
 499              strip_tags($activity->content->concept), $class);
 500      echo html_writer::end_tag('div');
 501  
 502      $url = new moodle_url('/user/view.php', array('course'=>$courseid, 'id'=>$activity->user->id));
 503      $name = $activity->user->fullname;
 504      $link = html_writer::link($url, $name, $class);
 505  
 506      echo html_writer::start_tag('div', array('class'=>'user'));
 507      echo $link .' - '. userdate($activity->timestamp);
 508      echo html_writer::end_tag('div');
 509  
 510      echo html_writer::end_tag('div');
 511  
 512      echo html_writer::end_tag('div');
 513      return;
 514  }
 515  /**
 516   * Given a course and a time, this module should find recent activity
 517   * that has occurred in glossary activities and print it out.
 518   * Return true if there was output, or false is there was none.
 519   *
 520   * @global object
 521   * @global object
 522   * @global object
 523   * @param object $course
 524   * @param object $viewfullnames
 525   * @param int $timestart
 526   * @return bool
 527   */
 528  function glossary_print_recent_activity($course, $viewfullnames, $timestart) {
 529      global $CFG, $USER, $DB, $OUTPUT, $PAGE;
 530  
 531      //TODO: use timestamp in approved field instead of changing timemodified when approving in 2.0
 532      if (!defined('GLOSSARY_RECENT_ACTIVITY_LIMIT')) {
 533          define('GLOSSARY_RECENT_ACTIVITY_LIMIT', 50);
 534      }
 535      $modinfo = get_fast_modinfo($course);
 536      $ids = array();
 537  
 538      foreach ($modinfo->cms as $cm) {
 539          if ($cm->modname != 'glossary') {
 540              continue;
 541          }
 542          if (!$cm->uservisible) {
 543              continue;
 544          }
 545          $ids[$cm->instance] = $cm->id;
 546      }
 547  
 548      if (!$ids) {
 549          return false;
 550      }
 551  
 552      // generate list of approval capabilities for all glossaries in the course.
 553      $approvals = array();
 554      foreach ($ids as $glinstanceid => $glcmid) {
 555          $context = context_module::instance($glcmid);
 556          if (has_capability('mod/glossary:view', $context)) {
 557              // get records glossary entries that are approved if user has no capability to approve entries.
 558              if (has_capability('mod/glossary:approve', $context)) {
 559                  $approvals[] = ' ge.glossaryid = :glsid'.$glinstanceid.' ';
 560              } else {
 561                  $approvals[] = ' (ge.approved = 1 AND ge.glossaryid = :glsid'.$glinstanceid.') ';
 562              }
 563              $params['glsid'.$glinstanceid] = $glinstanceid;
 564          }
 565      }
 566  
 567      if (count($approvals) == 0) {
 568          return false;
 569      }
 570      $userfieldsapi = \core_user\fields::for_userpic();
 571      $userfields = $userfieldsapi->get_sql('u', false, '', 'userid', false)->selects;
 572      $selectsql = 'SELECT ge.id, ge.concept, ge.approved, ge.timemodified, ge.glossaryid,
 573              ' . $userfields;
 574      $countsql = 'SELECT COUNT(*)';
 575  
 576      $joins = array(' FROM {glossary_entries} ge ');
 577      $joins[] = 'JOIN {user} u ON u.id = ge.userid ';
 578      $fromsql = implode("\n", $joins);
 579  
 580      $params['timestart'] = $timestart;
 581      $clausesql = ' WHERE ge.timemodified > :timestart ';
 582  
 583      if (count($approvals) > 0) {
 584          $approvalsql = 'AND ('. implode(' OR ', $approvals) .') ';
 585      } else {
 586          $approvalsql = '';
 587      }
 588      $ordersql = 'ORDER BY ge.timemodified ASC';
 589      $entries = $DB->get_records_sql($selectsql.$fromsql.$clausesql.$approvalsql.$ordersql, $params, 0, (GLOSSARY_RECENT_ACTIVITY_LIMIT+1));
 590  
 591      if (empty($entries)) {
 592          return false;
 593      }
 594  
 595      echo $OUTPUT->heading(get_string('newentries', 'glossary') . ':', 6);
 596      $strftimerecent = get_string('strftimerecent');
 597      $entrycount = 0;
 598      foreach ($entries as $entry) {
 599          if ($entrycount < GLOSSARY_RECENT_ACTIVITY_LIMIT) {
 600              if ($entry->approved) {
 601                  $dimmed = '';
 602                  $urlparams = array('g' => $entry->glossaryid, 'mode' => 'entry', 'hook' => $entry->id);
 603              } else {
 604                  $dimmed = ' dimmed_text';
 605                  $urlparams = array('id' => $ids[$entry->glossaryid], 'mode' => 'approval', 'hook' => format_text($entry->concept, true));
 606              }
 607              $link = new moodle_url($CFG->wwwroot.'/mod/glossary/view.php' , $urlparams);
 608              echo '<div class="head'.$dimmed.'">';
 609              echo '<div class="date">'.userdate($entry->timemodified, $strftimerecent).'</div>';
 610              echo '<div class="name">'.fullname($entry, $viewfullnames).'</div>';
 611              echo '</div>';
 612              echo '<div class="info"><a href="'.$link.'">'.format_string($entry->concept, true).'</a></div>';
 613              $entrycount += 1;
 614          } else {
 615              $numnewentries = $DB->count_records_sql($countsql.$joins[0].$clausesql.$approvalsql, $params);
 616              echo '<div class="head"><div class="activityhead">'.get_string('andmorenewentries', 'glossary', $numnewentries - GLOSSARY_RECENT_ACTIVITY_LIMIT).'</div></div>';
 617              break;
 618          }
 619      }
 620  
 621      return true;
 622  }
 623  
 624  /**
 625   * @global object
 626   * @param object $log
 627   */
 628  function glossary_log_info($log) {
 629      global $DB;
 630  
 631      return $DB->get_record_sql("SELECT e.*, u.firstname, u.lastname
 632                                    FROM {glossary_entries} e, {user} u
 633                                   WHERE e.id = ? AND u.id = ?", array($log->info, $log->userid));
 634  }
 635  
 636  /**
 637   * Function to be run periodically according to the moodle cron
 638   * This function searches for things that need to be done, such
 639   * as sending out mail, toggling flags etc ...
 640   * @return bool
 641   */
 642  function glossary_cron () {
 643      return true;
 644  }
 645  
 646  /**
 647   * Return grade for given user or all users.
 648   *
 649   * @param stdClass $glossary A glossary instance
 650   * @param int $userid Optional user id, 0 means all users
 651   * @return array An array of grades, false if none
 652   */
 653  function glossary_get_user_grades($glossary, $userid=0) {
 654      global $CFG;
 655  
 656      require_once($CFG->dirroot.'/rating/lib.php');
 657  
 658      $ratingoptions = new stdClass;
 659  
 660      //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
 661      $ratingoptions->modulename = 'glossary';
 662      $ratingoptions->moduleid   = $glossary->id;
 663      $ratingoptions->component  = 'mod_glossary';
 664      $ratingoptions->ratingarea = 'entry';
 665  
 666      $ratingoptions->userid = $userid;
 667      $ratingoptions->aggregationmethod = $glossary->assessed;
 668      $ratingoptions->scaleid = $glossary->scale;
 669      $ratingoptions->itemtable = 'glossary_entries';
 670      $ratingoptions->itemtableusercolumn = 'userid';
 671  
 672      $rm = new rating_manager();
 673      return $rm->get_user_grades($ratingoptions);
 674  }
 675  
 676  /**
 677   * Return rating related permissions
 678   *
 679   * @param int $contextid the context id
 680   * @param string $component The component we want to get permissions for
 681   * @param string $ratingarea The ratingarea that we want to get permissions for
 682   * @return array an associative array of the user's rating permissions
 683   */
 684  function glossary_rating_permissions($contextid, $component, $ratingarea) {
 685      if ($component != 'mod_glossary' || $ratingarea != 'entry') {
 686          // We don't know about this component/ratingarea so just return null to get the
 687          // default restrictive permissions.
 688          return null;
 689      }
 690      $context = context::instance_by_id($contextid);
 691      return array(
 692          'view'    => has_capability('mod/glossary:viewrating', $context),
 693          'viewany' => has_capability('mod/glossary:viewanyrating', $context),
 694          'viewall' => has_capability('mod/glossary:viewallratings', $context),
 695          'rate'    => has_capability('mod/glossary:rate', $context)
 696      );
 697  }
 698  
 699  /**
 700   * Validates a submitted rating
 701   * @param array $params submitted data
 702   *            context => object the context in which the rated items exists [required]
 703   *            component => The component for this module - should always be mod_forum [required]
 704   *            ratingarea => object the context in which the rated items exists [required]
 705   *            itemid => int the ID of the object being rated [required]
 706   *            scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
 707   *            rating => int the submitted rating
 708   *            rateduserid => int the id of the user whose items have been rated. NOT the user who submitted the ratings. 0 to update all. [required]
 709   *            aggregation => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [optional]
 710   * @return boolean true if the rating is valid. Will throw rating_exception if not
 711   */
 712  function glossary_rating_validate($params) {
 713      global $DB, $USER;
 714  
 715      // Check the component is mod_forum
 716      if ($params['component'] != 'mod_glossary') {
 717          throw new rating_exception('invalidcomponent');
 718      }
 719  
 720      // Check the ratingarea is post (the only rating area in forum)
 721      if ($params['ratingarea'] != 'entry') {
 722          throw new rating_exception('invalidratingarea');
 723      }
 724  
 725      // Check the rateduserid is not the current user .. you can't rate your own posts
 726      if ($params['rateduserid'] == $USER->id) {
 727          throw new rating_exception('nopermissiontorate');
 728      }
 729  
 730      $glossarysql = "SELECT g.id as glossaryid, g.scale, g.course, e.userid as userid, e.approved, e.timecreated, g.assesstimestart, g.assesstimefinish
 731                        FROM {glossary_entries} e
 732                        JOIN {glossary} g ON e.glossaryid = g.id
 733                       WHERE e.id = :itemid";
 734      $glossaryparams = array('itemid' => $params['itemid']);
 735      $info = $DB->get_record_sql($glossarysql, $glossaryparams);
 736      if (!$info) {
 737          //item doesn't exist
 738          throw new rating_exception('invaliditemid');
 739      }
 740  
 741      if ($info->scale != $params['scaleid']) {
 742          //the scale being submitted doesnt match the one in the database
 743          throw new rating_exception('invalidscaleid');
 744      }
 745  
 746      //check that the submitted rating is valid for the scale
 747  
 748      // lower limit
 749      if ($params['rating'] < 0  && $params['rating'] != RATING_UNSET_RATING) {
 750          throw new rating_exception('invalidnum');
 751      }
 752  
 753      // upper limit
 754      if ($info->scale < 0) {
 755          //its a custom scale
 756          $scalerecord = $DB->get_record('scale', array('id' => -$info->scale));
 757          if ($scalerecord) {
 758              $scalearray = explode(',', $scalerecord->scale);
 759              if ($params['rating'] > count($scalearray)) {
 760                  throw new rating_exception('invalidnum');
 761              }
 762          } else {
 763              throw new rating_exception('invalidscaleid');
 764          }
 765      } else if ($params['rating'] > $info->scale) {
 766          //if its numeric and submitted rating is above maximum
 767          throw new rating_exception('invalidnum');
 768      }
 769  
 770      //check the item we're rating was created in the assessable time window
 771      if (!empty($info->assesstimestart) && !empty($info->assesstimefinish)) {
 772          if ($info->timecreated < $info->assesstimestart || $info->timecreated > $info->assesstimefinish) {
 773              throw new rating_exception('notavailable');
 774          }
 775      }
 776  
 777      $cm = get_coursemodule_from_instance('glossary', $info->glossaryid, $info->course, false, MUST_EXIST);
 778      $context = context_module::instance($cm->id, MUST_EXIST);
 779  
 780      // if the supplied context doesnt match the item's context
 781      if ($context->id != $params['context']->id) {
 782          throw new rating_exception('invalidcontext');
 783      }
 784  
 785      return true;
 786  }
 787  
 788  /**
 789   * Update activity grades
 790   *
 791   * @category grade
 792   * @param stdClass $glossary Null means all glossaries (with extra cmidnumber property)
 793   * @param int $userid specific user only, 0 means all
 794   * @param bool $nullifnone If true and the user has no grade then a grade item with rawgrade == null will be inserted
 795   */
 796  function glossary_update_grades($glossary=null, $userid=0, $nullifnone=true) {
 797      global $CFG, $DB;
 798      require_once($CFG->libdir.'/gradelib.php');
 799  
 800      if (!$glossary->assessed) {
 801          glossary_grade_item_update($glossary);
 802  
 803      } else if ($grades = glossary_get_user_grades($glossary, $userid)) {
 804          glossary_grade_item_update($glossary, $grades);
 805  
 806      } else if ($userid and $nullifnone) {
 807          $grade = new stdClass();
 808          $grade->userid   = $userid;
 809          $grade->rawgrade = NULL;
 810          glossary_grade_item_update($glossary, $grade);
 811  
 812      } else {
 813          glossary_grade_item_update($glossary);
 814      }
 815  }
 816  
 817  /**
 818   * Create/update grade item for given glossary
 819   *
 820   * @category grade
 821   * @param stdClass $glossary object with extra cmidnumber
 822   * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
 823   * @return int, 0 if ok, error code otherwise
 824   */
 825  function glossary_grade_item_update($glossary, $grades=NULL) {
 826      global $CFG;
 827      require_once($CFG->libdir.'/gradelib.php');
 828  
 829      $params = array('itemname'=>$glossary->name, 'idnumber'=>$glossary->cmidnumber);
 830  
 831      if (!$glossary->assessed or $glossary->scale == 0) {
 832          $params['gradetype'] = GRADE_TYPE_NONE;
 833  
 834      } else if ($glossary->scale > 0) {
 835          $params['gradetype'] = GRADE_TYPE_VALUE;
 836          $params['grademax']  = $glossary->scale;
 837          $params['grademin']  = 0;
 838  
 839      } else if ($glossary->scale < 0) {
 840          $params['gradetype'] = GRADE_TYPE_SCALE;
 841          $params['scaleid']   = -$glossary->scale;
 842      }
 843  
 844      if ($grades  === 'reset') {
 845          $params['reset'] = true;
 846          $grades = NULL;
 847      }
 848  
 849      return grade_update('mod/glossary', $glossary->course, 'mod', 'glossary', $glossary->id, 0, $grades, $params);
 850  }
 851  
 852  /**
 853   * Delete grade item for given glossary
 854   *
 855   * @category grade
 856   * @param object $glossary object
 857   */
 858  function glossary_grade_item_delete($glossary) {
 859      global $CFG;
 860      require_once($CFG->libdir.'/gradelib.php');
 861  
 862      return grade_update('mod/glossary', $glossary->course, 'mod', 'glossary', $glossary->id, 0, NULL, array('deleted'=>1));
 863  }
 864  
 865  /**
 866   * @deprecated since Moodle 3.8
 867   */
 868  function glossary_scale_used() {
 869      throw new coding_exception('glossary_scale_used() can not be used anymore. Plugins can implement ' .
 870          '<modname>_scale_used_anywhere, all implementations of <modname>_scale_used are now ignored');
 871  }
 872  
 873  /**
 874   * Checks if scale is being used by any instance of glossary
 875   *
 876   * This is used to find out if scale used anywhere
 877   *
 878   * @global object
 879   * @param int $scaleid
 880   * @return boolean True if the scale is used by any glossary
 881   */
 882  function glossary_scale_used_anywhere($scaleid) {
 883      global $DB;
 884  
 885      if ($scaleid and $DB->record_exists_select('glossary', "scale = ? and assessed > 0", [-$scaleid])) {
 886          return true;
 887      } else {
 888          return false;
 889      }
 890  }
 891  
 892  //////////////////////////////////////////////////////////////////////////////////////
 893  /// Any other glossary functions go here.  Each of them must have a name that
 894  /// starts with glossary_
 895  
 896  /**
 897   * This function return an array of valid glossary_formats records
 898   * Everytime it's called, every existing format is checked, new formats
 899   * are included if detected and old formats are deleted and any glossary
 900   * using an invalid format is updated to the default (dictionary).
 901   *
 902   * @global object
 903   * @global object
 904   * @return array
 905   */
 906  function glossary_get_available_formats() {
 907      global $CFG, $DB;
 908  
 909      // Get available formats (plugin) and insert them (if necessary) into glossary_formats.
 910      $formats = get_list_of_plugins('mod/glossary/formats', 'TEMPLATE');
 911      $pluginformats = array();
 912      $formatrecords = $DB->get_records("glossary_formats");
 913  
 914      foreach ($formats as $format) {
 915          // If the format file exists.
 916          if (file_exists($CFG->dirroot.'/mod/glossary/formats/'.$format.'/'.$format.'_format.php')) {
 917              include_once($CFG->dirroot.'/mod/glossary/formats/'.$format.'/'.$format.'_format.php');
 918              //If the function exists
 919              if (function_exists('glossary_show_entry_'.$format)) {
 920                  // Acummulate it as a valid format.
 921                  $pluginformats[] = $format;
 922  
 923                  // Check if the format exists in the table.
 924                  $rec = null;
 925                  foreach ($formatrecords as $record) {
 926                      if ($record->name == $format) {
 927                          $rec = $record;
 928                          break;
 929                      }
 930                  }
 931  
 932                  if (!$rec) {
 933                      // Insert the record in glossary_formats.
 934                      $gf = new stdClass();
 935                      $gf->name = $format;
 936                      $gf->popupformatname = $format;
 937                      $gf->visible = 1;
 938                      $id = $DB->insert_record('glossary_formats', $gf);
 939                      $rec = $DB->get_record('glossary_formats', array('id' => $id));
 940                      array_push($formatrecords, $rec);
 941                  }
 942  
 943                  if (empty($rec->showtabs)) {
 944                      glossary_set_default_visible_tabs($rec);
 945                  }
 946              }
 947          }
 948      }
 949  
 950      // Delete non_existent formats from glossary_formats table.
 951      foreach ($formatrecords as $record) {
 952          $todelete = false;
 953          // If the format in DB isn't a valid previously detected format then delete the record.
 954          if (!in_array($record->name, $pluginformats)) {
 955              $todelete = true;
 956          }
 957  
 958          if ($todelete) {
 959              // Delete the format.
 960              $DB->delete_records('glossary_formats', array('id' => $record->id));
 961              unset($formatrecords[$record->id]);
 962  
 963              // Reassign existing glossaries to default (dictionary) format.
 964              if ($glossaries = $DB->get_records('glossary', array('displayformat' => $record->name))) {
 965                  foreach($glossaries as $glossary) {
 966                      $DB->set_field('glossary', 'displayformat', 'dictionary', array('id' => $glossary->id));
 967                  }
 968              }
 969          }
 970      }
 971  
 972      return $formatrecords;
 973  }
 974  
 975  /**
 976   * @param bool $debug
 977   * @param string $text
 978   * @param int $br
 979   */
 980  function glossary_debug($debug,$text,$br=1) {
 981      if ( $debug ) {
 982          echo '<font color="red">' . $text . '</font>';
 983          if ( $br ) {
 984              echo '<br />';
 985          }
 986      }
 987  }
 988  
 989  /**
 990   *
 991   * @global object
 992   * @param int $glossaryid
 993   * @param string $entrylist
 994   * @param string $pivot
 995   * @return array
 996   */
 997  function glossary_get_entries($glossaryid, $entrylist, $pivot = "") {
 998      global $DB;
 999      if ($pivot) {
1000         $pivot .= ",";
1001      }
1002  
1003      return $DB->get_records_sql("SELECT $pivot id,userid,concept,definition,format
1004                                     FROM {glossary_entries}
1005                                    WHERE glossaryid = ?
1006                                          AND id IN ($entrylist)", array($glossaryid));
1007  }
1008  
1009  /**
1010   * @global object
1011   * @global object
1012   * @param object $concept
1013   * @param string $courseid
1014   * @return array
1015   */
1016  function glossary_get_entries_search($concept, $courseid) {
1017      global $DB;
1018  
1019      //Check if the user is an admin
1020      $bypassadmin = 1; //This means NO (by default)
1021      if (has_capability('moodle/course:viewhiddenactivities', context_system::instance())) {
1022          $bypassadmin = 0; //This means YES
1023      }
1024  
1025      //Check if the user is a teacher
1026      $bypassteacher = 1; //This means NO (by default)
1027      if (has_capability('mod/glossary:manageentries', context_course::instance($courseid))) {
1028          $bypassteacher = 0; //This means YES
1029      }
1030  
1031      $conceptlower = core_text::strtolower(trim($concept));
1032  
1033      $params = array('courseid1'=>$courseid, 'courseid2'=>$courseid, 'conceptlower'=>$conceptlower, 'concept'=>$concept);
1034      $sensitiveconceptsql = $DB->sql_equal('concept', ':concept');
1035  
1036      return $DB->get_records_sql("SELECT e.*, g.name as glossaryname, cm.id as cmid, cm.course as courseid
1037                                     FROM {glossary_entries} e, {glossary} g,
1038                                          {course_modules} cm, {modules} m
1039                                    WHERE m.name = 'glossary' AND
1040                                          cm.module = m.id AND
1041                                          (cm.visible = 1 OR  cm.visible = $bypassadmin OR
1042                                              (cm.course = :courseid1 AND cm.visible = $bypassteacher)) AND
1043                                          g.id = cm.instance AND
1044                                          e.glossaryid = g.id  AND
1045                                          ( (e.casesensitive != 1 AND LOWER(concept) = :conceptlower) OR
1046                                            (e.casesensitive = 1 and $sensitiveconceptsql)) AND
1047                                          (g.course = :courseid2 OR g.globalglossary = 1) AND
1048                                           e.usedynalink != 0 AND
1049                                           g.usedynalink != 0", $params);
1050  }
1051  
1052  /**
1053   * @global object
1054   * @global object
1055   * @param object $course
1056   * @param object $course
1057   * @param object $glossary
1058   * @param object $entry
1059   * @param string $mode
1060   * @param string $hook
1061   * @param int $printicons
1062   * @param int $displayformat
1063   * @param bool $printview
1064   * @return mixed
1065   */
1066  function glossary_print_entry($course, $cm, $glossary, $entry, $mode='',$hook='',$printicons = 1, $displayformat  = -1, $printview = false) {
1067      global $USER, $CFG;
1068      $return = false;
1069      if ( $displayformat < 0 ) {
1070          $displayformat = $glossary->displayformat;
1071      }
1072      if ($entry->approved or ($USER->id == $entry->userid) or ($mode == 'approval' and !$entry->approved) ) {
1073          $formatfile = $CFG->dirroot.'/mod/glossary/formats/'.$displayformat.'/'.$displayformat.'_format.php';
1074          if ($printview) {
1075              $functionname = 'glossary_print_entry_'.$displayformat;
1076          } else {
1077              $functionname = 'glossary_show_entry_'.$displayformat;
1078          }
1079  
1080          if (file_exists($formatfile)) {
1081              include_once($formatfile);
1082              if (function_exists($functionname)) {
1083                  $return = $functionname($course, $cm, $glossary, $entry,$mode,$hook,$printicons);
1084              } else if ($printview) {
1085                  //If the glossary_print_entry_XXXX function doesn't exist, print default (old) print format
1086                  $return = glossary_print_entry_default($entry, $glossary, $cm);
1087              }
1088          }
1089      }
1090      return $return;
1091  }
1092  
1093  /**
1094   * Default (old) print format used if custom function doesn't exist in format
1095   *
1096   * @param object $entry
1097   * @param object $glossary
1098   * @param object $cm
1099   * @return void Output is echo'd
1100   */
1101  function glossary_print_entry_default ($entry, $glossary, $cm) {
1102      global $CFG;
1103  
1104      require_once($CFG->libdir . '/filelib.php');
1105  
1106      echo $OUTPUT->heading(strip_tags($entry->concept), 4);
1107  
1108      $definition = $entry->definition;
1109  
1110      $definition = '<span class="nolink">' . strip_tags($definition) . '</span>';
1111  
1112      $context = context_module::instance($cm->id);
1113      $definition = file_rewrite_pluginfile_urls($definition, 'pluginfile.php', $context->id, 'mod_glossary', 'entry', $entry->id);
1114  
1115      $options = new stdClass();
1116      $options->para = false;
1117      $options->trusted = $entry->definitiontrust;
1118      $options->context = $context;
1119      $options->overflowdiv = true;
1120      $definition = format_text($definition, $entry->definitionformat, $options);
1121      echo ($definition);
1122      echo '<br /><br />';
1123  }
1124  
1125  /**
1126   * Print glossary concept/term as a heading &lt;h4>
1127   * @param object $entry
1128   */
1129  function  glossary_print_entry_concept($entry, $return=false) {
1130      global $OUTPUT;
1131  
1132      $text = $OUTPUT->heading(format_string($entry->concept), 4);
1133      if (!empty($entry->highlight)) {
1134          $text = highlight($entry->highlight, $text);
1135      }
1136  
1137      if ($return) {
1138          return $text;
1139      } else {
1140          echo $text;
1141      }
1142  }
1143  
1144  /**
1145   *
1146   * @global moodle_database DB
1147   * @param object $entry
1148   * @param object $glossary
1149   * @param object $cm
1150   */
1151  function glossary_print_entry_definition($entry, $glossary, $cm) {
1152      global $GLOSSARY_EXCLUDEENTRY;
1153  
1154      $definition = $entry->definition;
1155  
1156      // Do not link self.
1157      $GLOSSARY_EXCLUDEENTRY = $entry->id;
1158  
1159      $context = context_module::instance($cm->id);
1160      $definition = file_rewrite_pluginfile_urls($definition, 'pluginfile.php', $context->id, 'mod_glossary', 'entry', $entry->id);
1161  
1162      $options = new stdClass();
1163      $options->para = false;
1164      $options->trusted = $entry->definitiontrust;
1165      $options->context = $context;
1166      $options->overflowdiv = true;
1167  
1168      $text = format_text($definition, $entry->definitionformat, $options);
1169  
1170      // Stop excluding concepts from autolinking
1171      unset($GLOSSARY_EXCLUDEENTRY);
1172  
1173      if (!empty($entry->highlight)) {
1174          $text = highlight($entry->highlight, $text);
1175      }
1176      if (isset($entry->footer)) {   // Unparsed footer info
1177          $text .= $entry->footer;
1178      }
1179      echo $text;
1180  }
1181  
1182  /**
1183   *
1184   * @global object
1185   * @param object $course
1186   * @param object $cm
1187   * @param object $glossary
1188   * @param object $entry
1189   * @param string $mode
1190   * @param string $hook
1191   * @param string $type
1192   * @return string|void
1193   */
1194  function  glossary_print_entry_aliases($course, $cm, $glossary, $entry,$mode='',$hook='', $type = 'print') {
1195      global $DB;
1196  
1197      $return = '';
1198      if ($aliases = $DB->get_fieldset_select('glossary_alias', 'alias', 'entryid = :entryid', ['entryid' => $entry->id])) {
1199          $id = "keyword-{$entry->id}";
1200          $return = html_writer::select($aliases, $id, '', false, ['id' => $id]);
1201      }
1202      if ($type == 'print') {
1203          echo $return;
1204      } else {
1205          return $return;
1206      }
1207  }
1208  
1209  /**
1210   *
1211   * @global object
1212   * @global object
1213   * @global object
1214   * @param object $course
1215   * @param object $cm
1216   * @param object $glossary
1217   * @param object $entry
1218   * @param string $mode
1219   * @param string $hook
1220   * @param string $type
1221   * @return string|void
1222   */
1223  function glossary_print_entry_icons($course, $cm, $glossary, $entry, $mode='',$hook='', $type = 'print') {
1224      global $USER, $CFG, $DB, $OUTPUT;
1225  
1226      $context = context_module::instance($cm->id);
1227  
1228      $output = false;   // To decide if we must really return text in "return". Activate when needed only!
1229      $importedentry = ($entry->sourceglossaryid == $glossary->id);
1230      $ismainglossary = $glossary->mainglossary;
1231  
1232      $return = '<span class="commands">';
1233      // Differentiate links for each entry.
1234      $altsuffix = strip_tags(format_text($entry->concept));
1235  
1236      if (!$entry->approved) {
1237          $output = true;
1238          $return .= html_writer::tag('span', get_string('entryishidden','glossary'),
1239              array('class' => 'glossary-hidden-note'));
1240      }
1241  
1242      if ($entry->approved || has_capability('mod/glossary:approve', $context)) {
1243          $output = true;
1244          $return .= \html_writer::link(
1245              new \moodle_url('/mod/glossary/showentry.php', ['eid' => $entry->id]),
1246              $OUTPUT->pix_icon('fp/link', get_string('entrylink', 'glossary', $altsuffix), 'theme'),
1247              ['title' => get_string('entrylink', 'glossary', $altsuffix), 'class' => 'icon']
1248          );
1249      }
1250  
1251      if (has_capability('mod/glossary:approve', $context) && !$glossary->defaultapproval && $entry->approved) {
1252          $output = true;
1253          $return .= '<a class="icon" title="' . get_string('disapprove', 'glossary').
1254                     '" href="approve.php?newstate=0&amp;eid='.$entry->id.'&amp;mode='.$mode.
1255                     '&amp;hook='.urlencode($hook).'&amp;sesskey='.sesskey().
1256                     '">' . $OUTPUT->pix_icon('t/block', get_string('disapprove', 'glossary')) . '</a>';
1257      }
1258  
1259      $iscurrentuser = ($entry->userid == $USER->id);
1260  
1261      if (has_capability('mod/glossary:manageentries', $context) or (isloggedin() and has_capability('mod/glossary:write', $context) and $iscurrentuser)) {
1262          // only teachers can export entries so check it out
1263          if (has_capability('mod/glossary:export', $context) and !$ismainglossary and !$importedentry) {
1264              $mainglossary = $DB->get_record('glossary', array('mainglossary'=>1,'course'=>$course->id));
1265              if ( $mainglossary ) {  // if there is a main glossary defined, allow to export the current entry
1266                  $output = true;
1267                  $return .= '<a class="icon" title="'.get_string('exporttomainglossary','glossary') . '" ' .
1268                      'href="exportentry.php?id='.$entry->id.'&amp;prevmode='.$mode.'&amp;hook='.urlencode($hook).'">' .
1269                      $OUTPUT->pix_icon('export', get_string('exporttomainglossary', 'glossary'), 'glossary') . '</a>';
1270              }
1271          }
1272  
1273          $icon = 't/delete';
1274          $iconcomponent = 'moodle';
1275          if ( $entry->sourceglossaryid ) {
1276              $icon = 'minus';   // graphical metaphor (minus) for deleting an imported entry
1277              $iconcomponent = 'glossary';
1278          }
1279  
1280          //Decide if an entry is editable:
1281          // -It isn't a imported entry (so nobody can edit a imported (from secondary to main) entry)) and
1282          // -The user is teacher or he is a student with time permissions (edit period or editalways defined).
1283          $ineditperiod = ((time() - $entry->timecreated <  $CFG->maxeditingtime) || $glossary->editalways);
1284          if ( !$importedentry and (has_capability('mod/glossary:manageentries', $context) or ($entry->userid == $USER->id and ($ineditperiod and has_capability('mod/glossary:write', $context))))) {
1285              $output = true;
1286              $url = "deleteentry.php?id=$cm->id&amp;mode=delete&amp;entry=$entry->id&amp;prevmode=$mode&amp;hook=".urlencode($hook);
1287              $return .= "<a class='icon' title=\"" . get_string("delete") . "\" " .
1288                         "href=\"$url\">" . $OUTPUT->pix_icon($icon, get_string('deleteentrya', 'mod_glossary', $altsuffix), $iconcomponent) . '</a>';
1289  
1290              $url = "edit.php?cmid=$cm->id&amp;id=$entry->id&amp;mode=$mode&amp;hook=".urlencode($hook);
1291              $return .= "<a class='icon' title=\"" . get_string("edit") . "\" href=\"$url\">" .
1292                         $OUTPUT->pix_icon('t/edit', get_string('editentrya', 'mod_glossary', $altsuffix)) . '</a>';
1293          } elseif ( $importedentry ) {
1294              $return .= "<font size=\"-1\">" . get_string("exportedentry","glossary") . "</font>";
1295          }
1296      }
1297      if (!empty($CFG->enableportfolios) && (has_capability('mod/glossary:exportentry', $context) || ($iscurrentuser && has_capability('mod/glossary:exportownentry', $context)))) {
1298          require_once($CFG->libdir . '/portfoliolib.php');
1299          $button = new portfolio_add_button();
1300          $button->set_callback_options('glossary_entry_portfolio_caller',  array('id' => $cm->id, 'entryid' => $entry->id), 'mod_glossary');
1301  
1302          $filecontext = $context;
1303          if ($entry->sourceglossaryid == $cm->instance) {
1304              if ($maincm = get_coursemodule_from_instance('glossary', $entry->glossaryid)) {
1305                  $filecontext = context_module::instance($maincm->id);
1306              }
1307          }
1308          $fs = get_file_storage();
1309          if ($files = $fs->get_area_files($filecontext->id, 'mod_glossary', 'attachment', $entry->id, "timemodified", false)
1310           || $files = $fs->get_area_files($filecontext->id, 'mod_glossary', 'entry', $entry->id, "timemodified", false)) {
1311  
1312              $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
1313          } else {
1314              $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
1315          }
1316  
1317          $return .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
1318      }
1319      $return .= '</span>';
1320  
1321      if (!empty($CFG->usecomments) && has_capability('mod/glossary:comment', $context) and $glossary->allowcomments) {
1322          require_once($CFG->dirroot . '/comment/lib.php');
1323          $cmt = new stdClass();
1324          $cmt->component = 'mod_glossary';
1325          $cmt->context  = $context;
1326          $cmt->course   = $course;
1327          $cmt->cm       = $cm;
1328          $cmt->area     = 'glossary_entry';
1329          $cmt->itemid   = $entry->id;
1330          $cmt->showcount = true;
1331          $comment = new comment($cmt);
1332          $return .= '<div>'.$comment->output(true).'</div>';
1333          $output = true;
1334      }
1335  
1336      //If we haven't calculated any REAL thing, delete result ($return)
1337      if (!$output) {
1338          $return = '';
1339      }
1340      //Print or get
1341      if ($type == 'print') {
1342          echo $return;
1343      } else {
1344          return $return;
1345      }
1346  }
1347  
1348  /**
1349   * @param object $course
1350   * @param object $cm
1351   * @param object $glossary
1352   * @param object $entry
1353   * @param string $mode
1354   * @param object $hook
1355   * @param bool $printicons
1356   * @param bool $aliases
1357   * @param bool $printseparator Whether to print a thematic break (separator) at the end of the lower section.
1358   * @return void
1359   */
1360  function glossary_print_entry_lower_section($course, $cm, $glossary, $entry, $mode, $hook, $printicons, $aliases = true,
1361          $printseparator = true) {
1362      if ($aliases) {
1363          $aliases = glossary_print_entry_aliases($course, $cm, $glossary, $entry, $mode, $hook,'html');
1364      }
1365      $icons   = '';
1366      if ($printicons) {
1367          $icons   = glossary_print_entry_icons($course, $cm, $glossary, $entry, $mode, $hook,'html');
1368      }
1369      if ($aliases || $icons || !empty($entry->rating)) {
1370          echo '<table>';
1371          if ( $aliases ) {
1372              $id = "keyword-{$entry->id}";
1373              echo '<tr valign="top"><td class="aliases">' .
1374                  '<label for="' . $id . '">' . get_string('aliases', 'glossary') . ': </label>' .
1375                  $aliases . '</td></tr>';
1376          }
1377          if ($icons) {
1378              echo '<tr valign="top"><td class="icons">'.$icons.'</td></tr>';
1379          }
1380          if (!empty($entry->rating)) {
1381              echo '<tr valign="top"><td class="ratings pt-3">';
1382              glossary_print_entry_ratings($course, $entry);
1383              echo '</td></tr>';
1384          }
1385          echo '</table>';
1386  
1387          if ($printseparator) {
1388              echo "<hr>\n";
1389          }
1390      }
1391  }
1392  
1393  /**
1394   * Print the list of attachments for this glossary entry
1395   *
1396   * @param object $entry
1397   * @param object $cm The coursemodule
1398   * @param string $format The format for this view (html, or text)
1399   * @param string $unused1 This parameter is no longer used
1400   * @param string $unused2 This parameter is no longer used
1401   */
1402  function glossary_print_entry_attachment($entry, $cm, $format = null, $unused1 = null, $unused2 = null) {
1403      // Valid format values: html: The HTML link for the attachment is an icon; and
1404      //                      text: The HTML link for the attachment is text.
1405      if ($entry->attachment) {
1406          echo '<div class="attachments">';
1407          echo glossary_print_attachments($entry, $cm, $format);
1408          echo '</div>';
1409      }
1410      if ($unused1) {
1411          debugging('The align parameter is deprecated, please use appropriate CSS instead', DEBUG_DEVELOPER);
1412      }
1413      if ($unused2 !== null) {
1414          debugging('The insidetable parameter is deprecated, please use appropriate CSS instead', DEBUG_DEVELOPER);
1415      }
1416  }
1417  
1418  /**
1419   * @global object
1420   * @param object $cm
1421   * @param object $entry
1422   * @param string $mode
1423   * @param string $align
1424   * @param bool $insidetable
1425   */
1426  function  glossary_print_entry_approval($cm, $entry, $mode, $align="right", $insidetable=true) {
1427      global $CFG, $OUTPUT;
1428  
1429      if ($mode == 'approval' and !$entry->approved) {
1430          if ($insidetable) {
1431              echo '<table class="glossaryapproval" align="'.$align.'"><tr><td align="'.$align.'">';
1432          }
1433          echo $OUTPUT->action_icon(
1434              new moodle_url('approve.php', array('eid' => $entry->id, 'mode' => $mode, 'sesskey' => sesskey())),
1435              new pix_icon('t/approve', get_string('approve','glossary'), '',
1436                  array('class' => 'iconsmall', 'align' => $align))
1437          );
1438          if ($insidetable) {
1439              echo '</td></tr></table>';
1440          }
1441      }
1442  }
1443  
1444  /**
1445   * It returns all entries from all glossaries that matches the specified criteria
1446   *  within a given $course. It performs an $extended search if necessary.
1447   * It restrict the search to only one $glossary if the $glossary parameter is set.
1448   *
1449   * @global object
1450   * @global object
1451   * @param object $course
1452   * @param array $searchterms
1453   * @param int $extended
1454   * @param object $glossary
1455   * @return array
1456   */
1457  function glossary_search($course, $searchterms, $extended = 0, $glossary = NULL) {
1458      global $CFG, $DB;
1459  
1460      if ( !$glossary ) {
1461          if ( $glossaries = $DB->get_records("glossary", array("course"=>$course->id)) ) {
1462              $glos = "";
1463              foreach ( $glossaries as $glossary ) {
1464                  $glos .= "$glossary->id,";
1465              }
1466              $glos = substr($glos,0,-1);
1467          }
1468      } else {
1469          $glos = $glossary->id;
1470      }
1471  
1472      if (!has_capability('mod/glossary:manageentries', context_course::instance($glossary->course))) {
1473          $glossarymodule = $DB->get_record("modules", array("name"=>"glossary"));
1474          $onlyvisible = " AND g.id = cm.instance AND cm.visible = 1 AND cm.module = $glossarymodule->id";
1475          $onlyvisibletable = ", {course_modules} cm";
1476      } else {
1477  
1478          $onlyvisible = "";
1479          $onlyvisibletable = "";
1480      }
1481  
1482      if ($DB->sql_regex_supported()) {
1483          $REGEXP    = $DB->sql_regex(true);
1484          $NOTREGEXP = $DB->sql_regex(false);
1485      }
1486  
1487      $searchcond = array();
1488      $params     = array();
1489      $i = 0;
1490  
1491      $concat = $DB->sql_concat('e.concept', "' '", 'e.definition');
1492  
1493  
1494      foreach ($searchterms as $searchterm) {
1495          $i++;
1496  
1497          $NOT = false; /// Initially we aren't going to perform NOT LIKE searches, only MSSQL and Oracle
1498                     /// will use it to simulate the "-" operator with LIKE clause
1499  
1500      /// Under Oracle and MSSQL, trim the + and - operators and perform
1501      /// simpler LIKE (or NOT LIKE) queries
1502          if (!$DB->sql_regex_supported()) {
1503              if (substr($searchterm, 0, 1) == '-') {
1504                  $NOT = true;
1505              }
1506              $searchterm = trim($searchterm, '+-');
1507          }
1508  
1509          // TODO: +- may not work for non latin languages
1510  
1511          if (substr($searchterm,0,1) == '+') {
1512              $searchterm = trim($searchterm, '+-');
1513              $searchterm = preg_quote($searchterm, '|');
1514              $searchcond[] = "$concat $REGEXP :ss$i";
1515              $params['ss'.$i] = "(^|[^a-zA-Z0-9])$searchterm([^a-zA-Z0-9]|$)";
1516  
1517          } else if (substr($searchterm,0,1) == "-") {
1518              $searchterm = trim($searchterm, '+-');
1519              $searchterm = preg_quote($searchterm, '|');
1520              $searchcond[] = "$concat $NOTREGEXP :ss$i";
1521              $params['ss'.$i] = "(^|[^a-zA-Z0-9])$searchterm([^a-zA-Z0-9]|$)";
1522  
1523          } else {
1524              $searchcond[] = $DB->sql_like($concat, ":ss$i", false, true, $NOT);
1525              $params['ss'.$i] = "%$searchterm%";
1526          }
1527      }
1528  
1529      if (empty($searchcond)) {
1530          $totalcount = 0;
1531          return array();
1532      }
1533  
1534      $searchcond = implode(" AND ", $searchcond);
1535  
1536      $sql = "SELECT e.*
1537                FROM {glossary_entries} e, {glossary} g $onlyvisibletable
1538               WHERE $searchcond
1539                 AND (e.glossaryid = g.id or e.sourceglossaryid = g.id) $onlyvisible
1540                 AND g.id IN ($glos) AND e.approved <> 0";
1541  
1542      return $DB->get_records_sql($sql, $params);
1543  }
1544  
1545  /**
1546   * @global object
1547   * @param array $searchterms
1548   * @param object $glossary
1549   * @param bool $extended
1550   * @return array
1551   */
1552  function glossary_search_entries($searchterms, $glossary, $extended) {
1553      global $DB;
1554  
1555      $course = $DB->get_record("course", array("id"=>$glossary->course));
1556      return glossary_search($course,$searchterms,$extended,$glossary);
1557  }
1558  
1559  /**
1560   * if return=html, then return a html string.
1561   * if return=text, then return a text-only string.
1562   * otherwise, print HTML for non-images, and return image HTML
1563   *     if attachment is an image, $align set its aligment.
1564   *
1565   * @global object
1566   * @global object
1567   * @param object $entry
1568   * @param object $cm
1569   * @param string $type html, txt, empty
1570   * @param string $unused This parameter is no longer used
1571   * @return string image string or nothing depending on $type param
1572   */
1573  function glossary_print_attachments($entry, $cm, $type=NULL, $unused = null) {
1574      global $CFG, $DB, $OUTPUT;
1575  
1576      if (!$context = context_module::instance($cm->id, IGNORE_MISSING)) {
1577          return '';
1578      }
1579  
1580      if ($entry->sourceglossaryid == $cm->instance) {
1581          if (!$maincm = get_coursemodule_from_instance('glossary', $entry->glossaryid)) {
1582              return '';
1583          }
1584          $filecontext = context_module::instance($maincm->id);
1585  
1586      } else {
1587          $filecontext = $context;
1588      }
1589  
1590      $strattachment = get_string('attachment', 'glossary');
1591  
1592      $fs = get_file_storage();
1593  
1594      $imagereturn = '';
1595      $output = '';
1596  
1597      if ($files = $fs->get_area_files($filecontext->id, 'mod_glossary', 'attachment', $entry->id, "timemodified", false)) {
1598          foreach ($files as $file) {
1599              $filename = $file->get_filename();
1600              $mimetype = $file->get_mimetype();
1601              $iconimage = $OUTPUT->pix_icon(file_file_icon($file), get_mimetype_description($file), 'moodle', array('class' => 'icon'));
1602              $path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$context->id.'/mod_glossary/attachment/'.$entry->id.'/'.$filename);
1603  
1604              if ($type == 'html') {
1605                  $output .= "<a href=\"$path\">$iconimage</a> ";
1606                  $output .= "<a href=\"$path\">".s($filename)."</a>";
1607                  $output .= "<br />";
1608  
1609              } else if ($type == 'text') {
1610                  $output .= "$strattachment ".s($filename).":\n$path\n";
1611  
1612              } else {
1613                  if (in_array($mimetype, array('image/gif', 'image/jpeg', 'image/png'))) {
1614                      // Image attachments don't get printed as links
1615                      $imagereturn .= "<br /><img src=\"$path\" alt=\"\" />";
1616                  } else {
1617                      $output .= "<a href=\"$path\">$iconimage</a> ";
1618                      $output .= format_text("<a href=\"$path\">".s($filename)."</a>", FORMAT_HTML, array('context'=>$context));
1619                      $output .= '<br />';
1620                  }
1621              }
1622          }
1623      }
1624  
1625      if ($type) {
1626          return $output;
1627      } else {
1628          echo $output;
1629          return $imagereturn;
1630      }
1631  }
1632  
1633  ////////////////////////////////////////////////////////////////////////////////
1634  // File API                                                                   //
1635  ////////////////////////////////////////////////////////////////////////////////
1636  
1637  /**
1638   * Lists all browsable file areas
1639   *
1640   * @package  mod_glossary
1641   * @category files
1642   * @param stdClass $course course object
1643   * @param stdClass $cm course module object
1644   * @param stdClass $context context object
1645   * @return array
1646   */
1647  function glossary_get_file_areas($course, $cm, $context) {
1648      return array(
1649          'attachment' => get_string('areaattachment', 'mod_glossary'),
1650          'entry' => get_string('areaentry', 'mod_glossary'),
1651      );
1652  }
1653  
1654  /**
1655   * File browsing support for glossary module.
1656   *
1657   * @param file_browser $browser
1658   * @param array $areas
1659   * @param stdClass $course
1660   * @param cm_info $cm
1661   * @param context $context
1662   * @param string $filearea
1663   * @param int $itemid
1664   * @param string $filepath
1665   * @param string $filename
1666   * @return file_info_stored file_info_stored instance or null if not found
1667   */
1668  function glossary_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
1669      global $CFG, $DB, $USER;
1670  
1671      if ($context->contextlevel != CONTEXT_MODULE) {
1672          return null;
1673      }
1674  
1675      if (!isset($areas[$filearea])) {
1676          return null;
1677      }
1678  
1679      if (is_null($itemid)) {
1680          require_once($CFG->dirroot.'/mod/glossary/locallib.php');
1681          return new glossary_file_info_container($browser, $course, $cm, $context, $areas, $filearea);
1682      }
1683  
1684      if (!$entry = $DB->get_record('glossary_entries', array('id' => $itemid))) {
1685          return null;
1686      }
1687  
1688      if (!$glossary = $DB->get_record('glossary', array('id' => $cm->instance))) {
1689          return null;
1690      }
1691  
1692      if ($glossary->defaultapproval and !$entry->approved and !has_capability('mod/glossary:approve', $context)) {
1693          return null;
1694      }
1695  
1696      // this trickery here is because we need to support source glossary access
1697      if ($entry->glossaryid == $cm->instance) {
1698          $filecontext = $context;
1699      } else if ($entry->sourceglossaryid == $cm->instance) {
1700          if (!$maincm = get_coursemodule_from_instance('glossary', $entry->glossaryid)) {
1701              return null;
1702          }
1703          $filecontext = context_module::instance($maincm->id);
1704      } else {
1705          return null;
1706      }
1707  
1708      $fs = get_file_storage();
1709      $filepath = is_null($filepath) ? '/' : $filepath;
1710      $filename = is_null($filename) ? '.' : $filename;
1711      if (!($storedfile = $fs->get_file($filecontext->id, 'mod_glossary', $filearea, $itemid, $filepath, $filename))) {
1712          return null;
1713      }
1714  
1715      // Checks to see if the user can manage files or is the owner.
1716      // TODO MDL-33805 - Do not use userid here and move the capability check above.
1717      if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
1718          return null;
1719      }
1720  
1721      $urlbase = $CFG->wwwroot.'/pluginfile.php';
1722  
1723      return new file_info_stored($browser, $filecontext, $storedfile, $urlbase, s($entry->concept), true, true, false, false);
1724  }
1725  
1726  /**
1727   * Serves the glossary attachments. Implements needed access control ;-)
1728   *
1729   * @package  mod_glossary
1730   * @category files
1731   * @param stdClass $course course object
1732   * @param stdClass $cm course module object
1733   * @param stdClsss $context context object
1734   * @param string $filearea file area
1735   * @param array $args extra arguments
1736   * @param bool $forcedownload whether or not force download
1737   * @param array $options additional options affecting the file serving
1738   * @return bool false if file not found, does not return if found - justsend the file
1739   */
1740  function glossary_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
1741      global $CFG, $DB;
1742  
1743      if ($context->contextlevel != CONTEXT_MODULE) {
1744          return false;
1745      }
1746  
1747      require_course_login($course, true, $cm);
1748  
1749      if ($filearea === 'attachment' or $filearea === 'entry') {
1750          $entryid = (int)array_shift($args);
1751  
1752          require_course_login($course, true, $cm);
1753  
1754          if (!$entry = $DB->get_record('glossary_entries', array('id'=>$entryid))) {
1755              return false;
1756          }
1757  
1758          if (!$glossary = $DB->get_record('glossary', array('id'=>$cm->instance))) {
1759              return false;
1760          }
1761  
1762          if ($glossary->defaultapproval and !$entry->approved and !has_capability('mod/glossary:approve', $context)) {
1763              return false;
1764          }
1765  
1766          // this trickery here is because we need to support source glossary access
1767  
1768          if ($entry->glossaryid == $cm->instance) {
1769              $filecontext = $context;
1770  
1771          } else if ($entry->sourceglossaryid == $cm->instance) {
1772              if (!$maincm = get_coursemodule_from_instance('glossary', $entry->glossaryid)) {
1773                  return false;
1774              }
1775              $filecontext = context_module::instance($maincm->id);
1776  
1777          } else {
1778              return false;
1779          }
1780  
1781          $relativepath = implode('/', $args);
1782          $fullpath = "/$filecontext->id/mod_glossary/$filearea/$entryid/$relativepath";
1783  
1784          $fs = get_file_storage();
1785          if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1786              return false;
1787          }
1788  
1789          // finally send the file
1790          send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security!
1791  
1792      } else if ($filearea === 'export') {
1793          require_login($course, false, $cm);
1794          require_capability('mod/glossary:export', $context);
1795  
1796          if (!$glossary = $DB->get_record('glossary', array('id'=>$cm->instance))) {
1797              return false;
1798          }
1799  
1800          $cat = array_shift($args);
1801          $cat = clean_param($cat, PARAM_ALPHANUM);
1802  
1803          $filename = clean_filename(strip_tags(format_string($glossary->name)).'.xml');
1804          $content = glossary_generate_export_file($glossary, NULL, $cat);
1805  
1806          send_file($content, $filename, 0, 0, true, true);
1807      }
1808  
1809      return false;
1810  }
1811  
1812  /**
1813   *
1814   */
1815  function glossary_print_tabbed_table_end() {
1816       echo "</div></div>";
1817  }
1818  
1819  /**
1820   * @param object $cm
1821   * @param object $glossary
1822   * @param string $mode
1823   * @param string $hook
1824   * @param string $sortkey
1825   * @param string $sortorder
1826   */
1827  function glossary_print_approval_menu($cm, $glossary,$mode, $hook, $sortkey = '', $sortorder = '') {
1828      if ($glossary->showalphabet) {
1829          echo '<div class="glossaryexplain">' . get_string("explainalphabet","glossary") . '</div><br />';
1830      }
1831      glossary_print_special_links($cm, $glossary, $mode, $hook);
1832  
1833      glossary_print_alphabet_links($cm, $glossary, $mode, $hook,$sortkey, $sortorder);
1834  
1835      glossary_print_all_links($cm, $glossary, $mode, $hook);
1836  
1837      glossary_print_sorting_links($cm, $mode, 'CREATION', 'asc');
1838  }
1839  /**
1840   * @param object $cm
1841   * @param object $glossary
1842   * @param string $hook
1843   * @param string $sortkey
1844   * @param string $sortorder
1845   */
1846  function glossary_print_import_menu($cm, $glossary, $mode, $hook, $sortkey='', $sortorder = '') {
1847      echo '<div class="glossaryexplain">' . get_string("explainimport","glossary") . '</div>';
1848  }
1849  
1850  /**
1851   * @param object $cm
1852   * @param object $glossary
1853   * @param string $hook
1854   * @param string $sortkey
1855   * @param string $sortorder
1856   */
1857  function glossary_print_export_menu($cm, $glossary, $mode, $hook, $sortkey='', $sortorder = '') {
1858      echo '<div class="glossaryexplain">' . get_string("explainexport","glossary") . '</div>';
1859  }
1860  /**
1861   * @param object $cm
1862   * @param object $glossary
1863   * @param string $hook
1864   * @param string $sortkey
1865   * @param string $sortorder
1866   */
1867  function glossary_print_alphabet_menu($cm, $glossary, $mode, $hook, $sortkey='', $sortorder = '') {
1868      if ( $mode != 'date' ) {
1869          if ($glossary->showalphabet) {
1870              echo '<div class="glossaryexplain">' . get_string("explainalphabet","glossary") . '</div><br />';
1871          }
1872  
1873          glossary_print_special_links($cm, $glossary, $mode, $hook);
1874  
1875          glossary_print_alphabet_links($cm, $glossary, $mode, $hook, $sortkey, $sortorder);
1876  
1877          glossary_print_all_links($cm, $glossary, $mode, $hook);
1878      } else {
1879          glossary_print_sorting_links($cm, $mode, $sortkey,$sortorder);
1880      }
1881  }
1882  
1883  /**
1884   * @param object $cm
1885   * @param object $glossary
1886   * @param string $hook
1887   * @param string $sortkey
1888   * @param string $sortorder
1889   */
1890  function glossary_print_author_menu($cm, $glossary,$mode, $hook, $sortkey = '', $sortorder = '') {
1891      if ($glossary->showalphabet) {
1892          echo '<div class="glossaryexplain">' . get_string("explainalphabet","glossary") . '</div><br />';
1893      }
1894  
1895      glossary_print_alphabet_links($cm, $glossary, $mode, $hook, $sortkey, $sortorder);
1896      glossary_print_all_links($cm, $glossary, $mode, $hook);
1897      glossary_print_sorting_links($cm, $mode, $sortkey,$sortorder);
1898  }
1899  
1900  /**
1901   * @global object
1902   * @global object
1903   * @param object $cm
1904   * @param object $glossary
1905   * @param string $hook
1906   * @param object $category
1907   */
1908  function glossary_print_categories_menu($cm, $glossary, $hook, $category) {
1909       global $CFG, $DB, $OUTPUT;
1910  
1911       $context = context_module::instance($cm->id);
1912  
1913      // Prepare format_string/text options
1914      $fmtoptions = array(
1915          'context' => $context);
1916  
1917       echo '<table border="0" width="100%">';
1918       echo '<tr>';
1919  
1920       echo '<td align="center" style="width:20%">';
1921       if (has_capability('mod/glossary:managecategories', $context)) {
1922               $options['id'] = $cm->id;
1923               $options['mode'] = 'cat';
1924               $options['hook'] = $hook;
1925               echo $OUTPUT->single_button(new moodle_url("editcategories.php", $options), get_string("editcategories","glossary"), "get");
1926       }
1927       echo '</td>';
1928  
1929       echo '<td align="center" style="width:60%">';
1930       echo '<b>';
1931  
1932       $menu = array();
1933       $menu[GLOSSARY_SHOW_ALL_CATEGORIES] = get_string("allcategories","glossary");
1934       $menu[GLOSSARY_SHOW_NOT_CATEGORISED] = get_string("notcategorised","glossary");
1935  
1936       $categories = $DB->get_records("glossary_categories", array("glossaryid"=>$glossary->id), "name ASC");
1937       $selected = '';
1938       if ( $categories ) {
1939            foreach ($categories as $currentcategory) {
1940                   $url = $currentcategory->id;
1941                   if ( $category ) {
1942                       if ($currentcategory->id == $category->id) {
1943                           $selected = $url;
1944                       }
1945                   }
1946                   $menu[$url] = format_string($currentcategory->name, true, $fmtoptions);
1947            }
1948       }
1949       if ( !$selected ) {
1950           $selected = GLOSSARY_SHOW_NOT_CATEGORISED;
1951       }
1952  
1953       if ( $category ) {
1954          echo format_string($category->name, true, $fmtoptions);
1955       } else {
1956          if ( $hook == GLOSSARY_SHOW_NOT_CATEGORISED ) {
1957  
1958              echo get_string("entrieswithoutcategory","glossary");
1959              $selected = GLOSSARY_SHOW_NOT_CATEGORISED;
1960  
1961          } else if ( empty($hook) ) {
1962  
1963              echo get_string("allcategories","glossary");
1964              $selected = GLOSSARY_SHOW_ALL_CATEGORIES;
1965  
1966          }
1967       }
1968       echo '</b></td>';
1969       echo '<td align="center" style="width:20%">';
1970  
1971       $select = new single_select(new moodle_url("/mod/glossary/view.php", array('id'=>$cm->id, 'mode'=>'cat')), 'hook', $menu, $selected, null, "catmenu");
1972       $select->set_label(get_string('categories', 'glossary'), array('class' => 'accesshide'));
1973       echo $OUTPUT->render($select);
1974  
1975       echo '</td>';
1976       echo '</tr>';
1977  
1978       echo '</table>';
1979  }
1980  
1981  /**
1982   * @global object
1983   * @param object $cm
1984   * @param object $glossary
1985   * @param string $mode
1986   * @param string $hook
1987   */
1988  function glossary_print_all_links($cm, $glossary, $mode, $hook) {
1989  global $CFG;
1990       if ( $glossary->showall) {
1991           $strallentries       = get_string("allentries", "glossary");
1992           if ( $hook == 'ALL' ) {
1993                echo "<b>$strallentries</b>";
1994           } else {
1995                $strexplainall = strip_tags(get_string("explainall","glossary"));
1996                echo "<a title=\"$strexplainall\" href=\"$CFG->wwwroot/mod/glossary/view.php?id=$cm->id&amp;mode=$mode&amp;hook=ALL\">$strallentries</a>";
1997           }
1998       }
1999  }
2000  
2001  /**
2002   * @global object
2003   * @param object $cm
2004   * @param object $glossary
2005   * @param string $mode
2006   * @param string $hook
2007   */
2008  function glossary_print_special_links($cm, $glossary, $mode, $hook) {
2009  global $CFG;
2010       if ( $glossary->showspecial) {
2011           $strspecial          = get_string("special", "glossary");
2012           if ( $hook == 'SPECIAL' ) {
2013                echo "<b>$strspecial</b> | ";
2014           } else {
2015                $strexplainspecial = strip_tags(get_string("explainspecial","glossary"));
2016                echo "<a title=\"$strexplainspecial\" href=\"$CFG->wwwroot/mod/glossary/view.php?id=$cm->id&amp;mode=$mode&amp;hook=SPECIAL\">$strspecial</a> | ";
2017           }
2018       }
2019  }
2020  
2021  /**
2022   * @global object
2023   * @param object $glossary
2024   * @param string $mode
2025   * @param string $hook
2026   * @param string $sortkey
2027   * @param string $sortorder
2028   */
2029  function glossary_print_alphabet_links($cm, $glossary, $mode, $hook, $sortkey, $sortorder) {
2030  global $CFG;
2031       if ( $glossary->showalphabet) {
2032            $alphabet = explode(",", get_string('alphabet', 'langconfig'));
2033            for ($i = 0; $i < count($alphabet); $i++) {
2034                if ( $hook == $alphabet[$i] and $hook) {
2035                     echo "<b>$alphabet[$i]</b>";
2036                } else {
2037                     echo "<a href=\"$CFG->wwwroot/mod/glossary/view.php?id=$cm->id&amp;mode=$mode&amp;hook=".urlencode($alphabet[$i])."&amp;sortkey=$sortkey&amp;sortorder=$sortorder\">$alphabet[$i]</a>";
2038                }
2039                echo ' | ';
2040            }
2041       }
2042  }
2043  
2044  /**
2045   * @global object
2046   * @param object $cm
2047   * @param string $mode
2048   * @param string $sortkey
2049   * @param string $sortorder
2050   */
2051  function glossary_print_sorting_links($cm, $mode, $sortkey = '',$sortorder = '') {
2052      global $CFG, $OUTPUT;
2053  
2054      $asc    = get_string("ascending","glossary");
2055      $desc   = get_string("descending","glossary");
2056      $bopen  = '<b>';
2057      $bclose = '</b>';
2058  
2059       $neworder = '';
2060       $currentorder = '';
2061       $currentsort = '';
2062       if ( $sortorder ) {
2063           if ( $sortorder == 'asc' ) {
2064               $currentorder = $asc;
2065               $neworder = '&amp;sortorder=desc';
2066               $newordertitle = get_string('changeto', 'glossary', $desc);
2067           } else {
2068               $currentorder = $desc;
2069               $neworder = '&amp;sortorder=asc';
2070               $newordertitle = get_string('changeto', 'glossary', $asc);
2071           }
2072           $icon = " " . $OUTPUT->pix_icon($sortorder, $newordertitle, 'glossary');
2073       } else {
2074           if ( $sortkey != 'CREATION' and $sortkey != 'UPDATE' and
2075                 $sortkey != 'FIRSTNAME' and $sortkey != 'LASTNAME' ) {
2076               $icon = "";
2077               $newordertitle = $asc;
2078           } else {
2079               $newordertitle = $desc;
2080               $neworder = '&amp;sortorder=desc';
2081               $icon = " " . $OUTPUT->pix_icon('asc', $newordertitle, 'glossary');
2082           }
2083       }
2084       $ficon     = '';
2085       $fneworder = '';
2086       $fbtag     = '';
2087       $fendbtag  = '';
2088  
2089       $sicon     = '';
2090       $sneworder = '';
2091  
2092       $sbtag      = '';
2093       $fbtag      = '';
2094       $fendbtag      = '';
2095       $sendbtag      = '';
2096  
2097       $sendbtag  = '';
2098  
2099       if ( $sortkey == 'CREATION' or $sortkey == 'FIRSTNAME' ) {
2100           $ficon       = $icon;
2101           $fneworder   = $neworder;
2102           $fordertitle = $newordertitle;
2103           $sordertitle = $asc;
2104           $fbtag       = $bopen;
2105           $fendbtag    = $bclose;
2106       } elseif ($sortkey == 'UPDATE' or $sortkey == 'LASTNAME') {
2107           $sicon = $icon;
2108           $sneworder   = $neworder;
2109           $fordertitle = $asc;
2110           $sordertitle = $newordertitle;
2111           $sbtag       = $bopen;
2112           $sendbtag    = $bclose;
2113       } else {
2114           $fordertitle = $asc;
2115           $sordertitle = $asc;
2116       }
2117  
2118       if ( $sortkey == 'CREATION' or $sortkey == 'UPDATE' ) {
2119           $forder = 'CREATION';
2120           $sorder =  'UPDATE';
2121           $fsort  = get_string("sortbycreation", "glossary");
2122           $ssort  = get_string("sortbylastupdate", "glossary");
2123  
2124           $currentsort = $fsort;
2125           if ($sortkey == 'UPDATE') {
2126               $currentsort = $ssort;
2127           }
2128           $sort        = get_string("sortchronogically", "glossary");
2129       } elseif ( $sortkey == 'FIRSTNAME' or $sortkey == 'LASTNAME') {
2130           $forder = 'FIRSTNAME';
2131           $sorder =  'LASTNAME';
2132           $fsort  = get_string("firstname");
2133           $ssort  = get_string("lastname");
2134  
2135           $currentsort = $fsort;
2136           if ($sortkey == 'LASTNAME') {
2137               $currentsort = $ssort;
2138           }
2139           $sort        = get_string("sortby", "glossary");
2140       }
2141       $current = '<span class="accesshide">'.get_string('current', 'glossary', "$currentsort $currentorder").'</span>';
2142       echo "<br />$current $sort: $sbtag<a title=\"$ssort $sordertitle\" href=\"$CFG->wwwroot/mod/glossary/view.php?id=$cm->id&amp;sortkey=$sorder$sneworder&amp;mode=$mode\">$ssort$sicon</a>$sendbtag | ".
2143                            "$fbtag<a title=\"$fsort $fordertitle\" href=\"$CFG->wwwroot/mod/glossary/view.php?id=$cm->id&amp;sortkey=$forder$fneworder&amp;mode=$mode\">$fsort$ficon</a>$fendbtag<br />";
2144  }
2145  
2146  /**
2147   *
2148   * @param object $entry0
2149   * @param object $entry1
2150   * @return int [-1 | 0 | 1]
2151   */
2152  function glossary_sort_entries ( $entry0, $entry1 ) {
2153  
2154      if ( core_text::strtolower(ltrim($entry0->concept)) < core_text::strtolower(ltrim($entry1->concept)) ) {
2155          return -1;
2156      } elseif ( core_text::strtolower(ltrim($entry0->concept)) > core_text::strtolower(ltrim($entry1->concept)) ) {
2157          return 1;
2158      } else {
2159          return 0;
2160      }
2161  }
2162  
2163  
2164  /**
2165   * @global object
2166   * @global object
2167   * @global object
2168   * @param object $course
2169   * @param object $entry
2170   * @return bool
2171   */
2172  function  glossary_print_entry_ratings($course, $entry) {
2173      global $OUTPUT;
2174      if( !empty($entry->rating) ){
2175          echo $OUTPUT->render($entry->rating);
2176      }
2177  }
2178  
2179  /**
2180   *
2181   * @global object
2182   * @global object
2183   * @global object
2184   * @param int $courseid
2185   * @param array $entries
2186   * @param int $displayformat
2187   */
2188  function glossary_print_dynaentry($courseid, $entries, $displayformat = -1) {
2189      global $USER, $CFG, $DB;
2190  
2191      echo '<div class="boxaligncenter">';
2192      echo '<table class="glossarypopup" cellspacing="0"><tr>';
2193      echo '<td>';
2194      if ( $entries ) {
2195          foreach ( $entries as $entry ) {
2196              if (! $glossary = $DB->get_record('glossary', array('id'=>$entry->glossaryid))) {
2197                  print_error('invalidid', 'glossary');
2198              }
2199              if (! $course = $DB->get_record('course', array('id'=>$glossary->course))) {
2200                  print_error('coursemisconf');
2201              }
2202              if (!$cm = get_coursemodule_from_instance('glossary', $entry->glossaryid, $glossary->course) ) {
2203                  print_error('invalidid', 'glossary');
2204              }
2205  
2206              //If displayformat is present, override glossary->displayformat
2207              if ($displayformat < 0) {
2208                  $dp = $glossary->displayformat;
2209              } else {
2210                  $dp = $displayformat;
2211              }
2212  
2213              //Get popupformatname
2214              $format = $DB->get_record('glossary_formats', array('name'=>$dp));
2215              $displayformat = $format->popupformatname;
2216  
2217              //Check displayformat variable and set to default if necessary
2218              if (!$displayformat) {
2219                  $displayformat = 'dictionary';
2220              }
2221  
2222              $formatfile = $CFG->dirroot.'/mod/glossary/formats/'.$displayformat.'/'.$displayformat.'_format.php';
2223              $functionname = 'glossary_show_entry_'.$displayformat;
2224  
2225              if (file_exists($formatfile)) {
2226                  include_once($formatfile);
2227                  if (function_exists($functionname)) {
2228                      $functionname($course, $cm, $glossary, $entry,'','','','');
2229                  }
2230              }
2231          }
2232      }
2233      echo '</td>';
2234      echo '</tr></table></div>';
2235  }
2236  
2237  /**
2238   *
2239   * @global object
2240   * @param array $entries
2241   * @param array $aliases
2242   * @param array $categories
2243   * @return string
2244   */
2245  function glossary_generate_export_csv($entries, $aliases, $categories) {
2246      global $CFG;
2247      $csv = '';
2248      $delimiter = '';
2249      require_once($CFG->libdir . '/csvlib.class.php');
2250      $delimiter = csv_import_reader::get_delimiter('comma');
2251      $csventries = array(0 => array(get_string('concept', 'glossary'), get_string('definition', 'glossary')));
2252      $csvaliases = array(0 => array());
2253      $csvcategories = array(0 => array());
2254      $aliascount = 0;
2255      $categorycount = 0;
2256  
2257      foreach ($entries as $entry) {
2258          $thisaliasesentry = array();
2259          $thiscategoriesentry = array();
2260          $thiscsventry = array($entry->concept, nl2br($entry->definition));
2261  
2262          if (array_key_exists($entry->id, $aliases) && is_array($aliases[$entry->id])) {
2263              $thiscount = count($aliases[$entry->id]);
2264              if ($thiscount > $aliascount) {
2265                  $aliascount = $thiscount;
2266              }
2267              foreach ($aliases[$entry->id] as $alias) {
2268                  $thisaliasesentry[] = trim($alias);
2269              }
2270          }
2271          if (array_key_exists($entry->id, $categories) && is_array($categories[$entry->id])) {
2272              $thiscount = count($categories[$entry->id]);
2273              if ($thiscount > $categorycount) {
2274                  $categorycount = $thiscount;
2275              }
2276              foreach ($categories[$entry->id] as $catentry) {
2277                  $thiscategoriesentry[] = trim($catentry);
2278              }
2279          }
2280          $csventries[$entry->id] = $thiscsventry;
2281          $csvaliases[$entry->id] = $thisaliasesentry;
2282          $csvcategories[$entry->id] = $thiscategoriesentry;
2283  
2284      }
2285      $returnstr = '';
2286      foreach ($csventries as $id => $row) {
2287          $aliasstr = '';
2288          $categorystr = '';
2289          if ($id == 0) {
2290              $aliasstr = get_string('alias', 'glossary');
2291              $categorystr = get_string('category', 'glossary');
2292          }
2293          $row = array_merge($row, array_pad($csvaliases[$id], $aliascount, $aliasstr), array_pad($csvcategories[$id], $categorycount, $categorystr));
2294          $returnstr .= '"' . implode('"' . $delimiter . '"', $row) . '"' . "\n";
2295      }
2296      return $returnstr;
2297  }
2298  
2299  /**
2300   *
2301   * @param object $glossary
2302   * @param string $ignored invalid parameter
2303   * @param int|string $hook
2304   * @return string
2305   */
2306  function glossary_generate_export_file($glossary, $ignored = "", $hook = 0) {
2307      global $CFG, $DB;
2308  
2309      // Large exports are likely to take their time and memory.
2310      core_php_time_limit::raise();
2311      raise_memory_limit(MEMORY_EXTRA);
2312  
2313      $cm = get_coursemodule_from_instance('glossary', $glossary->id, $glossary->course);
2314      $context = context_module::instance($cm->id);
2315  
2316      $co  = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
2317  
2318      $co .= glossary_start_tag("GLOSSARY",0,true);
2319      $co .= glossary_start_tag("INFO",1,true);
2320          $co .= glossary_full_tag("NAME",2,false,$glossary->name);
2321          $co .= glossary_full_tag("INTRO",2,false,$glossary->intro);
2322          $co .= glossary_full_tag("INTROFORMAT",2,false,$glossary->introformat);
2323          $co .= glossary_full_tag("ALLOWDUPLICATEDENTRIES",2,false,$glossary->allowduplicatedentries);
2324          $co .= glossary_full_tag("DISPLAYFORMAT",2,false,$glossary->displayformat);
2325          $co .= glossary_full_tag("SHOWSPECIAL",2,false,$glossary->showspecial);
2326          $co .= glossary_full_tag("SHOWALPHABET",2,false,$glossary->showalphabet);
2327          $co .= glossary_full_tag("SHOWALL",2,false,$glossary->showall);
2328          $co .= glossary_full_tag("ALLOWCOMMENTS",2,false,$glossary->allowcomments);
2329          $co .= glossary_full_tag("USEDYNALINK",2,false,$glossary->usedynalink);
2330          $co .= glossary_full_tag("DEFAULTAPPROVAL",2,false,$glossary->defaultapproval);
2331          $co .= glossary_full_tag("GLOBALGLOSSARY",2,false,$glossary->globalglossary);
2332          $co .= glossary_full_tag("ENTBYPAGE",2,false,$glossary->entbypage);
2333          $co .= glossary_xml_export_files('INTROFILES', 2, $context->id, 'intro', 0);
2334  
2335          if ( $entries = $DB->get_records("glossary_entries", array("glossaryid"=>$glossary->id))) {
2336              $co .= glossary_start_tag("ENTRIES",2,true);
2337              foreach ($entries as $entry) {
2338                  $permissiongranted = 1;
2339                  if ( $hook ) {
2340                      switch ( $hook ) {
2341                      case "ALL":
2342                      case "SPECIAL":
2343                      break;
2344                      default:
2345                          $permissiongranted = ($entry->concept[ strlen($hook)-1 ] == $hook);
2346                      break;
2347                      }
2348                  }
2349                  if ( $hook ) {
2350                      switch ( $hook ) {
2351                      case GLOSSARY_SHOW_ALL_CATEGORIES:
2352                      break;
2353                      case GLOSSARY_SHOW_NOT_CATEGORISED:
2354                          $permissiongranted = !$DB->record_exists("glossary_entries_categories", array("entryid"=>$entry->id));
2355                      break;
2356                      default:
2357                          $permissiongranted = $DB->record_exists("glossary_entries_categories", array("entryid"=>$entry->id, "categoryid"=>$hook));
2358                      break;
2359                      }
2360                  }
2361                  if ( $entry->approved and $permissiongranted ) {
2362                      $co .= glossary_start_tag("ENTRY",3,true);
2363                      $co .= glossary_full_tag("CONCEPT",4,false,trim($entry->concept));
2364                      $co .= glossary_full_tag("DEFINITION",4,false,$entry->definition);
2365                      $co .= glossary_full_tag("FORMAT",4,false,$entry->definitionformat); // note: use old name for BC reasons
2366                      $co .= glossary_full_tag("USEDYNALINK",4,false,$entry->usedynalink);
2367                      $co .= glossary_full_tag("CASESENSITIVE",4,false,$entry->casesensitive);
2368                      $co .= glossary_full_tag("FULLMATCH",4,false,$entry->fullmatch);
2369                      $co .= glossary_full_tag("TEACHERENTRY",4,false,$entry->teacherentry);
2370  
2371                      if ( $aliases = $DB->get_records("glossary_alias", array("entryid"=>$entry->id))) {
2372                          $co .= glossary_start_tag("ALIASES",4,true);
2373                          foreach ($aliases as $alias) {
2374                              $co .= glossary_start_tag("ALIAS",5,true);
2375                                  $co .= glossary_full_tag("NAME",6,false,trim($alias->alias));
2376                              $co .= glossary_end_tag("ALIAS",5,true);
2377                          }
2378                          $co .= glossary_end_tag("ALIASES",4,true);
2379                      }
2380                      if ( $catentries = $DB->get_records("glossary_entries_categories", array("entryid"=>$entry->id))) {
2381                          $co .= glossary_start_tag("CATEGORIES",4,true);
2382                          foreach ($catentries as $catentry) {
2383                              $category = $DB->get_record("glossary_categories", array("id"=>$catentry->categoryid));
2384  
2385                              $co .= glossary_start_tag("CATEGORY",5,true);
2386                                  $co .= glossary_full_tag("NAME",6,false,$category->name);
2387                                  $co .= glossary_full_tag("USEDYNALINK",6,false,$category->usedynalink);
2388                              $co .= glossary_end_tag("CATEGORY",5,true);
2389                          }
2390                          $co .= glossary_end_tag("CATEGORIES",4,true);
2391                      }
2392  
2393                      // Export files embedded in entries.
2394                      $co .= glossary_xml_export_files('ENTRYFILES', 4, $context->id, 'entry', $entry->id);
2395  
2396                      // Export attachments.
2397                      $co .= glossary_xml_export_files('ATTACHMENTFILES', 4, $context->id, 'attachment', $entry->id);
2398  
2399                      // Export tags.
2400                      $tags = core_tag_tag::get_item_tags_array('mod_glossary', 'glossary_entries', $entry->id);
2401                      if (count($tags)) {
2402                          $co .= glossary_start_tag("TAGS", 4, true);
2403                          foreach ($tags as $tag) {
2404                              $co .= glossary_full_tag("TAG", 5, false, $tag);
2405                          }
2406                          $co .= glossary_end_tag("TAGS", 4, true);
2407                      }
2408  
2409                      $co .= glossary_end_tag("ENTRY",3,true);
2410                  }
2411              }
2412              $co .= glossary_end_tag("ENTRIES",2,true);
2413  
2414          }
2415  
2416  
2417      $co .= glossary_end_tag("INFO",1,true);
2418      $co .= glossary_end_tag("GLOSSARY",0,true);
2419  
2420      return $co;
2421  }
2422  /// Functions designed by Eloy Lafuente
2423  /// Functions to create, open and write header of the xml file
2424  
2425  /**
2426   * Read import file and convert to current charset
2427   *
2428   * @global object
2429   * @param string $file
2430   * @return string
2431   */
2432  function glossary_read_imported_file($file_content) {
2433      global $CFG;
2434      require_once  "../../lib/xmlize.php";
2435  
2436      return xmlize($file_content, 0);
2437  }
2438  
2439  /**
2440   * Return the xml start tag
2441   *
2442   * @param string $tag
2443   * @param int $level
2444   * @param bool $endline
2445   * @return string
2446   */
2447  function glossary_start_tag($tag,$level=0,$endline=false) {
2448          if ($endline) {
2449             $endchar = "\n";
2450          } else {
2451             $endchar = "";
2452          }
2453          return str_repeat(" ",$level*2)."<".strtoupper($tag).">".$endchar;
2454  }
2455  
2456  /**
2457   * Return the xml end tag
2458   * @param string $tag
2459   * @param int $level
2460   * @param bool $endline
2461   * @return string
2462   */
2463  function glossary_end_tag($tag,$level=0,$endline=true) {
2464          if ($endline) {
2465             $endchar = "\n";
2466          } else {
2467             $endchar = "";
2468          }
2469          return str_repeat(" ",$level*2)."</".strtoupper($tag).">".$endchar;
2470  }
2471  
2472  /**
2473   * Return the start tag, the contents and the end tag
2474   *
2475   * @global object
2476   * @param string $tag
2477   * @param int $level
2478   * @param bool $endline
2479   * @param string $content
2480   * @return string
2481   */
2482  function glossary_full_tag($tag, $level, $endline, $content) {
2483          global $CFG;
2484  
2485          $st = glossary_start_tag($tag,$level,$endline);
2486          $co = preg_replace("/\r\n|\r/", "\n", s($content));
2487          $et = glossary_end_tag($tag,0,true);
2488          return $st.$co.$et;
2489  }
2490  
2491  /**
2492   * Prepares file area to export as part of XML export
2493   *
2494   * @param string $tag XML tag to use for the group
2495   * @param int $taglevel
2496   * @param int $contextid
2497   * @param string $filearea
2498   * @param int $itemid
2499   * @return string
2500   */
2501  function glossary_xml_export_files($tag, $taglevel, $contextid, $filearea, $itemid) {
2502      $co = '';
2503      $fs = get_file_storage();
2504      if ($files = $fs->get_area_files(
2505          $contextid, 'mod_glossary', $filearea, $itemid, 'itemid,filepath,filename', false)) {
2506          $co .= glossary_start_tag($tag, $taglevel, true);
2507          foreach ($files as $file) {
2508              $co .= glossary_start_tag('FILE', $taglevel + 1, true);
2509              $co .= glossary_full_tag('FILENAME', $taglevel + 2, false, $file->get_filename());
2510              $co .= glossary_full_tag('FILEPATH', $taglevel + 2, false, $file->get_filepath());
2511              $co .= glossary_full_tag('CONTENTS', $taglevel + 2, false, base64_encode($file->get_content()));
2512              $co .= glossary_full_tag('FILEAUTHOR', $taglevel + 2, false, $file->get_author());
2513              $co .= glossary_full_tag('FILELICENSE', $taglevel + 2, false, $file->get_license());
2514              $co .= glossary_end_tag('FILE', $taglevel + 1);
2515          }
2516          $co .= glossary_end_tag($tag, $taglevel);
2517      }
2518      return $co;
2519  }
2520  
2521  /**
2522   * Parses files from XML import and inserts them into file system
2523   *
2524   * @param array $xmlparent parent element in parsed XML tree
2525   * @param string $tag
2526   * @param int $contextid
2527   * @param string $filearea
2528   * @param int $itemid
2529   * @return int
2530   */
2531  function glossary_xml_import_files($xmlparent, $tag, $contextid, $filearea, $itemid) {
2532      global $USER, $CFG;
2533      $count = 0;
2534      if (isset($xmlparent[$tag][0]['#']['FILE'])) {
2535          $fs = get_file_storage();
2536          $files = $xmlparent[$tag][0]['#']['FILE'];
2537          foreach ($files as $file) {
2538              $filerecord = array(
2539                  'contextid' => $contextid,
2540                  'component' => 'mod_glossary',
2541                  'filearea'  => $filearea,
2542                  'itemid'    => $itemid,
2543                  'filepath'  => $file['#']['FILEPATH'][0]['#'],
2544                  'filename'  => $file['#']['FILENAME'][0]['#'],
2545                  'userid'    => $USER->id
2546              );
2547              if (array_key_exists('FILEAUTHOR', $file['#'])) {
2548                  $filerecord['author'] = $file['#']['FILEAUTHOR'][0]['#'];
2549              }
2550              if (array_key_exists('FILELICENSE', $file['#'])) {
2551                  $license = $file['#']['FILELICENSE'][0]['#'];
2552                  require_once($CFG->libdir . "/licenselib.php");
2553                  if (license_manager::get_license_by_shortname($license)) {
2554                      $filerecord['license'] = $license;
2555                  }
2556              }
2557              $content =  $file['#']['CONTENTS'][0]['#'];
2558              $fs->create_file_from_string($filerecord, base64_decode($content));
2559              $count++;
2560          }
2561      }
2562      return $count;
2563  }
2564  
2565  /**
2566   * How many unrated entries are in the given glossary for a given user?
2567   *
2568   * @global moodle_database $DB
2569   * @param int $glossaryid
2570   * @param int $userid
2571   * @return int
2572   */
2573  function glossary_count_unrated_entries($glossaryid, $userid) {
2574      global $DB;
2575  
2576      $sql = "SELECT COUNT('x') as num
2577                FROM {glossary_entries}
2578               WHERE glossaryid = :glossaryid AND
2579                     userid <> :userid";
2580      $params = array('glossaryid' => $glossaryid, 'userid' => $userid);
2581      $entries = $DB->count_records_sql($sql, $params);
2582  
2583      if ($entries) {
2584          // We need to get the contextid for the glossaryid we have been given.
2585          $sql = "SELECT ctx.id
2586                    FROM {context} ctx
2587                    JOIN {course_modules} cm ON cm.id = ctx.instanceid
2588                    JOIN {modules} m ON m.id = cm.module
2589                    JOIN {glossary} g ON g.id = cm.instance
2590                   WHERE ctx.contextlevel = :contextlevel AND
2591                         m.name = 'glossary' AND
2592                         g.id = :glossaryid";
2593          $contextid = $DB->get_field_sql($sql, array('glossaryid' => $glossaryid, 'contextlevel' => CONTEXT_MODULE));
2594  
2595          // Now we need to count the ratings that this user has made
2596          $sql = "SELECT COUNT('x') AS num
2597                    FROM {glossary_entries} e
2598                    JOIN {rating} r ON r.itemid = e.id
2599                   WHERE e.glossaryid = :glossaryid AND
2600                         r.userid = :userid AND
2601                         r.component = 'mod_glossary' AND
2602                         r.ratingarea = 'entry' AND
2603                         r.contextid = :contextid";
2604          $params = array('glossaryid' => $glossaryid, 'userid' => $userid, 'contextid' => $contextid);
2605          $rated = $DB->count_records_sql($sql, $params);
2606          if ($rated) {
2607              // The number or enties minus the number or rated entries equals the number of unrated
2608              // entries
2609              if ($entries > $rated) {
2610                  return $entries - $rated;
2611              } else {
2612                  return 0;    // Just in case there was a counting error
2613              }
2614          } else {
2615              return (int)$entries;
2616          }
2617      } else {
2618          return 0;
2619      }
2620  }
2621  
2622  /**
2623   *
2624   * Returns the html code to represent any pagging bar. Paramenters are:
2625   *
2626   * The function dinamically show the first and last pages, and "scroll" over pages.
2627   * Fully compatible with Moodle's print_paging_bar() function. Perhaps some day this
2628   * could replace the general one. ;-)
2629   *
2630   * @param int $totalcount total number of records to be displayed
2631   * @param int $page page currently selected (0 based)
2632   * @param int $perpage number of records per page
2633   * @param string $baseurl url to link in each page, the string 'page=XX' will be added automatically.
2634   *
2635   * @param int $maxpageallowed Optional maximum number of page allowed.
2636   * @param int $maxdisplay Optional maximum number of page links to show in the bar
2637   * @param string $separator Optional string to be used between pages in the bar
2638   * @param string $specialtext Optional string to be showed as an special link
2639   * @param string $specialvalue Optional value (page) to be used in the special link
2640   * @param bool $previousandnext Optional to decide if we want the previous and next links
2641   * @return string
2642   */
2643  function glossary_get_paging_bar($totalcount, $page, $perpage, $baseurl, $maxpageallowed=99999, $maxdisplay=20, $separator="&nbsp;", $specialtext="", $specialvalue=-1, $previousandnext = true) {
2644  
2645      $code = '';
2646  
2647      $showspecial = false;
2648      $specialselected = false;
2649  
2650      //Check if we have to show the special link
2651      if (!empty($specialtext)) {
2652          $showspecial = true;
2653      }
2654      //Check if we are with the special link selected
2655      if ($showspecial && $page == $specialvalue) {
2656          $specialselected = true;
2657      }
2658  
2659      //If there are results (more than 1 page)
2660      if ($totalcount > $perpage) {
2661          $code .= "<div style=\"text-align:center\">";
2662          $code .= "<p>".get_string("page").":";
2663  
2664          $maxpage = (int)(($totalcount-1)/$perpage);
2665  
2666          //Lower and upper limit of page
2667          if ($page < 0) {
2668              $page = 0;
2669          }
2670          if ($page > $maxpageallowed) {
2671              $page = $maxpageallowed;
2672          }
2673          if ($page > $maxpage) {
2674              $page = $maxpage;
2675          }
2676  
2677          //Calculate the window of pages
2678          $pagefrom = $page - ((int)($maxdisplay / 2));
2679          if ($pagefrom < 0) {
2680              $pagefrom = 0;
2681          }
2682          $pageto = $pagefrom + $maxdisplay - 1;
2683          if ($pageto > $maxpageallowed) {
2684              $pageto = $maxpageallowed;
2685          }
2686          if ($pageto > $maxpage) {
2687              $pageto = $maxpage;
2688          }
2689  
2690          //Some movements can be necessary if don't see enought pages
2691          if ($pageto - $pagefrom < $maxdisplay - 1) {
2692              if ($pageto - $maxdisplay + 1 > 0) {
2693                  $pagefrom = $pageto - $maxdisplay + 1;
2694              }
2695          }
2696  
2697          //Calculate first and last if necessary
2698          $firstpagecode = '';
2699          $lastpagecode = '';
2700          if ($pagefrom > 0) {
2701              $firstpagecode = "$separator<a href=\"{$baseurl}page=0\">1</a>";
2702              if ($pagefrom > 1) {
2703                  $firstpagecode .= "$separator...";
2704              }
2705          }
2706          if ($pageto < $maxpage) {
2707              if ($pageto < $maxpage -1) {
2708                  $lastpagecode = "$separator...";
2709              }
2710              $lastpagecode .= "$separator<a href=\"{$baseurl}page=$maxpage\">".($maxpage+1)."</a>";
2711          }
2712  
2713          //Previous
2714          if ($page > 0 && $previousandnext) {
2715              $pagenum = $page - 1;
2716              $code .= "&nbsp;(<a  href=\"{$baseurl}page=$pagenum\">".get_string("previous")."</a>)&nbsp;";
2717          }
2718  
2719          //Add first
2720          $code .= $firstpagecode;
2721  
2722          $pagenum = $pagefrom;
2723  
2724          //List of maxdisplay pages
2725          while ($pagenum <= $pageto) {
2726              $pagetoshow = $pagenum +1;
2727              if ($pagenum == $page && !$specialselected) {
2728                  $code .= "$separator<b>$pagetoshow</b>";
2729              } else {
2730                  $code .= "$separator<a href=\"{$baseurl}page=$pagenum\">$pagetoshow</a>";
2731              }
2732              $pagenum++;
2733          }
2734  
2735          //Add last
2736          $code .= $lastpagecode;
2737  
2738          //Next
2739          if ($page < $maxpage && $page < $maxpageallowed && $previousandnext) {
2740              $pagenum = $page + 1;
2741              $code .= "$separator(<a href=\"{$baseurl}page=$pagenum\">".get_string("next")."</a>)";
2742          }
2743  
2744          //Add special
2745          if ($showspecial) {
2746              $code .= '<br />';
2747              if ($specialselected) {
2748                  $code .= "$separator<b>$specialtext</b>";
2749              } else {
2750                  $code .= "$separator<a href=\"{$baseurl}page=$specialvalue\">$specialtext</a>";
2751              }
2752          }
2753  
2754          //End html
2755          $code .= "</p>";
2756          $code .= "</div>";
2757      }
2758  
2759      return $code;
2760  }
2761  
2762  /**
2763   * List the actions that correspond to a view of this module.
2764   * This is used by the participation report.
2765   *
2766   * Note: This is not used by new logging system. Event with
2767   *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
2768   *       be considered as view action.
2769   *
2770   * @return array
2771   */
2772  function glossary_get_view_actions() {
2773      return array('view','view all','view entry');
2774  }
2775  
2776  /**
2777   * List the actions that correspond to a post of this module.
2778   * This is used by the participation report.
2779   *
2780   * Note: This is not used by new logging system. Event with
2781   *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
2782   *       will be considered as post action.
2783   *
2784   * @return array
2785   */
2786  function glossary_get_post_actions() {
2787      return array('add category','add entry','approve entry','delete category','delete entry','edit category','update entry');
2788  }
2789  
2790  
2791  /**
2792   * Implementation of the function for printing the form elements that control
2793   * whether the course reset functionality affects the glossary.
2794   * @param object $mform form passed by reference
2795   */
2796  function glossary_reset_course_form_definition(&$mform) {
2797      $mform->addElement('header', 'glossaryheader', get_string('modulenameplural', 'glossary'));
2798      $mform->addElement('checkbox', 'reset_glossary_all', get_string('resetglossariesall','glossary'));
2799  
2800      $mform->addElement('select', 'reset_glossary_types', get_string('resetglossaries', 'glossary'),
2801                         array('main'=>get_string('mainglossary', 'glossary'), 'secondary'=>get_string('secondaryglossary', 'glossary')), array('multiple' => 'multiple'));
2802      $mform->setAdvanced('reset_glossary_types');
2803      $mform->disabledIf('reset_glossary_types', 'reset_glossary_all', 'checked');
2804  
2805      $mform->addElement('checkbox', 'reset_glossary_notenrolled', get_string('deletenotenrolled', 'glossary'));
2806      $mform->disabledIf('reset_glossary_notenrolled', 'reset_glossary_all', 'checked');
2807  
2808      $mform->addElement('checkbox', 'reset_glossary_ratings', get_string('deleteallratings'));
2809      $mform->disabledIf('reset_glossary_ratings', 'reset_glossary_all', 'checked');
2810  
2811      $mform->addElement('checkbox', 'reset_glossary_comments', get_string('deleteallcomments'));
2812      $mform->disabledIf('reset_glossary_comments', 'reset_glossary_all', 'checked');
2813  
2814      $mform->addElement('checkbox', 'reset_glossary_tags', get_string('removeallglossarytags', 'glossary'));
2815      $mform->disabledIf('reset_glossary_tags', 'reset_glossary_all', 'checked');
2816  }
2817  
2818  /**
2819   * Course reset form defaults.
2820   * @return array
2821   */
2822  function glossary_reset_course_form_defaults($course) {
2823      return array('reset_glossary_all'=>0, 'reset_glossary_ratings'=>1, 'reset_glossary_comments'=>1, 'reset_glossary_notenrolled'=>0);
2824  }
2825  
2826  /**
2827   * Removes all grades from gradebook
2828   *
2829   * @param int $courseid The ID of the course to reset
2830   * @param string $type The optional type of glossary. 'main', 'secondary' or ''
2831   */
2832  function glossary_reset_gradebook($courseid, $type='') {
2833      global $DB;
2834  
2835      switch ($type) {
2836          case 'main'      : $type = "AND g.mainglossary=1"; break;
2837          case 'secondary' : $type = "AND g.mainglossary=0"; break;
2838          default          : $type = ""; //all
2839      }
2840  
2841      $sql = "SELECT g.*, cm.idnumber as cmidnumber, g.course as courseid
2842                FROM {glossary} g, {course_modules} cm, {modules} m
2843               WHERE m.name='glossary' AND m.id=cm.module AND cm.instance=g.id AND g.course=? $type";
2844  
2845      if ($glossarys = $DB->get_records_sql($sql, array($courseid))) {
2846          foreach ($glossarys as $glossary) {
2847              glossary_grade_item_update($glossary, 'reset');
2848          }
2849      }
2850  }
2851  /**
2852   * Actual implementation of the reset course functionality, delete all the
2853   * glossary responses for course $data->courseid.
2854   *
2855   * @global object
2856   * @param $data the data submitted from the reset course.
2857   * @return array status array
2858   */
2859  function glossary_reset_userdata($data) {
2860      global $CFG, $DB;
2861      require_once($CFG->dirroot.'/rating/lib.php');
2862  
2863      $componentstr = get_string('modulenameplural', 'glossary');
2864      $status = array();
2865  
2866      $allentriessql = "SELECT e.id
2867                          FROM {glossary_entries} e
2868                               JOIN {glossary} g ON e.glossaryid = g.id
2869                         WHERE g.course = ?";
2870  
2871      $allglossariessql = "SELECT g.id
2872                             FROM {glossary} g
2873                            WHERE g.course = ?";
2874  
2875      $params = array($data->courseid);
2876  
2877      $fs = get_file_storage();
2878  
2879      $rm = new rating_manager();
2880      $ratingdeloptions = new stdClass;
2881      $ratingdeloptions->component = 'mod_glossary';
2882      $ratingdeloptions->ratingarea = 'entry';
2883  
2884      // delete entries if requested
2885      if (!empty($data->reset_glossary_all)
2886           or (!empty($data->reset_glossary_types) and in_array('main', $data->reset_glossary_types) and in_array('secondary', $data->reset_glossary_types))) {
2887  
2888          $params[] = 'glossary_entry';
2889          $DB->delete_records_select('comments', "itemid IN ($allentriessql) AND commentarea=?", $params);
2890          $DB->delete_records_select('glossary_alias',    "entryid IN ($allentriessql)", $params);
2891          $DB->delete_records_select('glossary_entries', "glossaryid IN ($allglossariessql)", $params);
2892  
2893          // now get rid of all attachments
2894          if ($glossaries = $DB->get_records_sql($allglossariessql, $params)) {
2895              foreach ($glossaries as $glossaryid=>$unused) {
2896                  if (!$cm = get_coursemodule_from_instance('glossary', $glossaryid)) {
2897                      continue;
2898                  }
2899                  $context = context_module::instance($cm->id);
2900                  $fs->delete_area_files($context->id, 'mod_glossary', 'attachment');
2901  
2902                  //delete ratings
2903                  $ratingdeloptions->contextid = $context->id;
2904                  $rm->delete_ratings($ratingdeloptions);
2905  
2906                  core_tag_tag::delete_instances('mod_glossary', null, $context->id);
2907              }
2908          }
2909  
2910          // remove all grades from gradebook
2911          if (empty($data->reset_gradebook_grades)) {
2912              glossary_reset_gradebook($data->courseid);
2913          }
2914  
2915          $status[] = array('component'=>$componentstr, 'item'=>get_string('resetglossariesall', 'glossary'), 'error'=>false);
2916  
2917      } else if (!empty($data->reset_glossary_types)) {
2918          $mainentriessql         = "$allentriessql AND g.mainglossary=1";
2919          $secondaryentriessql    = "$allentriessql AND g.mainglossary=0";
2920  
2921          $mainglossariessql      = "$allglossariessql AND g.mainglossary=1";
2922          $secondaryglossariessql = "$allglossariessql AND g.mainglossary=0";
2923  
2924          if (in_array('main', $data->reset_glossary_types)) {
2925              $params[] = 'glossary_entry';
2926              $DB->delete_records_select('comments', "itemid IN ($mainentriessql) AND commentarea=?", $params);
2927              $DB->delete_records_select('glossary_entries', "glossaryid IN ($mainglossariessql)", $params);
2928  
2929              if ($glossaries = $DB->get_records_sql($mainglossariessql, $params)) {
2930                  foreach ($glossaries as $glossaryid=>$unused) {
2931                      if (!$cm = get_coursemodule_from_instance('glossary', $glossaryid)) {
2932                          continue;
2933                      }
2934                      $context = context_module::instance($cm->id);
2935                      $fs->delete_area_files($context->id, 'mod_glossary', 'attachment');
2936  
2937                      //delete ratings
2938                      $ratingdeloptions->contextid = $context->id;
2939                      $rm->delete_ratings($ratingdeloptions);
2940  
2941                      core_tag_tag::delete_instances('mod_glossary', null, $context->id);
2942                  }
2943              }
2944  
2945              // remove all grades from gradebook
2946              if (empty($data->reset_gradebook_grades)) {
2947                  glossary_reset_gradebook($data->courseid, 'main');
2948              }
2949  
2950              $status[] = array('component'=>$componentstr, 'item'=>get_string('resetglossaries', 'glossary').': '.get_string('mainglossary', 'glossary'), 'error'=>false);
2951  
2952          } else if (in_array('secondary', $data->reset_glossary_types)) {
2953              $params[] = 'glossary_entry';
2954              $DB->delete_records_select('comments', "itemid IN ($secondaryentriessql) AND commentarea=?", $params);
2955              $DB->delete_records_select('glossary_entries', "glossaryid IN ($secondaryglossariessql)", $params);
2956              // remove exported source flag from entries in main glossary
2957              $DB->execute("UPDATE {glossary_entries}
2958                               SET sourceglossaryid=0
2959                             WHERE glossaryid IN ($mainglossariessql)", $params);
2960  
2961              if ($glossaries = $DB->get_records_sql($secondaryglossariessql, $params)) {
2962                  foreach ($glossaries as $glossaryid=>$unused) {
2963                      if (!$cm = get_coursemodule_from_instance('glossary', $glossaryid)) {
2964                          continue;
2965                      }
2966                      $context = context_module::instance($cm->id);
2967                      $fs->delete_area_files($context->id, 'mod_glossary', 'attachment');
2968  
2969                      //delete ratings
2970                      $ratingdeloptions->contextid = $context->id;
2971                      $rm->delete_ratings($ratingdeloptions);
2972  
2973                      core_tag_tag::delete_instances('mod_glossary', null, $context->id);
2974                  }
2975              }
2976  
2977              // remove all grades from gradebook
2978              if (empty($data->reset_gradebook_grades)) {
2979                  glossary_reset_gradebook($data->courseid, 'secondary');
2980              }
2981  
2982              $status[] = array('component'=>$componentstr, 'item'=>get_string('resetglossaries', 'glossary').': '.get_string('secondaryglossary', 'glossary'), 'error'=>false);
2983          }
2984      }
2985  
2986      // remove entries by users not enrolled into course
2987      if (!empty($data->reset_glossary_notenrolled)) {
2988          $entriessql = "SELECT e.id, e.userid, e.glossaryid, u.id AS userexists, u.deleted AS userdeleted
2989                           FROM {glossary_entries} e
2990                                JOIN {glossary} g ON e.glossaryid = g.id
2991                                LEFT JOIN {user} u ON e.userid = u.id
2992                          WHERE g.course = ? AND e.userid > 0";
2993  
2994          $course_context = context_course::instance($data->courseid);
2995          $notenrolled = array();
2996          $rs = $DB->get_recordset_sql($entriessql, $params);
2997          if ($rs->valid()) {
2998              foreach ($rs as $entry) {
2999                  if (array_key_exists($entry->userid, $notenrolled) or !$entry->userexists or $entry->userdeleted
3000                    or !is_enrolled($course_context , $entry->userid)) {
3001                      $DB->delete_records('comments', array('commentarea'=>'glossary_entry', 'itemid'=>$entry->id));
3002                      $DB->delete_records('glossary_entries', array('id'=>$entry->id));
3003  
3004                      if ($cm = get_coursemodule_from_instance('glossary', $entry->glossaryid)) {
3005                          $context = context_module::instance($cm->id);
3006                          $fs->delete_area_files($context->id, 'mod_glossary', 'attachment', $entry->id);
3007  
3008                          //delete ratings
3009                          $ratingdeloptions->contextid = $context->id;
3010                          $rm->delete_ratings($ratingdeloptions);
3011                      }
3012                  }
3013              }
3014              $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotenrolled', 'glossary'), 'error'=>false);
3015          }
3016          $rs->close();
3017      }
3018  
3019      // remove all ratings
3020      if (!empty($data->reset_glossary_ratings)) {
3021          //remove ratings
3022          if ($glossaries = $DB->get_records_sql($allglossariessql, $params)) {
3023              foreach ($glossaries as $glossaryid=>$unused) {
3024                  if (!$cm = get_coursemodule_from_instance('glossary', $glossaryid)) {
3025                      continue;
3026                  }
3027                  $context = context_module::instance($cm->id);
3028  
3029                  //delete ratings
3030                  $ratingdeloptions->contextid = $context->id;
3031                  $rm->delete_ratings($ratingdeloptions);
3032              }
3033          }
3034  
3035          // remove all grades from gradebook
3036          if (empty($data->reset_gradebook_grades)) {
3037              glossary_reset_gradebook($data->courseid);
3038          }
3039          $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallratings'), 'error'=>false);
3040      }
3041  
3042      // remove comments
3043      if (!empty($data->reset_glossary_comments)) {
3044          $params[] = 'glossary_entry';
3045          $DB->delete_records_select('comments', "itemid IN ($allentriessql) AND commentarea= ? ", $params);
3046          $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallcomments'), 'error'=>false);
3047      }
3048  
3049      // Remove all the tags.
3050      if (!empty($data->reset_glossary_tags)) {
3051          if ($glossaries = $DB->get_records_sql($allglossariessql, $params)) {
3052              foreach ($glossaries as $glossaryid => $unused) {
3053                  if (!$cm = get_coursemodule_from_instance('glossary', $glossaryid)) {
3054                      continue;
3055                  }
3056  
3057                  $context = context_module::instance($cm->id);
3058                  core_tag_tag::delete_instances('mod_glossary', null, $context->id);
3059              }
3060          }
3061  
3062          $status[] = array('component' => $componentstr, 'item' => get_string('tagsdeleted', 'glossary'), 'error' => false);
3063      }
3064  
3065      /// updating dates - shift may be negative too
3066      if ($data->timeshift) {
3067          // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
3068          // See MDL-9367.
3069          shift_course_mod_dates('glossary', array('assesstimestart', 'assesstimefinish'), $data->timeshift, $data->courseid);
3070          $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
3071      }
3072  
3073      return $status;
3074  }
3075  
3076  /**
3077   * Returns all other caps used in module
3078   * @return array
3079   */
3080  function glossary_get_extra_capabilities() {
3081      return ['moodle/rating:view', 'moodle/rating:viewany', 'moodle/rating:viewall', 'moodle/rating:rate',
3082              'moodle/comment:view', 'moodle/comment:post', 'moodle/comment:delete'];
3083  }
3084  
3085  /**
3086   * @param string $feature FEATURE_xx constant for requested feature
3087   * @return mixed True if module supports feature, null if doesn't know
3088   */
3089  function glossary_supports($feature) {
3090      switch($feature) {
3091          case FEATURE_GROUPS:                  return false;
3092          case FEATURE_GROUPINGS:               return false;
3093          case FEATURE_MOD_INTRO:               return true;
3094          case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
3095          case FEATURE_COMPLETION_HAS_RULES:    return true;
3096          case FEATURE_GRADE_HAS_GRADE:         return true;
3097          case FEATURE_GRADE_OUTCOMES:          return true;
3098          case FEATURE_RATE:                    return true;
3099          case FEATURE_BACKUP_MOODLE2:          return true;
3100          case FEATURE_SHOW_DESCRIPTION:        return true;
3101          case FEATURE_COMMENT:                 return true;
3102  
3103          default: return null;
3104      }
3105  }
3106  
3107  function glossary_extend_navigation($navigation, $course, $module, $cm) {
3108      global $CFG, $DB;
3109  
3110      $displayformat = $DB->get_record('glossary_formats', array('name' => $module->displayformat));
3111      // Get visible tabs for the format and check if the menu needs to be displayed.
3112      $showtabs = glossary_get_visible_tabs($displayformat);
3113  
3114      foreach ($showtabs as $showtabkey => $showtabvalue) {
3115  
3116          switch($showtabvalue) {
3117              case GLOSSARY_STANDARD :
3118                  $navigation->add(get_string('standardview', 'glossary'), new moodle_url('/mod/glossary/view.php',
3119                          array('id' => $cm->id, 'mode' => 'letter')));
3120                  break;
3121              case GLOSSARY_CATEGORY :
3122                  $navigation->add(get_string('categoryview', 'glossary'), new moodle_url('/mod/glossary/view.php',
3123                          array('id' => $cm->id, 'mode' => 'cat')));
3124                  break;
3125              case GLOSSARY_DATE :
3126                  $navigation->add(get_string('dateview', 'glossary'), new moodle_url('/mod/glossary/view.php',
3127                          array('id' => $cm->id, 'mode' => 'date')));
3128                  break;
3129              case GLOSSARY_AUTHOR :
3130                  $navigation->add(get_string('authorview', 'glossary'), new moodle_url('/mod/glossary/view.php',
3131                          array('id' => $cm->id, 'mode' => 'author')));
3132                  break;
3133          }
3134      }
3135  }
3136  
3137  /**
3138   * Adds module specific settings to the settings block
3139   *
3140   * @param settings_navigation $settings The settings navigation object
3141   * @param navigation_node $glossarynode The node to add module settings to
3142   */
3143  function glossary_extend_settings_navigation(settings_navigation $settings, navigation_node $glossarynode) {
3144      global $PAGE, $DB, $CFG, $USER;
3145  
3146      $mode = optional_param('mode', '', PARAM_ALPHA);
3147      $hook = optional_param('hook', 'ALL', PARAM_CLEAN);
3148  
3149      if (has_capability('mod/glossary:import', $PAGE->cm->context)) {
3150          $glossarynode->add(get_string('importentries', 'glossary'), new moodle_url('/mod/glossary/import.php', array('id'=>$PAGE->cm->id)));
3151      }
3152  
3153      if (has_capability('mod/glossary:export', $PAGE->cm->context)) {
3154          $glossarynode->add(get_string('exportentries', 'glossary'), new moodle_url('/mod/glossary/export.php', array('id'=>$PAGE->cm->id, 'mode'=>$mode, 'hook'=>$hook)));
3155      }
3156  
3157      if (has_capability('mod/glossary:approve', $PAGE->cm->context) && ($hiddenentries = $DB->count_records('glossary_entries', array('glossaryid'=>$PAGE->cm->instance, 'approved'=>0)))) {
3158          $glossarynode->add(get_string('waitingapproval', 'glossary'), new moodle_url('/mod/glossary/view.php', array('id'=>$PAGE->cm->id, 'mode'=>'approval')));
3159      }
3160  
3161      if (has_capability('mod/glossary:write', $PAGE->cm->context)) {
3162          $glossarynode->add(get_string('addentry', 'glossary'), new moodle_url('/mod/glossary/edit.php', array('cmid'=>$PAGE->cm->id)));
3163      }
3164  
3165      $glossary = $DB->get_record('glossary', array("id" => $PAGE->cm->instance));
3166  
3167      if (!empty($CFG->enablerssfeeds) && !empty($CFG->glossary_enablerssfeeds) && $glossary->rsstype && $glossary->rssarticles && has_capability('mod/glossary:view', $PAGE->cm->context)) {
3168          require_once("$CFG->libdir/rsslib.php");
3169  
3170          $string = get_string('rsstype', 'glossary');
3171  
3172          $url = new moodle_url(rss_get_url($PAGE->cm->context->id, $USER->id, 'mod_glossary', $glossary->id));
3173          $glossarynode->add($string, $url, settings_navigation::TYPE_SETTING, null, null, new pix_icon('i/rss', ''));
3174      }
3175  }
3176  
3177  /**
3178   * Running addtional permission check on plugin, for example, plugins
3179   * may have switch to turn on/off comments option, this callback will
3180   * affect UI display, not like pluginname_comment_validate only throw
3181   * exceptions.
3182   * Capability check has been done in comment->check_permissions(), we
3183   * don't need to do it again here.
3184   *
3185   * @package  mod_glossary
3186   * @category comment
3187   *
3188   * @param stdClass $comment_param {
3189   *              context  => context the context object
3190   *              courseid => int course id
3191   *              cm       => stdClass course module object
3192   *              commentarea => string comment area
3193   *              itemid      => int itemid
3194   * }
3195   * @return array
3196   */
3197  function glossary_comment_permissions($comment_param) {
3198      return array('post'=>true, 'view'=>true);
3199  }
3200  
3201  /**
3202   * Validate comment parameter before perform other comments actions
3203   *
3204   * @package  mod_glossary
3205   * @category comment
3206   *
3207   * @param stdClass $comment_param {
3208   *              context  => context the context object
3209   *              courseid => int course id
3210   *              cm       => stdClass course module object
3211   *              commentarea => string comment area
3212   *              itemid      => int itemid
3213   * }
3214   * @return boolean
3215   */
3216  function glossary_comment_validate($comment_param) {
3217      global $DB;
3218      // validate comment area
3219      if ($comment_param->commentarea != 'glossary_entry') {
3220          throw new comment_exception('invalidcommentarea');
3221      }
3222      if (!$record = $DB->get_record('glossary_entries', array('id'=>$comment_param->itemid))) {
3223          throw new comment_exception('invalidcommentitemid');
3224      }
3225      if ($record->sourceglossaryid && $record->sourceglossaryid == $comment_param->cm->instance) {
3226          $glossary = $DB->get_record('glossary', array('id'=>$record->sourceglossaryid));
3227      } else {
3228          $glossary = $DB->get_record('glossary', array('id'=>$record->glossaryid));
3229      }
3230      if (!$glossary) {
3231          throw new comment_exception('invalidid', 'data');
3232      }
3233      if (!$course = $DB->get_record('course', array('id'=>$glossary->course))) {
3234          throw new comment_exception('coursemisconf');
3235      }
3236      if (!$cm = get_coursemodule_from_instance('glossary', $glossary->id, $course->id)) {
3237          throw new comment_exception('invalidcoursemodule');
3238      }
3239      $context = context_module::instance($cm->id);
3240  
3241      if ($glossary->defaultapproval and !$record->approved and !has_capability('mod/glossary:approve', $context)) {
3242          throw new comment_exception('notapproved', 'glossary');
3243      }
3244      // validate context id
3245      if ($context->id != $comment_param->context->id) {
3246          throw new comment_exception('invalidcontext');
3247      }
3248      // validation for comment deletion
3249      if (!empty($comment_param->commentid)) {
3250          if ($comment = $DB->get_record('comments', array('id'=>$comment_param->commentid))) {
3251              if ($comment->commentarea != 'glossary_entry') {
3252                  throw new comment_exception('invalidcommentarea');
3253              }
3254              if ($comment->contextid != $comment_param->context->id) {
3255                  throw new comment_exception('invalidcontext');
3256              }
3257              if ($comment->itemid != $comment_param->itemid) {
3258                  throw new comment_exception('invalidcommentitemid');
3259              }
3260          } else {
3261              throw new comment_exception('invalidcommentid');
3262          }
3263      }
3264      return true;
3265  }
3266  
3267  /**
3268   * Return a list of page types
3269   * @param string $pagetype current page type
3270   * @param stdClass $parentcontext Block's parent context
3271   * @param stdClass $currentcontext Current context of block
3272   */
3273  function glossary_page_type_list($pagetype, $parentcontext, $currentcontext) {
3274      $module_pagetype = array(
3275          'mod-glossary-*'=>get_string('page-mod-glossary-x', 'glossary'),
3276          'mod-glossary-view'=>get_string('page-mod-glossary-view', 'glossary'),
3277          'mod-glossary-edit'=>get_string('page-mod-glossary-edit', 'glossary'));
3278      return $module_pagetype;
3279  }
3280  
3281  /**
3282   * Return list of all glossary tabs.
3283   * @throws coding_exception
3284   * @return array
3285   */
3286  function glossary_get_all_tabs() {
3287  
3288      return array (
3289          GLOSSARY_AUTHOR => get_string('authorview', 'glossary'),
3290          GLOSSARY_CATEGORY => get_string('categoryview', 'glossary'),
3291          GLOSSARY_DATE => get_string('dateview', 'glossary')
3292      );
3293  }
3294  
3295  /**
3296   * Set 'showtabs' value for glossary formats
3297   * @param stdClass $glossaryformat record from 'glossary_formats' table
3298   */
3299  function glossary_set_default_visible_tabs($glossaryformat) {
3300      global $DB;
3301  
3302      switch($glossaryformat->name) {
3303          case GLOSSARY_CONTINUOUS:
3304              $showtabs = 'standard,category,date';
3305              break;
3306          case GLOSSARY_DICTIONARY:
3307              $showtabs = 'standard';
3308              // Special code for upgraded instances that already had categories set up
3309              // in this format - enable "category" tab.
3310              // In new instances only 'standard' tab will be visible.
3311              if ($DB->record_exists_sql("SELECT 1
3312                      FROM {glossary} g, {glossary_categories} gc
3313                      WHERE g.id = gc.glossaryid and g.displayformat = ?",
3314                      array(GLOSSARY_DICTIONARY))) {
3315                  $showtabs .= ',category';
3316              }
3317              break;
3318          case GLOSSARY_FULLWITHOUTAUTHOR:
3319              $showtabs = 'standard,category,date';
3320              break;
3321          default:
3322              $showtabs = 'standard,category,date,author';
3323              break;
3324      }
3325  
3326      $DB->set_field('glossary_formats', 'showtabs', $showtabs, array('id' => $glossaryformat->id));
3327      $glossaryformat->showtabs = $showtabs;
3328  }
3329  
3330  /**
3331   * Convert 'showtabs' string to array
3332   * @param stdClass $displayformat record from 'glossary_formats' table
3333   * @return array
3334   */
3335  function glossary_get_visible_tabs($displayformat) {
3336      if (empty($displayformat->showtabs)) {
3337          glossary_set_default_visible_tabs($displayformat);
3338      }
3339      $showtabs = preg_split('/,/', $displayformat->showtabs, -1, PREG_SPLIT_NO_EMPTY);
3340  
3341      return $showtabs;
3342  }
3343  
3344  /**
3345   * Notify that the glossary was viewed.
3346   *
3347   * This will trigger relevant events and activity completion.
3348   *
3349   * @param stdClass $glossary The glossary object.
3350   * @param stdClass $course   The course object.
3351   * @param stdClass $cm       The course module object.
3352   * @param stdClass $context  The context object.
3353   * @param string   $mode     The mode in which the glossary was viewed.
3354   * @since Moodle 3.1
3355   */
3356  function glossary_view($glossary, $course, $cm, $context, $mode) {
3357  
3358      // Completion trigger.
3359      $completion = new completion_info($course);
3360      $completion->set_module_viewed($cm);
3361  
3362      // Trigger the course module viewed event.
3363      $event = \mod_glossary\event\course_module_viewed::create(array(
3364          'objectid' => $glossary->id,
3365          'context' => $context,
3366          'other' => array('mode' => $mode)
3367      ));
3368      $event->add_record_snapshot('course', $course);
3369      $event->add_record_snapshot('course_modules', $cm);
3370      $event->add_record_snapshot('glossary', $glossary);
3371      $event->trigger();
3372  }
3373  
3374  /**
3375   * Notify that a glossary entry was viewed.
3376   *
3377   * This will trigger relevant events.
3378   *
3379   * @param stdClass $entry    The entry object.
3380   * @param stdClass $context  The context object.
3381   * @since Moodle 3.1
3382   */
3383  function glossary_entry_view($entry, $context) {
3384  
3385      // Trigger the entry viewed event.
3386      $event = \mod_glossary\event\entry_viewed::create(array(
3387          'objectid' => $entry->id,
3388          'context' => $context
3389      ));
3390      $event->add_record_snapshot('glossary_entries', $entry);
3391      $event->trigger();
3392  
3393  }
3394  
3395  /**
3396   * Returns the entries of a glossary by letter.
3397   *
3398   * @param  object $glossary The glossary.
3399   * @param  context $context The context of the glossary.
3400   * @param  string $letter The letter, or ALL, or SPECIAL.
3401   * @param  int $from Fetch records from.
3402   * @param  int $limit Number of records to fetch.
3403   * @param  array $options Accepts:
3404   *                        - (bool) includenotapproved. When false, includes the non-approved entries created by
3405   *                          the current user. When true, also includes the ones that the user has the permission to approve.
3406   * @return array The first element being the recordset, the second the number of entries.
3407   * @since Moodle 3.1
3408   */
3409  function glossary_get_entries_by_letter($glossary, $context, $letter, $from, $limit, $options = array()) {
3410  
3411      $qb = new mod_glossary_entry_query_builder($glossary);
3412      if ($letter != 'ALL' && $letter != 'SPECIAL' && core_text::strlen($letter)) {
3413          $qb->filter_by_concept_letter($letter);
3414      }
3415      if ($letter == 'SPECIAL') {
3416          $qb->filter_by_concept_non_letter();
3417      }
3418  
3419      if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3420          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL);
3421      } else {
3422          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF);
3423      }
3424  
3425      $qb->add_field('*', 'entries');
3426      $qb->join_user();
3427      $qb->add_user_fields();
3428      $qb->order_by('concept', 'entries');
3429      $qb->order_by('id', 'entries', 'ASC'); // Sort on ID to avoid random ordering when entries share an ordering value.
3430      $qb->limit($from, $limit);
3431  
3432      // Fetching the entries.
3433      $count = $qb->count_records();
3434      $entries = $qb->get_records();
3435  
3436      return array($entries, $count);
3437  }
3438  
3439  /**
3440   * Returns the entries of a glossary by date.
3441   *
3442   * @param  object $glossary The glossary.
3443   * @param  context $context The context of the glossary.
3444   * @param  string $order The mode of ordering: CREATION or UPDATE.
3445   * @param  string $sort The direction of the ordering: ASC or DESC.
3446   * @param  int $from Fetch records from.
3447   * @param  int $limit Number of records to fetch.
3448   * @param  array $options Accepts:
3449   *                        - (bool) includenotapproved. When false, includes the non-approved entries created by
3450   *                          the current user. When true, also includes the ones that the user has the permission to approve.
3451   * @return array The first element being the recordset, the second the number of entries.
3452   * @since Moodle 3.1
3453   */
3454  function glossary_get_entries_by_date($glossary, $context, $order, $sort, $from, $limit, $options = array()) {
3455  
3456      $qb = new mod_glossary_entry_query_builder($glossary);
3457      if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3458          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL);
3459      } else {
3460          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF);
3461      }
3462  
3463      $qb->add_field('*', 'entries');
3464      $qb->join_user();
3465      $qb->add_user_fields();
3466      $qb->limit($from, $limit);
3467  
3468      if ($order == 'CREATION') {
3469          $qb->order_by('timecreated', 'entries', $sort);
3470      } else {
3471          $qb->order_by('timemodified', 'entries', $sort);
3472      }
3473      $qb->order_by('id', 'entries', $sort); // Sort on ID to avoid random ordering when entries share an ordering value.
3474  
3475      // Fetching the entries.
3476      $count = $qb->count_records();
3477      $entries = $qb->get_records();
3478  
3479      return array($entries, $count);
3480  }
3481  
3482  /**
3483   * Returns the entries of a glossary by category.
3484   *
3485   * @param  object $glossary The glossary.
3486   * @param  context $context The context of the glossary.
3487   * @param  int $categoryid The category ID, or GLOSSARY_SHOW_* constant.
3488   * @param  int $from Fetch records from.
3489   * @param  int $limit Number of records to fetch.
3490   * @param  array $options Accepts:
3491   *                        - (bool) includenotapproved. When false, includes the non-approved entries created by
3492   *                          the current user. When true, also includes the ones that the user has the permission to approve.
3493   * @return array The first element being the recordset, the second the number of entries.
3494   * @since Moodle 3.1
3495   */
3496  function glossary_get_entries_by_category($glossary, $context, $categoryid, $from, $limit, $options = array()) {
3497  
3498      $qb = new mod_glossary_entry_query_builder($glossary);
3499      if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3500          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL);
3501      } else {
3502          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF);
3503      }
3504  
3505      $qb->join_category($categoryid);
3506      $qb->join_user();
3507  
3508      // The first field must be the relationship ID when viewing all categories.
3509      if ($categoryid === GLOSSARY_SHOW_ALL_CATEGORIES) {
3510          $qb->add_field('id', 'entries_categories', 'cid');
3511      }
3512  
3513      $qb->add_field('*', 'entries');
3514      $qb->add_field('categoryid', 'entries_categories');
3515      $qb->add_user_fields();
3516  
3517      if ($categoryid === GLOSSARY_SHOW_ALL_CATEGORIES) {
3518          $qb->add_field('name', 'categories', 'categoryname');
3519          $qb->order_by('name', 'categories');
3520  
3521      } else if ($categoryid === GLOSSARY_SHOW_NOT_CATEGORISED) {
3522          $qb->where('categoryid', 'entries_categories', null);
3523      }
3524  
3525      // Sort on additional fields to avoid random ordering when entries share an ordering value.
3526      $qb->order_by('concept', 'entries');
3527      $qb->order_by('id', 'entries', 'ASC');
3528      $qb->limit($from, $limit);
3529  
3530      // Fetching the entries.
3531      $count = $qb->count_records();
3532      $entries = $qb->get_records();
3533  
3534      return array($entries, $count);
3535  }
3536  
3537  /**
3538   * Returns the entries of a glossary by author.
3539   *
3540   * @param  object $glossary The glossary.
3541   * @param  context $context The context of the glossary.
3542   * @param  string $letter The letter
3543   * @param  string $field The field to search: FIRSTNAME or LASTNAME.
3544   * @param  string $sort The sorting: ASC or DESC.
3545   * @param  int $from Fetch records from.
3546   * @param  int $limit Number of records to fetch.
3547   * @param  array $options Accepts:
3548   *                        - (bool) includenotapproved. When false, includes the non-approved entries created by
3549   *                          the current user. When true, also includes the ones that the user has the permission to approve.
3550   * @return array The first element being the recordset, the second the number of entries.
3551   * @since Moodle 3.1
3552   */
3553  function glossary_get_entries_by_author($glossary, $context, $letter, $field, $sort, $from, $limit, $options = array()) {
3554  
3555      $firstnamefirst = $field === 'FIRSTNAME';
3556      $qb = new mod_glossary_entry_query_builder($glossary);
3557      if ($letter != 'ALL' && $letter != 'SPECIAL' && core_text::strlen($letter)) {
3558          $qb->filter_by_author_letter($letter, $firstnamefirst);
3559      }
3560      if ($letter == 'SPECIAL') {
3561          $qb->filter_by_author_non_letter($firstnamefirst);
3562      }
3563  
3564      if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3565          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL);
3566      } else {
3567          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF);
3568      }
3569  
3570      $qb->add_field('*', 'entries');
3571      $qb->join_user(true);
3572      $qb->add_user_fields();
3573      $qb->order_by_author($firstnamefirst, $sort);
3574      $qb->order_by('concept', 'entries');
3575      $qb->order_by('id', 'entries', 'ASC'); // Sort on ID to avoid random ordering when entries share an ordering value.
3576      $qb->limit($from, $limit);
3577  
3578      // Fetching the entries.
3579      $count = $qb->count_records();
3580      $entries = $qb->get_records();
3581  
3582      return array($entries, $count);
3583  }
3584  
3585  /**
3586   * Returns the entries of a glossary by category.
3587   *
3588   * @param  object $glossary The glossary.
3589   * @param  context $context The context of the glossary.
3590   * @param  int $authorid The author ID.
3591   * @param  string $order The mode of ordering: CONCEPT, CREATION or UPDATE.
3592   * @param  string $sort The direction of the ordering: ASC or DESC.
3593   * @param  int $from Fetch records from.
3594   * @param  int $limit Number of records to fetch.
3595   * @param  array $options Accepts:
3596   *                        - (bool) includenotapproved. When false, includes the non-approved entries created by
3597   *                          the current user. When true, also includes the ones that the user has the permission to approve.
3598   * @return array The first element being the recordset, the second the number of entries.
3599   * @since Moodle 3.1
3600   */
3601  function glossary_get_entries_by_author_id($glossary, $context, $authorid, $order, $sort, $from, $limit, $options = array()) {
3602  
3603      $qb = new mod_glossary_entry_query_builder($glossary);
3604      if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3605          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL);
3606      } else {
3607          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF);
3608      }
3609  
3610      $qb->add_field('*', 'entries');
3611      $qb->join_user(true);
3612      $qb->add_user_fields();
3613      $qb->where('id', 'user', $authorid);
3614  
3615      if ($order == 'CREATION') {
3616          $qb->order_by('timecreated', 'entries', $sort);
3617      } else if ($order == 'UPDATE') {
3618          $qb->order_by('timemodified', 'entries', $sort);
3619      } else {
3620          $qb->order_by('concept', 'entries', $sort);
3621      }
3622      $qb->order_by('id', 'entries', $sort); // Sort on ID to avoid random ordering when entries share an ordering value.
3623  
3624      $qb->limit($from, $limit);
3625  
3626      // Fetching the entries.
3627      $count = $qb->count_records();
3628      $entries = $qb->get_records();
3629  
3630      return array($entries, $count);
3631  }
3632  
3633  /**
3634   * Returns the authors in a glossary
3635   *
3636   * @param  object $glossary The glossary.
3637   * @param  context $context The context of the glossary.
3638   * @param  int $limit Number of records to fetch.
3639   * @param  int $from Fetch records from.
3640   * @param  array $options Accepts:
3641   *                        - (bool) includenotapproved. When false, includes self even if all of their entries require approval.
3642   *                          When true, also includes authors only having entries pending approval.
3643   * @return array The first element being the recordset, the second the number of entries.
3644   * @since Moodle 3.1
3645   */
3646  function glossary_get_authors($glossary, $context, $limit, $from, $options = array()) {
3647      global $DB, $USER;
3648  
3649      $params = array();
3650      $userfieldsapi = \core_user\fields::for_userpic();
3651      $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
3652  
3653      $approvedsql = '(ge.approved <> 0 OR ge.userid = :myid)';
3654      $params['myid'] = $USER->id;
3655      if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3656          $approvedsql = '1 = 1';
3657      }
3658  
3659      $sqlselectcount = "SELECT COUNT(DISTINCT(u.id))";
3660      $sqlselect = "SELECT DISTINCT(u.id) AS userId, $userfields";
3661      $sql = "  FROM {user} u
3662                JOIN {glossary_entries} ge
3663                  ON ge.userid = u.id
3664                 AND (ge.glossaryid = :gid1 OR ge.sourceglossaryid = :gid2)
3665                 AND $approvedsql";
3666      $ordersql = " ORDER BY u.lastname, u.firstname";
3667  
3668      $params['gid1'] = $glossary->id;
3669      $params['gid2'] = $glossary->id;
3670  
3671      $count = $DB->count_records_sql($sqlselectcount . $sql, $params);
3672      $users = $DB->get_recordset_sql($sqlselect . $sql . $ordersql, $params, $from, $limit);
3673  
3674      return array($users, $count);
3675  }
3676  
3677  /**
3678   * Returns the categories of a glossary.
3679   *
3680   * @param  object $glossary The glossary.
3681   * @param  int $from Fetch records from.
3682   * @param  int $limit Number of records to fetch.
3683   * @return array The first element being the recordset, the second the number of entries.
3684   * @since Moodle 3.1
3685   */
3686  function glossary_get_categories($glossary, $from, $limit) {
3687      global $DB;
3688  
3689      $count = $DB->count_records('glossary_categories', array('glossaryid' => $glossary->id));
3690      $categories = $DB->get_recordset('glossary_categories', array('glossaryid' => $glossary->id), 'name ASC', '*', $from, $limit);
3691  
3692      return array($categories, $count);
3693  }
3694  
3695  /**
3696   * Get the SQL where clause for searching terms.
3697   *
3698   * Note that this does not handle invalid or too short terms.
3699   *
3700   * @param array   $terms      Array of terms.
3701   * @param bool    $fullsearch Whether or not full search should be enabled.
3702   * @param int     $glossaryid The ID of a glossary to reduce the search results.
3703   * @return array The first element being the where clause, the second array of parameters.
3704   * @since Moodle 3.1
3705   */
3706  function glossary_get_search_terms_sql(array $terms, $fullsearch = true, $glossaryid = null) {
3707      global $DB;
3708      static $i = 0;
3709  
3710      if ($DB->sql_regex_supported()) {
3711          $regexp = $DB->sql_regex(true);
3712          $notregexp = $DB->sql_regex(false);
3713      }
3714  
3715      $params = array();
3716      $conditions = array();
3717  
3718      foreach ($terms as $searchterm) {
3719          $i++;
3720  
3721          $not = false; // Initially we aren't going to perform NOT LIKE searches, only MSSQL and Oracle
3722                        // will use it to simulate the "-" operator with LIKE clause.
3723  
3724          if (empty($fullsearch)) {
3725              // With fullsearch disabled, look only within concepts and aliases.
3726              $concat = $DB->sql_concat('ge.concept', "' '", "COALESCE(al.alias, :emptychar{$i})");
3727          } else {
3728              // With fullsearch enabled, look also within definitions.
3729              $concat = $DB->sql_concat('ge.concept', "' '", 'ge.definition', "' '", "COALESCE(al.alias, :emptychar{$i})");
3730          }
3731          $params['emptychar' . $i] = '';
3732  
3733          // Under Oracle and MSSQL, trim the + and - operators and perform simpler LIKE (or NOT LIKE) queries.
3734          if (!$DB->sql_regex_supported()) {
3735              if (substr($searchterm, 0, 1) === '-') {
3736                  $not = true;
3737              }
3738              $searchterm = trim($searchterm, '+-');
3739          }
3740  
3741          if (substr($searchterm, 0, 1) === '+') {
3742              $searchterm = trim($searchterm, '+-');
3743              $conditions[] = "$concat $regexp :searchterm{$i}";
3744              $params['searchterm' . $i] = '(^|[^a-zA-Z0-9])' . preg_quote($searchterm, '|') . '([^a-zA-Z0-9]|$)';
3745  
3746          } else if (substr($searchterm, 0, 1) === "-") {
3747              $searchterm = trim($searchterm, '+-');
3748              $conditions[] = "$concat $notregexp :searchterm{$i}";
3749              $params['searchterm' . $i] = '(^|[^a-zA-Z0-9])' . preg_quote($searchterm, '|') . '([^a-zA-Z0-9]|$)';
3750  
3751          } else {
3752              $conditions[] = $DB->sql_like($concat, ":searchterm{$i}", false, true, $not);
3753              $params['searchterm' . $i] = '%' . $DB->sql_like_escape($searchterm) . '%';
3754          }
3755      }
3756  
3757      // Reduce the search results by restricting it to one glossary.
3758      if (isset($glossaryid)) {
3759          $conditions[] = 'ge.glossaryid = :glossaryid';
3760          $params['glossaryid'] = $glossaryid;
3761      }
3762  
3763      // When there are no conditions we add a negative one to ensure that we don't return anything.
3764      if (empty($conditions)) {
3765          $conditions[] = '1 = 2';
3766      }
3767  
3768      $where = implode(' AND ', $conditions);
3769      return array($where, $params);
3770  }
3771  
3772  
3773  /**
3774   * Returns the entries of a glossary by search.
3775   *
3776   * @param  object $glossary The glossary.
3777   * @param  context $context The context of the glossary.
3778   * @param  string $query The search query.
3779   * @param  bool $fullsearch Whether or not full search is required.
3780   * @param  string $order The mode of ordering: CONCEPT, CREATION or UPDATE.
3781   * @param  string $sort The direction of the ordering: ASC or DESC.
3782   * @param  int $from Fetch records from.
3783   * @param  int $limit Number of records to fetch.
3784   * @param  array $options Accepts:
3785   *                        - (bool) includenotapproved. When false, includes the non-approved entries created by
3786   *                          the current user. When true, also includes the ones that the user has the permission to approve.
3787   * @return array The first element being the array of results, the second the number of entries.
3788   * @since Moodle 3.1
3789   */
3790  function glossary_get_entries_by_search($glossary, $context, $query, $fullsearch, $order, $sort, $from, $limit,
3791                                          $options = array()) {
3792      global $DB, $USER;
3793  
3794      // Clean terms.
3795      $terms = explode(' ', $query);
3796      foreach ($terms as $key => $term) {
3797          if (strlen(trim($term, '+-')) < 1) {
3798              unset($terms[$key]);
3799          }
3800      }
3801  
3802      list($searchcond, $params) = glossary_get_search_terms_sql($terms, $fullsearch, $glossary->id);
3803  
3804      $userfieldsapi = \core_user\fields::for_userpic();
3805      $userfields = $userfieldsapi->get_sql('u', false, 'userdata', 'userdataid', false)->selects;
3806  
3807      // Need one inner view here to avoid distinct + text.
3808      $sqlwrapheader = 'SELECT ge.*, ge.concept AS glossarypivot, ' . $userfields . '
3809                          FROM {glossary_entries} ge
3810                          LEFT JOIN {user} u ON u.id = ge.userid
3811                          JOIN ( ';
3812      $sqlwrapfooter = ' ) gei ON (ge.id = gei.id)';
3813      $sqlselect  = "SELECT DISTINCT ge.id";
3814      $sqlfrom    = "FROM {glossary_entries} ge
3815                     LEFT JOIN {glossary_alias} al ON al.entryid = ge.id";
3816  
3817      if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3818          $approvedsql = '';
3819      } else {
3820          $approvedsql = 'AND (ge.approved <> 0 OR ge.userid = :myid)';
3821          $params['myid'] = $USER->id;
3822      }
3823  
3824      if ($order == 'CREATION') {
3825          $sqlorderby = "ORDER BY ge.timecreated $sort";
3826      } else if ($order == 'UPDATE') {
3827          $sqlorderby = "ORDER BY ge.timemodified $sort";
3828      } else {
3829          $sqlorderby = "ORDER BY ge.concept $sort";
3830      }
3831      $sqlorderby .= " , ge.id ASC"; // Sort on ID to avoid random ordering when entries share an ordering value.
3832  
3833      $sqlwhere = "WHERE ($searchcond) $approvedsql";
3834  
3835      // Fetching the entries.
3836      $count = $DB->count_records_sql("SELECT COUNT(DISTINCT(ge.id)) $sqlfrom $sqlwhere", $params);
3837  
3838      $query = "$sqlwrapheader $sqlselect $sqlfrom $sqlwhere $sqlwrapfooter $sqlorderby";
3839      $entries = $DB->get_records_sql($query, $params, $from, $limit);
3840  
3841      return array($entries, $count);
3842  }
3843  
3844  /**
3845   * Returns the entries of a glossary by term.
3846   *
3847   * @param  object $glossary The glossary.
3848   * @param  context $context The context of the glossary.
3849   * @param  string $term The term we are searching for, a concept or alias.
3850   * @param  int $from Fetch records from.
3851   * @param  int $limit Number of records to fetch.
3852   * @param  array $options Accepts:
3853   *                        - (bool) includenotapproved. When false, includes the non-approved entries created by
3854   *                          the current user. When true, also includes the ones that the user has the permission to approve.
3855   * @return array The first element being the recordset, the second the number of entries.
3856   * @since Moodle 3.1
3857   */
3858  function glossary_get_entries_by_term($glossary, $context, $term, $from, $limit, $options = array()) {
3859  
3860      // Build the query.
3861      $qb = new mod_glossary_entry_query_builder($glossary);
3862      if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3863          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL);
3864      } else {
3865          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF);
3866      }
3867  
3868      $qb->add_field('*', 'entries');
3869      $qb->join_alias();
3870      $qb->join_user();
3871      $qb->add_user_fields();
3872      $qb->filter_by_term($term);
3873  
3874      $qb->order_by('concept', 'entries');
3875      $qb->order_by('id', 'entries');     // Sort on ID to avoid random ordering when entries share an ordering value.
3876      $qb->limit($from, $limit);
3877  
3878      // Fetching the entries.
3879      $count = $qb->count_records();
3880      $entries = $qb->get_records();
3881  
3882      return array($entries, $count);
3883  }
3884  
3885  /**
3886   * Returns the entries to be approved.
3887   *
3888   * @param  object $glossary The glossary.
3889   * @param  context $context The context of the glossary.
3890   * @param  string $letter The letter, or ALL, or SPECIAL.
3891   * @param  string $order The mode of ordering: CONCEPT, CREATION or UPDATE.
3892   * @param  string $sort The direction of the ordering: ASC or DESC.
3893   * @param  int $from Fetch records from.
3894   * @param  int $limit Number of records to fetch.
3895   * @return array The first element being the recordset, the second the number of entries.
3896   * @since Moodle 3.1
3897   */
3898  function glossary_get_entries_to_approve($glossary, $context, $letter, $order, $sort, $from, $limit) {
3899  
3900      $qb = new mod_glossary_entry_query_builder($glossary);
3901      if ($letter != 'ALL' && $letter != 'SPECIAL' && core_text::strlen($letter)) {
3902          $qb->filter_by_concept_letter($letter);
3903      }
3904      if ($letter == 'SPECIAL') {
3905          $qb->filter_by_concept_non_letter();
3906      }
3907  
3908      $qb->add_field('*', 'entries');
3909      $qb->join_user();
3910      $qb->add_user_fields();
3911      $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ONLY);
3912      if ($order == 'CREATION') {
3913          $qb->order_by('timecreated', 'entries', $sort);
3914      } else if ($order == 'UPDATE') {
3915          $qb->order_by('timemodified', 'entries', $sort);
3916      } else {
3917          $qb->order_by('concept', 'entries', $sort);
3918      }
3919      $qb->order_by('id', 'entries', $sort); // Sort on ID to avoid random ordering when entries share an ordering value.
3920      $qb->limit($from, $limit);
3921  
3922      // Fetching the entries.
3923      $count = $qb->count_records();
3924      $entries = $qb->get_records();
3925  
3926      return array($entries, $count);
3927  }
3928  
3929  /**
3930   * Fetch an entry.
3931   *
3932   * @param  int $id The entry ID.
3933   * @return object|false The entry, or false when not found.
3934   * @since Moodle 3.1
3935   */
3936  function glossary_get_entry_by_id($id) {
3937  
3938      // Build the query.
3939      $qb = new mod_glossary_entry_query_builder();
3940      $qb->add_field('*', 'entries');
3941      $qb->join_user();
3942      $qb->add_user_fields();
3943      $qb->where('id', 'entries', $id);
3944  
3945      // Fetching the entries.
3946      $entries = $qb->get_records();
3947      if (empty($entries)) {
3948          return false;
3949      }
3950      return array_pop($entries);
3951  }
3952  
3953  /**
3954   * Checks if the current user can see the glossary entry.
3955   *
3956   * @since Moodle 3.1
3957   * @param stdClass $entry
3958   * @param cm_info  $cminfo
3959   * @return bool
3960   */
3961  function glossary_can_view_entry($entry, $cminfo) {
3962      global $USER;
3963  
3964      $cm = $cminfo->get_course_module_record();
3965      $context = \context_module::instance($cm->id);
3966  
3967      // Recheck uservisible although it should have already been checked in core_search.
3968      if ($cminfo->uservisible === false) {
3969          return false;
3970      }
3971  
3972      // Check approval.
3973      if (empty($entry->approved) && $entry->userid != $USER->id && !has_capability('mod/glossary:approve', $context)) {
3974          return false;
3975      }
3976  
3977      return true;
3978  }
3979  
3980  /**
3981   * Check if a concept exists in a glossary.
3982   *
3983   * @param  stdClass $glossary glossary object
3984   * @param  string $concept the concept to check
3985   * @return bool true if exists
3986   * @since  Moodle 3.2
3987   */
3988  function glossary_concept_exists($glossary, $concept) {
3989      global $DB;
3990  
3991      return $DB->record_exists_select('glossary_entries', 'glossaryid = :glossaryid AND LOWER(concept) = :concept',
3992          array(
3993              'glossaryid' => $glossary->id,
3994              'concept'    => core_text::strtolower($concept)
3995          )
3996      );
3997  }
3998  
3999  /**
4000   * Return the editor and attachment options when editing a glossary entry
4001   *
4002   * @param  stdClass $course  course object
4003   * @param  stdClass $context context object
4004   * @param  stdClass $entry   entry object
4005   * @return array array containing the editor and attachment options
4006   * @since  Moodle 3.2
4007   */
4008  function glossary_get_editor_and_attachment_options($course, $context, $entry) {
4009      $maxfiles = 99;                // TODO: add some setting.
4010      $maxbytes = $course->maxbytes; // TODO: add some setting.
4011  
4012      $definitionoptions = array('trusttext' => true, 'maxfiles' => $maxfiles, 'maxbytes' => $maxbytes, 'context' => $context,
4013          'subdirs' => file_area_contains_subdirs($context, 'mod_glossary', 'entry', $entry->id));
4014      $attachmentoptions = array('subdirs' => false, 'maxfiles' => $maxfiles, 'maxbytes' => $maxbytes);
4015      return array($definitionoptions, $attachmentoptions);
4016  }
4017  
4018  /**
4019   * Creates or updates a glossary entry
4020   *
4021   * @param  stdClass $entry entry data
4022   * @param  stdClass $course course object
4023   * @param  stdClass $cm course module object
4024   * @param  stdClass $glossary glossary object
4025   * @param  stdClass $context context object
4026   * @return stdClass the complete new or updated entry
4027   * @since  Moodle 3.2
4028   */
4029  function glossary_edit_entry($entry, $course, $cm, $glossary, $context) {
4030      global $DB, $USER;
4031  
4032      list($definitionoptions, $attachmentoptions) = glossary_get_editor_and_attachment_options($course, $context, $entry);
4033  
4034      $timenow = time();
4035  
4036      $categories = empty($entry->categories) ? array() : $entry->categories;
4037      unset($entry->categories);
4038      $aliases = trim($entry->aliases);
4039      unset($entry->aliases);
4040  
4041      if (empty($entry->id)) {
4042          $entry->glossaryid       = $glossary->id;
4043          $entry->timecreated      = $timenow;
4044          $entry->userid           = $USER->id;
4045          $entry->timecreated      = $timenow;
4046          $entry->sourceglossaryid = 0;
4047          $entry->teacherentry     = has_capability('mod/glossary:manageentries', $context);
4048          $isnewentry              = true;
4049      } else {
4050          $isnewentry              = false;
4051      }
4052  
4053      $entry->concept          = trim($entry->concept);
4054      $entry->definition       = '';          // Updated later.
4055      $entry->definitionformat = FORMAT_HTML; // Updated later.
4056      $entry->definitiontrust  = 0;           // Updated later.
4057      $entry->timemodified     = $timenow;
4058      $entry->approved         = 0;
4059      $entry->usedynalink      = isset($entry->usedynalink) ? $entry->usedynalink : 0;
4060      $entry->casesensitive    = isset($entry->casesensitive) ? $entry->casesensitive : 0;
4061      $entry->fullmatch        = isset($entry->fullmatch) ? $entry->fullmatch : 0;
4062  
4063      if ($glossary->defaultapproval or has_capability('mod/glossary:approve', $context)) {
4064          $entry->approved = 1;
4065      }
4066  
4067      if ($isnewentry) {
4068          // Add new entry.
4069          $entry->id = $DB->insert_record('glossary_entries', $entry);
4070      } else {
4071          // Update existing entry.
4072          $DB->update_record('glossary_entries', $entry);
4073      }
4074  
4075      // Save and relink embedded images and save attachments.
4076      if (!empty($entry->definition_editor)) {
4077          $entry = file_postupdate_standard_editor($entry, 'definition', $definitionoptions, $context, 'mod_glossary', 'entry',
4078              $entry->id);
4079      }
4080      if (!empty($entry->attachment_filemanager)) {
4081          $entry = file_postupdate_standard_filemanager($entry, 'attachment', $attachmentoptions, $context, 'mod_glossary',
4082              'attachment', $entry->id);
4083      }
4084  
4085      // Store the updated value values.
4086      $DB->update_record('glossary_entries', $entry);
4087  
4088      // Refetch complete entry.
4089      $entry = $DB->get_record('glossary_entries', array('id' => $entry->id));
4090  
4091      // Update entry categories.
4092      $DB->delete_records('glossary_entries_categories', array('entryid' => $entry->id));
4093      // TODO: this deletes cats from both both main and secondary glossary :-(.
4094      if (!empty($categories) and array_search(0, $categories) === false) {
4095          foreach ($categories as $catid) {
4096              $newcategory = new stdClass();
4097              $newcategory->entryid    = $entry->id;
4098              $newcategory->categoryid = $catid;
4099              $DB->insert_record('glossary_entries_categories', $newcategory, false);
4100          }
4101      }
4102  
4103      // Update aliases.
4104      $DB->delete_records('glossary_alias', array('entryid' => $entry->id));
4105      if ($aliases !== '') {
4106          $aliases = explode("\n", $aliases);
4107          foreach ($aliases as $alias) {
4108              $alias = trim($alias);
4109              if ($alias !== '') {
4110                  $newalias = new stdClass();
4111                  $newalias->entryid = $entry->id;
4112                  $newalias->alias   = $alias;
4113                  $DB->insert_record('glossary_alias', $newalias, false);
4114              }
4115          }
4116      }
4117  
4118      // Trigger event and update completion (if entry was created).
4119      $eventparams = array(
4120          'context' => $context,
4121          'objectid' => $entry->id,
4122          'other' => array('concept' => $entry->concept)
4123      );
4124      if ($isnewentry) {
4125          $event = \mod_glossary\event\entry_created::create($eventparams);
4126      } else {
4127          $event = \mod_glossary\event\entry_updated::create($eventparams);
4128      }
4129      $event->add_record_snapshot('glossary_entries', $entry);
4130      $event->trigger();
4131      if ($isnewentry) {
4132          // Update completion state.
4133          $completion = new completion_info($course);
4134          if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC && $glossary->completionentries && $entry->approved) {
4135              $completion->update_state($cm, COMPLETION_COMPLETE);
4136          }
4137      }
4138  
4139      // Reset caches.
4140      if ($isnewentry) {
4141          if ($entry->usedynalink and $entry->approved) {
4142              \mod_glossary\local\concept_cache::reset_glossary($glossary);
4143          }
4144      } else {
4145          // So many things may affect the linking, let's just purge the cache always on edit.
4146          \mod_glossary\local\concept_cache::reset_glossary($glossary);
4147      }
4148      return $entry;
4149  }
4150  
4151  /**
4152   * Check if the module has any update that affects the current user since a given time.
4153   *
4154   * @param  cm_info $cm course module data
4155   * @param  int $from the time to check updates from
4156   * @param  array $filter  if we need to check only specific updates
4157   * @return stdClass an object with the different type of areas indicating if they were updated or not
4158   * @since Moodle 3.2
4159   */
4160  function glossary_check_updates_since(cm_info $cm, $from, $filter = array()) {
4161      global $DB;
4162  
4163      $updates = course_check_module_updates_since($cm, $from, array('attachment', 'entry'), $filter);
4164  
4165      $updates->entries = (object) array('updated' => false);
4166      $select = 'glossaryid = :id AND (timecreated > :since1 OR timemodified > :since2)';
4167      $params = array('id' => $cm->instance, 'since1' => $from, 'since2' => $from);
4168      if (!has_capability('mod/glossary:approve', $cm->context)) {
4169          $select .= ' AND approved = 1';
4170      }
4171  
4172      $entries = $DB->get_records_select('glossary_entries', $select, $params, '', 'id');
4173      if (!empty($entries)) {
4174          $updates->entries->updated = true;
4175          $updates->entries->itemids = array_keys($entries);
4176      }
4177  
4178      return $updates;
4179  }
4180  
4181  /**
4182   * Get icon mapping for font-awesome.
4183   *
4184   * @return array
4185   */
4186  function mod_glossary_get_fontawesome_icon_map() {
4187      return [
4188          'mod_glossary:export' => 'fa-download',
4189          'mod_glossary:minus' => 'fa-minus'
4190      ];
4191  }
4192  
4193  /**
4194   * This function receives a calendar event and returns the action associated with it, or null if there is none.
4195   *
4196   * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
4197   * is not displayed on the block.
4198   *
4199   * @param calendar_event $event
4200   * @param \core_calendar\action_factory $factory
4201   * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
4202   * @return \core_calendar\local\event\entities\action_interface|null
4203   */
4204  function mod_glossary_core_calendar_provide_event_action(calendar_event $event,
4205                                                           \core_calendar\action_factory $factory,
4206                                                           int $userid = 0) {
4207      global $USER;
4208  
4209      if (!$userid) {
4210          $userid = $USER->id;
4211      }
4212  
4213      $cm = get_fast_modinfo($event->courseid, $userid)->instances['glossary'][$event->instance];
4214  
4215      if (!$cm->uservisible) {
4216          // The module is not visible to the user for any reason.
4217          return null;
4218      }
4219  
4220      $completion = new \completion_info($cm->get_course());
4221  
4222      $completiondata = $completion->get_data($cm, false, $userid);
4223  
4224      if ($completiondata->completionstate != COMPLETION_INCOMPLETE) {
4225          return null;
4226      }
4227  
4228      return $factory->create_instance(
4229          get_string('view'),
4230          new \moodle_url('/mod/glossary/view.php', ['id' => $cm->id]),
4231          1,
4232          true
4233      );
4234  }
4235  
4236  /**
4237   * Add a get_coursemodule_info function in case any glossary type wants to add 'extra' information
4238   * for the course (see resource).
4239   *
4240   * Given a course_module object, this function returns any "extra" information that may be needed
4241   * when printing this activity in a course listing.  See get_array_of_activities() in course/lib.php.
4242   *
4243   * @param stdClass $coursemodule The coursemodule object (record).
4244   * @return cached_cm_info An object on information that the courses
4245   *                        will know about (most noticeably, an icon).
4246   */
4247  function glossary_get_coursemodule_info($coursemodule) {
4248      global $DB;
4249  
4250      $dbparams = ['id' => $coursemodule->instance];
4251      $fields = 'id, name, intro, introformat, completionentries';
4252      if (!$glossary = $DB->get_record('glossary', $dbparams, $fields)) {
4253          return false;
4254      }
4255  
4256      $result = new cached_cm_info();
4257      $result->name = $glossary->name;
4258  
4259      if ($coursemodule->showdescription) {
4260          // Convert intro to html. Do not filter cached version, filters run at display time.
4261          $result->content = format_module_intro('glossary', $glossary, $coursemodule->id, false);
4262      }
4263  
4264      // Populate the custom completion rules as key => value pairs, but only if the completion mode is 'automatic'.
4265      if ($coursemodule->completion == COMPLETION_TRACKING_AUTOMATIC) {
4266          $result->customdata['customcompletionrules']['completionentries'] = $glossary->completionentries;
4267      }
4268  
4269      return $result;
4270  }
4271  
4272  /**
4273   * Callback which returns human-readable strings describing the active completion custom rules for the module instance.
4274   *
4275   * @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
4276   * @return array $descriptions the array of descriptions for the custom rules.
4277   */
4278  function mod_glossary_get_completion_active_rule_descriptions($cm) {
4279      // Values will be present in cm_info, and we assume these are up to date.
4280      if (empty($cm->customdata['customcompletionrules'])
4281          || $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
4282          return [];
4283      }
4284  
4285      $descriptions = [];
4286      foreach ($cm->customdata['customcompletionrules'] as $key => $val) {
4287          switch ($key) {
4288              case 'completionentries':
4289                  if (!empty($val)) {
4290                      $descriptions[] = get_string('completionentriesdesc', 'glossary', $val);
4291                  }
4292                  break;
4293              default:
4294                  break;
4295          }
4296      }
4297      return $descriptions;
4298  }
4299  
4300  /**
4301   * Checks if the current user can delete the given glossary entry.
4302   *
4303   * @since Moodle 3.10
4304   * @param stdClass $entry the entry database object
4305   * @param stdClass $glossary the glossary database object
4306   * @param stdClass $context the glossary context
4307   * @param bool $return Whether to return a boolean value or stop the execution (exception)
4308   * @return bool if the user can delete the entry
4309   * @throws moodle_exception
4310   */
4311  function mod_glossary_can_delete_entry($entry, $glossary, $context, $return = true) {
4312      global $USER, $CFG;
4313  
4314      $manageentries = has_capability('mod/glossary:manageentries', $context);
4315  
4316      if ($manageentries) {   // Users with the capability will always be able to delete entries.
4317          return true;
4318      }
4319  
4320      if ($entry->userid != $USER->id) { // Guest id is never matched, no need for special check here.
4321          if ($return) {
4322              return false;
4323          }
4324          throw new moodle_exception('nopermissiontodelentry');
4325      }
4326  
4327      $ineditperiod = ((time() - $entry->timecreated < $CFG->maxeditingtime) || $glossary->editalways);
4328  
4329      if (!$ineditperiod) {
4330          if ($return) {
4331              return false;
4332          }
4333          throw new moodle_exception('errdeltimeexpired', 'glossary');
4334      }
4335  
4336      return true;
4337  }
4338  
4339  /**
4340   * Deletes the given entry, this function does not perform capabilities/permission checks.
4341   *
4342   * @since Moodle 3.10
4343   * @param stdClass $entry the entry database object
4344   * @param stdClass $glossary the glossary database object
4345   * @param stdClass $cm the glossary course moduule object
4346   * @param stdClass $context the glossary context
4347   * @param stdClass $course the glossary course
4348   * @param string $hook the hook, usually type of filtering, value
4349   * @param string $prevmode the previsualisation mode
4350   * @throws moodle_exception
4351   */
4352  function mod_glossary_delete_entry($entry, $glossary, $cm, $context, $course, $hook = '', $prevmode = '') {
4353      global $CFG, $DB;
4354  
4355      $origentry = fullclone($entry);
4356  
4357      // If it is an imported entry, just delete the relation.
4358      if ($entry->sourceglossaryid) {
4359          if (!$newcm = get_coursemodule_from_instance('glossary', $entry->sourceglossaryid)) {
4360              print_error('invalidcoursemodule');
4361          }
4362          $newcontext = context_module::instance($newcm->id);
4363  
4364          $entry->glossaryid       = $entry->sourceglossaryid;
4365          $entry->sourceglossaryid = 0;
4366          $DB->update_record('glossary_entries', $entry);
4367  
4368          // Move attachments too.
4369          $fs = get_file_storage();
4370  
4371          if ($oldfiles = $fs->get_area_files($context->id, 'mod_glossary', 'attachment', $entry->id)) {
4372              foreach ($oldfiles as $oldfile) {
4373                  $filerecord = new stdClass();
4374                  $filerecord->contextid = $newcontext->id;
4375                  $fs->create_file_from_storedfile($filerecord, $oldfile);
4376              }
4377              $fs->delete_area_files($context->id, 'mod_glossary', 'attachment', $entry->id);
4378              $entry->attachment = '1';
4379          } else {
4380              $entry->attachment = '0';
4381          }
4382          $DB->update_record('glossary_entries', $entry);
4383  
4384      } else {
4385          $fs = get_file_storage();
4386          $fs->delete_area_files($context->id, 'mod_glossary', 'attachment', $entry->id);
4387          $DB->delete_records("comments",
4388              ['itemid' => $entry->id, 'commentarea' => 'glossary_entry', 'contextid' => $context->id]);
4389          $DB->delete_records("glossary_alias", ["entryid" => $entry->id]);
4390          $DB->delete_records("glossary_entries", ["id" => $entry->id]);
4391  
4392          // Update completion state.
4393          $completion = new completion_info($course);
4394          if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC && $glossary->completionentries) {
4395              $completion->update_state($cm, COMPLETION_INCOMPLETE, $entry->userid);
4396          }
4397  
4398          // Delete glossary entry ratings.
4399          require_once($CFG->dirroot.'/rating/lib.php');
4400          $delopt = new stdClass;
4401          $delopt->contextid = $context->id;
4402          $delopt->component = 'mod_glossary';
4403          $delopt->ratingarea = 'entry';
4404          $delopt->itemid = $entry->id;
4405          $rm = new rating_manager();
4406          $rm->delete_ratings($delopt);
4407      }
4408  
4409      // Delete cached RSS feeds.
4410      if (!empty($CFG->enablerssfeeds)) {
4411          require_once($CFG->dirroot . '/mod/glossary/rsslib.php');
4412          glossary_rss_delete_file($glossary);
4413      }
4414  
4415      core_tag_tag::remove_all_item_tags('mod_glossary', 'glossary_entries', $origentry->id);
4416  
4417      $event = \mod_glossary\event\entry_deleted::create(
4418          [
4419              'context' => $context,
4420              'objectid' => $origentry->id,
4421              'other' => [
4422                  'mode' => $prevmode,
4423                  'hook' => $hook,
4424                  'concept' => $origentry->concept
4425              ]
4426          ]
4427      );
4428      $event->add_record_snapshot('glossary_entries', $origentry);
4429      $event->trigger();
4430  
4431      // Reset caches.
4432      if ($entry->usedynalink and $entry->approved) {
4433          \mod_glossary\local\concept_cache::reset_glossary($glossary);
4434      }
4435  }
4436  
4437  /**
4438   * Checks if the current user can update the given glossary entry.
4439   *
4440   * @since Moodle 3.10
4441   * @param stdClass $entry the entry database object
4442   * @param stdClass $glossary the glossary database object
4443   * @param stdClass $context the glossary context
4444   * @param object $cm the course module object (cm record or cm_info instance)
4445   * @param bool $return Whether to return a boolean value or stop the execution (exception)
4446   * @return bool if the user can update the entry
4447   * @throws moodle_exception
4448   */
4449  function mod_glossary_can_update_entry(stdClass $entry, stdClass $glossary, stdClass $context, object $cm,
4450          bool $return = true): bool {
4451  
4452      global $USER, $CFG;
4453  
4454      $ineditperiod = ((time() - $entry->timecreated < $CFG->maxeditingtime) || $glossary->editalways);
4455      if (!has_capability('mod/glossary:manageentries', $context) and
4456              !($entry->userid == $USER->id and ($ineditperiod and has_capability('mod/glossary:write', $context)))) {
4457  
4458          if ($USER->id != $entry->userid) {
4459              if ($return) {
4460                  return false;
4461              }
4462              throw new moodle_exception('errcannoteditothers', 'glossary', "view.php?id=$cm->id&amp;mode=entry&amp;hook=$entry->id");
4463          } else if (!$ineditperiod) {
4464              if ($return) {
4465                  return false;
4466              }
4467              throw new moodle_exception('erredittimeexpired', 'glossary', "view.php?id=$cm->id&amp;mode=entry&amp;hook=$entry->id");
4468          }
4469      }
4470  
4471      return true;
4472  }
4473  
4474  /**
4475   * Prepares an entry for editing, adding aliases and category information.
4476   *
4477   * @param  stdClass $entry the entry being edited
4478   * @return stdClass the entry with the additional data
4479   */
4480  function mod_glossary_prepare_entry_for_edition(stdClass $entry): stdClass {
4481      global $DB;
4482  
4483      if ($aliases = $DB->get_records_menu("glossary_alias", ["entryid" => $entry->id], '', 'id, alias')) {
4484          $entry->aliases = implode("\n", $aliases) . "\n";
4485      }
4486      if ($categoriesarr = $DB->get_records_menu("glossary_entries_categories", ['entryid' => $entry->id], '', 'id, categoryid')) {
4487          // TODO: this fetches cats from both main and secondary glossary :-(
4488          $entry->categories = array_values($categoriesarr);
4489      }
4490  
4491      return $entry;
4492  }