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