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