Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 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 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]

   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_fieldset_select('glossary_alias', 'alias', 'entryid = :entryid', ['entryid' => $entry->id])) {
1184          $id = "keyword-{$entry->id}";
1185          $return = html_writer::select($aliases, $id, '', false, ['id' => $id]);
1186      }
1187      if ($type == 'print') {
1188          echo $return;
1189      } else {
1190          return $return;
1191      }
1192  }
1193  
1194  /**
1195   *
1196   * @global object
1197   * @global object
1198   * @global object
1199   * @param object $course
1200   * @param object $cm
1201   * @param object $glossary
1202   * @param object $entry
1203   * @param string $mode
1204   * @param string $hook
1205   * @param string $type
1206   * @return string|void
1207   */
1208  function glossary_print_entry_icons($course, $cm, $glossary, $entry, $mode='',$hook='', $type = 'print') {
1209      global $USER, $CFG, $DB, $OUTPUT;
1210  
1211      $context = context_module::instance($cm->id);
1212  
1213      $output = false;   // To decide if we must really return text in "return". Activate when needed only!
1214      $importedentry = ($entry->sourceglossaryid == $glossary->id);
1215      $ismainglossary = $glossary->mainglossary;
1216  
1217      $return = '<span class="commands">';
1218      // Differentiate links for each entry.
1219      $altsuffix = strip_tags(format_text($entry->concept));
1220  
1221      if (!$entry->approved) {
1222          $output = true;
1223          $return .= html_writer::tag('span', get_string('entryishidden','glossary'),
1224              array('class' => 'glossary-hidden-note'));
1225      }
1226  
1227      if ($entry->approved || has_capability('mod/glossary:approve', $context)) {
1228          $output = true;
1229          $return .= \html_writer::link(
1230              new \moodle_url('/mod/glossary/showentry.php', ['eid' => $entry->id]),
1231              $OUTPUT->pix_icon('fp/link', get_string('entrylink', 'glossary', $altsuffix), 'theme'),
1232              ['title' => get_string('entrylink', 'glossary', $altsuffix), 'class' => 'icon']
1233          );
1234      }
1235  
1236      if (has_capability('mod/glossary:approve', $context) && !$glossary->defaultapproval && $entry->approved) {
1237          $output = true;
1238          $return .= '<a class="icon" title="' . get_string('disapprove', 'glossary').
1239                     '" href="approve.php?newstate=0&amp;eid='.$entry->id.'&amp;mode='.$mode.
1240                     '&amp;hook='.urlencode($hook).'&amp;sesskey='.sesskey().
1241                     '">' . $OUTPUT->pix_icon('t/block', get_string('disapprove', 'glossary')) . '</a>';
1242      }
1243  
1244      $iscurrentuser = ($entry->userid == $USER->id);
1245  
1246      if (has_capability('mod/glossary:manageentries', $context) or (isloggedin() and has_capability('mod/glossary:write', $context) and $iscurrentuser)) {
1247          // only teachers can export entries so check it out
1248          if (has_capability('mod/glossary:export', $context) and !$ismainglossary and !$importedentry) {
1249              $mainglossary = $DB->get_record('glossary', array('mainglossary'=>1,'course'=>$course->id));
1250              if ( $mainglossary ) {  // if there is a main glossary defined, allow to export the current entry
1251                  $output = true;
1252                  $return .= '<a class="icon" title="'.get_string('exporttomainglossary','glossary') . '" ' .
1253                      'href="exportentry.php?id='.$entry->id.'&amp;prevmode='.$mode.'&amp;hook='.urlencode($hook).'">' .
1254                      $OUTPUT->pix_icon('export', get_string('exporttomainglossary', 'glossary'), 'glossary') . '</a>';
1255              }
1256          }
1257  
1258          $icon = 't/delete';
1259          $iconcomponent = 'moodle';
1260          if ( $entry->sourceglossaryid ) {
1261              $icon = 'minus';   // graphical metaphor (minus) for deleting an imported entry
1262              $iconcomponent = 'glossary';
1263          }
1264  
1265          //Decide if an entry is editable:
1266          // -It isn't a imported entry (so nobody can edit a imported (from secondary to main) entry)) and
1267          // -The user is teacher or he is a student with time permissions (edit period or editalways defined).
1268          $ineditperiod = ((time() - $entry->timecreated <  $CFG->maxeditingtime) || $glossary->editalways);
1269          if ( !$importedentry and (has_capability('mod/glossary:manageentries', $context) or ($entry->userid == $USER->id and ($ineditperiod and has_capability('mod/glossary:write', $context))))) {
1270              $output = true;
1271              $url = "deleteentry.php?id=$cm->id&amp;mode=delete&amp;entry=$entry->id&amp;prevmode=$mode&amp;hook=".urlencode($hook);
1272              $return .= "<a class='icon' title=\"" . get_string("delete") . "\" " .
1273                         "href=\"$url\">" . $OUTPUT->pix_icon($icon, get_string('deleteentrya', 'mod_glossary', $altsuffix), $iconcomponent) . '</a>';
1274  
1275              $url = "edit.php?cmid=$cm->id&amp;id=$entry->id&amp;mode=$mode&amp;hook=".urlencode($hook);
1276              $return .= "<a class='icon' title=\"" . get_string("edit") . "\" href=\"$url\">" .
1277                         $OUTPUT->pix_icon('t/edit', get_string('editentrya', 'mod_glossary', $altsuffix)) . '</a>';
1278          } elseif ( $importedentry ) {
1279              $return .= "<font size=\"-1\">" . get_string("exportedentry","glossary") . "</font>";
1280          }
1281      }
1282      if (!empty($CFG->enableportfolios) && (has_capability('mod/glossary:exportentry', $context) || ($iscurrentuser && has_capability('mod/glossary:exportownentry', $context)))) {
1283          require_once($CFG->libdir . '/portfoliolib.php');
1284          $button = new portfolio_add_button();
1285          $button->set_callback_options('glossary_entry_portfolio_caller',  array('id' => $cm->id, 'entryid' => $entry->id), 'mod_glossary');
1286  
1287          $filecontext = $context;
1288          if ($entry->sourceglossaryid == $cm->instance) {
1289              if ($maincm = get_coursemodule_from_instance('glossary', $entry->glossaryid)) {
1290                  $filecontext = context_module::instance($maincm->id);
1291              }
1292          }
1293          $fs = get_file_storage();
1294          if ($files = $fs->get_area_files($filecontext->id, 'mod_glossary', 'attachment', $entry->id, "timemodified", false)
1295           || $files = $fs->get_area_files($filecontext->id, 'mod_glossary', 'entry', $entry->id, "timemodified", false)) {
1296  
1297              $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
1298          } else {
1299              $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
1300          }
1301  
1302          $return .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
1303      }
1304      $return .= '</span>';
1305  
1306      if (!empty($CFG->usecomments) && has_capability('mod/glossary:comment', $context) and $glossary->allowcomments) {
1307          require_once($CFG->dirroot . '/comment/lib.php');
1308          $cmt = new stdClass();
1309          $cmt->component = 'mod_glossary';
1310          $cmt->context  = $context;
1311          $cmt->course   = $course;
1312          $cmt->cm       = $cm;
1313          $cmt->area     = 'glossary_entry';
1314          $cmt->itemid   = $entry->id;
1315          $cmt->showcount = true;
1316          $comment = new comment($cmt);
1317          $return .= '<div>'.$comment->output(true).'</div>';
1318          $output = true;
1319      }
1320  
1321      //If we haven't calculated any REAL thing, delete result ($return)
1322      if (!$output) {
1323          $return = '';
1324      }
1325      //Print or get
1326      if ($type == 'print') {
1327          echo $return;
1328      } else {
1329          return $return;
1330      }
1331  }
1332  
1333  /**
1334   * @param object $course
1335   * @param object $cm
1336   * @param object $glossary
1337   * @param object $entry
1338   * @param string $mode
1339   * @param object $hook
1340   * @param bool $printicons
1341   * @param bool $aliases
1342   * @return void
1343   */
1344  function  glossary_print_entry_lower_section($course, $cm, $glossary, $entry, $mode, $hook, $printicons, $aliases=true) {
1345      if ($aliases) {
1346          $aliases = glossary_print_entry_aliases($course, $cm, $glossary, $entry, $mode, $hook,'html');
1347      }
1348      $icons   = '';
1349      if ($printicons) {
1350          $icons   = glossary_print_entry_icons($course, $cm, $glossary, $entry, $mode, $hook,'html');
1351      }
1352      if ($aliases || $icons || !empty($entry->rating)) {
1353          echo '<table>';
1354          if ( $aliases ) {
1355              $id = "keyword-{$entry->id}";
1356              echo '<tr valign="top"><td class="aliases">' .
1357                  '<label for="' . $id . '">' . get_string('aliases', 'glossary') . ': </label>' .
1358                  $aliases . '</td></tr>';
1359          }
1360          if ($icons) {
1361              echo '<tr valign="top"><td class="icons">'.$icons.'</td></tr>';
1362          }
1363          if (!empty($entry->rating)) {
1364              echo '<tr valign="top"><td class="ratings pt-3">';
1365              glossary_print_entry_ratings($course, $entry);
1366              echo '</td></tr>';
1367          }
1368          echo '</table>';
1369          echo "<hr>\n";
1370      }
1371  }
1372  
1373  /**
1374   * Print the list of attachments for this glossary entry
1375   *
1376   * @param object $entry
1377   * @param object $cm The coursemodule
1378   * @param string $format The format for this view (html, or text)
1379   * @param string $unused1 This parameter is no longer used
1380   * @param string $unused2 This parameter is no longer used
1381   */
1382  function glossary_print_entry_attachment($entry, $cm, $format = null, $unused1 = null, $unused2 = null) {
1383      // Valid format values: html: The HTML link for the attachment is an icon; and
1384      //                      text: The HTML link for the attachment is text.
1385      if ($entry->attachment) {
1386          echo '<div class="attachments">';
1387          echo glossary_print_attachments($entry, $cm, $format);
1388          echo '</div>';
1389      }
1390      if ($unused1) {
1391          debugging('The align parameter is deprecated, please use appropriate CSS instead', DEBUG_DEVELOPER);
1392      }
1393      if ($unused2 !== null) {
1394          debugging('The insidetable parameter is deprecated, please use appropriate CSS instead', DEBUG_DEVELOPER);
1395      }
1396  }
1397  
1398  /**
1399   * @global object
1400   * @param object $cm
1401   * @param object $entry
1402   * @param string $mode
1403   * @param string $align
1404   * @param bool $insidetable
1405   */
1406  function  glossary_print_entry_approval($cm, $entry, $mode, $align="right", $insidetable=true) {
1407      global $CFG, $OUTPUT;
1408  
1409      if ($mode == 'approval' and !$entry->approved) {
1410          if ($insidetable) {
1411              echo '<table class="glossaryapproval" align="'.$align.'"><tr><td align="'.$align.'">';
1412          }
1413          echo $OUTPUT->action_icon(
1414              new moodle_url('approve.php', array('eid' => $entry->id, 'mode' => $mode, 'sesskey' => sesskey())),
1415              new pix_icon('t/approve', get_string('approve','glossary'), '',
1416                  array('class' => 'iconsmall', 'align' => $align))
1417          );
1418          if ($insidetable) {
1419              echo '</td></tr></table>';
1420          }
1421      }
1422  }
1423  
1424  /**
1425   * It returns all entries from all glossaries that matches the specified criteria
1426   *  within a given $course. It performs an $extended search if necessary.
1427   * It restrict the search to only one $glossary if the $glossary parameter is set.
1428   *
1429   * @global object
1430   * @global object
1431   * @param object $course
1432   * @param array $searchterms
1433   * @param int $extended
1434   * @param object $glossary
1435   * @return array
1436   */
1437  function glossary_search($course, $searchterms, $extended = 0, $glossary = NULL) {
1438      global $CFG, $DB;
1439  
1440      if ( !$glossary ) {
1441          if ( $glossaries = $DB->get_records("glossary", array("course"=>$course->id)) ) {
1442              $glos = "";
1443              foreach ( $glossaries as $glossary ) {
1444                  $glos .= "$glossary->id,";
1445              }
1446              $glos = substr($glos,0,-1);
1447          }
1448      } else {
1449          $glos = $glossary->id;
1450      }
1451  
1452      if (!has_capability('mod/glossary:manageentries', context_course::instance($glossary->course))) {
1453          $glossarymodule = $DB->get_record("modules", array("name"=>"glossary"));
1454          $onlyvisible = " AND g.id = cm.instance AND cm.visible = 1 AND cm.module = $glossarymodule->id";
1455          $onlyvisibletable = ", {course_modules} cm";
1456      } else {
1457  
1458          $onlyvisible = "";
1459          $onlyvisibletable = "";
1460      }
1461  
1462      if ($DB->sql_regex_supported()) {
1463          $REGEXP    = $DB->sql_regex(true);
1464          $NOTREGEXP = $DB->sql_regex(false);
1465      }
1466  
1467      $searchcond = array();
1468      $params     = array();
1469      $i = 0;
1470  
1471      $concat = $DB->sql_concat('e.concept', "' '", 'e.definition');
1472  
1473  
1474      foreach ($searchterms as $searchterm) {
1475          $i++;
1476  
1477          $NOT = false; /// Initially we aren't going to perform NOT LIKE searches, only MSSQL and Oracle
1478                     /// will use it to simulate the "-" operator with LIKE clause
1479  
1480      /// Under Oracle and MSSQL, trim the + and - operators and perform
1481      /// simpler LIKE (or NOT LIKE) queries
1482          if (!$DB->sql_regex_supported()) {
1483              if (substr($searchterm, 0, 1) == '-') {
1484                  $NOT = true;
1485              }
1486              $searchterm = trim($searchterm, '+-');
1487          }
1488  
1489          // TODO: +- may not work for non latin languages
1490  
1491          if (substr($searchterm,0,1) == '+') {
1492              $searchterm = trim($searchterm, '+-');
1493              $searchterm = preg_quote($searchterm, '|');
1494              $searchcond[] = "$concat $REGEXP :ss$i";
1495              $params['ss'.$i] = "(^|[^a-zA-Z0-9])$searchterm([^a-zA-Z0-9]|$)";
1496  
1497          } else if (substr($searchterm,0,1) == "-") {
1498              $searchterm = trim($searchterm, '+-');
1499              $searchterm = preg_quote($searchterm, '|');
1500              $searchcond[] = "$concat $NOTREGEXP :ss$i";
1501              $params['ss'.$i] = "(^|[^a-zA-Z0-9])$searchterm([^a-zA-Z0-9]|$)";
1502  
1503          } else {
1504              $searchcond[] = $DB->sql_like($concat, ":ss$i", false, true, $NOT);
1505              $params['ss'.$i] = "%$searchterm%";
1506          }
1507      }
1508  
1509      if (empty($searchcond)) {
1510          $totalcount = 0;
1511          return array();
1512      }
1513  
1514      $searchcond = implode(" AND ", $searchcond);
1515  
1516      $sql = "SELECT e.*
1517                FROM {glossary_entries} e, {glossary} g $onlyvisibletable
1518               WHERE $searchcond
1519                 AND (e.glossaryid = g.id or e.sourceglossaryid = g.id) $onlyvisible
1520                 AND g.id IN ($glos) AND e.approved <> 0";
1521  
1522      return $DB->get_records_sql($sql, $params);
1523  }
1524  
1525  /**
1526   * @global object
1527   * @param array $searchterms
1528   * @param object $glossary
1529   * @param bool $extended
1530   * @return array
1531   */
1532  function glossary_search_entries($searchterms, $glossary, $extended) {
1533      global $DB;
1534  
1535      $course = $DB->get_record("course", array("id"=>$glossary->course));
1536      return glossary_search($course,$searchterms,$extended,$glossary);
1537  }
1538  
1539  /**
1540   * if return=html, then return a html string.
1541   * if return=text, then return a text-only string.
1542   * otherwise, print HTML for non-images, and return image HTML
1543   *     if attachment is an image, $align set its aligment.
1544   *
1545   * @global object
1546   * @global object
1547   * @param object $entry
1548   * @param object $cm
1549   * @param string $type html, txt, empty
1550   * @param string $unused This parameter is no longer used
1551   * @return string image string or nothing depending on $type param
1552   */
1553  function glossary_print_attachments($entry, $cm, $type=NULL, $unused = null) {
1554      global $CFG, $DB, $OUTPUT;
1555  
1556      if (!$context = context_module::instance($cm->id, IGNORE_MISSING)) {
1557          return '';
1558      }
1559  
1560      if ($entry->sourceglossaryid == $cm->instance) {
1561          if (!$maincm = get_coursemodule_from_instance('glossary', $entry->glossaryid)) {
1562              return '';
1563          }
1564          $filecontext = context_module::instance($maincm->id);
1565  
1566      } else {
1567          $filecontext = $context;
1568      }
1569  
1570      $strattachment = get_string('attachment', 'glossary');
1571  
1572      $fs = get_file_storage();
1573  
1574      $imagereturn = '';
1575      $output = '';
1576  
1577      if ($files = $fs->get_area_files($filecontext->id, 'mod_glossary', 'attachment', $entry->id, "timemodified", false)) {
1578          foreach ($files as $file) {
1579              $filename = $file->get_filename();
1580              $mimetype = $file->get_mimetype();
1581              $iconimage = $OUTPUT->pix_icon(file_file_icon($file), get_mimetype_description($file), 'moodle', array('class' => 'icon'));
1582              $path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$context->id.'/mod_glossary/attachment/'.$entry->id.'/'.$filename);
1583  
1584              if ($type == 'html') {
1585                  $output .= "<a href=\"$path\">$iconimage</a> ";
1586                  $output .= "<a href=\"$path\">".s($filename)."</a>";
1587                  $output .= "<br />";
1588  
1589              } else if ($type == 'text') {
1590                  $output .= "$strattachment ".s($filename).":\n$path\n";
1591  
1592              } else {
1593                  if (in_array($mimetype, array('image/gif', 'image/jpeg', 'image/png'))) {
1594                      // Image attachments don't get printed as links
1595                      $imagereturn .= "<br /><img src=\"$path\" alt=\"\" />";
1596                  } else {
1597                      $output .= "<a href=\"$path\">$iconimage</a> ";
1598                      $output .= format_text("<a href=\"$path\">".s($filename)."</a>", FORMAT_HTML, array('context'=>$context));
1599                      $output .= '<br />';
1600                  }
1601              }
1602          }
1603      }
1604  
1605      if ($type) {
1606          return $output;
1607      } else {
1608          echo $output;
1609          return $imagereturn;
1610      }
1611  }
1612  
1613  ////////////////////////////////////////////////////////////////////////////////
1614  // File API                                                                   //
1615  ////////////////////////////////////////////////////////////////////////////////
1616  
1617  /**
1618   * Lists all browsable file areas
1619   *
1620   * @package  mod_glossary
1621   * @category files
1622   * @param stdClass $course course object
1623   * @param stdClass $cm course module object
1624   * @param stdClass $context context object
1625   * @return array
1626   */
1627  function glossary_get_file_areas($course, $cm, $context) {
1628      return array(
1629          'attachment' => get_string('areaattachment', 'mod_glossary'),
1630          'entry' => get_string('areaentry', 'mod_glossary'),
1631      );
1632  }
1633  
1634  /**
1635   * File browsing support for glossary module.
1636   *
1637   * @param file_browser $browser
1638   * @param array $areas
1639   * @param stdClass $course
1640   * @param cm_info $cm
1641   * @param context $context
1642   * @param string $filearea
1643   * @param int $itemid
1644   * @param string $filepath
1645   * @param string $filename
1646   * @return file_info_stored file_info_stored instance or null if not found
1647   */
1648  function glossary_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
1649      global $CFG, $DB, $USER;
1650  
1651      if ($context->contextlevel != CONTEXT_MODULE) {
1652          return null;
1653      }
1654  
1655      if (!isset($areas[$filearea])) {
1656          return null;
1657      }
1658  
1659      if (is_null($itemid)) {
1660          require_once($CFG->dirroot.'/mod/glossary/locallib.php');
1661          return new glossary_file_info_container($browser, $course, $cm, $context, $areas, $filearea);
1662      }
1663  
1664      if (!$entry = $DB->get_record('glossary_entries', array('id' => $itemid))) {
1665          return null;
1666      }
1667  
1668      if (!$glossary = $DB->get_record('glossary', array('id' => $cm->instance))) {
1669          return null;
1670      }
1671  
1672      if ($glossary->defaultapproval and !$entry->approved and !has_capability('mod/glossary:approve', $context)) {
1673          return null;
1674      }
1675  
1676      // this trickery here is because we need to support source glossary access
1677      if ($entry->glossaryid == $cm->instance) {
1678          $filecontext = $context;
1679      } else if ($entry->sourceglossaryid == $cm->instance) {
1680          if (!$maincm = get_coursemodule_from_instance('glossary', $entry->glossaryid)) {
1681              return null;
1682          }
1683          $filecontext = context_module::instance($maincm->id);
1684      } else {
1685          return null;
1686      }
1687  
1688      $fs = get_file_storage();
1689      $filepath = is_null($filepath) ? '/' : $filepath;
1690      $filename = is_null($filename) ? '.' : $filename;
1691      if (!($storedfile = $fs->get_file($filecontext->id, 'mod_glossary', $filearea, $itemid, $filepath, $filename))) {
1692          return null;
1693      }
1694  
1695      // Checks to see if the user can manage files or is the owner.
1696      // TODO MDL-33805 - Do not use userid here and move the capability check above.
1697      if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
1698          return null;
1699      }
1700  
1701      $urlbase = $CFG->wwwroot.'/pluginfile.php';
1702  
1703      return new file_info_stored($browser, $filecontext, $storedfile, $urlbase, s($entry->concept), true, true, false, false);
1704  }
1705  
1706  /**
1707   * Serves the glossary attachments. Implements needed access control ;-)
1708   *
1709   * @package  mod_glossary
1710   * @category files
1711   * @param stdClass $course course object
1712   * @param stdClass $cm course module object
1713   * @param stdClsss $context context object
1714   * @param string $filearea file area
1715   * @param array $args extra arguments
1716   * @param bool $forcedownload whether or not force download
1717   * @param array $options additional options affecting the file serving
1718   * @return bool false if file not found, does not return if found - justsend the file
1719   */
1720  function glossary_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
1721      global $CFG, $DB;
1722  
1723      if ($context->contextlevel != CONTEXT_MODULE) {
1724          return false;
1725      }
1726  
1727      require_course_login($course, true, $cm);
1728  
1729      if ($filearea === 'attachment' or $filearea === 'entry') {
1730          $entryid = (int)array_shift($args);
1731  
1732          require_course_login($course, true, $cm);
1733  
1734          if (!$entry = $DB->get_record('glossary_entries', array('id'=>$entryid))) {
1735              return false;
1736          }
1737  
1738          if (!$glossary = $DB->get_record('glossary', array('id'=>$cm->instance))) {
1739              return false;
1740          }
1741  
1742          if ($glossary->defaultapproval and !$entry->approved and !has_capability('mod/glossary:approve', $context)) {
1743              return false;
1744          }
1745  
1746          // this trickery here is because we need to support source glossary access
1747  
1748          if ($entry->glossaryid == $cm->instance) {
1749              $filecontext = $context;
1750  
1751          } else if ($entry->sourceglossaryid == $cm->instance) {
1752              if (!$maincm = get_coursemodule_from_instance('glossary', $entry->glossaryid)) {
1753                  return false;
1754              }
1755              $filecontext = context_module::instance($maincm->id);
1756  
1757          } else {
1758              return false;
1759          }
1760  
1761          $relativepath = implode('/', $args);
1762          $fullpath = "/$filecontext->id/mod_glossary/$filearea/$entryid/$relativepath";
1763  
1764          $fs = get_file_storage();
1765          if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1766              return false;
1767          }
1768  
1769          // finally send the file
1770          send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security!
1771  
1772      } else if ($filearea === 'export') {
1773          require_login($course, false, $cm);
1774          require_capability('mod/glossary:export', $context);
1775  
1776          if (!$glossary = $DB->get_record('glossary', array('id'=>$cm->instance))) {
1777              return false;
1778          }
1779  
1780          $cat = array_shift($args);
1781          $cat = clean_param($cat, PARAM_ALPHANUM);
1782  
1783          $filename = clean_filename(strip_tags(format_string($glossary->name)).'.xml');
1784          $content = glossary_generate_export_file($glossary, NULL, $cat);
1785  
1786          send_file($content, $filename, 0, 0, true, true);
1787      }
1788  
1789      return false;
1790  }
1791  
1792  /**
1793   *
1794   */
1795  function glossary_print_tabbed_table_end() {
1796       echo "</div></div>";
1797  }
1798  
1799  /**
1800   * @param object $cm
1801   * @param object $glossary
1802   * @param string $mode
1803   * @param string $hook
1804   * @param string $sortkey
1805   * @param string $sortorder
1806   */
1807  function glossary_print_approval_menu($cm, $glossary,$mode, $hook, $sortkey = '', $sortorder = '') {
1808      if ($glossary->showalphabet) {
1809          echo '<div class="glossaryexplain">' . get_string("explainalphabet","glossary") . '</div><br />';
1810      }
1811      glossary_print_special_links($cm, $glossary, $mode, $hook);
1812  
1813      glossary_print_alphabet_links($cm, $glossary, $mode, $hook,$sortkey, $sortorder);
1814  
1815      glossary_print_all_links($cm, $glossary, $mode, $hook);
1816  
1817      glossary_print_sorting_links($cm, $mode, 'CREATION', 'asc');
1818  }
1819  /**
1820   * @param object $cm
1821   * @param object $glossary
1822   * @param string $hook
1823   * @param string $sortkey
1824   * @param string $sortorder
1825   */
1826  function glossary_print_import_menu($cm, $glossary, $mode, $hook, $sortkey='', $sortorder = '') {
1827      echo '<div class="glossaryexplain">' . get_string("explainimport","glossary") . '</div>';
1828  }
1829  
1830  /**
1831   * @param object $cm
1832   * @param object $glossary
1833   * @param string $hook
1834   * @param string $sortkey
1835   * @param string $sortorder
1836   */
1837  function glossary_print_export_menu($cm, $glossary, $mode, $hook, $sortkey='', $sortorder = '') {
1838      echo '<div class="glossaryexplain">' . get_string("explainexport","glossary") . '</div>';
1839  }
1840  /**
1841   * @param object $cm
1842   * @param object $glossary
1843   * @param string $hook
1844   * @param string $sortkey
1845   * @param string $sortorder
1846   */
1847  function glossary_print_alphabet_menu($cm, $glossary, $mode, $hook, $sortkey='', $sortorder = '') {
1848      if ( $mode != 'date' ) {
1849          if ($glossary->showalphabet) {
1850              echo '<div class="glossaryexplain">' . get_string("explainalphabet","glossary") . '</div><br />';
1851          }
1852  
1853          glossary_print_special_links($cm, $glossary, $mode, $hook);
1854  
1855          glossary_print_alphabet_links($cm, $glossary, $mode, $hook, $sortkey, $sortorder);
1856  
1857          glossary_print_all_links($cm, $glossary, $mode, $hook);
1858      } else {
1859          glossary_print_sorting_links($cm, $mode, $sortkey,$sortorder);
1860      }
1861  }
1862  
1863  /**
1864   * @param object $cm
1865   * @param object $glossary
1866   * @param string $hook
1867   * @param string $sortkey
1868   * @param string $sortorder
1869   */
1870  function glossary_print_author_menu($cm, $glossary,$mode, $hook, $sortkey = '', $sortorder = '') {
1871      if ($glossary->showalphabet) {
1872          echo '<div class="glossaryexplain">' . get_string("explainalphabet","glossary") . '</div><br />';
1873      }
1874  
1875      glossary_print_alphabet_links($cm, $glossary, $mode, $hook, $sortkey, $sortorder);
1876      glossary_print_all_links($cm, $glossary, $mode, $hook);
1877      glossary_print_sorting_links($cm, $mode, $sortkey,$sortorder);
1878  }
1879  
1880  /**
1881   * @global object
1882   * @global object
1883   * @param object $cm
1884   * @param object $glossary
1885   * @param string $hook
1886   * @param object $category
1887   */
1888  function glossary_print_categories_menu($cm, $glossary, $hook, $category) {
1889       global $CFG, $DB, $OUTPUT;
1890  
1891       $context = context_module::instance($cm->id);
1892  
1893      // Prepare format_string/text options
1894      $fmtoptions = array(
1895          'context' => $context);
1896  
1897       echo '<table border="0" width="100%">';
1898       echo '<tr>';
1899  
1900       echo '<td align="center" style="width:20%">';
1901       if (has_capability('mod/glossary:managecategories', $context)) {
1902               $options['id'] = $cm->id;
1903               $options['mode'] = 'cat';
1904               $options['hook'] = $hook;
1905               echo $OUTPUT->single_button(new moodle_url("editcategories.php", $options), get_string("editcategories","glossary"), "get");
1906       }
1907       echo '</td>';
1908  
1909       echo '<td align="center" style="width:60%">';
1910       echo '<b>';
1911  
1912       $menu = array();
1913       $menu[GLOSSARY_SHOW_ALL_CATEGORIES] = get_string("allcategories","glossary");
1914       $menu[GLOSSARY_SHOW_NOT_CATEGORISED] = get_string("notcategorised","glossary");
1915  
1916       $categories = $DB->get_records("glossary_categories", array("glossaryid"=>$glossary->id), "name ASC");
1917       $selected = '';
1918       if ( $categories ) {
1919            foreach ($categories as $currentcategory) {
1920                   $url = $currentcategory->id;
1921                   if ( $category ) {
1922                       if ($currentcategory->id == $category->id) {
1923                           $selected = $url;
1924                       }
1925                   }
1926                   $menu[$url] = format_string($currentcategory->name, true, $fmtoptions);
1927            }
1928       }
1929       if ( !$selected ) {
1930           $selected = GLOSSARY_SHOW_NOT_CATEGORISED;
1931       }
1932  
1933       if ( $category ) {
1934          echo format_string($category->name, true, $fmtoptions);
1935       } else {
1936          if ( $hook == GLOSSARY_SHOW_NOT_CATEGORISED ) {
1937  
1938              echo get_string("entrieswithoutcategory","glossary");
1939              $selected = GLOSSARY_SHOW_NOT_CATEGORISED;
1940  
1941          } elseif ( $hook == GLOSSARY_SHOW_ALL_CATEGORIES ) {
1942  
1943              echo get_string("allcategories","glossary");
1944              $selected = GLOSSARY_SHOW_ALL_CATEGORIES;
1945  
1946          }
1947       }
1948       echo '</b></td>';
1949       echo '<td align="center" style="width:20%">';
1950  
1951       $select = new single_select(new moodle_url("/mod/glossary/view.php", array('id'=>$cm->id, 'mode'=>'cat')), 'hook', $menu, $selected, null, "catmenu");
1952       $select->set_label(get_string('categories', 'glossary'), array('class' => 'accesshide'));
1953       echo $OUTPUT->render($select);
1954  
1955       echo '</td>';
1956       echo '</tr>';
1957  
1958       echo '</table>';
1959  }
1960  
1961  /**
1962   * @global object
1963   * @param object $cm
1964   * @param object $glossary
1965   * @param string $mode
1966   * @param string $hook
1967   */
1968  function glossary_print_all_links($cm, $glossary, $mode, $hook) {
1969  global $CFG;
1970       if ( $glossary->showall) {
1971           $strallentries       = get_string("allentries", "glossary");
1972           if ( $hook == 'ALL' ) {
1973                echo "<b>$strallentries</b>";
1974           } else {
1975                $strexplainall = strip_tags(get_string("explainall","glossary"));
1976                echo "<a title=\"$strexplainall\" href=\"$CFG->wwwroot/mod/glossary/view.php?id=$cm->id&amp;mode=$mode&amp;hook=ALL\">$strallentries</a>";
1977           }
1978       }
1979  }
1980  
1981  /**
1982   * @global object
1983   * @param object $cm
1984   * @param object $glossary
1985   * @param string $mode
1986   * @param string $hook
1987   */
1988  function glossary_print_special_links($cm, $glossary, $mode, $hook) {
1989  global $CFG;
1990       if ( $glossary->showspecial) {
1991           $strspecial          = get_string("special", "glossary");
1992           if ( $hook == 'SPECIAL' ) {
1993                echo "<b>$strspecial</b> | ";
1994           } else {
1995                $strexplainspecial = strip_tags(get_string("explainspecial","glossary"));
1996                echo "<a title=\"$strexplainspecial\" href=\"$CFG->wwwroot/mod/glossary/view.php?id=$cm->id&amp;mode=$mode&amp;hook=SPECIAL\">$strspecial</a> | ";
1997           }
1998       }
1999  }
2000  
2001  /**
2002   * @global object
2003   * @param object $glossary
2004   * @param string $mode
2005   * @param string $hook
2006   * @param string $sortkey
2007   * @param string $sortorder
2008   */
2009  function glossary_print_alphabet_links($cm, $glossary, $mode, $hook, $sortkey, $sortorder) {
2010  global $CFG;
2011       if ( $glossary->showalphabet) {
2012            $alphabet = explode(",", get_string('alphabet', 'langconfig'));
2013            for ($i = 0; $i < count($alphabet); $i++) {
2014                if ( $hook == $alphabet[$i] and $hook) {
2015                     echo "<b>$alphabet[$i]</b>";
2016                } else {
2017                     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>";
2018                }
2019                echo ' | ';
2020            }
2021       }
2022  }
2023  
2024  /**
2025   * @global object
2026   * @param object $cm
2027   * @param string $mode
2028   * @param string $sortkey
2029   * @param string $sortorder
2030   */
2031  function glossary_print_sorting_links($cm, $mode, $sortkey = '',$sortorder = '') {
2032      global $CFG, $OUTPUT;
2033  
2034      $asc    = get_string("ascending","glossary");
2035      $desc   = get_string("descending","glossary");
2036      $bopen  = '<b>';
2037      $bclose = '</b>';
2038  
2039       $neworder = '';
2040       $currentorder = '';
2041       $currentsort = '';
2042       if ( $sortorder ) {
2043           if ( $sortorder == 'asc' ) {
2044               $currentorder = $asc;
2045               $neworder = '&amp;sortorder=desc';
2046               $newordertitle = get_string('changeto', 'glossary', $desc);
2047           } else {
2048               $currentorder = $desc;
2049               $neworder = '&amp;sortorder=asc';
2050               $newordertitle = get_string('changeto', 'glossary', $asc);
2051           }
2052           $icon = " " . $OUTPUT->pix_icon($sortorder, $newordertitle, 'glossary');
2053       } else {
2054           if ( $sortkey != 'CREATION' and $sortkey != 'UPDATE' and
2055                 $sortkey != 'FIRSTNAME' and $sortkey != 'LASTNAME' ) {
2056               $icon = "";
2057               $newordertitle = $asc;
2058           } else {
2059               $newordertitle = $desc;
2060               $neworder = '&amp;sortorder=desc';
2061               $icon = " " . $OUTPUT->pix_icon('asc', $newordertitle, 'glossary');
2062           }
2063       }
2064       $ficon     = '';
2065       $fneworder = '';
2066       $fbtag     = '';
2067       $fendbtag  = '';
2068  
2069       $sicon     = '';
2070       $sneworder = '';
2071  
2072       $sbtag      = '';
2073       $fbtag      = '';
2074       $fendbtag      = '';
2075       $sendbtag      = '';
2076  
2077       $sendbtag  = '';
2078  
2079       if ( $sortkey == 'CREATION' or $sortkey == 'FIRSTNAME' ) {
2080           $ficon       = $icon;
2081           $fneworder   = $neworder;
2082           $fordertitle = $newordertitle;
2083           $sordertitle = $asc;
2084           $fbtag       = $bopen;
2085           $fendbtag    = $bclose;
2086       } elseif ($sortkey == 'UPDATE' or $sortkey == 'LASTNAME') {
2087           $sicon = $icon;
2088           $sneworder   = $neworder;
2089           $fordertitle = $asc;
2090           $sordertitle = $newordertitle;
2091           $sbtag       = $bopen;
2092           $sendbtag    = $bclose;
2093       } else {
2094           $fordertitle = $asc;
2095           $sordertitle = $asc;
2096       }
2097  
2098       if ( $sortkey == 'CREATION' or $sortkey == 'UPDATE' ) {
2099           $forder = 'CREATION';
2100           $sorder =  'UPDATE';
2101           $fsort  = get_string("sortbycreation", "glossary");
2102           $ssort  = get_string("sortbylastupdate", "glossary");
2103  
2104           $currentsort = $fsort;
2105           if ($sortkey == 'UPDATE') {
2106               $currentsort = $ssort;
2107           }
2108           $sort        = get_string("sortchronogically", "glossary");
2109       } elseif ( $sortkey == 'FIRSTNAME' or $sortkey == 'LASTNAME') {
2110           $forder = 'FIRSTNAME';
2111           $sorder =  'LASTNAME';
2112           $fsort  = get_string("firstname");
2113           $ssort  = get_string("lastname");
2114  
2115           $currentsort = $fsort;
2116           if ($sortkey == 'LASTNAME') {
2117               $currentsort = $ssort;
2118           }
2119           $sort        = get_string("sortby", "glossary");
2120       }
2121       $current = '<span class="accesshide">'.get_string('current', 'glossary', "$currentsort $currentorder").'</span>';
2122       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 | ".
2123                            "$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 />";
2124  }
2125  
2126  /**
2127   *
2128   * @param object $entry0
2129   * @param object $entry1
2130   * @return int [-1 | 0 | 1]
2131   */
2132  function glossary_sort_entries ( $entry0, $entry1 ) {
2133  
2134      if ( core_text::strtolower(ltrim($entry0->concept)) < core_text::strtolower(ltrim($entry1->concept)) ) {
2135          return -1;
2136      } elseif ( core_text::strtolower(ltrim($entry0->concept)) > core_text::strtolower(ltrim($entry1->concept)) ) {
2137          return 1;
2138      } else {
2139          return 0;
2140      }
2141  }
2142  
2143  
2144  /**
2145   * @global object
2146   * @global object
2147   * @global object
2148   * @param object $course
2149   * @param object $entry
2150   * @return bool
2151   */
2152  function  glossary_print_entry_ratings($course, $entry) {
2153      global $OUTPUT;
2154      if( !empty($entry->rating) ){
2155          echo $OUTPUT->render($entry->rating);
2156      }
2157  }
2158  
2159  /**
2160   *
2161   * @global object
2162   * @global object
2163   * @global object
2164   * @param int $courseid
2165   * @param array $entries
2166   * @param int $displayformat
2167   */
2168  function glossary_print_dynaentry($courseid, $entries, $displayformat = -1) {
2169      global $USER, $CFG, $DB;
2170  
2171      echo '<div class="boxaligncenter">';
2172      echo '<table class="glossarypopup" cellspacing="0"><tr>';
2173      echo '<td>';
2174      if ( $entries ) {
2175          foreach ( $entries as $entry ) {
2176              if (! $glossary = $DB->get_record('glossary', array('id'=>$entry->glossaryid))) {
2177                  print_error('invalidid', 'glossary');
2178              }
2179              if (! $course = $DB->get_record('course', array('id'=>$glossary->course))) {
2180                  print_error('coursemisconf');
2181              }
2182              if (!$cm = get_coursemodule_from_instance('glossary', $entry->glossaryid, $glossary->course) ) {
2183                  print_error('invalidid', 'glossary');
2184              }
2185  
2186              //If displayformat is present, override glossary->displayformat
2187              if ($displayformat < 0) {
2188                  $dp = $glossary->displayformat;
2189              } else {
2190                  $dp = $displayformat;
2191              }
2192  
2193              //Get popupformatname
2194              $format = $DB->get_record('glossary_formats', array('name'=>$dp));
2195              $displayformat = $format->popupformatname;
2196  
2197              //Check displayformat variable and set to default if necessary
2198              if (!$displayformat) {
2199                  $displayformat = 'dictionary';
2200              }
2201  
2202              $formatfile = $CFG->dirroot.'/mod/glossary/formats/'.$displayformat.'/'.$displayformat.'_format.php';
2203              $functionname = 'glossary_show_entry_'.$displayformat;
2204  
2205              if (file_exists($formatfile)) {
2206                  include_once($formatfile);
2207                  if (function_exists($functionname)) {
2208                      $functionname($course, $cm, $glossary, $entry,'','','','');
2209                  }
2210              }
2211          }
2212      }
2213      echo '</td>';
2214      echo '</tr></table></div>';
2215  }
2216  
2217  /**
2218   *
2219   * @global object
2220   * @param array $entries
2221   * @param array $aliases
2222   * @param array $categories
2223   * @return string
2224   */
2225  function glossary_generate_export_csv($entries, $aliases, $categories) {
2226      global $CFG;
2227      $csv = '';
2228      $delimiter = '';
2229      require_once($CFG->libdir . '/csvlib.class.php');
2230      $delimiter = csv_import_reader::get_delimiter('comma');
2231      $csventries = array(0 => array(get_string('concept', 'glossary'), get_string('definition', 'glossary')));
2232      $csvaliases = array(0 => array());
2233      $csvcategories = array(0 => array());
2234      $aliascount = 0;
2235      $categorycount = 0;
2236  
2237      foreach ($entries as $entry) {
2238          $thisaliasesentry = array();
2239          $thiscategoriesentry = array();
2240          $thiscsventry = array($entry->concept, nl2br($entry->definition));
2241  
2242          if (array_key_exists($entry->id, $aliases) && is_array($aliases[$entry->id])) {
2243              $thiscount = count($aliases[$entry->id]);
2244              if ($thiscount > $aliascount) {
2245                  $aliascount = $thiscount;
2246              }
2247              foreach ($aliases[$entry->id] as $alias) {
2248                  $thisaliasesentry[] = trim($alias);
2249              }
2250          }
2251          if (array_key_exists($entry->id, $categories) && is_array($categories[$entry->id])) {
2252              $thiscount = count($categories[$entry->id]);
2253              if ($thiscount > $categorycount) {
2254                  $categorycount = $thiscount;
2255              }
2256              foreach ($categories[$entry->id] as $catentry) {
2257                  $thiscategoriesentry[] = trim($catentry);
2258              }
2259          }
2260          $csventries[$entry->id] = $thiscsventry;
2261          $csvaliases[$entry->id] = $thisaliasesentry;
2262          $csvcategories[$entry->id] = $thiscategoriesentry;
2263  
2264      }
2265      $returnstr = '';
2266      foreach ($csventries as $id => $row) {
2267          $aliasstr = '';
2268          $categorystr = '';
2269          if ($id == 0) {
2270              $aliasstr = get_string('alias', 'glossary');
2271              $categorystr = get_string('category', 'glossary');
2272          }
2273          $row = array_merge($row, array_pad($csvaliases[$id], $aliascount, $aliasstr), array_pad($csvcategories[$id], $categorycount, $categorystr));
2274          $returnstr .= '"' . implode('"' . $delimiter . '"', $row) . '"' . "\n";
2275      }
2276      return $returnstr;
2277  }
2278  
2279  /**
2280   *
2281   * @param object $glossary
2282   * @param string $ignored invalid parameter
2283   * @param int|string $hook
2284   * @return string
2285   */
2286  function glossary_generate_export_file($glossary, $ignored = "", $hook = 0) {
2287      global $CFG, $DB;
2288  
2289      // Large exports are likely to take their time and memory.
2290      core_php_time_limit::raise();
2291      raise_memory_limit(MEMORY_EXTRA);
2292  
2293      $cm = get_coursemodule_from_instance('glossary', $glossary->id, $glossary->course);
2294      $context = context_module::instance($cm->id);
2295  
2296      $co  = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
2297  
2298      $co .= glossary_start_tag("GLOSSARY",0,true);
2299      $co .= glossary_start_tag("INFO",1,true);
2300          $co .= glossary_full_tag("NAME",2,false,$glossary->name);
2301          $co .= glossary_full_tag("INTRO",2,false,$glossary->intro);
2302          $co .= glossary_full_tag("INTROFORMAT",2,false,$glossary->introformat);
2303          $co .= glossary_full_tag("ALLOWDUPLICATEDENTRIES",2,false,$glossary->allowduplicatedentries);
2304          $co .= glossary_full_tag("DISPLAYFORMAT",2,false,$glossary->displayformat);
2305          $co .= glossary_full_tag("SHOWSPECIAL",2,false,$glossary->showspecial);
2306          $co .= glossary_full_tag("SHOWALPHABET",2,false,$glossary->showalphabet);
2307          $co .= glossary_full_tag("SHOWALL",2,false,$glossary->showall);
2308          $co .= glossary_full_tag("ALLOWCOMMENTS",2,false,$glossary->allowcomments);
2309          $co .= glossary_full_tag("USEDYNALINK",2,false,$glossary->usedynalink);
2310          $co .= glossary_full_tag("DEFAULTAPPROVAL",2,false,$glossary->defaultapproval);
2311          $co .= glossary_full_tag("GLOBALGLOSSARY",2,false,$glossary->globalglossary);
2312          $co .= glossary_full_tag("ENTBYPAGE",2,false,$glossary->entbypage);
2313          $co .= glossary_xml_export_files('INTROFILES', 2, $context->id, 'intro', 0);
2314  
2315          if ( $entries = $DB->get_records("glossary_entries", array("glossaryid"=>$glossary->id))) {
2316              $co .= glossary_start_tag("ENTRIES",2,true);
2317              foreach ($entries as $entry) {
2318                  $permissiongranted = 1;
2319                  if ( $hook ) {
2320                      switch ( $hook ) {
2321                      case "ALL":
2322                      case "SPECIAL":
2323                      break;
2324                      default:
2325                          $permissiongranted = ($entry->concept[ strlen($hook)-1 ] == $hook);
2326                      break;
2327                      }
2328                  }
2329                  if ( $hook ) {
2330                      switch ( $hook ) {
2331                      case GLOSSARY_SHOW_ALL_CATEGORIES:
2332                      break;
2333                      case GLOSSARY_SHOW_NOT_CATEGORISED:
2334                          $permissiongranted = !$DB->record_exists("glossary_entries_categories", array("entryid"=>$entry->id));
2335                      break;
2336                      default:
2337                          $permissiongranted = $DB->record_exists("glossary_entries_categories", array("entryid"=>$entry->id, "categoryid"=>$hook));
2338                      break;
2339                      }
2340                  }
2341                  if ( $entry->approved and $permissiongranted ) {
2342                      $co .= glossary_start_tag("ENTRY",3,true);
2343                      $co .= glossary_full_tag("CONCEPT",4,false,trim($entry->concept));
2344                      $co .= glossary_full_tag("DEFINITION",4,false,$entry->definition);
2345                      $co .= glossary_full_tag("FORMAT",4,false,$entry->definitionformat); // note: use old name for BC reasons
2346                      $co .= glossary_full_tag("USEDYNALINK",4,false,$entry->usedynalink);
2347                      $co .= glossary_full_tag("CASESENSITIVE",4,false,$entry->casesensitive);
2348                      $co .= glossary_full_tag("FULLMATCH",4,false,$entry->fullmatch);
2349                      $co .= glossary_full_tag("TEACHERENTRY",4,false,$entry->teacherentry);
2350  
2351                      if ( $aliases = $DB->get_records("glossary_alias", array("entryid"=>$entry->id))) {
2352                          $co .= glossary_start_tag("ALIASES",4,true);
2353                          foreach ($aliases as $alias) {
2354                              $co .= glossary_start_tag("ALIAS",5,true);
2355                                  $co .= glossary_full_tag("NAME",6,false,trim($alias->alias));
2356                              $co .= glossary_end_tag("ALIAS",5,true);
2357                          }
2358                          $co .= glossary_end_tag("ALIASES",4,true);
2359                      }
2360                      if ( $catentries = $DB->get_records("glossary_entries_categories", array("entryid"=>$entry->id))) {
2361                          $co .= glossary_start_tag("CATEGORIES",4,true);
2362                          foreach ($catentries as $catentry) {
2363                              $category = $DB->get_record("glossary_categories", array("id"=>$catentry->categoryid));
2364  
2365                              $co .= glossary_start_tag("CATEGORY",5,true);
2366                                  $co .= glossary_full_tag("NAME",6,false,$category->name);
2367                                  $co .= glossary_full_tag("USEDYNALINK",6,false,$category->usedynalink);
2368                              $co .= glossary_end_tag("CATEGORY",5,true);
2369                          }
2370                          $co .= glossary_end_tag("CATEGORIES",4,true);
2371                      }
2372  
2373                      // Export files embedded in entries.
2374                      $co .= glossary_xml_export_files('ENTRYFILES', 4, $context->id, 'entry', $entry->id);
2375  
2376                      // Export attachments.
2377                      $co .= glossary_xml_export_files('ATTACHMENTFILES', 4, $context->id, 'attachment', $entry->id);
2378  
2379                      // Export tags.
2380                      $tags = core_tag_tag::get_item_tags_array('mod_glossary', 'glossary_entries', $entry->id);
2381                      if (count($tags)) {
2382                          $co .= glossary_start_tag("TAGS", 4, true);
2383                          foreach ($tags as $tag) {
2384                              $co .= glossary_full_tag("TAG", 5, false, $tag);
2385                          }
2386                          $co .= glossary_end_tag("TAGS", 4, true);
2387                      }
2388  
2389                      $co .= glossary_end_tag("ENTRY",3,true);
2390                  }
2391              }
2392              $co .= glossary_end_tag("ENTRIES",2,true);
2393  
2394          }
2395  
2396  
2397      $co .= glossary_end_tag("INFO",1,true);
2398      $co .= glossary_end_tag("GLOSSARY",0,true);
2399  
2400      return $co;
2401  }
2402  /// Functions designed by Eloy Lafuente
2403  /// Functions to create, open and write header of the xml file
2404  
2405  /**
2406   * Read import file and convert to current charset
2407   *
2408   * @global object
2409   * @param string $file
2410   * @return string
2411   */
2412  function glossary_read_imported_file($file_content) {
2413      global $CFG;
2414      require_once  "../../lib/xmlize.php";
2415  
2416      return xmlize($file_content, 0);
2417  }
2418  
2419  /**
2420   * Return the xml start tag
2421   *
2422   * @param string $tag
2423   * @param int $level
2424   * @param bool $endline
2425   * @return string
2426   */
2427  function glossary_start_tag($tag,$level=0,$endline=false) {
2428          if ($endline) {
2429             $endchar = "\n";
2430          } else {
2431             $endchar = "";
2432          }
2433          return str_repeat(" ",$level*2)."<".strtoupper($tag).">".$endchar;
2434  }
2435  
2436  /**
2437   * Return the xml end tag
2438   * @param string $tag
2439   * @param int $level
2440   * @param bool $endline
2441   * @return string
2442   */
2443  function glossary_end_tag($tag,$level=0,$endline=true) {
2444          if ($endline) {
2445             $endchar = "\n";
2446          } else {
2447             $endchar = "";
2448          }
2449          return str_repeat(" ",$level*2)."</".strtoupper($tag).">".$endchar;
2450  }
2451  
2452  /**
2453   * Return the start tag, the contents and the end tag
2454   *
2455   * @global object
2456   * @param string $tag
2457   * @param int $level
2458   * @param bool $endline
2459   * @param string $content
2460   * @return string
2461   */
2462  function glossary_full_tag($tag,$level=0,$endline=true,$content) {
2463          global $CFG;
2464  
2465          $st = glossary_start_tag($tag,$level,$endline);
2466          $co = preg_replace("/\r\n|\r/", "\n", s($content));
2467          $et = glossary_end_tag($tag,0,true);
2468          return $st.$co.$et;
2469  }
2470  
2471  /**
2472   * Prepares file area to export as part of XML export
2473   *
2474   * @param string $tag XML tag to use for the group
2475   * @param int $taglevel
2476   * @param int $contextid
2477   * @param string $filearea
2478   * @param int $itemid
2479   * @return string
2480   */
2481  function glossary_xml_export_files($tag, $taglevel, $contextid, $filearea, $itemid) {
2482      $co = '';
2483      $fs = get_file_storage();
2484      if ($files = $fs->get_area_files(
2485          $contextid, 'mod_glossary', $filearea, $itemid, 'itemid,filepath,filename', false)) {
2486          $co .= glossary_start_tag($tag, $taglevel, true);
2487          foreach ($files as $file) {
2488              $co .= glossary_start_tag('FILE', $taglevel + 1, true);
2489              $co .= glossary_full_tag('FILENAME', $taglevel + 2, false, $file->get_filename());
2490              $co .= glossary_full_tag('FILEPATH', $taglevel + 2, false, $file->get_filepath());
2491              $co .= glossary_full_tag('CONTENTS', $taglevel + 2, false, base64_encode($file->get_content()));
2492              $co .= glossary_full_tag('FILEAUTHOR', $taglevel + 2, false, $file->get_author());
2493              $co .= glossary_full_tag('FILELICENSE', $taglevel + 2, false, $file->get_license());
2494              $co .= glossary_end_tag('FILE', $taglevel + 1);
2495          }
2496          $co .= glossary_end_tag($tag, $taglevel);
2497      }
2498      return $co;
2499  }
2500  
2501  /**
2502   * Parses files from XML import and inserts them into file system
2503   *
2504   * @param array $xmlparent parent element in parsed XML tree
2505   * @param string $tag
2506   * @param int $contextid
2507   * @param string $filearea
2508   * @param int $itemid
2509   * @return int
2510   */
2511  function glossary_xml_import_files($xmlparent, $tag, $contextid, $filearea, $itemid) {
2512      global $USER, $CFG;
2513      $count = 0;
2514      if (isset($xmlparent[$tag][0]['#']['FILE'])) {
2515          $fs = get_file_storage();
2516          $files = $xmlparent[$tag][0]['#']['FILE'];
2517          foreach ($files as $file) {
2518              $filerecord = array(
2519                  'contextid' => $contextid,
2520                  'component' => 'mod_glossary',
2521                  'filearea'  => $filearea,
2522                  'itemid'    => $itemid,
2523                  'filepath'  => $file['#']['FILEPATH'][0]['#'],
2524                  'filename'  => $file['#']['FILENAME'][0]['#'],
2525                  'userid'    => $USER->id
2526              );
2527              if (array_key_exists('FILEAUTHOR', $file['#'])) {
2528                  $filerecord['author'] = $file['#']['FILEAUTHOR'][0]['#'];
2529              }
2530              if (array_key_exists('FILELICENSE', $file['#'])) {
2531                  $license = $file['#']['FILELICENSE'][0]['#'];
2532                  require_once($CFG->libdir . "/licenselib.php");
2533                  if (license_manager::get_license_by_shortname($license)) {
2534                      $filerecord['license'] = $license;
2535                  }
2536              }
2537              $content =  $file['#']['CONTENTS'][0]['#'];
2538              $fs->create_file_from_string($filerecord, base64_decode($content));
2539              $count++;
2540          }
2541      }
2542      return $count;
2543  }
2544  
2545  /**
2546   * How many unrated entries are in the given glossary for a given user?
2547   *
2548   * @global moodle_database $DB
2549   * @param int $glossaryid
2550   * @param int $userid
2551   * @return int
2552   */
2553  function glossary_count_unrated_entries($glossaryid, $userid) {
2554      global $DB;
2555  
2556      $sql = "SELECT COUNT('x') as num
2557                FROM {glossary_entries}
2558               WHERE glossaryid = :glossaryid AND
2559                     userid <> :userid";
2560      $params = array('glossaryid' => $glossaryid, 'userid' => $userid);
2561      $entries = $DB->count_records_sql($sql, $params);
2562  
2563      if ($entries) {
2564          // We need to get the contextid for the glossaryid we have been given.
2565          $sql = "SELECT ctx.id
2566                    FROM {context} ctx
2567                    JOIN {course_modules} cm ON cm.id = ctx.instanceid
2568                    JOIN {modules} m ON m.id = cm.module
2569                    JOIN {glossary} g ON g.id = cm.instance
2570                   WHERE ctx.contextlevel = :contextlevel AND
2571                         m.name = 'glossary' AND
2572                         g.id = :glossaryid";
2573          $contextid = $DB->get_field_sql($sql, array('glossaryid' => $glossaryid, 'contextlevel' => CONTEXT_MODULE));
2574  
2575          // Now we need to count the ratings that this user has made
2576          $sql = "SELECT COUNT('x') AS num
2577                    FROM {glossary_entries} e
2578                    JOIN {rating} r ON r.itemid = e.id
2579                   WHERE e.glossaryid = :glossaryid AND
2580                         r.userid = :userid AND
2581                         r.component = 'mod_glossary' AND
2582                         r.ratingarea = 'entry' AND
2583                         r.contextid = :contextid";
2584          $params = array('glossaryid' => $glossaryid, 'userid' => $userid, 'contextid' => $contextid);
2585          $rated = $DB->count_records_sql($sql, $params);
2586          if ($rated) {
2587              // The number or enties minus the number or rated entries equals the number of unrated
2588              // entries
2589              if ($entries > $rated) {
2590                  return $entries - $rated;
2591              } else {
2592                  return 0;    // Just in case there was a counting error
2593              }
2594          } else {
2595              return (int)$entries;
2596          }
2597      } else {
2598          return 0;
2599      }
2600  }
2601  
2602  /**
2603   *
2604   * Returns the html code to represent any pagging bar. Paramenters are:
2605   *
2606   * The function dinamically show the first and last pages, and "scroll" over pages.
2607   * Fully compatible with Moodle's print_paging_bar() function. Perhaps some day this
2608   * could replace the general one. ;-)
2609   *
2610   * @param int $totalcount total number of records to be displayed
2611   * @param int $page page currently selected (0 based)
2612   * @param int $perpage number of records per page
2613   * @param string $baseurl url to link in each page, the string 'page=XX' will be added automatically.
2614   *
2615   * @param int $maxpageallowed Optional maximum number of page allowed.
2616   * @param int $maxdisplay Optional maximum number of page links to show in the bar
2617   * @param string $separator Optional string to be used between pages in the bar
2618   * @param string $specialtext Optional string to be showed as an special link
2619   * @param string $specialvalue Optional value (page) to be used in the special link
2620   * @param bool $previousandnext Optional to decide if we want the previous and next links
2621   * @return string
2622   */
2623  function glossary_get_paging_bar($totalcount, $page, $perpage, $baseurl, $maxpageallowed=99999, $maxdisplay=20, $separator="&nbsp;", $specialtext="", $specialvalue=-1, $previousandnext = true) {
2624  
2625      $code = '';
2626  
2627      $showspecial = false;
2628      $specialselected = false;
2629  
2630      //Check if we have to show the special link
2631      if (!empty($specialtext)) {
2632          $showspecial = true;
2633      }
2634      //Check if we are with the special link selected
2635      if ($showspecial && $page == $specialvalue) {
2636          $specialselected = true;
2637      }
2638  
2639      //If there are results (more than 1 page)
2640      if ($totalcount > $perpage) {
2641          $code .= "<div style=\"text-align:center\">";
2642          $code .= "<p>".get_string("page").":";
2643  
2644          $maxpage = (int)(($totalcount-1)/$perpage);
2645  
2646          //Lower and upper limit of page
2647          if ($page < 0) {
2648              $page = 0;
2649          }
2650          if ($page > $maxpageallowed) {
2651              $page = $maxpageallowed;
2652          }
2653          if ($page > $maxpage) {
2654              $page = $maxpage;
2655          }
2656  
2657          //Calculate the window of pages
2658          $pagefrom = $page - ((int)($maxdisplay / 2));
2659          if ($pagefrom < 0) {
2660              $pagefrom = 0;
2661          }
2662          $pageto = $pagefrom + $maxdisplay - 1;
2663          if ($pageto > $maxpageallowed) {
2664              $pageto = $maxpageallowed;
2665          }
2666          if ($pageto > $maxpage) {
2667              $pageto = $maxpage;
2668          }
2669  
2670          //Some movements can be necessary if don't see enought pages
2671          if ($pageto - $pagefrom < $maxdisplay - 1) {
2672              if ($pageto - $maxdisplay + 1 > 0) {
2673                  $pagefrom = $pageto - $maxdisplay + 1;
2674              }
2675          }
2676  
2677          //Calculate first and last if necessary
2678          $firstpagecode = '';
2679          $lastpagecode = '';
2680          if ($pagefrom > 0) {
2681              $firstpagecode = "$separator<a href=\"{$baseurl}page=0\">1</a>";
2682              if ($pagefrom > 1) {
2683                  $firstpagecode .= "$separator...";
2684              }
2685          }
2686          if ($pageto < $maxpage) {
2687              if ($pageto < $maxpage -1) {
2688                  $lastpagecode = "$separator...";
2689              }
2690              $lastpagecode .= "$separator<a href=\"{$baseurl}page=$maxpage\">".($maxpage+1)."</a>";
2691          }
2692  
2693          //Previous
2694          if ($page > 0 && $previousandnext) {
2695              $pagenum = $page - 1;
2696              $code .= "&nbsp;(<a  href=\"{$baseurl}page=$pagenum\">".get_string("previous")."</a>)&nbsp;";
2697          }
2698  
2699          //Add first
2700          $code .= $firstpagecode;
2701  
2702          $pagenum = $pagefrom;
2703  
2704          //List of maxdisplay pages
2705          while ($pagenum <= $pageto) {
2706              $pagetoshow = $pagenum +1;
2707              if ($pagenum == $page && !$specialselected) {
2708                  $code .= "$separator<b>$pagetoshow</b>";
2709              } else {
2710                  $code .= "$separator<a href=\"{$baseurl}page=$pagenum\">$pagetoshow</a>";
2711              }
2712              $pagenum++;
2713          }
2714  
2715          //Add last
2716          $code .= $lastpagecode;
2717  
2718          //Next
2719          if ($page < $maxpage && $page < $maxpageallowed && $previousandnext) {
2720              $pagenum = $page + 1;
2721              $code .= "$separator(<a href=\"{$baseurl}page=$pagenum\">".get_string("next")."</a>)";
2722          }
2723  
2724          //Add special
2725          if ($showspecial) {
2726              $code .= '<br />';
2727              if ($specialselected) {
2728                  $code .= "$separator<b>$specialtext</b>";
2729              } else {
2730                  $code .= "$separator<a href=\"{$baseurl}page=$specialvalue\">$specialtext</a>";
2731              }
2732          }
2733  
2734          //End html
2735          $code .= "</p>";
2736          $code .= "</div>";
2737      }
2738  
2739      return $code;
2740  }
2741  
2742  /**
2743   * List the actions that correspond to a view of this module.
2744   * This is used by the participation report.
2745   *
2746   * Note: This is not used by new logging system. Event with
2747   *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
2748   *       be considered as view action.
2749   *
2750   * @return array
2751   */
2752  function glossary_get_view_actions() {
2753      return array('view','view all','view entry');
2754  }
2755  
2756  /**
2757   * List the actions that correspond to a post of this module.
2758   * This is used by the participation report.
2759   *
2760   * Note: This is not used by new logging system. Event with
2761   *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
2762   *       will be considered as post action.
2763   *
2764   * @return array
2765   */
2766  function glossary_get_post_actions() {
2767      return array('add category','add entry','approve entry','delete category','delete entry','edit category','update entry');
2768  }
2769  
2770  
2771  /**
2772   * Implementation of the function for printing the form elements that control
2773   * whether the course reset functionality affects the glossary.
2774   * @param object $mform form passed by reference
2775   */
2776  function glossary_reset_course_form_definition(&$mform) {
2777      $mform->addElement('header', 'glossaryheader', get_string('modulenameplural', 'glossary'));
2778      $mform->addElement('checkbox', 'reset_glossary_all', get_string('resetglossariesall','glossary'));
2779  
2780      $mform->addElement('select', 'reset_glossary_types', get_string('resetglossaries', 'glossary'),
2781                         array('main'=>get_string('mainglossary', 'glossary'), 'secondary'=>get_string('secondaryglossary', 'glossary')), array('multiple' => 'multiple'));
2782      $mform->setAdvanced('reset_glossary_types');
2783      $mform->disabledIf('reset_glossary_types', 'reset_glossary_all', 'checked');
2784  
2785      $mform->addElement('checkbox', 'reset_glossary_notenrolled', get_string('deletenotenrolled', 'glossary'));
2786      $mform->disabledIf('reset_glossary_notenrolled', 'reset_glossary_all', 'checked');
2787  
2788      $mform->addElement('checkbox', 'reset_glossary_ratings', get_string('deleteallratings'));
2789      $mform->disabledIf('reset_glossary_ratings', 'reset_glossary_all', 'checked');
2790  
2791      $mform->addElement('checkbox', 'reset_glossary_comments', get_string('deleteallcomments'));
2792      $mform->disabledIf('reset_glossary_comments', 'reset_glossary_all', 'checked');
2793  
2794      $mform->addElement('checkbox', 'reset_glossary_tags', get_string('removeallglossarytags', 'glossary'));
2795      $mform->disabledIf('reset_glossary_tags', 'reset_glossary_all', 'checked');
2796  }
2797  
2798  /**
2799   * Course reset form defaults.
2800   * @return array
2801   */
2802  function glossary_reset_course_form_defaults($course) {
2803      return array('reset_glossary_all'=>0, 'reset_glossary_ratings'=>1, 'reset_glossary_comments'=>1, 'reset_glossary_notenrolled'=>0);
2804  }
2805  
2806  /**
2807   * Removes all grades from gradebook
2808   *
2809   * @param int $courseid The ID of the course to reset
2810   * @param string $type The optional type of glossary. 'main', 'secondary' or ''
2811   */
2812  function glossary_reset_gradebook($courseid, $type='') {
2813      global $DB;
2814  
2815      switch ($type) {
2816          case 'main'      : $type = "AND g.mainglossary=1"; break;
2817          case 'secondary' : $type = "AND g.mainglossary=0"; break;
2818          default          : $type = ""; //all
2819      }
2820  
2821      $sql = "SELECT g.*, cm.idnumber as cmidnumber, g.course as courseid
2822                FROM {glossary} g, {course_modules} cm, {modules} m
2823               WHERE m.name='glossary' AND m.id=cm.module AND cm.instance=g.id AND g.course=? $type";
2824  
2825      if ($glossarys = $DB->get_records_sql($sql, array($courseid))) {
2826          foreach ($glossarys as $glossary) {
2827              glossary_grade_item_update($glossary, 'reset');
2828          }
2829      }
2830  }
2831  /**
2832   * Actual implementation of the reset course functionality, delete all the
2833   * glossary responses for course $data->courseid.
2834   *
2835   * @global object
2836   * @param $data the data submitted from the reset course.
2837   * @return array status array
2838   */
2839  function glossary_reset_userdata($data) {
2840      global $CFG, $DB;
2841      require_once($CFG->dirroot.'/rating/lib.php');
2842  
2843      $componentstr = get_string('modulenameplural', 'glossary');
2844      $status = array();
2845  
2846      $allentriessql = "SELECT e.id
2847                          FROM {glossary_entries} e
2848                               JOIN {glossary} g ON e.glossaryid = g.id
2849                         WHERE g.course = ?";
2850  
2851      $allglossariessql = "SELECT g.id
2852                             FROM {glossary} g
2853                            WHERE g.course = ?";
2854  
2855      $params = array($data->courseid);
2856  
2857      $fs = get_file_storage();
2858  
2859      $rm = new rating_manager();
2860      $ratingdeloptions = new stdClass;
2861      $ratingdeloptions->component = 'mod_glossary';
2862      $ratingdeloptions->ratingarea = 'entry';
2863  
2864      // delete entries if requested
2865      if (!empty($data->reset_glossary_all)
2866           or (!empty($data->reset_glossary_types) and in_array('main', $data->reset_glossary_types) and in_array('secondary', $data->reset_glossary_types))) {
2867  
2868          $params[] = 'glossary_entry';
2869          $DB->delete_records_select('comments', "itemid IN ($allentriessql) AND commentarea=?", $params);
2870          $DB->delete_records_select('glossary_alias',    "entryid IN ($allentriessql)", $params);
2871          $DB->delete_records_select('glossary_entries', "glossaryid IN ($allglossariessql)", $params);
2872  
2873          // now get rid of all attachments
2874          if ($glossaries = $DB->get_records_sql($allglossariessql, $params)) {
2875              foreach ($glossaries as $glossaryid=>$unused) {
2876                  if (!$cm = get_coursemodule_from_instance('glossary', $glossaryid)) {
2877                      continue;
2878                  }
2879                  $context = context_module::instance($cm->id);
2880                  $fs->delete_area_files($context->id, 'mod_glossary', 'attachment');
2881  
2882                  //delete ratings
2883                  $ratingdeloptions->contextid = $context->id;
2884                  $rm->delete_ratings($ratingdeloptions);
2885  
2886                  core_tag_tag::delete_instances('mod_glossary', null, $context->id);
2887              }
2888          }
2889  
2890          // remove all grades from gradebook
2891          if (empty($data->reset_gradebook_grades)) {
2892              glossary_reset_gradebook($data->courseid);
2893          }
2894  
2895          $status[] = array('component'=>$componentstr, 'item'=>get_string('resetglossariesall', 'glossary'), 'error'=>false);
2896  
2897      } else if (!empty($data->reset_glossary_types)) {
2898          $mainentriessql         = "$allentriessql AND g.mainglossary=1";
2899          $secondaryentriessql    = "$allentriessql AND g.mainglossary=0";
2900  
2901          $mainglossariessql      = "$allglossariessql AND g.mainglossary=1";
2902          $secondaryglossariessql = "$allglossariessql AND g.mainglossary=0";
2903  
2904          if (in_array('main', $data->reset_glossary_types)) {
2905              $params[] = 'glossary_entry';
2906              $DB->delete_records_select('comments', "itemid IN ($mainentriessql) AND commentarea=?", $params);
2907              $DB->delete_records_select('glossary_entries', "glossaryid IN ($mainglossariessql)", $params);
2908  
2909              if ($glossaries = $DB->get_records_sql($mainglossariessql, $params)) {
2910                  foreach ($glossaries as $glossaryid=>$unused) {
2911                      if (!$cm = get_coursemodule_from_instance('glossary', $glossaryid)) {
2912                          continue;
2913                      }
2914                      $context = context_module::instance($cm->id);
2915                      $fs->delete_area_files($context->id, 'mod_glossary', 'attachment');
2916  
2917                      //delete ratings
2918                      $ratingdeloptions->contextid = $context->id;
2919                      $rm->delete_ratings($ratingdeloptions);
2920  
2921                      core_tag_tag::delete_instances('mod_glossary', null, $context->id);
2922                  }
2923              }
2924  
2925              // remove all grades from gradebook
2926              if (empty($data->reset_gradebook_grades)) {
2927                  glossary_reset_gradebook($data->courseid, 'main');
2928              }
2929  
2930              $status[] = array('component'=>$componentstr, 'item'=>get_string('resetglossaries', 'glossary').': '.get_string('mainglossary', 'glossary'), 'error'=>false);
2931  
2932          } else if (in_array('secondary', $data->reset_glossary_types)) {
2933              $params[] = 'glossary_entry';
2934              $DB->delete_records_select('comments', "itemid IN ($secondaryentriessql) AND commentarea=?", $params);
2935              $DB->delete_records_select('glossary_entries', "glossaryid IN ($secondaryglossariessql)", $params);
2936              // remove exported source flag from entries in main glossary
2937              $DB->execute("UPDATE {glossary_entries}
2938                               SET sourceglossaryid=0
2939                             WHERE glossaryid IN ($mainglossariessql)", $params);
2940  
2941              if ($glossaries = $DB->get_records_sql($secondaryglossariessql, $params)) {
2942                  foreach ($glossaries as $glossaryid=>$unused) {
2943                      if (!$cm = get_coursemodule_from_instance('glossary', $glossaryid)) {
2944                          continue;
2945                      }
2946                      $context = context_module::instance($cm->id);
2947                      $fs->delete_area_files($context->id, 'mod_glossary', 'attachment');
2948  
2949                      //delete ratings
2950                      $ratingdeloptions->contextid = $context->id;
2951                      $rm->delete_ratings($ratingdeloptions);
2952  
2953                      core_tag_tag::delete_instances('mod_glossary', null, $context->id);
2954                  }
2955              }
2956  
2957              // remove all grades from gradebook
2958              if (empty($data->reset_gradebook_grades)) {
2959                  glossary_reset_gradebook($data->courseid, 'secondary');
2960              }
2961  
2962              $status[] = array('component'=>$componentstr, 'item'=>get_string('resetglossaries', 'glossary').': '.get_string('secondaryglossary', 'glossary'), 'error'=>false);
2963          }
2964      }
2965  
2966      // remove entries by users not enrolled into course
2967      if (!empty($data->reset_glossary_notenrolled)) {
2968          $entriessql = "SELECT e.id, e.userid, e.glossaryid, u.id AS userexists, u.deleted AS userdeleted
2969                           FROM {glossary_entries} e
2970                                JOIN {glossary} g ON e.glossaryid = g.id
2971                                LEFT JOIN {user} u ON e.userid = u.id
2972                          WHERE g.course = ? AND e.userid > 0";
2973  
2974          $course_context = context_course::instance($data->courseid);
2975          $notenrolled = array();
2976          $rs = $DB->get_recordset_sql($entriessql, $params);
2977          if ($rs->valid()) {
2978              foreach ($rs as $entry) {
2979                  if (array_key_exists($entry->userid, $notenrolled) or !$entry->userexists or $entry->userdeleted
2980                    or !is_enrolled($course_context , $entry->userid)) {
2981                      $DB->delete_records('comments', array('commentarea'=>'glossary_entry', 'itemid'=>$entry->id));
2982                      $DB->delete_records('glossary_entries', array('id'=>$entry->id));
2983  
2984                      if ($cm = get_coursemodule_from_instance('glossary', $entry->glossaryid)) {
2985                          $context = context_module::instance($cm->id);
2986                          $fs->delete_area_files($context->id, 'mod_glossary', 'attachment', $entry->id);
2987  
2988                          //delete ratings
2989                          $ratingdeloptions->contextid = $context->id;
2990                          $rm->delete_ratings($ratingdeloptions);
2991                      }
2992                  }
2993              }
2994              $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotenrolled', 'glossary'), 'error'=>false);
2995          }
2996          $rs->close();
2997      }
2998  
2999      // remove all ratings
3000      if (!empty($data->reset_glossary_ratings)) {
3001          //remove ratings
3002          if ($glossaries = $DB->get_records_sql($allglossariessql, $params)) {
3003              foreach ($glossaries as $glossaryid=>$unused) {
3004                  if (!$cm = get_coursemodule_from_instance('glossary', $glossaryid)) {
3005                      continue;
3006                  }
3007                  $context = context_module::instance($cm->id);
3008  
3009                  //delete ratings
3010                  $ratingdeloptions->contextid = $context->id;
3011                  $rm->delete_ratings($ratingdeloptions);
3012              }
3013          }
3014  
3015          // remove all grades from gradebook
3016          if (empty($data->reset_gradebook_grades)) {
3017              glossary_reset_gradebook($data->courseid);
3018          }
3019          $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallratings'), 'error'=>false);
3020      }
3021  
3022      // remove comments
3023      if (!empty($data->reset_glossary_comments)) {
3024          $params[] = 'glossary_entry';
3025          $DB->delete_records_select('comments', "itemid IN ($allentriessql) AND commentarea= ? ", $params);
3026          $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallcomments'), 'error'=>false);
3027      }
3028  
3029      // Remove all the tags.
3030      if (!empty($data->reset_glossary_tags)) {
3031          if ($glossaries = $DB->get_records_sql($allglossariessql, $params)) {
3032              foreach ($glossaries as $glossaryid => $unused) {
3033                  if (!$cm = get_coursemodule_from_instance('glossary', $glossaryid)) {
3034                      continue;
3035                  }
3036  
3037                  $context = context_module::instance($cm->id);
3038                  core_tag_tag::delete_instances('mod_glossary', null, $context->id);
3039              }
3040          }
3041  
3042          $status[] = array('component' => $componentstr, 'item' => get_string('tagsdeleted', 'glossary'), 'error' => false);
3043      }
3044  
3045      /// updating dates - shift may be negative too
3046      if ($data->timeshift) {
3047          // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset.
3048          // See MDL-9367.
3049          shift_course_mod_dates('glossary', array('assesstimestart', 'assesstimefinish'), $data->timeshift, $data->courseid);
3050          $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
3051      }
3052  
3053      return $status;
3054  }
3055  
3056  /**
3057   * Returns all other caps used in module
3058   * @return array
3059   */
3060  function glossary_get_extra_capabilities() {
3061      return ['moodle/rating:view', 'moodle/rating:viewany', 'moodle/rating:viewall', 'moodle/rating:rate',
3062              'moodle/comment:view', 'moodle/comment:post', 'moodle/comment:delete'];
3063  }
3064  
3065  /**
3066   * @param string $feature FEATURE_xx constant for requested feature
3067   * @return mixed True if module supports feature, null if doesn't know
3068   */
3069  function glossary_supports($feature) {
3070      switch($feature) {
3071          case FEATURE_GROUPS:                  return false;
3072          case FEATURE_GROUPINGS:               return false;
3073          case FEATURE_MOD_INTRO:               return true;
3074          case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
3075          case FEATURE_COMPLETION_HAS_RULES:    return true;
3076          case FEATURE_GRADE_HAS_GRADE:         return true;
3077          case FEATURE_GRADE_OUTCOMES:          return true;
3078          case FEATURE_RATE:                    return true;
3079          case FEATURE_BACKUP_MOODLE2:          return true;
3080          case FEATURE_SHOW_DESCRIPTION:        return true;
3081          case FEATURE_COMMENT:                 return true;
3082  
3083          default: return null;
3084      }
3085  }
3086  
3087  /**
3088   * Obtains the automatic completion state for this glossary based on any conditions
3089   * in glossary settings.
3090   *
3091   * @global object
3092   * @global object
3093   * @param object $course Course
3094   * @param object $cm Course-module
3095   * @param int $userid User ID
3096   * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
3097   * @return bool True if completed, false if not. (If no conditions, then return
3098   *   value depends on comparison type)
3099   */
3100  function glossary_get_completion_state($course,$cm,$userid,$type) {
3101      global $CFG, $DB;
3102  
3103      // Get glossary details
3104      if (!($glossary=$DB->get_record('glossary',array('id'=>$cm->instance)))) {
3105          throw new Exception("Can't find glossary {$cm->instance}");
3106      }
3107  
3108      $result=$type; // Default return value
3109  
3110      if ($glossary->completionentries) {
3111          $value = $glossary->completionentries <=
3112                   $DB->count_records('glossary_entries',array('glossaryid'=>$glossary->id, 'userid'=>$userid, 'approved'=>1));
3113          if ($type == COMPLETION_AND) {
3114              $result = $result && $value;
3115          } else {
3116              $result = $result || $value;
3117          }
3118      }
3119  
3120      return $result;
3121  }
3122  
3123  function glossary_extend_navigation($navigation, $course, $module, $cm) {
3124      global $CFG, $DB;
3125  
3126      $displayformat = $DB->get_record('glossary_formats', array('name' => $module->displayformat));
3127      // Get visible tabs for the format and check if the menu needs to be displayed.
3128      $showtabs = glossary_get_visible_tabs($displayformat);
3129  
3130      foreach ($showtabs as $showtabkey => $showtabvalue) {
3131  
3132          switch($showtabvalue) {
3133              case GLOSSARY_STANDARD :
3134                  $navigation->add(get_string('standardview', 'glossary'), new moodle_url('/mod/glossary/view.php',
3135                          array('id' => $cm->id, 'mode' => 'letter')));
3136                  break;
3137              case GLOSSARY_CATEGORY :
3138                  $navigation->add(get_string('categoryview', 'glossary'), new moodle_url('/mod/glossary/view.php',
3139                          array('id' => $cm->id, 'mode' => 'cat')));
3140                  break;
3141              case GLOSSARY_DATE :
3142                  $navigation->add(get_string('dateview', 'glossary'), new moodle_url('/mod/glossary/view.php',
3143                          array('id' => $cm->id, 'mode' => 'date')));
3144                  break;
3145              case GLOSSARY_AUTHOR :
3146                  $navigation->add(get_string('authorview', 'glossary'), new moodle_url('/mod/glossary/view.php',
3147                          array('id' => $cm->id, 'mode' => 'author')));
3148                  break;
3149          }
3150      }
3151  }
3152  
3153  /**
3154   * Adds module specific settings to the settings block
3155   *
3156   * @param settings_navigation $settings The settings navigation object
3157   * @param navigation_node $glossarynode The node to add module settings to
3158   */
3159  function glossary_extend_settings_navigation(settings_navigation $settings, navigation_node $glossarynode) {
3160      global $PAGE, $DB, $CFG, $USER;
3161  
3162      $mode = optional_param('mode', '', PARAM_ALPHA);
3163      $hook = optional_param('hook', 'ALL', PARAM_CLEAN);
3164  
3165      if (has_capability('mod/glossary:import', $PAGE->cm->context)) {
3166          $glossarynode->add(get_string('importentries', 'glossary'), new moodle_url('/mod/glossary/import.php', array('id'=>$PAGE->cm->id)));
3167      }
3168  
3169      if (has_capability('mod/glossary:export', $PAGE->cm->context)) {
3170          $glossarynode->add(get_string('exportentries', 'glossary'), new moodle_url('/mod/glossary/export.php', array('id'=>$PAGE->cm->id, 'mode'=>$mode, 'hook'=>$hook)));
3171      }
3172  
3173      if (has_capability('mod/glossary:approve', $PAGE->cm->context) && ($hiddenentries = $DB->count_records('glossary_entries', array('glossaryid'=>$PAGE->cm->instance, 'approved'=>0)))) {
3174          $glossarynode->add(get_string('waitingapproval', 'glossary'), new moodle_url('/mod/glossary/view.php', array('id'=>$PAGE->cm->id, 'mode'=>'approval')));
3175      }
3176  
3177      if (has_capability('mod/glossary:write', $PAGE->cm->context)) {
3178          $glossarynode->add(get_string('addentry', 'glossary'), new moodle_url('/mod/glossary/edit.php', array('cmid'=>$PAGE->cm->id)));
3179      }
3180  
3181      $glossary = $DB->get_record('glossary', array("id" => $PAGE->cm->instance));
3182  
3183      if (!empty($CFG->enablerssfeeds) && !empty($CFG->glossary_enablerssfeeds) && $glossary->rsstype && $glossary->rssarticles && has_capability('mod/glossary:view', $PAGE->cm->context)) {
3184          require_once("$CFG->libdir/rsslib.php");
3185  
3186          $string = get_string('rsstype', 'glossary');
3187  
3188          $url = new moodle_url(rss_get_url($PAGE->cm->context->id, $USER->id, 'mod_glossary', $glossary->id));
3189          $glossarynode->add($string, $url, settings_navigation::TYPE_SETTING, null, null, new pix_icon('i/rss', ''));
3190      }
3191  }
3192  
3193  /**
3194   * Running addtional permission check on plugin, for example, plugins
3195   * may have switch to turn on/off comments option, this callback will
3196   * affect UI display, not like pluginname_comment_validate only throw
3197   * exceptions.
3198   * Capability check has been done in comment->check_permissions(), we
3199   * don't need to do it again here.
3200   *
3201   * @package  mod_glossary
3202   * @category comment
3203   *
3204   * @param stdClass $comment_param {
3205   *              context  => context the context object
3206   *              courseid => int course id
3207   *              cm       => stdClass course module object
3208   *              commentarea => string comment area
3209   *              itemid      => int itemid
3210   * }
3211   * @return array
3212   */
3213  function glossary_comment_permissions($comment_param) {
3214      return array('post'=>true, 'view'=>true);
3215  }
3216  
3217  /**
3218   * Validate comment parameter before perform other comments actions
3219   *
3220   * @package  mod_glossary
3221   * @category comment
3222   *
3223   * @param stdClass $comment_param {
3224   *              context  => context the context object
3225   *              courseid => int course id
3226   *              cm       => stdClass course module object
3227   *              commentarea => string comment area
3228   *              itemid      => int itemid
3229   * }
3230   * @return boolean
3231   */
3232  function glossary_comment_validate($comment_param) {
3233      global $DB;
3234      // validate comment area
3235      if ($comment_param->commentarea != 'glossary_entry') {
3236          throw new comment_exception('invalidcommentarea');
3237      }
3238      if (!$record = $DB->get_record('glossary_entries', array('id'=>$comment_param->itemid))) {
3239          throw new comment_exception('invalidcommentitemid');
3240      }
3241      if ($record->sourceglossaryid && $record->sourceglossaryid == $comment_param->cm->instance) {
3242          $glossary = $DB->get_record('glossary', array('id'=>$record->sourceglossaryid));
3243      } else {
3244          $glossary = $DB->get_record('glossary', array('id'=>$record->glossaryid));
3245      }
3246      if (!$glossary) {
3247          throw new comment_exception('invalidid', 'data');
3248      }
3249      if (!$course = $DB->get_record('course', array('id'=>$glossary->course))) {
3250          throw new comment_exception('coursemisconf');
3251      }
3252      if (!$cm = get_coursemodule_from_instance('glossary', $glossary->id, $course->id)) {
3253          throw new comment_exception('invalidcoursemodule');
3254      }
3255      $context = context_module::instance($cm->id);
3256  
3257      if ($glossary->defaultapproval and !$record->approved and !has_capability('mod/glossary:approve', $context)) {
3258          throw new comment_exception('notapproved', 'glossary');
3259      }
3260      // validate context id
3261      if ($context->id != $comment_param->context->id) {
3262          throw new comment_exception('invalidcontext');
3263      }
3264      // validation for comment deletion
3265      if (!empty($comment_param->commentid)) {
3266          if ($comment = $DB->get_record('comments', array('id'=>$comment_param->commentid))) {
3267              if ($comment->commentarea != 'glossary_entry') {
3268                  throw new comment_exception('invalidcommentarea');
3269              }
3270              if ($comment->contextid != $comment_param->context->id) {
3271                  throw new comment_exception('invalidcontext');
3272              }
3273              if ($comment->itemid != $comment_param->itemid) {
3274                  throw new comment_exception('invalidcommentitemid');
3275              }
3276          } else {
3277              throw new comment_exception('invalidcommentid');
3278          }
3279      }
3280      return true;
3281  }
3282  
3283  /**
3284   * Return a list of page types
3285   * @param string $pagetype current page type
3286   * @param stdClass $parentcontext Block's parent context
3287   * @param stdClass $currentcontext Current context of block
3288   */
3289  function glossary_page_type_list($pagetype, $parentcontext, $currentcontext) {
3290      $module_pagetype = array(
3291          'mod-glossary-*'=>get_string('page-mod-glossary-x', 'glossary'),
3292          'mod-glossary-view'=>get_string('page-mod-glossary-view', 'glossary'),
3293          'mod-glossary-edit'=>get_string('page-mod-glossary-edit', 'glossary'));
3294      return $module_pagetype;
3295  }
3296  
3297  /**
3298   * Return list of all glossary tabs.
3299   * @throws coding_exception
3300   * @return array
3301   */
3302  function glossary_get_all_tabs() {
3303  
3304      return array (
3305          GLOSSARY_AUTHOR => get_string('authorview', 'glossary'),
3306          GLOSSARY_CATEGORY => get_string('categoryview', 'glossary'),
3307          GLOSSARY_DATE => get_string('dateview', 'glossary')
3308      );
3309  }
3310  
3311  /**
3312   * Set 'showtabs' value for glossary formats
3313   * @param stdClass $glossaryformat record from 'glossary_formats' table
3314   */
3315  function glossary_set_default_visible_tabs($glossaryformat) {
3316      global $DB;
3317  
3318      switch($glossaryformat->name) {
3319          case GLOSSARY_CONTINUOUS:
3320              $showtabs = 'standard,category,date';
3321              break;
3322          case GLOSSARY_DICTIONARY:
3323              $showtabs = 'standard';
3324              // Special code for upgraded instances that already had categories set up
3325              // in this format - enable "category" tab.
3326              // In new instances only 'standard' tab will be visible.
3327              if ($DB->record_exists_sql("SELECT 1
3328                      FROM {glossary} g, {glossary_categories} gc
3329                      WHERE g.id = gc.glossaryid and g.displayformat = ?",
3330                      array(GLOSSARY_DICTIONARY))) {
3331                  $showtabs .= ',category';
3332              }
3333              break;
3334          case GLOSSARY_FULLWITHOUTAUTHOR:
3335              $showtabs = 'standard,category,date';
3336              break;
3337          default:
3338              $showtabs = 'standard,category,date,author';
3339              break;
3340      }
3341  
3342      $DB->set_field('glossary_formats', 'showtabs', $showtabs, array('id' => $glossaryformat->id));
3343      $glossaryformat->showtabs = $showtabs;
3344  }
3345  
3346  /**
3347   * Convert 'showtabs' string to array
3348   * @param stdClass $displayformat record from 'glossary_formats' table
3349   * @return array
3350   */
3351  function glossary_get_visible_tabs($displayformat) {
3352      if (empty($displayformat->showtabs)) {
3353          glossary_set_default_visible_tabs($displayformat);
3354      }
3355      $showtabs = preg_split('/,/', $displayformat->showtabs, -1, PREG_SPLIT_NO_EMPTY);
3356  
3357      return $showtabs;
3358  }
3359  
3360  /**
3361   * Notify that the glossary was viewed.
3362   *
3363   * This will trigger relevant events and activity completion.
3364   *
3365   * @param stdClass $glossary The glossary object.
3366   * @param stdClass $course   The course object.
3367   * @param stdClass $cm       The course module object.
3368   * @param stdClass $context  The context object.
3369   * @param string   $mode     The mode in which the glossary was viewed.
3370   * @since Moodle 3.1
3371   */
3372  function glossary_view($glossary, $course, $cm, $context, $mode) {
3373  
3374      // Completion trigger.
3375      $completion = new completion_info($course);
3376      $completion->set_module_viewed($cm);
3377  
3378      // Trigger the course module viewed event.
3379      $event = \mod_glossary\event\course_module_viewed::create(array(
3380          'objectid' => $glossary->id,
3381          'context' => $context,
3382          'other' => array('mode' => $mode)
3383      ));
3384      $event->add_record_snapshot('course', $course);
3385      $event->add_record_snapshot('course_modules', $cm);
3386      $event->add_record_snapshot('glossary', $glossary);
3387      $event->trigger();
3388  }
3389  
3390  /**
3391   * Notify that a glossary entry was viewed.
3392   *
3393   * This will trigger relevant events.
3394   *
3395   * @param stdClass $entry    The entry object.
3396   * @param stdClass $context  The context object.
3397   * @since Moodle 3.1
3398   */
3399  function glossary_entry_view($entry, $context) {
3400  
3401      // Trigger the entry viewed event.
3402      $event = \mod_glossary\event\entry_viewed::create(array(
3403          'objectid' => $entry->id,
3404          'context' => $context
3405      ));
3406      $event->add_record_snapshot('glossary_entries', $entry);
3407      $event->trigger();
3408  
3409  }
3410  
3411  /**
3412   * Returns the entries of a glossary by letter.
3413   *
3414   * @param  object $glossary The glossary.
3415   * @param  context $context The context of the glossary.
3416   * @param  string $letter The letter, or ALL, or SPECIAL.
3417   * @param  int $from Fetch records from.
3418   * @param  int $limit Number of records to fetch.
3419   * @param  array $options Accepts:
3420   *                        - (bool) includenotapproved. When false, includes the non-approved entries created by
3421   *                          the current user. When true, also includes the ones that the user has the permission to approve.
3422   * @return array The first element being the recordset, the second the number of entries.
3423   * @since Moodle 3.1
3424   */
3425  function glossary_get_entries_by_letter($glossary, $context, $letter, $from, $limit, $options = array()) {
3426  
3427      $qb = new mod_glossary_entry_query_builder($glossary);
3428      if ($letter != 'ALL' && $letter != 'SPECIAL' && core_text::strlen($letter)) {
3429          $qb->filter_by_concept_letter($letter);
3430      }
3431      if ($letter == 'SPECIAL') {
3432          $qb->filter_by_concept_non_letter();
3433      }
3434  
3435      if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3436          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL);
3437      } else {
3438          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF);
3439      }
3440  
3441      $qb->add_field('*', 'entries');
3442      $qb->join_user();
3443      $qb->add_user_fields();
3444      $qb->order_by('concept', 'entries');
3445      $qb->order_by('id', 'entries', 'ASC'); // Sort on ID to avoid random ordering when entries share an ordering value.
3446      $qb->limit($from, $limit);
3447  
3448      // Fetching the entries.
3449      $count = $qb->count_records();
3450      $entries = $qb->get_records();
3451  
3452      return array($entries, $count);
3453  }
3454  
3455  /**
3456   * Returns the entries of a glossary by date.
3457   *
3458   * @param  object $glossary The glossary.
3459   * @param  context $context The context of the glossary.
3460   * @param  string $order The mode of ordering: CREATION or UPDATE.
3461   * @param  string $sort The direction of the ordering: ASC or DESC.
3462   * @param  int $from Fetch records from.
3463   * @param  int $limit Number of records to fetch.
3464   * @param  array $options Accepts:
3465   *                        - (bool) includenotapproved. When false, includes the non-approved entries created by
3466   *                          the current user. When true, also includes the ones that the user has the permission to approve.
3467   * @return array The first element being the recordset, the second the number of entries.
3468   * @since Moodle 3.1
3469   */
3470  function glossary_get_entries_by_date($glossary, $context, $order, $sort, $from, $limit, $options = array()) {
3471  
3472      $qb = new mod_glossary_entry_query_builder($glossary);
3473      if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3474          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL);
3475      } else {
3476          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF);
3477      }
3478  
3479      $qb->add_field('*', 'entries');
3480      $qb->join_user();
3481      $qb->add_user_fields();
3482      $qb->limit($from, $limit);
3483  
3484      if ($order == 'CREATION') {
3485          $qb->order_by('timecreated', 'entries', $sort);
3486      } else {
3487          $qb->order_by('timemodified', 'entries', $sort);
3488      }
3489      $qb->order_by('id', 'entries', $sort); // Sort on ID to avoid random ordering when entries share an ordering value.
3490  
3491      // Fetching the entries.
3492      $count = $qb->count_records();
3493      $entries = $qb->get_records();
3494  
3495      return array($entries, $count);
3496  }
3497  
3498  /**
3499   * Returns the entries of a glossary by category.
3500   *
3501   * @param  object $glossary The glossary.
3502   * @param  context $context The context of the glossary.
3503   * @param  int $categoryid The category ID, or GLOSSARY_SHOW_* constant.
3504   * @param  int $from Fetch records from.
3505   * @param  int $limit Number of records to fetch.
3506   * @param  array $options Accepts:
3507   *                        - (bool) includenotapproved. When false, includes the non-approved entries created by
3508   *                          the current user. When true, also includes the ones that the user has the permission to approve.
3509   * @return array The first element being the recordset, the second the number of entries.
3510   * @since Moodle 3.1
3511   */
3512  function glossary_get_entries_by_category($glossary, $context, $categoryid, $from, $limit, $options = array()) {
3513  
3514      $qb = new mod_glossary_entry_query_builder($glossary);
3515      if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3516          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL);
3517      } else {
3518          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF);
3519      }
3520  
3521      $qb->join_category($categoryid);
3522      $qb->join_user();
3523  
3524      // The first field must be the relationship ID when viewing all categories.
3525      if ($categoryid === GLOSSARY_SHOW_ALL_CATEGORIES) {
3526          $qb->add_field('id', 'entries_categories', 'cid');
3527      }
3528  
3529      $qb->add_field('*', 'entries');
3530      $qb->add_field('categoryid', 'entries_categories');
3531      $qb->add_user_fields();
3532  
3533      if ($categoryid === GLOSSARY_SHOW_ALL_CATEGORIES) {
3534          $qb->add_field('name', 'categories', 'categoryname');
3535          $qb->order_by('name', 'categories');
3536  
3537      } else if ($categoryid === GLOSSARY_SHOW_NOT_CATEGORISED) {
3538          $qb->where('categoryid', 'entries_categories', null);
3539      }
3540  
3541      // Sort on additional fields to avoid random ordering when entries share an ordering value.
3542      $qb->order_by('concept', 'entries');
3543      $qb->order_by('id', 'entries', 'ASC');
3544      $qb->limit($from, $limit);
3545  
3546      // Fetching the entries.
3547      $count = $qb->count_records();
3548      $entries = $qb->get_records();
3549  
3550      return array($entries, $count);
3551  }
3552  
3553  /**
3554   * Returns the entries of a glossary by author.
3555   *
3556   * @param  object $glossary The glossary.
3557   * @param  context $context The context of the glossary.
3558   * @param  string $letter The letter
3559   * @param  string $field The field to search: FIRSTNAME or LASTNAME.
3560   * @param  string $sort The sorting: ASC or DESC.
3561   * @param  int $from Fetch records from.
3562   * @param  int $limit Number of records to fetch.
3563   * @param  array $options Accepts:
3564   *                        - (bool) includenotapproved. When false, includes the non-approved entries created by
3565   *                          the current user. When true, also includes the ones that the user has the permission to approve.
3566   * @return array The first element being the recordset, the second the number of entries.
3567   * @since Moodle 3.1
3568   */
3569  function glossary_get_entries_by_author($glossary, $context, $letter, $field, $sort, $from, $limit, $options = array()) {
3570  
3571      $firstnamefirst = $field === 'FIRSTNAME';
3572      $qb = new mod_glossary_entry_query_builder($glossary);
3573      if ($letter != 'ALL' && $letter != 'SPECIAL' && core_text::strlen($letter)) {
3574          $qb->filter_by_author_letter($letter, $firstnamefirst);
3575      }
3576      if ($letter == 'SPECIAL') {
3577          $qb->filter_by_author_non_letter($firstnamefirst);
3578      }
3579  
3580      if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3581          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL);
3582      } else {
3583          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF);
3584      }
3585  
3586      $qb->add_field('*', 'entries');
3587      $qb->join_user(true);
3588      $qb->add_user_fields();
3589      $qb->order_by_author($firstnamefirst, $sort);
3590      $qb->order_by('concept', 'entries');
3591      $qb->order_by('id', 'entries', 'ASC'); // Sort on ID to avoid random ordering when entries share an ordering value.
3592      $qb->limit($from, $limit);
3593  
3594      // Fetching the entries.
3595      $count = $qb->count_records();
3596      $entries = $qb->get_records();
3597  
3598      return array($entries, $count);
3599  }
3600  
3601  /**
3602   * Returns the entries of a glossary by category.
3603   *
3604   * @param  object $glossary The glossary.
3605   * @param  context $context The context of the glossary.
3606   * @param  int $authorid The author ID.
3607   * @param  string $order The mode of ordering: CONCEPT, CREATION or UPDATE.
3608   * @param  string $sort The direction of the ordering: ASC or DESC.
3609   * @param  int $from Fetch records from.
3610   * @param  int $limit Number of records to fetch.
3611   * @param  array $options Accepts:
3612   *                        - (bool) includenotapproved. When false, includes the non-approved entries created by
3613   *                          the current user. When true, also includes the ones that the user has the permission to approve.
3614   * @return array The first element being the recordset, the second the number of entries.
3615   * @since Moodle 3.1
3616   */
3617  function glossary_get_entries_by_author_id($glossary, $context, $authorid, $order, $sort, $from, $limit, $options = array()) {
3618  
3619      $qb = new mod_glossary_entry_query_builder($glossary);
3620      if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3621          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL);
3622      } else {
3623          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF);
3624      }
3625  
3626      $qb->add_field('*', 'entries');
3627      $qb->join_user(true);
3628      $qb->add_user_fields();
3629      $qb->where('id', 'user', $authorid);
3630  
3631      if ($order == 'CREATION') {
3632          $qb->order_by('timecreated', 'entries', $sort);
3633      } else if ($order == 'UPDATE') {
3634          $qb->order_by('timemodified', 'entries', $sort);
3635      } else {
3636          $qb->order_by('concept', 'entries', $sort);
3637      }
3638      $qb->order_by('id', 'entries', $sort); // Sort on ID to avoid random ordering when entries share an ordering value.
3639  
3640      $qb->limit($from, $limit);
3641  
3642      // Fetching the entries.
3643      $count = $qb->count_records();
3644      $entries = $qb->get_records();
3645  
3646      return array($entries, $count);
3647  }
3648  
3649  /**
3650   * Returns the authors in a glossary
3651   *
3652   * @param  object $glossary The glossary.
3653   * @param  context $context The context of the glossary.
3654   * @param  int $limit Number of records to fetch.
3655   * @param  int $from Fetch records from.
3656   * @param  array $options Accepts:
3657   *                        - (bool) includenotapproved. When false, includes self even if all of their entries require approval.
3658   *                          When true, also includes authors only having entries pending approval.
3659   * @return array The first element being the recordset, the second the number of entries.
3660   * @since Moodle 3.1
3661   */
3662  function glossary_get_authors($glossary, $context, $limit, $from, $options = array()) {
3663      global $DB, $USER;
3664  
3665      $params = array();
3666      $userfields = user_picture::fields('u', null);
3667  
3668      $approvedsql = '(ge.approved <> 0 OR ge.userid = :myid)';
3669      $params['myid'] = $USER->id;
3670      if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3671          $approvedsql = '1 = 1';
3672      }
3673  
3674      $sqlselectcount = "SELECT COUNT(DISTINCT(u.id))";
3675      $sqlselect = "SELECT DISTINCT(u.id) AS userId, $userfields";
3676      $sql = "  FROM {user} u
3677                JOIN {glossary_entries} ge
3678                  ON ge.userid = u.id
3679                 AND (ge.glossaryid = :gid1 OR ge.sourceglossaryid = :gid2)
3680                 AND $approvedsql";
3681      $ordersql = " ORDER BY u.lastname, u.firstname";
3682  
3683      $params['gid1'] = $glossary->id;
3684      $params['gid2'] = $glossary->id;
3685  
3686      $count = $DB->count_records_sql($sqlselectcount . $sql, $params);
3687      $users = $DB->get_recordset_sql($sqlselect . $sql . $ordersql, $params, $from, $limit);
3688  
3689      return array($users, $count);
3690  }
3691  
3692  /**
3693   * Returns the categories of a glossary.
3694   *
3695   * @param  object $glossary The glossary.
3696   * @param  int $from Fetch records from.
3697   * @param  int $limit Number of records to fetch.
3698   * @return array The first element being the recordset, the second the number of entries.
3699   * @since Moodle 3.1
3700   */
3701  function glossary_get_categories($glossary, $from, $limit) {
3702      global $DB;
3703  
3704      $count = $DB->count_records('glossary_categories', array('glossaryid' => $glossary->id));
3705      $categories = $DB->get_recordset('glossary_categories', array('glossaryid' => $glossary->id), 'name ASC', '*', $from, $limit);
3706  
3707      return array($categories, $count);
3708  }
3709  
3710  /**
3711   * Get the SQL where clause for searching terms.
3712   *
3713   * Note that this does not handle invalid or too short terms.
3714   *
3715   * @param array   $terms      Array of terms.
3716   * @param bool    $fullsearch Whether or not full search should be enabled.
3717   * @param int     $glossaryid The ID of a glossary to reduce the search results.
3718   * @return array The first element being the where clause, the second array of parameters.
3719   * @since Moodle 3.1
3720   */
3721  function glossary_get_search_terms_sql(array $terms, $fullsearch = true, $glossaryid = null) {
3722      global $DB;
3723      static $i = 0;
3724  
3725      if ($DB->sql_regex_supported()) {
3726          $regexp = $DB->sql_regex(true);
3727          $notregexp = $DB->sql_regex(false);
3728      }
3729  
3730      $params = array();
3731      $conditions = array();
3732  
3733      foreach ($terms as $searchterm) {
3734          $i++;
3735  
3736          $not = false; // Initially we aren't going to perform NOT LIKE searches, only MSSQL and Oracle
3737                        // will use it to simulate the "-" operator with LIKE clause.
3738  
3739          if (empty($fullsearch)) {
3740              // With fullsearch disabled, look only within concepts and aliases.
3741              $concat = $DB->sql_concat('ge.concept', "' '", "COALESCE(al.alias, :emptychar{$i})");
3742          } else {
3743              // With fullsearch enabled, look also within definitions.
3744              $concat = $DB->sql_concat('ge.concept', "' '", 'ge.definition', "' '", "COALESCE(al.alias, :emptychar{$i})");
3745          }
3746          $params['emptychar' . $i] = '';
3747  
3748          // Under Oracle and MSSQL, trim the + and - operators and perform simpler LIKE (or NOT LIKE) queries.
3749          if (!$DB->sql_regex_supported()) {
3750              if (substr($searchterm, 0, 1) === '-') {
3751                  $not = true;
3752              }
3753              $searchterm = trim($searchterm, '+-');
3754          }
3755  
3756          if (substr($searchterm, 0, 1) === '+') {
3757              $searchterm = trim($searchterm, '+-');
3758              $conditions[] = "$concat $regexp :searchterm{$i}";
3759              $params['searchterm' . $i] = '(^|[^a-zA-Z0-9])' . preg_quote($searchterm, '|') . '([^a-zA-Z0-9]|$)';
3760  
3761          } else if (substr($searchterm, 0, 1) === "-") {
3762              $searchterm = trim($searchterm, '+-');
3763              $conditions[] = "$concat $notregexp :searchterm{$i}";
3764              $params['searchterm' . $i] = '(^|[^a-zA-Z0-9])' . preg_quote($searchterm, '|') . '([^a-zA-Z0-9]|$)';
3765  
3766          } else {
3767              $conditions[] = $DB->sql_like($concat, ":searchterm{$i}", false, true, $not);
3768              $params['searchterm' . $i] = '%' . $DB->sql_like_escape($searchterm) . '%';
3769          }
3770      }
3771  
3772      // Reduce the search results by restricting it to one glossary.
3773      if (isset($glossaryid)) {
3774          $conditions[] = 'ge.glossaryid = :glossaryid';
3775          $params['glossaryid'] = $glossaryid;
3776      }
3777  
3778      // When there are no conditions we add a negative one to ensure that we don't return anything.
3779      if (empty($conditions)) {
3780          $conditions[] = '1 = 2';
3781      }
3782  
3783      $where = implode(' AND ', $conditions);
3784      return array($where, $params);
3785  }
3786  
3787  
3788  /**
3789   * Returns the entries of a glossary by search.
3790   *
3791   * @param  object $glossary The glossary.
3792   * @param  context $context The context of the glossary.
3793   * @param  string $query The search query.
3794   * @param  bool $fullsearch Whether or not full search is required.
3795   * @param  string $order The mode of ordering: CONCEPT, CREATION or UPDATE.
3796   * @param  string $sort The direction of the ordering: ASC or DESC.
3797   * @param  int $from Fetch records from.
3798   * @param  int $limit Number of records to fetch.
3799   * @param  array $options Accepts:
3800   *                        - (bool) includenotapproved. When false, includes the non-approved entries created by
3801   *                          the current user. When true, also includes the ones that the user has the permission to approve.
3802   * @return array The first element being the array of results, the second the number of entries.
3803   * @since Moodle 3.1
3804   */
3805  function glossary_get_entries_by_search($glossary, $context, $query, $fullsearch, $order, $sort, $from, $limit,
3806                                          $options = array()) {
3807      global $DB, $USER;
3808  
3809      // Clean terms.
3810      $terms = explode(' ', $query);
3811      foreach ($terms as $key => $term) {
3812          if (strlen(trim($term, '+-')) < 1) {
3813              unset($terms[$key]);
3814          }
3815      }
3816  
3817      list($searchcond, $params) = glossary_get_search_terms_sql($terms, $fullsearch, $glossary->id);
3818  
3819      $userfields = user_picture::fields('u', null, 'userdataid', 'userdata');
3820  
3821      // Need one inner view here to avoid distinct + text.
3822      $sqlwrapheader = 'SELECT ge.*, ge.concept AS glossarypivot, ' . $userfields . '
3823                          FROM {glossary_entries} ge
3824                          LEFT JOIN {user} u ON u.id = ge.userid
3825                          JOIN ( ';
3826      $sqlwrapfooter = ' ) gei ON (ge.id = gei.id)';
3827      $sqlselect  = "SELECT DISTINCT ge.id";
3828      $sqlfrom    = "FROM {glossary_entries} ge
3829                     LEFT JOIN {glossary_alias} al ON al.entryid = ge.id";
3830  
3831      if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3832          $approvedsql = '';
3833      } else {
3834          $approvedsql = 'AND (ge.approved <> 0 OR ge.userid = :myid)';
3835          $params['myid'] = $USER->id;
3836      }
3837  
3838      if ($order == 'CREATION') {
3839          $sqlorderby = "ORDER BY ge.timecreated $sort";
3840      } else if ($order == 'UPDATE') {
3841          $sqlorderby = "ORDER BY ge.timemodified $sort";
3842      } else {
3843          $sqlorderby = "ORDER BY ge.concept $sort";
3844      }
3845      $sqlorderby .= " , ge.id ASC"; // Sort on ID to avoid random ordering when entries share an ordering value.
3846  
3847      $sqlwhere = "WHERE ($searchcond) $approvedsql";
3848  
3849      // Fetching the entries.
3850      $count = $DB->count_records_sql("SELECT COUNT(DISTINCT(ge.id)) $sqlfrom $sqlwhere", $params);
3851  
3852      $query = "$sqlwrapheader $sqlselect $sqlfrom $sqlwhere $sqlwrapfooter $sqlorderby";
3853      $entries = $DB->get_records_sql($query, $params, $from, $limit);
3854  
3855      return array($entries, $count);
3856  }
3857  
3858  /**
3859   * Returns the entries of a glossary by term.
3860   *
3861   * @param  object $glossary The glossary.
3862   * @param  context $context The context of the glossary.
3863   * @param  string $term The term we are searching for, a concept or alias.
3864   * @param  int $from Fetch records from.
3865   * @param  int $limit Number of records to fetch.
3866   * @param  array $options Accepts:
3867   *                        - (bool) includenotapproved. When false, includes the non-approved entries created by
3868   *                          the current user. When true, also includes the ones that the user has the permission to approve.
3869   * @return array The first element being the recordset, the second the number of entries.
3870   * @since Moodle 3.1
3871   */
3872  function glossary_get_entries_by_term($glossary, $context, $term, $from, $limit, $options = array()) {
3873  
3874      // Build the query.
3875      $qb = new mod_glossary_entry_query_builder($glossary);
3876      if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3877          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL);
3878      } else {
3879          $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF);
3880      }
3881  
3882      $qb->add_field('*', 'entries');
3883      $qb->join_alias();
3884      $qb->join_user();
3885      $qb->add_user_fields();
3886      $qb->filter_by_term($term);
3887  
3888      $qb->order_by('concept', 'entries');
3889      $qb->order_by('id', 'entries');     // Sort on ID to avoid random ordering when entries share an ordering value.
3890      $qb->limit($from, $limit);
3891  
3892      // Fetching the entries.
3893      $count = $qb->count_records();
3894      $entries = $qb->get_records();
3895  
3896      return array($entries, $count);
3897  }
3898  
3899  /**
3900   * Returns the entries to be approved.
3901   *
3902   * @param  object $glossary The glossary.
3903   * @param  context $context The context of the glossary.
3904   * @param  string $letter The letter, or ALL, or SPECIAL.
3905   * @param  string $order The mode of ordering: CONCEPT, CREATION or UPDATE.
3906   * @param  string $sort The direction of the ordering: ASC or DESC.
3907   * @param  int $from Fetch records from.
3908   * @param  int $limit Number of records to fetch.
3909   * @return array The first element being the recordset, the second the number of entries.
3910   * @since Moodle 3.1
3911   */
3912  function glossary_get_entries_to_approve($glossary, $context, $letter, $order, $sort, $from, $limit) {
3913  
3914      $qb = new mod_glossary_entry_query_builder($glossary);
3915      if ($letter != 'ALL' && $letter != 'SPECIAL' && core_text::strlen($letter)) {
3916          $qb->filter_by_concept_letter($letter);
3917      }
3918      if ($letter == 'SPECIAL') {
3919          $qb->filter_by_concept_non_letter();
3920      }
3921  
3922      $qb->add_field('*', 'entries');
3923      $qb->join_user();
3924      $qb->add_user_fields();
3925      $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ONLY);
3926      if ($order == 'CREATION') {
3927          $qb->order_by('timecreated', 'entries', $sort);
3928      } else if ($order == 'UPDATE') {
3929          $qb->order_by('timemodified', 'entries', $sort);
3930      } else {
3931          $qb->order_by('concept', 'entries', $sort);
3932      }
3933      $qb->order_by('id', 'entries', $sort); // Sort on ID to avoid random ordering when entries share an ordering value.
3934      $qb->limit($from, $limit);
3935  
3936      // Fetching the entries.
3937      $count = $qb->count_records();
3938      $entries = $qb->get_records();
3939  
3940      return array($entries, $count);
3941  }
3942  
3943  /**
3944   * Fetch an entry.
3945   *
3946   * @param  int $id The entry ID.
3947   * @return object|false The entry, or false when not found.
3948   * @since Moodle 3.1
3949   */
3950  function glossary_get_entry_by_id($id) {
3951  
3952      // Build the query.
3953      $qb = new mod_glossary_entry_query_builder();
3954      $qb->add_field('*', 'entries');
3955      $qb->join_user();
3956      $qb->add_user_fields();
3957      $qb->where('id', 'entries', $id);
3958  
3959      // Fetching the entries.
3960      $entries = $qb->get_records();
3961      if (empty($entries)) {
3962          return false;
3963      }
3964      return array_pop($entries);
3965  }
3966  
3967  /**
3968   * Checks if the current user can see the glossary entry.
3969   *
3970   * @since Moodle 3.1
3971   * @param stdClass $entry
3972   * @param cm_info  $cminfo
3973   * @return bool
3974   */
3975  function glossary_can_view_entry($entry, $cminfo) {
3976      global $USER;
3977  
3978      $cm = $cminfo->get_course_module_record();
3979      $context = \context_module::instance($cm->id);
3980  
3981      // Recheck uservisible although it should have already been checked in core_search.
3982      if ($cminfo->uservisible === false) {
3983          return false;
3984      }
3985  
3986      // Check approval.
3987      if (empty($entry->approved) && $entry->userid != $USER->id && !has_capability('mod/glossary:approve', $context)) {
3988          return false;
3989      }
3990  
3991      return true;
3992  }
3993  
3994  /**
3995   * Check if a concept exists in a glossary.
3996   *
3997   * @param  stdClass $glossary glossary object
3998   * @param  string $concept the concept to check
3999   * @return bool true if exists
4000   * @since  Moodle 3.2
4001   */
4002  function glossary_concept_exists($glossary, $concept) {
4003      global $DB;
4004  
4005      return $DB->record_exists_select('glossary_entries', 'glossaryid = :glossaryid AND LOWER(concept) = :concept',
4006          array(
4007              'glossaryid' => $glossary->id,
4008              'concept'    => core_text::strtolower($concept)
4009          )
4010      );
4011  }
4012  
4013  /**
4014   * Return the editor and attachment options when editing a glossary entry
4015   *
4016   * @param  stdClass $course  course object
4017   * @param  stdClass $context context object
4018   * @param  stdClass $entry   entry object
4019   * @return array array containing the editor and attachment options
4020   * @since  Moodle 3.2
4021   */
4022  function glossary_get_editor_and_attachment_options($course, $context, $entry) {
4023      $maxfiles = 99;                // TODO: add some setting.
4024      $maxbytes = $course->maxbytes; // TODO: add some setting.
4025  
4026      $definitionoptions = array('trusttext' => true, 'maxfiles' => $maxfiles, 'maxbytes' => $maxbytes, 'context' => $context,
4027          'subdirs' => file_area_contains_subdirs($context, 'mod_glossary', 'entry', $entry->id));
4028      $attachmentoptions = array('subdirs' => false, 'maxfiles' => $maxfiles, 'maxbytes' => $maxbytes);
4029      return array($definitionoptions, $attachmentoptions);
4030  }
4031  
4032  /**
4033   * Creates or updates a glossary entry
4034   *
4035   * @param  stdClass $entry entry data
4036   * @param  stdClass $course course object
4037   * @param  stdClass $cm course module object
4038   * @param  stdClass $glossary glossary object
4039   * @param  stdClass $context context object
4040   * @return stdClass the complete new or updated entry
4041   * @since  Moodle 3.2
4042   */
4043  function glossary_edit_entry($entry, $course, $cm, $glossary, $context) {
4044      global $DB, $USER;
4045  
4046      list($definitionoptions, $attachmentoptions) = glossary_get_editor_and_attachment_options($course, $context, $entry);
4047  
4048      $timenow = time();
4049  
4050      $categories = empty($entry->categories) ? array() : $entry->categories;
4051      unset($entry->categories);
4052      $aliases = trim($entry->aliases);
4053      unset($entry->aliases);
4054  
4055      if (empty($entry->id)) {
4056          $entry->glossaryid       = $glossary->id;
4057          $entry->timecreated      = $timenow;
4058          $entry->userid           = $USER->id;
4059          $entry->timecreated      = $timenow;
4060          $entry->sourceglossaryid = 0;
4061          $entry->teacherentry     = has_capability('mod/glossary:manageentries', $context);
4062          $isnewentry              = true;
4063      } else {
4064          $isnewentry              = false;
4065      }
4066  
4067      $entry->concept          = trim($entry->concept);
4068      $entry->definition       = '';          // Updated later.
4069      $entry->definitionformat = FORMAT_HTML; // Updated later.
4070      $entry->definitiontrust  = 0;           // Updated later.
4071      $entry->timemodified     = $timenow;
4072      $entry->approved         = 0;
4073      $entry->usedynalink      = isset($entry->usedynalink) ? $entry->usedynalink : 0;
4074      $entry->casesensitive    = isset($entry->casesensitive) ? $entry->casesensitive : 0;
4075      $entry->fullmatch        = isset($entry->fullmatch) ? $entry->fullmatch : 0;
4076  
4077      if ($glossary->defaultapproval or has_capability('mod/glossary:approve', $context)) {
4078          $entry->approved = 1;
4079      }
4080  
4081      if ($isnewentry) {
4082          // Add new entry.
4083          $entry->id = $DB->insert_record('glossary_entries', $entry);
4084      } else {
4085          // Update existing entry.
4086          $DB->update_record('glossary_entries', $entry);
4087      }
4088  
4089      // Save and relink embedded images and save attachments.
4090      if (!empty($entry->definition_editor)) {
4091          $entry = file_postupdate_standard_editor($entry, 'definition', $definitionoptions, $context, 'mod_glossary', 'entry',
4092              $entry->id);
4093      }
4094      if (!empty($entry->attachment_filemanager)) {
4095          $entry = file_postupdate_standard_filemanager($entry, 'attachment', $attachmentoptions, $context, 'mod_glossary',
4096              'attachment', $entry->id);
4097      }
4098  
4099      // Store the updated value values.
4100      $DB->update_record('glossary_entries', $entry);
4101  
4102      // Refetch complete entry.
4103      $entry = $DB->get_record('glossary_entries', array('id' => $entry->id));
4104  
4105      // Update entry categories.
4106      $DB->delete_records('glossary_entries_categories', array('entryid' => $entry->id));
4107      // TODO: this deletes cats from both both main and secondary glossary :-(.
4108      if (!empty($categories) and array_search(0, $categories) === false) {
4109          foreach ($categories as $catid) {
4110              $newcategory = new stdClass();
4111              $newcategory->entryid    = $entry->id;
4112              $newcategory->categoryid = $catid;
4113              $DB->insert_record('glossary_entries_categories', $newcategory, false);
4114          }
4115      }
4116  
4117      // Update aliases.
4118      $DB->delete_records('glossary_alias', array('entryid' => $entry->id));
4119      if ($aliases !== '') {
4120          $aliases = explode("\n", $aliases);
4121          foreach ($aliases as $alias) {
4122              $alias = trim($alias);
4123              if ($alias !== '') {
4124                  $newalias = new stdClass();
4125                  $newalias->entryid = $entry->id;
4126                  $newalias->alias   = $alias;
4127                  $DB->insert_record('glossary_alias', $newalias, false);
4128              }
4129          }
4130      }
4131  
4132      // Trigger event and update completion (if entry was created).
4133      $eventparams = array(
4134          'context' => $context,
4135          'objectid' => $entry->id,
4136          'other' => array('concept' => $entry->concept)
4137      );
4138      if ($isnewentry) {
4139          $event = \mod_glossary\event\entry_created::create($eventparams);
4140      } else {
4141          $event = \mod_glossary\event\entry_updated::create($eventparams);
4142      }
4143      $event->add_record_snapshot('glossary_entries', $entry);
4144      $event->trigger();
4145      if ($isnewentry) {
4146          // Update completion state.
4147          $completion = new completion_info($course);
4148          if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC && $glossary->completionentries && $entry->approved) {
4149              $completion->update_state($cm, COMPLETION_COMPLETE);
4150          }
4151      }
4152  
4153      // Reset caches.
4154      if ($isnewentry) {
4155          if ($entry->usedynalink and $entry->approved) {
4156              \mod_glossary\local\concept_cache::reset_glossary($glossary);
4157          }
4158      } else {
4159          // So many things may affect the linking, let's just purge the cache always on edit.
4160          \mod_glossary\local\concept_cache::reset_glossary($glossary);
4161      }
4162      return $entry;
4163  }
4164  
4165  /**
4166   * Check if the module has any update that affects the current user since a given time.
4167   *
4168   * @param  cm_info $cm course module data
4169   * @param  int $from the time to check updates from
4170   * @param  array $filter  if we need to check only specific updates
4171   * @return stdClass an object with the different type of areas indicating if they were updated or not
4172   * @since Moodle 3.2
4173   */
4174  function glossary_check_updates_since(cm_info $cm, $from, $filter = array()) {
4175      global $DB;
4176  
4177      $updates = course_check_module_updates_since($cm, $from, array('attachment', 'entry'), $filter);
4178  
4179      $updates->entries = (object) array('updated' => false);
4180      $select = 'glossaryid = :id AND (timecreated > :since1 OR timemodified > :since2)';
4181      $params = array('id' => $cm->instance, 'since1' => $from, 'since2' => $from);
4182      if (!has_capability('mod/glossary:approve', $cm->context)) {
4183          $select .= ' AND approved = 1';
4184      }
4185  
4186      $entries = $DB->get_records_select('glossary_entries', $select, $params, '', 'id');
4187      if (!empty($entries)) {
4188          $updates->entries->updated = true;
4189          $updates->entries->itemids = array_keys($entries);
4190      }
4191  
4192      return $updates;
4193  }
4194  
4195  /**
4196   * Get icon mapping for font-awesome.
4197   *
4198   * @return array
4199   */
4200  function mod_glossary_get_fontawesome_icon_map() {
4201      return [
4202          'mod_glossary:export' => 'fa-download',
4203          'mod_glossary:minus' => 'fa-minus'
4204      ];
4205  }
4206  
4207  /**
4208   * This function receives a calendar event and returns the action associated with it, or null if there is none.
4209   *
4210   * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
4211   * is not displayed on the block.
4212   *
4213   * @param calendar_event $event
4214   * @param \core_calendar\action_factory $factory
4215   * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
4216   * @return \core_calendar\local\event\entities\action_interface|null
4217   */
4218  function mod_glossary_core_calendar_provide_event_action(calendar_event $event,
4219                                                           \core_calendar\action_factory $factory,
4220                                                           int $userid = 0) {
4221      global $USER;
4222  
4223      if (!$userid) {
4224          $userid = $USER->id;
4225      }
4226  
4227      $cm = get_fast_modinfo($event->courseid, $userid)->instances['glossary'][$event->instance];
4228  
4229      if (!$cm->uservisible) {
4230          // The module is not visible to the user for any reason.
4231          return null;
4232      }
4233  
4234      $completion = new \completion_info($cm->get_course());
4235  
4236      $completiondata = $completion->get_data($cm, false, $userid);
4237  
4238      if ($completiondata->completionstate != COMPLETION_INCOMPLETE) {
4239          return null;
4240      }
4241  
4242      return $factory->create_instance(
4243          get_string('view'),
4244          new \moodle_url('/mod/glossary/view.php', ['id' => $cm->id]),
4245          1,
4246          true
4247      );
4248  }
4249  
4250  /**
4251   * Add a get_coursemodule_info function in case any glossary type wants to add 'extra' information
4252   * for the course (see resource).
4253   *
4254   * Given a course_module object, this function returns any "extra" information that may be needed
4255   * when printing this activity in a course listing.  See get_array_of_activities() in course/lib.php.
4256   *
4257   * @param stdClass $coursemodule The coursemodule object (record).
4258   * @return cached_cm_info An object on information that the courses
4259   *                        will know about (most noticeably, an icon).
4260   */
4261  function glossary_get_coursemodule_info($coursemodule) {
4262      global $DB;
4263  
4264      $dbparams = ['id' => $coursemodule->instance];
4265      $fields = 'id, name, intro, introformat, completionentries';
4266      if (!$glossary = $DB->get_record('glossary', $dbparams, $fields)) {
4267          return false;
4268      }
4269  
4270      $result = new cached_cm_info();
4271      $result->name = $glossary->name;
4272  
4273      if ($coursemodule->showdescription) {
4274          // Convert intro to html. Do not filter cached version, filters run at display time.
4275          $result->content = format_module_intro('glossary', $glossary, $coursemodule->id, false);
4276      }
4277  
4278      // Populate the custom completion rules as key => value pairs, but only if the completion mode is 'automatic'.
4279      if ($coursemodule->completion == COMPLETION_TRACKING_AUTOMATIC) {
4280          $result->customdata['customcompletionrules']['completionentries'] = $glossary->completionentries;
4281      }
4282  
4283      return $result;
4284  }
4285  
4286  /**
4287   * Callback which returns human-readable strings describing the active completion custom rules for the module instance.
4288   *
4289   * @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
4290   * @return array $descriptions the array of descriptions for the custom rules.
4291   */
4292  function mod_glossary_get_completion_active_rule_descriptions($cm) {
4293      // Values will be present in cm_info, and we assume these are up to date.
4294      if (empty($cm->customdata['customcompletionrules'])
4295          || $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
4296          return [];
4297      }
4298  
4299      $descriptions = [];
4300      foreach ($cm->customdata['customcompletionrules'] as $key => $val) {
4301          switch ($key) {
4302              case 'completionentries':
4303                  if (!empty($val)) {
4304                      $descriptions[] = get_string('completionentriesdesc', 'glossary', $val);
4305                  }
4306                  break;
4307              default:
4308                  break;
4309          }
4310      }
4311      return $descriptions;
4312  }
4313  
4314  /**
4315   * Checks if the current user can delete the given glossary entry.
4316   *
4317   * @since Moodle 3.10
4318   * @param stdClass $entry the entry database object
4319   * @param stdClass $glossary the glossary database object
4320   * @param stdClass $context the glossary context
4321   * @param bool $return Whether to return a boolean value or stop the execution (exception)
4322   * @return bool if the user can delete the entry
4323   * @throws moodle_exception
4324   */
4325  function mod_glossary_can_delete_entry($entry, $glossary, $context, $return = true) {
4326      global $USER, $CFG;
4327  
4328      $manageentries = has_capability('mod/glossary:manageentries', $context);
4329  
4330      if ($manageentries) {   // Users with the capability will always be able to delete entries.
4331          return true;
4332      }
4333  
4334      if ($entry->userid != $USER->id) { // Guest id is never matched, no need for special check here.
4335          if ($return) {
4336              return false;
4337          }
4338          throw new moodle_exception('nopermissiontodelentry');
4339      }
4340  
4341      $ineditperiod = ((time() - $entry->timecreated < $CFG->maxeditingtime) || $glossary->editalways);
4342  
4343      if (!$ineditperiod) {
4344          if ($return) {
4345              return false;
4346          }
4347          throw new moodle_exception('errdeltimeexpired', 'glossary');
4348      }
4349  
4350      return true;
4351  }
4352  
4353  /**
4354   * Deletes the given entry, this function does not perform capabilities/permission checks.
4355   *
4356   * @since Moodle 3.10
4357   * @param stdClass $entry the entry database object
4358   * @param stdClass $glossary the glossary database object
4359   * @param stdClass $cm the glossary course moduule object
4360   * @param stdClass $context the glossary context
4361   * @param stdClass $course the glossary course
4362   * @param string $hook the hook, usually type of filtering, value
4363   * @param string $prevmode the previsualisation mode
4364   * @throws moodle_exception
4365   */
4366  function mod_glossary_delete_entry($entry, $glossary, $cm, $context, $course, $hook = '', $prevmode = '') {
4367      global $CFG, $DB;
4368  
4369      $origentry = fullclone($entry);
4370  
4371      // If it is an imported entry, just delete the relation.
4372      if ($entry->sourceglossaryid) {
4373          if (!$newcm = get_coursemodule_from_instance('glossary', $entry->sourceglossaryid)) {
4374              print_error('invalidcoursemodule');
4375          }
4376          $newcontext = context_module::instance($newcm->id);
4377  
4378          $entry->glossaryid       = $entry->sourceglossaryid;
4379          $entry->sourceglossaryid = 0;
4380          $DB->update_record('glossary_entries', $entry);
4381  
4382          // Move attachments too.
4383          $fs = get_file_storage();
4384  
4385          if ($oldfiles = $fs->get_area_files($context->id, 'mod_glossary', 'attachment', $entry->id)) {
4386              foreach ($oldfiles as $oldfile) {
4387                  $filerecord = new stdClass();
4388                  $filerecord->contextid = $newcontext->id;
4389                  $fs->create_file_from_storedfile($filerecord, $oldfile);
4390              }
4391              $fs->delete_area_files($context->id, 'mod_glossary', 'attachment', $entry->id);
4392              $entry->attachment = '1';
4393          } else {
4394              $entry->attachment = '0';
4395          }
4396          $DB->update_record('glossary_entries', $entry);
4397  
4398      } else {
4399          $fs = get_file_storage();
4400          $fs->delete_area_files($context->id, 'mod_glossary', 'attachment', $entry->id);
4401          $DB->delete_records("comments",
4402              ['itemid' => $entry->id, 'commentarea' => 'glossary_entry', 'contextid' => $context->id]);
4403          $DB->delete_records("glossary_alias", ["entryid" => $entry->id]);
4404          $DB->delete_records("glossary_entries", ["id" => $entry->id]);
4405  
4406          // Update completion state.
4407          $completion = new completion_info($course);
4408          if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC && $glossary->completionentries) {
4409              $completion->update_state($cm, COMPLETION_INCOMPLETE, $entry->userid);
4410          }
4411  
4412          // Delete glossary entry ratings.
4413          require_once($CFG->dirroot.'/rating/lib.php');
4414          $delopt = new stdClass;
4415          $delopt->contextid = $context->id;
4416          $delopt->component = 'mod_glossary';
4417          $delopt->ratingarea = 'entry';
4418          $delopt->itemid = $entry->id;
4419          $rm = new rating_manager();
4420          $rm->delete_ratings($delopt);
4421      }
4422  
4423      // Delete cached RSS feeds.
4424      if (!empty($CFG->enablerssfeeds)) {
4425          require_once($CFG->dirroot . '/mod/glossary/rsslib.php');
4426          glossary_rss_delete_file($glossary);
4427      }
4428  
4429      core_tag_tag::remove_all_item_tags('mod_glossary', 'glossary_entries', $origentry->id);
4430  
4431      $event = \mod_glossary\event\entry_deleted::create(
4432          [
4433              'context' => $context,
4434              'objectid' => $origentry->id,
4435              'other' => [
4436                  'mode' => $prevmode,
4437                  'hook' => $hook,
4438                  'concept' => $origentry->concept
4439              ]
4440          ]
4441      );
4442      $event->add_record_snapshot('glossary_entries', $origentry);
4443      $event->trigger();
4444  
4445      // Reset caches.
4446      if ($entry->usedynalink and $entry->approved) {
4447          \mod_glossary\local\concept_cache::reset_glossary($glossary);
4448      }
4449  }
4450  
4451  /**
4452   * Checks if the current user can update the given glossary entry.
4453   *
4454   * @since Moodle 3.10
4455   * @param stdClass $entry the entry database object
4456   * @param stdClass $glossary the glossary database object
4457   * @param stdClass $context the glossary context
4458   * @param object $cm the course module object (cm record or cm_info instance)
4459   * @param bool $return Whether to return a boolean value or stop the execution (exception)
4460   * @return bool if the user can update the entry
4461   * @throws moodle_exception
4462   */
4463  function mod_glossary_can_update_entry(stdClass $entry, stdClass $glossary, stdClass $context, object $cm,
4464          bool $return = true): bool {
4465  
4466      global $USER, $CFG;
4467  
4468      $ineditperiod = ((time() - $entry->timecreated < $CFG->maxeditingtime) || $glossary->editalways);
4469      if (!has_capability('mod/glossary:manageentries', $context) and
4470              !($entry->userid == $USER->id and ($ineditperiod and has_capability('mod/glossary:write', $context)))) {
4471  
4472          if ($USER->id != $entry->userid) {
4473              if ($return) {
4474                  return false;
4475              }
4476              throw new moodle_exception('errcannoteditothers', 'glossary', "view.php?id=$cm->id&amp;mode=entry&amp;hook=$entry->id");
4477          } else if (!$ineditperiod) {
4478              if ($return) {
4479                  return false;
4480              }
4481              throw new moodle_exception('erredittimeexpired', 'glossary', "view.php?id=$cm->id&amp;mode=entry&amp;hook=$entry->id");
4482          }
4483      }
4484  
4485      return true;
4486  }
4487  
4488  /**
4489   * Prepares an entry for editing, adding aliases and category information.
4490   *
4491   * @param  stdClass $entry the entry being edited
4492   * @return stdClass the entry with the additional data
4493   */
4494  function mod_glossary_prepare_entry_for_edition(stdClass $entry): stdClass {
4495      global $DB;
4496  
4497      if ($aliases = $DB->get_records_menu("glossary_alias", ["entryid" => $entry->id], '', 'id, alias')) {
4498          $entry->aliases = implode("\n", $aliases) . "\n";
4499      }
4500      if ($categoriesarr = $DB->get_records_menu("glossary_entries_categories", ['entryid' => $entry->id], '', 'id, categoryid')) {
4501          // TODO: this fetches cats from both main and secondary glossary :-(
4502          $entry->categories = array_values($categoriesarr);
4503      }
4504  
4505      return $entry;
4506  }