Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
  • Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 37 and 311] [Versions 38 and 311] [Versions 39 and 311]

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