Proyectos de Subversion Moodle

Rev

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