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