Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]
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 array $aliases = []; 226 227 /** @var array categories. */ 228 private array $categories = []; 229 230 /* 231 * @return array 232 */ 233 public static function expected_callbackargs() { 234 return array( 235 'entryid' => true, 236 'id' => true, 237 ); 238 } 239 240 /** 241 * load up all data required for this export. 242 * 243 * @return void 244 */ 245 public function load_data() { 246 global $DB; 247 if (!$this->cm = get_coursemodule_from_id('glossary', $this->id)) { 248 throw new portfolio_caller_exception('invalidid', 'glossary'); 249 } 250 if (!$this->glossary = $DB->get_record('glossary', array('id' => $this->cm->instance))) { 251 throw new portfolio_caller_exception('invalidid', 'glossary'); 252 } 253 if ($this->entryid) { 254 if (!$this->entry = $DB->get_record('glossary_entries', array('id' => $this->entryid))) { 255 throw new portfolio_caller_exception('noentry', 'glossary'); 256 } 257 // in case we don't have USER this will make the entry be printed 258 $this->entry->approved = true; 259 } 260 $this->categories = $DB->get_records_sql('SELECT ec.entryid, c.name FROM {glossary_entries_categories} ec 261 JOIN {glossary_categories} c 262 ON c.id = ec.categoryid 263 WHERE ec.entryid = ?', array($this->entryid)); 264 $context = context_module::instance($this->cm->id); 265 if ($this->entry->sourceglossaryid == $this->cm->instance) { 266 if ($maincm = get_coursemodule_from_instance('glossary', $this->entry->glossaryid)) { 267 $context = context_module::instance($maincm->id); 268 } 269 } 270 $this->aliases = $DB->get_records('glossary_alias', ['entryid' => $this->entryid]); 271 $fs = get_file_storage(); 272 $this->multifiles = array_merge( 273 $fs->get_area_files($context->id, 'mod_glossary', 'attachment', $this->entry->id, "timemodified", false), 274 $fs->get_area_files($context->id, 'mod_glossary', 'entry', $this->entry->id, "timemodified", false) 275 ); 276 277 if (!empty($this->multifiles)) { 278 $this->add_format(PORTFOLIO_FORMAT_RICHHTML); 279 } else { 280 $this->add_format(PORTFOLIO_FORMAT_PLAINHTML); 281 } 282 } 283 284 /** 285 * how long might we expect this export to take 286 * 287 * @return constant one of PORTFOLIO_TIME_XX 288 */ 289 public function expected_time() { 290 return PORTFOLIO_TIME_LOW; 291 } 292 293 /** 294 * make sure that the current user is allowed to do this 295 * 296 * @return boolean 297 */ 298 public function check_permissions() { 299 $context = context_module::instance($this->cm->id); 300 return has_capability('mod/glossary:exportentry', $context) 301 || ($this->entry->userid == $this->user->id && has_capability('mod/glossary:exportownentry', $context)); 302 } 303 304 /** 305 * return a nice name to be displayed about this export location 306 * 307 * @return string 308 */ 309 public static function display_name() { 310 return get_string('modulename', 'glossary'); 311 } 312 313 /** 314 * prepare the package ready to be passed off to the portfolio plugin 315 * 316 * @return void 317 */ 318 public function prepare_package() { 319 global $DB; 320 321 $format = $this->exporter->get('format'); 322 $content = self::entry_content($this->course, $this->cm, $this->glossary, $this->entry, $this->aliases, $format); 323 324 if ($this->exporter->get('formatclass') === PORTFOLIO_FORMAT_PLAINHTML) { 325 $filename = clean_filename($this->entry->concept) . '.html'; 326 $this->exporter->write_new_file($content, $filename); 327 328 } else if ($this->exporter->get('formatclass') === PORTFOLIO_FORMAT_RICHHTML) { 329 if ($this->multifiles) { 330 foreach ($this->multifiles as $file) { 331 $this->exporter->copy_existing_file($file); 332 } 333 } 334 $filename = clean_filename($this->entry->concept) . '.html'; 335 $this->exporter->write_new_file($content, $filename); 336 337 } else if ($this->exporter->get('formatclass') === PORTFOLIO_FORMAT_LEAP2A) { 338 $writer = $this->get('exporter')->get('format')->leap2a_writer(); 339 $entry = new portfolio_format_leap2a_entry('glossaryentry' . $this->entry->id, $this->entry->concept, 'entry', $content); 340 $entry->author = $DB->get_record('user', array('id' => $this->entry->userid), 'id,firstname,lastname,email'); 341 $entry->published = $this->entry->timecreated; 342 $entry->updated = $this->entry->timemodified; 343 if ($this->multifiles) { 344 $writer->link_files($entry, $this->multifiles); 345 foreach ($this->multifiles as $file) { 346 $this->exporter->copy_existing_file($file); 347 } 348 } 349 if ($this->categories) { 350 foreach ($this->categories as $cat) { 351 // this essentially treats them as plain tags 352 // leap has the idea of category schemes 353 // but I think this is overkill here 354 $entry->add_category($cat->name); 355 } 356 } 357 $writer->add_entry($entry); 358 $content = $writer->to_xml(); 359 $filename = $this->get('exporter')->get('format')->manifest_name(); 360 $this->exporter->write_new_file($content, $filename); 361 362 } else { 363 throw new portfolio_caller_exception('unexpected_format_class', 'glossary'); 364 } 365 } 366 367 /** 368 * return the sha1 of this content 369 * 370 * @return string 371 */ 372 public function get_sha1() { 373 if ($this->multifiles) { 374 return sha1(serialize($this->entry) . $this->get_sha1_file()); 375 } 376 return sha1(serialize($this->entry)); 377 } 378 379 /** 380 * what formats this function *generally* supports 381 * 382 * @return array 383 */ 384 public static function base_supported_formats() { 385 return array(PORTFOLIO_FORMAT_RICHHTML, PORTFOLIO_FORMAT_PLAINHTML, PORTFOLIO_FORMAT_LEAP2A); 386 } 387 388 /** 389 * helper function to get the html content of an entry 390 * for both this class and the full glossary exporter 391 * this is a very simplified version of the dictionary format output, 392 * but with its 500 levels of indirection removed 393 * and file rewriting handled by the portfolio export format. 394 * 395 * @param stdclass $course 396 * @param stdclass $cm 397 * @param stdclass $glossary 398 * @param stdclass $entry 399 * 400 * @return string 401 */ 402 public static function entry_content($course, $cm, $glossary, $entry, $aliases, $format) { 403 global $OUTPUT, $DB; 404 $entry = clone $entry; 405 $context = context_module::instance($cm->id); 406 $options = portfolio_format_text_options(); 407 $options->trusted = $entry->definitiontrust; 408 $options->context = $context; 409 410 $output = '<table class="glossarypost dictionary" cellspacing="0">' . "\n"; 411 $output .= '<tr valign="top">' . "\n"; 412 $output .= '<td class="entry">' . "\n"; 413 414 $output .= '<div class="concept">'; 415 $output .= format_text($OUTPUT->heading($entry->concept, 3), FORMAT_MOODLE, $options); 416 $output .= '</div> ' . "\n"; 417 418 $entry->definition = format_text($entry->definition, $entry->definitionformat, $options); 419 $output .= portfolio_rewrite_pluginfile_urls($entry->definition, $context->id, 'mod_glossary', 'entry', $entry->id, $format); 420 421 if (isset($entry->footer)) { 422 $output .= $entry->footer; 423 } 424 425 $output .= '</td></tr>' . "\n"; 426 427 if (!empty($aliases)) { 428 $output .= '<tr valign="top"><td class="entrylowersection">'; 429 $key = (count($aliases) == 1) ? 'alias' : 'aliases'; 430 $output .= get_string($key, 'glossary') . ': '; 431 foreach ($aliases as $alias) { 432 $output .= s($alias->alias) . ','; 433 } 434 $output = substr($output, 0, -1); 435 $output .= '</td></tr>' . "\n"; 436 } 437 438 if ($entry->sourceglossaryid == $cm->instance) { 439 if (!$maincm = get_coursemodule_from_instance('glossary', $entry->glossaryid)) { 440 return ''; 441 } 442 $filecontext = context_module::instance($maincm->id); 443 444 } else { 445 $filecontext = $context; 446 } 447 $fs = get_file_storage(); 448 if ($files = $fs->get_area_files($filecontext->id, 'mod_glossary', 'attachment', $entry->id, "timemodified", false)) { 449 $output .= '<table border="0" width="100%"><tr><td>' . "\n"; 450 451 foreach ($files as $file) { 452 $output .= $format->file_output($file); 453 } 454 $output .= '</td></tr></table>' . "\n"; 455 } 456 457 $output .= '</table>' . "\n"; 458 459 return $output; 460 } 461 } 462 463 464 /** 465 * Class representing the virtual node with all itemids in the file browser 466 * 467 * @category files 468 * @copyright 2012 David Mudrak <david@moodle.com> 469 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 470 */ 471 class glossary_file_info_container extends file_info { 472 /** @var file_browser */ 473 protected $browser; 474 /** @var stdClass */ 475 protected $course; 476 /** @var stdClass */ 477 protected $cm; 478 /** @var string */ 479 protected $component; 480 /** @var stdClass */ 481 protected $context; 482 /** @var array */ 483 protected $areas; 484 /** @var string */ 485 protected $filearea; 486 487 /** 488 * Constructor (in case you did not realize it ;-) 489 * 490 * @param file_browser $browser 491 * @param stdClass $course 492 * @param stdClass $cm 493 * @param stdClass $context 494 * @param array $areas 495 * @param string $filearea 496 */ 497 public function __construct($browser, $course, $cm, $context, $areas, $filearea) { 498 parent::__construct($browser, $context); 499 $this->browser = $browser; 500 $this->course = $course; 501 $this->cm = $cm; 502 $this->component = 'mod_glossary'; 503 $this->context = $context; 504 $this->areas = $areas; 505 $this->filearea = $filearea; 506 } 507 508 /** 509 * @return array with keys contextid, filearea, itemid, filepath and filename 510 */ 511 public function get_params() { 512 return array( 513 'contextid' => $this->context->id, 514 'component' => $this->component, 515 'filearea' => $this->filearea, 516 'itemid' => null, 517 'filepath' => null, 518 'filename' => null, 519 ); 520 } 521 522 /** 523 * Can new files or directories be added via the file browser 524 * 525 * @return bool 526 */ 527 public function is_writable() { 528 return false; 529 } 530 531 /** 532 * Should this node be considered as a folder in the file browser 533 * 534 * @return bool 535 */ 536 public function is_directory() { 537 return true; 538 } 539 540 /** 541 * Returns localised visible name of this node 542 * 543 * @return string 544 */ 545 public function get_visible_name() { 546 return $this->areas[$this->filearea]; 547 } 548 549 /** 550 * Returns list of children nodes 551 * 552 * @return array of file_info instances 553 */ 554 public function get_children() { 555 return $this->get_filtered_children('*', false, true); 556 } 557 558 /** 559 * Help function to return files matching extensions or their count 560 * 561 * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg') 562 * @param bool|int $countonly if false returns the children, if an int returns just the 563 * count of children but stops counting when $countonly number of children is reached 564 * @param bool $returnemptyfolders if true returns items that don't have matching files inside 565 * @return array|int array of file_info instances or the count 566 */ 567 private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) { 568 global $DB; 569 $sql = 'SELECT DISTINCT f.itemid, ge.concept 570 FROM {files} f 571 JOIN {modules} m ON (m.name = :modulename AND m.visible = 1) 572 JOIN {course_modules} cm ON (cm.module = m.id AND cm.id = :instanceid) 573 JOIN {glossary} g ON g.id = cm.instance 574 JOIN {glossary_entries} ge ON (ge.glossaryid = g.id AND ge.id = f.itemid) 575 WHERE f.contextid = :contextid 576 AND f.component = :component 577 AND f.filearea = :filearea'; 578 $params = array( 579 'modulename' => 'glossary', 580 'instanceid' => $this->context->instanceid, 581 'contextid' => $this->context->id, 582 'component' => $this->component, 583 'filearea' => $this->filearea); 584 if (!$returnemptyfolders) { 585 $sql .= ' AND f.filename <> :emptyfilename'; 586 $params['emptyfilename'] = '.'; 587 } 588 list($sql2, $params2) = $this->build_search_files_sql($extensions, 'f'); 589 $sql .= ' '.$sql2; 590 $params = array_merge($params, $params2); 591 if ($countonly !== false) { 592 $sql .= ' ORDER BY ge.concept, f.itemid'; 593 } 594 595 $rs = $DB->get_recordset_sql($sql, $params); 596 $children = array(); 597 foreach ($rs as $record) { 598 if ($child = $this->browser->get_file_info($this->context, 'mod_glossary', $this->filearea, $record->itemid)) { 599 $children[] = $child; 600 } 601 if ($countonly !== false && count($children) >= $countonly) { 602 break; 603 } 604 } 605 $rs->close(); 606 if ($countonly !== false) { 607 return count($children); 608 } 609 return $children; 610 } 611 612 /** 613 * Returns list of children which are either files matching the specified extensions 614 * or folders that contain at least one such file. 615 * 616 * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg') 617 * @return array of file_info instances 618 */ 619 public function get_non_empty_children($extensions = '*') { 620 return $this->get_filtered_children($extensions, false); 621 } 622 623 /** 624 * Returns the number of children which are either files matching the specified extensions 625 * or folders containing at least one such file. 626 * 627 * @param string|array $extensions, for example '*' or array('.gif','.jpg') 628 * @param int $limit stop counting after at least $limit non-empty children are found 629 * @return int 630 */ 631 public function count_non_empty_children($extensions = '*', $limit = 1) { 632 return $this->get_filtered_children($extensions, $limit); 633 } 634 635 /** 636 * Returns parent file_info instance 637 * 638 * @return file_info or null for root 639 */ 640 public function get_parent() { 641 return $this->browser->get_file_info($this->context); 642 } 643 } 644 645 /** 646 * Returns glossary entries tagged with a specified tag. 647 * 648 * This is a callback used by the tag area mod_glossary/glossary_entries to search for glossary entries 649 * tagged with a specific tag. 650 * 651 * @param core_tag_tag $tag 652 * @param bool $exclusivemode if set to true it means that no other entities tagged with this tag 653 * are displayed on the page and the per-page limit may be bigger 654 * @param int $fromctx context id where the link was displayed, may be used by callbacks 655 * to display items in the same context first 656 * @param int $ctx context id where to search for records 657 * @param bool $rec search in subcontexts as well 658 * @param int $page 0-based number of page being displayed 659 * @return \core_tag\output\tagindex 660 */ 661 function mod_glossary_get_tagged_entries($tag, $exclusivemode = false, $fromctx = 0, $ctx = 0, $rec = 1, $page = 0) { 662 global $OUTPUT; 663 $perpage = $exclusivemode ? 20 : 5; 664 665 // Build the SQL query. 666 $ctxselect = context_helper::get_preload_record_columns_sql('ctx'); 667 $query = "SELECT ge.id, ge.concept, ge.glossaryid, ge.approved, ge.userid, 668 cm.id AS cmid, c.id AS courseid, c.shortname, c.fullname, $ctxselect 669 FROM {glossary_entries} ge 670 JOIN {glossary} g ON g.id = ge.glossaryid 671 JOIN {modules} m ON m.name='glossary' 672 JOIN {course_modules} cm ON cm.module = m.id AND cm.instance = g.id 673 JOIN {tag_instance} tt ON ge.id = tt.itemid 674 JOIN {course} c ON cm.course = c.id 675 JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :coursemodulecontextlevel 676 WHERE tt.itemtype = :itemtype AND tt.tagid = :tagid AND tt.component = :component 677 AND cm.deletioninprogress = 0 678 AND ge.id %ITEMFILTER% AND c.id %COURSEFILTER%"; 679 680 $params = array('itemtype' => 'glossary_entries', 'tagid' => $tag->id, 'component' => 'mod_glossary', 681 'coursemodulecontextlevel' => CONTEXT_MODULE); 682 683 if ($ctx) { 684 $context = $ctx ? context::instance_by_id($ctx) : context_system::instance(); 685 $query .= $rec ? ' AND (ctx.id = :contextid OR ctx.path LIKE :path)' : ' AND ctx.id = :contextid'; 686 $params['contextid'] = $context->id; 687 $params['path'] = $context->path.'/%'; 688 } 689 690 $query .= " ORDER BY "; 691 if ($fromctx) { 692 // In order-clause specify that modules from inside "fromctx" context should be returned first. 693 $fromcontext = context::instance_by_id($fromctx); 694 $query .= ' (CASE WHEN ctx.id = :fromcontextid OR ctx.path LIKE :frompath THEN 0 ELSE 1 END),'; 695 $params['fromcontextid'] = $fromcontext->id; 696 $params['frompath'] = $fromcontext->path.'/%'; 697 } 698 $query .= ' c.sortorder, cm.id, ge.id'; 699 700 $totalpages = $page + 1; 701 702 // Use core_tag_index_builder to build and filter the list of items. 703 $builder = new core_tag_index_builder('mod_glossary', 'glossary_entries', $query, $params, $page * $perpage, $perpage + 1); 704 while ($item = $builder->has_item_that_needs_access_check()) { 705 context_helper::preload_from_record($item); 706 $courseid = $item->courseid; 707 if (!$builder->can_access_course($courseid)) { 708 $builder->set_accessible($item, false); 709 continue; 710 } 711 $modinfo = get_fast_modinfo($builder->get_course($courseid)); 712 // Set accessibility of this item and all other items in the same course. 713 $builder->walk(function ($taggeditem) use ($courseid, $modinfo, $builder) { 714 global $USER; 715 if ($taggeditem->courseid == $courseid) { 716 $accessible = false; 717 if (($cm = $modinfo->get_cm($taggeditem->cmid)) && $cm->uservisible) { 718 if ($taggeditem->approved) { 719 $accessible = true; 720 } else if ($taggeditem->userid == $USER->id) { 721 $accessible = true; 722 } else { 723 $accessible = has_capability('mod/glossary:approve', context_module::instance($cm->id)); 724 } 725 } 726 $builder->set_accessible($taggeditem, $accessible); 727 } 728 }); 729 } 730 731 $items = $builder->get_items(); 732 if (count($items) > $perpage) { 733 $totalpages = $page + 2; // We don't need exact page count, just indicate that the next page exists. 734 array_pop($items); 735 } 736 737 // Build the display contents. 738 if ($items) { 739 $tagfeed = new core_tag\output\tagfeed(); 740 foreach ($items as $item) { 741 context_helper::preload_from_record($item); 742 $modinfo = get_fast_modinfo($item->courseid); 743 $cm = $modinfo->get_cm($item->cmid); 744 $pageurl = new moodle_url('/mod/glossary/showentry.php', array('eid' => $item->id, 'displayformat' => 'dictionary')); 745 $pagename = format_string($item->concept, true, array('context' => context_module::instance($item->cmid))); 746 $pagename = html_writer::link($pageurl, $pagename); 747 $courseurl = course_get_url($item->courseid, $cm->sectionnum); 748 $cmname = html_writer::link($cm->url, $cm->get_formatted_name()); 749 $coursename = format_string($item->fullname, true, array('context' => context_course::instance($item->courseid))); 750 $coursename = html_writer::link($courseurl, $coursename); 751 $icon = html_writer::link($pageurl, html_writer::empty_tag('img', array('src' => $cm->get_icon_url()))); 752 753 $approved = ""; 754 if (!$item->approved) { 755 $approved = '<br>'. html_writer::span(get_string('entrynotapproved', 'mod_glossary'), 'badge badge-warning'); 756 } 757 $tagfeed->add($icon, $pagename, $cmname.'<br>'.$coursename.$approved); 758 } 759 760 $content = $OUTPUT->render_from_template('core_tag/tagfeed', 761 $tagfeed->export_for_template($OUTPUT)); 762 763 return new core_tag\output\tagindex($tag, 'mod_glossary', 'glossary_entries', $content, 764 $exclusivemode, $fromctx, $ctx, $rec, $page, $totalpages); 765 } 766 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body