See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]
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 object $concept 1013 * @param string $courseid 1014 * @return array 1015 */ 1016 function glossary_get_entries_search($concept, $courseid) { 1017 global $DB; 1018 1019 //Check if the user is an admin 1020 $bypassadmin = 1; //This means NO (by default) 1021 if (has_capability('moodle/course:viewhiddenactivities', context_system::instance())) { 1022 $bypassadmin = 0; //This means YES 1023 } 1024 1025 //Check if the user is a teacher 1026 $bypassteacher = 1; //This means NO (by default) 1027 if (has_capability('mod/glossary:manageentries', context_course::instance($courseid))) { 1028 $bypassteacher = 0; //This means YES 1029 } 1030 1031 $conceptlower = core_text::strtolower(trim($concept)); 1032 1033 $params = array('courseid1'=>$courseid, 'courseid2'=>$courseid, 'conceptlower'=>$conceptlower, 'concept'=>$concept); 1034 $sensitiveconceptsql = $DB->sql_equal('concept', ':concept'); 1035 1036 return $DB->get_records_sql("SELECT e.*, g.name as glossaryname, cm.id as cmid, cm.course as courseid 1037 FROM {glossary_entries} e, {glossary} g, 1038 {course_modules} cm, {modules} m 1039 WHERE m.name = 'glossary' AND 1040 cm.module = m.id AND 1041 (cm.visible = 1 OR cm.visible = $bypassadmin OR 1042 (cm.course = :courseid1 AND cm.visible = $bypassteacher)) AND 1043 g.id = cm.instance AND 1044 e.glossaryid = g.id AND 1045 ( (e.casesensitive != 1 AND LOWER(concept) = :conceptlower) OR 1046 (e.casesensitive = 1 and $sensitiveconceptsql)) AND 1047 (g.course = :courseid2 OR g.globalglossary = 1) AND 1048 e.usedynalink != 0 AND 1049 g.usedynalink != 0", $params); 1050 } 1051 1052 /** 1053 * @global object 1054 * @global object 1055 * @param object $course 1056 * @param object $course 1057 * @param object $glossary 1058 * @param object $entry 1059 * @param string $mode 1060 * @param string $hook 1061 * @param int $printicons 1062 * @param int $displayformat 1063 * @param bool $printview 1064 * @return mixed 1065 */ 1066 function glossary_print_entry($course, $cm, $glossary, $entry, $mode='',$hook='',$printicons = 1, $displayformat = -1, $printview = false) { 1067 global $USER, $CFG; 1068 $return = false; 1069 if ( $displayformat < 0 ) { 1070 $displayformat = $glossary->displayformat; 1071 } 1072 if ($entry->approved or ($USER->id == $entry->userid) or ($mode == 'approval' and !$entry->approved) ) { 1073 $formatfile = $CFG->dirroot.'/mod/glossary/formats/'.$displayformat.'/'.$displayformat.'_format.php'; 1074 if ($printview) { 1075 $functionname = 'glossary_print_entry_'.$displayformat; 1076 } else { 1077 $functionname = 'glossary_show_entry_'.$displayformat; 1078 } 1079 1080 if (file_exists($formatfile)) { 1081 include_once($formatfile); 1082 if (function_exists($functionname)) { 1083 $return = $functionname($course, $cm, $glossary, $entry,$mode,$hook,$printicons); 1084 } else if ($printview) { 1085 //If the glossary_print_entry_XXXX function doesn't exist, print default (old) print format 1086 $return = glossary_print_entry_default($entry, $glossary, $cm); 1087 } 1088 } 1089 } 1090 return $return; 1091 } 1092 1093 /** 1094 * Default (old) print format used if custom function doesn't exist in format 1095 * 1096 * @param object $entry 1097 * @param object $glossary 1098 * @param object $cm 1099 * @return void Output is echo'd 1100 */ 1101 function glossary_print_entry_default ($entry, $glossary, $cm) { 1102 global $CFG; 1103 1104 require_once($CFG->libdir . '/filelib.php'); 1105 1106 echo $OUTPUT->heading(strip_tags($entry->concept), 4); 1107 1108 $definition = $entry->definition; 1109 1110 $definition = '<span class="nolink">' . strip_tags($definition) . '</span>'; 1111 1112 $context = context_module::instance($cm->id); 1113 $definition = file_rewrite_pluginfile_urls($definition, 'pluginfile.php', $context->id, 'mod_glossary', 'entry', $entry->id); 1114 1115 $options = new stdClass(); 1116 $options->para = false; 1117 $options->trusted = $entry->definitiontrust; 1118 $options->context = $context; 1119 $options->overflowdiv = true; 1120 $definition = format_text($definition, $entry->definitionformat, $options); 1121 echo ($definition); 1122 echo '<br /><br />'; 1123 } 1124 1125 /** 1126 * Print glossary concept/term as a heading <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&eid='.$entry->id.'&mode='.$mode. 1255 '&hook='.urlencode($hook).'&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.'&prevmode='.$mode.'&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&mode=delete&entry=$entry->id&prevmode=$mode&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&id=$entry->id&mode=$mode&hook=".urlencode($hook); 1291 $return .= "<a class='icon' title=\"" . get_string("edit") . "\" href=\"$url\">" . 1292 $OUTPUT->pix_icon('t/edit', get_string('editentrya', 'mod_glossary', $altsuffix)) . '</a>'; 1293 } elseif ( $importedentry ) { 1294 $return .= "<font size=\"-1\">" . get_string("exportedentry","glossary") . "</font>"; 1295 } 1296 } 1297 if (!empty($CFG->enableportfolios) && (has_capability('mod/glossary:exportentry', $context) || ($iscurrentuser && has_capability('mod/glossary:exportownentry', $context)))) { 1298 require_once($CFG->libdir . '/portfoliolib.php'); 1299 $button = new portfolio_add_button(); 1300 $button->set_callback_options('glossary_entry_portfolio_caller', array('id' => $cm->id, 'entryid' => $entry->id), 'mod_glossary'); 1301 1302 $filecontext = $context; 1303 if ($entry->sourceglossaryid == $cm->instance) { 1304 if ($maincm = get_coursemodule_from_instance('glossary', $entry->glossaryid)) { 1305 $filecontext = context_module::instance($maincm->id); 1306 } 1307 } 1308 $fs = get_file_storage(); 1309 if ($files = $fs->get_area_files($filecontext->id, 'mod_glossary', 'attachment', $entry->id, "timemodified", false) 1310 || $files = $fs->get_area_files($filecontext->id, 'mod_glossary', 'entry', $entry->id, "timemodified", false)) { 1311 1312 $button->set_formats(PORTFOLIO_FORMAT_RICHHTML); 1313 } else { 1314 $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML); 1315 } 1316 1317 $return .= $button->to_html(PORTFOLIO_ADD_ICON_LINK); 1318 } 1319 $return .= '</span>'; 1320 1321 if (!empty($CFG->usecomments) && has_capability('mod/glossary:comment', $context) and $glossary->allowcomments) { 1322 require_once($CFG->dirroot . '/comment/lib.php'); 1323 $cmt = new stdClass(); 1324 $cmt->component = 'mod_glossary'; 1325 $cmt->context = $context; 1326 $cmt->course = $course; 1327 $cmt->cm = $cm; 1328 $cmt->area = 'glossary_entry'; 1329 $cmt->itemid = $entry->id; 1330 $cmt->showcount = true; 1331 $comment = new comment($cmt); 1332 $return .= '<div>'.$comment->output(true).'</div>'; 1333 $output = true; 1334 } 1335 1336 //If we haven't calculated any REAL thing, delete result ($return) 1337 if (!$output) { 1338 $return = ''; 1339 } 1340 //Print or get 1341 if ($type == 'print') { 1342 echo $return; 1343 } else { 1344 return $return; 1345 } 1346 } 1347 1348 /** 1349 * @param object $course 1350 * @param object $cm 1351 * @param object $glossary 1352 * @param object $entry 1353 * @param string $mode 1354 * @param object $hook 1355 * @param bool $printicons 1356 * @param bool $aliases 1357 * @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&mode=$mode&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&mode=$mode&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&mode=$mode&hook=".urlencode($alphabet[$i])."&sortkey=$sortkey&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 = '&sortorder=desc'; 2066 $newordertitle = get_string('changeto', 'glossary', $desc); 2067 } else { 2068 $currentorder = $desc; 2069 $neworder = '&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 = '&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&sortkey=$sorder$sneworder&mode=$mode\">$ssort$sicon</a>$sendbtag | ". 2143 "$fbtag<a title=\"$fsort $fordertitle\" href=\"$CFG->wwwroot/mod/glossary/view.php?id=$cm->id&sortkey=$forder$fneworder&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=" ", $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 .= " (<a href=\"{$baseurl}page=$pagenum\">".get_string("previous")."</a>) "; 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 object $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('pendingapproval', 'glossary'), 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&mode=entry&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&mode=entry&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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body