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