Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

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