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 */
26require_once($CFG->libdir . '/completionlib.php');
27
28define("GLOSSARY_SHOW_ALL_CATEGORIES", 0);
29define("GLOSSARY_SHOW_NOT_CATEGORISED", -1);
30
31define("GLOSSARY_NO_VIEW", -1);
32define("GLOSSARY_STANDARD_VIEW", 0);
33define("GLOSSARY_CATEGORY_VIEW", 1);
34define("GLOSSARY_DATE_VIEW", 2);
35define("GLOSSARY_AUTHOR_VIEW", 3);
36define("GLOSSARY_ADDENTRY_VIEW", 4);
37define("GLOSSARY_IMPORT_VIEW", 5);
38define("GLOSSARY_EXPORT_VIEW", 6);
39define("GLOSSARY_APPROVAL_VIEW", 7);
40
41// Glossary tabs.
42define('GLOSSARY_STANDARD', 'standard');
43define('GLOSSARY_AUTHOR', 'author');
44define('GLOSSARY_CATEGORY', 'category');
45define('GLOSSARY_DATE', 'date');
46
47// Glossary displayformats.
48define('GLOSSARY_CONTINUOUS', 'continuous');
49define('GLOSSARY_DICTIONARY', 'dictionary');
50define('GLOSSARY_FULLWITHOUTAUTHOR', 'fullwithoutauthor');
51
52require_once(__DIR__ . '/deprecatedlib.php');
53
54/// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
55/**
56 * @global object
57 * @param object $glossary
58 * @return int
59 */
60function 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 */
110function 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 */
158function 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 */
248function 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 */
295function 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 */
318function 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 */
358function 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 */
479function 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 */
528function 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 */
628function 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 */
642function 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 */
653function 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 */
684function 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 */
712function 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 */
796function 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 */
825function 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 */
858function 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 */
868function 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 */
882function 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 */
906function 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 */
980function 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 */
997function 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 */
1016function 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 */
1066function 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 */
1101function 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 */
1129function  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 */
1151function 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 */
1194function  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 */
1223function 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 */
1359function  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 */
1397function 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 */
1421function  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 */
1452function 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 */
1547function 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 */
1568function 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 */
1642function 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 */
1663function 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 */
1735function 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 */
1810function 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 */
1822function 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 */
1841function 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 */
1852function 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 */
1862function 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 */
1885function 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 */
1903function 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 */
1983function glossary_print_all_links($cm, $glossary, $mode, $hook) {
1984global $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 */
2003function glossary_print_special_links($cm, $glossary, $mode, $hook) {
2004global $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 */
2024function glossary_print_alphabet_links($cm, $glossary, $mode, $hook, $sortkey, $sortorder) {
2025global $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 */
2046function 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 */
2147function 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 */
2167function  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 */
2183function 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 */
2240function 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 */
2301function 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 */
2427function 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 */
2442function 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 */
2458function 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 */
2477function 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 */
2496function 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 */
2526function 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 */
2568function 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 */
2638function 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 */
2767function 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 */
2781function 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 */
2791function 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 */
2817function 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 */
2827function 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 */
2854function 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 */
3075function 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 */
3084function 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
3102function 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 */
3138function 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 */
3192function 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 */
3211function 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 */
3268function 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 */
3281function 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 */
3294function 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 */
3330function 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 */
3351function 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 */
3378function 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 */
3404function 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 */
3449function 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 */
3491function 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 */
3548function 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 */
3596function 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 */
3641function 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 */
3681function 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);
3686
3687    return array($categories, $count);
3688}
3689
3690/**
3691 * Get the SQL where clause for searching terms.
3692 *
3693 * Note that this does not handle invalid or too short terms.
3694 *
3695 * @param array   $terms      Array of terms.
3696 * @param bool    $fullsearch Whether or not full search should be enabled.
3697 * @param int     $glossaryid The ID of a glossary to reduce the search results.
3698 * @return array The first element being the where clause, the second array of parameters.
3699 * @since Moodle 3.1
3700 */
3701function glossary_get_search_terms_sql(array $terms, $fullsearch = true, $glossaryid = null) {
3702    global $DB;
3703    static $i = 0;
3704
3705    if ($DB->sql_regex_supported()) {
3706        $regexp = $DB->sql_regex(true);
3707        $notregexp = $DB->sql_regex(false);
3708    }
3709
3710    $params = array();
3711    $conditions = array();
3712
3713    foreach ($terms as $searchterm) {
3714        $i++;
3715
3716        $not = false; // Initially we aren't going to perform NOT LIKE searches, only MSSQL and Oracle
3717                      // will use it to simulate the "-" operator with LIKE clause.
3718
3719        if (empty($fullsearch)) {
3720            // With fullsearch disabled, look only within concepts and aliases.
3721            $concat = $DB->sql_concat('ge.concept', "' '", "COALESCE(al.alias, :emptychar{$i})");
3722        } else {
3723            // With fullsearch enabled, look also within definitions.
3724            $concat = $DB->sql_concat('ge.concept', "' '", 'ge.definition', "' '", "COALESCE(al.alias, :emptychar{$i})");
3725        }
3726        $params['emptychar' . $i] = '';
3727
3728        // Under Oracle and MSSQL, trim the + and - operators and perform simpler LIKE (or NOT LIKE) queries.
3729        if (!$DB->sql_regex_supported()) {
3730            if (substr($searchterm, 0, 1) === '-') {
3731                $not = true;
3732            }
3733            $searchterm = trim($searchterm, '+-');
3734        }
3735
3736        if (substr($searchterm, 0, 1) === '+') {
3737            $searchterm = trim($searchterm, '+-');
3738            $conditions[] = "$concat $regexp :searchterm{$i}";
3739            $params['searchterm' . $i] = '(^|[^a-zA-Z0-9])' . preg_quote($searchterm, '|') . '([^a-zA-Z0-9]|$)';
3740
3741        } else if (substr($searchterm, 0, 1) === "-") {
3742            $searchterm = trim($searchterm, '+-');
3743            $conditions[] = "$concat $notregexp :searchterm{$i}";
3744            $params['searchterm' . $i] = '(^|[^a-zA-Z0-9])' . preg_quote($searchterm, '|') . '([^a-zA-Z0-9]|$)';
3745
3746        } else {
3747            $conditions[] = $DB->sql_like($concat, ":searchterm{$i}", false, true, $not);
3748            $params['searchterm' . $i] = '%' . $DB->sql_like_escape($searchterm) . '%';
3749        }
3750    }
3751
3752    // Reduce the search results by restricting it to one glossary.
3753    if (isset($glossaryid)) {
3754        $conditions[] = 'ge.glossaryid = :glossaryid';
3755        $params['glossaryid'] = $glossaryid;
3756    }
3757
3758    // When there are no conditions we add a negative one to ensure that we don't return anything.
3759    if (empty($conditions)) {
3760        $conditions[] = '1 = 2';
3761    }
3762
3763    $where = implode(' AND ', $conditions);
3764    return array($where, $params);
3765}
3766
3767
3768/**
3769 * Returns the entries of a glossary by search.
3770 *
3771 * @param  object $glossary The glossary.
3772 * @param  context $context The context of the glossary.
3773 * @param  string $query The search query.
3774 * @param  bool $fullsearch Whether or not full search is required.
3775 * @param  string $order The mode of ordering: CONCEPT, CREATION or UPDATE.
3776 * @param  string $sort The direction of the ordering: ASC or DESC.
3777 * @param  int $from Fetch records from.
3778 * @param  int $limit Number of records to fetch.
3779 * @param  array $options Accepts:
3780 *                        - (bool) includenotapproved. When false, includes the non-approved entries created by
3781 *                          the current user. When true, also includes the ones that the user has the permission to approve.
3782 * @return array The first element being the array of results, the second the number of entries.
3783 * @since Moodle 3.1
3784 */
3785function glossary_get_entries_by_search($glossary, $context, $query, $fullsearch, $order, $sort, $from, $limit,
3786                                        $options = array()) {
3787    global $DB, $USER;
3788
3789    // Clean terms.
3790    $terms = explode(' ', $query);
3791    foreach ($terms as $key => $term) {
3792        if (strlen(trim($term, '+-')) < 1) {
3793            unset($terms[$key]);
3794        }
3795    }
3796
3797    list($searchcond, $params) = glossary_get_search_terms_sql($terms, $fullsearch, $glossary->id);
3798
3799    $userfieldsapi = \core_user\fields::for_userpic();
3800    $userfields = $userfieldsapi->get_sql('u', false, 'userdata', 'userdataid', false)->selects;
3801
3802    // Need one inner view here to avoid distinct + text.
3803    $sqlwrapheader = 'SELECT ge.*, ge.concept AS glossarypivot, ' . $userfields . '
3804                        FROM {glossary_entries} ge
3805                        LEFT JOIN {user} u ON u.id = ge.userid
3806                        JOIN ( ';
3807    $sqlwrapfooter = ' ) gei ON (ge.id = gei.id)';
3808    $sqlselect  = "SELECT DISTINCT ge.id";
3809    $sqlfrom    = "FROM {glossary_entries} ge
3810                   LEFT JOIN {glossary_alias} al ON al.entryid = ge.id";
3811
3812    if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3813        $approvedsql = '';
3814    } else {
3815        $approvedsql = 'AND (ge.approved <> 0 OR ge.userid = :myid)';
3816        $params['myid'] = $USER->id;
3817    }
3818
3819    if ($order == 'CREATION') {
3820        $sqlorderby = "ORDER BY ge.timecreated $sort";
3821    } else if ($order == 'UPDATE') {
3822        $sqlorderby = "ORDER BY ge.timemodified $sort";
3823    } else {
3824        $sqlorderby = "ORDER BY ge.concept $sort";
3825    }
3826    $sqlorderby .= " , ge.id ASC"; // Sort on ID to avoid random ordering when entries share an ordering value.
3827
3828    $sqlwhere = "WHERE ($searchcond) $approvedsql";
3829
3830    // Fetching the entries.
3831    $count = $DB->count_records_sql("SELECT COUNT(DISTINCT(ge.id)) $sqlfrom $sqlwhere", $params);
3832
3833    $query = "$sqlwrapheader $sqlselect $sqlfrom $sqlwhere $sqlwrapfooter $sqlorderby";
3834    $entries = $DB->get_records_sql($query, $params, $from, $limit);
3835
3836    return array($entries, $count);
3837}
3838
3839/**
3840 * Returns the entries of a glossary by term.
3841 *
3842 * @param  object $glossary The glossary.
3843 * @param  context $context The context of the glossary.
3844 * @param  string $term The term we are searching for, a concept or alias.
3845 * @param  int $from Fetch records from.
3846 * @param  int $limit Number of records to fetch.
3847 * @param  array $options Accepts:
3848 *                        - (bool) includenotapproved. When false, includes the non-approved entries created by
3849 *                          the current user. When true, also includes the ones that the user has the permission to approve.
3850 * @return array The first element being the recordset, the second the number of entries.
3851 * @since Moodle 3.1
3852 */
3853function glossary_get_entries_by_term($glossary, $context, $term, $from, $limit, $options = array()) {
3854
3855    // Build the query.
3856    $qb = new mod_glossary_entry_query_builder($glossary);
3857    if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) {
3858        $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL);
3859    } else {
3860        $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF);
3861    }
3862
3863    $qb->add_field('*', 'entries');
3864    $qb->join_alias();
3865    $qb->join_user();
3866    $qb->add_user_fields();
3867    $qb->filter_by_term($term);
3868
3869    $qb->order_by('concept', 'entries');
3870    $qb->order_by('id', 'entries');     // Sort on ID to avoid random ordering when entries share an ordering value.
3871    $qb->limit($from, $limit);
3872
3873    // Fetching the entries.
3874    $count = $qb->count_records();
3875    $entries = $qb->get_records();
3876
3877    return array($entries, $count);
3878}
3879
3880/**
3881 * Returns the entries to be approved.
3882 *
3883 * @param  object $glossary The glossary.
3884 * @param  context $context The context of the glossary.
3885 * @param  string $letter The letter, or ALL, or SPECIAL.
3886 * @param  string $order The mode of ordering: CONCEPT, CREATION or UPDATE.
3887 * @param  string $sort The direction of the ordering: ASC or DESC.
3888 * @param  int $from Fetch records from.
3889 * @param  int $limit Number of records to fetch.
3890 * @return array The first element being the recordset, the second the number of entries.
3891 * @since Moodle 3.1
3892 */
3893function glossary_get_entries_to_approve($glossary, $context, $letter, $order, $sort, $from, $limit) {
3894
3895    $qb = new mod_glossary_entry_query_builder($glossary);
3896    if ($letter != 'ALL' && $letter != 'SPECIAL' && core_text::strlen($letter)) {
3897        $qb->filter_by_concept_letter($letter);
3898    }
3899    if ($letter == 'SPECIAL') {
3900        $qb->filter_by_concept_non_letter();
3901    }
3902
3903    $qb->add_field('*', 'entries');
3904    $qb->join_user();
3905    $qb->add_user_fields();
3906    $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ONLY);
3907    if ($order == 'CREATION') {
3908        $qb->order_by('timecreated', 'entries', $sort);
3909    } else if ($order == 'UPDATE') {
3910        $qb->order_by('timemodified', 'entries', $sort);
3911    } else {
3912        $qb->order_by('concept', 'entries', $sort);
3913    }
3914    $qb->order_by('id', 'entries', $sort); // Sort on ID to avoid random ordering when entries share an ordering value.
3915    $qb->limit($from, $limit);
3916
3917    // Fetching the entries.
3918    $count = $qb->count_records();
3919    $entries = $qb->get_records();
3920
3921    return array($entries, $count);
3922}
3923
3924/**
3925 * Fetch an entry.
3926 *
3927 * @param  int $id The entry ID.
3928 * @return object|false The entry, or false when not found.
3929 * @since Moodle 3.1
3930 */
3931function glossary_get_entry_by_id($id) {
3932
3933    // Build the query.
3934    $qb = new mod_glossary_entry_query_builder();
3935    $qb->add_field('*', 'entries');
3936    $qb->join_user();
3937    $qb->add_user_fields();
3938    $qb->where('id', 'entries', $id);
3939
3940    // Fetching the entries.
3941    $entries = $qb->get_records();
3942    if (empty($entries)) {
3943        return false;
3944    }
3945    return array_pop($entries);
3946}
3947
3948/**
3949 * Checks if the current user can see the glossary entry.
3950 *
3951 * @since Moodle 3.1
3952 * @param stdClass $entry
3953 * @param cm_info  $cminfo
3954 * @return bool
3955 */
3956function glossary_can_view_entry($entry, $cminfo) {
3957    global $USER;
3958
3959    $cm = $cminfo->get_course_module_record();
3960    $context = \context_module::instance($cm->id);
3961
3962    // Recheck uservisible although it should have already been checked in core_search.
3963    if ($cminfo->uservisible === false) {
3964        return false;
3965    }
3966
3967    // Check approval.
3968    if (empty($entry->approved) && $entry->userid != $USER->id && !has_capability('mod/glossary:approve', $context)) {
3969        return false;
3970    }
3971
3972    return true;
3973}
3974
3975/**
3976 * Check if a concept exists in a glossary.
3977 *
3978 * @param  stdClass $glossary glossary object
3979 * @param  string $concept the concept to check
3980 * @return bool true if exists
3981 * @since  Moodle 3.2
3982 */
3983function glossary_concept_exists($glossary, $concept) {
3984    global $DB;
3985
3986    return $DB->record_exists_select('glossary_entries', 'glossaryid = :glossaryid AND LOWER(concept) = :concept',
3987        array(
3988            'glossaryid' => $glossary->id,
3989            'concept'    => core_text::strtolower($concept)
3990        )
3991    );
3992}
3993
3994/**
3995 * Return the editor and attachment options when editing a glossary entry
3996 *
3997 * @param  stdClass $course  course object
3998 * @param  stdClass $context context object
3999 * @param  stdClass $entry   entry object
4000 * @return array array containing the editor and attachment options
4001 * @since  Moodle 3.2
4002 */
4003function glossary_get_editor_and_attachment_options($course, $context, $entry) {
4004    $maxfiles = 99;                // TODO: add some setting.
4005    $maxbytes = $course->maxbytes; // TODO: add some setting.
4006
4007    $definitionoptions = array('trusttext' => true, 'maxfiles' => $maxfiles, 'maxbytes' => $maxbytes, 'context' => $context,
4008        'subdirs' => file_area_contains_subdirs($context, 'mod_glossary', 'entry', $entry->id));
4009    $attachmentoptions = array('subdirs' => false, 'maxfiles' => $maxfiles, 'maxbytes' => $maxbytes);
4010    return array($definitionoptions, $attachmentoptions);
4011}
4012
4013/**
4014 * Creates or updates a glossary entry
4015 *
4016 * @param  stdClass $entry entry data
4017 * @param  stdClass $course course object
4018 * @param  stdClass $cm course module object
4019 * @param  stdClass $glossary glossary object
4020 * @param  stdClass $context context object
4021 * @return stdClass the complete new or updated entry
4022 * @since  Moodle 3.2
4023 */
4024function glossary_edit_entry($entry, $course, $cm, $glossary, $context) {
4025    global $DB, $USER;
4026
4027    list($definitionoptions, $attachmentoptions) = glossary_get_editor_and_attachment_options($course, $context, $entry);
4028
4029    $timenow = time();
4030
4031    $categories = empty($entry->categories) ? array() : $entry->categories;
4032    unset($entry->categories);
4033    $aliases = trim($entry->aliases);
4034    unset($entry->aliases);
4035
4036    if (empty($entry->id)) {
4037        $entry->glossaryid       = $glossary->id;
4038        $entry->timecreated      = $timenow;
4039        $entry->userid           = $USER->id;
4040        $entry->timecreated      = $timenow;
4041        $entry->sourceglossaryid = 0;
4042        $entry->teacherentry     = has_capability('mod/glossary:manageentries', $context);
4043        $isnewentry              = true;
4044    } else {
4045        $isnewentry              = false;
4046    }
4047
4048    $entry->concept          = trim($entry->concept);
4049    $entry->definition       = '';          // Updated later.
4050    $entry->definitionformat = FORMAT_HTML; // Updated later.
4051    $entry->definitiontrust  = 0;           // Updated later.
4052    $entry->timemodified     = $timenow;
4053    $entry->approved         = 0;
4054    $entry->usedynalink      = isset($entry->usedynalink) ? $entry->usedynalink : 0;
4055    $entry->casesensitive    = isset($entry->casesensitive) ? $entry->casesensitive : 0;
4056    $entry->fullmatch        = isset($entry->fullmatch) ? $entry->fullmatch : 0;
4057
4058    if ($glossary->defaultapproval or has_capability('mod/glossary:approve', $context)) {
4059        $entry->approved = 1;
4060    }
4061
4062    if ($isnewentry) {
4063        // Add new entry.
4064        $entry->id = $DB->insert_record('glossary_entries', $entry);
4065    } else {
4066        // Update existing entry.
4067        $DB->update_record('glossary_entries', $entry);
4068    }
4069
4070    // Save and relink embedded images and save attachments.
4071    if (!empty($entry->definition_editor)) {
4072        $entry = file_postupdate_standard_editor($entry, 'definition', $definitionoptions, $context, 'mod_glossary', 'entry',
4073            $entry->id);
4074    }
4075    if (!empty($entry->attachment_filemanager)) {
4076        $entry = file_postupdate_standard_filemanager($entry, 'attachment', $attachmentoptions, $context, 'mod_glossary',
4077            'attachment', $entry->id);
4078    }
4079
4080    // Store the updated value values.
4081    $DB->update_record('glossary_entries', $entry);
4082
4083    // Refetch complete entry.
4084    $entry = $DB->get_record('glossary_entries', array('id' => $entry->id));
4085
4086    // Update entry categories.
4087    $DB->delete_records('glossary_entries_categories', array('entryid' => $entry->id));
4088    // TODO: this deletes cats from both both main and secondary glossary :-(.
4089    if (!empty($categories) and array_search(0, $categories) === false) {
4090        foreach ($categories as $catid) {
4091            $newcategory = new stdClass();
4092            $newcategory->entryid    = $entry->id;
4093            $newcategory->categoryid = $catid;
4094            $DB->insert_record('glossary_entries_categories', $newcategory, false);
4095        }
4096    }
4097
4098    // Update aliases.
4099    $DB->delete_records('glossary_alias', array('entryid' => $entry->id));
4100    if ($aliases !== '') {
4101        $aliases = explode("\n", $aliases);
4102        foreach ($aliases as $alias) {
4103            $alias = trim($alias);
4104            if ($alias !== '') {
4105                $newalias = new stdClass();
4106                $newalias->entryid = $entry->id;
4107                $newalias->alias   = $alias;
4108                $DB->insert_record('glossary_alias', $newalias, false);
4109            }
4110        }
4111    }
4112
4113    // Trigger event and update completion (if entry was created).
4114    $eventparams = array(
4115        'context' => $context,
4116        'objectid' => $entry->id,
4117        'other' => array('concept' => $entry->concept)
4118    );
4119    if ($isnewentry) {
4120        $event = \mod_glossary\event\entry_created::create($eventparams);
4121    } else {
4122        $event = \mod_glossary\event\entry_updated::create($eventparams);
4123    }
4124    $event->add_record_snapshot('glossary_entries', $entry);
4125    $event->trigger();
4126    if ($isnewentry) {
4127        // Update completion state.
4128        $completion = new completion_info($course);
4129        if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC && $glossary->completionentries && $entry->approved) {
4130            $completion->update_state($cm, COMPLETION_COMPLETE);
4131        }
4132    }
4133
4134    // Reset caches.
4135    if ($isnewentry) {
4136        if ($entry->usedynalink and $entry->approved) {
4137            \mod_glossary\local\concept_cache::reset_glossary($glossary);
4138        }
4139    } else {
4140        // So many things may affect the linking, let's just purge the cache always on edit.
4141        \mod_glossary\local\concept_cache::reset_glossary($glossary);
4142    }
4143    return $entry;
4144}
4145
4146/**
4147 * Check if the module has any update that affects the current user since a given time.
4148 *
4149 * @param  cm_info $cm course module data
4150 * @param  int $from the time to check updates from
4151 * @param  array $filter  if we need to check only specific updates
4152 * @return stdClass an object with the different type of areas indicating if they were updated or not
4153 * @since Moodle 3.2
4154 */
4155function glossary_check_updates_since(cm_info $cm, $from, $filter = array()) {
4156    global $DB;
4157
4158    $updates = course_check_module_updates_since($cm, $from, array('attachment', 'entry'), $filter);
4159
4160    $updates->entries = (object) array('updated' => false);
4161    $select = 'glossaryid = :id AND (timecreated > :since1 OR timemodified > :since2)';
4162    $params = array('id' => $cm->instance, 'since1' => $from, 'since2' => $from);
4163    if (!has_capability('mod/glossary:approve', $cm->context)) {
4164        $select .= ' AND approved = 1';
4165    }
4166
4167    $entries = $DB->get_records_select('glossary_entries', $select, $params, '', 'id');
4168    if (!empty($entries)) {
4169        $updates->entries->updated = true;
4170        $updates->entries->itemids = array_keys($entries);
4171    }
4172
4173    return $updates;
4174}
4175
4176/**
4177 * Get icon mapping for font-awesome.
4178 *
4179 * @return array
4180 */
4181function mod_glossary_get_fontawesome_icon_map() {
4182    return [
4183        'mod_glossary:export' => 'fa-download',
4184        'mod_glossary:minus' => 'fa-minus'
4185    ];
4186}
4187
4188/**
4189 * This function receives a calendar event and returns the action associated with it, or null if there is none.
4190 *
4191 * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
4192 * is not displayed on the block.
4193 *
4194 * @param calendar_event $event
4195 * @param \core_calendar\action_factory $factory
4196 * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
4197 * @return \core_calendar\local\event\entities\action_interface|null
4198 */
4199function mod_glossary_core_calendar_provide_event_action(calendar_event $event,
4200                                                         \core_calendar\action_factory $factory,
4201                                                         int $userid = 0) {
4202    global $USER;
4203
4204    if (!$userid) {
4205        $userid = $USER->id;
4206    }
4207
4208    $cm = get_fast_modinfo($event->courseid, $userid)->instances['glossary'][$event->instance];
4209
4210    if (!$cm->uservisible) {
4211        // The module is not visible to the user for any reason.
4212        return null;
4213    }
4214
4215    $completion = new \completion_info($cm->get_course());
4216
4217    $completiondata = $completion->get_data($cm, false, $userid);
4218
4219    if ($completiondata->completionstate != COMPLETION_INCOMPLETE) {
4220        return null;
4221    }
4222
4223    return $factory->create_instance(
4224        get_string('view'),
4225        new \moodle_url('/mod/glossary/view.php', ['id' => $cm->id]),
4226        1,
4227        true
4228    );
4229}
4230
4231/**
4232 * Add a get_coursemodule_info function in case any glossary type wants to add 'extra' information
4233 * for the course (see resource).
4234 *
4235 * Given a course_module object, this function returns any "extra" information that may be needed
4236 * when printing this activity in a course listing.  See get_array_of_activities() in course/lib.php.
4237 *
4238 * @param stdClass $coursemodule The coursemodule object (record).
4239 * @return cached_cm_info An object on information that the courses
4240 *                        will know about (most noticeably, an icon).
4241 */
4242function glossary_get_coursemodule_info($coursemodule) {
4243    global $DB;
4244
4245    $dbparams = ['id' => $coursemodule->instance];
4246    $fields = 'id, name, intro, introformat, completionentries';
4247    if (!$glossary = $DB->get_record('glossary', $dbparams, $fields)) {
4248        return false;
4249    }
4250
4251    $result = new cached_cm_info();
4252    $result->name = $glossary->name;
4253
4254    if ($coursemodule->showdescription) {
4255        // Convert intro to html. Do not filter cached version, filters run at display time.
4256        $result->content = format_module_intro('glossary', $glossary, $coursemodule->id, false);
4257    }
4258
4259    // Populate the custom completion rules as key => value pairs, but only if the completion mode is 'automatic'.
4260    if ($coursemodule->completion == COMPLETION_TRACKING_AUTOMATIC) {
4261        $result->customdata['customcompletionrules']['completionentries'] = $glossary->completionentries;
4262    }
4263
4264    return $result;
4265}
4266
4267/**
4268 * Callback which returns human-readable strings describing the active completion custom rules for the module instance.
4269 *
4270 * @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
4271 * @return array $descriptions the array of descriptions for the custom rules.
4272 */
4273function mod_glossary_get_completion_active_rule_descriptions($cm) {
4274    // Values will be present in cm_info, and we assume these are up to date.
4275    if (empty($cm->customdata['customcompletionrules'])
4276        || $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
4277        return [];
4278    }
4279
4280    $descriptions = [];
4281    foreach ($cm->customdata['customcompletionrules'] as $key => $val) {
4282        switch ($key) {
4283            case 'completionentries':
4284                if (!empty($val)) {
4285                    $descriptions[] = get_string('completionentriesdesc', 'glossary', $val);
4286                }
4287                break;
4288            default:
4289                break;
4290        }
4291    }
4292    return $descriptions;
4293}
4294
4295/**
4296 * Checks if the current user can delete the given glossary entry.
4297 *
4298 * @since Moodle 3.10
4299 * @param stdClass $entry the entry database object
4300 * @param stdClass $glossary the glossary database object
4301 * @param stdClass $context the glossary context
4302 * @param bool $return Whether to return a boolean value or stop the execution (exception)
4303 * @return bool if the user can delete the entry
4304 * @throws moodle_exception
4305 */
4306function mod_glossary_can_delete_entry($entry, $glossary, $context, $return = true) {
4307    global $USER, $CFG;
4308
4309    $manageentries = has_capability('mod/glossary:manageentries', $context);
4310
4311    if ($manageentries) {   // Users with the capability will always be able to delete entries.
4312        return true;
4313    }
4314
4315    if ($entry->userid != $USER->id) { // Guest id is never matched, no need for special check here.
4316        if ($return) {
4317            return false;
4318        }
4319        throw new moodle_exception('nopermissiontodelentry');
4320    }
4321
4322    $ineditperiod = ((time() - $entry->timecreated < $CFG->maxeditingtime) || $glossary->editalways);
4323
4324    if (!$ineditperiod) {
4325        if ($return) {
4326            return false;
4327        }
4328        throw new moodle_exception('errdeltimeexpired', 'glossary');
4329    }
4330
4331    return true;
4332}
4333
4334/**
4335 * Deletes the given entry, this function does not perform capabilities/permission checks.
4336 *
4337 * @since Moodle 3.10
4338 * @param stdClass $entry the entry database object
4339 * @param stdClass $glossary the glossary database object
4340 * @param stdClass $cm the glossary course moduule object
4341 * @param stdClass $context the glossary context
4342 * @param stdClass $course the glossary course
4343 * @param string $hook the hook, usually type of filtering, value
4344 * @param string $prevmode the previsualisation mode
4345 * @throws moodle_exception
4346 */
4347function mod_glossary_delete_entry($entry, $glossary, $cm, $context, $course, $hook = '', $prevmode = '') {
4348    global $CFG, $DB;
4349
4350    $origentry = fullclone($entry);
4351
4352    // If it is an imported entry, just delete the relation.
4353    if ($entry->sourceglossaryid) {
4354        if (!$newcm = get_coursemodule_from_instance('glossary', $entry->sourceglossaryid)) {
4355            print_error('invalidcoursemodule');
4356        }
4357        $newcontext = context_module::instance($newcm->id);
4358
4359        $entry->glossaryid       = $entry->sourceglossaryid;
4360        $entry->sourceglossaryid = 0;
4361        $DB->update_record('glossary_entries', $entry);
4362
4363        // Move attachments too.
4364        $fs = get_file_storage();
4365
4366        if ($oldfiles = $fs->get_area_files($context->id, 'mod_glossary', 'attachment', $entry->id)) {
4367            foreach ($oldfiles as $oldfile) {
4368                $filerecord = new stdClass();
4369                $filerecord->contextid = $newcontext->id;
4370                $fs->create_file_from_storedfile($filerecord, $oldfile);
4371            }
4372            $fs->delete_area_files($context->id, 'mod_glossary', 'attachment', $entry->id);
4373            $entry->attachment = '1';
4374        } else {
4375            $entry->attachment = '0';
4376        }
4377        $DB->update_record('glossary_entries', $entry);
4378
4379    } else {
4380        $fs = get_file_storage();
4381        $fs->delete_area_files($context->id, 'mod_glossary', 'attachment', $entry->id);
4382        $DB->delete_records("comments",
4383            ['itemid' => $entry->id, 'commentarea' => 'glossary_entry', 'contextid' => $context->id]);
4384        $DB->delete_records("glossary_alias", ["entryid" => $entry->id]);
4385        $DB->delete_records("glossary_entries", ["id" => $entry->id]);
4386
4387        // Update completion state.
4388        $completion = new completion_info($course);
4389        if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC && $glossary->completionentries) {
4390            $completion->update_state($cm, COMPLETION_INCOMPLETE, $entry->userid);
4391        }
4392
4393        // Delete glossary entry ratings.
4394        require_once($CFG->dirroot.'/rating/lib.php');
4395        $delopt = new stdClass;
4396        $delopt->contextid = $context->id;
4397        $delopt->component = 'mod_glossary';
4398        $delopt->ratingarea = 'entry';
4399        $delopt->itemid = $entry->id;
4400        $rm = new rating_manager();
4401        $rm->delete_ratings($delopt);
4402    }
4403
4404    // Delete cached RSS feeds.
4405    if (!empty($CFG->enablerssfeeds)) {
4406        require_once($CFG->dirroot . '/mod/glossary/rsslib.php');
4407        glossary_rss_delete_file($glossary);
4408    }
4409
4410    core_tag_tag::remove_all_item_tags('mod_glossary', 'glossary_entries', $origentry->id);
4411
4412    $event = \mod_glossary\event\entry_deleted::create(
4413        [
4414            'context' => $context,
4415            'objectid' => $origentry->id,
4416            'other' => [
4417                'mode' => $prevmode,
4418                'hook' => $hook,
4419                'concept' => $origentry->concept
4420            ]
4421        ]
4422    );
4423    $event->add_record_snapshot('glossary_entries', $origentry);
4424    $event->trigger();
4425
4426    // Reset caches.
4427    if ($entry->usedynalink and $entry->approved) {
4428        \mod_glossary\local\concept_cache::reset_glossary($glossary);
4429    }
4430}
4431
4432/**
4433 * Checks if the current user can update the given glossary entry.
4434 *
4435 * @since Moodle 3.10
4436 * @param stdClass $entry the entry database object
4437 * @param stdClass $glossary the glossary database object
4438 * @param stdClass $context the glossary context
4439 * @param object $cm the course module object (cm record or cm_info instance)
4440 * @param bool $return Whether to return a boolean value or stop the execution (exception)
4441 * @return bool if the user can update the entry
4442 * @throws moodle_exception
4443 */
4444function mod_glossary_can_update_entry(stdClass $entry, stdClass $glossary, stdClass $context, object $cm,
4445        bool $return = true): bool {
4446
4447    global $USER, $CFG;
4448
4449    $ineditperiod = ((time() - $entry->timecreated < $CFG->maxeditingtime) || $glossary->editalways);
4450    if (!has_capability('mod/glossary:manageentries', $context) and
4451            !($entry->userid == $USER->id and ($ineditperiod and has_capability('mod/glossary:write', $context)))) {
4452
4453        if ($USER->id != $entry->userid) {
4454            if ($return) {
4455                return false;
4456            }
4457            throw new moodle_exception('errcannoteditothers', 'glossary', "view.php?id=$cm->id&amp;mode=entry&amp;hook=$entry->id");
4458        } else if (!$ineditperiod) {
4459            if ($return) {
4460                return false;
4461            }
4462            throw new moodle_exception('erredittimeexpired', 'glossary', "view.php?id=$cm->id&amp;mode=entry&amp;hook=$entry->id");
4463        }
4464    }
4465
4466    return true;
4467}
4468
4469/**
4470 * Prepares an entry for editing, adding aliases and category information.
4471 *
4472 * @param  stdClass $entry the entry being edited
4473 * @return stdClass the entry with the additional data
4474 */
4475function mod_glossary_prepare_entry_for_edition(stdClass $entry): stdClass {
4476    global $DB;
4477
4478    if ($aliases = $DB->get_records_menu("glossary_alias", ["entryid" => $entry->id], '', 'id, alias')) {
4479        $entry->aliases = implode("\n", $aliases) . "\n";
4480    }
4481    if ($categoriesarr = $DB->get_records_menu("glossary_entries_categories", ['entryid' => $entry->id], '', 'id, categoryid')) {
4482        // TODO: this fetches cats from both main and secondary glossary :-(
4483        $entry->categories = array_values($categoriesarr);
4484    }
4485
4486    return $entry;
4487}
4488