See Release Notes
Long Term Support Release
Differences Between: [Versions 401 and 402] [Versions 401 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Library of functions for forum outside of the core api 19 * 20 * @package mod_forum 21 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 // Event types. 26 define('FORUM_EVENT_TYPE_DUE', 'due'); 27 28 require_once($CFG->dirroot . '/mod/forum/lib.php'); 29 require_once($CFG->libdir . '/portfolio/caller.php'); 30 31 /** 32 * @package mod_forum 33 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com} 34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 */ 36 class forum_portfolio_caller extends portfolio_module_caller_base { 37 38 protected $postid; 39 protected $discussionid; 40 protected $attachment; 41 42 private $post; 43 private $forum; 44 private $discussion; 45 private $posts; 46 private $keyedfiles; // just using multifiles isn't enough if we're exporting a full thread 47 48 /** 49 * @return array 50 */ 51 public static function expected_callbackargs() { 52 return array( 53 'postid' => false, 54 'discussionid' => false, 55 'attachment' => false, 56 ); 57 } 58 /** 59 * @param array $callbackargs 60 */ 61 function __construct($callbackargs) { 62 parent::__construct($callbackargs); 63 if (!$this->postid && !$this->discussionid) { 64 throw new portfolio_caller_exception('mustprovidediscussionorpost', 'forum'); 65 } 66 } 67 /** 68 * @global object 69 */ 70 public function load_data() { 71 global $DB; 72 73 if ($this->postid) { 74 if (!$this->post = $DB->get_record('forum_posts', array('id' => $this->postid))) { 75 throw new portfolio_caller_exception('invalidpostid', 'forum'); 76 } 77 } 78 79 $dparams = array(); 80 if ($this->discussionid) { 81 $dbparams = array('id' => $this->discussionid); 82 } else if ($this->post) { 83 $dbparams = array('id' => $this->post->discussion); 84 } else { 85 throw new portfolio_caller_exception('mustprovidediscussionorpost', 'forum'); 86 } 87 88 if (!$this->discussion = $DB->get_record('forum_discussions', $dbparams)) { 89 throw new portfolio_caller_exception('invaliddiscussionid', 'forum'); 90 } 91 92 if (!$this->forum = $DB->get_record('forum', array('id' => $this->discussion->forum))) { 93 throw new portfolio_caller_exception('invalidforumid', 'forum'); 94 } 95 96 if (!$this->cm = get_coursemodule_from_instance('forum', $this->forum->id)) { 97 throw new portfolio_caller_exception('invalidcoursemodule'); 98 } 99 100 $this->modcontext = context_module::instance($this->cm->id); 101 $fs = get_file_storage(); 102 if ($this->post) { 103 if ($this->attachment) { 104 // Make sure the requested file belongs to this post. 105 $file = $fs->get_file_by_id($this->attachment); 106 if ($file->get_contextid() != $this->modcontext->id 107 || $file->get_itemid() != $this->post->id) { 108 throw new portfolio_caller_exception('filenotfound'); 109 } 110 $this->set_file_and_format_data($this->attachment); 111 } else { 112 $attach = $fs->get_area_files($this->modcontext->id, 'mod_forum', 'attachment', $this->post->id, 'timemodified', false); 113 $embed = $fs->get_area_files($this->modcontext->id, 'mod_forum', 'post', $this->post->id, 'timemodified', false); 114 $files = array_merge($attach, $embed); 115 $this->set_file_and_format_data($files); 116 } 117 if (!empty($this->multifiles)) { 118 $this->keyedfiles[$this->post->id] = $this->multifiles; 119 } else if (!empty($this->singlefile)) { 120 $this->keyedfiles[$this->post->id] = array($this->singlefile); 121 } 122 } else { // whole thread 123 $fs = get_file_storage(); 124 $this->posts = forum_get_all_discussion_posts($this->discussion->id, 'p.created ASC'); 125 $this->multifiles = array(); 126 foreach ($this->posts as $post) { 127 $attach = $fs->get_area_files($this->modcontext->id, 'mod_forum', 'attachment', $post->id, 'timemodified', false); 128 $embed = $fs->get_area_files($this->modcontext->id, 'mod_forum', 'post', $post->id, 'timemodified', false); 129 $files = array_merge($attach, $embed); 130 if ($files) { 131 $this->keyedfiles[$post->id] = $files; 132 } else { 133 continue; 134 } 135 $this->multifiles = array_merge($this->multifiles, array_values($this->keyedfiles[$post->id])); 136 } 137 } 138 if (empty($this->multifiles) && !empty($this->singlefile)) { 139 $this->multifiles = array($this->singlefile); // copy_files workaround 140 } 141 // depending on whether there are files or not, we might have to change richhtml/plainhtml 142 if (empty($this->attachment)) { 143 if (!empty($this->multifiles)) { 144 $this->add_format(PORTFOLIO_FORMAT_RICHHTML); 145 } else { 146 $this->add_format(PORTFOLIO_FORMAT_PLAINHTML); 147 } 148 } 149 } 150 151 /** 152 * @global object 153 * @return string 154 */ 155 function get_return_url() { 156 global $CFG; 157 return $CFG->wwwroot . '/mod/forum/discuss.php?d=' . $this->discussion->id; 158 } 159 /** 160 * @global object 161 * @return array 162 */ 163 function get_navigation() { 164 global $CFG; 165 166 $navlinks = array(); 167 $navlinks[] = array( 168 'name' => format_string($this->discussion->name), 169 'link' => $CFG->wwwroot . '/mod/forum/discuss.php?d=' . $this->discussion->id, 170 'type' => 'title' 171 ); 172 return array($navlinks, $this->cm); 173 } 174 /** 175 * either a whole discussion 176 * a single post, with or without attachment 177 * or just an attachment with no post 178 * 179 * @global object 180 * @global object 181 * @uses PORTFOLIO_FORMAT_RICH 182 * @return mixed 183 */ 184 function prepare_package() { 185 global $CFG; 186 187 // set up the leap2a writer if we need it 188 $writingleap = false; 189 if ($this->exporter->get('formatclass') == PORTFOLIO_FORMAT_LEAP2A) { 190 $leapwriter = $this->exporter->get('format')->leap2a_writer(); 191 $writingleap = true; 192 } 193 if ($this->attachment) { // simplest case first - single file attachment 194 $this->copy_files(array($this->singlefile), $this->attachment); 195 if ($writingleap) { // if we're writing leap, make the manifest to go along with the file 196 $entry = new portfolio_format_leap2a_file($this->singlefile->get_filename(), $this->singlefile); 197 $leapwriter->add_entry($entry); 198 return $this->exporter->write_new_file($leapwriter->to_xml(), $this->exporter->get('format')->manifest_name(), true); 199 } 200 201 } else if (empty($this->post)) { // exporting whole discussion 202 $content = ''; // if we're just writing HTML, start a string to add each post to 203 $ids = array(); // if we're writing leap2a, keep track of all entryids so we can add a selection element 204 foreach ($this->posts as $post) { 205 $posthtml = $this->prepare_post($post); 206 if ($writingleap) { 207 $ids[] = $this->prepare_post_leap2a($leapwriter, $post, $posthtml); 208 } else { 209 $content .= $posthtml . '<br /><br />'; 210 } 211 } 212 $this->copy_files($this->multifiles); 213 $name = 'discussion.html'; 214 $manifest = ($this->exporter->get('format') instanceof PORTFOLIO_FORMAT_RICH); 215 if ($writingleap) { 216 // add on an extra 'selection' entry 217 $selection = new portfolio_format_leap2a_entry('forumdiscussion' . $this->discussionid, 218 get_string('discussion', 'forum') . ': ' . $this->discussion->name, 'selection'); 219 $leapwriter->add_entry($selection); 220 $leapwriter->make_selection($selection, $ids, 'Grouping'); 221 $content = $leapwriter->to_xml(); 222 $name = $this->get('exporter')->get('format')->manifest_name(); 223 } 224 $this->get('exporter')->write_new_file($content, $name, $manifest); 225 226 } else { // exporting a single post 227 $posthtml = $this->prepare_post($this->post); 228 229 $content = $posthtml; 230 $name = 'post.html'; 231 $manifest = ($this->exporter->get('format') instanceof PORTFOLIO_FORMAT_RICH); 232 233 if ($writingleap) { 234 $this->prepare_post_leap2a($leapwriter, $this->post, $posthtml); 235 $content = $leapwriter->to_xml(); 236 $name = $this->exporter->get('format')->manifest_name(); 237 } 238 $this->copy_files($this->multifiles); 239 $this->get('exporter')->write_new_file($content, $name, $manifest); 240 } 241 } 242 243 /** 244 * helper function to add a leap2a entry element 245 * that corresponds to a single forum post, 246 * including any attachments 247 * 248 * the entry/ies are added directly to the leapwriter, which is passed by ref 249 * 250 * @param portfolio_format_leap2a_writer $leapwriter writer object to add entries to 251 * @param object $post the stdclass object representing the database record 252 * @param string $posthtml the content of the post (prepared by {@link prepare_post} 253 * 254 * @return int id of new entry 255 */ 256 private function prepare_post_leap2a(portfolio_format_leap2a_writer $leapwriter, $post, $posthtml) { 257 $entry = new portfolio_format_leap2a_entry('forumpost' . $post->id, $post->subject, 'resource', $posthtml); 258 $entry->published = $post->created; 259 $entry->updated = $post->modified; 260 $entry->author = $post->author; 261 if (is_array($this->keyedfiles) && array_key_exists($post->id, $this->keyedfiles) && is_array($this->keyedfiles[$post->id])) { 262 $leapwriter->link_files($entry, $this->keyedfiles[$post->id], 'forumpost' . $post->id . 'attachment'); 263 } 264 $entry->add_category('web', 'resource_type'); 265 $leapwriter->add_entry($entry); 266 return $entry->id; 267 } 268 269 /** 270 * @param array $files 271 * @param mixed $justone false of id of single file to copy 272 * @return bool|void 273 */ 274 private function copy_files($files, $justone=false) { 275 if (empty($files)) { 276 return; 277 } 278 foreach ($files as $f) { 279 if ($justone && $f->get_id() != $justone) { 280 continue; 281 } 282 $this->get('exporter')->copy_existing_file($f); 283 if ($justone && $f->get_id() == $justone) { 284 return true; // all we need to do 285 } 286 } 287 } 288 /** 289 * this is a very cut down version of what is in forum_make_mail_post 290 * 291 * @global object 292 * @param int $post 293 * @return string 294 */ 295 private function prepare_post($post, $fileoutputextras=null) { 296 global $DB; 297 static $users; 298 if (empty($users)) { 299 $users = array($this->user->id => $this->user); 300 } 301 if (!array_key_exists($post->userid, $users)) { 302 $users[$post->userid] = $DB->get_record('user', array('id' => $post->userid)); 303 } 304 // add the user object on to the post so we can pass it to the leap writer if necessary 305 $post->author = $users[$post->userid]; 306 $viewfullnames = true; 307 // format the post body 308 $options = portfolio_format_text_options(); 309 $format = $this->get('exporter')->get('format'); 310 $formattedtext = format_text($post->message, $post->messageformat, $options, $this->get('course')->id); 311 $formattedtext = portfolio_rewrite_pluginfile_urls($formattedtext, $this->modcontext->id, 'mod_forum', 'post', $post->id, $format); 312 313 $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">'; 314 315 $output .= '<tr class="header"><td>';// can't print picture. 316 $output .= '</td>'; 317 318 if ($post->parent) { 319 $output .= '<td class="topic">'; 320 } else { 321 $output .= '<td class="topic starter">'; 322 } 323 $output .= '<div class="subject">'.format_string($post->subject).'</div>'; 324 325 $fullname = fullname($users[$post->userid], $viewfullnames); 326 $by = new stdClass(); 327 $by->name = $fullname; 328 $by->date = userdate($post->modified, '', core_date::get_user_timezone($this->user)); 329 $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>'; 330 331 $output .= '</td></tr>'; 332 333 $output .= '<tr><td class="left side" valign="top">'; 334 335 $output .= '</td><td class="content">'; 336 337 $output .= $formattedtext; 338 339 if (is_array($this->keyedfiles) && array_key_exists($post->id, $this->keyedfiles) && is_array($this->keyedfiles[$post->id]) && count($this->keyedfiles[$post->id]) > 0) { 340 $output .= '<div class="attachments">'; 341 $output .= '<br /><b>' . get_string('attachments', 'forum') . '</b>:<br /><br />'; 342 foreach ($this->keyedfiles[$post->id] as $file) { 343 $output .= $format->file_output($file) . '<br/ >'; 344 } 345 $output .= "</div>"; 346 } 347 348 $output .= '</td></tr></table>'."\n\n"; 349 350 return $output; 351 } 352 /** 353 * @return string 354 */ 355 function get_sha1() { 356 $filesha = ''; 357 try { 358 $filesha = $this->get_sha1_file(); 359 } catch (portfolio_caller_exception $e) { } // no files 360 361 if ($this->post) { 362 return sha1($filesha . ',' . $this->post->subject . ',' . $this->post->message); 363 } else { 364 $sha1s = array($filesha); 365 foreach ($this->posts as $post) { 366 $sha1s[] = sha1($post->subject . ',' . $post->message); 367 } 368 return sha1(implode(',', $sha1s)); 369 } 370 } 371 372 function expected_time() { 373 $filetime = $this->expected_time_file(); 374 if ($this->posts) { 375 $posttime = portfolio_expected_time_db(count($this->posts)); 376 if ($filetime < $posttime) { 377 return $posttime; 378 } 379 } 380 return $filetime; 381 } 382 /** 383 * @uses CONTEXT_MODULE 384 * @return bool 385 */ 386 function check_permissions() { 387 $context = context_module::instance($this->cm->id); 388 if ($this->post) { 389 return (has_capability('mod/forum:exportpost', $context) 390 || ($this->post->userid == $this->user->id 391 && has_capability('mod/forum:exportownpost', $context))); 392 } 393 return has_capability('mod/forum:exportdiscussion', $context); 394 } 395 /** 396 * @return string 397 */ 398 public static function display_name() { 399 return get_string('modulename', 'forum'); 400 } 401 402 public static function base_supported_formats() { 403 return array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_RICHHTML, PORTFOLIO_FORMAT_PLAINHTML, PORTFOLIO_FORMAT_LEAP2A); 404 } 405 } 406 407 408 /** 409 * Class representing the virtual node with all itemids in the file browser 410 * 411 * @category files 412 * @copyright 2012 David Mudrak <david@moodle.com> 413 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 414 */ 415 class forum_file_info_container extends file_info { 416 /** @var file_browser */ 417 protected $browser; 418 /** @var stdClass */ 419 protected $course; 420 /** @var stdClass */ 421 protected $cm; 422 /** @var string */ 423 protected $component; 424 /** @var stdClass */ 425 protected $context; 426 /** @var array */ 427 protected $areas; 428 /** @var string */ 429 protected $filearea; 430 431 /** 432 * Constructor (in case you did not realize it ;-) 433 * 434 * @param file_browser $browser 435 * @param stdClass $course 436 * @param stdClass $cm 437 * @param stdClass $context 438 * @param array $areas 439 * @param string $filearea 440 */ 441 public function __construct($browser, $course, $cm, $context, $areas, $filearea) { 442 parent::__construct($browser, $context); 443 $this->browser = $browser; 444 $this->course = $course; 445 $this->cm = $cm; 446 $this->component = 'mod_forum'; 447 $this->context = $context; 448 $this->areas = $areas; 449 $this->filearea = $filearea; 450 } 451 452 /** 453 * @return array with keys contextid, filearea, itemid, filepath and filename 454 */ 455 public function get_params() { 456 return array( 457 'contextid' => $this->context->id, 458 'component' => $this->component, 459 'filearea' => $this->filearea, 460 'itemid' => null, 461 'filepath' => null, 462 'filename' => null, 463 ); 464 } 465 466 /** 467 * Can new files or directories be added via the file browser 468 * 469 * @return bool 470 */ 471 public function is_writable() { 472 return false; 473 } 474 475 /** 476 * Should this node be considered as a folder in the file browser 477 * 478 * @return bool 479 */ 480 public function is_directory() { 481 return true; 482 } 483 484 /** 485 * Returns localised visible name of this node 486 * 487 * @return string 488 */ 489 public function get_visible_name() { 490 return $this->areas[$this->filearea]; 491 } 492 493 /** 494 * Returns list of children nodes 495 * 496 * @return array of file_info instances 497 */ 498 public function get_children() { 499 return $this->get_filtered_children('*', false, true); 500 } 501 /** 502 * Help function to return files matching extensions or their count 503 * 504 * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg') 505 * @param bool|int $countonly if false returns the children, if an int returns just the 506 * count of children but stops counting when $countonly number of children is reached 507 * @param bool $returnemptyfolders if true returns items that don't have matching files inside 508 * @return array|int array of file_info instances or the count 509 */ 510 private function get_filtered_children($extensions = '*', $countonly = false, $returnemptyfolders = false) { 511 global $DB; 512 $params = array('contextid' => $this->context->id, 513 'component' => $this->component, 514 'filearea' => $this->filearea); 515 $sql = 'SELECT DISTINCT itemid 516 FROM {files} 517 WHERE contextid = :contextid 518 AND component = :component 519 AND filearea = :filearea'; 520 if (!$returnemptyfolders) { 521 $sql .= ' AND filename <> :emptyfilename'; 522 $params['emptyfilename'] = '.'; 523 } 524 list($sql2, $params2) = $this->build_search_files_sql($extensions); 525 $sql .= ' '.$sql2; 526 $params = array_merge($params, $params2); 527 if ($countonly !== false) { 528 $sql .= ' ORDER BY itemid DESC'; 529 } 530 531 $rs = $DB->get_recordset_sql($sql, $params); 532 $children = array(); 533 foreach ($rs as $record) { 534 if (($child = $this->browser->get_file_info($this->context, 'mod_forum', $this->filearea, $record->itemid)) 535 && ($returnemptyfolders || $child->count_non_empty_children($extensions))) { 536 $children[] = $child; 537 } 538 if ($countonly !== false && count($children) >= $countonly) { 539 break; 540 } 541 } 542 $rs->close(); 543 if ($countonly !== false) { 544 return count($children); 545 } 546 return $children; 547 } 548 549 /** 550 * Returns list of children which are either files matching the specified extensions 551 * or folders that contain at least one such file. 552 * 553 * @param string|array $extensions, either '*' or array of lowercase extensions, i.e. array('.gif','.jpg') 554 * @return array of file_info instances 555 */ 556 public function get_non_empty_children($extensions = '*') { 557 return $this->get_filtered_children($extensions, false); 558 } 559 560 /** 561 * Returns the number of children which are either files matching the specified extensions 562 * or folders containing at least one such file. 563 * 564 * @param string|array $extensions, for example '*' or array('.gif','.jpg') 565 * @param int $limit stop counting after at least $limit non-empty children are found 566 * @return int 567 */ 568 public function count_non_empty_children($extensions = '*', $limit = 1) { 569 return $this->get_filtered_children($extensions, $limit); 570 } 571 572 /** 573 * Returns parent file_info instance 574 * 575 * @return file_info or null for root 576 */ 577 public function get_parent() { 578 return $this->browser->get_file_info($this->context); 579 } 580 } 581 582 /** 583 * Returns forum posts tagged with a specified tag. 584 * 585 * This is a callback used by the tag area mod_forum/forum_posts to search for forum posts 586 * tagged with a specific tag. 587 * 588 * @param core_tag_tag $tag 589 * @param bool $exclusivemode if set to true it means that no other entities tagged with this tag 590 * are displayed on the page and the per-page limit may be bigger 591 * @param int $fromctx context id where the link was displayed, may be used by callbacks 592 * to display items in the same context first 593 * @param int $ctx context id where to search for records 594 * @param bool $rec search in subcontexts as well 595 * @param int $page 0-based number of page being displayed 596 * @return \core_tag\output\tagindex 597 */ 598 function mod_forum_get_tagged_posts($tag, $exclusivemode = false, $fromctx = 0, $ctx = 0, $rec = 1, $page = 0) { 599 global $OUTPUT; 600 $perpage = $exclusivemode ? 20 : 5; 601 602 // Build the SQL query. 603 $ctxselect = context_helper::get_preload_record_columns_sql('ctx'); 604 $query = "SELECT fp.id, fp.subject, fd.forum, fp.discussion, f.type, fd.timestart, fd.timeend, fd.groupid, fd.firstpost, 605 fp.parent, fp.userid, 606 cm.id AS cmid, c.id AS courseid, c.shortname, c.fullname, $ctxselect 607 FROM {forum_posts} fp 608 JOIN {forum_discussions} fd ON fp.discussion = fd.id 609 JOIN {forum} f ON f.id = fd.forum 610 JOIN {modules} m ON m.name='forum' 611 JOIN {course_modules} cm ON cm.module = m.id AND cm.instance = f.id 612 JOIN {tag_instance} tt ON fp.id = tt.itemid 613 JOIN {course} c ON cm.course = c.id 614 JOIN {context} ctx ON ctx.instanceid = cm.id AND ctx.contextlevel = :coursemodulecontextlevel 615 WHERE tt.itemtype = :itemtype AND tt.tagid = :tagid AND tt.component = :component 616 AND cm.deletioninprogress = 0 617 AND fp.id %ITEMFILTER% AND c.id %COURSEFILTER%"; 618 619 $params = array('itemtype' => 'forum_posts', 'tagid' => $tag->id, 'component' => 'mod_forum', 620 'coursemodulecontextlevel' => CONTEXT_MODULE); 621 622 if ($ctx) { 623 $context = $ctx ? context::instance_by_id($ctx) : context_system::instance(); 624 $query .= $rec ? ' AND (ctx.id = :contextid OR ctx.path LIKE :path)' : ' AND ctx.id = :contextid'; 625 $params['contextid'] = $context->id; 626 $params['path'] = $context->path.'/%'; 627 } 628 629 $query .= " ORDER BY "; 630 if ($fromctx) { 631 // In order-clause specify that modules from inside "fromctx" context should be returned first. 632 $fromcontext = context::instance_by_id($fromctx); 633 $query .= ' (CASE WHEN ctx.id = :fromcontextid OR ctx.path LIKE :frompath THEN 0 ELSE 1 END),'; 634 $params['fromcontextid'] = $fromcontext->id; 635 $params['frompath'] = $fromcontext->path.'/%'; 636 } 637 $query .= ' c.sortorder, cm.id, fp.id'; 638 639 $totalpages = $page + 1; 640 641 // Use core_tag_index_builder to build and filter the list of items. 642 $builder = new core_tag_index_builder('mod_forum', 'forum_posts', $query, $params, $page * $perpage, $perpage + 1); 643 while ($item = $builder->has_item_that_needs_access_check()) { 644 context_helper::preload_from_record($item); 645 $courseid = $item->courseid; 646 if (!$builder->can_access_course($courseid)) { 647 $builder->set_accessible($item, false); 648 continue; 649 } 650 $modinfo = get_fast_modinfo($builder->get_course($courseid)); 651 // Set accessibility of this item and all other items in the same course. 652 $builder->walk(function ($taggeditem) use ($courseid, $modinfo, $builder, $item) { 653 // Checking permission for Q&A forums performs additional DB queries, do not do them in bulk. 654 if ($taggeditem->courseid == $courseid && ($taggeditem->type != 'qanda' || $taggeditem->id == $item->id)) { 655 $cm = $modinfo->get_cm($taggeditem->cmid); 656 $forum = (object)['id' => $taggeditem->forum, 657 'course' => $taggeditem->courseid, 658 'type' => $taggeditem->type 659 ]; 660 $discussion = (object)['id' => $taggeditem->discussion, 661 'timestart' => $taggeditem->timestart, 662 'timeend' => $taggeditem->timeend, 663 'groupid' => $taggeditem->groupid, 664 'firstpost' => $taggeditem->firstpost 665 ]; 666 $post = (object)['id' => $taggeditem->id, 667 'parent' => $taggeditem->parent, 668 'userid' => $taggeditem->userid, 669 'groupid' => $taggeditem->groupid 670 ]; 671 672 $accessible = forum_user_can_see_post($forum, $discussion, $post, null, $cm); 673 $builder->set_accessible($taggeditem, $accessible); 674 } 675 }); 676 } 677 678 $items = $builder->get_items(); 679 if (count($items) > $perpage) { 680 $totalpages = $page + 2; // We don't need exact page count, just indicate that the next page exists. 681 array_pop($items); 682 } 683 684 // Build the display contents. 685 if ($items) { 686 $tagfeed = new core_tag\output\tagfeed(); 687 foreach ($items as $item) { 688 context_helper::preload_from_record($item); 689 $modinfo = get_fast_modinfo($item->courseid); 690 $cm = $modinfo->get_cm($item->cmid); 691 $pageurl = new moodle_url('/mod/forum/discuss.php', array('d' => $item->discussion), 'p' . $item->id); 692 $pagename = format_string($item->subject, true, array('context' => context_module::instance($item->cmid))); 693 $pagename = html_writer::link($pageurl, $pagename); 694 $courseurl = course_get_url($item->courseid, $cm->sectionnum); 695 $cmname = html_writer::link($cm->url, $cm->get_formatted_name()); 696 $coursename = format_string($item->fullname, true, array('context' => context_course::instance($item->courseid))); 697 $coursename = html_writer::link($courseurl, $coursename); 698 $icon = html_writer::link($pageurl, html_writer::empty_tag('img', array('src' => $cm->get_icon_url()))); 699 $tagfeed->add($icon, $pagename, $cmname.'<br>'.$coursename); 700 } 701 702 $content = $OUTPUT->render_from_template('core_tag/tagfeed', 703 $tagfeed->export_for_template($OUTPUT)); 704 705 return new core_tag\output\tagindex($tag, 'mod_forum', 'forum_posts', $content, 706 $exclusivemode, $fromctx, $ctx, $rec, $page, $totalpages); 707 } 708 } 709 710 /** 711 * Update the calendar entries for this forum activity. 712 * 713 * @param stdClass $forum the row from the database table forum. 714 * @param int $cmid The coursemodule id 715 * @return bool 716 */ 717 function forum_update_calendar($forum, $cmid) { 718 global $DB, $CFG; 719 720 require_once($CFG->dirroot.'/calendar/lib.php'); 721 722 $event = new stdClass(); 723 724 if (!empty($forum->duedate)) { 725 $event->name = get_string('calendardue', 'forum', $forum->name); 726 $event->description = format_module_intro('forum', $forum, $cmid, false); 727 $event->format = FORMAT_HTML; 728 $event->courseid = $forum->course; 729 $event->modulename = 'forum'; 730 $event->instance = $forum->id; 731 $event->type = CALENDAR_EVENT_TYPE_ACTION; 732 $event->eventtype = FORUM_EVENT_TYPE_DUE; 733 $event->timestart = $forum->duedate; 734 $event->timesort = $forum->duedate; 735 $event->visible = instance_is_visible('forum', $forum); 736 } 737 738 $event->id = $DB->get_field('event', 'id', 739 array('modulename' => 'forum', 'instance' => $forum->id, 'eventtype' => FORUM_EVENT_TYPE_DUE)); 740 741 if ($event->id) { 742 $calendarevent = calendar_event::load($event->id); 743 if (!empty($forum->duedate)) { 744 // Calendar event exists so update it. 745 $calendarevent->update($event); 746 } else { 747 // Calendar event is no longer needed. 748 $calendarevent->delete(); 749 } 750 } else if (!empty($forum->duedate)) { 751 // Event doesn't exist so create one. 752 calendar_event::create($event); 753 } 754 755 return true; 756 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body