Differences Between: [Versions 310 and 400] [Versions 311 and 400] [Versions 39 and 400] [Versions 400 and 401] [Versions 400 and 402] [Versions 400 and 403]
1 <?php 2 3 // This file is part of Moodle - http://moodle.org/ 4 // 5 // Moodle is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // Moodle is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 17 18 /** 19 * Library of functions and constants for module glossary 20 * outside of what is required for the core moodle api 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 27 require_once($CFG->libdir . '/portfolio/caller.php'); 28 require_once($CFG->libdir . '/filelib.php'); 29 30 /** 31 * class to handle exporting an entire glossary database 32 */ 33 class glossary_full_portfolio_caller extends portfolio_module_caller_base { 34 35 private $glossary; 36 private $exportdata; 37 private $keyedfiles = array(); // keyed on entry 38 39 /** 40 * return array of expected call back arguments 41 * and whether they are required or not 42 * 43 * @return array 44 */ 45 public static function expected_callbackargs() { 46 return array( 47 'id' => true, 48 ); 49 } 50 51 /** 52 * load up all data required for this export. 53 * 54 * @return void 55 */ 56 public function load_data() { 57 global $DB; 58 if (!$this->cm = get_coursemodule_from_id('glossary', $this->id)) { 59 throw new portfolio_caller_exception('invalidid', 'glossary'); 60 } 61 if (!$this->glossary = $DB->get_record('glossary', array('id' => $this->cm->instance))) { 62 throw new portfolio_caller_exception('invalidid', 'glossary'); 63 } 64 $entries = $DB->get_records('glossary_entries', array('glossaryid' => $this->glossary->id)); 65 list($where, $params) = $DB->get_in_or_equal(array_keys($entries)); 66 67 $aliases = $DB->get_records_select('glossary_alias', 'entryid ' . $where, $params); 68 $categoryentries = $DB->get_records_sql('SELECT ec.entryid, c.name FROM {glossary_entries_categories} ec 69 JOIN {glossary_categories} c 70 ON c.id = ec.categoryid 71 WHERE ec.entryid ' . $where, $params); 72 73 $this->exportdata = array('entries' => $entries, 'aliases' => $aliases, 'categoryentries' => $categoryentries); 74 $fs = get_file_storage(); 75 $context = context_module::instance($this->cm->id); 76 $this->multifiles = array(); 77 foreach (array_keys($entries) as $entry) { 78 $this->keyedfiles[$entry] = array_merge( 79 $fs->get_area_files($context->id, 'mod_glossary', 'attachment', $entry, "timemodified", false), 80 $fs->get_area_files($context->id, 'mod_glossary', 'entry', $entry, "timemodified", false) 81 ); 82 $this->multifiles = array_merge($this->multifiles, $this->keyedfiles[$entry]); 83 } 84 } 85 86 /** 87 * how long might we expect this export to take 88 * 89 * @return constant one of PORTFOLIO_TIME_XX 90 */ 91 public function expected_time() { 92 $filetime = portfolio_expected_time_file($this->multifiles); 93 $dbtime = portfolio_expected_time_db(count($this->exportdata['entries'])); 94 return ($filetime > $dbtime) ? $filetime : $dbtime; 95 } 96 97 /** 98 * return the sha1 of this content 99 * 100 * @return string 101 */ 102 public function get_sha1() { 103 $file = ''; 104 if ($this->multifiles) { 105 $file = $this->get_sha1_file(); 106 } 107 return sha1(serialize($this->exportdata) . $file); 108 } 109 110 /** 111 * prepare the package ready to be passed off to the portfolio plugin 112 * 113 * @return void 114 */ 115 public function prepare_package() { 116 $entries = $this->exportdata['entries']; 117 $aliases = array(); 118 $categories = array(); 119 if (is_array($this->exportdata['aliases'])) { 120 foreach ($this->exportdata['aliases'] as $alias) { 121 if (!array_key_exists($alias->entryid, $aliases)) { 122 $aliases[$alias->entryid] = array(); 123 } 124 $aliases[$alias->entryid][] = $alias->alias; 125 } 126 } 127 if (is_array($this->exportdata['categoryentries'])) { 128 foreach ($this->exportdata['categoryentries'] as $cat) { 129 if (!array_key_exists($cat->entryid, $categories)) { 130 $categories[$cat->entryid] = array(); 131 } 132 $categories[$cat->entryid][] = $cat->name; 133 } 134 } 135 if ($this->get('exporter')->get('formatclass') == PORTFOLIO_FORMAT_SPREADSHEET) { 136 $csv = glossary_generate_export_csv($entries, $aliases, $categories); 137 $this->exporter->write_new_file($csv, clean_filename($this->cm->name) . '.csv', false); 138 return; 139 } else if ($this->get('exporter')->get('formatclass') == PORTFOLIO_FORMAT_LEAP2A) { 140 $ids = array(); // keep track of these to make into a selection later 141 global $USER, $DB; 142 $writer = $this->get('exporter')->get('format')->leap2a_writer($USER); 143 $format = $this->exporter->get('format'); 144 $filename = $this->get('exporter')->get('format')->manifest_name(); 145 foreach ($entries as $e) { 146 $content = glossary_entry_portfolio_caller::entry_content( 147 $this->course, 148 $this->cm, 149 $this->glossary, 150 $e, 151 (array_key_exists($e->id, $aliases) ? $aliases[$e->id] : array()), 152 $format 153 ); 154 $entry = new portfolio_format_leap2a_entry('glossaryentry' . $e->id, $e->concept, 'entry', $content); 155 $entry->author = $DB->get_record('user', array('id' => $e->userid), 'id,firstname,lastname,email'); 156 $entry->published = $e->timecreated; 157 $entry->updated = $e->timemodified; 158 if (!empty($this->keyedfiles[$e->id])) { 159 $writer->link_files($entry, $this->keyedfiles[$e->id], 'glossaryentry' . $e->id . 'file'); 160 foreach ($this->keyedfiles[$e->id] as $file) { 161 $this->exporter->copy_existing_file($file); 162 } 163 } 164 if (!empty($categories[$e->id])) { 165 foreach ($categories[$e->id] as $cat) { 166 // this essentially treats them as plain tags 167 // leap has the idea of category schemes 168 // but I think this is overkill here 169 $entry->add_category($cat); 170 } 171 } 172 $writer->add_entry($entry); 173 $ids[] = $entry->id; 174 } 175 $selection = new portfolio_format_leap2a_entry('wholeglossary' . $this->glossary->id, get_string('modulename', 'glossary'), 'selection'); 176 $writer->add_entry($selection); 177 $writer->make_selection($selection, $ids, 'Grouping'); 178 $content = $writer->to_xml(); 179 } 180 $this->exporter->write_new_file($content, $filename, true); 181 } 182 183 /** 184 * make sure that the current user is allowed to do this 185 * 186 * @return boolean 187 */ 188 public function check_permissions() { 189 return has_capability('mod/glossary:export', context_module::instance($this->cm->id)); 190 } 191 192 /** 193 * return a nice name to be displayed about this export location 194 * 195 * @return string 196 */ 197 public static function display_name() { 198 return get_string('modulename', 'glossary'); 199 } 200 201 /** 202 * what formats this function *generally* supports 203 * 204 * @return array 205 */ 206 public static function base_supported_formats() { 207 return array(PORTFOLIO_FORMAT_SPREADSHEET, PORTFOLIO_FORMAT_LEAP2A); 208 } 209 } 210 211 /** 212 * class to export a single glossary entry 213 * 214 * @package mod_glossary 215 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} 216 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 217 */ 218 class glossary_entry_portfolio_caller extends portfolio_module_caller_base { 219 220 private $glossary; 221 private $entry; 222 protected $entryid; 223 224 /** @var array Array that contains all aliases for the given glossary entry. */ 225 private $aliases = []; 226 227 /* 228 * @return array 229 */ 230 public static function expected_callbackargs() { 231 return array( 232 'entryid' => true, 233 'id' => true, 234 ); 235 } 236 237 /** 238 * load up all data required for this export. 239 * 240 * @return void 241 */ 242 public function load_data() { 243 global $DB; 244 if (!$this->cm = get_coursemodule_from_id('glossary', $this->id)) { 245 throw new portfolio_caller_exception('invalidid', 'glossary'); 246 } 247 if (!$this->glossary = $DB->get_record('glossary', array('id' => $this->cm->instance))) { 248 throw new portfolio_caller_exception('invalidid', 'glossary'); 249 } 250 if ($this->entryid) { 251 if (!$this->entry = $DB->get_record('glossary_entries', array('id' => $this->entryid))) { 252 throw new portfolio_caller_exception('noentry', 'glossary'); 253 } 254 // in case we don't have USER this will make the entry be printed 255 $this->entry->approved = true; 256 } 257 $this->categories = $DB->get_records_sql('SELECT ec.entryid, c.name FROM {glossary_entries_categories} ec 258 JOIN {glossary_categories} c 259 ON c.id = ec.categoryid 260 WHERE ec.entryid = ?', array($this->entryid)); 261 $context = context_module::instance($this->cm->id); 262 if ($this->entry->sourceglossaryid == $this->cm->instance) { 263 if ($maincm = get_coursemodule_from_instance('glossary', $this->entry->glossaryid)) { 264 $context = context_module::instance($maincm->id); 265 } 266 } 267 $this->aliases = $DB->get_records('glossary_alias', ['entryid' => $this->entryid]); 268 $fs = get_file_storage(); 269 $this->multifiles = array_merge( 270 $fs->get_area_files($context->id, 'mod_glossary', 'attachment', $this->entry->id, "timemodified", false), 271 $fs->get_area_files($context->id, 'mod_glossary', 'entry', $this->entry->id, "timemodified", false) 272 ); 273 274 if (!empty($this->multifiles)) { 275 $this->add_format(PORTFOLIO_FORMAT_RICHHTML); 276 } else { 277 $this->add_format(PORTFOLIO_FORMAT_PLAINHTML); 278 } 279 } 280 281 /** 282 * how long might we expect this export to take 283 * 284 * @return constant one of PORTFOLIO_TIME_XX 285 */ 286 public function expected_time() { 287 return PORTFOLIO_TIME_LOW; 288 } 289 290 /** 291 * make sure that the current user is allowed to do this 292 * 293 * @return boolean 294 */ 295 public function check_permissions() { 296 $context = context_module::instance($this->cm->id); 297 return has_capability('mod/glossary:exportentry', $context) 298 || ($this->entry->userid == $this->user->id && has_capability('mod/glossary:exportownentry', $context)); 299 } 300 301 /** 302 * return a nice name to be displayed about this export location 303 * 304 * @return string 305 */ 306 public static function display_name() { 307 return get_string('modulename', 'glossary'); 308 } 309 310 /** 311 * prepare the package ready to be passed off to the portfolio plugin 312 * 313 * @return void 314 */ 315 public function prepare_package() { 316 global $DB; 317 318 $format = $this->exporter->get('format'); 319 $content = self::entry_content($this->course, $this->cm, $this->glossary, $this->entry, $this->aliases, $format); 320 321 if ($this->exporter->get('formatclass') === PORTFOLIO_FORMAT_PLAINHTML) { 322 $filename = clean_filename($this->entry->concept) . '.html'; 323 $this->exporter->write_new_file($content, $filename); 324 325 } else if ($this->exporter->get('formatclass') === PORTFOLIO_FORMAT_RICHHTML) { 326 if ($this->multifiles) { 327 foreach ($this->multifiles as $file) { 328 $this->exporter->copy_existing_file($file); 329 } 330 } 331 $filename = clean_filename($this->entry->concept) . '.html'; 332 $this->exporter->write_new_file($content, $filename); 333 334 } else if ($this->exporter->get('formatclass') === PORTFOLIO_FORMAT_LEAP2A) { 335 $writer = $this->get('exporter')->get('format')->leap2a_writer(); 336 $entry = new portfolio_format_leap2a_entry('glossaryentry' . $this->entry->id, $this->entry->concept, 'entry', $content); 337 $entry->author = $DB->get_record('user', array('id' => $this->entry->userid), 'id,firstname,lastname,email'); 338 $entry->published = $this->entry->timecreated; 339 $entry->updated = $this->entry->timemodified; 340 if ($this->multifiles) { 341 $writer->link_files($entry, $this->multifiles); 342 foreach ($this->multifiles as $file) { 343 $this->exporter->copy_existing_file($file); 344 } 345 } 346 if ($this->categories) { 347 foreach ($this->categories as $cat) { 348 // this essentially treats them as plain tags 349 // leap has the idea of category schemes 350 // but I think this is overkill here 351 $entry->add_category($cat->name); 352 } 353 } 354 $writer->add_entry($entry); 355 $content = $writer->to_xml(); 356 $filename = $this->get('exporter')->get('format')->manifest_name(); 357 $this->exporter->write_new_file($content, $filename); 358 359 } else { 360 throw new portfolio_caller_exception('unexpected_format_class', 'glossary'); 361 } 362 } 363 364 /** 365 * return the sha1 of this content 366 * 367 * @return string 368 */ 369 public function get_sha1() { 370 if ($this->multifiles) { 371 return sha1(serialize($this->entry) . $this->get_sha1_file()); 372 } 373 return sha1(serialize($this->entry)); 374 } 375 376 /** 377 * what formats this function *generally* supports 378 * 379 * @return array 380 */ 381 public static function base_supported_formats() { 382 return array(PORTFOLIO_FORMAT_RICHHTML, PORTFOLIO_FORMAT_PLAINHTML, PORTFOLIO_FORMAT_LEAP2A); 383 } 384 385 /** 386 * helper function to get the html content of an entry 387 * for both this class and the full glossary exporter 388 * this is a very simplified version of the dictionary format output, 389 * but with its 500 levels of indirection removed 390 * and file rewriting handled by the portfolio export format. 391 * 392 * @param stdclass $course 393 * @param stdclass $cm 394 * @param stdclass $glossary 395 * @param stdclass $entry 396 * 397 * @return string 398 */ 399 public static function entry_content($course, $cm, $glossary, $entry, $aliases, $format) { 400 global $OUTPUT, $DB; 401 $entry = clone $entry; 402 $context = context_module::instance($cm->id); 403 $options = portfolio_format_text_options(); 404 $options->trusted = $entry->definitiontrust; 405 $options->context = $context; 406 407 $output = '<table class="glossarypost dictionary" cellspacing="0">' . "\n"; 408 $output .= '<tr valign="top">' . "\n"; 409 $output .= '<td class="entry">' . "\n"; 410 411 $output .= '<div class="concept">'; 412 $output .= format_text($OUTPUT->heading($entry->concept, 3), FORMAT_MOODLE, $options); 413 $output .= '</div> ' . "\n"; 414 415 $entry->definition = format_text($entry->definition, $entry->definitionformat, $options); 416 $output .= portfolio_rewrite_pluginfile_urls($entry->definition, $context->id, 'mod_glossary', 'entry', $entry->id, $format); 417 418 if (isset($entry->footer)) { 419 $output .= $entry->footer; 420 } 421 422 $output .= '</td></tr>' . "\n"; 423 424 if (!empty($aliases)) { 425 $output .= '<tr valign="top"><td class="entrylowersection">'; 426 $key = (count($aliases) == 1) ? 'alias' : 'aliases'; 427 $output .= get_string($key, 'glossary') . ': '; 428 foreach ($aliases as $alias) { 429 $output .= s($alias->alias) . ','; 430 } 431 $output = substr($output, 0, -1); 432 $output .= '</td></tr>' . "\n"; 433 } 434 435 if ($entry->sourceglossaryid == $cm->instance) { 436 if (!$maincm = get_coursemodule_from_instance('glossary', $entry->glossaryid)) { 437 return ''; 438 } 439 $filecontext = context_module::instance($maincm->id); 440 441 } else { 442 $filecontext = $context; 443 } 444 $fs = get_file_storage(); 445 if ($files = $fs->get_area_files($filecontext->id, 'mod_glossary', 'attachment', $entry->id, "timemodified", false)) { 446 $output .= '<table border="0" width="100%"><tr><td>' . "\n"; 447 448 foreach ($files as $file) { 449 $output .= $format->file_output($file); 450 } 451 $output .= '</td></tr></table>' . "\n"; 452 } 453 454 $output .= '</table>' . "\n"; 455 456 return $output; 457 } 458 } 459 460 461 /** 462 * Class representing the virtual node with all itemids in the file browser 463 * 464 * @category files 465 * @copyright 2012 David Mudrak <david@moodle.com> 466 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 467 */ 468 class glossary_file_info_container extends file_info { 469 /** @var file_browser */ 470 protected $browser; 471 /** @var stdClass */ 472 protected $course; 473 /** @var stdClass */ 474 protected $cm; 475 /** @var string */ 476 protected $component; 477 /** @var stdClass */ 478 protected $context; 479 /** @var array */ 480 protected $areas; 481 /** @var string */ 482 protected $filearea; 483 484 /** 485 * Constructor (in case you did not realize it ;-) 486 * 487 * @param file_browser $browser 488 * @param stdClass $course 489 * @param stdClass $cm 490 * @param stdClass $context 491 * @param array $areas 492 * @param string $filearea 493 */ 494 public function __construct($browser, $course, $cm, $context, $areas, $filearea) { 495 parent::__construct($browser, $context); 496 $this->browser = $browser; 497 $this->course = $course; 498 $this->cm = $cm; 499 $this->component = 'mod_glossary'; 500 $this->context = $context; 501 $this->areas = $areas; 502 $this->filearea = $filearea; 503 } 504 505 /** 506 * @return array with keys contextid, filearea, itemid, filepath and filename 507 */ 508 public function get_params() { 509 return array( 510 'contextid' => $this->context->id, 511 'component' => $this->component, 512 'filearea' => $this->filearea, 513 'itemid' => null, 514 'filepath' => null, 515 'filename' => null, 516 ); 517 } 518 519 /** 520 * Can new files or directories be added via the file browser 521 * 522 * @return bool 523 */ 524 public function is_writable() { 525 return false; 526 } 527 528 /** 529 * Should this node be considered as a folder in the file browser 530 * 531 * @return bool 532 */ 533 public function is_directory() { 534 return true; 535 } 536 537 /** 538 * Returns localised visible name of this node 539 * 540 * @return string 541 */ 542 public function get_visible_name() { 543 return $this->areas[$this->filearea]; 544 } 545 546 /** 547 * Returns list of children nodes 548 * 549 * @return array of file_info instances 550 */ 551 public function get_children() { 552 return $this->get_filtered_children('*', false, true); 553 } 554 555 /** 556 * Help function to return files matching extensions or their count 557 * 558 * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg') 559 * @param bool|int $countonly if false returns the children, if an int returns just the 560 * count of children but stops counting when $countonly number of children is reached 561 * @param bool $returnemptyfolders if true returns items that don't have matching files inside 562 * @return array|int array of file_info instances or the count 563 */ 564 private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) { 565 global $DB; 566 $sql = 'SELECT DISTINCT f.itemid, ge.concept 567 FROM {files} f 568 JOIN {modules} m ON (m.name = :modulename AND m.visible = 1) 569 JOIN {course_modules} cm ON (cm.module = m.id AND cm.id = :instanceid) 570 JOIN {glossary} g ON g.id = cm.instance 571 JOIN {glossary_entries} ge ON (ge.glossaryid = g.id AND ge.id = f.itemid) 572 WHERE f.contextid = :contextid 573 AND f.component = :component 574 AND f.filearea = :filearea'; 575 $params = array( 576 'modulename' => 'glossary', 577 'instanceid' => $this->context->instanceid, 578 'contextid' => $this->context->id, 579 'component' => $this->component, 580 'filearea' => $this->filearea); 581 if (!$returnemptyfolders) { 582 $sql .= ' AND f.filename <> :emptyfilename'; 583 $params['emptyfilename'] = '.'; 584 } 585 list($sql2, $params2) = $this->build_search_files_sql($extensions, 'f'); 586 $sql .= ' '.$sql2; 587 $params = array_merge($params, $params2); 588 if ($countonly !== false) { 589 $sql .= ' ORDER BY ge.concept, f.itemid'; 590 } 591 592 $rs = $DB->get_recordset_sql($sql, $params); 593 $children = array(); 594 foreach ($rs as $record) { 595 if ($child = $this->browser->get_file_info($this->context, 'mod_glossary', $this->filearea, $record->itemid)) { 596 $children[] = $child; 597 } 598 if ($countonly !== false && count($children) >= $countonly) { 599 break; 600 } 601 } 602 $rs->close(); 603 if ($countonly !== false) { 604 return count($children); 605 } 606 return $children; 607 } 608 609 /** 610 * Returns list of children which are either files matching the specified extensions 611 * or folders that contain at least one such file. 612 * 613 * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg') 614 * @return array of file_info instances 615 */ 616 public function get_non_empty_children($extensions = '*') { 617 return $this->get_filtered_children($extensions, false); 618 } 619 620 /** 621 * Returns the number of children which are either files matching the specified extensions 622 * or folders containing at least one such file. 623 * 624 * @param string|array $extensions, for example '*' or array('.gif','.jpg') 625 * @param int $limit stop counting after at least $limit non-empty children are found 626 * @return int 627 */ 628 public function count_non_empty_children($extensions = '*', $limit = 1) { 629 return $this->get_filtered_children($extensions, $limit); 630 } 631 632 /** 633 * Returns parent file_info instance 634 * 635 * @return file_info or null for root 636 */ 637 public function get_parent() { 638 return $this->browser->get_file_info($this->context); 639 } 640 } 641 642 /** 643 * Returns glossary entries tagged with a specified tag. 644 * 645 * This is a callback used by the tag area mod_glossary/glossary_entries to search for glossary entries 646 * tagged with a specific tag. 647 * 648 * @param core_tag_tag $tag 649 * @param bool $exclusivemode if set to true it means that no other entities tagged with this tag 650 * are displayed on the page and the per-page limit may be bigger 651 * @param int $fromctx context id where the link was displayed, may be used by callbacks 652 * to display items in the same context first 653 * @param int $ctx context id where to search for records 654 * @param bool $rec search in subcontexts as well 655 * @param int $page 0-based number of page being displayed 656 * @return \core_tag\output\tagindex 657 */ 658 function mod_glossary_get_tagged_entries($tag, $exclusivemode = false, $fromctx = 0, $ctx = 0, $rec = 1, $page = 0) { 659 global $OUTPUT; 660 $perpage = $exclusivemode ? 20 : 5; 661 662 // Build the SQL query. 663 $ctxselect = context_helper::get_preload_record_columns_sql('ctx'); 664 $query = "SELECT ge.id, ge.concept, ge.glossaryid, ge.approved, ge.userid, 665 cm.id AS cmid, c.id AS courseid, c.shortname, c.fullname, $ctxselect 666 FROM {glossary_entries} ge 667 JOIN {glossary} g ON g.id = ge.glossaryid 668 JOIN {modules} m ON m.name='glossary' 669 JOIN {course_modules} cm ON cm.module = m.id AND cm.instance = g.id 670 JOIN {tag_instance} tt ON ge.id = tt.itemid 671 JOIN {course} c ON cm.course = c.id 672 JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :coursemodulecontextlevel 673 WHERE tt.itemtype = :itemtype AND tt.tagid = :tagid AND tt.component = :component 674 AND cm.deletioninprogress = 0 675 AND ge.id %ITEMFILTER% AND c.id %COURSEFILTER%"; 676 677 $params = array('itemtype' => 'glossary_entries', 'tagid' => $tag->id, 'component' => 'mod_glossary', 678 'coursemodulecontextlevel' => CONTEXT_MODULE); 679 680 if ($ctx) { 681 $context = $ctx ? context::instance_by_id($ctx) : context_system::instance(); 682 $query .= $rec ? ' AND (ctx.id = :contextid OR ctx.path LIKE :path)' : ' AND ctx.id = :contextid'; 683 $params['contextid'] = $context->id; 684 $params['path'] = $context->path.'/%'; 685 } 686 687 $query .= " ORDER BY "; 688 if ($fromctx) { 689 // In order-clause specify that modules from inside "fromctx" context should be returned first. 690 $fromcontext = context::instance_by_id($fromctx); 691 $query .= ' (CASE WHEN ctx.id = :fromcontextid OR ctx.path LIKE :frompath THEN 0 ELSE 1 END),'; 692 $params['fromcontextid'] = $fromcontext->id; 693 $params['frompath'] = $fromcontext->path.'/%'; 694 } 695 $query .= ' c.sortorder, cm.id, ge.id'; 696 697 $totalpages = $page + 1; 698 699 // Use core_tag_index_builder to build and filter the list of items. 700 $builder = new core_tag_index_builder('mod_glossary', 'glossary_entries', $query, $params, $page * $perpage, $perpage + 1); 701 while ($item = $builder->has_item_that_needs_access_check()) { 702 context_helper::preload_from_record($item); 703 $courseid = $item->courseid; 704 if (!$builder->can_access_course($courseid)) { 705 $builder->set_accessible($item, false); 706 continue; 707 } 708 $modinfo = get_fast_modinfo($builder->get_course($courseid)); 709 // Set accessibility of this item and all other items in the same course. 710 $builder->walk(function ($taggeditem) use ($courseid, $modinfo, $builder) { 711 global $USER; 712 if ($taggeditem->courseid == $courseid) { 713 $accessible = false; 714 if (($cm = $modinfo->get_cm($taggeditem->cmid)) && $cm->uservisible) { 715 if ($taggeditem->approved) { 716 $accessible = true; 717 } else if ($taggeditem->userid == $USER->id) { 718 $accessible = true; 719 } else { 720 $accessible = has_capability('mod/glossary:approve', context_module::instance($cm->id)); 721 } 722 } 723 $builder->set_accessible($taggeditem, $accessible); 724 } 725 }); 726 } 727 728 $items = $builder->get_items(); 729 if (count($items) > $perpage) { 730 $totalpages = $page + 2; // We don't need exact page count, just indicate that the next page exists. 731 array_pop($items); 732 } 733 734 // Build the display contents. 735 if ($items) { 736 $tagfeed = new core_tag\output\tagfeed(); 737 foreach ($items as $item) { 738 context_helper::preload_from_record($item); 739 $modinfo = get_fast_modinfo($item->courseid); 740 $cm = $modinfo->get_cm($item->cmid); 741 $pageurl = new moodle_url('/mod/glossary/showentry.php', array('eid' => $item->id, 'displayformat' => 'dictionary')); 742 $pagename = format_string($item->concept, true, array('context' => context_module::instance($item->cmid))); 743 $pagename = html_writer::link($pageurl, $pagename); 744 $courseurl = course_get_url($item->courseid, $cm->sectionnum); 745 $cmname = html_writer::link($cm->url, $cm->get_formatted_name()); 746 $coursename = format_string($item->fullname, true, array('context' => context_course::instance($item->courseid))); 747 $coursename = html_writer::link($courseurl, $coursename); 748 $icon = html_writer::link($pageurl, html_writer::empty_tag('img', array('src' => $cm->get_icon_url()))); 749 750 $approved = ""; 751 if (!$item->approved) { 752 $approved = '<br>'. html_writer::span(get_string('entrynotapproved', 'mod_glossary'), 'badge badge-warning'); 753 } 754 $tagfeed->add($icon, $pagename, $cmname.'<br>'.$coursename.$approved); 755 } 756 757 $content = $OUTPUT->render_from_template('core_tag/tagfeed', 758 $tagfeed->export_for_template($OUTPUT)); 759 760 return new core_tag\output\tagindex($tag, 'mod_glossary', 'glossary_entries', $content, 761 $exclusivemode, $fromctx, $ctx, $rec, $page, $totalpages); 762 } 763 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body