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