Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]
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_fieldset_select('glossary_alias', 'alias', 'entryid = :entryid', ['entryid' => $entry->id])) { 1184 $id = "keyword-{$entry->id}"; 1185 $return = html_writer::select($aliases, $id, '', false, ['id' => $id]); 1186 } 1187 if ($type == 'print') { 1188 echo $return; 1189 } else { 1190 return $return; 1191 } 1192 } 1193 1194 /** 1195 * 1196 * @global object 1197 * @global object 1198 * @global object 1199 * @param object $course 1200 * @param object $cm 1201 * @param object $glossary 1202 * @param object $entry 1203 * @param string $mode 1204 * @param string $hook 1205 * @param string $type 1206 * @return string|void 1207 */ 1208 function glossary_print_entry_icons($course, $cm, $glossary, $entry, $mode='',$hook='', $type = 'print') { 1209 global $USER, $CFG, $DB, $OUTPUT; 1210 1211 $context = context_module::instance($cm->id); 1212 1213 $output = false; // To decide if we must really return text in "return". Activate when needed only! 1214 $importedentry = ($entry->sourceglossaryid == $glossary->id); 1215 $ismainglossary = $glossary->mainglossary; 1216 1217 $return = '<span class="commands">'; 1218 // Differentiate links for each entry. 1219 $altsuffix = strip_tags(format_text($entry->concept)); 1220 1221 if (!$entry->approved) { 1222 $output = true; 1223 $return .= html_writer::tag('span', get_string('entryishidden','glossary'), 1224 array('class' => 'glossary-hidden-note')); 1225 } 1226 1227 if ($entry->approved || has_capability('mod/glossary:approve', $context)) { 1228 $output = true; 1229 $return .= \html_writer::link( 1230 new \moodle_url('/mod/glossary/showentry.php', ['eid' => $entry->id]), 1231 $OUTPUT->pix_icon('fp/link', get_string('entrylink', 'glossary', $altsuffix), 'theme'), 1232 ['title' => get_string('entrylink', 'glossary', $altsuffix), 'class' => 'icon'] 1233 ); 1234 } 1235 1236 if (has_capability('mod/glossary:approve', $context) && !$glossary->defaultapproval && $entry->approved) { 1237 $output = true; 1238 $return .= '<a class="icon" title="' . get_string('disapprove', 'glossary'). 1239 '" href="approve.php?newstate=0&eid='.$entry->id.'&mode='.$mode. 1240 '&hook='.urlencode($hook).'&sesskey='.sesskey(). 1241 '">' . $OUTPUT->pix_icon('t/block', get_string('disapprove', 'glossary')) . '</a>'; 1242 } 1243 1244 $iscurrentuser = ($entry->userid == $USER->id); 1245 1246 if (has_capability('mod/glossary:manageentries', $context) or (isloggedin() and has_capability('mod/glossary:write', $context) and $iscurrentuser)) { 1247 // only teachers can export entries so check it out 1248 if (has_capability('mod/glossary:export', $context) and !$ismainglossary and !$importedentry) { 1249 $mainglossary = $DB->get_record('glossary', array('mainglossary'=>1,'course'=>$course->id)); 1250 if ( $mainglossary ) { // if there is a main glossary defined, allow to export the current entry 1251 $output = true; 1252 $return .= '<a class="icon" title="'.get_string('exporttomainglossary','glossary') . '" ' . 1253 'href="exportentry.php?id='.$entry->id.'&prevmode='.$mode.'&hook='.urlencode($hook).'">' . 1254 $OUTPUT->pix_icon('export', get_string('exporttomainglossary', 'glossary'), 'glossary') . '</a>'; 1255 } 1256 } 1257 1258 $icon = 't/delete'; 1259 $iconcomponent = 'moodle'; 1260 if ( $entry->sourceglossaryid ) { 1261 $icon = 'minus'; // graphical metaphor (minus) for deleting an imported entry 1262 $iconcomponent = 'glossary'; 1263 } 1264 1265 //Decide if an entry is editable: 1266 // -It isn't a imported entry (so nobody can edit a imported (from secondary to main) entry)) and 1267 // -The user is teacher or he is a student with time permissions (edit period or editalways defined). 1268 $ineditperiod = ((time() - $entry->timecreated < $CFG->maxeditingtime) || $glossary->editalways); 1269 if ( !$importedentry and (has_capability('mod/glossary:manageentries', $context) or ($entry->userid == $USER->id and ($ineditperiod and has_capability('mod/glossary:write', $context))))) { 1270 $output = true; 1271 $url = "deleteentry.php?id=$cm->id&mode=delete&entry=$entry->id&prevmode=$mode&hook=".urlencode($hook); 1272 $return .= "<a class='icon' title=\"" . get_string("delete") . "\" " . 1273 "href=\"$url\">" . $OUTPUT->pix_icon($icon, get_string('deleteentrya', 'mod_glossary', $altsuffix), $iconcomponent) . '</a>'; 1274 1275 $url = "edit.php?cmid=$cm->id&id=$entry->id&mode=$mode&hook=".urlencode($hook); 1276 $return .= "<a class='icon' title=\"" . get_string("edit") . "\" href=\"$url\">" . 1277 $OUTPUT->pix_icon('t/edit', get_string('editentrya', 'mod_glossary', $altsuffix)) . '</a>'; 1278 } elseif ( $importedentry ) { 1279 $return .= "<font size=\"-1\">" . get_string("exportedentry","glossary") . "</font>"; 1280 } 1281 } 1282 if (!empty($CFG->enableportfolios) && (has_capability('mod/glossary:exportentry', $context) || ($iscurrentuser && has_capability('mod/glossary:exportownentry', $context)))) { 1283 require_once($CFG->libdir . '/portfoliolib.php'); 1284 $button = new portfolio_add_button(); 1285 $button->set_callback_options('glossary_entry_portfolio_caller', array('id' => $cm->id, 'entryid' => $entry->id), 'mod_glossary'); 1286 1287 $filecontext = $context; 1288 if ($entry->sourceglossaryid == $cm->instance) { 1289 if ($maincm = get_coursemodule_from_instance('glossary', $entry->glossaryid)) { 1290 $filecontext = context_module::instance($maincm->id); 1291 } 1292 } 1293 $fs = get_file_storage(); 1294 if ($files = $fs->get_area_files($filecontext->id, 'mod_glossary', 'attachment', $entry->id, "timemodified", false) 1295 || $files = $fs->get_area_files($filecontext->id, 'mod_glossary', 'entry', $entry->id, "timemodified", false)) { 1296 1297 $button->set_formats(PORTFOLIO_FORMAT_RICHHTML); 1298 } else { 1299 $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML); 1300 } 1301 1302 $return .= $button->to_html(PORTFOLIO_ADD_ICON_LINK); 1303 } 1304 $return .= '</span>'; 1305 1306 if (!empty($CFG->usecomments) && has_capability('mod/glossary:comment', $context) and $glossary->allowcomments) { 1307 require_once($CFG->dirroot . '/comment/lib.php'); 1308 $cmt = new stdClass(); 1309 $cmt->component = 'mod_glossary'; 1310 $cmt->context = $context; 1311 $cmt->course = $course; 1312 $cmt->cm = $cm; 1313 $cmt->area = 'glossary_entry'; 1314 $cmt->itemid = $entry->id; 1315 $cmt->showcount = true; 1316 $comment = new comment($cmt); 1317 $return .= '<div>'.$comment->output(true).'</div>'; 1318 $output = true; 1319 } 1320 1321 //If we haven't calculated any REAL thing, delete result ($return) 1322 if (!$output) { 1323 $return = ''; 1324 } 1325 //Print or get 1326 if ($type == 'print') { 1327 echo $return; 1328 } else { 1329 return $return; 1330 } 1331 } 1332 1333 /** 1334 * @param object $course 1335 * @param object $cm 1336 * @param object $glossary 1337 * @param object $entry 1338 * @param string $mode 1339 * @param object $hook 1340 * @param bool $printicons 1341 * @param bool $aliases 1342 * @return void 1343 */ 1344 function glossary_print_entry_lower_section($course, $cm, $glossary, $entry, $mode, $hook, $printicons, $aliases=true) { 1345 if ($aliases) { 1346 $aliases = glossary_print_entry_aliases($course, $cm, $glossary, $entry, $mode, $hook,'html'); 1347 } 1348 $icons = ''; 1349 if ($printicons) { 1350 $icons = glossary_print_entry_icons($course, $cm, $glossary, $entry, $mode, $hook,'html'); 1351 } 1352 if ($aliases || $icons || !empty($entry->rating)) { 1353 echo '<table>'; 1354 if ( $aliases ) { 1355 $id = "keyword-{$entry->id}"; 1356 echo '<tr valign="top"><td class="aliases">' . 1357 '<label for="' . $id . '">' . get_string('aliases', 'glossary') . ': </label>' . 1358 $aliases . '</td></tr>'; 1359 } 1360 if ($icons) { 1361 echo '<tr valign="top"><td class="icons">'.$icons.'</td></tr>'; 1362 } 1363 if (!empty($entry->rating)) { 1364 echo '<tr valign="top"><td class="ratings pt-3">'; 1365 glossary_print_entry_ratings($course, $entry); 1366 echo '</td></tr>'; 1367 } 1368 echo '</table>'; 1369 echo "<hr>\n"; 1370 } 1371 } 1372 1373 /** 1374 * Print the list of attachments for this glossary entry 1375 * 1376 * @param object $entry 1377 * @param object $cm The coursemodule 1378 * @param string $format The format for this view (html, or text) 1379 * @param string $unused1 This parameter is no longer used 1380 * @param string $unused2 This parameter is no longer used 1381 */ 1382 function glossary_print_entry_attachment($entry, $cm, $format = null, $unused1 = null, $unused2 = null) { 1383 // Valid format values: html: The HTML link for the attachment is an icon; and 1384 // text: The HTML link for the attachment is text. 1385 if ($entry->attachment) { 1386 echo '<div class="attachments">'; 1387 echo glossary_print_attachments($entry, $cm, $format); 1388 echo '</div>'; 1389 } 1390 if ($unused1) { 1391 debugging('The align parameter is deprecated, please use appropriate CSS instead', DEBUG_DEVELOPER); 1392 } 1393 if ($unused2 !== null) { 1394 debugging('The insidetable parameter is deprecated, please use appropriate CSS instead', DEBUG_DEVELOPER); 1395 } 1396 } 1397 1398 /** 1399 * @global object 1400 * @param object $cm 1401 * @param object $entry 1402 * @param string $mode 1403 * @param string $align 1404 * @param bool $insidetable 1405 */ 1406 function glossary_print_entry_approval($cm, $entry, $mode, $align="right", $insidetable=true) { 1407 global $CFG, $OUTPUT; 1408 1409 if ($mode == 'approval' and !$entry->approved) { 1410 if ($insidetable) { 1411 echo '<table class="glossaryapproval" align="'.$align.'"><tr><td align="'.$align.'">'; 1412 } 1413 echo $OUTPUT->action_icon( 1414 new moodle_url('approve.php', array('eid' => $entry->id, 'mode' => $mode, 'sesskey' => sesskey())), 1415 new pix_icon('t/approve', get_string('approve','glossary'), '', 1416 array('class' => 'iconsmall', 'align' => $align)) 1417 ); 1418 if ($insidetable) { 1419 echo '</td></tr></table>'; 1420 } 1421 } 1422 } 1423 1424 /** 1425 * It returns all entries from all glossaries that matches the specified criteria 1426 * within a given $course. It performs an $extended search if necessary. 1427 * It restrict the search to only one $glossary if the $glossary parameter is set. 1428 * 1429 * @global object 1430 * @global object 1431 * @param object $course 1432 * @param array $searchterms 1433 * @param int $extended 1434 * @param object $glossary 1435 * @return array 1436 */ 1437 function glossary_search($course, $searchterms, $extended = 0, $glossary = NULL) { 1438 global $CFG, $DB; 1439 1440 if ( !$glossary ) { 1441 if ( $glossaries = $DB->get_records("glossary", array("course"=>$course->id)) ) { 1442 $glos = ""; 1443 foreach ( $glossaries as $glossary ) { 1444 $glos .= "$glossary->id,"; 1445 } 1446 $glos = substr($glos,0,-1); 1447 } 1448 } else { 1449 $glos = $glossary->id; 1450 } 1451 1452 if (!has_capability('mod/glossary:manageentries', context_course::instance($glossary->course))) { 1453 $glossarymodule = $DB->get_record("modules", array("name"=>"glossary")); 1454 $onlyvisible = " AND g.id = cm.instance AND cm.visible = 1 AND cm.module = $glossarymodule->id"; 1455 $onlyvisibletable = ", {course_modules} cm"; 1456 } else { 1457 1458 $onlyvisible = ""; 1459 $onlyvisibletable = ""; 1460 } 1461 1462 if ($DB->sql_regex_supported()) { 1463 $REGEXP = $DB->sql_regex(true); 1464 $NOTREGEXP = $DB->sql_regex(false); 1465 } 1466 1467 $searchcond = array(); 1468 $params = array(); 1469 $i = 0; 1470 1471 $concat = $DB->sql_concat('e.concept', "' '", 'e.definition'); 1472 1473 1474 foreach ($searchterms as $searchterm) { 1475 $i++; 1476 1477 $NOT = false; /// Initially we aren't going to perform NOT LIKE searches, only MSSQL and Oracle 1478 /// will use it to simulate the "-" operator with LIKE clause 1479 1480 /// Under Oracle and MSSQL, trim the + and - operators and perform 1481 /// simpler LIKE (or NOT LIKE) queries 1482 if (!$DB->sql_regex_supported()) { 1483 if (substr($searchterm, 0, 1) == '-') { 1484 $NOT = true; 1485 } 1486 $searchterm = trim($searchterm, '+-'); 1487 } 1488 1489 // TODO: +- may not work for non latin languages 1490 1491 if (substr($searchterm,0,1) == '+') { 1492 $searchterm = trim($searchterm, '+-'); 1493 $searchterm = preg_quote($searchterm, '|'); 1494 $searchcond[] = "$concat $REGEXP :ss$i"; 1495 $params['ss'.$i] = "(^|[^a-zA-Z0-9])$searchterm([^a-zA-Z0-9]|$)"; 1496 1497 } else if (substr($searchterm,0,1) == "-") { 1498 $searchterm = trim($searchterm, '+-'); 1499 $searchterm = preg_quote($searchterm, '|'); 1500 $searchcond[] = "$concat $NOTREGEXP :ss$i"; 1501 $params['ss'.$i] = "(^|[^a-zA-Z0-9])$searchterm([^a-zA-Z0-9]|$)"; 1502 1503 } else { 1504 $searchcond[] = $DB->sql_like($concat, ":ss$i", false, true, $NOT); 1505 $params['ss'.$i] = "%$searchterm%"; 1506 } 1507 } 1508 1509 if (empty($searchcond)) { 1510 $totalcount = 0; 1511 return array(); 1512 } 1513 1514 $searchcond = implode(" AND ", $searchcond); 1515 1516 $sql = "SELECT e.* 1517 FROM {glossary_entries} e, {glossary} g $onlyvisibletable 1518 WHERE $searchcond 1519 AND (e.glossaryid = g.id or e.sourceglossaryid = g.id) $onlyvisible 1520 AND g.id IN ($glos) AND e.approved <> 0"; 1521 1522 return $DB->get_records_sql($sql, $params); 1523 } 1524 1525 /** 1526 * @global object 1527 * @param array $searchterms 1528 * @param object $glossary 1529 * @param bool $extended 1530 * @return array 1531 */ 1532 function glossary_search_entries($searchterms, $glossary, $extended) { 1533 global $DB; 1534 1535 $course = $DB->get_record("course", array("id"=>$glossary->course)); 1536 return glossary_search($course,$searchterms,$extended,$glossary); 1537 } 1538 1539 /** 1540 * if return=html, then return a html string. 1541 * if return=text, then return a text-only string. 1542 * otherwise, print HTML for non-images, and return image HTML 1543 * if attachment is an image, $align set its aligment. 1544 * 1545 * @global object 1546 * @global object 1547 * @param object $entry 1548 * @param object $cm 1549 * @param string $type html, txt, empty 1550 * @param string $unused This parameter is no longer used 1551 * @return string image string or nothing depending on $type param 1552 */ 1553 function glossary_print_attachments($entry, $cm, $type=NULL, $unused = null) { 1554 global $CFG, $DB, $OUTPUT; 1555 1556 if (!$context = context_module::instance($cm->id, IGNORE_MISSING)) { 1557 return ''; 1558 } 1559 1560 if ($entry->sourceglossaryid == $cm->instance) { 1561 if (!$maincm = get_coursemodule_from_instance('glossary', $entry->glossaryid)) { 1562 return ''; 1563 } 1564 $filecontext = context_module::instance($maincm->id); 1565 1566 } else { 1567 $filecontext = $context; 1568 } 1569 1570 $strattachment = get_string('attachment', 'glossary'); 1571 1572 $fs = get_file_storage(); 1573 1574 $imagereturn = ''; 1575 $output = ''; 1576 1577 if ($files = $fs->get_area_files($filecontext->id, 'mod_glossary', 'attachment', $entry->id, "timemodified", false)) { 1578 foreach ($files as $file) { 1579 $filename = $file->get_filename(); 1580 $mimetype = $file->get_mimetype(); 1581 $iconimage = $OUTPUT->pix_icon(file_file_icon($file), get_mimetype_description($file), 'moodle', array('class' => 'icon')); 1582 $path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$context->id.'/mod_glossary/attachment/'.$entry->id.'/'.$filename); 1583 1584 if ($type == 'html') { 1585 $output .= "<a href=\"$path\">$iconimage</a> "; 1586 $output .= "<a href=\"$path\">".s($filename)."</a>"; 1587 $output .= "<br />"; 1588 1589 } else if ($type == 'text') { 1590 $output .= "$strattachment ".s($filename).":\n$path\n"; 1591 1592 } else { 1593 if (in_array($mimetype, array('image/gif', 'image/jpeg', 'image/png'))) { 1594 // Image attachments don't get printed as links 1595 $imagereturn .= "<br /><img src=\"$path\" alt=\"\" />"; 1596 } else { 1597 $output .= "<a href=\"$path\">$iconimage</a> "; 1598 $output .= format_text("<a href=\"$path\">".s($filename)."</a>", FORMAT_HTML, array('context'=>$context)); 1599 $output .= '<br />'; 1600 } 1601 } 1602 } 1603 } 1604 1605 if ($type) { 1606 return $output; 1607 } else { 1608 echo $output; 1609 return $imagereturn; 1610 } 1611 } 1612 1613 //////////////////////////////////////////////////////////////////////////////// 1614 // File API // 1615 //////////////////////////////////////////////////////////////////////////////// 1616 1617 /** 1618 * Lists all browsable file areas 1619 * 1620 * @package mod_glossary 1621 * @category files 1622 * @param stdClass $course course object 1623 * @param stdClass $cm course module object 1624 * @param stdClass $context context object 1625 * @return array 1626 */ 1627 function glossary_get_file_areas($course, $cm, $context) { 1628 return array( 1629 'attachment' => get_string('areaattachment', 'mod_glossary'), 1630 'entry' => get_string('areaentry', 'mod_glossary'), 1631 ); 1632 } 1633 1634 /** 1635 * File browsing support for glossary module. 1636 * 1637 * @param file_browser $browser 1638 * @param array $areas 1639 * @param stdClass $course 1640 * @param cm_info $cm 1641 * @param context $context 1642 * @param string $filearea 1643 * @param int $itemid 1644 * @param string $filepath 1645 * @param string $filename 1646 * @return file_info_stored file_info_stored instance or null if not found 1647 */ 1648 function glossary_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) { 1649 global $CFG, $DB, $USER; 1650 1651 if ($context->contextlevel != CONTEXT_MODULE) { 1652 return null; 1653 } 1654 1655 if (!isset($areas[$filearea])) { 1656 return null; 1657 } 1658 1659 if (is_null($itemid)) { 1660 require_once($CFG->dirroot.'/mod/glossary/locallib.php'); 1661 return new glossary_file_info_container($browser, $course, $cm, $context, $areas, $filearea); 1662 } 1663 1664 if (!$entry = $DB->get_record('glossary_entries', array('id' => $itemid))) { 1665 return null; 1666 } 1667 1668 if (!$glossary = $DB->get_record('glossary', array('id' => $cm->instance))) { 1669 return null; 1670 } 1671 1672 if ($glossary->defaultapproval and !$entry->approved and !has_capability('mod/glossary:approve', $context)) { 1673 return null; 1674 } 1675 1676 // this trickery here is because we need to support source glossary access 1677 if ($entry->glossaryid == $cm->instance) { 1678 $filecontext = $context; 1679 } else if ($entry->sourceglossaryid == $cm->instance) { 1680 if (!$maincm = get_coursemodule_from_instance('glossary', $entry->glossaryid)) { 1681 return null; 1682 } 1683 $filecontext = context_module::instance($maincm->id); 1684 } else { 1685 return null; 1686 } 1687 1688 $fs = get_file_storage(); 1689 $filepath = is_null($filepath) ? '/' : $filepath; 1690 $filename = is_null($filename) ? '.' : $filename; 1691 if (!($storedfile = $fs->get_file($filecontext->id, 'mod_glossary', $filearea, $itemid, $filepath, $filename))) { 1692 return null; 1693 } 1694 1695 // Checks to see if the user can manage files or is the owner. 1696 // TODO MDL-33805 - Do not use userid here and move the capability check above. 1697 if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) { 1698 return null; 1699 } 1700 1701 $urlbase = $CFG->wwwroot.'/pluginfile.php'; 1702 1703 return new file_info_stored($browser, $filecontext, $storedfile, $urlbase, s($entry->concept), true, true, false, false); 1704 } 1705 1706 /** 1707 * Serves the glossary attachments. Implements needed access control ;-) 1708 * 1709 * @package mod_glossary 1710 * @category files 1711 * @param stdClass $course course object 1712 * @param stdClass $cm course module object 1713 * @param stdClsss $context context object 1714 * @param string $filearea file area 1715 * @param array $args extra arguments 1716 * @param bool $forcedownload whether or not force download 1717 * @param array $options additional options affecting the file serving 1718 * @return bool false if file not found, does not return if found - justsend the file 1719 */ 1720 function glossary_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { 1721 global $CFG, $DB; 1722 1723 if ($context->contextlevel != CONTEXT_MODULE) { 1724 return false; 1725 } 1726 1727 require_course_login($course, true, $cm); 1728 1729 if ($filearea === 'attachment' or $filearea === 'entry') { 1730 $entryid = (int)array_shift($args); 1731 1732 require_course_login($course, true, $cm); 1733 1734 if (!$entry = $DB->get_record('glossary_entries', array('id'=>$entryid))) { 1735 return false; 1736 } 1737 1738 if (!$glossary = $DB->get_record('glossary', array('id'=>$cm->instance))) { 1739 return false; 1740 } 1741 1742 if ($glossary->defaultapproval and !$entry->approved and !has_capability('mod/glossary:approve', $context)) { 1743 return false; 1744 } 1745 1746 // this trickery here is because we need to support source glossary access 1747 1748 if ($entry->glossaryid == $cm->instance) { 1749 $filecontext = $context; 1750 1751 } else if ($entry->sourceglossaryid == $cm->instance) { 1752 if (!$maincm = get_coursemodule_from_instance('glossary', $entry->glossaryid)) { 1753 return false; 1754 } 1755 $filecontext = context_module::instance($maincm->id); 1756 1757 } else { 1758 return false; 1759 } 1760 1761 $relativepath = implode('/', $args); 1762 $fullpath = "/$filecontext->id/mod_glossary/$filearea/$entryid/$relativepath"; 1763 1764 $fs = get_file_storage(); 1765 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { 1766 return false; 1767 } 1768 1769 // finally send the file 1770 send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security! 1771 1772 } else if ($filearea === 'export') { 1773 require_login($course, false, $cm); 1774 require_capability('mod/glossary:export', $context); 1775 1776 if (!$glossary = $DB->get_record('glossary', array('id'=>$cm->instance))) { 1777 return false; 1778 } 1779 1780 $cat = array_shift($args); 1781 $cat = clean_param($cat, PARAM_ALPHANUM); 1782 1783 $filename = clean_filename(strip_tags(format_string($glossary->name)).'.xml'); 1784 $content = glossary_generate_export_file($glossary, NULL, $cat); 1785 1786 send_file($content, $filename, 0, 0, true, true); 1787 } 1788 1789 return false; 1790 } 1791 1792 /** 1793 * 1794 */ 1795 function glossary_print_tabbed_table_end() { 1796 echo "</div></div>"; 1797 } 1798 1799 /** 1800 * @param object $cm 1801 * @param object $glossary 1802 * @param string $mode 1803 * @param string $hook 1804 * @param string $sortkey 1805 * @param string $sortorder 1806 */ 1807 function glossary_print_approval_menu($cm, $glossary,$mode, $hook, $sortkey = '', $sortorder = '') { 1808 if ($glossary->showalphabet) { 1809 echo '<div class="glossaryexplain">' . get_string("explainalphabet","glossary") . '</div><br />'; 1810 } 1811 glossary_print_special_links($cm, $glossary, $mode, $hook); 1812 1813 glossary_print_alphabet_links($cm, $glossary, $mode, $hook,$sortkey, $sortorder); 1814 1815 glossary_print_all_links($cm, $glossary, $mode, $hook); 1816 1817 glossary_print_sorting_links($cm, $mode, 'CREATION', 'asc'); 1818 } 1819 /** 1820 * @param object $cm 1821 * @param object $glossary 1822 * @param string $hook 1823 * @param string $sortkey 1824 * @param string $sortorder 1825 */ 1826 function glossary_print_import_menu($cm, $glossary, $mode, $hook, $sortkey='', $sortorder = '') { 1827 echo '<div class="glossaryexplain">' . get_string("explainimport","glossary") . '</div>'; 1828 } 1829 1830 /** 1831 * @param object $cm 1832 * @param object $glossary 1833 * @param string $hook 1834 * @param string $sortkey 1835 * @param string $sortorder 1836 */ 1837 function glossary_print_export_menu($cm, $glossary, $mode, $hook, $sortkey='', $sortorder = '') { 1838 echo '<div class="glossaryexplain">' . get_string("explainexport","glossary") . '</div>'; 1839 } 1840 /** 1841 * @param object $cm 1842 * @param object $glossary 1843 * @param string $hook 1844 * @param string $sortkey 1845 * @param string $sortorder 1846 */ 1847 function glossary_print_alphabet_menu($cm, $glossary, $mode, $hook, $sortkey='', $sortorder = '') { 1848 if ( $mode != 'date' ) { 1849 if ($glossary->showalphabet) { 1850 echo '<div class="glossaryexplain">' . get_string("explainalphabet","glossary") . '</div><br />'; 1851 } 1852 1853 glossary_print_special_links($cm, $glossary, $mode, $hook); 1854 1855 glossary_print_alphabet_links($cm, $glossary, $mode, $hook, $sortkey, $sortorder); 1856 1857 glossary_print_all_links($cm, $glossary, $mode, $hook); 1858 } else { 1859 glossary_print_sorting_links($cm, $mode, $sortkey,$sortorder); 1860 } 1861 } 1862 1863 /** 1864 * @param object $cm 1865 * @param object $glossary 1866 * @param string $hook 1867 * @param string $sortkey 1868 * @param string $sortorder 1869 */ 1870 function glossary_print_author_menu($cm, $glossary,$mode, $hook, $sortkey = '', $sortorder = '') { 1871 if ($glossary->showalphabet) { 1872 echo '<div class="glossaryexplain">' . get_string("explainalphabet","glossary") . '</div><br />'; 1873 } 1874 1875 glossary_print_alphabet_links($cm, $glossary, $mode, $hook, $sortkey, $sortorder); 1876 glossary_print_all_links($cm, $glossary, $mode, $hook); 1877 glossary_print_sorting_links($cm, $mode, $sortkey,$sortorder); 1878 } 1879 1880 /** 1881 * @global object 1882 * @global object 1883 * @param object $cm 1884 * @param object $glossary 1885 * @param string $hook 1886 * @param object $category 1887 */ 1888 function glossary_print_categories_menu($cm, $glossary, $hook, $category) { 1889 global $CFG, $DB, $OUTPUT; 1890 1891 $context = context_module::instance($cm->id); 1892 1893 // Prepare format_string/text options 1894 $fmtoptions = array( 1895 'context' => $context); 1896 1897 echo '<table border="0" width="100%">'; 1898 echo '<tr>'; 1899 1900 echo '<td align="center" style="width:20%">'; 1901 if (has_capability('mod/glossary:managecategories', $context)) { 1902 $options['id'] = $cm->id; 1903 $options['mode'] = 'cat'; 1904 $options['hook'] = $hook; 1905 echo $OUTPUT->single_button(new moodle_url("editcategories.php", $options), get_string("editcategories","glossary"), "get"); 1906 } 1907 echo '</td>'; 1908 1909 echo '<td align="center" style="width:60%">'; 1910 echo '<b>'; 1911 1912 $menu = array(); 1913 $menu[GLOSSARY_SHOW_ALL_CATEGORIES] = get_string("allcategories","glossary"); 1914 $menu[GLOSSARY_SHOW_NOT_CATEGORISED] = get_string("notcategorised","glossary"); 1915 1916 $categories = $DB->get_records("glossary_categories", array("glossaryid"=>$glossary->id), "name ASC"); 1917 $selected = ''; 1918 if ( $categories ) { 1919 foreach ($categories as $currentcategory) { 1920 $url = $currentcategory->id; 1921 if ( $category ) { 1922 if ($currentcategory->id == $category->id) { 1923 $selected = $url; 1924 } 1925 } 1926 $menu[$url] = format_string($currentcategory->name, true, $fmtoptions); 1927 } 1928 } 1929 if ( !$selected ) { 1930 $selected = GLOSSARY_SHOW_NOT_CATEGORISED; 1931 } 1932 1933 if ( $category ) { 1934 echo format_string($category->name, true, $fmtoptions); 1935 } else { 1936 if ( $hook == GLOSSARY_SHOW_NOT_CATEGORISED ) { 1937 1938 echo get_string("entrieswithoutcategory","glossary"); 1939 $selected = GLOSSARY_SHOW_NOT_CATEGORISED; 1940 1941 } elseif ( $hook == GLOSSARY_SHOW_ALL_CATEGORIES ) { 1942 1943 echo get_string("allcategories","glossary"); 1944 $selected = GLOSSARY_SHOW_ALL_CATEGORIES; 1945 1946 } 1947 } 1948 echo '</b></td>'; 1949 echo '<td align="center" style="width:20%">'; 1950 1951 $select = new single_select(new moodle_url("/mod/glossary/view.php", array('id'=>$cm->id, 'mode'=>'cat')), 'hook', $menu, $selected, null, "catmenu"); 1952 $select->set_label(get_string('categories', 'glossary'), array('class' => 'accesshide')); 1953 echo $OUTPUT->render($select); 1954 1955 echo '</td>'; 1956 echo '</tr>'; 1957 1958 echo '</table>'; 1959 } 1960 1961 /** 1962 * @global object 1963 * @param object $cm 1964 * @param object $glossary 1965 * @param string $mode 1966 * @param string $hook 1967 */ 1968 function glossary_print_all_links($cm, $glossary, $mode, $hook) { 1969 global $CFG; 1970 if ( $glossary->showall) { 1971 $strallentries = get_string("allentries", "glossary"); 1972 if ( $hook == 'ALL' ) { 1973 echo "<b>$strallentries</b>"; 1974 } else { 1975 $strexplainall = strip_tags(get_string("explainall","glossary")); 1976 echo "<a title=\"$strexplainall\" href=\"$CFG->wwwroot/mod/glossary/view.php?id=$cm->id&mode=$mode&hook=ALL\">$strallentries</a>"; 1977 } 1978 } 1979 } 1980 1981 /** 1982 * @global object 1983 * @param object $cm 1984 * @param object $glossary 1985 * @param string $mode 1986 * @param string $hook 1987 */ 1988 function glossary_print_special_links($cm, $glossary, $mode, $hook) { 1989 global $CFG; 1990 if ( $glossary->showspecial) { 1991 $strspecial = get_string("special", "glossary"); 1992 if ( $hook == 'SPECIAL' ) { 1993 echo "<b>$strspecial</b> | "; 1994 } else { 1995 $strexplainspecial = strip_tags(get_string("explainspecial","glossary")); 1996 echo "<a title=\"$strexplainspecial\" href=\"$CFG->wwwroot/mod/glossary/view.php?id=$cm->id&mode=$mode&hook=SPECIAL\">$strspecial</a> | "; 1997 } 1998 } 1999 } 2000 2001 /** 2002 * @global object 2003 * @param object $glossary 2004 * @param string $mode 2005 * @param string $hook 2006 * @param string $sortkey 2007 * @param string $sortorder 2008 */ 2009 function glossary_print_alphabet_links($cm, $glossary, $mode, $hook, $sortkey, $sortorder) { 2010 global $CFG; 2011 if ( $glossary->showalphabet) { 2012 $alphabet = explode(",", get_string('alphabet', 'langconfig')); 2013 for ($i = 0; $i < count($alphabet); $i++) { 2014 if ( $hook == $alphabet[$i] and $hook) { 2015 echo "<b>$alphabet[$i]</b>"; 2016 } else { 2017 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>"; 2018 } 2019 echo ' | '; 2020 } 2021 } 2022 } 2023 2024 /** 2025 * @global object 2026 * @param object $cm 2027 * @param string $mode 2028 * @param string $sortkey 2029 * @param string $sortorder 2030 */ 2031 function glossary_print_sorting_links($cm, $mode, $sortkey = '',$sortorder = '') { 2032 global $CFG, $OUTPUT; 2033 2034 $asc = get_string("ascending","glossary"); 2035 $desc = get_string("descending","glossary"); 2036 $bopen = '<b>'; 2037 $bclose = '</b>'; 2038 2039 $neworder = ''; 2040 $currentorder = ''; 2041 $currentsort = ''; 2042 if ( $sortorder ) { 2043 if ( $sortorder == 'asc' ) { 2044 $currentorder = $asc; 2045 $neworder = '&sortorder=desc'; 2046 $newordertitle = get_string('changeto', 'glossary', $desc); 2047 } else { 2048 $currentorder = $desc; 2049 $neworder = '&sortorder=asc'; 2050 $newordertitle = get_string('changeto', 'glossary', $asc); 2051 } 2052 $icon = " " . $OUTPUT->pix_icon($sortorder, $newordertitle, 'glossary'); 2053 } else { 2054 if ( $sortkey != 'CREATION' and $sortkey != 'UPDATE' and 2055 $sortkey != 'FIRSTNAME' and $sortkey != 'LASTNAME' ) { 2056 $icon = ""; 2057 $newordertitle = $asc; 2058 } else { 2059 $newordertitle = $desc; 2060 $neworder = '&sortorder=desc'; 2061 $icon = " " . $OUTPUT->pix_icon('asc', $newordertitle, 'glossary'); 2062 } 2063 } 2064 $ficon = ''; 2065 $fneworder = ''; 2066 $fbtag = ''; 2067 $fendbtag = ''; 2068 2069 $sicon = ''; 2070 $sneworder = ''; 2071 2072 $sbtag = ''; 2073 $fbtag = ''; 2074 $fendbtag = ''; 2075 $sendbtag = ''; 2076 2077 $sendbtag = ''; 2078 2079 if ( $sortkey == 'CREATION' or $sortkey == 'FIRSTNAME' ) { 2080 $ficon = $icon; 2081 $fneworder = $neworder; 2082 $fordertitle = $newordertitle; 2083 $sordertitle = $asc; 2084 $fbtag = $bopen; 2085 $fendbtag = $bclose; 2086 } elseif ($sortkey == 'UPDATE' or $sortkey == 'LASTNAME') { 2087 $sicon = $icon; 2088 $sneworder = $neworder; 2089 $fordertitle = $asc; 2090 $sordertitle = $newordertitle; 2091 $sbtag = $bopen; 2092 $sendbtag = $bclose; 2093 } else { 2094 $fordertitle = $asc; 2095 $sordertitle = $asc; 2096 } 2097 2098 if ( $sortkey == 'CREATION' or $sortkey == 'UPDATE' ) { 2099 $forder = 'CREATION'; 2100 $sorder = 'UPDATE'; 2101 $fsort = get_string("sortbycreation", "glossary"); 2102 $ssort = get_string("sortbylastupdate", "glossary"); 2103 2104 $currentsort = $fsort; 2105 if ($sortkey == 'UPDATE') { 2106 $currentsort = $ssort; 2107 } 2108 $sort = get_string("sortchronogically", "glossary"); 2109 } elseif ( $sortkey == 'FIRSTNAME' or $sortkey == 'LASTNAME') { 2110 $forder = 'FIRSTNAME'; 2111 $sorder = 'LASTNAME'; 2112 $fsort = get_string("firstname"); 2113 $ssort = get_string("lastname"); 2114 2115 $currentsort = $fsort; 2116 if ($sortkey == 'LASTNAME') { 2117 $currentsort = $ssort; 2118 } 2119 $sort = get_string("sortby", "glossary"); 2120 } 2121 $current = '<span class="accesshide">'.get_string('current', 'glossary', "$currentsort $currentorder").'</span>'; 2122 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 | ". 2123 "$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 />"; 2124 } 2125 2126 /** 2127 * 2128 * @param object $entry0 2129 * @param object $entry1 2130 * @return int [-1 | 0 | 1] 2131 */ 2132 function glossary_sort_entries ( $entry0, $entry1 ) { 2133 2134 if ( core_text::strtolower(ltrim($entry0->concept)) < core_text::strtolower(ltrim($entry1->concept)) ) { 2135 return -1; 2136 } elseif ( core_text::strtolower(ltrim($entry0->concept)) > core_text::strtolower(ltrim($entry1->concept)) ) { 2137 return 1; 2138 } else { 2139 return 0; 2140 } 2141 } 2142 2143 2144 /** 2145 * @global object 2146 * @global object 2147 * @global object 2148 * @param object $course 2149 * @param object $entry 2150 * @return bool 2151 */ 2152 function glossary_print_entry_ratings($course, $entry) { 2153 global $OUTPUT; 2154 if( !empty($entry->rating) ){ 2155 echo $OUTPUT->render($entry->rating); 2156 } 2157 } 2158 2159 /** 2160 * 2161 * @global object 2162 * @global object 2163 * @global object 2164 * @param int $courseid 2165 * @param array $entries 2166 * @param int $displayformat 2167 */ 2168 function glossary_print_dynaentry($courseid, $entries, $displayformat = -1) { 2169 global $USER, $CFG, $DB; 2170 2171 echo '<div class="boxaligncenter">'; 2172 echo '<table class="glossarypopup" cellspacing="0"><tr>'; 2173 echo '<td>'; 2174 if ( $entries ) { 2175 foreach ( $entries as $entry ) { 2176 if (! $glossary = $DB->get_record('glossary', array('id'=>$entry->glossaryid))) { 2177 print_error('invalidid', 'glossary'); 2178 } 2179 if (! $course = $DB->get_record('course', array('id'=>$glossary->course))) { 2180 print_error('coursemisconf'); 2181 } 2182 if (!$cm = get_coursemodule_from_instance('glossary', $entry->glossaryid, $glossary->course) ) { 2183 print_error('invalidid', 'glossary'); 2184 } 2185 2186 //If displayformat is present, override glossary->displayformat 2187 if ($displayformat < 0) { 2188 $dp = $glossary->displayformat; 2189 } else { 2190 $dp = $displayformat; 2191 } 2192 2193 //Get popupformatname 2194 $format = $DB->get_record('glossary_formats', array('name'=>$dp)); 2195 $displayformat = $format->popupformatname; 2196 2197 //Check displayformat variable and set to default if necessary 2198 if (!$displayformat) { 2199 $displayformat = 'dictionary'; 2200 } 2201 2202 $formatfile = $CFG->dirroot.'/mod/glossary/formats/'.$displayformat.'/'.$displayformat.'_format.php'; 2203 $functionname = 'glossary_show_entry_'.$displayformat; 2204 2205 if (file_exists($formatfile)) { 2206 include_once($formatfile); 2207 if (function_exists($functionname)) { 2208 $functionname($course, $cm, $glossary, $entry,'','','',''); 2209 } 2210 } 2211 } 2212 } 2213 echo '</td>'; 2214 echo '</tr></table></div>'; 2215 } 2216 2217 /** 2218 * 2219 * @global object 2220 * @param array $entries 2221 * @param array $aliases 2222 * @param array $categories 2223 * @return string 2224 */ 2225 function glossary_generate_export_csv($entries, $aliases, $categories) { 2226 global $CFG; 2227 $csv = ''; 2228 $delimiter = ''; 2229 require_once($CFG->libdir . '/csvlib.class.php'); 2230 $delimiter = csv_import_reader::get_delimiter('comma'); 2231 $csventries = array(0 => array(get_string('concept', 'glossary'), get_string('definition', 'glossary'))); 2232 $csvaliases = array(0 => array()); 2233 $csvcategories = array(0 => array()); 2234 $aliascount = 0; 2235 $categorycount = 0; 2236 2237 foreach ($entries as $entry) { 2238 $thisaliasesentry = array(); 2239 $thiscategoriesentry = array(); 2240 $thiscsventry = array($entry->concept, nl2br($entry->definition)); 2241 2242 if (array_key_exists($entry->id, $aliases) && is_array($aliases[$entry->id])) { 2243 $thiscount = count($aliases[$entry->id]); 2244 if ($thiscount > $aliascount) { 2245 $aliascount = $thiscount; 2246 } 2247 foreach ($aliases[$entry->id] as $alias) { 2248 $thisaliasesentry[] = trim($alias); 2249 } 2250 } 2251 if (array_key_exists($entry->id, $categories) && is_array($categories[$entry->id])) { 2252 $thiscount = count($categories[$entry->id]); 2253 if ($thiscount > $categorycount) { 2254 $categorycount = $thiscount; 2255 } 2256 foreach ($categories[$entry->id] as $catentry) { 2257 $thiscategoriesentry[] = trim($catentry); 2258 } 2259 } 2260 $csventries[$entry->id] = $thiscsventry; 2261 $csvaliases[$entry->id] = $thisaliasesentry; 2262 $csvcategories[$entry->id] = $thiscategoriesentry; 2263 2264 } 2265 $returnstr = ''; 2266 foreach ($csventries as $id => $row) { 2267 $aliasstr = ''; 2268 $categorystr = ''; 2269 if ($id == 0) { 2270 $aliasstr = get_string('alias', 'glossary'); 2271 $categorystr = get_string('category', 'glossary'); 2272 } 2273 $row = array_merge($row, array_pad($csvaliases[$id], $aliascount, $aliasstr), array_pad($csvcategories[$id], $categorycount, $categorystr)); 2274 $returnstr .= '"' . implode('"' . $delimiter . '"', $row) . '"' . "\n"; 2275 } 2276 return $returnstr; 2277 } 2278 2279 /** 2280 * 2281 * @param object $glossary 2282 * @param string $ignored invalid parameter 2283 * @param int|string $hook 2284 * @return string 2285 */ 2286 function glossary_generate_export_file($glossary, $ignored = "", $hook = 0) { 2287 global $CFG, $DB; 2288 2289 // Large exports are likely to take their time and memory. 2290 core_php_time_limit::raise(); 2291 raise_memory_limit(MEMORY_EXTRA); 2292 2293 $cm = get_coursemodule_from_instance('glossary', $glossary->id, $glossary->course); 2294 $context = context_module::instance($cm->id); 2295 2296 $co = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; 2297 2298 $co .= glossary_start_tag("GLOSSARY",0,true); 2299 $co .= glossary_start_tag("INFO",1,true); 2300 $co .= glossary_full_tag("NAME",2,false,$glossary->name); 2301 $co .= glossary_full_tag("INTRO",2,false,$glossary->intro); 2302 $co .= glossary_full_tag("INTROFORMAT",2,false,$glossary->introformat); 2303 $co .= glossary_full_tag("ALLOWDUPLICATEDENTRIES",2,false,$glossary->allowduplicatedentries); 2304 $co .= glossary_full_tag("DISPLAYFORMAT",2,false,$glossary->displayformat); 2305 $co .= glossary_full_tag("SHOWSPECIAL",2,false,$glossary->showspecial); 2306 $co .= glossary_full_tag("SHOWALPHABET",2,false,$glossary->showalphabet); 2307 $co .= glossary_full_tag("SHOWALL",2,false,$glossary->showall); 2308 $co .= glossary_full_tag("ALLOWCOMMENTS",2,false,$glossary->allowcomments); 2309 $co .= glossary_full_tag("USEDYNALINK",2,false,$glossary->usedynalink); 2310 $co .= glossary_full_tag("DEFAULTAPPROVAL",2,false,$glossary->defaultapproval); 2311 $co .= glossary_full_tag("GLOBALGLOSSARY",2,false,$glossary->globalglossary); 2312 $co .= glossary_full_tag("ENTBYPAGE",2,false,$glossary->entbypage); 2313 $co .= glossary_xml_export_files('INTROFILES', 2, $context->id, 'intro', 0); 2314 2315 if ( $entries = $DB->get_records("glossary_entries", array("glossaryid"=>$glossary->id))) { 2316 $co .= glossary_start_tag("ENTRIES",2,true); 2317 foreach ($entries as $entry) { 2318 $permissiongranted = 1; 2319 if ( $hook ) { 2320 switch ( $hook ) { 2321 case "ALL": 2322 case "SPECIAL": 2323 break; 2324 default: 2325 $permissiongranted = ($entry->concept[ strlen($hook)-1 ] == $hook); 2326 break; 2327 } 2328 } 2329 if ( $hook ) { 2330 switch ( $hook ) { 2331 case GLOSSARY_SHOW_ALL_CATEGORIES: 2332 break; 2333 case GLOSSARY_SHOW_NOT_CATEGORISED: 2334 $permissiongranted = !$DB->record_exists("glossary_entries_categories", array("entryid"=>$entry->id)); 2335 break; 2336 default: 2337 $permissiongranted = $DB->record_exists("glossary_entries_categories", array("entryid"=>$entry->id, "categoryid"=>$hook)); 2338 break; 2339 } 2340 } 2341 if ( $entry->approved and $permissiongranted ) { 2342 $co .= glossary_start_tag("ENTRY",3,true); 2343 $co .= glossary_full_tag("CONCEPT",4,false,trim($entry->concept)); 2344 $co .= glossary_full_tag("DEFINITION",4,false,$entry->definition); 2345 $co .= glossary_full_tag("FORMAT",4,false,$entry->definitionformat); // note: use old name for BC reasons 2346 $co .= glossary_full_tag("USEDYNALINK",4,false,$entry->usedynalink); 2347 $co .= glossary_full_tag("CASESENSITIVE",4,false,$entry->casesensitive); 2348 $co .= glossary_full_tag("FULLMATCH",4,false,$entry->fullmatch); 2349 $co .= glossary_full_tag("TEACHERENTRY",4,false,$entry->teacherentry); 2350 2351 if ( $aliases = $DB->get_records("glossary_alias", array("entryid"=>$entry->id))) { 2352 $co .= glossary_start_tag("ALIASES",4,true); 2353 foreach ($aliases as $alias) { 2354 $co .= glossary_start_tag("ALIAS",5,true); 2355 $co .= glossary_full_tag("NAME",6,false,trim($alias->alias)); 2356 $co .= glossary_end_tag("ALIAS",5,true); 2357 } 2358 $co .= glossary_end_tag("ALIASES",4,true); 2359 } 2360 if ( $catentries = $DB->get_records("glossary_entries_categories", array("entryid"=>$entry->id))) { 2361 $co .= glossary_start_tag("CATEGORIES",4,true); 2362 foreach ($catentries as $catentry) { 2363 $category = $DB->get_record("glossary_categories", array("id"=>$catentry->categoryid)); 2364 2365 $co .= glossary_start_tag("CATEGORY",5,true); 2366 $co .= glossary_full_tag("NAME",6,false,$category->name); 2367 $co .= glossary_full_tag("USEDYNALINK",6,false,$category->usedynalink); 2368 $co .= glossary_end_tag("CATEGORY",5,true); 2369 } 2370 $co .= glossary_end_tag("CATEGORIES",4,true); 2371 } 2372 2373 // Export files embedded in entries. 2374 $co .= glossary_xml_export_files('ENTRYFILES', 4, $context->id, 'entry', $entry->id); 2375 2376 // Export attachments. 2377 $co .= glossary_xml_export_files('ATTACHMENTFILES', 4, $context->id, 'attachment', $entry->id); 2378 2379 // Export tags. 2380 $tags = core_tag_tag::get_item_tags_array('mod_glossary', 'glossary_entries', $entry->id); 2381 if (count($tags)) { 2382 $co .= glossary_start_tag("TAGS", 4, true); 2383 foreach ($tags as $tag) { 2384 $co .= glossary_full_tag("TAG", 5, false, $tag); 2385 } 2386 $co .= glossary_end_tag("TAGS", 4, true); 2387 } 2388 2389 $co .= glossary_end_tag("ENTRY",3,true); 2390 } 2391 } 2392 $co .= glossary_end_tag("ENTRIES",2,true); 2393 2394 } 2395 2396 2397 $co .= glossary_end_tag("INFO",1,true); 2398 $co .= glossary_end_tag("GLOSSARY",0,true); 2399 2400 return $co; 2401 } 2402 /// Functions designed by Eloy Lafuente 2403 /// Functions to create, open and write header of the xml file 2404 2405 /** 2406 * Read import file and convert to current charset 2407 * 2408 * @global object 2409 * @param string $file 2410 * @return string 2411 */ 2412 function glossary_read_imported_file($file_content) { 2413 global $CFG; 2414 require_once "../../lib/xmlize.php"; 2415 2416 return xmlize($file_content, 0); 2417 } 2418 2419 /** 2420 * Return the xml start tag 2421 * 2422 * @param string $tag 2423 * @param int $level 2424 * @param bool $endline 2425 * @return string 2426 */ 2427 function glossary_start_tag($tag,$level=0,$endline=false) { 2428 if ($endline) { 2429 $endchar = "\n"; 2430 } else { 2431 $endchar = ""; 2432 } 2433 return str_repeat(" ",$level*2)."<".strtoupper($tag).">".$endchar; 2434 } 2435 2436 /** 2437 * Return the xml end tag 2438 * @param string $tag 2439 * @param int $level 2440 * @param bool $endline 2441 * @return string 2442 */ 2443 function glossary_end_tag($tag,$level=0,$endline=true) { 2444 if ($endline) { 2445 $endchar = "\n"; 2446 } else { 2447 $endchar = ""; 2448 } 2449 return str_repeat(" ",$level*2)."</".strtoupper($tag).">".$endchar; 2450 } 2451 2452 /** 2453 * Return the start tag, the contents and the end tag 2454 * 2455 * @global object 2456 * @param string $tag 2457 * @param int $level 2458 * @param bool $endline 2459 * @param string $content 2460 * @return string 2461 */ 2462 function glossary_full_tag($tag,$level=0,$endline=true,$content) { 2463 global $CFG; 2464 2465 $st = glossary_start_tag($tag,$level,$endline); 2466 $co = preg_replace("/\r\n|\r/", "\n", s($content)); 2467 $et = glossary_end_tag($tag,0,true); 2468 return $st.$co.$et; 2469 } 2470 2471 /** 2472 * Prepares file area to export as part of XML export 2473 * 2474 * @param string $tag XML tag to use for the group 2475 * @param int $taglevel 2476 * @param int $contextid 2477 * @param string $filearea 2478 * @param int $itemid 2479 * @return string 2480 */ 2481 function glossary_xml_export_files($tag, $taglevel, $contextid, $filearea, $itemid) { 2482 $co = ''; 2483 $fs = get_file_storage(); 2484 if ($files = $fs->get_area_files( 2485 $contextid, 'mod_glossary', $filearea, $itemid, 'itemid,filepath,filename', false)) { 2486 $co .= glossary_start_tag($tag, $taglevel, true); 2487 foreach ($files as $file) { 2488 $co .= glossary_start_tag('FILE', $taglevel + 1, true); 2489 $co .= glossary_full_tag('FILENAME', $taglevel + 2, false, $file->get_filename()); 2490 $co .= glossary_full_tag('FILEPATH', $taglevel + 2, false, $file->get_filepath()); 2491 $co .= glossary_full_tag('CONTENTS', $taglevel + 2, false, base64_encode($file->get_content())); 2492 $co .= glossary_full_tag('FILEAUTHOR', $taglevel + 2, false, $file->get_author()); 2493 $co .= glossary_full_tag('FILELICENSE', $taglevel + 2, false, $file->get_license()); 2494 $co .= glossary_end_tag('FILE', $taglevel + 1); 2495 } 2496 $co .= glossary_end_tag($tag, $taglevel); 2497 } 2498 return $co; 2499 } 2500 2501 /** 2502 * Parses files from XML import and inserts them into file system 2503 * 2504 * @param array $xmlparent parent element in parsed XML tree 2505 * @param string $tag 2506 * @param int $contextid 2507 * @param string $filearea 2508 * @param int $itemid 2509 * @return int 2510 */ 2511 function glossary_xml_import_files($xmlparent, $tag, $contextid, $filearea, $itemid) { 2512 global $USER, $CFG; 2513 $count = 0; 2514 if (isset($xmlparent[$tag][0]['#']['FILE'])) { 2515 $fs = get_file_storage(); 2516 $files = $xmlparent[$tag][0]['#']['FILE']; 2517 foreach ($files as $file) { 2518 $filerecord = array( 2519 'contextid' => $contextid, 2520 'component' => 'mod_glossary', 2521 'filearea' => $filearea, 2522 'itemid' => $itemid, 2523 'filepath' => $file['#']['FILEPATH'][0]['#'], 2524 'filename' => $file['#']['FILENAME'][0]['#'], 2525 'userid' => $USER->id 2526 ); 2527 if (array_key_exists('FILEAUTHOR', $file['#'])) { 2528 $filerecord['author'] = $file['#']['FILEAUTHOR'][0]['#']; 2529 } 2530 if (array_key_exists('FILELICENSE', $file['#'])) { 2531 $license = $file['#']['FILELICENSE'][0]['#']; 2532 require_once($CFG->libdir . "/licenselib.php"); 2533 if (license_manager::get_license_by_shortname($license)) { 2534 $filerecord['license'] = $license; 2535 } 2536 } 2537 $content = $file['#']['CONTENTS'][0]['#']; 2538 $fs->create_file_from_string($filerecord, base64_decode($content)); 2539 $count++; 2540 } 2541 } 2542 return $count; 2543 } 2544 2545 /** 2546 * How many unrated entries are in the given glossary for a given user? 2547 * 2548 * @global moodle_database $DB 2549 * @param int $glossaryid 2550 * @param int $userid 2551 * @return int 2552 */ 2553 function glossary_count_unrated_entries($glossaryid, $userid) { 2554 global $DB; 2555 2556 $sql = "SELECT COUNT('x') as num 2557 FROM {glossary_entries} 2558 WHERE glossaryid = :glossaryid AND 2559 userid <> :userid"; 2560 $params = array('glossaryid' => $glossaryid, 'userid' => $userid); 2561 $entries = $DB->count_records_sql($sql, $params); 2562 2563 if ($entries) { 2564 // We need to get the contextid for the glossaryid we have been given. 2565 $sql = "SELECT ctx.id 2566 FROM {context} ctx 2567 JOIN {course_modules} cm ON cm.id = ctx.instanceid 2568 JOIN {modules} m ON m.id = cm.module 2569 JOIN {glossary} g ON g.id = cm.instance 2570 WHERE ctx.contextlevel = :contextlevel AND 2571 m.name = 'glossary' AND 2572 g.id = :glossaryid"; 2573 $contextid = $DB->get_field_sql($sql, array('glossaryid' => $glossaryid, 'contextlevel' => CONTEXT_MODULE)); 2574 2575 // Now we need to count the ratings that this user has made 2576 $sql = "SELECT COUNT('x') AS num 2577 FROM {glossary_entries} e 2578 JOIN {rating} r ON r.itemid = e.id 2579 WHERE e.glossaryid = :glossaryid AND 2580 r.userid = :userid AND 2581 r.component = 'mod_glossary' AND 2582 r.ratingarea = 'entry' AND 2583 r.contextid = :contextid"; 2584 $params = array('glossaryid' => $glossaryid, 'userid' => $userid, 'contextid' => $contextid); 2585 $rated = $DB->count_records_sql($sql, $params); 2586 if ($rated) { 2587 // The number or enties minus the number or rated entries equals the number of unrated 2588 // entries 2589 if ($entries > $rated) { 2590 return $entries - $rated; 2591 } else { 2592 return 0; // Just in case there was a counting error 2593 } 2594 } else { 2595 return (int)$entries; 2596 } 2597 } else { 2598 return 0; 2599 } 2600 } 2601 2602 /** 2603 * 2604 * Returns the html code to represent any pagging bar. Paramenters are: 2605 * 2606 * The function dinamically show the first and last pages, and "scroll" over pages. 2607 * Fully compatible with Moodle's print_paging_bar() function. Perhaps some day this 2608 * could replace the general one. ;-) 2609 * 2610 * @param int $totalcount total number of records to be displayed 2611 * @param int $page page currently selected (0 based) 2612 * @param int $perpage number of records per page 2613 * @param string $baseurl url to link in each page, the string 'page=XX' will be added automatically. 2614 * 2615 * @param int $maxpageallowed Optional maximum number of page allowed. 2616 * @param int $maxdisplay Optional maximum number of page links to show in the bar 2617 * @param string $separator Optional string to be used between pages in the bar 2618 * @param string $specialtext Optional string to be showed as an special link 2619 * @param string $specialvalue Optional value (page) to be used in the special link 2620 * @param bool $previousandnext Optional to decide if we want the previous and next links 2621 * @return string 2622 */ 2623 function glossary_get_paging_bar($totalcount, $page, $perpage, $baseurl, $maxpageallowed=99999, $maxdisplay=20, $separator=" ", $specialtext="", $specialvalue=-1, $previousandnext = true) { 2624 2625 $code = ''; 2626 2627 $showspecial = false; 2628 $specialselected = false; 2629 2630 //Check if we have to show the special link 2631 if (!empty($specialtext)) { 2632 $showspecial = true; 2633 } 2634 //Check if we are with the special link selected 2635 if ($showspecial && $page == $specialvalue) { 2636 $specialselected = true; 2637 } 2638 2639 //If there are results (more than 1 page) 2640 if ($totalcount > $perpage) { 2641 $code .= "<div style=\"text-align:center\">"; 2642 $code .= "<p>".get_string("page").":"; 2643 2644 $maxpage = (int)(($totalcount-1)/$perpage); 2645 2646 //Lower and upper limit of page 2647 if ($page < 0) { 2648 $page = 0; 2649 } 2650 if ($page > $maxpageallowed) { 2651 $page = $maxpageallowed; 2652 } 2653 if ($page > $maxpage) { 2654 $page = $maxpage; 2655 } 2656 2657 //Calculate the window of pages 2658 $pagefrom = $page - ((int)($maxdisplay / 2)); 2659 if ($pagefrom < 0) { 2660 $pagefrom = 0; 2661 } 2662 $pageto = $pagefrom + $maxdisplay - 1; 2663 if ($pageto > $maxpageallowed) { 2664 $pageto = $maxpageallowed; 2665 } 2666 if ($pageto > $maxpage) { 2667 $pageto = $maxpage; 2668 } 2669 2670 //Some movements can be necessary if don't see enought pages 2671 if ($pageto - $pagefrom < $maxdisplay - 1) { 2672 if ($pageto - $maxdisplay + 1 > 0) { 2673 $pagefrom = $pageto - $maxdisplay + 1; 2674 } 2675 } 2676 2677 //Calculate first and last if necessary 2678 $firstpagecode = ''; 2679 $lastpagecode = ''; 2680 if ($pagefrom > 0) { 2681 $firstpagecode = "$separator<a href=\"{$baseurl}page=0\">1</a>"; 2682 if ($pagefrom > 1) { 2683 $firstpagecode .= "$separator..."; 2684 } 2685 } 2686 if ($pageto < $maxpage) { 2687 if ($pageto < $maxpage -1) { 2688 $lastpagecode = "$separator..."; 2689 } 2690 $lastpagecode .= "$separator<a href=\"{$baseurl}page=$maxpage\">".($maxpage+1)."</a>"; 2691 } 2692 2693 //Previous 2694 if ($page > 0 && $previousandnext) { 2695 $pagenum = $page - 1; 2696 $code .= " (<a href=\"{$baseurl}page=$pagenum\">".get_string("previous")."</a>) "; 2697 } 2698 2699 //Add first 2700 $code .= $firstpagecode; 2701 2702 $pagenum = $pagefrom; 2703 2704 //List of maxdisplay pages 2705 while ($pagenum <= $pageto) { 2706 $pagetoshow = $pagenum +1; 2707 if ($pagenum == $page && !$specialselected) { 2708 $code .= "$separator<b>$pagetoshow</b>"; 2709 } else { 2710 $code .= "$separator<a href=\"{$baseurl}page=$pagenum\">$pagetoshow</a>"; 2711 } 2712 $pagenum++; 2713 } 2714 2715 //Add last 2716 $code .= $lastpagecode; 2717 2718 //Next 2719 if ($page < $maxpage && $page < $maxpageallowed && $previousandnext) { 2720 $pagenum = $page + 1; 2721 $code .= "$separator(<a href=\"{$baseurl}page=$pagenum\">".get_string("next")."</a>)"; 2722 } 2723 2724 //Add special 2725 if ($showspecial) { 2726 $code .= '<br />'; 2727 if ($specialselected) { 2728 $code .= "$separator<b>$specialtext</b>"; 2729 } else { 2730 $code .= "$separator<a href=\"{$baseurl}page=$specialvalue\">$specialtext</a>"; 2731 } 2732 } 2733 2734 //End html 2735 $code .= "</p>"; 2736 $code .= "</div>"; 2737 } 2738 2739 return $code; 2740 } 2741 2742 /** 2743 * List the actions that correspond to a view of this module. 2744 * This is used by the participation report. 2745 * 2746 * Note: This is not used by new logging system. Event with 2747 * crud = 'r' and edulevel = LEVEL_PARTICIPATING will 2748 * be considered as view action. 2749 * 2750 * @return array 2751 */ 2752 function glossary_get_view_actions() { 2753 return array('view','view all','view entry'); 2754 } 2755 2756 /** 2757 * List the actions that correspond to a post of this module. 2758 * This is used by the participation report. 2759 * 2760 * Note: This is not used by new logging system. Event with 2761 * crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING 2762 * will be considered as post action. 2763 * 2764 * @return array 2765 */ 2766 function glossary_get_post_actions() { 2767 return array('add category','add entry','approve entry','delete category','delete entry','edit category','update entry'); 2768 } 2769 2770 2771 /** 2772 * Implementation of the function for printing the form elements that control 2773 * whether the course reset functionality affects the glossary. 2774 * @param object $mform form passed by reference 2775 */ 2776 function glossary_reset_course_form_definition(&$mform) { 2777 $mform->addElement('header', 'glossaryheader', get_string('modulenameplural', 'glossary')); 2778 $mform->addElement('checkbox', 'reset_glossary_all', get_string('resetglossariesall','glossary')); 2779 2780 $mform->addElement('select', 'reset_glossary_types', get_string('resetglossaries', 'glossary'), 2781 array('main'=>get_string('mainglossary', 'glossary'), 'secondary'=>get_string('secondaryglossary', 'glossary')), array('multiple' => 'multiple')); 2782 $mform->setAdvanced('reset_glossary_types'); 2783 $mform->disabledIf('reset_glossary_types', 'reset_glossary_all', 'checked'); 2784 2785 $mform->addElement('checkbox', 'reset_glossary_notenrolled', get_string('deletenotenrolled', 'glossary')); 2786 $mform->disabledIf('reset_glossary_notenrolled', 'reset_glossary_all', 'checked'); 2787 2788 $mform->addElement('checkbox', 'reset_glossary_ratings', get_string('deleteallratings')); 2789 $mform->disabledIf('reset_glossary_ratings', 'reset_glossary_all', 'checked'); 2790 2791 $mform->addElement('checkbox', 'reset_glossary_comments', get_string('deleteallcomments')); 2792 $mform->disabledIf('reset_glossary_comments', 'reset_glossary_all', 'checked'); 2793 2794 $mform->addElement('checkbox', 'reset_glossary_tags', get_string('removeallglossarytags', 'glossary')); 2795 $mform->disabledIf('reset_glossary_tags', 'reset_glossary_all', 'checked'); 2796 } 2797 2798 /** 2799 * Course reset form defaults. 2800 * @return array 2801 */ 2802 function glossary_reset_course_form_defaults($course) { 2803 return array('reset_glossary_all'=>0, 'reset_glossary_ratings'=>1, 'reset_glossary_comments'=>1, 'reset_glossary_notenrolled'=>0); 2804 } 2805 2806 /** 2807 * Removes all grades from gradebook 2808 * 2809 * @param int $courseid The ID of the course to reset 2810 * @param string $type The optional type of glossary. 'main', 'secondary' or '' 2811 */ 2812 function glossary_reset_gradebook($courseid, $type='') { 2813 global $DB; 2814 2815 switch ($type) { 2816 case 'main' : $type = "AND g.mainglossary=1"; break; 2817 case 'secondary' : $type = "AND g.mainglossary=0"; break; 2818 default : $type = ""; //all 2819 } 2820 2821 $sql = "SELECT g.*, cm.idnumber as cmidnumber, g.course as courseid 2822 FROM {glossary} g, {course_modules} cm, {modules} m 2823 WHERE m.name='glossary' AND m.id=cm.module AND cm.instance=g.id AND g.course=? $type"; 2824 2825 if ($glossarys = $DB->get_records_sql($sql, array($courseid))) { 2826 foreach ($glossarys as $glossary) { 2827 glossary_grade_item_update($glossary, 'reset'); 2828 } 2829 } 2830 } 2831 /** 2832 * Actual implementation of the reset course functionality, delete all the 2833 * glossary responses for course $data->courseid. 2834 * 2835 * @global object 2836 * @param $data the data submitted from the reset course. 2837 * @return array status array 2838 */ 2839 function glossary_reset_userdata($data) { 2840 global $CFG, $DB; 2841 require_once($CFG->dirroot.'/rating/lib.php'); 2842 2843 $componentstr = get_string('modulenameplural', 'glossary'); 2844 $status = array(); 2845 2846 $allentriessql = "SELECT e.id 2847 FROM {glossary_entries} e 2848 JOIN {glossary} g ON e.glossaryid = g.id 2849 WHERE g.course = ?"; 2850 2851 $allglossariessql = "SELECT g.id 2852 FROM {glossary} g 2853 WHERE g.course = ?"; 2854 2855 $params = array($data->courseid); 2856 2857 $fs = get_file_storage(); 2858 2859 $rm = new rating_manager(); 2860 $ratingdeloptions = new stdClass; 2861 $ratingdeloptions->component = 'mod_glossary'; 2862 $ratingdeloptions->ratingarea = 'entry'; 2863 2864 // delete entries if requested 2865 if (!empty($data->reset_glossary_all) 2866 or (!empty($data->reset_glossary_types) and in_array('main', $data->reset_glossary_types) and in_array('secondary', $data->reset_glossary_types))) { 2867 2868 $params[] = 'glossary_entry'; 2869 $DB->delete_records_select('comments', "itemid IN ($allentriessql) AND commentarea=?", $params); 2870 $DB->delete_records_select('glossary_alias', "entryid IN ($allentriessql)", $params); 2871 $DB->delete_records_select('glossary_entries', "glossaryid IN ($allglossariessql)", $params); 2872 2873 // now get rid of all attachments 2874 if ($glossaries = $DB->get_records_sql($allglossariessql, $params)) { 2875 foreach ($glossaries as $glossaryid=>$unused) { 2876 if (!$cm = get_coursemodule_from_instance('glossary', $glossaryid)) { 2877 continue; 2878 } 2879 $context = context_module::instance($cm->id); 2880 $fs->delete_area_files($context->id, 'mod_glossary', 'attachment'); 2881 2882 //delete ratings 2883 $ratingdeloptions->contextid = $context->id; 2884 $rm->delete_ratings($ratingdeloptions); 2885 2886 core_tag_tag::delete_instances('mod_glossary', null, $context->id); 2887 } 2888 } 2889 2890 // remove all grades from gradebook 2891 if (empty($data->reset_gradebook_grades)) { 2892 glossary_reset_gradebook($data->courseid); 2893 } 2894 2895 $status[] = array('component'=>$componentstr, 'item'=>get_string('resetglossariesall', 'glossary'), 'error'=>false); 2896 2897 } else if (!empty($data->reset_glossary_types)) { 2898 $mainentriessql = "$allentriessql AND g.mainglossary=1"; 2899 $secondaryentriessql = "$allentriessql AND g.mainglossary=0"; 2900 2901 $mainglossariessql = "$allglossariessql AND g.mainglossary=1"; 2902 $secondaryglossariessql = "$allglossariessql AND g.mainglossary=0"; 2903 2904 if (in_array('main', $data->reset_glossary_types)) { 2905 $params[] = 'glossary_entry'; 2906 $DB->delete_records_select('comments', "itemid IN ($mainentriessql) AND commentarea=?", $params); 2907 $DB->delete_records_select('glossary_entries', "glossaryid IN ($mainglossariessql)", $params); 2908 2909 if ($glossaries = $DB->get_records_sql($mainglossariessql, $params)) { 2910 foreach ($glossaries as $glossaryid=>$unused) { 2911 if (!$cm = get_coursemodule_from_instance('glossary', $glossaryid)) { 2912 continue; 2913 } 2914 $context = context_module::instance($cm->id); 2915 $fs->delete_area_files($context->id, 'mod_glossary', 'attachment'); 2916 2917 //delete ratings 2918 $ratingdeloptions->contextid = $context->id; 2919 $rm->delete_ratings($ratingdeloptions); 2920 2921 core_tag_tag::delete_instances('mod_glossary', null, $context->id); 2922 } 2923 } 2924 2925 // remove all grades from gradebook 2926 if (empty($data->reset_gradebook_grades)) { 2927 glossary_reset_gradebook($data->courseid, 'main'); 2928 } 2929 2930 $status[] = array('component'=>$componentstr, 'item'=>get_string('resetglossaries', 'glossary').': '.get_string('mainglossary', 'glossary'), 'error'=>false); 2931 2932 } else if (in_array('secondary', $data->reset_glossary_types)) { 2933 $params[] = 'glossary_entry'; 2934 $DB->delete_records_select('comments', "itemid IN ($secondaryentriessql) AND commentarea=?", $params); 2935 $DB->delete_records_select('glossary_entries', "glossaryid IN ($secondaryglossariessql)", $params); 2936 // remove exported source flag from entries in main glossary 2937 $DB->execute("UPDATE {glossary_entries} 2938 SET sourceglossaryid=0 2939 WHERE glossaryid IN ($mainglossariessql)", $params); 2940 2941 if ($glossaries = $DB->get_records_sql($secondaryglossariessql, $params)) { 2942 foreach ($glossaries as $glossaryid=>$unused) { 2943 if (!$cm = get_coursemodule_from_instance('glossary', $glossaryid)) { 2944 continue; 2945 } 2946 $context = context_module::instance($cm->id); 2947 $fs->delete_area_files($context->id, 'mod_glossary', 'attachment'); 2948 2949 //delete ratings 2950 $ratingdeloptions->contextid = $context->id; 2951 $rm->delete_ratings($ratingdeloptions); 2952 2953 core_tag_tag::delete_instances('mod_glossary', null, $context->id); 2954 } 2955 } 2956 2957 // remove all grades from gradebook 2958 if (empty($data->reset_gradebook_grades)) { 2959 glossary_reset_gradebook($data->courseid, 'secondary'); 2960 } 2961 2962 $status[] = array('component'=>$componentstr, 'item'=>get_string('resetglossaries', 'glossary').': '.get_string('secondaryglossary', 'glossary'), 'error'=>false); 2963 } 2964 } 2965 2966 // remove entries by users not enrolled into course 2967 if (!empty($data->reset_glossary_notenrolled)) { 2968 $entriessql = "SELECT e.id, e.userid, e.glossaryid, u.id AS userexists, u.deleted AS userdeleted 2969 FROM {glossary_entries} e 2970 JOIN {glossary} g ON e.glossaryid = g.id 2971 LEFT JOIN {user} u ON e.userid = u.id 2972 WHERE g.course = ? AND e.userid > 0"; 2973 2974 $course_context = context_course::instance($data->courseid); 2975 $notenrolled = array(); 2976 $rs = $DB->get_recordset_sql($entriessql, $params); 2977 if ($rs->valid()) { 2978 foreach ($rs as $entry) { 2979 if (array_key_exists($entry->userid, $notenrolled) or !$entry->userexists or $entry->userdeleted 2980 or !is_enrolled($course_context , $entry->userid)) { 2981 $DB->delete_records('comments', array('commentarea'=>'glossary_entry', 'itemid'=>$entry->id)); 2982 $DB->delete_records('glossary_entries', array('id'=>$entry->id)); 2983 2984 if ($cm = get_coursemodule_from_instance('glossary', $entry->glossaryid)) { 2985 $context = context_module::instance($cm->id); 2986 $fs->delete_area_files($context->id, 'mod_glossary', 'attachment', $entry->id); 2987 2988 //delete ratings 2989 $ratingdeloptions->contextid = $context->id; 2990 $rm->delete_ratings($ratingdeloptions); 2991 } 2992 } 2993 } 2994 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotenrolled', 'glossary'), 'error'=>false); 2995 } 2996 $rs->close(); 2997 } 2998 2999 // remove all ratings 3000 if (!empty($data->reset_glossary_ratings)) { 3001 //remove ratings 3002 if ($glossaries = $DB->get_records_sql($allglossariessql, $params)) { 3003 foreach ($glossaries as $glossaryid=>$unused) { 3004 if (!$cm = get_coursemodule_from_instance('glossary', $glossaryid)) { 3005 continue; 3006 } 3007 $context = context_module::instance($cm->id); 3008 3009 //delete ratings 3010 $ratingdeloptions->contextid = $context->id; 3011 $rm->delete_ratings($ratingdeloptions); 3012 } 3013 } 3014 3015 // remove all grades from gradebook 3016 if (empty($data->reset_gradebook_grades)) { 3017 glossary_reset_gradebook($data->courseid); 3018 } 3019 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallratings'), 'error'=>false); 3020 } 3021 3022 // remove comments 3023 if (!empty($data->reset_glossary_comments)) { 3024 $params[] = 'glossary_entry'; 3025 $DB->delete_records_select('comments', "itemid IN ($allentriessql) AND commentarea= ? ", $params); 3026 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallcomments'), 'error'=>false); 3027 } 3028 3029 // Remove all the tags. 3030 if (!empty($data->reset_glossary_tags)) { 3031 if ($glossaries = $DB->get_records_sql($allglossariessql, $params)) { 3032 foreach ($glossaries as $glossaryid => $unused) { 3033 if (!$cm = get_coursemodule_from_instance('glossary', $glossaryid)) { 3034 continue; 3035 } 3036 3037 $context = context_module::instance($cm->id); 3038 core_tag_tag::delete_instances('mod_glossary', null, $context->id); 3039 } 3040 } 3041 3042 $status[] = array('component' => $componentstr, 'item' => get_string('tagsdeleted', 'glossary'), 'error' => false); 3043 } 3044 3045 /// updating dates - shift may be negative too 3046 if ($data->timeshift) { 3047 // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. 3048 // See MDL-9367. 3049 shift_course_mod_dates('glossary', array('assesstimestart', 'assesstimefinish'), $data->timeshift, $data->courseid); 3050 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false); 3051 } 3052 3053 return $status; 3054 } 3055 3056 /** 3057 * Returns all other caps used in module 3058 * @return array 3059 */ 3060 function glossary_get_extra_capabilities() { 3061 return ['moodle/rating:view', 'moodle/rating:viewany', 'moodle/rating:viewall', 'moodle/rating:rate', 3062 'moodle/comment:view', 'moodle/comment:post', 'moodle/comment:delete']; 3063 } 3064 3065 /** 3066 * @param string $feature FEATURE_xx constant for requested feature 3067 * @return mixed True if module supports feature, null if doesn't know 3068 */ 3069 function glossary_supports($feature) { 3070 switch($feature) { 3071 case FEATURE_GROUPS: return false; 3072 case FEATURE_GROUPINGS: return false; 3073 case FEATURE_MOD_INTRO: return true; 3074 case FEATURE_COMPLETION_TRACKS_VIEWS: return true; 3075 case FEATURE_COMPLETION_HAS_RULES: return true; 3076 case FEATURE_GRADE_HAS_GRADE: return true; 3077 case FEATURE_GRADE_OUTCOMES: return true; 3078 case FEATURE_RATE: return true; 3079 case FEATURE_BACKUP_MOODLE2: return true; 3080 case FEATURE_SHOW_DESCRIPTION: return true; 3081 case FEATURE_COMMENT: return true; 3082 3083 default: return null; 3084 } 3085 } 3086 3087 /** 3088 * Obtains the automatic completion state for this glossary based on any conditions 3089 * in glossary settings. 3090 * 3091 * @global object 3092 * @global object 3093 * @param object $course Course 3094 * @param object $cm Course-module 3095 * @param int $userid User ID 3096 * @param bool $type Type of comparison (or/and; can be used as return value if no conditions) 3097 * @return bool True if completed, false if not. (If no conditions, then return 3098 * value depends on comparison type) 3099 */ 3100 function glossary_get_completion_state($course,$cm,$userid,$type) { 3101 global $CFG, $DB; 3102 3103 // Get glossary details 3104 if (!($glossary=$DB->get_record('glossary',array('id'=>$cm->instance)))) { 3105 throw new Exception("Can't find glossary {$cm->instance}"); 3106 } 3107 3108 $result=$type; // Default return value 3109 3110 if ($glossary->completionentries) { 3111 $value = $glossary->completionentries <= 3112 $DB->count_records('glossary_entries',array('glossaryid'=>$glossary->id, 'userid'=>$userid, 'approved'=>1)); 3113 if ($type == COMPLETION_AND) { 3114 $result = $result && $value; 3115 } else { 3116 $result = $result || $value; 3117 } 3118 } 3119 3120 return $result; 3121 } 3122 3123 function glossary_extend_navigation($navigation, $course, $module, $cm) { 3124 global $CFG, $DB; 3125 3126 $displayformat = $DB->get_record('glossary_formats', array('name' => $module->displayformat)); 3127 // Get visible tabs for the format and check if the menu needs to be displayed. 3128 $showtabs = glossary_get_visible_tabs($displayformat); 3129 3130 foreach ($showtabs as $showtabkey => $showtabvalue) { 3131 3132 switch($showtabvalue) { 3133 case GLOSSARY_STANDARD : 3134 $navigation->add(get_string('standardview', 'glossary'), new moodle_url('/mod/glossary/view.php', 3135 array('id' => $cm->id, 'mode' => 'letter'))); 3136 break; 3137 case GLOSSARY_CATEGORY : 3138 $navigation->add(get_string('categoryview', 'glossary'), new moodle_url('/mod/glossary/view.php', 3139 array('id' => $cm->id, 'mode' => 'cat'))); 3140 break; 3141 case GLOSSARY_DATE : 3142 $navigation->add(get_string('dateview', 'glossary'), new moodle_url('/mod/glossary/view.php', 3143 array('id' => $cm->id, 'mode' => 'date'))); 3144 break; 3145 case GLOSSARY_AUTHOR : 3146 $navigation->add(get_string('authorview', 'glossary'), new moodle_url('/mod/glossary/view.php', 3147 array('id' => $cm->id, 'mode' => 'author'))); 3148 break; 3149 } 3150 } 3151 } 3152 3153 /** 3154 * Adds module specific settings to the settings block 3155 * 3156 * @param settings_navigation $settings The settings navigation object 3157 * @param navigation_node $glossarynode The node to add module settings to 3158 */ 3159 function glossary_extend_settings_navigation(settings_navigation $settings, navigation_node $glossarynode) { 3160 global $PAGE, $DB, $CFG, $USER; 3161 3162 $mode = optional_param('mode', '', PARAM_ALPHA); 3163 $hook = optional_param('hook', 'ALL', PARAM_CLEAN); 3164 3165 if (has_capability('mod/glossary:import', $PAGE->cm->context)) { 3166 $glossarynode->add(get_string('importentries', 'glossary'), new moodle_url('/mod/glossary/import.php', array('id'=>$PAGE->cm->id))); 3167 } 3168 3169 if (has_capability('mod/glossary:export', $PAGE->cm->context)) { 3170 $glossarynode->add(get_string('exportentries', 'glossary'), new moodle_url('/mod/glossary/export.php', array('id'=>$PAGE->cm->id, 'mode'=>$mode, 'hook'=>$hook))); 3171 } 3172 3173 if (has_capability('mod/glossary:approve', $PAGE->cm->context) && ($hiddenentries = $DB->count_records('glossary_entries', array('glossaryid'=>$PAGE->cm->instance, 'approved'=>0)))) { 3174 $glossarynode->add(get_string('waitingapproval', 'glossary'), new moodle_url('/mod/glossary/view.php', array('id'=>$PAGE->cm->id, 'mode'=>'approval'))); 3175 } 3176 3177 if (has_capability('mod/glossary:write', $PAGE->cm->context)) { 3178 $glossarynode->add(get_string('addentry', 'glossary'), new moodle_url('/mod/glossary/edit.php', array('cmid'=>$PAGE->cm->id))); 3179 } 3180 3181 $glossary = $DB->get_record('glossary', array("id" => $PAGE->cm->instance)); 3182 3183 if (!empty($CFG->enablerssfeeds) && !empty($CFG->glossary_enablerssfeeds) && $glossary->rsstype && $glossary->rssarticles && has_capability('mod/glossary:view', $PAGE->cm->context)) { 3184 require_once("$CFG->libdir/rsslib.php"); 3185 3186 $string = get_string('rsstype', 'glossary'); 3187 3188 $url = new moodle_url(rss_get_url($PAGE->cm->context->id, $USER->id, 'mod_glossary', $glossary->id)); 3189 $glossarynode->add($string, $url, settings_navigation::TYPE_SETTING, null, null, new pix_icon('i/rss', '')); 3190 } 3191 } 3192 3193 /** 3194 * Running addtional permission check on plugin, for example, plugins 3195 * may have switch to turn on/off comments option, this callback will 3196 * affect UI display, not like pluginname_comment_validate only throw 3197 * exceptions. 3198 * Capability check has been done in comment->check_permissions(), we 3199 * don't need to do it again here. 3200 * 3201 * @package mod_glossary 3202 * @category comment 3203 * 3204 * @param stdClass $comment_param { 3205 * context => context the context object 3206 * courseid => int course id 3207 * cm => stdClass course module object 3208 * commentarea => string comment area 3209 * itemid => int itemid 3210 * } 3211 * @return array 3212 */ 3213 function glossary_comment_permissions($comment_param) { 3214 return array('post'=>true, 'view'=>true); 3215 } 3216 3217 /** 3218 * Validate comment parameter before perform other comments actions 3219 * 3220 * @package mod_glossary 3221 * @category comment 3222 * 3223 * @param stdClass $comment_param { 3224 * context => context the context object 3225 * courseid => int course id 3226 * cm => stdClass course module object 3227 * commentarea => string comment area 3228 * itemid => int itemid 3229 * } 3230 * @return boolean 3231 */ 3232 function glossary_comment_validate($comment_param) { 3233 global $DB; 3234 // validate comment area 3235 if ($comment_param->commentarea != 'glossary_entry') { 3236 throw new comment_exception('invalidcommentarea'); 3237 } 3238 if (!$record = $DB->get_record('glossary_entries', array('id'=>$comment_param->itemid))) { 3239 throw new comment_exception('invalidcommentitemid'); 3240 } 3241 if ($record->sourceglossaryid && $record->sourceglossaryid == $comment_param->cm->instance) { 3242 $glossary = $DB->get_record('glossary', array('id'=>$record->sourceglossaryid)); 3243 } else { 3244 $glossary = $DB->get_record('glossary', array('id'=>$record->glossaryid)); 3245 } 3246 if (!$glossary) { 3247 throw new comment_exception('invalidid', 'data'); 3248 } 3249 if (!$course = $DB->get_record('course', array('id'=>$glossary->course))) { 3250 throw new comment_exception('coursemisconf'); 3251 } 3252 if (!$cm = get_coursemodule_from_instance('glossary', $glossary->id, $course->id)) { 3253 throw new comment_exception('invalidcoursemodule'); 3254 } 3255 $context = context_module::instance($cm->id); 3256 3257 if ($glossary->defaultapproval and !$record->approved and !has_capability('mod/glossary:approve', $context)) { 3258 throw new comment_exception('notapproved', 'glossary'); 3259 } 3260 // validate context id 3261 if ($context->id != $comment_param->context->id) { 3262 throw new comment_exception('invalidcontext'); 3263 } 3264 // validation for comment deletion 3265 if (!empty($comment_param->commentid)) { 3266 if ($comment = $DB->get_record('comments', array('id'=>$comment_param->commentid))) { 3267 if ($comment->commentarea != 'glossary_entry') { 3268 throw new comment_exception('invalidcommentarea'); 3269 } 3270 if ($comment->contextid != $comment_param->context->id) { 3271 throw new comment_exception('invalidcontext'); 3272 } 3273 if ($comment->itemid != $comment_param->itemid) { 3274 throw new comment_exception('invalidcommentitemid'); 3275 } 3276 } else { 3277 throw new comment_exception('invalidcommentid'); 3278 } 3279 } 3280 return true; 3281 } 3282 3283 /** 3284 * Return a list of page types 3285 * @param string $pagetype current page type 3286 * @param stdClass $parentcontext Block's parent context 3287 * @param stdClass $currentcontext Current context of block 3288 */ 3289 function glossary_page_type_list($pagetype, $parentcontext, $currentcontext) { 3290 $module_pagetype = array( 3291 'mod-glossary-*'=>get_string('page-mod-glossary-x', 'glossary'), 3292 'mod-glossary-view'=>get_string('page-mod-glossary-view', 'glossary'), 3293 'mod-glossary-edit'=>get_string('page-mod-glossary-edit', 'glossary')); 3294 return $module_pagetype; 3295 } 3296 3297 /** 3298 * Return list of all glossary tabs. 3299 * @throws coding_exception 3300 * @return array 3301 */ 3302 function glossary_get_all_tabs() { 3303 3304 return array ( 3305 GLOSSARY_AUTHOR => get_string('authorview', 'glossary'), 3306 GLOSSARY_CATEGORY => get_string('categoryview', 'glossary'), 3307 GLOSSARY_DATE => get_string('dateview', 'glossary') 3308 ); 3309 } 3310 3311 /** 3312 * Set 'showtabs' value for glossary formats 3313 * @param stdClass $glossaryformat record from 'glossary_formats' table 3314 */ 3315 function glossary_set_default_visible_tabs($glossaryformat) { 3316 global $DB; 3317 3318 switch($glossaryformat->name) { 3319 case GLOSSARY_CONTINUOUS: 3320 $showtabs = 'standard,category,date'; 3321 break; 3322 case GLOSSARY_DICTIONARY: 3323 $showtabs = 'standard'; 3324 // Special code for upgraded instances that already had categories set up 3325 // in this format - enable "category" tab. 3326 // In new instances only 'standard' tab will be visible. 3327 if ($DB->record_exists_sql("SELECT 1 3328 FROM {glossary} g, {glossary_categories} gc 3329 WHERE g.id = gc.glossaryid and g.displayformat = ?", 3330 array(GLOSSARY_DICTIONARY))) { 3331 $showtabs .= ',category'; 3332 } 3333 break; 3334 case GLOSSARY_FULLWITHOUTAUTHOR: 3335 $showtabs = 'standard,category,date'; 3336 break; 3337 default: 3338 $showtabs = 'standard,category,date,author'; 3339 break; 3340 } 3341 3342 $DB->set_field('glossary_formats', 'showtabs', $showtabs, array('id' => $glossaryformat->id)); 3343 $glossaryformat->showtabs = $showtabs; 3344 } 3345 3346 /** 3347 * Convert 'showtabs' string to array 3348 * @param stdClass $displayformat record from 'glossary_formats' table 3349 * @return array 3350 */ 3351 function glossary_get_visible_tabs($displayformat) { 3352 if (empty($displayformat->showtabs)) { 3353 glossary_set_default_visible_tabs($displayformat); 3354 } 3355 $showtabs = preg_split('/,/', $displayformat->showtabs, -1, PREG_SPLIT_NO_EMPTY); 3356 3357 return $showtabs; 3358 } 3359 3360 /** 3361 * Notify that the glossary was viewed. 3362 * 3363 * This will trigger relevant events and activity completion. 3364 * 3365 * @param stdClass $glossary The glossary object. 3366 * @param stdClass $course The course object. 3367 * @param stdClass $cm The course module object. 3368 * @param stdClass $context The context object. 3369 * @param string $mode The mode in which the glossary was viewed. 3370 * @since Moodle 3.1 3371 */ 3372 function glossary_view($glossary, $course, $cm, $context, $mode) { 3373 3374 // Completion trigger. 3375 $completion = new completion_info($course); 3376 $completion->set_module_viewed($cm); 3377 3378 // Trigger the course module viewed event. 3379 $event = \mod_glossary\event\course_module_viewed::create(array( 3380 'objectid' => $glossary->id, 3381 'context' => $context, 3382 'other' => array('mode' => $mode) 3383 )); 3384 $event->add_record_snapshot('course', $course); 3385 $event->add_record_snapshot('course_modules', $cm); 3386 $event->add_record_snapshot('glossary', $glossary); 3387 $event->trigger(); 3388 } 3389 3390 /** 3391 * Notify that a glossary entry was viewed. 3392 * 3393 * This will trigger relevant events. 3394 * 3395 * @param stdClass $entry The entry object. 3396 * @param stdClass $context The context object. 3397 * @since Moodle 3.1 3398 */ 3399 function glossary_entry_view($entry, $context) { 3400 3401 // Trigger the entry viewed event. 3402 $event = \mod_glossary\event\entry_viewed::create(array( 3403 'objectid' => $entry->id, 3404 'context' => $context 3405 )); 3406 $event->add_record_snapshot('glossary_entries', $entry); 3407 $event->trigger(); 3408 3409 } 3410 3411 /** 3412 * Returns the entries of a glossary by letter. 3413 * 3414 * @param object $glossary The glossary. 3415 * @param context $context The context of the glossary. 3416 * @param string $letter The letter, or ALL, or SPECIAL. 3417 * @param int $from Fetch records from. 3418 * @param int $limit Number of records to fetch. 3419 * @param array $options Accepts: 3420 * - (bool) includenotapproved. When false, includes the non-approved entries created by 3421 * the current user. When true, also includes the ones that the user has the permission to approve. 3422 * @return array The first element being the recordset, the second the number of entries. 3423 * @since Moodle 3.1 3424 */ 3425 function glossary_get_entries_by_letter($glossary, $context, $letter, $from, $limit, $options = array()) { 3426 3427 $qb = new mod_glossary_entry_query_builder($glossary); 3428 if ($letter != 'ALL' && $letter != 'SPECIAL' && core_text::strlen($letter)) { 3429 $qb->filter_by_concept_letter($letter); 3430 } 3431 if ($letter == 'SPECIAL') { 3432 $qb->filter_by_concept_non_letter(); 3433 } 3434 3435 if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) { 3436 $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL); 3437 } else { 3438 $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF); 3439 } 3440 3441 $qb->add_field('*', 'entries'); 3442 $qb->join_user(); 3443 $qb->add_user_fields(); 3444 $qb->order_by('concept', 'entries'); 3445 $qb->order_by('id', 'entries', 'ASC'); // Sort on ID to avoid random ordering when entries share an ordering value. 3446 $qb->limit($from, $limit); 3447 3448 // Fetching the entries. 3449 $count = $qb->count_records(); 3450 $entries = $qb->get_records(); 3451 3452 return array($entries, $count); 3453 } 3454 3455 /** 3456 * Returns the entries of a glossary by date. 3457 * 3458 * @param object $glossary The glossary. 3459 * @param context $context The context of the glossary. 3460 * @param string $order The mode of ordering: CREATION or UPDATE. 3461 * @param string $sort The direction of the ordering: ASC or DESC. 3462 * @param int $from Fetch records from. 3463 * @param int $limit Number of records to fetch. 3464 * @param array $options Accepts: 3465 * - (bool) includenotapproved. When false, includes the non-approved entries created by 3466 * the current user. When true, also includes the ones that the user has the permission to approve. 3467 * @return array The first element being the recordset, the second the number of entries. 3468 * @since Moodle 3.1 3469 */ 3470 function glossary_get_entries_by_date($glossary, $context, $order, $sort, $from, $limit, $options = array()) { 3471 3472 $qb = new mod_glossary_entry_query_builder($glossary); 3473 if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) { 3474 $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL); 3475 } else { 3476 $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF); 3477 } 3478 3479 $qb->add_field('*', 'entries'); 3480 $qb->join_user(); 3481 $qb->add_user_fields(); 3482 $qb->limit($from, $limit); 3483 3484 if ($order == 'CREATION') { 3485 $qb->order_by('timecreated', 'entries', $sort); 3486 } else { 3487 $qb->order_by('timemodified', 'entries', $sort); 3488 } 3489 $qb->order_by('id', 'entries', $sort); // Sort on ID to avoid random ordering when entries share an ordering value. 3490 3491 // Fetching the entries. 3492 $count = $qb->count_records(); 3493 $entries = $qb->get_records(); 3494 3495 return array($entries, $count); 3496 } 3497 3498 /** 3499 * Returns the entries of a glossary by category. 3500 * 3501 * @param object $glossary The glossary. 3502 * @param context $context The context of the glossary. 3503 * @param int $categoryid The category ID, or GLOSSARY_SHOW_* constant. 3504 * @param int $from Fetch records from. 3505 * @param int $limit Number of records to fetch. 3506 * @param array $options Accepts: 3507 * - (bool) includenotapproved. When false, includes the non-approved entries created by 3508 * the current user. When true, also includes the ones that the user has the permission to approve. 3509 * @return array The first element being the recordset, the second the number of entries. 3510 * @since Moodle 3.1 3511 */ 3512 function glossary_get_entries_by_category($glossary, $context, $categoryid, $from, $limit, $options = array()) { 3513 3514 $qb = new mod_glossary_entry_query_builder($glossary); 3515 if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) { 3516 $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL); 3517 } else { 3518 $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF); 3519 } 3520 3521 $qb->join_category($categoryid); 3522 $qb->join_user(); 3523 3524 // The first field must be the relationship ID when viewing all categories. 3525 if ($categoryid === GLOSSARY_SHOW_ALL_CATEGORIES) { 3526 $qb->add_field('id', 'entries_categories', 'cid'); 3527 } 3528 3529 $qb->add_field('*', 'entries'); 3530 $qb->add_field('categoryid', 'entries_categories'); 3531 $qb->add_user_fields(); 3532 3533 if ($categoryid === GLOSSARY_SHOW_ALL_CATEGORIES) { 3534 $qb->add_field('name', 'categories', 'categoryname'); 3535 $qb->order_by('name', 'categories'); 3536 3537 } else if ($categoryid === GLOSSARY_SHOW_NOT_CATEGORISED) { 3538 $qb->where('categoryid', 'entries_categories', null); 3539 } 3540 3541 // Sort on additional fields to avoid random ordering when entries share an ordering value. 3542 $qb->order_by('concept', 'entries'); 3543 $qb->order_by('id', 'entries', 'ASC'); 3544 $qb->limit($from, $limit); 3545 3546 // Fetching the entries. 3547 $count = $qb->count_records(); 3548 $entries = $qb->get_records(); 3549 3550 return array($entries, $count); 3551 } 3552 3553 /** 3554 * Returns the entries of a glossary by author. 3555 * 3556 * @param object $glossary The glossary. 3557 * @param context $context The context of the glossary. 3558 * @param string $letter The letter 3559 * @param string $field The field to search: FIRSTNAME or LASTNAME. 3560 * @param string $sort The sorting: ASC or DESC. 3561 * @param int $from Fetch records from. 3562 * @param int $limit Number of records to fetch. 3563 * @param array $options Accepts: 3564 * - (bool) includenotapproved. When false, includes the non-approved entries created by 3565 * the current user. When true, also includes the ones that the user has the permission to approve. 3566 * @return array The first element being the recordset, the second the number of entries. 3567 * @since Moodle 3.1 3568 */ 3569 function glossary_get_entries_by_author($glossary, $context, $letter, $field, $sort, $from, $limit, $options = array()) { 3570 3571 $firstnamefirst = $field === 'FIRSTNAME'; 3572 $qb = new mod_glossary_entry_query_builder($glossary); 3573 if ($letter != 'ALL' && $letter != 'SPECIAL' && core_text::strlen($letter)) { 3574 $qb->filter_by_author_letter($letter, $firstnamefirst); 3575 } 3576 if ($letter == 'SPECIAL') { 3577 $qb->filter_by_author_non_letter($firstnamefirst); 3578 } 3579 3580 if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) { 3581 $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL); 3582 } else { 3583 $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF); 3584 } 3585 3586 $qb->add_field('*', 'entries'); 3587 $qb->join_user(true); 3588 $qb->add_user_fields(); 3589 $qb->order_by_author($firstnamefirst, $sort); 3590 $qb->order_by('concept', 'entries'); 3591 $qb->order_by('id', 'entries', 'ASC'); // Sort on ID to avoid random ordering when entries share an ordering value. 3592 $qb->limit($from, $limit); 3593 3594 // Fetching the entries. 3595 $count = $qb->count_records(); 3596 $entries = $qb->get_records(); 3597 3598 return array($entries, $count); 3599 } 3600 3601 /** 3602 * Returns the entries of a glossary by category. 3603 * 3604 * @param object $glossary The glossary. 3605 * @param context $context The context of the glossary. 3606 * @param int $authorid The author ID. 3607 * @param string $order The mode of ordering: CONCEPT, CREATION or UPDATE. 3608 * @param string $sort The direction of the ordering: ASC or DESC. 3609 * @param int $from Fetch records from. 3610 * @param int $limit Number of records to fetch. 3611 * @param array $options Accepts: 3612 * - (bool) includenotapproved. When false, includes the non-approved entries created by 3613 * the current user. When true, also includes the ones that the user has the permission to approve. 3614 * @return array The first element being the recordset, the second the number of entries. 3615 * @since Moodle 3.1 3616 */ 3617 function glossary_get_entries_by_author_id($glossary, $context, $authorid, $order, $sort, $from, $limit, $options = array()) { 3618 3619 $qb = new mod_glossary_entry_query_builder($glossary); 3620 if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) { 3621 $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL); 3622 } else { 3623 $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF); 3624 } 3625 3626 $qb->add_field('*', 'entries'); 3627 $qb->join_user(true); 3628 $qb->add_user_fields(); 3629 $qb->where('id', 'user', $authorid); 3630 3631 if ($order == 'CREATION') { 3632 $qb->order_by('timecreated', 'entries', $sort); 3633 } else if ($order == 'UPDATE') { 3634 $qb->order_by('timemodified', 'entries', $sort); 3635 } else { 3636 $qb->order_by('concept', 'entries', $sort); 3637 } 3638 $qb->order_by('id', 'entries', $sort); // Sort on ID to avoid random ordering when entries share an ordering value. 3639 3640 $qb->limit($from, $limit); 3641 3642 // Fetching the entries. 3643 $count = $qb->count_records(); 3644 $entries = $qb->get_records(); 3645 3646 return array($entries, $count); 3647 } 3648 3649 /** 3650 * Returns the authors in a glossary 3651 * 3652 * @param object $glossary The glossary. 3653 * @param context $context The context of the glossary. 3654 * @param int $limit Number of records to fetch. 3655 * @param int $from Fetch records from. 3656 * @param array $options Accepts: 3657 * - (bool) includenotapproved. When false, includes self even if all of their entries require approval. 3658 * When true, also includes authors only having entries pending approval. 3659 * @return array The first element being the recordset, the second the number of entries. 3660 * @since Moodle 3.1 3661 */ 3662 function glossary_get_authors($glossary, $context, $limit, $from, $options = array()) { 3663 global $DB, $USER; 3664 3665 $params = array(); 3666 $userfields = user_picture::fields('u', null); 3667 3668 $approvedsql = '(ge.approved <> 0 OR ge.userid = :myid)'; 3669 $params['myid'] = $USER->id; 3670 if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) { 3671 $approvedsql = '1 = 1'; 3672 } 3673 3674 $sqlselectcount = "SELECT COUNT(DISTINCT(u.id))"; 3675 $sqlselect = "SELECT DISTINCT(u.id) AS userId, $userfields"; 3676 $sql = " FROM {user} u 3677 JOIN {glossary_entries} ge 3678 ON ge.userid = u.id 3679 AND (ge.glossaryid = :gid1 OR ge.sourceglossaryid = :gid2) 3680 AND $approvedsql"; 3681 $ordersql = " ORDER BY u.lastname, u.firstname"; 3682 3683 $params['gid1'] = $glossary->id; 3684 $params['gid2'] = $glossary->id; 3685 3686 $count = $DB->count_records_sql($sqlselectcount . $sql, $params); 3687 $users = $DB->get_recordset_sql($sqlselect . $sql . $ordersql, $params, $from, $limit); 3688 3689 return array($users, $count); 3690 } 3691 3692 /** 3693 * Returns the categories of a glossary. 3694 * 3695 * @param object $glossary The glossary. 3696 * @param int $from Fetch records from. 3697 * @param int $limit Number of records to fetch. 3698 * @return array The first element being the recordset, the second the number of entries. 3699 * @since Moodle 3.1 3700 */ 3701 function glossary_get_categories($glossary, $from, $limit) { 3702 global $DB; 3703 3704 $count = $DB->count_records('glossary_categories', array('glossaryid' => $glossary->id)); 3705 $categories = $DB->get_recordset('glossary_categories', array('glossaryid' => $glossary->id), 'name ASC', '*', $from, $limit); 3706 3707 return array($categories, $count); 3708 } 3709 3710 /** 3711 * Get the SQL where clause for searching terms. 3712 * 3713 * Note that this does not handle invalid or too short terms. 3714 * 3715 * @param array $terms Array of terms. 3716 * @param bool $fullsearch Whether or not full search should be enabled. 3717 * @param int $glossaryid The ID of a glossary to reduce the search results. 3718 * @return array The first element being the where clause, the second array of parameters. 3719 * @since Moodle 3.1 3720 */ 3721 function glossary_get_search_terms_sql(array $terms, $fullsearch = true, $glossaryid = null) { 3722 global $DB; 3723 static $i = 0; 3724 3725 if ($DB->sql_regex_supported()) { 3726 $regexp = $DB->sql_regex(true); 3727 $notregexp = $DB->sql_regex(false); 3728 } 3729 3730 $params = array(); 3731 $conditions = array(); 3732 3733 foreach ($terms as $searchterm) { 3734 $i++; 3735 3736 $not = false; // Initially we aren't going to perform NOT LIKE searches, only MSSQL and Oracle 3737 // will use it to simulate the "-" operator with LIKE clause. 3738 3739 if (empty($fullsearch)) { 3740 // With fullsearch disabled, look only within concepts and aliases. 3741 $concat = $DB->sql_concat('ge.concept', "' '", "COALESCE(al.alias, :emptychar{$i})"); 3742 } else { 3743 // With fullsearch enabled, look also within definitions. 3744 $concat = $DB->sql_concat('ge.concept', "' '", 'ge.definition', "' '", "COALESCE(al.alias, :emptychar{$i})"); 3745 } 3746 $params['emptychar' . $i] = ''; 3747 3748 // Under Oracle and MSSQL, trim the + and - operators and perform simpler LIKE (or NOT LIKE) queries. 3749 if (!$DB->sql_regex_supported()) { 3750 if (substr($searchterm, 0, 1) === '-') { 3751 $not = true; 3752 } 3753 $searchterm = trim($searchterm, '+-'); 3754 } 3755 3756 if (substr($searchterm, 0, 1) === '+') { 3757 $searchterm = trim($searchterm, '+-'); 3758 $conditions[] = "$concat $regexp :searchterm{$i}"; 3759 $params['searchterm' . $i] = '(^|[^a-zA-Z0-9])' . preg_quote($searchterm, '|') . '([^a-zA-Z0-9]|$)'; 3760 3761 } else if (substr($searchterm, 0, 1) === "-") { 3762 $searchterm = trim($searchterm, '+-'); 3763 $conditions[] = "$concat $notregexp :searchterm{$i}"; 3764 $params['searchterm' . $i] = '(^|[^a-zA-Z0-9])' . preg_quote($searchterm, '|') . '([^a-zA-Z0-9]|$)'; 3765 3766 } else { 3767 $conditions[] = $DB->sql_like($concat, ":searchterm{$i}", false, true, $not); 3768 $params['searchterm' . $i] = '%' . $DB->sql_like_escape($searchterm) . '%'; 3769 } 3770 } 3771 3772 // Reduce the search results by restricting it to one glossary. 3773 if (isset($glossaryid)) { 3774 $conditions[] = 'ge.glossaryid = :glossaryid'; 3775 $params['glossaryid'] = $glossaryid; 3776 } 3777 3778 // When there are no conditions we add a negative one to ensure that we don't return anything. 3779 if (empty($conditions)) { 3780 $conditions[] = '1 = 2'; 3781 } 3782 3783 $where = implode(' AND ', $conditions); 3784 return array($where, $params); 3785 } 3786 3787 3788 /** 3789 * Returns the entries of a glossary by search. 3790 * 3791 * @param object $glossary The glossary. 3792 * @param context $context The context of the glossary. 3793 * @param string $query The search query. 3794 * @param bool $fullsearch Whether or not full search is required. 3795 * @param string $order The mode of ordering: CONCEPT, CREATION or UPDATE. 3796 * @param string $sort The direction of the ordering: ASC or DESC. 3797 * @param int $from Fetch records from. 3798 * @param int $limit Number of records to fetch. 3799 * @param array $options Accepts: 3800 * - (bool) includenotapproved. When false, includes the non-approved entries created by 3801 * the current user. When true, also includes the ones that the user has the permission to approve. 3802 * @return array The first element being the array of results, the second the number of entries. 3803 * @since Moodle 3.1 3804 */ 3805 function glossary_get_entries_by_search($glossary, $context, $query, $fullsearch, $order, $sort, $from, $limit, 3806 $options = array()) { 3807 global $DB, $USER; 3808 3809 // Clean terms. 3810 $terms = explode(' ', $query); 3811 foreach ($terms as $key => $term) { 3812 if (strlen(trim($term, '+-')) < 1) { 3813 unset($terms[$key]); 3814 } 3815 } 3816 3817 list($searchcond, $params) = glossary_get_search_terms_sql($terms, $fullsearch, $glossary->id); 3818 3819 $userfields = user_picture::fields('u', null, 'userdataid', 'userdata'); 3820 3821 // Need one inner view here to avoid distinct + text. 3822 $sqlwrapheader = 'SELECT ge.*, ge.concept AS glossarypivot, ' . $userfields . ' 3823 FROM {glossary_entries} ge 3824 LEFT JOIN {user} u ON u.id = ge.userid 3825 JOIN ( '; 3826 $sqlwrapfooter = ' ) gei ON (ge.id = gei.id)'; 3827 $sqlselect = "SELECT DISTINCT ge.id"; 3828 $sqlfrom = "FROM {glossary_entries} ge 3829 LEFT JOIN {glossary_alias} al ON al.entryid = ge.id"; 3830 3831 if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) { 3832 $approvedsql = ''; 3833 } else { 3834 $approvedsql = 'AND (ge.approved <> 0 OR ge.userid = :myid)'; 3835 $params['myid'] = $USER->id; 3836 } 3837 3838 if ($order == 'CREATION') { 3839 $sqlorderby = "ORDER BY ge.timecreated $sort"; 3840 } else if ($order == 'UPDATE') { 3841 $sqlorderby = "ORDER BY ge.timemodified $sort"; 3842 } else { 3843 $sqlorderby = "ORDER BY ge.concept $sort"; 3844 } 3845 $sqlorderby .= " , ge.id ASC"; // Sort on ID to avoid random ordering when entries share an ordering value. 3846 3847 $sqlwhere = "WHERE ($searchcond) $approvedsql"; 3848 3849 // Fetching the entries. 3850 $count = $DB->count_records_sql("SELECT COUNT(DISTINCT(ge.id)) $sqlfrom $sqlwhere", $params); 3851 3852 $query = "$sqlwrapheader $sqlselect $sqlfrom $sqlwhere $sqlwrapfooter $sqlorderby"; 3853 $entries = $DB->get_records_sql($query, $params, $from, $limit); 3854 3855 return array($entries, $count); 3856 } 3857 3858 /** 3859 * Returns the entries of a glossary by term. 3860 * 3861 * @param object $glossary The glossary. 3862 * @param context $context The context of the glossary. 3863 * @param string $term The term we are searching for, a concept or alias. 3864 * @param int $from Fetch records from. 3865 * @param int $limit Number of records to fetch. 3866 * @param array $options Accepts: 3867 * - (bool) includenotapproved. When false, includes the non-approved entries created by 3868 * the current user. When true, also includes the ones that the user has the permission to approve. 3869 * @return array The first element being the recordset, the second the number of entries. 3870 * @since Moodle 3.1 3871 */ 3872 function glossary_get_entries_by_term($glossary, $context, $term, $from, $limit, $options = array()) { 3873 3874 // Build the query. 3875 $qb = new mod_glossary_entry_query_builder($glossary); 3876 if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) { 3877 $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL); 3878 } else { 3879 $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF); 3880 } 3881 3882 $qb->add_field('*', 'entries'); 3883 $qb->join_alias(); 3884 $qb->join_user(); 3885 $qb->add_user_fields(); 3886 $qb->filter_by_term($term); 3887 3888 $qb->order_by('concept', 'entries'); 3889 $qb->order_by('id', 'entries'); // Sort on ID to avoid random ordering when entries share an ordering value. 3890 $qb->limit($from, $limit); 3891 3892 // Fetching the entries. 3893 $count = $qb->count_records(); 3894 $entries = $qb->get_records(); 3895 3896 return array($entries, $count); 3897 } 3898 3899 /** 3900 * Returns the entries to be approved. 3901 * 3902 * @param object $glossary The glossary. 3903 * @param context $context The context of the glossary. 3904 * @param string $letter The letter, or ALL, or SPECIAL. 3905 * @param string $order The mode of ordering: CONCEPT, CREATION or UPDATE. 3906 * @param string $sort The direction of the ordering: ASC or DESC. 3907 * @param int $from Fetch records from. 3908 * @param int $limit Number of records to fetch. 3909 * @return array The first element being the recordset, the second the number of entries. 3910 * @since Moodle 3.1 3911 */ 3912 function glossary_get_entries_to_approve($glossary, $context, $letter, $order, $sort, $from, $limit) { 3913 3914 $qb = new mod_glossary_entry_query_builder($glossary); 3915 if ($letter != 'ALL' && $letter != 'SPECIAL' && core_text::strlen($letter)) { 3916 $qb->filter_by_concept_letter($letter); 3917 } 3918 if ($letter == 'SPECIAL') { 3919 $qb->filter_by_concept_non_letter(); 3920 } 3921 3922 $qb->add_field('*', 'entries'); 3923 $qb->join_user(); 3924 $qb->add_user_fields(); 3925 $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ONLY); 3926 if ($order == 'CREATION') { 3927 $qb->order_by('timecreated', 'entries', $sort); 3928 } else if ($order == 'UPDATE') { 3929 $qb->order_by('timemodified', 'entries', $sort); 3930 } else { 3931 $qb->order_by('concept', 'entries', $sort); 3932 } 3933 $qb->order_by('id', 'entries', $sort); // Sort on ID to avoid random ordering when entries share an ordering value. 3934 $qb->limit($from, $limit); 3935 3936 // Fetching the entries. 3937 $count = $qb->count_records(); 3938 $entries = $qb->get_records(); 3939 3940 return array($entries, $count); 3941 } 3942 3943 /** 3944 * Fetch an entry. 3945 * 3946 * @param int $id The entry ID. 3947 * @return object|false The entry, or false when not found. 3948 * @since Moodle 3.1 3949 */ 3950 function glossary_get_entry_by_id($id) { 3951 3952 // Build the query. 3953 $qb = new mod_glossary_entry_query_builder(); 3954 $qb->add_field('*', 'entries'); 3955 $qb->join_user(); 3956 $qb->add_user_fields(); 3957 $qb->where('id', 'entries', $id); 3958 3959 // Fetching the entries. 3960 $entries = $qb->get_records(); 3961 if (empty($entries)) { 3962 return false; 3963 } 3964 return array_pop($entries); 3965 } 3966 3967 /** 3968 * Checks if the current user can see the glossary entry. 3969 * 3970 * @since Moodle 3.1 3971 * @param stdClass $entry 3972 * @param cm_info $cminfo 3973 * @return bool 3974 */ 3975 function glossary_can_view_entry($entry, $cminfo) { 3976 global $USER; 3977 3978 $cm = $cminfo->get_course_module_record(); 3979 $context = \context_module::instance($cm->id); 3980 3981 // Recheck uservisible although it should have already been checked in core_search. 3982 if ($cminfo->uservisible === false) { 3983 return false; 3984 } 3985 3986 // Check approval. 3987 if (empty($entry->approved) && $entry->userid != $USER->id && !has_capability('mod/glossary:approve', $context)) { 3988 return false; 3989 } 3990 3991 return true; 3992 } 3993 3994 /** 3995 * Check if a concept exists in a glossary. 3996 * 3997 * @param stdClass $glossary glossary object 3998 * @param string $concept the concept to check 3999 * @return bool true if exists 4000 * @since Moodle 3.2 4001 */ 4002 function glossary_concept_exists($glossary, $concept) { 4003 global $DB; 4004 4005 return $DB->record_exists_select('glossary_entries', 'glossaryid = :glossaryid AND LOWER(concept) = :concept', 4006 array( 4007 'glossaryid' => $glossary->id, 4008 'concept' => core_text::strtolower($concept) 4009 ) 4010 ); 4011 } 4012 4013 /** 4014 * Return the editor and attachment options when editing a glossary entry 4015 * 4016 * @param stdClass $course course object 4017 * @param stdClass $context context object 4018 * @param stdClass $entry entry object 4019 * @return array array containing the editor and attachment options 4020 * @since Moodle 3.2 4021 */ 4022 function glossary_get_editor_and_attachment_options($course, $context, $entry) { 4023 $maxfiles = 99; // TODO: add some setting. 4024 $maxbytes = $course->maxbytes; // TODO: add some setting. 4025 4026 $definitionoptions = array('trusttext' => true, 'maxfiles' => $maxfiles, 'maxbytes' => $maxbytes, 'context' => $context, 4027 'subdirs' => file_area_contains_subdirs($context, 'mod_glossary', 'entry', $entry->id)); 4028 $attachmentoptions = array('subdirs' => false, 'maxfiles' => $maxfiles, 'maxbytes' => $maxbytes); 4029 return array($definitionoptions, $attachmentoptions); 4030 } 4031 4032 /** 4033 * Creates or updates a glossary entry 4034 * 4035 * @param stdClass $entry entry data 4036 * @param stdClass $course course object 4037 * @param stdClass $cm course module object 4038 * @param stdClass $glossary glossary object 4039 * @param stdClass $context context object 4040 * @return stdClass the complete new or updated entry 4041 * @since Moodle 3.2 4042 */ 4043 function glossary_edit_entry($entry, $course, $cm, $glossary, $context) { 4044 global $DB, $USER; 4045 4046 list($definitionoptions, $attachmentoptions) = glossary_get_editor_and_attachment_options($course, $context, $entry); 4047 4048 $timenow = time(); 4049 4050 $categories = empty($entry->categories) ? array() : $entry->categories; 4051 unset($entry->categories); 4052 $aliases = trim($entry->aliases); 4053 unset($entry->aliases); 4054 4055 if (empty($entry->id)) { 4056 $entry->glossaryid = $glossary->id; 4057 $entry->timecreated = $timenow; 4058 $entry->userid = $USER->id; 4059 $entry->timecreated = $timenow; 4060 $entry->sourceglossaryid = 0; 4061 $entry->teacherentry = has_capability('mod/glossary:manageentries', $context); 4062 $isnewentry = true; 4063 } else { 4064 $isnewentry = false; 4065 } 4066 4067 $entry->concept = trim($entry->concept); 4068 $entry->definition = ''; // Updated later. 4069 $entry->definitionformat = FORMAT_HTML; // Updated later. 4070 $entry->definitiontrust = 0; // Updated later. 4071 $entry->timemodified = $timenow; 4072 $entry->approved = 0; 4073 $entry->usedynalink = isset($entry->usedynalink) ? $entry->usedynalink : 0; 4074 $entry->casesensitive = isset($entry->casesensitive) ? $entry->casesensitive : 0; 4075 $entry->fullmatch = isset($entry->fullmatch) ? $entry->fullmatch : 0; 4076 4077 if ($glossary->defaultapproval or has_capability('mod/glossary:approve', $context)) { 4078 $entry->approved = 1; 4079 } 4080 4081 if ($isnewentry) { 4082 // Add new entry. 4083 $entry->id = $DB->insert_record('glossary_entries', $entry); 4084 } else { 4085 // Update existing entry. 4086 $DB->update_record('glossary_entries', $entry); 4087 } 4088 4089 // Save and relink embedded images and save attachments. 4090 if (!empty($entry->definition_editor)) { 4091 $entry = file_postupdate_standard_editor($entry, 'definition', $definitionoptions, $context, 'mod_glossary', 'entry', 4092 $entry->id); 4093 } 4094 if (!empty($entry->attachment_filemanager)) { 4095 $entry = file_postupdate_standard_filemanager($entry, 'attachment', $attachmentoptions, $context, 'mod_glossary', 4096 'attachment', $entry->id); 4097 } 4098 4099 // Store the updated value values. 4100 $DB->update_record('glossary_entries', $entry); 4101 4102 // Refetch complete entry. 4103 $entry = $DB->get_record('glossary_entries', array('id' => $entry->id)); 4104 4105 // Update entry categories. 4106 $DB->delete_records('glossary_entries_categories', array('entryid' => $entry->id)); 4107 // TODO: this deletes cats from both both main and secondary glossary :-(. 4108 if (!empty($categories) and array_search(0, $categories) === false) { 4109 foreach ($categories as $catid) { 4110 $newcategory = new stdClass(); 4111 $newcategory->entryid = $entry->id; 4112 $newcategory->categoryid = $catid; 4113 $DB->insert_record('glossary_entries_categories', $newcategory, false); 4114 } 4115 } 4116 4117 // Update aliases. 4118 $DB->delete_records('glossary_alias', array('entryid' => $entry->id)); 4119 if ($aliases !== '') { 4120 $aliases = explode("\n", $aliases); 4121 foreach ($aliases as $alias) { 4122 $alias = trim($alias); 4123 if ($alias !== '') { 4124 $newalias = new stdClass(); 4125 $newalias->entryid = $entry->id; 4126 $newalias->alias = $alias; 4127 $DB->insert_record('glossary_alias', $newalias, false); 4128 } 4129 } 4130 } 4131 4132 // Trigger event and update completion (if entry was created). 4133 $eventparams = array( 4134 'context' => $context, 4135 'objectid' => $entry->id, 4136 'other' => array('concept' => $entry->concept) 4137 ); 4138 if ($isnewentry) { 4139 $event = \mod_glossary\event\entry_created::create($eventparams); 4140 } else { 4141 $event = \mod_glossary\event\entry_updated::create($eventparams); 4142 } 4143 $event->add_record_snapshot('glossary_entries', $entry); 4144 $event->trigger(); 4145 if ($isnewentry) { 4146 // Update completion state. 4147 $completion = new completion_info($course); 4148 if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC && $glossary->completionentries && $entry->approved) { 4149 $completion->update_state($cm, COMPLETION_COMPLETE); 4150 } 4151 } 4152 4153 // Reset caches. 4154 if ($isnewentry) { 4155 if ($entry->usedynalink and $entry->approved) { 4156 \mod_glossary\local\concept_cache::reset_glossary($glossary); 4157 } 4158 } else { 4159 // So many things may affect the linking, let's just purge the cache always on edit. 4160 \mod_glossary\local\concept_cache::reset_glossary($glossary); 4161 } 4162 return $entry; 4163 } 4164 4165 /** 4166 * Check if the module has any update that affects the current user since a given time. 4167 * 4168 * @param cm_info $cm course module data 4169 * @param int $from the time to check updates from 4170 * @param array $filter if we need to check only specific updates 4171 * @return stdClass an object with the different type of areas indicating if they were updated or not 4172 * @since Moodle 3.2 4173 */ 4174 function glossary_check_updates_since(cm_info $cm, $from, $filter = array()) { 4175 global $DB; 4176 4177 $updates = course_check_module_updates_since($cm, $from, array('attachment', 'entry'), $filter); 4178 4179 $updates->entries = (object) array('updated' => false); 4180 $select = 'glossaryid = :id AND (timecreated > :since1 OR timemodified > :since2)'; 4181 $params = array('id' => $cm->instance, 'since1' => $from, 'since2' => $from); 4182 if (!has_capability('mod/glossary:approve', $cm->context)) { 4183 $select .= ' AND approved = 1'; 4184 } 4185 4186 $entries = $DB->get_records_select('glossary_entries', $select, $params, '', 'id'); 4187 if (!empty($entries)) { 4188 $updates->entries->updated = true; 4189 $updates->entries->itemids = array_keys($entries); 4190 } 4191 4192 return $updates; 4193 } 4194 4195 /** 4196 * Get icon mapping for font-awesome. 4197 * 4198 * @return array 4199 */ 4200 function mod_glossary_get_fontawesome_icon_map() { 4201 return [ 4202 'mod_glossary:export' => 'fa-download', 4203 'mod_glossary:minus' => 'fa-minus' 4204 ]; 4205 } 4206 4207 /** 4208 * This function receives a calendar event and returns the action associated with it, or null if there is none. 4209 * 4210 * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event 4211 * is not displayed on the block. 4212 * 4213 * @param calendar_event $event 4214 * @param \core_calendar\action_factory $factory 4215 * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default). 4216 * @return \core_calendar\local\event\entities\action_interface|null 4217 */ 4218 function mod_glossary_core_calendar_provide_event_action(calendar_event $event, 4219 \core_calendar\action_factory $factory, 4220 int $userid = 0) { 4221 global $USER; 4222 4223 if (!$userid) { 4224 $userid = $USER->id; 4225 } 4226 4227 $cm = get_fast_modinfo($event->courseid, $userid)->instances['glossary'][$event->instance]; 4228 4229 if (!$cm->uservisible) { 4230 // The module is not visible to the user for any reason. 4231 return null; 4232 } 4233 4234 $completion = new \completion_info($cm->get_course()); 4235 4236 $completiondata = $completion->get_data($cm, false, $userid); 4237 4238 if ($completiondata->completionstate != COMPLETION_INCOMPLETE) { 4239 return null; 4240 } 4241 4242 return $factory->create_instance( 4243 get_string('view'), 4244 new \moodle_url('/mod/glossary/view.php', ['id' => $cm->id]), 4245 1, 4246 true 4247 ); 4248 } 4249 4250 /** 4251 * Add a get_coursemodule_info function in case any glossary type wants to add 'extra' information 4252 * for the course (see resource). 4253 * 4254 * Given a course_module object, this function returns any "extra" information that may be needed 4255 * when printing this activity in a course listing. See get_array_of_activities() in course/lib.php. 4256 * 4257 * @param stdClass $coursemodule The coursemodule object (record). 4258 * @return cached_cm_info An object on information that the courses 4259 * will know about (most noticeably, an icon). 4260 */ 4261 function glossary_get_coursemodule_info($coursemodule) { 4262 global $DB; 4263 4264 $dbparams = ['id' => $coursemodule->instance]; 4265 $fields = 'id, name, intro, introformat, completionentries'; 4266 if (!$glossary = $DB->get_record('glossary', $dbparams, $fields)) { 4267 return false; 4268 } 4269 4270 $result = new cached_cm_info(); 4271 $result->name = $glossary->name; 4272 4273 if ($coursemodule->showdescription) { 4274 // Convert intro to html. Do not filter cached version, filters run at display time. 4275 $result->content = format_module_intro('glossary', $glossary, $coursemodule->id, false); 4276 } 4277 4278 // Populate the custom completion rules as key => value pairs, but only if the completion mode is 'automatic'. 4279 if ($coursemodule->completion == COMPLETION_TRACKING_AUTOMATIC) { 4280 $result->customdata['customcompletionrules']['completionentries'] = $glossary->completionentries; 4281 } 4282 4283 return $result; 4284 } 4285 4286 /** 4287 * Callback which returns human-readable strings describing the active completion custom rules for the module instance. 4288 * 4289 * @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules'] 4290 * @return array $descriptions the array of descriptions for the custom rules. 4291 */ 4292 function mod_glossary_get_completion_active_rule_descriptions($cm) { 4293 // Values will be present in cm_info, and we assume these are up to date. 4294 if (empty($cm->customdata['customcompletionrules']) 4295 || $cm->completion != COMPLETION_TRACKING_AUTOMATIC) { 4296 return []; 4297 } 4298 4299 $descriptions = []; 4300 foreach ($cm->customdata['customcompletionrules'] as $key => $val) { 4301 switch ($key) { 4302 case 'completionentries': 4303 if (!empty($val)) { 4304 $descriptions[] = get_string('completionentriesdesc', 'glossary', $val); 4305 } 4306 break; 4307 default: 4308 break; 4309 } 4310 } 4311 return $descriptions; 4312 } 4313 4314 /** 4315 * Checks if the current user can delete the given glossary entry. 4316 * 4317 * @since Moodle 3.10 4318 * @param stdClass $entry the entry database object 4319 * @param stdClass $glossary the glossary database object 4320 * @param stdClass $context the glossary context 4321 * @param bool $return Whether to return a boolean value or stop the execution (exception) 4322 * @return bool if the user can delete the entry 4323 * @throws moodle_exception 4324 */ 4325 function mod_glossary_can_delete_entry($entry, $glossary, $context, $return = true) { 4326 global $USER, $CFG; 4327 4328 $manageentries = has_capability('mod/glossary:manageentries', $context); 4329 4330 if ($manageentries) { // Users with the capability will always be able to delete entries. 4331 return true; 4332 } 4333 4334 if ($entry->userid != $USER->id) { // Guest id is never matched, no need for special check here. 4335 if ($return) { 4336 return false; 4337 } 4338 throw new moodle_exception('nopermissiontodelentry'); 4339 } 4340 4341 $ineditperiod = ((time() - $entry->timecreated < $CFG->maxeditingtime) || $glossary->editalways); 4342 4343 if (!$ineditperiod) { 4344 if ($return) { 4345 return false; 4346 } 4347 throw new moodle_exception('errdeltimeexpired', 'glossary'); 4348 } 4349 4350 return true; 4351 } 4352 4353 /** 4354 * Deletes the given entry, this function does not perform capabilities/permission checks. 4355 * 4356 * @since Moodle 3.10 4357 * @param stdClass $entry the entry database object 4358 * @param stdClass $glossary the glossary database object 4359 * @param stdClass $cm the glossary course moduule object 4360 * @param stdClass $context the glossary context 4361 * @param stdClass $course the glossary course 4362 * @param string $hook the hook, usually type of filtering, value 4363 * @param string $prevmode the previsualisation mode 4364 * @throws moodle_exception 4365 */ 4366 function mod_glossary_delete_entry($entry, $glossary, $cm, $context, $course, $hook = '', $prevmode = '') { 4367 global $CFG, $DB; 4368 4369 $origentry = fullclone($entry); 4370 4371 // If it is an imported entry, just delete the relation. 4372 if ($entry->sourceglossaryid) { 4373 if (!$newcm = get_coursemodule_from_instance('glossary', $entry->sourceglossaryid)) { 4374 print_error('invalidcoursemodule'); 4375 } 4376 $newcontext = context_module::instance($newcm->id); 4377 4378 $entry->glossaryid = $entry->sourceglossaryid; 4379 $entry->sourceglossaryid = 0; 4380 $DB->update_record('glossary_entries', $entry); 4381 4382 // Move attachments too. 4383 $fs = get_file_storage(); 4384 4385 if ($oldfiles = $fs->get_area_files($context->id, 'mod_glossary', 'attachment', $entry->id)) { 4386 foreach ($oldfiles as $oldfile) { 4387 $filerecord = new stdClass(); 4388 $filerecord->contextid = $newcontext->id; 4389 $fs->create_file_from_storedfile($filerecord, $oldfile); 4390 } 4391 $fs->delete_area_files($context->id, 'mod_glossary', 'attachment', $entry->id); 4392 $entry->attachment = '1'; 4393 } else { 4394 $entry->attachment = '0'; 4395 } 4396 $DB->update_record('glossary_entries', $entry); 4397 4398 } else { 4399 $fs = get_file_storage(); 4400 $fs->delete_area_files($context->id, 'mod_glossary', 'attachment', $entry->id); 4401 $DB->delete_records("comments", 4402 ['itemid' => $entry->id, 'commentarea' => 'glossary_entry', 'contextid' => $context->id]); 4403 $DB->delete_records("glossary_alias", ["entryid" => $entry->id]); 4404 $DB->delete_records("glossary_entries", ["id" => $entry->id]); 4405 4406 // Update completion state. 4407 $completion = new completion_info($course); 4408 if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC && $glossary->completionentries) { 4409 $completion->update_state($cm, COMPLETION_INCOMPLETE, $entry->userid); 4410 } 4411 4412 // Delete glossary entry ratings. 4413 require_once($CFG->dirroot.'/rating/lib.php'); 4414 $delopt = new stdClass; 4415 $delopt->contextid = $context->id; 4416 $delopt->component = 'mod_glossary'; 4417 $delopt->ratingarea = 'entry'; 4418 $delopt->itemid = $entry->id; 4419 $rm = new rating_manager(); 4420 $rm->delete_ratings($delopt); 4421 } 4422 4423 // Delete cached RSS feeds. 4424 if (!empty($CFG->enablerssfeeds)) { 4425 require_once($CFG->dirroot . '/mod/glossary/rsslib.php'); 4426 glossary_rss_delete_file($glossary); 4427 } 4428 4429 core_tag_tag::remove_all_item_tags('mod_glossary', 'glossary_entries', $origentry->id); 4430 4431 $event = \mod_glossary\event\entry_deleted::create( 4432 [ 4433 'context' => $context, 4434 'objectid' => $origentry->id, 4435 'other' => [ 4436 'mode' => $prevmode, 4437 'hook' => $hook, 4438 'concept' => $origentry->concept 4439 ] 4440 ] 4441 ); 4442 $event->add_record_snapshot('glossary_entries', $origentry); 4443 $event->trigger(); 4444 4445 // Reset caches. 4446 if ($entry->usedynalink and $entry->approved) { 4447 \mod_glossary\local\concept_cache::reset_glossary($glossary); 4448 } 4449 } 4450 4451 /** 4452 * Checks if the current user can update the given glossary entry. 4453 * 4454 * @since Moodle 3.10 4455 * @param stdClass $entry the entry database object 4456 * @param stdClass $glossary the glossary database object 4457 * @param stdClass $context the glossary context 4458 * @param object $cm the course module object (cm record or cm_info instance) 4459 * @param bool $return Whether to return a boolean value or stop the execution (exception) 4460 * @return bool if the user can update the entry 4461 * @throws moodle_exception 4462 */ 4463 function mod_glossary_can_update_entry(stdClass $entry, stdClass $glossary, stdClass $context, object $cm, 4464 bool $return = true): bool { 4465 4466 global $USER, $CFG; 4467 4468 $ineditperiod = ((time() - $entry->timecreated < $CFG->maxeditingtime) || $glossary->editalways); 4469 if (!has_capability('mod/glossary:manageentries', $context) and 4470 !($entry->userid == $USER->id and ($ineditperiod and has_capability('mod/glossary:write', $context)))) { 4471 4472 if ($USER->id != $entry->userid) { 4473 if ($return) { 4474 return false; 4475 } 4476 throw new moodle_exception('errcannoteditothers', 'glossary', "view.php?id=$cm->id&mode=entry&hook=$entry->id"); 4477 } else if (!$ineditperiod) { 4478 if ($return) { 4479 return false; 4480 } 4481 throw new moodle_exception('erredittimeexpired', 'glossary', "view.php?id=$cm->id&mode=entry&hook=$entry->id"); 4482 } 4483 } 4484 4485 return true; 4486 } 4487 4488 /** 4489 * Prepares an entry for editing, adding aliases and category information. 4490 * 4491 * @param stdClass $entry the entry being edited 4492 * @return stdClass the entry with the additional data 4493 */ 4494 function mod_glossary_prepare_entry_for_edition(stdClass $entry): stdClass { 4495 global $DB; 4496 4497 if ($aliases = $DB->get_records_menu("glossary_alias", ["entryid" => $entry->id], '', 'id, alias')) { 4498 $entry->aliases = implode("\n", $aliases) . "\n"; 4499 } 4500 if ($categoriesarr = $DB->get_records_menu("glossary_entries_categories", ['entryid' => $entry->id], '', 'id, categoryid')) { 4501 // TODO: this fetches cats from both main and secondary glossary :-( 4502 $entry->categories = array_values($categoriesarr); 4503 } 4504 4505 return $entry; 4506 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body