Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 37 and 311] [Versions 38 and 311] [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 * @return void 1358 */ 1359 function glossary_print_entry_lower_section($course, $cm, $glossary, $entry, $mode, $hook, $printicons, $aliases=true) { 1360 if ($aliases) { 1361 $aliases = glossary_print_entry_aliases($course, $cm, $glossary, $entry, $mode, $hook,'html'); 1362 } 1363 $icons = ''; 1364 if ($printicons) { 1365 $icons = glossary_print_entry_icons($course, $cm, $glossary, $entry, $mode, $hook,'html'); 1366 } 1367 if ($aliases || $icons || !empty($entry->rating)) { 1368 echo '<table>'; 1369 if ( $aliases ) { 1370 $id = "keyword-{$entry->id}"; 1371 echo '<tr valign="top"><td class="aliases">' . 1372 '<label for="' . $id . '">' . get_string('aliases', 'glossary') . ': </label>' . 1373 $aliases . '</td></tr>'; 1374 } 1375 if ($icons) { 1376 echo '<tr valign="top"><td class="icons">'.$icons.'</td></tr>'; 1377 } 1378 if (!empty($entry->rating)) { 1379 echo '<tr valign="top"><td class="ratings pt-3">'; 1380 glossary_print_entry_ratings($course, $entry); 1381 echo '</td></tr>'; 1382 } 1383 echo '</table>'; 1384 echo "<hr>\n"; 1385 } 1386 } 1387 1388 /** 1389 * Print the list of attachments for this glossary entry 1390 * 1391 * @param object $entry 1392 * @param object $cm The coursemodule 1393 * @param string $format The format for this view (html, or text) 1394 * @param string $unused1 This parameter is no longer used 1395 * @param string $unused2 This parameter is no longer used 1396 */ 1397 function glossary_print_entry_attachment($entry, $cm, $format = null, $unused1 = null, $unused2 = null) { 1398 // Valid format values: html: The HTML link for the attachment is an icon; and 1399 // text: The HTML link for the attachment is text. 1400 if ($entry->attachment) { 1401 echo '<div class="attachments">'; 1402 echo glossary_print_attachments($entry, $cm, $format); 1403 echo '</div>'; 1404 } 1405 if ($unused1) { 1406 debugging('The align parameter is deprecated, please use appropriate CSS instead', DEBUG_DEVELOPER); 1407 } 1408 if ($unused2 !== null) { 1409 debugging('The insidetable parameter is deprecated, please use appropriate CSS instead', DEBUG_DEVELOPER); 1410 } 1411 } 1412 1413 /** 1414 * @global object 1415 * @param object $cm 1416 * @param object $entry 1417 * @param string $mode 1418 * @param string $align 1419 * @param bool $insidetable 1420 */ 1421 function glossary_print_entry_approval($cm, $entry, $mode, $align="right", $insidetable=true) { 1422 global $CFG, $OUTPUT; 1423 1424 if ($mode == 'approval' and !$entry->approved) { 1425 if ($insidetable) { 1426 echo '<table class="glossaryapproval" align="'.$align.'"><tr><td align="'.$align.'">'; 1427 } 1428 echo $OUTPUT->action_icon( 1429 new moodle_url('approve.php', array('eid' => $entry->id, 'mode' => $mode, 'sesskey' => sesskey())), 1430 new pix_icon('t/approve', get_string('approve','glossary'), '', 1431 array('class' => 'iconsmall', 'align' => $align)) 1432 ); 1433 if ($insidetable) { 1434 echo '</td></tr></table>'; 1435 } 1436 } 1437 } 1438 1439 /** 1440 * It returns all entries from all glossaries that matches the specified criteria 1441 * within a given $course. It performs an $extended search if necessary. 1442 * It restrict the search to only one $glossary if the $glossary parameter is set. 1443 * 1444 * @global object 1445 * @global object 1446 * @param object $course 1447 * @param array $searchterms 1448 * @param int $extended 1449 * @param object $glossary 1450 * @return array 1451 */ 1452 function glossary_search($course, $searchterms, $extended = 0, $glossary = NULL) { 1453 global $CFG, $DB; 1454 1455 if ( !$glossary ) { 1456 if ( $glossaries = $DB->get_records("glossary", array("course"=>$course->id)) ) { 1457 $glos = ""; 1458 foreach ( $glossaries as $glossary ) { 1459 $glos .= "$glossary->id,"; 1460 } 1461 $glos = substr($glos,0,-1); 1462 } 1463 } else { 1464 $glos = $glossary->id; 1465 } 1466 1467 if (!has_capability('mod/glossary:manageentries', context_course::instance($glossary->course))) { 1468 $glossarymodule = $DB->get_record("modules", array("name"=>"glossary")); 1469 $onlyvisible = " AND g.id = cm.instance AND cm.visible = 1 AND cm.module = $glossarymodule->id"; 1470 $onlyvisibletable = ", {course_modules} cm"; 1471 } else { 1472 1473 $onlyvisible = ""; 1474 $onlyvisibletable = ""; 1475 } 1476 1477 if ($DB->sql_regex_supported()) { 1478 $REGEXP = $DB->sql_regex(true); 1479 $NOTREGEXP = $DB->sql_regex(false); 1480 } 1481 1482 $searchcond = array(); 1483 $params = array(); 1484 $i = 0; 1485 1486 $concat = $DB->sql_concat('e.concept', "' '", 'e.definition'); 1487 1488 1489 foreach ($searchterms as $searchterm) { 1490 $i++; 1491 1492 $NOT = false; /// Initially we aren't going to perform NOT LIKE searches, only MSSQL and Oracle 1493 /// will use it to simulate the "-" operator with LIKE clause 1494 1495 /// Under Oracle and MSSQL, trim the + and - operators and perform 1496 /// simpler LIKE (or NOT LIKE) queries 1497 if (!$DB->sql_regex_supported()) { 1498 if (substr($searchterm, 0, 1) == '-') { 1499 $NOT = true; 1500 } 1501 $searchterm = trim($searchterm, '+-'); 1502 } 1503 1504 // TODO: +- may not work for non latin languages 1505 1506 if (substr($searchterm,0,1) == '+') { 1507 $searchterm = trim($searchterm, '+-'); 1508 $searchterm = preg_quote($searchterm, '|'); 1509 $searchcond[] = "$concat $REGEXP :ss$i"; 1510 $params['ss'.$i] = "(^|[^a-zA-Z0-9])$searchterm([^a-zA-Z0-9]|$)"; 1511 1512 } else if (substr($searchterm,0,1) == "-") { 1513 $searchterm = trim($searchterm, '+-'); 1514 $searchterm = preg_quote($searchterm, '|'); 1515 $searchcond[] = "$concat $NOTREGEXP :ss$i"; 1516 $params['ss'.$i] = "(^|[^a-zA-Z0-9])$searchterm([^a-zA-Z0-9]|$)"; 1517 1518 } else { 1519 $searchcond[] = $DB->sql_like($concat, ":ss$i", false, true, $NOT); 1520 $params['ss'.$i] = "%$searchterm%"; 1521 } 1522 } 1523 1524 if (empty($searchcond)) { 1525 $totalcount = 0; 1526 return array(); 1527 } 1528 1529 $searchcond = implode(" AND ", $searchcond); 1530 1531 $sql = "SELECT e.* 1532 FROM {glossary_entries} e, {glossary} g $onlyvisibletable 1533 WHERE $searchcond 1534 AND (e.glossaryid = g.id or e.sourceglossaryid = g.id) $onlyvisible 1535 AND g.id IN ($glos) AND e.approved <> 0"; 1536 1537 return $DB->get_records_sql($sql, $params); 1538 } 1539 1540 /** 1541 * @global object 1542 * @param array $searchterms 1543 * @param object $glossary 1544 * @param bool $extended 1545 * @return array 1546 */ 1547 function glossary_search_entries($searchterms, $glossary, $extended) { 1548 global $DB; 1549 1550 $course = $DB->get_record("course", array("id"=>$glossary->course)); 1551 return glossary_search($course,$searchterms,$extended,$glossary); 1552 } 1553 1554 /** 1555 * if return=html, then return a html string. 1556 * if return=text, then return a text-only string. 1557 * otherwise, print HTML for non-images, and return image HTML 1558 * if attachment is an image, $align set its aligment. 1559 * 1560 * @global object 1561 * @global object 1562 * @param object $entry 1563 * @param object $cm 1564 * @param string $type html, txt, empty 1565 * @param string $unused This parameter is no longer used 1566 * @return string image string or nothing depending on $type param 1567 */ 1568 function glossary_print_attachments($entry, $cm, $type=NULL, $unused = null) { 1569 global $CFG, $DB, $OUTPUT; 1570 1571 if (!$context = context_module::instance($cm->id, IGNORE_MISSING)) { 1572 return ''; 1573 } 1574 1575 if ($entry->sourceglossaryid == $cm->instance) { 1576 if (!$maincm = get_coursemodule_from_instance('glossary', $entry->glossaryid)) { 1577 return ''; 1578 } 1579 $filecontext = context_module::instance($maincm->id); 1580 1581 } else { 1582 $filecontext = $context; 1583 } 1584 1585 $strattachment = get_string('attachment', 'glossary'); 1586 1587 $fs = get_file_storage(); 1588 1589 $imagereturn = ''; 1590 $output = ''; 1591 1592 if ($files = $fs->get_area_files($filecontext->id, 'mod_glossary', 'attachment', $entry->id, "timemodified", false)) { 1593 foreach ($files as $file) { 1594 $filename = $file->get_filename(); 1595 $mimetype = $file->get_mimetype(); 1596 $iconimage = $OUTPUT->pix_icon(file_file_icon($file), get_mimetype_description($file), 'moodle', array('class' => 'icon')); 1597 $path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$context->id.'/mod_glossary/attachment/'.$entry->id.'/'.$filename); 1598 1599 if ($type == 'html') { 1600 $output .= "<a href=\"$path\">$iconimage</a> "; 1601 $output .= "<a href=\"$path\">".s($filename)."</a>"; 1602 $output .= "<br />"; 1603 1604 } else if ($type == 'text') { 1605 $output .= "$strattachment ".s($filename).":\n$path\n"; 1606 1607 } else { 1608 if (in_array($mimetype, array('image/gif', 'image/jpeg', 'image/png'))) { 1609 // Image attachments don't get printed as links 1610 $imagereturn .= "<br /><img src=\"$path\" alt=\"\" />"; 1611 } else { 1612 $output .= "<a href=\"$path\">$iconimage</a> "; 1613 $output .= format_text("<a href=\"$path\">".s($filename)."</a>", FORMAT_HTML, array('context'=>$context)); 1614 $output .= '<br />'; 1615 } 1616 } 1617 } 1618 } 1619 1620 if ($type) { 1621 return $output; 1622 } else { 1623 echo $output; 1624 return $imagereturn; 1625 } 1626 } 1627 1628 //////////////////////////////////////////////////////////////////////////////// 1629 // File API // 1630 //////////////////////////////////////////////////////////////////////////////// 1631 1632 /** 1633 * Lists all browsable file areas 1634 * 1635 * @package mod_glossary 1636 * @category files 1637 * @param stdClass $course course object 1638 * @param stdClass $cm course module object 1639 * @param stdClass $context context object 1640 * @return array 1641 */ 1642 function glossary_get_file_areas($course, $cm, $context) { 1643 return array( 1644 'attachment' => get_string('areaattachment', 'mod_glossary'), 1645 'entry' => get_string('areaentry', 'mod_glossary'), 1646 ); 1647 } 1648 1649 /** 1650 * File browsing support for glossary module. 1651 * 1652 * @param file_browser $browser 1653 * @param array $areas 1654 * @param stdClass $course 1655 * @param cm_info $cm 1656 * @param context $context 1657 * @param string $filearea 1658 * @param int $itemid 1659 * @param string $filepath 1660 * @param string $filename 1661 * @return file_info_stored file_info_stored instance or null if not found 1662 */ 1663 function glossary_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) { 1664 global $CFG, $DB, $USER; 1665 1666 if ($context->contextlevel != CONTEXT_MODULE) { 1667 return null; 1668 } 1669 1670 if (!isset($areas[$filearea])) { 1671 return null; 1672 } 1673 1674 if (is_null($itemid)) { 1675 require_once($CFG->dirroot.'/mod/glossary/locallib.php'); 1676 return new glossary_file_info_container($browser, $course, $cm, $context, $areas, $filearea); 1677 } 1678 1679 if (!$entry = $DB->get_record('glossary_entries', array('id' => $itemid))) { 1680 return null; 1681 } 1682 1683 if (!$glossary = $DB->get_record('glossary', array('id' => $cm->instance))) { 1684 return null; 1685 } 1686 1687 if ($glossary->defaultapproval and !$entry->approved and !has_capability('mod/glossary:approve', $context)) { 1688 return null; 1689 } 1690 1691 // this trickery here is because we need to support source glossary access 1692 if ($entry->glossaryid == $cm->instance) { 1693 $filecontext = $context; 1694 } else if ($entry->sourceglossaryid == $cm->instance) { 1695 if (!$maincm = get_coursemodule_from_instance('glossary', $entry->glossaryid)) { 1696 return null; 1697 } 1698 $filecontext = context_module::instance($maincm->id); 1699 } else { 1700 return null; 1701 } 1702 1703 $fs = get_file_storage(); 1704 $filepath = is_null($filepath) ? '/' : $filepath; 1705 $filename = is_null($filename) ? '.' : $filename; 1706 if (!($storedfile = $fs->get_file($filecontext->id, 'mod_glossary', $filearea, $itemid, $filepath, $filename))) { 1707 return null; 1708 } 1709 1710 // Checks to see if the user can manage files or is the owner. 1711 // TODO MDL-33805 - Do not use userid here and move the capability check above. 1712 if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) { 1713 return null; 1714 } 1715 1716 $urlbase = $CFG->wwwroot.'/pluginfile.php'; 1717 1718 return new file_info_stored($browser, $filecontext, $storedfile, $urlbase, s($entry->concept), true, true, false, false); 1719 } 1720 1721 /** 1722 * Serves the glossary attachments. Implements needed access control ;-) 1723 * 1724 * @package mod_glossary 1725 * @category files 1726 * @param stdClass $course course object 1727 * @param stdClass $cm course module object 1728 * @param stdClsss $context context object 1729 * @param string $filearea file area 1730 * @param array $args extra arguments 1731 * @param bool $forcedownload whether or not force download 1732 * @param array $options additional options affecting the file serving 1733 * @return bool false if file not found, does not return if found - justsend the file 1734 */ 1735 function glossary_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { 1736 global $CFG, $DB; 1737 1738 if ($context->contextlevel != CONTEXT_MODULE) { 1739 return false; 1740 } 1741 1742 require_course_login($course, true, $cm); 1743 1744 if ($filearea === 'attachment' or $filearea === 'entry') { 1745 $entryid = (int)array_shift($args); 1746 1747 require_course_login($course, true, $cm); 1748 1749 if (!$entry = $DB->get_record('glossary_entries', array('id'=>$entryid))) { 1750 return false; 1751 } 1752 1753 if (!$glossary = $DB->get_record('glossary', array('id'=>$cm->instance))) { 1754 return false; 1755 } 1756 1757 if ($glossary->defaultapproval and !$entry->approved and !has_capability('mod/glossary:approve', $context)) { 1758 return false; 1759 } 1760 1761 // this trickery here is because we need to support source glossary access 1762 1763 if ($entry->glossaryid == $cm->instance) { 1764 $filecontext = $context; 1765 1766 } else if ($entry->sourceglossaryid == $cm->instance) { 1767 if (!$maincm = get_coursemodule_from_instance('glossary', $entry->glossaryid)) { 1768 return false; 1769 } 1770 $filecontext = context_module::instance($maincm->id); 1771 1772 } else { 1773 return false; 1774 } 1775 1776 $relativepath = implode('/', $args); 1777 $fullpath = "/$filecontext->id/mod_glossary/$filearea/$entryid/$relativepath"; 1778 1779 $fs = get_file_storage(); 1780 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { 1781 return false; 1782 } 1783 1784 // finally send the file 1785 send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security! 1786 1787 } else if ($filearea === 'export') { 1788 require_login($course, false, $cm); 1789 require_capability('mod/glossary:export', $context); 1790 1791 if (!$glossary = $DB->get_record('glossary', array('id'=>$cm->instance))) { 1792 return false; 1793 } 1794 1795 $cat = array_shift($args); 1796 $cat = clean_param($cat, PARAM_ALPHANUM); 1797 1798 $filename = clean_filename(strip_tags(format_string($glossary->name)).'.xml'); 1799 $content = glossary_generate_export_file($glossary, NULL, $cat); 1800 1801 send_file($content, $filename, 0, 0, true, true); 1802 } 1803 1804 return false; 1805 } 1806 1807 /** 1808 * 1809 */ 1810 function glossary_print_tabbed_table_end() { 1811 echo "</div></div>"; 1812 } 1813 1814 /** 1815 * @param object $cm 1816 * @param object $glossary 1817 * @param string $mode 1818 * @param string $hook 1819 * @param string $sortkey 1820 * @param string $sortorder 1821 */ 1822 function glossary_print_approval_menu($cm, $glossary,$mode, $hook, $sortkey = '', $sortorder = '') { 1823 if ($glossary->showalphabet) { 1824 echo '<div class="glossaryexplain">' . get_string("explainalphabet","glossary") . '</div><br />'; 1825 } 1826 glossary_print_special_links($cm, $glossary, $mode, $hook); 1827 1828 glossary_print_alphabet_links($cm, $glossary, $mode, $hook,$sortkey, $sortorder); 1829 1830 glossary_print_all_links($cm, $glossary, $mode, $hook); 1831 1832 glossary_print_sorting_links($cm, $mode, 'CREATION', 'asc'); 1833 } 1834 /** 1835 * @param object $cm 1836 * @param object $glossary 1837 * @param string $hook 1838 * @param string $sortkey 1839 * @param string $sortorder 1840 */ 1841 function glossary_print_import_menu($cm, $glossary, $mode, $hook, $sortkey='', $sortorder = '') { 1842 echo '<div class="glossaryexplain">' . get_string("explainimport","glossary") . '</div>'; 1843 } 1844 1845 /** 1846 * @param object $cm 1847 * @param object $glossary 1848 * @param string $hook 1849 * @param string $sortkey 1850 * @param string $sortorder 1851 */ 1852 function glossary_print_export_menu($cm, $glossary, $mode, $hook, $sortkey='', $sortorder = '') { 1853 echo '<div class="glossaryexplain">' . get_string("explainexport","glossary") . '</div>'; 1854 } 1855 /** 1856 * @param object $cm 1857 * @param object $glossary 1858 * @param string $hook 1859 * @param string $sortkey 1860 * @param string $sortorder 1861 */ 1862 function glossary_print_alphabet_menu($cm, $glossary, $mode, $hook, $sortkey='', $sortorder = '') { 1863 if ( $mode != 'date' ) { 1864 if ($glossary->showalphabet) { 1865 echo '<div class="glossaryexplain">' . get_string("explainalphabet","glossary") . '</div><br />'; 1866 } 1867 1868 glossary_print_special_links($cm, $glossary, $mode, $hook); 1869 1870 glossary_print_alphabet_links($cm, $glossary, $mode, $hook, $sortkey, $sortorder); 1871 1872 glossary_print_all_links($cm, $glossary, $mode, $hook); 1873 } else { 1874 glossary_print_sorting_links($cm, $mode, $sortkey,$sortorder); 1875 } 1876 } 1877 1878 /** 1879 * @param object $cm 1880 * @param object $glossary 1881 * @param string $hook 1882 * @param string $sortkey 1883 * @param string $sortorder 1884 */ 1885 function glossary_print_author_menu($cm, $glossary,$mode, $hook, $sortkey = '', $sortorder = '') { 1886 if ($glossary->showalphabet) { 1887 echo '<div class="glossaryexplain">' . get_string("explainalphabet","glossary") . '</div><br />'; 1888 } 1889 1890 glossary_print_alphabet_links($cm, $glossary, $mode, $hook, $sortkey, $sortorder); 1891 glossary_print_all_links($cm, $glossary, $mode, $hook); 1892 glossary_print_sorting_links($cm, $mode, $sortkey,$sortorder); 1893 } 1894 1895 /** 1896 * @global object 1897 * @global object 1898 * @param object $cm 1899 * @param object $glossary 1900 * @param string $hook 1901 * @param object $category 1902 */ 1903 function glossary_print_categories_menu($cm, $glossary, $hook, $category) { 1904 global $CFG, $DB, $OUTPUT; 1905 1906 $context = context_module::instance($cm->id); 1907 1908 // Prepare format_string/text options 1909 $fmtoptions = array( 1910 'context' => $context); 1911 1912 echo '<table border="0" width="100%">'; 1913 echo '<tr>'; 1914 1915 echo '<td align="center" style="width:20%">'; 1916 if (has_capability('mod/glossary:managecategories', $context)) { 1917 $options['id'] = $cm->id; 1918 $options['mode'] = 'cat'; 1919 $options['hook'] = $hook; 1920 echo $OUTPUT->single_button(new moodle_url("editcategories.php", $options), get_string("editcategories","glossary"), "get"); 1921 } 1922 echo '</td>'; 1923 1924 echo '<td align="center" style="width:60%">'; 1925 echo '<b>'; 1926 1927 $menu = array(); 1928 $menu[GLOSSARY_SHOW_ALL_CATEGORIES] = get_string("allcategories","glossary"); 1929 $menu[GLOSSARY_SHOW_NOT_CATEGORISED] = get_string("notcategorised","glossary"); 1930 1931 $categories = $DB->get_records("glossary_categories", array("glossaryid"=>$glossary->id), "name ASC"); 1932 $selected = ''; 1933 if ( $categories ) { 1934 foreach ($categories as $currentcategory) { 1935 $url = $currentcategory->id; 1936 if ( $category ) { 1937 if ($currentcategory->id == $category->id) { 1938 $selected = $url; 1939 } 1940 } 1941 $menu[$url] = format_string($currentcategory->name, true, $fmtoptions); 1942 } 1943 } 1944 if ( !$selected ) { 1945 $selected = GLOSSARY_SHOW_NOT_CATEGORISED; 1946 } 1947 1948 if ( $category ) { 1949 echo format_string($category->name, true, $fmtoptions); 1950 } else { 1951 if ( $hook == GLOSSARY_SHOW_NOT_CATEGORISED ) { 1952 1953 echo get_string("entrieswithoutcategory","glossary"); 1954 $selected = GLOSSARY_SHOW_NOT_CATEGORISED; 1955 1956 } else if ( empty($hook) ) { 1957 1958 echo get_string("allcategories","glossary"); 1959 $selected = GLOSSARY_SHOW_ALL_CATEGORIES; 1960 1961 } 1962 } 1963 echo '</b></td>'; 1964 echo '<td align="center" style="width:20%">'; 1965 1966 $select = new single_select(new moodle_url("/mod/glossary/view.php", array('id'=>$cm->id, 'mode'=>'cat')), 'hook', $menu, $selected, null, "catmenu"); 1967 $select->set_label(get_string('categories', 'glossary'), array('class' => 'accesshide')); 1968 echo $OUTPUT->render($select); 1969 1970 echo '</td>'; 1971 echo '</tr>'; 1972 1973 echo '</table>'; 1974 } 1975 1976 /** 1977 * @global object 1978 * @param object $cm 1979 * @param object $glossary 1980 * @param string $mode 1981 * @param string $hook 1982 */ 1983 function glossary_print_all_links($cm, $glossary, $mode, $hook) { 1984 global $CFG; 1985 if ( $glossary->showall) { 1986 $strallentries = get_string("allentries", "glossary"); 1987 if ( $hook == 'ALL' ) { 1988 echo "<b>$strallentries</b>"; 1989 } else { 1990 $strexplainall = strip_tags(get_string("explainall","glossary")); 1991 echo "<a title=\"$strexplainall\" href=\"$CFG->wwwroot/mod/glossary/view.php?id=$cm->id&mode=$mode&hook=ALL\">$strallentries</a>"; 1992 } 1993 } 1994 } 1995 1996 /** 1997 * @global object 1998 * @param object $cm 1999 * @param object $glossary 2000 * @param string $mode 2001 * @param string $hook 2002 */ 2003 function glossary_print_special_links($cm, $glossary, $mode, $hook) { 2004 global $CFG; 2005 if ( $glossary->showspecial) { 2006 $strspecial = get_string("special", "glossary"); 2007 if ( $hook == 'SPECIAL' ) { 2008 echo "<b>$strspecial</b> | "; 2009 } else { 2010 $strexplainspecial = strip_tags(get_string("explainspecial","glossary")); 2011 echo "<a title=\"$strexplainspecial\" href=\"$CFG->wwwroot/mod/glossary/view.php?id=$cm->id&mode=$mode&hook=SPECIAL\">$strspecial</a> | "; 2012 } 2013 } 2014 } 2015 2016 /** 2017 * @global object 2018 * @param object $glossary 2019 * @param string $mode 2020 * @param string $hook 2021 * @param string $sortkey 2022 * @param string $sortorder 2023 */ 2024 function glossary_print_alphabet_links($cm, $glossary, $mode, $hook, $sortkey, $sortorder) { 2025 global $CFG; 2026 if ( $glossary->showalphabet) { 2027 $alphabet = explode(",", get_string('alphabet', 'langconfig')); 2028 for ($i = 0; $i < count($alphabet); $i++) { 2029 if ( $hook == $alphabet[$i] and $hook) { 2030 echo "<b>$alphabet[$i]</b>"; 2031 } else { 2032 echo "<a href=\"$CFG->wwwroot/mod/glossary/view.php?id=$cm->id&mode=$mode&hook=".urlencode($alphabet[$i])."&sortkey=$sortkey&sortorder=$sortorder\">$alphabet[$i]</a>"; 2033 } 2034 echo ' | '; 2035 } 2036 } 2037 } 2038 2039 /** 2040 * @global object 2041 * @param object $cm 2042 * @param string $mode 2043 * @param string $sortkey 2044 * @param string $sortorder 2045 */ 2046 function glossary_print_sorting_links($cm, $mode, $sortkey = '',$sortorder = '') { 2047 global $CFG, $OUTPUT; 2048 2049 $asc = get_string("ascending","glossary"); 2050 $desc = get_string("descending","glossary"); 2051 $bopen = '<b>'; 2052 $bclose = '</b>'; 2053 2054 $neworder = ''; 2055 $currentorder = ''; 2056 $currentsort = ''; 2057 if ( $sortorder ) { 2058 if ( $sortorder == 'asc' ) { 2059 $currentorder = $asc; 2060 $neworder = '&sortorder=desc'; 2061 $newordertitle = get_string('changeto', 'glossary', $desc); 2062 } else { 2063 $currentorder = $desc; 2064 $neworder = '&sortorder=asc'; 2065 $newordertitle = get_string('changeto', 'glossary', $asc); 2066 } 2067 $icon = " " . $OUTPUT->pix_icon($sortorder, $newordertitle, 'glossary'); 2068 } else { 2069 if ( $sortkey != 'CREATION' and $sortkey != 'UPDATE' and 2070 $sortkey != 'FIRSTNAME' and $sortkey != 'LASTNAME' ) { 2071 $icon = ""; 2072 $newordertitle = $asc; 2073 } else { 2074 $newordertitle = $desc; 2075 $neworder = '&sortorder=desc'; 2076 $icon = " " . $OUTPUT->pix_icon('asc', $newordertitle, 'glossary'); 2077 } 2078 } 2079 $ficon = ''; 2080 $fneworder = ''; 2081 $fbtag = ''; 2082 $fendbtag = ''; 2083 2084 $sicon = ''; 2085 $sneworder = ''; 2086 2087 $sbtag = ''; 2088 $fbtag = ''; 2089 $fendbtag = ''; 2090 $sendbtag = ''; 2091 2092 $sendbtag = ''; 2093 2094 if ( $sortkey == 'CREATION' or $sortkey == 'FIRSTNAME' ) { 2095 $ficon = $icon; 2096 $fneworder = $neworder; 2097 $fordertitle = $newordertitle; 2098 $sordertitle = $asc; 2099 $fbtag = $bopen; 2100 $fendbtag = $bclose; 2101 } elseif ($sortkey == 'UPDATE' or $sortkey == 'LASTNAME') { 2102 $sicon = $icon; 2103 $sneworder = $neworder; 2104 $fordertitle = $asc; 2105 $sordertitle = $newordertitle; 2106 $sbtag = $bopen; 2107 $sendbtag = $bclose; 2108 } else { 2109 $fordertitle = $asc; 2110 $sordertitle = $asc; 2111 } 2112 2113 if ( $sortkey == 'CREATION' or $sortkey == 'UPDATE' ) { 2114 $forder = 'CREATION'; 2115 $sorder = 'UPDATE'; 2116 $fsort = get_string("sortbycreation", "glossary"); 2117 $ssort = get_string("sortbylastupdate", "glossary"); 2118 2119 $currentsort = $fsort; 2120 if ($sortkey == 'UPDATE') { 2121 $currentsort = $ssort; 2122 } 2123 $sort = get_string("sortchronogically", "glossary"); 2124 } elseif ( $sortkey == 'FIRSTNAME' or $sortkey == 'LASTNAME') { 2125 $forder = 'FIRSTNAME'; 2126 $sorder = 'LASTNAME'; 2127 $fsort = get_string("firstname"); 2128 $ssort = get_string("lastname"); 2129 2130 $currentsort = $fsort; 2131 if ($sortkey == 'LASTNAME') { 2132 $currentsort = $ssort; 2133 } 2134 $sort = get_string("sortby", "glossary"); 2135 } 2136 $current = '<span class="accesshide">'.get_string('current', 'glossary', "$currentsort $currentorder").'</span>'; 2137 echo "<br />$current $sort: $sbtag<a title=\"$ssort $sordertitle\" href=\"$CFG->wwwroot/mod/glossary/view.php?id=$cm->id&sortkey=$sorder$sneworder&mode=$mode\">$ssort$sicon</a>$sendbtag | ". 2138 "$fbtag<a title=\"$fsort $fordertitle\" href=\"$CFG->wwwroot/mod/glossary/view.php?id=$cm->id&sortkey=$forder$fneworder&mode=$mode\">$fsort$ficon</a>$fendbtag<br />"; 2139 } 2140 2141 /** 2142 * 2143 * @param object $entry0 2144 * @param object $entry1 2145 * @return int [-1 | 0 | 1] 2146 */ 2147 function glossary_sort_entries ( $entry0, $entry1 ) { 2148 2149 if ( core_text::strtolower(ltrim($entry0->concept)) < core_text::strtolower(ltrim($entry1->concept)) ) { 2150 return -1; 2151 } elseif ( core_text::strtolower(ltrim($entry0->concept)) > core_text::strtolower(ltrim($entry1->concept)) ) { 2152 return 1; 2153 } else { 2154 return 0; 2155 } 2156 } 2157 2158 2159 /** 2160 * @global object 2161 * @global object 2162 * @global object 2163 * @param object $course 2164 * @param object $entry 2165 * @return bool 2166 */ 2167 function glossary_print_entry_ratings($course, $entry) { 2168 global $OUTPUT; 2169 if( !empty($entry->rating) ){ 2170 echo $OUTPUT->render($entry->rating); 2171 } 2172 } 2173 2174 /** 2175 * 2176 * @global object 2177 * @global object 2178 * @global object 2179 * @param int $courseid 2180 * @param array $entries 2181 * @param int $displayformat 2182 */ 2183 function glossary_print_dynaentry($courseid, $entries, $displayformat = -1) { 2184 global $USER, $CFG, $DB; 2185 2186 echo '<div class="boxaligncenter">'; 2187 echo '<table class="glossarypopup" cellspacing="0"><tr>'; 2188 echo '<td>'; 2189 if ( $entries ) { 2190 foreach ( $entries as $entry ) { 2191 if (! $glossary = $DB->get_record('glossary', array('id'=>$entry->glossaryid))) { 2192 print_error('invalidid', 'glossary'); 2193 } 2194 if (! $course = $DB->get_record('course', array('id'=>$glossary->course))) { 2195 print_error('coursemisconf'); 2196 } 2197 if (!$cm = get_coursemodule_from_instance('glossary', $entry->glossaryid, $glossary->course) ) { 2198 print_error('invalidid', 'glossary'); 2199 } 2200 2201 //If displayformat is present, override glossary->displayformat 2202 if ($displayformat < 0) { 2203 $dp = $glossary->displayformat; 2204 } else { 2205 $dp = $displayformat; 2206 } 2207 2208 //Get popupformatname 2209 $format = $DB->get_record('glossary_formats', array('name'=>$dp)); 2210 $displayformat = $format->popupformatname; 2211 2212 //Check displayformat variable and set to default if necessary 2213 if (!$displayformat) { 2214 $displayformat = 'dictionary'; 2215 } 2216 2217 $formatfile = $CFG->dirroot.'/mod/glossary/formats/'.$displayformat.'/'.$displayformat.'_format.php'; 2218 $functionname = 'glossary_show_entry_'.$displayformat; 2219 2220 if (file_exists($formatfile)) { 2221 include_once($formatfile); 2222 if (function_exists($functionname)) { 2223 $functionname($course, $cm, $glossary, $entry,'','','',''); 2224 } 2225 } 2226 } 2227 } 2228 echo '</td>'; 2229 echo '</tr></table></div>'; 2230 } 2231 2232 /** 2233 * 2234 * @global object 2235 * @param array $entries 2236 * @param array $aliases 2237 * @param array $categories 2238 * @return string 2239 */ 2240 function glossary_generate_export_csv($entries, $aliases, $categories) { 2241 global $CFG; 2242 $csv = ''; 2243 $delimiter = ''; 2244 require_once($CFG->libdir . '/csvlib.class.php'); 2245 $delimiter = csv_import_reader::get_delimiter('comma'); 2246 $csventries = array(0 => array(get_string('concept', 'glossary'), get_string('definition', 'glossary'))); 2247 $csvaliases = array(0 => array()); 2248 $csvcategories = array(0 => array()); 2249 $aliascount = 0; 2250 $categorycount = 0; 2251 2252 foreach ($entries as $entry) { 2253 $thisaliasesentry = array(); 2254 $thiscategoriesentry = array(); 2255 $thiscsventry = array($entry->concept, nl2br($entry->definition)); 2256 2257 if (array_key_exists($entry->id, $aliases) && is_array($aliases[$entry->id])) { 2258 $thiscount = count($aliases[$entry->id]); 2259 if ($thiscount > $aliascount) { 2260 $aliascount = $thiscount; 2261 } 2262 foreach ($aliases[$entry->id] as $alias) { 2263 $thisaliasesentry[] = trim($alias); 2264 } 2265 } 2266 if (array_key_exists($entry->id, $categories) && is_array($categories[$entry->id])) { 2267 $thiscount = count($categories[$entry->id]); 2268 if ($thiscount > $categorycount) { 2269 $categorycount = $thiscount; 2270 } 2271 foreach ($categories[$entry->id] as $catentry) { 2272 $thiscategoriesentry[] = trim($catentry); 2273 } 2274 } 2275 $csventries[$entry->id] = $thiscsventry; 2276 $csvaliases[$entry->id] = $thisaliasesentry; 2277 $csvcategories[$entry->id] = $thiscategoriesentry; 2278 2279 } 2280 $returnstr = ''; 2281 foreach ($csventries as $id => $row) { 2282 $aliasstr = ''; 2283 $categorystr = ''; 2284 if ($id == 0) { 2285 $aliasstr = get_string('alias', 'glossary'); 2286 $categorystr = get_string('category', 'glossary'); 2287 } 2288 $row = array_merge($row, array_pad($csvaliases[$id], $aliascount, $aliasstr), array_pad($csvcategories[$id], $categorycount, $categorystr)); 2289 $returnstr .= '"' . implode('"' . $delimiter . '"', $row) . '"' . "\n"; 2290 } 2291 return $returnstr; 2292 } 2293 2294 /** 2295 * 2296 * @param object $glossary 2297 * @param string $ignored invalid parameter 2298 * @param int|string $hook 2299 * @return string 2300 */ 2301 function glossary_generate_export_file($glossary, $ignored = "", $hook = 0) { 2302 global $CFG, $DB; 2303 2304 // Large exports are likely to take their time and memory. 2305 core_php_time_limit::raise(); 2306 raise_memory_limit(MEMORY_EXTRA); 2307 2308 $cm = get_coursemodule_from_instance('glossary', $glossary->id, $glossary->course); 2309 $context = context_module::instance($cm->id); 2310 2311 $co = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; 2312 2313 $co .= glossary_start_tag("GLOSSARY",0,true); 2314 $co .= glossary_start_tag("INFO",1,true); 2315 $co .= glossary_full_tag("NAME",2,false,$glossary->name); 2316 $co .= glossary_full_tag("INTRO",2,false,$glossary->intro); 2317 $co .= glossary_full_tag("INTROFORMAT",2,false,$glossary->introformat); 2318 $co .= glossary_full_tag("ALLOWDUPLICATEDENTRIES",2,false,$glossary->allowduplicatedentries); 2319 $co .= glossary_full_tag("DISPLAYFORMAT",2,false,$glossary->displayformat); 2320 $co .= glossary_full_tag("SHOWSPECIAL",2,false,$glossary->showspecial); 2321 $co .= glossary_full_tag("SHOWALPHABET",2,false,$glossary->showalphabet); 2322 $co .= glossary_full_tag("SHOWALL",2,false,$glossary->showall); 2323 $co .= glossary_full_tag("ALLOWCOMMENTS",2,false,$glossary->allowcomments); 2324 $co .= glossary_full_tag("USEDYNALINK",2,false,$glossary->usedynalink); 2325 $co .= glossary_full_tag("DEFAULTAPPROVAL",2,false,$glossary->defaultapproval); 2326 $co .= glossary_full_tag("GLOBALGLOSSARY",2,false,$glossary->globalglossary); 2327 $co .= glossary_full_tag("ENTBYPAGE",2,false,$glossary->entbypage); 2328 $co .= glossary_xml_export_files('INTROFILES', 2, $context->id, 'intro', 0); 2329 2330 if ( $entries = $DB->get_records("glossary_entries", array("glossaryid"=>$glossary->id))) { 2331 $co .= glossary_start_tag("ENTRIES",2,true); 2332 foreach ($entries as $entry) { 2333 $permissiongranted = 1; 2334 if ( $hook ) { 2335 switch ( $hook ) { 2336 case "ALL": 2337 case "SPECIAL": 2338 break; 2339 default: 2340 $permissiongranted = ($entry->concept[ strlen($hook)-1 ] == $hook); 2341 break; 2342 } 2343 } 2344 if ( $hook ) { 2345 switch ( $hook ) { 2346 case GLOSSARY_SHOW_ALL_CATEGORIES: 2347 break; 2348 case GLOSSARY_SHOW_NOT_CATEGORISED: 2349 $permissiongranted = !$DB->record_exists("glossary_entries_categories", array("entryid"=>$entry->id)); 2350 break; 2351 default: 2352 $permissiongranted = $DB->record_exists("glossary_entries_categories", array("entryid"=>$entry->id, "categoryid"=>$hook)); 2353 break; 2354 } 2355 } 2356 if ( $entry->approved and $permissiongranted ) { 2357 $co .= glossary_start_tag("ENTRY",3,true); 2358 $co .= glossary_full_tag("CONCEPT",4,false,trim($entry->concept)); 2359 $co .= glossary_full_tag("DEFINITION",4,false,$entry->definition); 2360 $co .= glossary_full_tag("FORMAT",4,false,$entry->definitionformat); // note: use old name for BC reasons 2361 $co .= glossary_full_tag("USEDYNALINK",4,false,$entry->usedynalink); 2362 $co .= glossary_full_tag("CASESENSITIVE",4,false,$entry->casesensitive); 2363 $co .= glossary_full_tag("FULLMATCH",4,false,$entry->fullmatch); 2364 $co .= glossary_full_tag("TEACHERENTRY",4,false,$entry->teacherentry); 2365 2366 if ( $aliases = $DB->get_records("glossary_alias", array("entryid"=>$entry->id))) { 2367 $co .= glossary_start_tag("ALIASES",4,true); 2368 foreach ($aliases as $alias) { 2369 $co .= glossary_start_tag("ALIAS",5,true); 2370 $co .= glossary_full_tag("NAME",6,false,trim($alias->alias)); 2371 $co .= glossary_end_tag("ALIAS",5,true); 2372 } 2373 $co .= glossary_end_tag("ALIASES",4,true); 2374 } 2375 if ( $catentries = $DB->get_records("glossary_entries_categories", array("entryid"=>$entry->id))) { 2376 $co .= glossary_start_tag("CATEGORIES",4,true); 2377 foreach ($catentries as $catentry) { 2378 $category = $DB->get_record("glossary_categories", array("id"=>$catentry->categoryid)); 2379 2380 $co .= glossary_start_tag("CATEGORY",5,true); 2381 $co .= glossary_full_tag("NAME",6,false,$category->name); 2382 $co .= glossary_full_tag("USEDYNALINK",6,false,$category->usedynalink); 2383 $co .= glossary_end_tag("CATEGORY",5,true); 2384 } 2385 $co .= glossary_end_tag("CATEGORIES",4,true); 2386 } 2387 2388 // Export files embedded in entries. 2389 $co .= glossary_xml_export_files('ENTRYFILES', 4, $context->id, 'entry', $entry->id); 2390 2391 // Export attachments. 2392 $co .= glossary_xml_export_files('ATTACHMENTFILES', 4, $context->id, 'attachment', $entry->id); 2393 2394 // Export tags. 2395 $tags = core_tag_tag::get_item_tags_array('mod_glossary', 'glossary_entries', $entry->id); 2396 if (count($tags)) { 2397 $co .= glossary_start_tag("TAGS", 4, true); 2398 foreach ($tags as $tag) { 2399 $co .= glossary_full_tag("TAG", 5, false, $tag); 2400 } 2401 $co .= glossary_end_tag("TAGS", 4, true); 2402 } 2403 2404 $co .= glossary_end_tag("ENTRY",3,true); 2405 } 2406 } 2407 $co .= glossary_end_tag("ENTRIES",2,true); 2408 2409 } 2410 2411 2412 $co .= glossary_end_tag("INFO",1,true); 2413 $co .= glossary_end_tag("GLOSSARY",0,true); 2414 2415 return $co; 2416 } 2417 /// Functions designed by Eloy Lafuente 2418 /// Functions to create, open and write header of the xml file 2419 2420 /** 2421 * Read import file and convert to current charset 2422 * 2423 * @global object 2424 * @param string $file 2425 * @return string 2426 */ 2427 function glossary_read_imported_file($file_content) { 2428 global $CFG; 2429 require_once "../../lib/xmlize.php"; 2430 2431 return xmlize($file_content, 0); 2432 } 2433 2434 /** 2435 * Return the xml start tag 2436 * 2437 * @param string $tag 2438 * @param int $level 2439 * @param bool $endline 2440 * @return string 2441 */ 2442 function glossary_start_tag($tag,$level=0,$endline=false) { 2443 if ($endline) { 2444 $endchar = "\n"; 2445 } else { 2446 $endchar = ""; 2447 } 2448 return str_repeat(" ",$level*2)."<".strtoupper($tag).">".$endchar; 2449 } 2450 2451 /** 2452 * Return the xml end tag 2453 * @param string $tag 2454 * @param int $level 2455 * @param bool $endline 2456 * @return string 2457 */ 2458 function glossary_end_tag($tag,$level=0,$endline=true) { 2459 if ($endline) { 2460 $endchar = "\n"; 2461 } else { 2462 $endchar = ""; 2463 } 2464 return str_repeat(" ",$level*2)."</".strtoupper($tag).">".$endchar; 2465 } 2466 2467 /** 2468 * Return the start tag, the contents and the end tag 2469 * 2470 * @global object 2471 * @param string $tag 2472 * @param int $level 2473 * @param bool $endline 2474 * @param string $content 2475 * @return string 2476 */ 2477 function glossary_full_tag($tag, $level, $endline, $content) { 2478 global $CFG; 2479 2480 $st = glossary_start_tag($tag,$level,$endline); 2481 $co = preg_replace("/\r\n|\r/", "\n", s($content)); 2482 $et = glossary_end_tag($tag,0,true); 2483 return $st.$co.$et; 2484 } 2485 2486 /** 2487 * Prepares file area to export as part of XML export 2488 * 2489 * @param string $tag XML tag to use for the group 2490 * @param int $taglevel 2491 * @param int $contextid 2492 * @param string $filearea 2493 * @param int $itemid 2494 * @return string 2495 */ 2496 function glossary_xml_export_files($tag, $taglevel, $contextid, $filearea, $itemid) { 2497 $co = ''; 2498 $fs = get_file_storage(); 2499 if ($files = $fs->get_area_files( 2500 $contextid, 'mod_glossary', $filearea, $itemid, 'itemid,filepath,filename', false)) { 2501 $co .= glossary_start_tag($tag, $taglevel, true); 2502 foreach ($files as $file) { 2503 $co .= glossary_start_tag('FILE', $taglevel + 1, true); 2504 $co .= glossary_full_tag('FILENAME', $taglevel + 2, false, $file->get_filename()); 2505 $co .= glossary_full_tag('FILEPATH', $taglevel + 2, false, $file->get_filepath()); 2506 $co .= glossary_full_tag('CONTENTS', $taglevel + 2, false, base64_encode($file->get_content())); 2507 $co .= glossary_full_tag('FILEAUTHOR', $taglevel + 2, false, $file->get_author()); 2508 $co .= glossary_full_tag('FILELICENSE', $taglevel + 2, false, $file->get_license()); 2509 $co .= glossary_end_tag('FILE', $taglevel + 1); 2510 } 2511 $co .= glossary_end_tag($tag, $taglevel); 2512 } 2513 return $co; 2514 } 2515 2516 /** 2517 * Parses files from XML import and inserts them into file system 2518 * 2519 * @param array $xmlparent parent element in parsed XML tree 2520 * @param string $tag 2521 * @param int $contextid 2522 * @param string $filearea 2523 * @param int $itemid 2524 * @return int 2525 */ 2526 function glossary_xml_import_files($xmlparent, $tag, $contextid, $filearea, $itemid) { 2527 global $USER, $CFG; 2528 $count = 0; 2529 if (isset($xmlparent[$tag][0]['#']['FILE'])) { 2530 $fs = get_file_storage(); 2531 $files = $xmlparent[$tag][0]['#']['FILE']; 2532 foreach ($files as $file) { 2533 $filerecord = array( 2534 'contextid' => $contextid, 2535 'component' => 'mod_glossary', 2536 'filearea' => $filearea, 2537 'itemid' => $itemid, 2538 'filepath' => $file['#']['FILEPATH'][0]['#'], 2539 'filename' => $file['#']['FILENAME'][0]['#'], 2540 'userid' => $USER->id 2541 ); 2542 if (array_key_exists('FILEAUTHOR', $file['#'])) { 2543 $filerecord['author'] = $file['#']['FILEAUTHOR'][0]['#']; 2544 } 2545 if (array_key_exists('FILELICENSE', $file['#'])) { 2546 $license = $file['#']['FILELICENSE'][0]['#']; 2547 require_once($CFG->libdir . "/licenselib.php"); 2548 if (license_manager::get_license_by_shortname($license)) { 2549 $filerecord['license'] = $license; 2550 } 2551 } 2552 $content = $file['#']['CONTENTS'][0]['#']; 2553 $fs->create_file_from_string($filerecord, base64_decode($content)); 2554 $count++; 2555 } 2556 } 2557 return $count; 2558 } 2559 2560 /** 2561 * How many unrated entries are in the given glossary for a given user? 2562 * 2563 * @global moodle_database $DB 2564 * @param int $glossaryid 2565 * @param int $userid 2566 * @return int 2567 */ 2568 function glossary_count_unrated_entries($glossaryid, $userid) { 2569 global $DB; 2570 2571 $sql = "SELECT COUNT('x') as num 2572 FROM {glossary_entries} 2573 WHERE glossaryid = :glossaryid AND 2574 userid <> :userid"; 2575 $params = array('glossaryid' => $glossaryid, 'userid' => $userid); 2576 $entries = $DB->count_records_sql($sql, $params); 2577 2578 if ($entries) { 2579 // We need to get the contextid for the glossaryid we have been given. 2580 $sql = "SELECT ctx.id 2581 FROM {context} ctx 2582 JOIN {course_modules} cm ON cm.id = ctx.instanceid 2583 JOIN {modules} m ON m.id = cm.module 2584 JOIN {glossary} g ON g.id = cm.instance 2585 WHERE ctx.contextlevel = :contextlevel AND 2586 m.name = 'glossary' AND 2587 g.id = :glossaryid"; 2588 $contextid = $DB->get_field_sql($sql, array('glossaryid' => $glossaryid, 'contextlevel' => CONTEXT_MODULE)); 2589 2590 // Now we need to count the ratings that this user has made 2591 $sql = "SELECT COUNT('x') AS num 2592 FROM {glossary_entries} e 2593 JOIN {rating} r ON r.itemid = e.id 2594 WHERE e.glossaryid = :glossaryid AND 2595 r.userid = :userid AND 2596 r.component = 'mod_glossary' AND 2597 r.ratingarea = 'entry' AND 2598 r.contextid = :contextid"; 2599 $params = array('glossaryid' => $glossaryid, 'userid' => $userid, 'contextid' => $contextid); 2600 $rated = $DB->count_records_sql($sql, $params); 2601 if ($rated) { 2602 // The number or enties minus the number or rated entries equals the number of unrated 2603 // entries 2604 if ($entries > $rated) { 2605 return $entries - $rated; 2606 } else { 2607 return 0; // Just in case there was a counting error 2608 } 2609 } else { 2610 return (int)$entries; 2611 } 2612 } else { 2613 return 0; 2614 } 2615 } 2616 2617 /** 2618 * 2619 * Returns the html code to represent any pagging bar. Paramenters are: 2620 * 2621 * The function dinamically show the first and last pages, and "scroll" over pages. 2622 * Fully compatible with Moodle's print_paging_bar() function. Perhaps some day this 2623 * could replace the general one. ;-) 2624 * 2625 * @param int $totalcount total number of records to be displayed 2626 * @param int $page page currently selected (0 based) 2627 * @param int $perpage number of records per page 2628 * @param string $baseurl url to link in each page, the string 'page=XX' will be added automatically. 2629 * 2630 * @param int $maxpageallowed Optional maximum number of page allowed. 2631 * @param int $maxdisplay Optional maximum number of page links to show in the bar 2632 * @param string $separator Optional string to be used between pages in the bar 2633 * @param string $specialtext Optional string to be showed as an special link 2634 * @param string $specialvalue Optional value (page) to be used in the special link 2635 * @param bool $previousandnext Optional to decide if we want the previous and next links 2636 * @return string 2637 */ 2638 function glossary_get_paging_bar($totalcount, $page, $perpage, $baseurl, $maxpageallowed=99999, $maxdisplay=20, $separator=" ", $specialtext="", $specialvalue=-1, $previousandnext = true) { 2639 2640 $code = ''; 2641 2642 $showspecial = false; 2643 $specialselected = false; 2644 2645 //Check if we have to show the special link 2646 if (!empty($specialtext)) { 2647 $showspecial = true; 2648 } 2649 //Check if we are with the special link selected 2650 if ($showspecial && $page == $specialvalue) { 2651 $specialselected = true; 2652 } 2653 2654 //If there are results (more than 1 page) 2655 if ($totalcount > $perpage) { 2656 $code .= "<div style=\"text-align:center\">"; 2657 $code .= "<p>".get_string("page").":"; 2658 2659 $maxpage = (int)(($totalcount-1)/$perpage); 2660 2661 //Lower and upper limit of page 2662 if ($page < 0) { 2663 $page = 0; 2664 } 2665 if ($page > $maxpageallowed) { 2666 $page = $maxpageallowed; 2667 } 2668 if ($page > $maxpage) { 2669 $page = $maxpage; 2670 } 2671 2672 //Calculate the window of pages 2673 $pagefrom = $page - ((int)($maxdisplay / 2)); 2674 if ($pagefrom < 0) { 2675 $pagefrom = 0; 2676 } 2677 $pageto = $pagefrom + $maxdisplay - 1; 2678 if ($pageto > $maxpageallowed) { 2679 $pageto = $maxpageallowed; 2680 } 2681 if ($pageto > $maxpage) { 2682 $pageto = $maxpage; 2683 } 2684 2685 //Some movements can be necessary if don't see enought pages 2686 if ($pageto - $pagefrom < $maxdisplay - 1) { 2687 if ($pageto - $maxdisplay + 1 > 0) { 2688 $pagefrom = $pageto - $maxdisplay + 1; 2689 } 2690 } 2691 2692 //Calculate first and last if necessary 2693 $firstpagecode = ''; 2694 $lastpagecode = ''; 2695 if ($pagefrom > 0) { 2696 $firstpagecode = "$separator<a href=\"{$baseurl}page=0\">1</a>"; 2697 if ($pagefrom > 1) { 2698 $firstpagecode .= "$separator..."; 2699 } 2700 } 2701 if ($pageto < $maxpage) { 2702 if ($pageto < $maxpage -1) { 2703 $lastpagecode = "$separator..."; 2704 } 2705 $lastpagecode .= "$separator<a href=\"{$baseurl}page=$maxpage\">".($maxpage+1)."</a>"; 2706 } 2707 2708 //Previous 2709 if ($page > 0 && $previousandnext) { 2710 $pagenum = $page - 1; 2711 $code .= " (<a href=\"{$baseurl}page=$pagenum\">".get_string("previous")."</a>) "; 2712 } 2713 2714 //Add first 2715 $code .= $firstpagecode; 2716 2717 $pagenum = $pagefrom; 2718 2719 //List of maxdisplay pages 2720 while ($pagenum <= $pageto) { 2721 $pagetoshow = $pagenum +1; 2722 if ($pagenum == $page && !$specialselected) { 2723 $code .= "$separator<b>$pagetoshow</b>"; 2724 } else { 2725 $code .= "$separator<a href=\"{$baseurl}page=$pagenum\">$pagetoshow</a>"; 2726 } 2727 $pagenum++; 2728 } 2729 2730 //Add last 2731 $code .= $lastpagecode; 2732 2733 //Next 2734 if ($page < $maxpage && $page < $maxpageallowed && $previousandnext) { 2735 $pagenum = $page + 1; 2736 $code .= "$separator(<a href=\"{$baseurl}page=$pagenum\">".get_string("next")."</a>)"; 2737 } 2738 2739 //Add special 2740 if ($showspecial) { 2741 $code .= '<br />'; 2742 if ($specialselected) { 2743 $code .= "$separator<b>$specialtext</b>"; 2744 } else { 2745 $code .= "$separator<a href=\"{$baseurl}page=$specialvalue\">$specialtext</a>"; 2746 } 2747 } 2748 2749 //End html 2750 $code .= "</p>"; 2751 $code .= "</div>"; 2752 } 2753 2754 return $code; 2755 } 2756 2757 /** 2758 * List the actions that correspond to a view of this module. 2759 * This is used by the participation report. 2760 * 2761 * Note: This is not used by new logging system. Event with 2762 * crud = 'r' and edulevel = LEVEL_PARTICIPATING will 2763 * be considered as view action. 2764 * 2765 * @return array 2766 */ 2767 function glossary_get_view_actions() { 2768 return array('view','view all','view entry'); 2769 } 2770 2771 /** 2772 * List the actions that correspond to a post of this module. 2773 * This is used by the participation report. 2774 * 2775 * Note: This is not used by new logging system. Event with 2776 * crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING 2777 * will be considered as post action. 2778 * 2779 * @return array 2780 */ 2781 function glossary_get_post_actions() { 2782 return array('add category','add entry','approve entry','delete category','delete entry','edit category','update entry'); 2783 } 2784 2785 2786 /** 2787 * Implementation of the function for printing the form elements that control 2788 * whether the course reset functionality affects the glossary. 2789 * @param object $mform form passed by reference 2790 */ 2791 function glossary_reset_course_form_definition(&$mform) { 2792 $mform->addElement('header', 'glossaryheader', get_string('modulenameplural', 'glossary')); 2793 $mform->addElement('checkbox', 'reset_glossary_all', get_string('resetglossariesall','glossary')); 2794 2795 $mform->addElement('select', 'reset_glossary_types', get_string('resetglossaries', 'glossary'), 2796 array('main'=>get_string('mainglossary', 'glossary'), 'secondary'=>get_string('secondaryglossary', 'glossary')), array('multiple' => 'multiple')); 2797 $mform->setAdvanced('reset_glossary_types'); 2798 $mform->disabledIf('reset_glossary_types', 'reset_glossary_all', 'checked'); 2799 2800 $mform->addElement('checkbox', 'reset_glossary_notenrolled', get_string('deletenotenrolled', 'glossary')); 2801 $mform->disabledIf('reset_glossary_notenrolled', 'reset_glossary_all', 'checked'); 2802 2803 $mform->addElement('checkbox', 'reset_glossary_ratings', get_string('deleteallratings')); 2804 $mform->disabledIf('reset_glossary_ratings', 'reset_glossary_all', 'checked'); 2805 2806 $mform->addElement('checkbox', 'reset_glossary_comments', get_string('deleteallcomments')); 2807 $mform->disabledIf('reset_glossary_comments', 'reset_glossary_all', 'checked'); 2808 2809 $mform->addElement('checkbox', 'reset_glossary_tags', get_string('removeallglossarytags', 'glossary')); 2810 $mform->disabledIf('reset_glossary_tags', 'reset_glossary_all', 'checked'); 2811 } 2812 2813 /** 2814 * Course reset form defaults. 2815 * @return array 2816 */ 2817 function glossary_reset_course_form_defaults($course) { 2818 return array('reset_glossary_all'=>0, 'reset_glossary_ratings'=>1, 'reset_glossary_comments'=>1, 'reset_glossary_notenrolled'=>0); 2819 } 2820 2821 /** 2822 * Removes all grades from gradebook 2823 * 2824 * @param int $courseid The ID of the course to reset 2825 * @param string $type The optional type of glossary. 'main', 'secondary' or '' 2826 */ 2827 function glossary_reset_gradebook($courseid, $type='') { 2828 global $DB; 2829 2830 switch ($type) { 2831 case 'main' : $type = "AND g.mainglossary=1"; break; 2832 case 'secondary' : $type = "AND g.mainglossary=0"; break; 2833 default : $type = ""; //all 2834 } 2835 2836 $sql = "SELECT g.*, cm.idnumber as cmidnumber, g.course as courseid 2837 FROM {glossary} g, {course_modules} cm, {modules} m 2838 WHERE m.name='glossary' AND m.id=cm.module AND cm.instance=g.id AND g.course=? $type"; 2839 2840 if ($glossarys = $DB->get_records_sql($sql, array($courseid))) { 2841 foreach ($glossarys as $glossary) { 2842 glossary_grade_item_update($glossary, 'reset'); 2843 } 2844 } 2845 } 2846 /** 2847 * Actual implementation of the reset course functionality, delete all the 2848 * glossary responses for course $data->courseid. 2849 * 2850 * @global object 2851 * @param $data the data submitted from the reset course. 2852 * @return array status array 2853 */ 2854 function glossary_reset_userdata($data) { 2855 global $CFG, $DB; 2856 require_once($CFG->dirroot.'/rating/lib.php'); 2857 2858 $componentstr = get_string('modulenameplural', 'glossary'); 2859 $status = array(); 2860 2861 $allentriessql = "SELECT e.id 2862 FROM {glossary_entries} e 2863 JOIN {glossary} g ON e.glossaryid = g.id 2864 WHERE g.course = ?"; 2865 2866 $allglossariessql = "SELECT g.id 2867 FROM {glossary} g 2868 WHERE g.course = ?"; 2869 2870 $params = array($data->courseid); 2871 2872 $fs = get_file_storage(); 2873 2874 $rm = new rating_manager(); 2875 $ratingdeloptions = new stdClass; 2876 $ratingdeloptions->component = 'mod_glossary'; 2877 $ratingdeloptions->ratingarea = 'entry'; 2878 2879 // delete entries if requested 2880 if (!empty($data->reset_glossary_all) 2881 or (!empty($data->reset_glossary_types) and in_array('main', $data->reset_glossary_types) and in_array('secondary', $data->reset_glossary_types))) { 2882 2883 $params[] = 'glossary_entry'; 2884 $DB->delete_records_select('comments', "itemid IN ($allentriessql) AND commentarea=?", $params); 2885 $DB->delete_records_select('glossary_alias', "entryid IN ($allentriessql)", $params); 2886 $DB->delete_records_select('glossary_entries', "glossaryid IN ($allglossariessql)", $params); 2887 2888 // now get rid of all attachments 2889 if ($glossaries = $DB->get_records_sql($allglossariessql, $params)) { 2890 foreach ($glossaries as $glossaryid=>$unused) { 2891 if (!$cm = get_coursemodule_from_instance('glossary', $glossaryid)) { 2892 continue; 2893 } 2894 $context = context_module::instance($cm->id); 2895 $fs->delete_area_files($context->id, 'mod_glossary', 'attachment'); 2896 2897 //delete ratings 2898 $ratingdeloptions->contextid = $context->id; 2899 $rm->delete_ratings($ratingdeloptions); 2900 2901 core_tag_tag::delete_instances('mod_glossary', null, $context->id); 2902 } 2903 } 2904 2905 // remove all grades from gradebook 2906 if (empty($data->reset_gradebook_grades)) { 2907 glossary_reset_gradebook($data->courseid); 2908 } 2909 2910 $status[] = array('component'=>$componentstr, 'item'=>get_string('resetglossariesall', 'glossary'), 'error'=>false); 2911 2912 } else if (!empty($data->reset_glossary_types)) { 2913 $mainentriessql = "$allentriessql AND g.mainglossary=1"; 2914 $secondaryentriessql = "$allentriessql AND g.mainglossary=0"; 2915 2916 $mainglossariessql = "$allglossariessql AND g.mainglossary=1"; 2917 $secondaryglossariessql = "$allglossariessql AND g.mainglossary=0"; 2918 2919 if (in_array('main', $data->reset_glossary_types)) { 2920 $params[] = 'glossary_entry'; 2921 $DB->delete_records_select('comments', "itemid IN ($mainentriessql) AND commentarea=?", $params); 2922 $DB->delete_records_select('glossary_entries', "glossaryid IN ($mainglossariessql)", $params); 2923 2924 if ($glossaries = $DB->get_records_sql($mainglossariessql, $params)) { 2925 foreach ($glossaries as $glossaryid=>$unused) { 2926 if (!$cm = get_coursemodule_from_instance('glossary', $glossaryid)) { 2927 continue; 2928 } 2929 $context = context_module::instance($cm->id); 2930 $fs->delete_area_files($context->id, 'mod_glossary', 'attachment'); 2931 2932 //delete ratings 2933 $ratingdeloptions->contextid = $context->id; 2934 $rm->delete_ratings($ratingdeloptions); 2935 2936 core_tag_tag::delete_instances('mod_glossary', null, $context->id); 2937 } 2938 } 2939 2940 // remove all grades from gradebook 2941 if (empty($data->reset_gradebook_grades)) { 2942 glossary_reset_gradebook($data->courseid, 'main'); 2943 } 2944 2945 $status[] = array('component'=>$componentstr, 'item'=>get_string('resetglossaries', 'glossary').': '.get_string('mainglossary', 'glossary'), 'error'=>false); 2946 2947 } else if (in_array('secondary', $data->reset_glossary_types)) { 2948 $params[] = 'glossary_entry'; 2949 $DB->delete_records_select('comments', "itemid IN ($secondaryentriessql) AND commentarea=?", $params); 2950 $DB->delete_records_select('glossary_entries', "glossaryid IN ($secondaryglossariessql)", $params); 2951 // remove exported source flag from entries in main glossary 2952 $DB->execute("UPDATE {glossary_entries} 2953 SET sourceglossaryid=0 2954 WHERE glossaryid IN ($mainglossariessql)", $params); 2955 2956 if ($glossaries = $DB->get_records_sql($secondaryglossariessql, $params)) { 2957 foreach ($glossaries as $glossaryid=>$unused) { 2958 if (!$cm = get_coursemodule_from_instance('glossary', $glossaryid)) { 2959 continue; 2960 } 2961 $context = context_module::instance($cm->id); 2962 $fs->delete_area_files($context->id, 'mod_glossary', 'attachment'); 2963 2964 //delete ratings 2965 $ratingdeloptions->contextid = $context->id; 2966 $rm->delete_ratings($ratingdeloptions); 2967 2968 core_tag_tag::delete_instances('mod_glossary', null, $context->id); 2969 } 2970 } 2971 2972 // remove all grades from gradebook 2973 if (empty($data->reset_gradebook_grades)) { 2974 glossary_reset_gradebook($data->courseid, 'secondary'); 2975 } 2976 2977 $status[] = array('component'=>$componentstr, 'item'=>get_string('resetglossaries', 'glossary').': '.get_string('secondaryglossary', 'glossary'), 'error'=>false); 2978 } 2979 } 2980 2981 // remove entries by users not enrolled into course 2982 if (!empty($data->reset_glossary_notenrolled)) { 2983 $entriessql = "SELECT e.id, e.userid, e.glossaryid, u.id AS userexists, u.deleted AS userdeleted 2984 FROM {glossary_entries} e 2985 JOIN {glossary} g ON e.glossaryid = g.id 2986 LEFT JOIN {user} u ON e.userid = u.id 2987 WHERE g.course = ? AND e.userid > 0"; 2988 2989 $course_context = context_course::instance($data->courseid); 2990 $notenrolled = array(); 2991 $rs = $DB->get_recordset_sql($entriessql, $params); 2992 if ($rs->valid()) { 2993 foreach ($rs as $entry) { 2994 if (array_key_exists($entry->userid, $notenrolled) or !$entry->userexists or $entry->userdeleted 2995 or !is_enrolled($course_context , $entry->userid)) { 2996 $DB->delete_records('comments', array('commentarea'=>'glossary_entry', 'itemid'=>$entry->id)); 2997 $DB->delete_records('glossary_entries', array('id'=>$entry->id)); 2998 2999 if ($cm = get_coursemodule_from_instance('glossary', $entry->glossaryid)) { 3000 $context = context_module::instance($cm->id); 3001 $fs->delete_area_files($context->id, 'mod_glossary', 'attachment', $entry->id); 3002 3003 //delete ratings 3004 $ratingdeloptions->contextid = $context->id; 3005 $rm->delete_ratings($ratingdeloptions); 3006 } 3007 } 3008 } 3009 $status[] = array('component'=>$componentstr, 'item'=>get_string('deletenotenrolled', 'glossary'), 'error'=>false); 3010 } 3011 $rs->close(); 3012 } 3013 3014 // remove all ratings 3015 if (!empty($data->reset_glossary_ratings)) { 3016 //remove ratings 3017 if ($glossaries = $DB->get_records_sql($allglossariessql, $params)) { 3018 foreach ($glossaries as $glossaryid=>$unused) { 3019 if (!$cm = get_coursemodule_from_instance('glossary', $glossaryid)) { 3020 continue; 3021 } 3022 $context = context_module::instance($cm->id); 3023 3024 //delete ratings 3025 $ratingdeloptions->contextid = $context->id; 3026 $rm->delete_ratings($ratingdeloptions); 3027 } 3028 } 3029 3030 // remove all grades from gradebook 3031 if (empty($data->reset_gradebook_grades)) { 3032 glossary_reset_gradebook($data->courseid); 3033 } 3034 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallratings'), 'error'=>false); 3035 } 3036 3037 // remove comments 3038 if (!empty($data->reset_glossary_comments)) { 3039 $params[] = 'glossary_entry'; 3040 $DB->delete_records_select('comments', "itemid IN ($allentriessql) AND commentarea= ? ", $params); 3041 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallcomments'), 'error'=>false); 3042 } 3043 3044 // Remove all the tags. 3045 if (!empty($data->reset_glossary_tags)) { 3046 if ($glossaries = $DB->get_records_sql($allglossariessql, $params)) { 3047 foreach ($glossaries as $glossaryid => $unused) { 3048 if (!$cm = get_coursemodule_from_instance('glossary', $glossaryid)) { 3049 continue; 3050 } 3051 3052 $context = context_module::instance($cm->id); 3053 core_tag_tag::delete_instances('mod_glossary', null, $context->id); 3054 } 3055 } 3056 3057 $status[] = array('component' => $componentstr, 'item' => get_string('tagsdeleted', 'glossary'), 'error' => false); 3058 } 3059 3060 /// updating dates - shift may be negative too 3061 if ($data->timeshift) { 3062 // Any changes to the list of dates that needs to be rolled should be same during course restore and course reset. 3063 // See MDL-9367. 3064 shift_course_mod_dates('glossary', array('assesstimestart', 'assesstimefinish'), $data->timeshift, $data->courseid); 3065 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false); 3066 } 3067 3068 return $status; 3069 } 3070 3071 /** 3072 * Returns all other caps used in module 3073 * @return array 3074 */ 3075 function glossary_get_extra_capabilities() { 3076 return ['moodle/rating:view', 'moodle/rating:viewany', 'moodle/rating:viewall', 'moodle/rating:rate', 3077 'moodle/comment:view', 'moodle/comment:post', 'moodle/comment:delete']; 3078 } 3079 3080 /** 3081 * @param string $feature FEATURE_xx constant for requested feature 3082 * @return mixed True if module supports feature, null if doesn't know 3083 */ 3084 function glossary_supports($feature) { 3085 switch($feature) { 3086 case FEATURE_GROUPS: return false; 3087 case FEATURE_GROUPINGS: return false; 3088 case FEATURE_MOD_INTRO: return true; 3089 case FEATURE_COMPLETION_TRACKS_VIEWS: return true; 3090 case FEATURE_COMPLETION_HAS_RULES: return true; 3091 case FEATURE_GRADE_HAS_GRADE: return true; 3092 case FEATURE_GRADE_OUTCOMES: return true; 3093 case FEATURE_RATE: return true; 3094 case FEATURE_BACKUP_MOODLE2: return true; 3095 case FEATURE_SHOW_DESCRIPTION: return true; 3096 case FEATURE_COMMENT: return true; 3097 3098 default: return null; 3099 } 3100 } 3101 3102 function glossary_extend_navigation($navigation, $course, $module, $cm) { 3103 global $CFG, $DB; 3104 3105 $displayformat = $DB->get_record('glossary_formats', array('name' => $module->displayformat)); 3106 // Get visible tabs for the format and check if the menu needs to be displayed. 3107 $showtabs = glossary_get_visible_tabs($displayformat); 3108 3109 foreach ($showtabs as $showtabkey => $showtabvalue) { 3110 3111 switch($showtabvalue) { 3112 case GLOSSARY_STANDARD : 3113 $navigation->add(get_string('standardview', 'glossary'), new moodle_url('/mod/glossary/view.php', 3114 array('id' => $cm->id, 'mode' => 'letter'))); 3115 break; 3116 case GLOSSARY_CATEGORY : 3117 $navigation->add(get_string('categoryview', 'glossary'), new moodle_url('/mod/glossary/view.php', 3118 array('id' => $cm->id, 'mode' => 'cat'))); 3119 break; 3120 case GLOSSARY_DATE : 3121 $navigation->add(get_string('dateview', 'glossary'), new moodle_url('/mod/glossary/view.php', 3122 array('id' => $cm->id, 'mode' => 'date'))); 3123 break; 3124 case GLOSSARY_AUTHOR : 3125 $navigation->add(get_string('authorview', 'glossary'), new moodle_url('/mod/glossary/view.php', 3126 array('id' => $cm->id, 'mode' => 'author'))); 3127 break; 3128 } 3129 } 3130 } 3131 3132 /** 3133 * Adds module specific settings to the settings block 3134 * 3135 * @param settings_navigation $settings The settings navigation object 3136 * @param navigation_node $glossarynode The node to add module settings to 3137 */ 3138 function glossary_extend_settings_navigation(settings_navigation $settings, navigation_node $glossarynode) { 3139 global $PAGE, $DB, $CFG, $USER; 3140 3141 $mode = optional_param('mode', '', PARAM_ALPHA); 3142 $hook = optional_param('hook', 'ALL', PARAM_CLEAN); 3143 3144 if (has_capability('mod/glossary:import', $PAGE->cm->context)) { 3145 $glossarynode->add(get_string('importentries', 'glossary'), new moodle_url('/mod/glossary/import.php', array('id'=>$PAGE->cm->id))); 3146 } 3147 3148 if (has_capability('mod/glossary:export', $PAGE->cm->context)) { 3149 $glossarynode->add(get_string('exportentries', 'glossary'), new moodle_url('/mod/glossary/export.php', array('id'=>$PAGE->cm->id, 'mode'=>$mode, 'hook'=>$hook))); 3150 } 3151 3152 if (has_capability('mod/glossary:approve', $PAGE->cm->context) && ($hiddenentries = $DB->count_records('glossary_entries', array('glossaryid'=>$PAGE->cm->instance, 'approved'=>0)))) { 3153 $glossarynode->add(get_string('waitingapproval', 'glossary'), new moodle_url('/mod/glossary/view.php', array('id'=>$PAGE->cm->id, 'mode'=>'approval'))); 3154 } 3155 3156 if (has_capability('mod/glossary:write', $PAGE->cm->context)) { 3157 $glossarynode->add(get_string('addentry', 'glossary'), new moodle_url('/mod/glossary/edit.php', array('cmid'=>$PAGE->cm->id))); 3158 } 3159 3160 $glossary = $DB->get_record('glossary', array("id" => $PAGE->cm->instance)); 3161 3162 if (!empty($CFG->enablerssfeeds) && !empty($CFG->glossary_enablerssfeeds) && $glossary->rsstype && $glossary->rssarticles && has_capability('mod/glossary:view', $PAGE->cm->context)) { 3163 require_once("$CFG->libdir/rsslib.php"); 3164 3165 $string = get_string('rsstype', 'glossary'); 3166 3167 $url = new moodle_url(rss_get_url($PAGE->cm->context->id, $USER->id, 'mod_glossary', $glossary->id)); 3168 $glossarynode->add($string, $url, settings_navigation::TYPE_SETTING, null, null, new pix_icon('i/rss', '')); 3169 } 3170 } 3171 3172 /** 3173 * Running addtional permission check on plugin, for example, plugins 3174 * may have switch to turn on/off comments option, this callback will 3175 * affect UI display, not like pluginname_comment_validate only throw 3176 * exceptions. 3177 * Capability check has been done in comment->check_permissions(), we 3178 * don't need to do it again here. 3179 * 3180 * @package mod_glossary 3181 * @category comment 3182 * 3183 * @param stdClass $comment_param { 3184 * context => context the context object 3185 * courseid => int course id 3186 * cm => stdClass course module object 3187 * commentarea => string comment area 3188 * itemid => int itemid 3189 * } 3190 * @return array 3191 */ 3192 function glossary_comment_permissions($comment_param) { 3193 return array('post'=>true, 'view'=>true); 3194 } 3195 3196 /** 3197 * Validate comment parameter before perform other comments actions 3198 * 3199 * @package mod_glossary 3200 * @category comment 3201 * 3202 * @param stdClass $comment_param { 3203 * context => context the context object 3204 * courseid => int course id 3205 * cm => stdClass course module object 3206 * commentarea => string comment area 3207 * itemid => int itemid 3208 * } 3209 * @return boolean 3210 */ 3211 function glossary_comment_validate($comment_param) { 3212 global $DB; 3213 // validate comment area 3214 if ($comment_param->commentarea != 'glossary_entry') { 3215 throw new comment_exception('invalidcommentarea'); 3216 } 3217 if (!$record = $DB->get_record('glossary_entries', array('id'=>$comment_param->itemid))) { 3218 throw new comment_exception('invalidcommentitemid'); 3219 } 3220 if ($record->sourceglossaryid && $record->sourceglossaryid == $comment_param->cm->instance) { 3221 $glossary = $DB->get_record('glossary', array('id'=>$record->sourceglossaryid)); 3222 } else { 3223 $glossary = $DB->get_record('glossary', array('id'=>$record->glossaryid)); 3224 } 3225 if (!$glossary) { 3226 throw new comment_exception('invalidid', 'data'); 3227 } 3228 if (!$course = $DB->get_record('course', array('id'=>$glossary->course))) { 3229 throw new comment_exception('coursemisconf'); 3230 } 3231 if (!$cm = get_coursemodule_from_instance('glossary', $glossary->id, $course->id)) { 3232 throw new comment_exception('invalidcoursemodule'); 3233 } 3234 $context = context_module::instance($cm->id); 3235 3236 if ($glossary->defaultapproval and !$record->approved and !has_capability('mod/glossary:approve', $context)) { 3237 throw new comment_exception('notapproved', 'glossary'); 3238 } 3239 // validate context id 3240 if ($context->id != $comment_param->context->id) { 3241 throw new comment_exception('invalidcontext'); 3242 } 3243 // validation for comment deletion 3244 if (!empty($comment_param->commentid)) { 3245 if ($comment = $DB->get_record('comments', array('id'=>$comment_param->commentid))) { 3246 if ($comment->commentarea != 'glossary_entry') { 3247 throw new comment_exception('invalidcommentarea'); 3248 } 3249 if ($comment->contextid != $comment_param->context->id) { 3250 throw new comment_exception('invalidcontext'); 3251 } 3252 if ($comment->itemid != $comment_param->itemid) { 3253 throw new comment_exception('invalidcommentitemid'); 3254 } 3255 } else { 3256 throw new comment_exception('invalidcommentid'); 3257 } 3258 } 3259 return true; 3260 } 3261 3262 /** 3263 * Return a list of page types 3264 * @param string $pagetype current page type 3265 * @param stdClass $parentcontext Block's parent context 3266 * @param stdClass $currentcontext Current context of block 3267 */ 3268 function glossary_page_type_list($pagetype, $parentcontext, $currentcontext) { 3269 $module_pagetype = array( 3270 'mod-glossary-*'=>get_string('page-mod-glossary-x', 'glossary'), 3271 'mod-glossary-view'=>get_string('page-mod-glossary-view', 'glossary'), 3272 'mod-glossary-edit'=>get_string('page-mod-glossary-edit', 'glossary')); 3273 return $module_pagetype; 3274 } 3275 3276 /** 3277 * Return list of all glossary tabs. 3278 * @throws coding_exception 3279 * @return array 3280 */ 3281 function glossary_get_all_tabs() { 3282 3283 return array ( 3284 GLOSSARY_AUTHOR => get_string('authorview', 'glossary'), 3285 GLOSSARY_CATEGORY => get_string('categoryview', 'glossary'), 3286 GLOSSARY_DATE => get_string('dateview', 'glossary') 3287 ); 3288 } 3289 3290 /** 3291 * Set 'showtabs' value for glossary formats 3292 * @param stdClass $glossaryformat record from 'glossary_formats' table 3293 */ 3294 function glossary_set_default_visible_tabs($glossaryformat) { 3295 global $DB; 3296 3297 switch($glossaryformat->name) { 3298 case GLOSSARY_CONTINUOUS: 3299 $showtabs = 'standard,category,date'; 3300 break; 3301 case GLOSSARY_DICTIONARY: 3302 $showtabs = 'standard'; 3303 // Special code for upgraded instances that already had categories set up 3304 // in this format - enable "category" tab. 3305 // In new instances only 'standard' tab will be visible. 3306 if ($DB->record_exists_sql("SELECT 1 3307 FROM {glossary} g, {glossary_categories} gc 3308 WHERE g.id = gc.glossaryid and g.displayformat = ?", 3309 array(GLOSSARY_DICTIONARY))) { 3310 $showtabs .= ',category'; 3311 } 3312 break; 3313 case GLOSSARY_FULLWITHOUTAUTHOR: 3314 $showtabs = 'standard,category,date'; 3315 break; 3316 default: 3317 $showtabs = 'standard,category,date,author'; 3318 break; 3319 } 3320 3321 $DB->set_field('glossary_formats', 'showtabs', $showtabs, array('id' => $glossaryformat->id)); 3322 $glossaryformat->showtabs = $showtabs; 3323 } 3324 3325 /** 3326 * Convert 'showtabs' string to array 3327 * @param stdClass $displayformat record from 'glossary_formats' table 3328 * @return array 3329 */ 3330 function glossary_get_visible_tabs($displayformat) { 3331 if (empty($displayformat->showtabs)) { 3332 glossary_set_default_visible_tabs($displayformat); 3333 } 3334 $showtabs = preg_split('/,/', $displayformat->showtabs, -1, PREG_SPLIT_NO_EMPTY); 3335 3336 return $showtabs; 3337 } 3338 3339 /** 3340 * Notify that the glossary was viewed. 3341 * 3342 * This will trigger relevant events and activity completion. 3343 * 3344 * @param stdClass $glossary The glossary object. 3345 * @param stdClass $course The course object. 3346 * @param stdClass $cm The course module object. 3347 * @param stdClass $context The context object. 3348 * @param string $mode The mode in which the glossary was viewed. 3349 * @since Moodle 3.1 3350 */ 3351 function glossary_view($glossary, $course, $cm, $context, $mode) { 3352 3353 // Completion trigger. 3354 $completion = new completion_info($course); 3355 $completion->set_module_viewed($cm); 3356 3357 // Trigger the course module viewed event. 3358 $event = \mod_glossary\event\course_module_viewed::create(array( 3359 'objectid' => $glossary->id, 3360 'context' => $context, 3361 'other' => array('mode' => $mode) 3362 )); 3363 $event->add_record_snapshot('course', $course); 3364 $event->add_record_snapshot('course_modules', $cm); 3365 $event->add_record_snapshot('glossary', $glossary); 3366 $event->trigger(); 3367 } 3368 3369 /** 3370 * Notify that a glossary entry was viewed. 3371 * 3372 * This will trigger relevant events. 3373 * 3374 * @param stdClass $entry The entry object. 3375 * @param stdClass $context The context object. 3376 * @since Moodle 3.1 3377 */ 3378 function glossary_entry_view($entry, $context) { 3379 3380 // Trigger the entry viewed event. 3381 $event = \mod_glossary\event\entry_viewed::create(array( 3382 'objectid' => $entry->id, 3383 'context' => $context 3384 )); 3385 $event->add_record_snapshot('glossary_entries', $entry); 3386 $event->trigger(); 3387 3388 } 3389 3390 /** 3391 * Returns the entries of a glossary by letter. 3392 * 3393 * @param object $glossary The glossary. 3394 * @param context $context The context of the glossary. 3395 * @param string $letter The letter, or ALL, or SPECIAL. 3396 * @param int $from Fetch records from. 3397 * @param int $limit Number of records to fetch. 3398 * @param array $options Accepts: 3399 * - (bool) includenotapproved. When false, includes the non-approved entries created by 3400 * the current user. When true, also includes the ones that the user has the permission to approve. 3401 * @return array The first element being the recordset, the second the number of entries. 3402 * @since Moodle 3.1 3403 */ 3404 function glossary_get_entries_by_letter($glossary, $context, $letter, $from, $limit, $options = array()) { 3405 3406 $qb = new mod_glossary_entry_query_builder($glossary); 3407 if ($letter != 'ALL' && $letter != 'SPECIAL' && core_text::strlen($letter)) { 3408 $qb->filter_by_concept_letter($letter); 3409 } 3410 if ($letter == 'SPECIAL') { 3411 $qb->filter_by_concept_non_letter(); 3412 } 3413 3414 if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) { 3415 $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL); 3416 } else { 3417 $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF); 3418 } 3419 3420 $qb->add_field('*', 'entries'); 3421 $qb->join_user(); 3422 $qb->add_user_fields(); 3423 $qb->order_by('concept', 'entries'); 3424 $qb->order_by('id', 'entries', 'ASC'); // Sort on ID to avoid random ordering when entries share an ordering value. 3425 $qb->limit($from, $limit); 3426 3427 // Fetching the entries. 3428 $count = $qb->count_records(); 3429 $entries = $qb->get_records(); 3430 3431 return array($entries, $count); 3432 } 3433 3434 /** 3435 * Returns the entries of a glossary by date. 3436 * 3437 * @param object $glossary The glossary. 3438 * @param context $context The context of the glossary. 3439 * @param string $order The mode of ordering: CREATION or UPDATE. 3440 * @param string $sort The direction of the ordering: ASC or DESC. 3441 * @param int $from Fetch records from. 3442 * @param int $limit Number of records to fetch. 3443 * @param array $options Accepts: 3444 * - (bool) includenotapproved. When false, includes the non-approved entries created by 3445 * the current user. When true, also includes the ones that the user has the permission to approve. 3446 * @return array The first element being the recordset, the second the number of entries. 3447 * @since Moodle 3.1 3448 */ 3449 function glossary_get_entries_by_date($glossary, $context, $order, $sort, $from, $limit, $options = array()) { 3450 3451 $qb = new mod_glossary_entry_query_builder($glossary); 3452 if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) { 3453 $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL); 3454 } else { 3455 $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF); 3456 } 3457 3458 $qb->add_field('*', 'entries'); 3459 $qb->join_user(); 3460 $qb->add_user_fields(); 3461 $qb->limit($from, $limit); 3462 3463 if ($order == 'CREATION') { 3464 $qb->order_by('timecreated', 'entries', $sort); 3465 } else { 3466 $qb->order_by('timemodified', 'entries', $sort); 3467 } 3468 $qb->order_by('id', 'entries', $sort); // Sort on ID to avoid random ordering when entries share an ordering value. 3469 3470 // Fetching the entries. 3471 $count = $qb->count_records(); 3472 $entries = $qb->get_records(); 3473 3474 return array($entries, $count); 3475 } 3476 3477 /** 3478 * Returns the entries of a glossary by category. 3479 * 3480 * @param object $glossary The glossary. 3481 * @param context $context The context of the glossary. 3482 * @param int $categoryid The category ID, or GLOSSARY_SHOW_* constant. 3483 * @param int $from Fetch records from. 3484 * @param int $limit Number of records to fetch. 3485 * @param array $options Accepts: 3486 * - (bool) includenotapproved. When false, includes the non-approved entries created by 3487 * the current user. When true, also includes the ones that the user has the permission to approve. 3488 * @return array The first element being the recordset, the second the number of entries. 3489 * @since Moodle 3.1 3490 */ 3491 function glossary_get_entries_by_category($glossary, $context, $categoryid, $from, $limit, $options = array()) { 3492 3493 $qb = new mod_glossary_entry_query_builder($glossary); 3494 if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) { 3495 $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL); 3496 } else { 3497 $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF); 3498 } 3499 3500 $qb->join_category($categoryid); 3501 $qb->join_user(); 3502 3503 // The first field must be the relationship ID when viewing all categories. 3504 if ($categoryid === GLOSSARY_SHOW_ALL_CATEGORIES) { 3505 $qb->add_field('id', 'entries_categories', 'cid'); 3506 } 3507 3508 $qb->add_field('*', 'entries'); 3509 $qb->add_field('categoryid', 'entries_categories'); 3510 $qb->add_user_fields(); 3511 3512 if ($categoryid === GLOSSARY_SHOW_ALL_CATEGORIES) { 3513 $qb->add_field('name', 'categories', 'categoryname'); 3514 $qb->order_by('name', 'categories'); 3515 3516 } else if ($categoryid === GLOSSARY_SHOW_NOT_CATEGORISED) { 3517 $qb->where('categoryid', 'entries_categories', null); 3518 } 3519 3520 // Sort on additional fields to avoid random ordering when entries share an ordering value. 3521 $qb->order_by('concept', 'entries'); 3522 $qb->order_by('id', 'entries', 'ASC'); 3523 $qb->limit($from, $limit); 3524 3525 // Fetching the entries. 3526 $count = $qb->count_records(); 3527 $entries = $qb->get_records(); 3528 3529 return array($entries, $count); 3530 } 3531 3532 /** 3533 * Returns the entries of a glossary by author. 3534 * 3535 * @param object $glossary The glossary. 3536 * @param context $context The context of the glossary. 3537 * @param string $letter The letter 3538 * @param string $field The field to search: FIRSTNAME or LASTNAME. 3539 * @param string $sort The sorting: ASC or DESC. 3540 * @param int $from Fetch records from. 3541 * @param int $limit Number of records to fetch. 3542 * @param array $options Accepts: 3543 * - (bool) includenotapproved. When false, includes the non-approved entries created by 3544 * the current user. When true, also includes the ones that the user has the permission to approve. 3545 * @return array The first element being the recordset, the second the number of entries. 3546 * @since Moodle 3.1 3547 */ 3548 function glossary_get_entries_by_author($glossary, $context, $letter, $field, $sort, $from, $limit, $options = array()) { 3549 3550 $firstnamefirst = $field === 'FIRSTNAME'; 3551 $qb = new mod_glossary_entry_query_builder($glossary); 3552 if ($letter != 'ALL' && $letter != 'SPECIAL' && core_text::strlen($letter)) { 3553 $qb->filter_by_author_letter($letter, $firstnamefirst); 3554 } 3555 if ($letter == 'SPECIAL') { 3556 $qb->filter_by_author_non_letter($firstnamefirst); 3557 } 3558 3559 if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) { 3560 $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL); 3561 } else { 3562 $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF); 3563 } 3564 3565 $qb->add_field('*', 'entries'); 3566 $qb->join_user(true); 3567 $qb->add_user_fields(); 3568 $qb->order_by_author($firstnamefirst, $sort); 3569 $qb->order_by('concept', 'entries'); 3570 $qb->order_by('id', 'entries', 'ASC'); // Sort on ID to avoid random ordering when entries share an ordering value. 3571 $qb->limit($from, $limit); 3572 3573 // Fetching the entries. 3574 $count = $qb->count_records(); 3575 $entries = $qb->get_records(); 3576 3577 return array($entries, $count); 3578 } 3579 3580 /** 3581 * Returns the entries of a glossary by category. 3582 * 3583 * @param object $glossary The glossary. 3584 * @param context $context The context of the glossary. 3585 * @param int $authorid The author ID. 3586 * @param string $order The mode of ordering: CONCEPT, CREATION or UPDATE. 3587 * @param string $sort The direction of the ordering: ASC or DESC. 3588 * @param int $from Fetch records from. 3589 * @param int $limit Number of records to fetch. 3590 * @param array $options Accepts: 3591 * - (bool) includenotapproved. When false, includes the non-approved entries created by 3592 * the current user. When true, also includes the ones that the user has the permission to approve. 3593 * @return array The first element being the recordset, the second the number of entries. 3594 * @since Moodle 3.1 3595 */ 3596 function glossary_get_entries_by_author_id($glossary, $context, $authorid, $order, $sort, $from, $limit, $options = array()) { 3597 3598 $qb = new mod_glossary_entry_query_builder($glossary); 3599 if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) { 3600 $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_ALL); 3601 } else { 3602 $qb->filter_by_non_approved(mod_glossary_entry_query_builder::NON_APPROVED_SELF); 3603 } 3604 3605 $qb->add_field('*', 'entries'); 3606 $qb->join_user(true); 3607 $qb->add_user_fields(); 3608 $qb->where('id', 'user', $authorid); 3609 3610 if ($order == 'CREATION') { 3611 $qb->order_by('timecreated', 'entries', $sort); 3612 } else if ($order == 'UPDATE') { 3613 $qb->order_by('timemodified', 'entries', $sort); 3614 } else { 3615 $qb->order_by('concept', 'entries', $sort); 3616 } 3617 $qb->order_by('id', 'entries', $sort); // Sort on ID to avoid random ordering when entries share an ordering value. 3618 3619 $qb->limit($from, $limit); 3620 3621 // Fetching the entries. 3622 $count = $qb->count_records(); 3623 $entries = $qb->get_records(); 3624 3625 return array($entries, $count); 3626 } 3627 3628 /** 3629 * Returns the authors in a glossary 3630 * 3631 * @param object $glossary The glossary. 3632 * @param context $context The context of the glossary. 3633 * @param int $limit Number of records to fetch. 3634 * @param int $from Fetch records from. 3635 * @param array $options Accepts: 3636 * - (bool) includenotapproved. When false, includes self even if all of their entries require approval. 3637 * When true, also includes authors only having entries pending approval. 3638 * @return array The first element being the recordset, the second the number of entries. 3639 * @since Moodle 3.1 3640 */ 3641 function glossary_get_authors($glossary, $context, $limit, $from, $options = array()) { 3642 global $DB, $USER; 3643 3644 $params = array(); 3645 $userfieldsapi = \core_user\fields::for_userpic(); 3646 $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects; 3647 3648 $approvedsql = '(ge.approved <> 0 OR ge.userid = :myid)'; 3649 $params['myid'] = $USER->id; 3650 if (!empty($options['includenotapproved']) && has_capability('mod/glossary:approve', $context)) { 3651 $approvedsql = '1 = 1'; 3652 } 3653 3654 $sqlselectcount = "SELECT COUNT(DISTINCT(u.id))"; 3655 $sqlselect = "SELECT DISTINCT(u.id) AS userId, $userfields"; 3656 $sql = " FROM {user} u 3657 JOIN {glossary_entries} ge 3658 ON ge.userid = u.id 3659 AND (ge.glossaryid = :gid1 OR ge.sourceglossaryid = :gid2) 3660 AND $approvedsql"; 3661 $ordersql = " ORDER BY u.lastname, u.firstname"; 3662 3663 $params['gid1'] = $glossary->id; 3664 $params['gid2'] = $glossary->id; 3665 3666 $count = $DB->count_records_sql($sqlselectcount . $sql, $params); 3667 $users = $DB->get_recordset_sql($sqlselect . $sql . $ordersql, $params, $from, $limit); 3668 3669 return array($users, $count); 3670 } 3671 3672 /** 3673 * Returns the categories of a glossary. 3674 * 3675 * @param object $glossary The glossary. 3676 * @param int $from Fetch records from. 3677 * @param int $limit Number of records to fetch. 3678 * @return array The first element being the recordset, the second the number of entries. 3679 * @since Moodle 3.1 3680 */ 3681 function glossary_get_categories($glossary, $from, $limit) { 3682 global $DB; 3683 3684 $count = $DB->count_records('glossary_categories', array('glossaryid' => $glossary->id)); 3685 $categories = $DB->get_recordset('glossary_categories', array('glossaryid' => $glossary->id), 'name ASC', '*', $from, $limit);