Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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

   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          throw new \moodle_exception('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          throw new \moodle_exception('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                  throw new \moodle_exception('invalidid', 'glossary');
2198              }
2199              if (! $course = $DB->get_record('course', array('id'=>$glossary->course))) {
2200                  throw new \moodle_exception('coursemisconf');
2201              }
2202              if (!$cm = get_coursemodule_from_instance('glossary', $entry->glossaryid, $glossary->course) ) {
2203                  throw new \moodle_exception('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, false if not, null if doesn't know or string for the module purpose.
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          case FEATURE_MOD_PURPOSE:             return MOD_PURPOSE_COLLABORATION;
3103  
3104          default: return null;
3105      }
3106  }
3107  
3108  function glossary_extend_navigation($navigation, $course, $module, $cm) {
3109      global $CFG, $DB;
3110  
3111      $displayformat = $DB->get_record('glossary_formats', array('name' => $module->displayformat));
3112      // Get visible tabs for the format and check if the menu needs to be displayed.
3113      $showtabs = glossary_get_visible_tabs($displayformat);
3114  
3115      foreach ($showtabs as $showtabkey => $showtabvalue) {
3116  
3117          switch($showtabvalue) {
3118              case GLOSSARY_STANDARD :
3119                  $navigation->add(get_string('standardview', 'glossary'), new moodle_url('/mod/glossary/view.php',
3120                          array('id' => $cm->id, 'mode' => 'letter')));
3121                  break;
3122              case GLOSSARY_CATEGORY :
3123                  $navigation->add(get_string('categoryview', 'glossary'), new moodle_url('/mod/glossary/view.php',
3124                          array('id' => $cm->id, 'mode' => 'cat')));
3125                  break;
3126              case GLOSSARY_DATE :
3127                  $navigation->add(get_string('dateview', 'glossary'), new moodle_url('/mod/glossary/view.php',
3128                          array('id' => $cm->id, 'mode' => 'date')));
3129                  break;
3130              case GLOSSARY_AUTHOR :
3131                  $navigation->add(get_string('authorview', 'glossary'), new moodle_url('/mod/glossary/view.php',
3132                          array('id' => $cm->id, 'mode' => 'author')));
3133                  break;
3134          }
3135      }
3136  }
3137  
3138  /**
3139   * Adds module specific settings to the settings block
3140   *
3141   * @param settings_navigation $settings The settings navigation object
3142   * @param navigation_node $glossarynode The node to add module settings to
3143   */
3144  function glossary_extend_settings_navigation(settings_navigation $settings, navigation_node $glossarynode) {
3145      global $DB, $CFG, $USER;
3146  
3147      $mode = optional_param('mode', '', PARAM_ALPHA);
3148      $hook = optional_param('hook', 'ALL', PARAM_CLEAN);
3149  
3150      if (has_capability('mod/glossary:import', $settings->get_page()->cm->context)) {
3151          $node = $glossarynode->add(get_string('importentries', 'glossary'),
3152              new moodle_url('/mod/glossary/import.php', ['id' => $settings->get_page()->cm->id]));
3153          $node->set_show_in_secondary_navigation(false);
3154      }
3155  
3156      if (has_capability('mod/glossary:export', $settings->get_page()->cm->context)) {
3157          $node = $glossarynode->add(get_string('exportentries', 'glossary'),
3158              new moodle_url('/mod/glossary/export.php', ['id' => $settings->get_page()->cm->id, 'mode' => $mode,
3159              'hook' => $hook]));
3160          $node->set_show_in_secondary_navigation(false);
3161      }
3162  
3163      $glossary = $DB->get_record('glossary', array("id" => $settings->get_page()->cm->instance));
3164      $hiddenentries = $DB->count_records('glossary_entries', ['glossaryid' => $settings->get_page()->cm->instance,
3165          'approved' => 0]);
3166  
3167      // Safe guard check - Ideally, there shouldn't be any hidden entries if the glossary has 'defaultapproval'.
3168      if (has_capability('mod/glossary:approve', $settings->get_page()->cm->context) &&
3169              (!$glossary->defaultapproval || $hiddenentries)) {
3170          $glossarynode->add(get_string('pendingapproval', 'glossary'),
3171              new moodle_url('/mod/glossary/view.php', ['id' => $settings->get_page()->cm->id, 'mode' => 'approval']),
3172              navigation_node::TYPE_CUSTOM, null, 'pendingapproval');
3173      }
3174  
3175      if (has_capability('mod/glossary:write', $settings->get_page()->cm->context)) {
3176          $node = $glossarynode->add(get_string('addentry', 'glossary'),
3177              new moodle_url('/mod/glossary/edit.php', ['cmid' => $settings->get_page()->cm->id]));
3178          $node->set_show_in_secondary_navigation(false);
3179      }
3180  
3181      if (!empty($CFG->enablerssfeeds) && !empty($CFG->glossary_enablerssfeeds) && $glossary->rsstype &&
3182              $glossary->rssarticles && has_capability('mod/glossary:view', $settings->get_page()->cm->context)) {
3183          require_once("$CFG->libdir/rsslib.php");
3184  
3185          $string = get_string('rsstype', 'glossary');
3186  
3187          $url = new moodle_url(rss_get_url($settings->get_page()->cm->context->id, $USER->id, 'mod_glossary',
3188              $glossary->id));
3189          $node = $glossarynode->add($string, $url, settings_navigation::TYPE_SETTING, null, null, new pix_icon('i/rss', ''));
3190          $node->set_show_in_secondary_navigation(false);
3191      }
3192  }
3193  
3194  /**
3195   * Running addtional permission check on plugin, for example, plugins
3196   * may have switch to turn on/off comments option, this callback will
3197   * affect UI display, not like pluginname_comment_validate only throw
3198   * exceptions.
3199   * Capability check has been done in comment->check_permissions(), we
3200   * don't need to do it again here.
3201   *
3202   * @package  mod_glossary
3203   * @category comment
3204   *
3205   * @param stdClass $comment_param {
3206   *              context  => context the context object
3207   *              courseid => int course id
3208   *              cm       => stdClass course module object
3209   *              commentarea => string comment area
3210   *              itemid      => int itemid
3211   * }
3212   * @return array
3213   */
3214  function glossary_comment_permissions($comment_param) {
3215      return array('post'=>true, 'view'=>true);
3216  }
3217  
3218  /**
3219   * Validate comment parameter before perform other comments actions
3220   *
3221   * @package  mod_glossary
3222   * @category comment
3223   *
3224   * @param stdClass $comment_param {
3225   *              context  => context the context object
3226   *              courseid => int course id
3227   *              cm       => stdClass course module object
3228   *              commentarea => string comment area
3229   *              itemid      => int itemid
3230   * }
3231   * @return boolean
3232   */
3233  function glossary_comment_validate($comment_param) {
3234      global $DB;
3235      // validate comment area
3236      if ($comment_param->commentarea != 'glossary_entry') {
3237          throw new comment_exception('invalidcommentarea');
3238      }
3239      if (!$record = $DB->get_record('glossary_entries', array('id'=>$comment_param->itemid))) {
3240          throw new comment_exception('invalidcommentitemid');
3241      }
3242      if ($record->sourceglossaryid && $record->sourceglossaryid == $comment_param->cm->instance) {
3243          $glossary = $DB->get_record('glossary', array('id'=>$record->sourceglossaryid));
3244      } else {
3245          $glossary = $DB->get_record('glossary', array('id'=>$record->glossaryid));
3246      }
3247      if (!$glossary) {
3248          throw new comment_exception('invalidid', 'data');
3249      }
3250      if (!$course = $DB->get_record('course', array('id'=>$glossary->course))) {
3251          throw new comment_exception('coursemisconf');
3252      }
3253      if (!$cm = get_coursemodule_from_instance('glossary', $glossary->id, $course->id)) {
3254          throw new comment_exception('invalidcoursemodule');
3255      }
3256      $context = context_module::instance($cm->id);
3257  
3258      if ($glossary->defaultapproval and !$record->approved and !has_capability('mod/glossary:approve', $context)) {
3259          throw new comment_exception('notapproved', 'glossary');
3260      }
3261      // validate context id
3262      if ($context->id != $comment_param->context->id) {
3263          throw new comment_exception('invalidcontext');
3264      }
3265      // validation for comment deletion
3266      if (!empty($comment_param->commentid)) {
3267          if ($comment = $DB->get_record('comments', array('id'=>$comment_param->commentid))) {
3268              if ($comment->commentarea != 'glossary_entry') {
3269                  throw new comment_exception('invalidcommentarea');
3270              }
3271              if ($comment->contextid != $comment_param->context->id) {
3272                  throw new comment_exception('invalidcontext');
3273              }
3274              if ($comment->itemid != $comment_param->itemid) {
3275                  throw new comment_exception('invalidcommentitemid');
3276              }
3277          } else {
3278              throw new comment_exception('invalidcommentid');
3279          }
3280      }
3281      return true;
3282  }
3283  
3284  /**
3285   * Return a list of page types
3286   * @param string $pagetype current page type
3287   * @param stdClass $parentcontext Block's parent context
3288   * @param stdClass $currentcontext Current context of block
3289   */
3290  function glossary_page_type_list($pagetype, $parentcontext, $currentcontext) {
3291      $module_pagetype = array(
3292          'mod-glossary-*'=>get_string('page-mod-glossary-x', 'glossary'),
3293          'mod-glossary-view'=>get_string('page-mod-glossary-view', 'glossary'),
3294          'mod-glossary-edit'=>get_string('page-mod-glossary-edit', 'glossary'));
3295      return $module_pagetype;
3296  }
3297  
3298  /**
3299   * Return list of all glossary tabs.
3300   * @throws coding_exception
3301   * @return array
3302   */
3303  function glossary_get_all_tabs() {
3304  
3305      return array (
3306          GLOSSARY_AUTHOR => get_string('authorview', 'glossary'),
3307          GLOSSARY_CATEGORY => get_string('categoryview', 'glossary'),
3308          GLOSSARY_DATE => get_string('dateview', 'glossary')
3309      );
3310  }
3311  
3312  /**
3313   * Set 'showtabs' value for glossary formats
3314   * @param stdClass $glossaryformat record from 'glossary_formats' table
3315   */
3316  function glossary_set_default_visible_tabs($glossaryformat) {
3317      global $DB;
3318  
3319      switch($glossaryformat->name) {
3320          case GLOSSARY_CONTINUOUS:
3321              $showtabs = 'standard,category,date';
3322              break;
3323          case GLOSSARY_DICTIONARY:
3324              $showtabs = 'standard';
3325              // Special code for upgraded instances that already had categories set up
3326              // in this format - enable "category" tab.
3327              // In new instances only 'standard' tab will be visible.
3328              if ($DB->record_exists_sql("SELECT 1
3329                      FROM {glossary} g, {glossary_categories} gc
3330                      WHERE g.id = gc.glossaryid and g.displayformat = ?",
3331                      array(GLOSSARY_DICTIONARY))) {
3332                  $showtabs .= ',category';
3333              }
3334              break;
3335          case GLOSSARY_FULLWITHOUTAUTHOR:
3336              $showtabs = 'standard,category,date';
3337              break;
3338          default:
3339              $showtabs = 'standard,category,date,author';
3340              break;
3341      }
3342  
3343      $DB->set_field('glossary_formats', 'showtabs', $showtabs, array('id' => $glossaryformat->id));
3344      $glossaryformat->showtabs = $showtabs;
3345  }
3346  
3347  /**
3348   * Convert 'showtabs' string to array
3349   * @param stdClass $displayformat record from 'glossary_formats' table
3350   * @return array
3351   */
3352  function glossary_get_visible_tabs($displayformat) {
3353      if (empty($displayformat->showtabs)) {
3354          glossary_set_default_visible_tabs($displayformat);
3355      }
3356      $showtabs = preg_split('/,/', $displayformat->showtabs, -1, PREG_SPLIT_NO_EMPTY);
3357  
3358      return $showtabs;
3359  }
3360  
3361  /**
3362   * Notify that the glossary was viewed.
3363   *
3364   * This will trigger relevant events and activity completion.
3365   *
3366   * @param stdClass $glossary The glossary object.
3367   * @param stdClass $course   The course object.
3368   * @param stdClass $cm       The course module object.
3369   * @param stdClass $context  The context object.
3370   * @param string   $mode     The mode in which the glossary was viewed.
3371   * @since Moodle 3.1
3372   */
3373  function glossary_view($glossary, $course, $cm, $context, $mode) {
3374  
3375      // Completion trigger.
3376      $completion = new completion_info($course);
3377      $completion->set_module_viewed($cm);
3378  
3379      // Trigger the course module viewed event.
3380      $event = \mod_glossary\event\course_module_viewed::create(array(
3381          'objectid' => $glossary->id,
3382          'context' => $context,
3383          'other' => array('mode' => $mode)
3384      ));
3385      $event->add_record_snapshot('course', $course);
3386      $event->add_record_snapshot('course_modules', $cm);
3387      $event->add_record_snapshot('glossary', $glossary);
3388      $event->trigger();
3389  }
3390  
3391  /**
3392   * Notify that a glossary entry was viewed.
3393   *
3394   * This will trigger relevant events.
3395   *
3396   * @param stdClass $entry    The entry object.
3397   * @param stdClass $context  The context object.
3398   * @since Moodle 3.1
3399   */
3400  function glossary_entry_view($entry, $context) {
3401  
3402      // Trigger the entry viewed event.
3403      $event = \mod_glossary\event\entry_viewed::create(array(
3404          'objectid' => $entry->id,
3405          'context' => $context
3406      ));
3407      $event->add_record_snapshot('glossary_entries', $entry);
3408      $event->trigger();
3409  
3410  }
3411  
3412  /**
3413   * Returns the entries of a glossary by letter.
3414   *
3415   * @param  object $glossary The glossary.
3416   * @param  context $context The context of the glossary.
3417   * @param  string $letter The letter, or ALL, or SPECIAL.
3418   * @param  int $from Fetch records from.
3419   * @param  int $limit Number of records to fetch.
3420   * @param  array $options Accepts:
3421   *                        - (bool) includenotapproved. When false, includes the non-approved entries created by
3422   *                          the current user. When true, also includes the ones that the user has the permission to approve.
3423   * @return array The first element being the recordset, the second the number of entries.
3424   * @since Moodle 3.1
3425   */
3426  function glossary_get_entries_by_letter($glossary, $context, $letter, $from, $limit, $options = array()) {
3427  
3428      $qb = new mod_glossary_entry_query_builder($glossary);
3429      if ($letter != 'ALL' && $letter != 'SPECIAL' && core_text::strlen($letter)) {
3430          $qb->filter_by_concept_letter($letter);
3431      }
3432      if ($letter == 'SPECIAL') {
3433          $qb->filter_by_concept_non_letter();
3434      }
3435  
3436      if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3437          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL);
3438      } else {
3439          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF);
3440      }
3441  
3442      $qb->add_field('*', 'entries');
3443      $qb->join_user();
3444      $qb->add_user_fields();
3445      $qb->order_by('concept', 'entries');
3446      $qb->order_by('id', 'entries', 'ASC'); // Sort on ID to avoid random ordering when entries share an ordering value.
3447      $qb->limit($from, $limit);
3448  
3449      // Fetching the entries.
3450      $count = $qb->count_records();
3451      $entries = $qb->get_records();
3452  
3453      return array($entries, $count);
3454  }
3455  
3456  /**
3457   * Returns the entries of a glossary by date.
3458   *
3459   * @param  object $glossary The glossary.
3460   * @param  context $context The context of the glossary.
3461   * @param  string $order The mode of ordering: CREATION or UPDATE.
3462   * @param  string $sort The direction of the ordering: ASC or DESC.
3463   * @param  int $from Fetch records from.
3464   * @param  int $limit Number of records to fetch.
3465   * @param  array $options Accepts:
3466   *                        - (bool) includenotapproved. When false, includes the non-approved entries created by
3467   *                          the current user. When true, also includes the ones that the user has the permission to approve.
3468   * @return array The first element being the recordset, the second the number of entries.
3469   * @since Moodle 3.1
3470   */
3471  function glossary_get_entries_by_date($glossary, $context, $order, $sort, $from, $limit, $options = array()) {
3472  
3473      $qb = new mod_glossary_entry_query_builder($glossary);
3474      if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3475          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL);
3476      } else {
3477          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF);
3478      }
3479  
3480      $qb->add_field('*', 'entries');
3481      $qb->join_user();
3482      $qb->add_user_fields();
3483      $qb->limit($from, $limit);
3484  
3485      if ($order == 'CREATION') {
3486          $qb->order_by('timecreated', 'entries', $sort);
3487      } else {
3488          $qb->order_by('timemodified', 'entries', $sort);
3489      }
3490      $qb->order_by('id', 'entries', $sort); // Sort on ID to avoid random ordering when entries share an ordering value.
3491  
3492      // Fetching the entries.
3493      $count = $qb->count_records();
3494      $entries = $qb->get_records();
3495  
3496      return array($entries, $count);
3497  }
3498  
3499  /**
3500   * Returns the entries of a glossary by category.
3501   *
3502   * @param  object $glossary The glossary.
3503   * @param  context $context The context of the glossary.
3504   * @param  int $categoryid The category ID, or GLOSSARY_SHOW_* constant.
3505   * @param  int $from Fetch records from.
3506   * @param  int $limit Number of records to fetch.
3507   * @param  array $options Accepts:
3508   *                        - (bool) includenotapproved. When false, includes the non-approved entries created by
3509   *                          the current user. When true, also includes the ones that the user has the permission to approve.
3510   * @return array The first element being the recordset, the second the number of entries.
3511   * @since Moodle 3.1
3512   */
3513  function glossary_get_entries_by_category($glossary, $context, $categoryid, $from, $limit, $options = array()) {
3514  
3515      $qb = new mod_glossary_entry_query_builder($glossary);
3516      if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3517          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL);
3518      } else {
3519          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF);
3520      }
3521  
3522      $qb->join_category($categoryid);
3523      $qb->join_user();
3524  
3525      // The first field must be the relationship ID when viewing all categories.
3526      if ($categoryid === GLOSSARY_SHOW_ALL_CATEGORIES) {
3527          $qb->add_field('id', 'entries_categories', 'cid');
3528      }
3529  
3530      $qb->add_field('*', 'entries');
3531      $qb->add_field('categoryid', 'entries_categories');
3532      $qb->add_user_fields();
3533  
3534      if ($categoryid === GLOSSARY_SHOW_ALL_CATEGORIES) {
3535          $qb->add_field('name', 'categories', 'categoryname');
3536          $qb->order_by('name', 'categories');
3537  
3538      } else if ($categoryid === GLOSSARY_SHOW_NOT_CATEGORISED) {
3539          $qb->where('categoryid', 'entries_categories', null);
3540      }
3541  
3542      // Sort on additional fields to avoid random ordering when entries share an ordering value.
3543      $qb->order_by('concept', 'entries');
3544      $qb->order_by('id', 'entries', 'ASC');
3545      $qb->limit($from, $limit);
3546  
3547      // Fetching the entries.
3548      $count = $qb->count_records();
3549      $entries = $qb->get_records();
3550  
3551      return array($entries, $count);
3552  }
3553  
3554  /**
3555   * Returns the entries of a glossary by author.
3556   *
3557   * @param  object $glossary The glossary.
3558   * @param  context $context The context of the glossary.
3559   * @param  string $letter The letter
3560   * @param  string $field The field to search: FIRSTNAME or LASTNAME.
3561   * @param  string $sort The sorting: ASC or DESC.
3562   * @param  int $from Fetch records from.
3563   * @param  int $limit Number of records to fetch.
3564   * @param  array $options Accepts:
3565   *                        - (bool) includenotapproved. When false, includes the non-approved entries created by
3566   *                          the current user. When true, also includes the ones that the user has the permission to approve.
3567   * @return array The first element being the recordset, the second the number of entries.
3568   * @since Moodle 3.1
3569   */
3570  function glossary_get_entries_by_author($glossary, $context, $letter, $field, $sort, $from, $limit, $options = array()) {
3571  
3572      $firstnamefirst = $field === 'FIRSTNAME';
3573      $qb = new mod_glossary_entry_query_builder($glossary);
3574      if ($letter != 'ALL' && $letter != 'SPECIAL' && core_text::strlen($letter)) {
3575          $qb->filter_by_author_letter($letter, $firstnamefirst);
3576      }
3577      if ($letter == 'SPECIAL') {
3578          $qb->filter_by_author_non_letter($firstnamefirst);
3579      }
3580  
3581      if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3582          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL);
3583      } else {
3584          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF);
3585      }
3586  
3587      $qb->add_field('*', 'entries');
3588      $qb->join_user(true);
3589      $qb->add_user_fields();
3590      $qb->order_by_author($firstnamefirst, $sort);
3591      $qb->order_by('concept', 'entries');
3592      $qb->order_by('id', 'entries', 'ASC'); // Sort on ID to avoid random ordering when entries share an ordering value.
3593      $qb->limit($from, $limit);
3594  
3595      // Fetching the entries.
3596      $count = $qb->count_records();
3597      $entries = $qb->get_records();
3598  
3599      return array($entries, $count);
3600  }
3601  
3602  /**
3603   * Returns the entries of a glossary by category.
3604   *
3605   * @param  object $glossary The glossary.
3606   * @param  context $context The context of the glossary.
3607   * @param  int $authorid The author ID.
3608   * @param  string $order The mode of ordering: CONCEPT, CREATION or UPDATE.
3609   * @param  string $sort The direction of the ordering: ASC or DESC.
3610   * @param  int $from Fetch records from.
3611   * @param  int $limit Number of records to fetch.
3612   * @param  array $options Accepts:
3613   *                        - (bool) includenotapproved. When false, includes the non-approved entries created by
3614   *                          the current user. When true, also includes the ones that the user has the permission to approve.
3615   * @return array The first element being the recordset, the second the number of entries.
3616   * @since Moodle 3.1
3617   */
3618  function glossary_get_entries_by_author_id($glossary, $context, $authorid, $order, $sort, $from, $limit, $options = array()) {
3619  
3620      $qb = new mod_glossary_entry_query_builder($glossary);
3621      if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3622          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL);
3623      } else {
3624          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF);
3625      }
3626  
3627      $qb->add_field('*', 'entries');
3628      $qb->join_user(true);
3629      $qb->add_user_fields();
3630      $qb->where('id', 'user', $authorid);
3631  
3632      if ($order == 'CREATION') {
3633          $qb->order_by('timecreated', 'entries', $sort);
3634      } else if ($order == 'UPDATE') {
3635          $qb->order_by('timemodified', 'entries', $sort);
3636      } else {
3637          $qb->order_by('concept', 'entries', $sort);
3638      }
3639      $qb->order_by('id', 'entries', $sort); // Sort on ID to avoid random ordering when entries share an ordering value.
3640  
3641      $qb->limit($from, $limit);
3642  
3643      // Fetching the entries.
3644      $count = $qb->count_records();
3645      $entries = $qb->get_records();
3646  
3647      return array($entries, $count);
3648  }
3649  
3650  /**
3651   * Returns the authors in a glossary
3652   *
3653   * @param  object $glossary The glossary.
3654   * @param  context $context The context of the glossary.
3655   * @param  int $limit Number of records to fetch.
3656   * @param  int $from Fetch records from.
3657   * @param  array $options Accepts:
3658   *                        - (bool) includenotapproved. When false, includes self even if all of their entries require approval.
3659   *                          When true, also includes authors only having entries pending approval.
3660   * @return array The first element being the recordset, the second the number of entries.
3661   * @since Moodle 3.1
3662   */
3663  function glossary_get_authors($glossary, $context, $limit, $from, $options = array()) {
3664      global $DB, $USER;
3665  
3666      $params = array();
3667      $userfieldsapi = \core_user\fields::for_userpic();
3668      $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
3669  
3670      $approvedsql = '(ge.approved <> 0 OR ge.userid = :myid)';
3671      $params['myid'] = $USER->id;
3672      if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3673          $approvedsql = '1 = 1';
3674      }
3675  
3676      $sqlselectcount = "SELECT COUNT(DISTINCT(u.id))";
3677      $sqlselect = "SELECT DISTINCT(u.id) AS userId, $userfields";
3678      $sql = "  FROM {user} u
3679                JOIN {glossary_entries} ge
3680                  ON ge.userid = u.id
3681                 AND (ge.glossaryid = :gid1 OR ge.sourceglossaryid = :gid2)
3682                 AND $approvedsql";
3683      $ordersql = " ORDER BY u.lastname, u.firstname";
3684  
3685      $params['gid1'] = $glossary->id;
3686      $params['gid2'] = $glossary->id;
3687  
3688      $count = $DB->count_records_sql($sqlselectcount . $sql, $params);
3689      $users = $DB->get_recordset_sql($sqlselect . $sql . $ordersql, $params, $from, $limit);
3690  
3691      return array($users, $count);
3692  }
3693  
3694  /**
3695   * Returns the categories of a glossary.
3696   *
3697   * @param  object $glossary The glossary.
3698   * @param  int $from Fetch records from.
3699   * @param  int $limit Number of records to fetch.
3700   * @return array The first element being the recordset, the second the number of entries.
3701   * @since Moodle 3.1
3702   */
3703  function glossary_get_categories($glossary, $from, $limit) {
3704      global $DB;
3705  
3706      $count = $DB->count_records('glossary_categories', array('glossaryid' => $glossary->id));
3707      $categories = $DB->get_recordset('glossary_categories', array('glossaryid' => $glossary->id), 'name ASC', '*', $from, $limit);
3708  
3709      return array($categories, $count);
3710  }
3711  
3712  /**
3713   * Get the SQL where clause for searching terms.
3714   *
3715   * Note that this does not handle invalid or too short terms.
3716   *
3717   * @param array   $terms      Array of terms.
3718   * @param bool    $fullsearch Whether or not full search should be enabled.
3719   * @param int     $glossaryid The ID of a glossary to reduce the search results.
3720   * @return array The first element being the where clause, the second array of parameters.
3721   * @since Moodle 3.1
3722   */
3723  function glossary_get_search_terms_sql(array $terms, $fullsearch = true, $glossaryid = null) {
3724      global $DB;
3725      static $i = 0;
3726  
3727      if ($DB->sql_regex_supported()) {
3728          $regexp = $DB->sql_regex(true);
3729          $notregexp = $DB->sql_regex(false);
3730      }
3731  
3732      $params = array();
3733      $conditions = array();
3734  
3735      foreach ($terms as $searchterm) {
3736          $i++;
3737  
3738          $not = false; // Initially we aren't going to perform NOT LIKE searches, only MSSQL and Oracle
3739                        // will use it to simulate the "-" operator with LIKE clause.
3740  
3741          if (empty($fullsearch)) {
3742              // With fullsearch disabled, look only within concepts and aliases.
3743              $concat = $DB->sql_concat('ge.concept', "' '", "COALESCE(al.alias, :emptychar{$i})");
3744          } else {
3745              // With fullsearch enabled, look also within definitions.
3746              $concat = $DB->sql_concat('ge.concept', "' '", 'ge.definition', "' '", "COALESCE(al.alias, :emptychar{$i})");
3747          }
3748          $params['emptychar' . $i] = '';
3749  
3750          // Under Oracle and MSSQL, trim the + and - operators and perform simpler LIKE (or NOT LIKE) queries.
3751          if (!$DB->sql_regex_supported()) {
3752              if (substr($searchterm, 0, 1) === '-') {
3753                  $not = true;
3754              }
3755              $searchterm = trim($searchterm, '+-');
3756          }
3757  
3758          if (substr($searchterm, 0, 1) === '+') {
3759              $searchterm = trim($searchterm, '+-');
3760              $conditions[] = "$concat $regexp :searchterm{$i}";
3761              $params['searchterm' . $i] = '(^|[^a-zA-Z0-9])' . preg_quote($searchterm, '|') . '([^a-zA-Z0-9]|$)';
3762  
3763          } else if (substr($searchterm, 0, 1) === "-") {
3764              $searchterm = trim($searchterm, '+-');
3765              $conditions[] = "$concat $notregexp :searchterm{$i}";
3766              $params['searchterm' . $i] = '(^|[^a-zA-Z0-9])' . preg_quote($searchterm, '|') . '([^a-zA-Z0-9]|$)';
3767  
3768          } else {
3769              $conditions[] = $DB->sql_like($concat, ":searchterm{$i}", false, true, $not);
3770              $params['searchterm' . $i] = '%' . $DB->sql_like_escape($searchterm) . '%';
3771          }
3772      }
3773  
3774      // Reduce the search results by restricting it to one glossary.
3775      if (isset($glossaryid)) {
3776          $conditions[] = 'ge.glossaryid = :glossaryid';
3777          $params['glossaryid'] = $glossaryid;
3778      }
3779  
3780      // When there are no conditions we add a negative one to ensure that we don't return anything.
3781      if (empty($conditions)) {
3782          $conditions[] = '1 = 2';
3783      }
3784  
3785      $where = implode(' AND ', $conditions);
3786      return array($where, $params);
3787  }
3788  
3789  
3790  /**
3791   * Returns the entries of a glossary by search.
3792   *
3793   * @param  object $glossary The glossary.
3794   * @param  context $context The context of the glossary.
3795   * @param  string $query The search query.
3796   * @param  bool $fullsearch Whether or not full search is required.
3797   * @param  string $order The mode of ordering: CONCEPT, CREATION or UPDATE.
3798   * @param  string $sort The direction of the ordering: ASC or DESC.
3799   * @param  int $from Fetch records from.
3800   * @param  int $limit Number of records to fetch.
3801   * @param  array $options Accepts:
3802   *                        - (bool) includenotapproved. When false, includes the non-approved entries created by
3803   *                          the current user. When true, also includes the ones that the user has the permission to approve.
3804   * @return array The first element being the array of results, the second the number of entries.
3805   * @since Moodle 3.1
3806   */
3807  function glossary_get_entries_by_search($glossary, $context, $query, $fullsearch, $order, $sort, $from, $limit,
3808                                          $options = array()) {
3809      global $DB, $USER;
3810  
3811      // Clean terms.
3812      $terms = explode(' ', $query);
3813      foreach ($terms as $key => $term) {
3814          if (strlen(trim($term, '+-')) < 1) {
3815              unset($terms[$key]);
3816          }
3817      }
3818  
3819      list($searchcond, $params) = glossary_get_search_terms_sql($terms, $fullsearch, $glossary->id);
3820  
3821      $userfieldsapi = \core_user\fields::for_userpic();
3822      $userfields = $userfieldsapi->get_sql('u', false, 'userdata', 'userdataid', false)->selects;
3823  
3824      // Need one inner view here to avoid distinct + text.
3825      $sqlwrapheader = 'SELECT ge.*, ge.concept AS glossarypivot, ' . $userfields . '
3826                          FROM {glossary_entries} ge
3827                          LEFT JOIN {user} u ON u.id = ge.userid
3828                          JOIN ( ';
3829      $sqlwrapfooter = ' ) gei ON (ge.id = gei.id)';
3830      $sqlselect  = "SELECT DISTINCT ge.id";
3831      $sqlfrom    = "FROM {glossary_entries} ge
3832                     LEFT JOIN {glossary_alias} al ON al.entryid = ge.id";
3833  
3834      if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3835          $approvedsql = '';
3836      } else {
3837          $approvedsql = 'AND (ge.approved <> 0 OR ge.userid = :myid)';
3838          $params['myid'] = $USER->id;
3839      }
3840  
3841      if ($order == 'CREATION') {
3842          $sqlorderby = "ORDER BY ge.timecreated $sort";
3843      } else if ($order == 'UPDATE') {
3844          $sqlorderby = "ORDER BY ge.timemodified $sort";
3845      } else {
3846          $sqlorderby = "ORDER BY ge.concept $sort";
3847      }
3848      $sqlorderby .= " , ge.id ASC"; // Sort on ID to avoid random ordering when entries share an ordering value.
3849  
3850      $sqlwhere = "WHERE ($searchcond) $approvedsql";
3851  
3852      // Fetching the entries.
3853      $count = $DB->count_records_sql("SELECT COUNT(DISTINCT(ge.id)) $sqlfrom $sqlwhere", $params);
3854  
3855      $query = "$sqlwrapheader $sqlselect $sqlfrom $sqlwhere $sqlwrapfooter $sqlorderby";
3856      $entries = $DB->get_records_sql($query, $params, $from, $limit);
3857  
3858      return array($entries, $count);
3859  }
3860  
3861  /**
3862   * Returns the entries of a glossary by term.
3863   *
3864   * @param  object $glossary The glossary.
3865   * @param  context $context The context of the glossary.
3866   * @param  string $term The term we are searching for, a concept or alias.
3867   * @param  int $from Fetch records from.
3868   * @param  int $limit Number of records to fetch.
3869   * @param  array $options Accepts:
3870   *                        - (bool) includenotapproved. When false, includes the non-approved entries created by
3871   *                          the current user. When true, also includes the ones that the user has the permission to approve.
3872   * @return array The first element being the recordset, the second the number of entries.
3873   * @since Moodle 3.1
3874   */
3875  function glossary_get_entries_by_term($glossary, $context, $term, $from, $limit, $options = array()) {
3876  
3877      // Build the query.
3878      $qb = new mod_glossary_entry_query_builder($glossary);
3879      if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3880          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL);
3881      } else {
3882          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF);
3883      }
3884  
3885      $qb->add_field('*', 'entries');
3886      $qb->join_alias();
3887      $qb->join_user();
3888      $qb->add_user_fields();
3889      $qb->filter_by_term($term);
3890  
3891      $qb->order_by('concept', 'entries');
3892      $qb->order_by('id', 'entries');     // Sort on ID to avoid random ordering when entries share an ordering value.
3893      $qb->limit($from, $limit);
3894  
3895      // Fetching the entries.
3896      $count = $qb->count_records();
3897      $entries = $qb->get_records();
3898  
3899      return array($entries, $count);
3900  }
3901  
3902  /**
3903   * Returns the entries to be approved.
3904   *
3905   * @param  object $glossary The glossary.
3906   * @param  context $context The context of the glossary.
3907   * @param  string $letter The letter, or ALL, or SPECIAL.
3908   * @param  string $order The mode of ordering: CONCEPT, CREATION or UPDATE.
3909   * @param  string $sort The direction of the ordering: ASC or DESC.
3910   * @param  int $from Fetch records from.
3911   * @param  int $limit Number of records to fetch.
3912   * @return array The first element being the recordset, the second the number of entries.
3913   * @since Moodle 3.1
3914   */
3915  function glossary_get_entries_to_approve($glossary, $context, $letter, $order, $sort, $from, $limit) {
3916  
3917      $qb = new mod_glossary_entry_query_builder($glossary);
3918      if ($letter != 'ALL' && $letter != 'SPECIAL' && core_text::strlen($letter)) {
3919          $qb->filter_by_concept_letter($letter);
3920      }
3921      if ($letter == 'SPECIAL') {
3922          $qb->filter_by_concept_non_letter();
3923      }
3924  
3925      $qb->add_field('*', 'entries');
3926      $qb->join_user();
3927      $qb->add_user_fields();
3928      $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ONLY);
3929      if ($order == 'CREATION') {
3930          $qb->order_by('timecreated', 'entries', $sort);
3931      } else if ($order == 'UPDATE') {
3932          $qb->order_by('timemodified', 'entries', $sort);
3933      } else {
3934          $qb->order_by('concept', 'entries', $sort);
3935      }
3936      $qb->order_by('id', 'entries', $sort); // Sort on ID to avoid random ordering when entries share an ordering value.
3937      $qb->limit($from, $limit);
3938  
3939      // Fetching the entries.
3940      $count = $qb->count_records();
3941      $entries = $qb->get_records();
3942  
3943      return array($entries, $count);
3944  }
3945  
3946  /**
3947   * Fetch an entry.
3948   *
3949   * @param  int $id The entry ID.
3950   * @return object|false The entry, or false when not found.
3951   * @since Moodle 3.1
3952   */
3953  function glossary_get_entry_by_id($id) {
3954  
3955      // Build the query.
3956      $qb = new mod_glossary_entry_query_builder();
3957      $qb->add_field('*', 'entries');
3958      $qb->join_user();
3959      $qb->add_user_fields();
3960      $qb->where('id', 'entries', $id);
3961  
3962      // Fetching the entries.
3963      $entries = $qb->get_records();
3964      if (empty($entries)) {
3965          return false;
3966      }
3967      return array_pop($entries);
3968  }
3969  
3970  /**
3971   * Checks if the current user can see the glossary entry.
3972   *
3973   * @since Moodle 3.1
3974   * @param stdClass $entry
3975   * @param cm_info  $cminfo
3976   * @return bool
3977   */
3978  function glossary_can_view_entry($entry, $cminfo) {
3979      global $USER;
3980  
3981      $cm = $cminfo->get_course_module_record();
3982      $context = \context_module::instance($cm->id);
3983  
3984      // Recheck uservisible although it should have already been checked in core_search.
3985      if ($cminfo->uservisible === false) {
3986          return false;
3987      }
3988  
3989      // Check approval.
3990      if (empty($entry->approved) && $entry->userid != $USER->id && !has_capability('mod/glossary:approve', $context)) {
3991          return false;
3992      }
3993  
3994      return true;
3995  }
3996  
3997  /**
3998   * Check if a concept exists in a glossary.
3999   *
4000   * @param  stdClass $glossary glossary object
4001   * @param  string $concept the concept to check
4002   * @return bool true if exists
4003   * @since  Moodle 3.2
4004   */
4005  function glossary_concept_exists($glossary, $concept) {
4006      global $DB;
4007  
4008      return $DB->record_exists_select('glossary_entries', 'glossaryid = :glossaryid AND LOWER(concept) = :concept',
4009          array(
4010              'glossaryid' => $glossary->id,
4011              'concept'    => core_text::strtolower($concept)
4012          )
4013      );
4014  }
4015  
4016  /**
4017   * Return the editor and attachment options when editing a glossary entry
4018   *
4019   * @param  stdClass $course  course object
4020   * @param  stdClass $context context object
4021   * @param  stdClass $entry   entry object
4022   * @return array array containing the editor and attachment options
4023   * @since  Moodle 3.2
4024   */
4025  function glossary_get_editor_and_attachment_options($course, $context, $entry) {
4026      $maxfiles = 99;                // TODO: add some setting.
4027      $maxbytes = $course->maxbytes; // TODO: add some setting.
4028  
4029      $definitionoptions = array('trusttext' => true, 'maxfiles' => $maxfiles, 'maxbytes' => $maxbytes, 'context' => $context,
4030          'subdirs' => file_area_contains_subdirs($context, 'mod_glossary', 'entry', $entry->id));
4031      $attachmentoptions = array('subdirs' => false, 'maxfiles' => $maxfiles, 'maxbytes' => $maxbytes);
4032      return array($definitionoptions, $attachmentoptions);
4033  }
4034  
4035  /**
4036   * Creates or updates a glossary entry
4037   *
4038   * @param  stdClass $entry entry data
4039   * @param  stdClass $course course object
4040   * @param  stdClass $cm course module object
4041   * @param  stdClass $glossary glossary object
4042   * @param  stdClass $context context object
4043   * @return stdClass the complete new or updated entry
4044   * @since  Moodle 3.2
4045   */
4046  function glossary_edit_entry($entry, $course, $cm, $glossary, $context) {
4047      global $DB, $USER;
4048  
4049      list($definitionoptions, $attachmentoptions) = glossary_get_editor_and_attachment_options($course, $context, $entry);
4050  
4051      $timenow = time();
4052  
4053      $categories = empty($entry->categories) ? array() : $entry->categories;
4054      unset($entry->categories);
4055      $aliases = trim($entry->aliases);
4056      unset($entry->aliases);
4057  
4058      if (empty($entry->id)) {
4059          $entry->glossaryid       = $glossary->id;
4060          $entry->timecreated      = $timenow;
4061          $entry->userid           = $USER->id;
4062          $entry->timecreated      = $timenow;
4063          $entry->sourceglossaryid = 0;
4064          $entry->teacherentry     = has_capability('mod/glossary:manageentries', $context);
4065          $isnewentry              = true;
4066      } else {
4067          $isnewentry              = false;
4068      }
4069  
4070      $entry->concept          = trim($entry->concept);
4071      $entry->definition       = '';          // Updated later.
4072      $entry->definitionformat = FORMAT_HTML; // Updated later.
4073      $entry->definitiontrust  = 0;           // Updated later.
4074      $entry->timemodified     = $timenow;
4075      $entry->approved         = 0;
4076      $entry->usedynalink      = isset($entry->usedynalink) ? $entry->usedynalink : 0;
4077      $entry->casesensitive    = isset($entry->casesensitive) ? $entry->casesensitive : 0;
4078      $entry->fullmatch        = isset($entry->fullmatch) ? $entry->fullmatch : 0;
4079  
4080      if ($glossary->defaultapproval or has_capability('mod/glossary:approve', $context)) {
4081          $entry->approved = 1;
4082      }
4083  
4084      if ($isnewentry) {
4085          // Add new entry.
4086          $entry->id = $DB->insert_record('glossary_entries', $entry);
4087      } else {
4088          // Update existing entry.
4089          $DB->update_record('glossary_entries', $entry);
4090      }
4091  
4092      // Save and relink embedded images and save attachments.
4093      if (!empty($entry->definition_editor)) {
4094          $entry = file_postupdate_standard_editor($entry, 'definition', $definitionoptions, $context, 'mod_glossary', 'entry',
4095              $entry->id);
4096      }
4097      if (!empty($entry->attachment_filemanager)) {
4098          $entry = file_postupdate_standard_filemanager($entry, 'attachment', $attachmentoptions, $context, 'mod_glossary',
4099              'attachment', $entry->id);
4100      }
4101  
4102      // Store the updated value values.
4103      $DB->update_record('glossary_entries', $entry);
4104  
4105      // Refetch complete entry.
4106      $entry = $DB->get_record('glossary_entries', array('id' => $entry->id));
4107  
4108      // Update entry categories.
4109      $DB->delete_records('glossary_entries_categories', array('entryid' => $entry->id));
4110      // TODO: this deletes cats from both both main and secondary glossary :-(.
4111      if (!empty($categories) and array_search(0, $categories) === false) {
4112          foreach ($categories as $catid) {
4113              $newcategory = new stdClass();
4114              $newcategory->entryid    = $entry->id;
4115              $newcategory->categoryid = $catid;
4116              $DB->insert_record('glossary_entries_categories', $newcategory, false);
4117          }
4118      }
4119  
4120      // Update aliases.
4121      $DB->delete_records('glossary_alias', array('entryid' => $entry->id));
4122      if ($aliases !== '') {
4123          $aliases = explode("\n", $aliases);
4124          foreach ($aliases as $alias) {
4125              $alias = trim($alias);
4126              if ($alias !== '') {
4127                  $newalias = new stdClass();
4128                  $newalias->entryid = $entry->id;
4129                  $newalias->alias   = $alias;
4130                  $DB->insert_record('glossary_alias', $newalias, false);
4131              }
4132          }
4133      }
4134  
4135      // Trigger event and update completion (if entry was created).
4136      $eventparams = array(
4137          'context' => $context,
4138          'objectid' => $entry->id,
4139          'other' => array('concept' => $entry->concept)
4140      );
4141      if ($isnewentry) {
4142          $event = \mod_glossary\event\entry_created::create($eventparams);
4143      } else {
4144          $event = \mod_glossary\event\entry_updated::create($eventparams);
4145      }
4146      $event->add_record_snapshot('glossary_entries', $entry);
4147      $event->trigger();
4148      if ($isnewentry) {
4149          // Update completion state.
4150          $completion = new completion_info($course);
4151          if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC && $glossary->completionentries && $entry->approved) {
4152              $completion->update_state($cm, COMPLETION_COMPLETE);
4153          }
4154      }
4155  
4156      // Reset caches.
4157      if ($isnewentry) {
4158          if ($entry->usedynalink and $entry->approved) {
4159              \mod_glossary\local\concept_cache::reset_glossary($glossary);
4160          }
4161      } else {
4162          // So many things may affect the linking, let's just purge the cache always on edit.
4163          \mod_glossary\local\concept_cache::reset_glossary($glossary);
4164      }
4165      return $entry;
4166  }
4167  
4168  /**
4169   * Check if the module has any update that affects the current user since a given time.
4170   *
4171   * @param  cm_info $cm course module data
4172   * @param  int $from the time to check updates from
4173   * @param  array $filter  if we need to check only specific updates
4174   * @return stdClass an object with the different type of areas indicating if they were updated or not
4175   * @since Moodle 3.2
4176   */
4177  function glossary_check_updates_since(cm_info $cm, $from, $filter = array()) {
4178      global $DB;
4179  
4180      $updates = course_check_module_updates_since($cm, $from, array('attachment', 'entry'), $filter);
4181  
4182      $updates->entries = (object) array('updated' => false);
4183      $select = 'glossaryid = :id AND (timecreated > :since1 OR timemodified > :since2)';
4184      $params = array('id' => $cm->instance, 'since1' => $from, 'since2' => $from);
4185      if (!has_capability('mod/glossary:approve', $cm->context)) {
4186          $select .= ' AND approved = 1';
4187      }
4188  
4189      $entries = $DB->get_records_select('glossary_entries', $select, $params, '', 'id');
4190      if (!empty($entries)) {
4191          $updates->entries->updated = true;
4192          $updates->entries->itemids = array_keys($entries);
4193      }
4194  
4195      return $updates;
4196  }
4197  
4198  /**
4199   * Get icon mapping for font-awesome.
4200   *
4201   * @return array
4202   */
4203  function mod_glossary_get_fontawesome_icon_map() {
4204      return [
4205          'mod_glossary:export' => 'fa-download',
4206          'mod_glossary:minus' => 'fa-minus'
4207      ];
4208  }
4209  
4210  /**
4211   * This function receives a calendar event and returns the action associated with it, or null if there is none.
4212   *
4213   * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
4214   * is not displayed on the block.
4215   *
4216   * @param calendar_event $event
4217   * @param \core_calendar\action_factory $factory
4218   * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
4219   * @return \core_calendar\local\event\entities\action_interface|null
4220   */
4221  function mod_glossary_core_calendar_provide_event_action(calendar_event $event,
4222                                                           \core_calendar\action_factory $factory,
4223                                                           int $userid = 0) {
4224      global $USER;
4225  
4226      if (!$userid) {
4227          $userid = $USER->id;
4228      }
4229  
4230      $cm = get_fast_modinfo($event->courseid, $userid)->instances['glossary'][$event->instance];
4231  
4232      if (!$cm->uservisible) {
4233          // The module is not visible to the user for any reason.
4234          return null;
4235      }
4236  
4237      $completion = new \completion_info($cm->get_course());
4238  
4239      $completiondata = $completion->get_data($cm, false, $userid);
4240  
4241      if ($completiondata->completionstate != COMPLETION_INCOMPLETE) {
4242          return null;
4243      }
4244  
4245      return $factory->create_instance(
4246          get_string('view'),
4247          new \moodle_url('/mod/glossary/view.php', ['id' => $cm->id]),
4248          1,
4249          true
4250      );
4251  }
4252  
4253  /**
4254   * Add a get_coursemodule_info function in case any glossary type wants to add 'extra' information
4255   * for the course (see resource).
4256   *
4257   * Given a course_module object, this function returns any "extra" information that may be needed
4258   * when printing this activity in a course listing.  See get_array_of_activities() in course/lib.php.
4259   *
4260   * @param stdClass $coursemodule The coursemodule object (record).
4261   * @return cached_cm_info An object on information that the courses
4262   *                        will know about (most noticeably, an icon).
4263   */
4264  function glossary_get_coursemodule_info($coursemodule) {
4265      global $DB;
4266  
4267      $dbparams = ['id' => $coursemodule->instance];
4268      $fields = 'id, name, intro, introformat, completionentries';
4269      if (!$glossary = $DB->get_record('glossary', $dbparams, $fields)) {
4270          return false;
4271      }
4272  
4273      $result = new cached_cm_info();
4274      $result->name = $glossary->name;
4275  
4276      if ($coursemodule->showdescription) {
4277          // Convert intro to html. Do not filter cached version, filters run at display time.
4278          $result->content = format_module_intro('glossary', $glossary, $coursemodule->id, false);
4279      }
4280  
4281      // Populate the custom completion rules as key => value pairs, but only if the completion mode is 'automatic'.
4282      if ($coursemodule->completion == COMPLETION_TRACKING_AUTOMATIC) {
4283          $result->customdata['customcompletionrules']['completionentries'] = $glossary->completionentries;
4284      }
4285  
4286      return $result;
4287  }
4288  
4289  /**
4290   * Callback which returns human-readable strings describing the active completion custom rules for the module instance.
4291   *
4292   * @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
4293   * @return array $descriptions the array of descriptions for the custom rules.
4294   */
4295  function mod_glossary_get_completion_active_rule_descriptions($cm) {
4296      // Values will be present in cm_info, and we assume these are up to date.
4297      if (empty($cm->customdata['customcompletionrules'])
4298          || $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
4299          return [];
4300      }
4301  
4302      $descriptions = [];
4303      foreach ($cm->customdata['customcompletionrules'] as $key => $val) {
4304          switch ($key) {
4305              case 'completionentries':
4306                  if (!empty($val)) {
4307                      $descriptions[] = get_string('completionentriesdesc', 'glossary', $val);
4308                  }
4309                  break;
4310              default:
4311                  break;
4312          }
4313      }
4314      return $descriptions;
4315  }
4316  
4317  /**
4318   * Checks if the current user can delete the given glossary entry.
4319   *
4320   * @since Moodle 3.10
4321   * @param stdClass $entry the entry database object
4322   * @param stdClass $glossary the glossary database object
4323   * @param stdClass $context the glossary context
4324   * @param bool $return Whether to return a boolean value or stop the execution (exception)
4325   * @return bool if the user can delete the entry
4326   * @throws moodle_exception
4327   */
4328  function mod_glossary_can_delete_entry($entry, $glossary, $context, $return = true) {
4329      global $USER, $CFG;
4330  
4331      $manageentries = has_capability('mod/glossary:manageentries', $context);
4332  
4333      if ($manageentries) {   // Users with the capability will always be able to delete entries.
4334          return true;
4335      }
4336  
4337      if ($entry->userid != $USER->id) { // Guest id is never matched, no need for special check here.
4338          if ($return) {
4339              return false;
4340          }
4341          throw new moodle_exception('nopermissiontodelentry');
4342      }
4343  
4344      $ineditperiod = ((time() - $entry->timecreated < $CFG->maxeditingtime) || $glossary->editalways);
4345  
4346      if (!$ineditperiod) {
4347          if ($return) {
4348              return false;
4349          }
4350          throw new moodle_exception('errdeltimeexpired', 'glossary');
4351      }
4352  
4353      return true;
4354  }
4355  
4356  /**
4357   * Deletes the given entry, this function does not perform capabilities/permission checks.
4358   *
4359   * @since Moodle 3.10
4360   * @param stdClass $entry the entry database object
4361   * @param stdClass $glossary the glossary database object
4362   * @param stdClass $cm the glossary course moduule object
4363   * @param stdClass $context the glossary context
4364   * @param stdClass $course the glossary course
4365   * @param string $hook the hook, usually type of filtering, value
4366   * @param string $prevmode the previsualisation mode
4367   * @throws moodle_exception
4368   */
4369  function mod_glossary_delete_entry($entry, $glossary, $cm, $context, $course, $hook = '', $prevmode = '') {
4370      global $CFG, $DB;
4371  
4372      $origentry = fullclone($entry);
4373  
4374      // If it is an imported entry, just delete the relation.
4375      if ($entry->sourceglossaryid) {
4376          if (!$newcm = get_coursemodule_from_instance('glossary', $entry->sourceglossaryid)) {
4377              throw new \moodle_exception('invalidcoursemodule');
4378          }
4379          $newcontext = context_module::instance($newcm->id);
4380  
4381          $entry->glossaryid       = $entry->sourceglossaryid;
4382          $entry->sourceglossaryid = 0;
4383          $DB->update_record('glossary_entries', $entry);
4384  
4385          // Move attachments too.
4386          $fs = get_file_storage();
4387  
4388          if ($oldfiles = $fs->get_area_files($context->id, 'mod_glossary', 'attachment', $entry->id)) {
4389              foreach ($oldfiles as $oldfile) {
4390                  $filerecord = new stdClass();
4391                  $filerecord->contextid = $newcontext->id;
4392                  $fs->create_file_from_storedfile($filerecord, $oldfile);
4393              }
4394              $fs->delete_area_files($context->id, 'mod_glossary', 'attachment', $entry->id);
4395              $entry->attachment = '1';
4396          } else {
4397              $entry->attachment = '0';
4398          }
4399          $DB->update_record('glossary_entries', $entry);
4400  
4401      } else {
4402          $fs = get_file_storage();
4403          $fs->delete_area_files($context->id, 'mod_glossary', 'attachment', $entry->id);
4404          $DB->delete_records("comments",
4405              ['itemid' => $entry->id, 'commentarea' => 'glossary_entry', 'contextid' => $context->id]);
4406          $DB->delete_records("glossary_alias", ["entryid" => $entry->id]);
4407          $DB->delete_records("glossary_entries", ["id" => $entry->id]);
4408  
4409          // Update completion state.
4410          $completion = new completion_info($course);
4411          if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC && $glossary->completionentries) {
4412              $completion->update_state($cm, COMPLETION_INCOMPLETE, $entry->userid);
4413          }
4414  
4415          // Delete glossary entry ratings.
4416          require_once($CFG->dirroot.'/rating/lib.php');
4417          $delopt = new stdClass;
4418          $delopt->contextid = $context->id;
4419          $delopt->component = 'mod_glossary';
4420          $delopt->ratingarea = 'entry';
4421          $delopt->itemid = $entry->id;
4422          $rm = new rating_manager();
4423          $rm->delete_ratings($delopt);
4424      }
4425  
4426      // Delete cached RSS feeds.
4427      if (!empty($CFG->enablerssfeeds)) {
4428          require_once($CFG->dirroot . '/mod/glossary/rsslib.php');
4429          glossary_rss_delete_file($glossary);
4430      }
4431  
4432      core_tag_tag::remove_all_item_tags('mod_glossary', 'glossary_entries', $origentry->id);
4433  
4434      $event = \mod_glossary\event\entry_deleted::create(
4435          [
4436              'context' => $context,
4437              'objectid' => $origentry->id,
4438              'other' => [
4439                  'mode' => $prevmode,
4440                  'hook' => $hook,
4441                  'concept' => $origentry->concept
4442              ]
4443          ]
4444      );
4445      $event->add_record_snapshot('glossary_entries', $origentry);
4446      $event->trigger();
4447  
4448      // Reset caches.
4449      if ($entry->usedynalink and $entry->approved) {
4450          \mod_glossary\local\concept_cache::reset_glossary($glossary);
4451      }
4452  }
4453  
4454  /**
4455   * Checks if the current user can update the given glossary entry.
4456   *
4457   * @since Moodle 3.10
4458   * @param stdClass $entry the entry database object
4459   * @param stdClass $glossary the glossary database object
4460   * @param stdClass $context the glossary context
4461   * @param object $cm the course module object (cm record or cm_info instance)
4462   * @param bool $return Whether to return a boolean value or stop the execution (exception)
4463   * @return bool if the user can update the entry
4464   * @throws moodle_exception
4465   */
4466  function mod_glossary_can_update_entry(stdClass $entry, stdClass $glossary, stdClass $context, object $cm,
4467          bool $return = true): bool {
4468  
4469      global $USER, $CFG;
4470  
4471      $ineditperiod = ((time() - $entry->timecreated < $CFG->maxeditingtime) || $glossary->editalways);
4472      if (!has_capability('mod/glossary:manageentries', $context) and
4473              !($entry->userid == $USER->id and ($ineditperiod and has_capability('mod/glossary:write', $context)))) {
4474  
4475          if ($USER->id != $entry->userid) {
4476              if ($return) {
4477                  return false;
4478              }
4479              throw new moodle_exception('errcannoteditothers', 'glossary', "view.php?id=$cm->id&amp;mode=entry&amp;hook=$entry->id");
4480          } else if (!$ineditperiod) {
4481              if ($return) {
4482                  return false;
4483              }
4484              throw new moodle_exception('erredittimeexpired', 'glossary', "view.php?id=$cm->id&amp;mode=entry&amp;hook=$entry->id");
4485          }
4486      }
4487  
4488      return true;
4489  }
4490  
4491  /**
4492   * Prepares an entry for editing, adding aliases and category information.
4493   *
4494   * @param  stdClass $entry the entry being edited
4495   * @return stdClass the entry with the additional data
4496   */
4497  function mod_glossary_prepare_entry_for_edition(stdClass $entry): stdClass {
4498      global $DB;
4499  
4500      if ($aliases = $DB->get_records_menu("glossary_alias", ["entryid" => $entry->id], '', 'id, alias')) {
4501          $entry->aliases = implode("\n", $aliases) . "\n";
4502      }
4503      if ($categoriesarr = $DB->get_records_menu("glossary_entries_categories", ['entryid' => $entry->id], '', 'id, categoryid')) {
4504          // TODO: this fetches cats from both main and secondary glossary :-(
4505          $entry->categories = array_values($categoriesarr);
4506      }
4507  
4508      return $entry;
4509  }