Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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