weekly release 4.0dev
[moodle.git] / mod / forum / deprecatedlib.php
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/>.
17 /**
18  * @package   mod_forum
19  * @copyright 2014 Andrew Robert Nicols <andrew@nicols.co.uk>
20  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
21  */
23 defined('MOODLE_INTERNAL') || die();
25 // Deprecated a very long time ago.
27 /**
28  * @deprecated since Moodle 1.1 - please do not use this function any more.
29  */
30 function forum_count_unrated_posts() {
31     throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
32 }
35 // Since Moodle 1.5.
37 /**
38  * @deprecated since Moodle 1.5 - please do not use this function any more.
39  */
40 function forum_tp_count_discussion_read_records() {
41     throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
42 }
44 /**
45  * @deprecated since Moodle 1.5 - please do not use this function any more.
46  */
47 function forum_get_user_discussions() {
48     throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
49 }
52 // Since Moodle 1.6.
54 /**
55  * @deprecated since Moodle 1.6 - please do not use this function any more.
56  */
57 function forum_tp_count_forum_posts() {
58     throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
59 }
61 /**
62  * @deprecated since Moodle 1.6 - please do not use this function any more.
63  */
64 function forum_tp_count_forum_read_records() {
65     throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
66 }
69 // Since Moodle 1.7.
71 /**
72  * @deprecated since Moodle 1.7 - please do not use this function any more.
73  */
74 function forum_get_open_modes() {
75     throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
76 }
79 // Since Moodle 1.9.
81 /**
82  * @deprecated since Moodle 1.9 MDL-13303 - please do not use this function any more.
83  */
84 function forum_get_child_posts() {
85     throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
86 }
88 /**
89  * @deprecated since Moodle 1.9 MDL-13303 - please do not use this function any more.
90  */
91 function forum_get_discussion_posts() {
92     throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
93 }
96 // Since Moodle 2.0.
98 /**
99  * @deprecated since Moodle 2.0 MDL-21657 - please do not use this function any more.
100  */
101 function forum_get_ratings() {
102     throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
105 /**
106  * @deprecated since Moodle 2.0 MDL-14632 - please do not use this function any more.
107  */
108 function forum_get_tracking_link() {
109     throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
112 /**
113  * @deprecated since Moodle 2.0 MDL-14113 - please do not use this function any more.
114  */
115 function forum_tp_count_discussion_unread_posts() {
116     throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
119 /**
120  * @deprecated since Moodle 2.0 MDL-23479 - please do not use this function any more.
121  */
122 function forum_convert_to_roles() {
123     throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
126 /**
127  * @deprecated since Moodle 2.0 MDL-14113 - please do not use this function any more.
128  */
129 function forum_tp_get_read_records() {
130     throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
133 /**
134  * @deprecated since Moodle 2.0 MDL-14113 - please do not use this function any more.
135  */
136 function forum_tp_get_discussion_read_records() {
137     throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
140 // Deprecated in 2.3.
142 /**
143  * @deprecated since Moodle 2.3 MDL-33166 - please do not use this function any more.
144  */
145 function forum_user_enrolled() {
146     throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
150 // Deprecated in 2.4.
152 /**
153  * @deprecated since Moodle 2.4 use forum_user_can_see_post() instead
154  */
155 function forum_user_can_view_post() {
156     throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
160 // Deprecated in 2.6.
162 /**
163  * FORUM_TRACKING_ON - deprecated alias for FORUM_TRACKING_FORCED.
164  * @deprecated since 2.6
165  */
166 define('FORUM_TRACKING_ON', 2);
168 /**
169  * @deprecated since Moodle 2.6
170  * @see shorten_text()
171  */
172 function forum_shorten_post($message) {
173     throw new coding_exception(__FUNCTION__ . '() can not be used any more. '
174         . 'Please use shorten_text($message, $CFG->forum_shortpost) instead.');
177 // Deprecated in 2.8.
179 /**
180  * @deprecated since Moodle 2.8 use \mod_forum\subscriptions::is_subscribed() instead
181  */
182 function forum_is_subscribed() {
183     throw new coding_exception(__FUNCTION__ . '() can not be used any more.');
186 /**
187  * @deprecated since Moodle 2.8 use \mod_forum\subscriptions::subscribe_user() instead
188  */
189 function forum_subscribe() {
190     throw new coding_exception(__FUNCTION__ . '() can not be used any more. Please use '
191         . \mod_forum\subscriptions::class . '::subscribe_user() instead');
194 /**
195  * @deprecated since Moodle 2.8 use \mod_forum\subscriptions::unsubscribe_user() instead
196  */
197 function forum_unsubscribe() {
198     throw new coding_exception(__FUNCTION__ . '() can not be used any more. Please use '
199         . \mod_forum\subscriptions::class . '::unsubscribe_user() instead');
202 /**
203  * @deprecated since Moodle 2.8 use \mod_forum\subscriptions::fetch_subscribed_users() instead
204   */
205 function forum_subscribed_users() {
206     throw new coding_exception(__FUNCTION__ . '() can not be used any more. Please use '
207         . \mod_forum\subscriptions::class . '::fetch_subscribed_users() instead');
210 /**
211  * Determine whether the forum is force subscribed.
212  *
213  * @deprecated since Moodle 2.8 use \mod_forum\subscriptions::is_forcesubscribed() instead
214  */
215 function forum_is_forcesubscribed($forum) {
216     throw new coding_exception(__FUNCTION__ . '() can not be used any more. Please use '
217         . \mod_forum\subscriptions::class . '::is_forcesubscribed() instead');
220 /**
221  * @deprecated since Moodle 2.8 use \mod_forum\subscriptions::set_subscription_mode() instead
222  */
223 function forum_forcesubscribe($forumid, $value = 1) {
224     throw new coding_exception(__FUNCTION__ . '() can not be used any more. Please use '
225         . \mod_forum\subscriptions::class . '::set_subscription_mode() instead');
228 /**
229  * @deprecated since Moodle 2.8 use \mod_forum\subscriptions::get_subscription_mode() instead
230  */
231 function forum_get_forcesubscribed($forum) {
232     throw new coding_exception(__FUNCTION__ . '() can not be used any more. Please use '
233         . \mod_forum\subscriptions::class . '::set_subscription_mode() instead');
236 /**
237  * @deprecated since Moodle 2.8 use \mod_forum\subscriptions::is_subscribed in combination wtih
238  * \mod_forum\subscriptions::fill_subscription_cache_for_course instead.
239  */
240 function forum_get_subscribed_forums() {
241     throw new coding_exception(__FUNCTION__ . '() can not be used any more. Please use '
242         . \mod_forum\subscriptions::class . '::is_subscribed(), and '
243         . \mod_forum\subscriptions::class . '::fill_subscription_cache_for_course() instead');
246 /**
247  * @deprecated since Moodle 2.8 use \mod_forum\subscriptions::get_unsubscribable_forums() instead
248  */
249 function forum_get_optional_subscribed_forums() {
250     throw new coding_exception(__FUNCTION__ . '() can not be used any more. Please use '
251         . \mod_forum\subscriptions::class . '::get_unsubscribable_forums() instead');
254 /**
255  * @deprecated since Moodle 2.8 use \mod_forum\subscriptions::get_potential_subscribers() instead
256  */
257 function forum_get_potential_subscribers() {
258     throw new coding_exception(__FUNCTION__ . '() can not be used any more. Please use '
259         . \mod_forum\subscriptions::class . '::get_potential_subscribers() instead');
262 /**
263  * Builds and returns the body of the email notification in plain text.
264  *
265  * @uses CONTEXT_MODULE
266  * @param object $course
267  * @param object $cm
268  * @param object $forum
269  * @param object $discussion
270  * @param object $post
271  * @param object $userfrom
272  * @param object $userto
273  * @param boolean $bare
274  * @param string $replyaddress The inbound address that a user can reply to the generated e-mail with. [Since 2.8].
275  * @return string The email body in plain text format.
276  * @deprecated since Moodle 3.0 use \mod_forum\output\forum_post_email instead
277  */
278 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false, $replyaddress = null) {
279     global $PAGE;
280     $renderable = new \mod_forum\output\forum_post_email(
281         $course,
282         $cm,
283         $forum,
284         $discussion,
285         $post,
286         $userfrom,
287         $userto,
288         forum_user_can_post($forum, $discussion, $userto, $cm, $course)
289         );
291     $modcontext = context_module::instance($cm->id);
292     $renderable->viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
294     if ($bare) {
295         $renderer = $PAGE->get_renderer('mod_forum', 'emaildigestfull', 'textemail');
296     } else {
297         $renderer = $PAGE->get_renderer('mod_forum', 'email', 'textemail');
298     }
300     debugging("forum_make_mail_text() has been deprecated, please use the \mod_forum\output\forum_post_email renderable instead.",
301             DEBUG_DEVELOPER);
303     return $renderer->render($renderable);
306 /**
307  * Builds and returns the body of the email notification in html format.
308  *
309  * @param object $course
310  * @param object $cm
311  * @param object $forum
312  * @param object $discussion
313  * @param object $post
314  * @param object $userfrom
315  * @param object $userto
316  * @param string $replyaddress The inbound address that a user can reply to the generated e-mail with. [Since 2.8].
317  * @return string The email text in HTML format
318  * @deprecated since Moodle 3.0 use \mod_forum\output\forum_post_email instead
319  */
320 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $replyaddress = null) {
321     return forum_make_mail_post($course,
322         $cm,
323         $forum,
324         $discussion,
325         $post,
326         $userfrom,
327         $userto,
328         forum_user_can_post($forum, $discussion, $userto, $cm, $course)
329     );
332 /**
333  * Given the data about a posting, builds up the HTML to display it and
334  * returns the HTML in a string.  This is designed for sending via HTML email.
335  *
336  * @param object $course
337  * @param object $cm
338  * @param object $forum
339  * @param object $discussion
340  * @param object $post
341  * @param object $userfrom
342  * @param object $userto
343  * @param bool $ownpost
344  * @param bool $reply
345  * @param bool $link
346  * @param bool $rate
347  * @param string $footer
348  * @return string
349  * @deprecated since Moodle 3.0 use \mod_forum\output\forum_post_email instead
350  */
351 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
352                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
353     global $PAGE;
354     $renderable = new \mod_forum\output\forum_post_email(
355         $course,
356         $cm,
357         $forum,
358         $discussion,
359         $post,
360         $userfrom,
361         $userto,
362         $reply);
364     $modcontext = context_module::instance($cm->id);
365     $renderable->viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
367     // Assume that this is being used as a standard forum email.
368     $renderer = $PAGE->get_renderer('mod_forum', 'email', 'htmlemail');
370     debugging("forum_make_mail_post() has been deprecated, please use the \mod_forum\output\forum_post_email renderable instead.",
371             DEBUG_DEVELOPER);
373     return $renderer->render($renderable);
376 /**
377  * Removes properties from user record that are not necessary for sending post notifications.
378  *
379  * @param stdClass $user
380  * @return void, $user parameter is modified
381  * @deprecated since Moodle 3.7
382  */
383 function forum_cron_minimise_user_record(stdClass $user) {
384     debugging("forum_cron_minimise_user_record() has been deprecated and has not been replaced.",
385             DEBUG_DEVELOPER);
387     // We store large amount of users in one huge array,
388     // make sure we do not store info there we do not actually need
389     // in mail generation code or messaging.
391     unset($user->institution);
392     unset($user->department);
393     unset($user->address);
394     unset($user->city);
395     unset($user->currentlogin);
396     unset($user->description);
397     unset($user->descriptionformat);
400 /**
401  * Function to be run periodically according to the scheduled task.
402  *
403  * Finds all posts that have yet to be mailed out, and mails them out to all subscribers as well as other maintance
404  * tasks.
405  *
406  * @deprecated since Moodle 3.7
407  */
408 function forum_cron() {
409     debugging("forum_cron() has been deprecated and replaced with new tasks. Please uses these instead.",
410             DEBUG_DEVELOPER);
413 /**
414  * Prints a forum discussion
415  *
416  * @uses CONTEXT_MODULE
417  * @uses FORUM_MODE_FLATNEWEST
418  * @uses FORUM_MODE_FLATOLDEST
419  * @uses FORUM_MODE_THREADED
420  * @uses FORUM_MODE_NESTED
421  * @param stdClass $course
422  * @param stdClass $cm
423  * @param stdClass $forum
424  * @param stdClass $discussion
425  * @param stdClass $post
426  * @param int $mode
427  * @param mixed $canreply
428  * @param bool $canrate
429  * @deprecated since Moodle 3.7
430  */
431 function forum_print_discussion($course, $cm, $forum, $discussion, $post, $mode, $canreply=NULL, $canrate=false) {
432     debugging('forum_print_discussion() has been deprecated, ' .
433         'please use \mod_forum\local\renderers\discussion instead.', DEBUG_DEVELOPER);
435     global $USER, $CFG;
437     require_once($CFG->dirroot.'/rating/lib.php');
439     $ownpost = (isloggedin() && $USER->id == $post->userid);
441     $modcontext = context_module::instance($cm->id);
442     if ($canreply === NULL) {
443         $reply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
444     } else {
445         $reply = $canreply;
446     }
448     // $cm holds general cache for forum functions
449     $cm->cache = new stdClass;
450     $cm->cache->groups      = groups_get_all_groups($course->id, 0, $cm->groupingid);
451     $cm->cache->usersgroups = array();
453     $posters = array();
455     // preload all posts - TODO: improve...
456     if ($mode == FORUM_MODE_FLATNEWEST) {
457         $sort = "p.created DESC";
458     } else {
459         $sort = "p.created ASC";
460     }
462     $forumtracked = forum_tp_is_tracked($forum);
463     $posts = forum_get_all_discussion_posts($discussion->id, $sort, $forumtracked);
464     $post = $posts[$post->id];
466     foreach ($posts as $pid=>$p) {
467         $posters[$p->userid] = $p->userid;
468     }
470     // preload all groups of ppl that posted in this discussion
471     if ($postersgroups = groups_get_all_groups($course->id, $posters, $cm->groupingid, 'gm.id, gm.groupid, gm.userid')) {
472         foreach($postersgroups as $pg) {
473             if (!isset($cm->cache->usersgroups[$pg->userid])) {
474                 $cm->cache->usersgroups[$pg->userid] = array();
475             }
476             $cm->cache->usersgroups[$pg->userid][$pg->groupid] = $pg->groupid;
477         }
478         unset($postersgroups);
479     }
481     //load ratings
482     if ($forum->assessed != RATING_AGGREGATE_NONE) {
483         $ratingoptions = new stdClass;
484         $ratingoptions->context = $modcontext;
485         $ratingoptions->component = 'mod_forum';
486         $ratingoptions->ratingarea = 'post';
487         $ratingoptions->items = $posts;
488         $ratingoptions->aggregate = $forum->assessed;//the aggregation method
489         $ratingoptions->scaleid = $forum->scale;
490         $ratingoptions->userid = $USER->id;
491         if ($forum->type == 'single' or !$discussion->id) {
492             $ratingoptions->returnurl = "$CFG->wwwroot/mod/forum/view.php?id=$cm->id";
493         } else {
494             $ratingoptions->returnurl = "$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id";
495         }
496         $ratingoptions->assesstimestart = $forum->assesstimestart;
497         $ratingoptions->assesstimefinish = $forum->assesstimefinish;
499         $rm = new rating_manager();
500         $posts = $rm->get_ratings($ratingoptions);
501     }
504     $post->forum = $forum->id;   // Add the forum id to the post object, later used by forum_print_post
505     $post->forumtype = $forum->type;
507     $post->subject = format_string($post->subject);
509     $postread = !empty($post->postread);
511     forum_print_post_start($post);
512     forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, false,
513                          '', '', $postread, true, $forumtracked);
515     switch ($mode) {
516         case FORUM_MODE_FLATOLDEST :
517         case FORUM_MODE_FLATNEWEST :
518         default:
519             forum_print_posts_flat($course, $cm, $forum, $discussion, $post, $mode, $reply, $forumtracked, $posts);
520             break;
522         case FORUM_MODE_THREADED :
523             forum_print_posts_threaded($course, $cm, $forum, $discussion, $post, 0, $reply, $forumtracked, $posts);
524             break;
526         case FORUM_MODE_NESTED :
527             forum_print_posts_nested($course, $cm, $forum, $discussion, $post, $reply, $forumtracked, $posts);
528             break;
529     }
530     forum_print_post_end($post);
534 /**
535  * Return a static array of posts that are open.
536  *
537  * @return array
538  * @deprecated since Moodle 3.7
539  */
540 function forum_post_nesting_cache() {
541     debugging('forum_post_nesting_cache() has been deprecated, ' .
542         'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
543     static $nesting = array();
544     return $nesting;
547 /**
548  * Return true for the first time this post was started
549  *
550  * @param int $id The id of the post to start
551  * @return bool
552  * @deprecated since Moodle 3.7
553  */
554 function forum_should_start_post_nesting($id) {
555     debugging('forum_should_start_post_nesting() has been deprecated, ' .
556         'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
557     $cache = forum_post_nesting_cache();
558     if (!array_key_exists($id, $cache)) {
559         $cache[$id] = 1;
560         return true;
561     } else {
562         $cache[$id]++;
563         return false;
564     }
567 /**
568  * Return true when all the opens are nested with a close.
569  *
570  * @param int $id The id of the post to end
571  * @return bool
572  * @deprecated since Moodle 3.7
573  */
574 function forum_should_end_post_nesting($id) {
575     debugging('forum_should_end_post_nesting() has been deprecated, ' .
576         'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
577     $cache = forum_post_nesting_cache();
578     if (!array_key_exists($id, $cache)) {
579         return true;
580     } else {
581         $cache[$id]--;
582         if ($cache[$id] == 0) {
583             unset($cache[$id]);
584             return true;
585         }
586     }
587     return false;
590 /**
591  * Start a forum post container
592  *
593  * @param object $post The post to print.
594  * @param bool $return Return the string or print it
595  * @return string
596  * @deprecated since Moodle 3.7
597  */
598 function forum_print_post_start($post, $return = false) {
599     debugging('forum_print_post_start() has been deprecated, ' .
600         'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
601     $output = '';
603     if (forum_should_start_post_nesting($post->id)) {
604         $attributes = [
605             'id' => 'p'.$post->id,
606             'tabindex' => -1,
607             'class' => 'relativelink'
608         ];
609         $output .= html_writer::start_tag('article', $attributes);
610     }
611     if ($return) {
612         return $output;
613     }
614     echo $output;
615     return;
618 /**
619  * End a forum post container
620  *
621  * @param object $post The post to print.
622  * @param bool $return Return the string or print it
623  * @return string
624  * @deprecated since Moodle 3.7
625  */
626 function forum_print_post_end($post, $return = false) {
627     debugging('forum_print_post_end() has been deprecated, ' .
628         'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
629     $output = '';
631     if (forum_should_end_post_nesting($post->id)) {
632         $output .= html_writer::end_tag('article');
633     }
634     if ($return) {
635         return $output;
636     }
637     echo $output;
638     return;
641 /**
642  * Print a forum post
643  * This function should always be surrounded with calls to forum_print_post_start
644  * and forum_print_post_end to create the surrounding container for the post.
645  * Replies can be nested before forum_print_post_end and should reflect the structure of
646  * thread.
647  *
648  * @global object
649  * @global object
650  * @uses FORUM_MODE_THREADED
651  * @uses PORTFOLIO_FORMAT_PLAINHTML
652  * @uses PORTFOLIO_FORMAT_FILE
653  * @uses PORTFOLIO_FORMAT_RICHHTML
654  * @uses PORTFOLIO_ADD_TEXT_LINK
655  * @uses CONTEXT_MODULE
656  * @param object $post The post to print.
657  * @param object $discussion
658  * @param object $forum
659  * @param object $cm
660  * @param object $course
661  * @param boolean $ownpost Whether this post belongs to the current user.
662  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
663  * @param boolean $link Just print a shortened version of the post as a link to the full post.
664  * @param string $footer Extra stuff to print after the message.
665  * @param string $highlight Space-separated list of terms to highlight.
666  * @param int $post_read true, false or -99. If we already know whether this user
667  *          has read this post, pass that in, otherwise, pass in -99, and this
668  *          function will work it out.
669  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
670  *          the current user can't see this post, if this argument is true
671  *          (the default) then print a dummy 'you can't see this post' post.
672  *          If false, don't output anything at all.
673  * @param bool|null $istracked
674  * @return void
675  * @deprecated since Moodle 3.7
676  */
677 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
678                           $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
679     debugging('forum_print_post() has been deprecated, ' .
680         'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
681     global $USER, $CFG, $OUTPUT;
683     require_once($CFG->libdir . '/filelib.php');
685     // String cache
686     static $str;
687     // This is an extremely hacky way to ensure we only print the 'unread' anchor
688     // the first time we encounter an unread post on a page. Ideally this would
689     // be moved into the caller somehow, and be better testable. But at the time
690     // of dealing with this bug, this static workaround was the most surgical and
691     // it fits together with only printing th unread anchor id once on a given page.
692     static $firstunreadanchorprinted = false;
694     $modcontext = context_module::instance($cm->id);
696     $post->course = $course->id;
697     $post->forum  = $forum->id;
698     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
699     if (!empty($CFG->enableplagiarism)) {
700         require_once($CFG->libdir.'/plagiarismlib.php');
701         $post->message .= plagiarism_get_links(array('userid' => $post->userid,
702             'content' => $post->message,
703             'cmid' => $cm->id,
704             'course' => $post->course,
705             'forum' => $post->forum));
706     }
708     // caching
709     if (!isset($cm->cache)) {
710         $cm->cache = new stdClass;
711     }
713     if (!isset($cm->cache->caps)) {
714         $cm->cache->caps = array();
715         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
716         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
717         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
718         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
719         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
720         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
721         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
722         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
723         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
724     }
726     if (!isset($cm->uservisible)) {
727         $cm->uservisible = \core_availability\info_module::is_user_visible($cm, 0, false);
728     }
730     if ($istracked && is_null($postisread)) {
731         $postisread = forum_tp_is_post_read($USER->id, $post);
732     }
734     if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm, false)) {
735         // Do _not_ check the deleted flag - we need to display a different UI.
736         $output = '';
737         if (!$dummyifcantsee) {
738             if ($return) {
739                 return $output;
740             }
741             echo $output;
742             return;
743         }
745         $output .= html_writer::start_tag('div', array('class' => 'forumpost clearfix',
746                                                        'aria-label' => get_string('hiddenforumpost', 'forum')));
747         $output .= html_writer::start_tag('header', array('class' => 'row header'));
748         $output .= html_writer::tag('div', '', array('class' => 'left picture', 'role' => 'presentation')); // Picture.
749         if ($post->parent) {
750             $output .= html_writer::start_tag('div', array('class' => 'topic'));
751         } else {
752             $output .= html_writer::start_tag('div', array('class' => 'topic starter'));
753         }
754         $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class' => 'subject',
755                                                                                            'role' => 'header',
756                                                                                            'id' => ('headp' . $post->id))); // Subject.
757         $authorclasses = array('class' => 'author');
758         $output .= html_writer::tag('address', get_string('forumauthorhidden', 'forum'), $authorclasses); // Author.
759         $output .= html_writer::end_tag('div');
760         $output .= html_writer::end_tag('header'); // Header.
761         $output .= html_writer::start_tag('div', array('class'=>'row'));
762         $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
763         $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
764         $output .= html_writer::end_tag('div'); // row
765         $output .= html_writer::end_tag('div'); // forumpost
767         if ($return) {
768             return $output;
769         }
770         echo $output;
771         return;
772     }
774     if (!empty($post->deleted)) {
775         // Note: Posts marked as deleted are still returned by the above forum_user_can_post because it is required for
776         // nesting of posts.
777         $output = '';
778         if (!$dummyifcantsee) {
779             if ($return) {
780                 return $output;
781             }
782             echo $output;
783             return;
784         }
785         $output .= html_writer::start_tag('div', [
786                 'class' => 'forumpost clearfix',
787                 'aria-label' => get_string('forumbodydeleted', 'forum'),
788             ]);
790         $output .= html_writer::start_tag('header', array('class' => 'row header'));
791         $output .= html_writer::tag('div', '', array('class' => 'left picture', 'role' => 'presentation'));
793         $classes = ['topic'];
794         if (!empty($post->parent)) {
795             $classes[] = 'starter';
796         }
797         $output .= html_writer::start_tag('div', ['class' => implode(' ', $classes)]);
799         // Subject.
800         $output .= html_writer::tag('div', get_string('forumsubjectdeleted', 'forum'), [
801                 'class' => 'subject',
802                 'role' => 'header',
803                 'id' => ('headp' . $post->id)
804             ]);
806         // Author.
807         $output .= html_writer::tag('address', '', ['class' => 'author']);
809         $output .= html_writer::end_tag('div');
810         $output .= html_writer::end_tag('header'); // End header.
811         $output .= html_writer::start_tag('div', ['class' => 'row']);
812         $output .= html_writer::tag('div', '&nbsp;', ['class' => 'left side']); // Groups.
813         $output .= html_writer::tag('div', get_string('forumbodydeleted', 'forum'), ['class' => 'content']); // Content.
814         $output .= html_writer::end_tag('div'); // End row.
815         $output .= html_writer::end_tag('div'); // End forumpost.
817         if ($return) {
818             return $output;
819         }
820         echo $output;
821         return;
822     }
824     if (empty($str)) {
825         $str = new stdClass;
826         $str->edit         = get_string('edit', 'forum');
827         $str->delete       = get_string('delete', 'forum');
828         $str->reply        = get_string('reply', 'forum');
829         $str->parent       = get_string('parent', 'forum');
830         $str->pruneheading = get_string('pruneheading', 'forum');
831         $str->prune        = get_string('prune', 'forum');
832         $str->displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
833         $str->markread     = get_string('markread', 'forum');
834         $str->markunread   = get_string('markunread', 'forum');
835     }
837     $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
839     // Build an object that represents the posting user
840     $postuser = new stdClass;
841     $postuserfields = explode(',', implode(',', \core_user\fields::get_picture_fields()));
842     $postuser = username_load_fields_from_object($postuser, $post, null, $postuserfields);
843     $postuser->id = $post->userid;
844     $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
845     $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
847     // Prepare the groups the posting user belongs to
848     if (isset($cm->cache->usersgroups)) {
849         $groups = array();
850         if (isset($cm->cache->usersgroups[$post->userid])) {
851             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
852                 $groups[$gid] = $cm->cache->groups[$gid];
853             }
854         }
855     } else {
856         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
857     }
859     // Prepare the attachements for the post, files then images
860     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
862     // Determine if we need to shorten this post
863     $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
865     // Prepare an array of commands
866     $commands = array();
868     // Add a permalink.
869     $permalink = new moodle_url($discussionlink);
870     $permalink->set_anchor('p' . $post->id);
871     $commands[] = array('url' => $permalink, 'text' => get_string('permalink', 'forum'), 'attributes' => ['rel' => 'bookmark']);
873     // SPECIAL CASE: The front page can display a news item post to non-logged in users.
874     // Don't display the mark read / unread controls in this case.
875     if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
876         $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
877         $text = $str->markunread;
878         if (!$postisread) {
879             $url->param('mark', 'read');
880             $text = $str->markread;
881         }
882         if ($str->displaymode == FORUM_MODE_THREADED) {
883             $url->param('parent', $post->parent);
884         } else {
885             $url->set_anchor('p'.$post->id);
886         }
887         $commands[] = array('url'=>$url, 'text'=>$text, 'attributes' => ['rel' => 'bookmark']);
888     }
890     // Zoom in to the parent specifically
891     if ($post->parent) {
892         $url = new moodle_url($discussionlink);
893         if ($str->displaymode == FORUM_MODE_THREADED) {
894             $url->param('parent', $post->parent);
895         } else {
896             $url->set_anchor('p'.$post->parent);
897         }
898         $commands[] = array('url'=>$url, 'text'=>$str->parent, 'attributes' => ['rel' => 'bookmark']);
899     }
901     // Hack for allow to edit news posts those are not displayed yet until they are displayed
902     $age = time() - $post->created;
903     if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
904         $age = 0;
905     }
907     if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
908         if (has_capability('moodle/course:manageactivities', $modcontext)) {
909             // The first post in single simple is the forum description.
910             $commands[] = array('url'=>new moodle_url('/course/modedit.php', array('update'=>$cm->id, 'sesskey'=>sesskey(), 'return'=>1)), 'text'=>$str->edit);
911         }
912     } else if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
913         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
914     }
916     if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
917         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
918     }
920     if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
921         // Do not allow deleting of first post in single simple type.
922     } else if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
923         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
924     }
926     if ($reply) {
927         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php#mformforum', array('reply'=>$post->id)), 'text'=>$str->reply);
928     }
930     if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
931         $p = array('postid' => $post->id);
932         require_once($CFG->libdir.'/portfoliolib.php');
933         $button = new portfolio_add_button();
934         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), 'mod_forum');
935         if (empty($attachments)) {
936             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
937         } else {
938             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
939         }
941         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
942         if (!empty($porfoliohtml)) {
943             $commands[] = $porfoliohtml;
944         }
945     }
946     // Finished building commands
949     // Begin output
951     $output  = '';
953     if ($istracked) {
954         if ($postisread) {
955             $forumpostclass = ' read';
956         } else {
957             $forumpostclass = ' unread';
958             // If this is the first unread post printed then give it an anchor and id of unread.
959             if (!$firstunreadanchorprinted) {
960                 $output .= html_writer::tag('a', '', array('id' => 'unread'));
961                 $firstunreadanchorprinted = true;
962             }
963         }
964     } else {
965         // ignore trackign status if not tracked or tracked param missing
966         $forumpostclass = '';
967     }
969     $topicclass = '';
970     if (empty($post->parent)) {
971         $topicclass = ' firstpost starter';
972     }
974     if (!empty($post->lastpost)) {
975         $forumpostclass .= ' lastpost';
976     }
978     // Flag to indicate whether we should hide the author or not.
979     $authorhidden = forum_is_author_hidden($post, $forum);
980     $postbyuser = new stdClass;
981     $postbyuser->post = $post->subject;
982     $postbyuser->user = $postuser->fullname;
983     $discussionbyuser = get_string('postbyuser', 'forum', $postbyuser);
984     // Begin forum post.
985     $output .= html_writer::start_div('forumpost clearfix' . $forumpostclass . $topicclass,
986         ['aria-label' => $discussionbyuser]);
987     // Begin header row.
988     $output .= html_writer::start_tag('header', ['class' => 'row header clearfix']);
990     // User picture.
991     if (!$authorhidden) {
992         $picture = $OUTPUT->user_picture($postuser, ['courseid' => $course->id]);
993         $output .= html_writer::div($picture, 'left picture', ['role' => 'presentation']);
994         $topicclass = 'topic' . $topicclass;
995     }
997     // Begin topic column.
998     $output .= html_writer::start_div($topicclass);
999     $postsubject = $post->subject;
1000     if (empty($post->subjectnoformat)) {
1001         $postsubject = format_string($postsubject);
1002     }
1003     $output .= html_writer::div($postsubject, 'subject', ['role' => 'heading', 'aria-level' => '1', 'id' => ('headp' . $post->id)]);
1005     if ($authorhidden) {
1006         $bytext = userdate_htmltime($post->created);
1007     } else {
1008         $by = new stdClass();
1009         $by->date = userdate_htmltime($post->created);
1010         $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
1011         $bytext = get_string('bynameondate', 'forum', $by);
1012     }
1013     $bytextoptions = [
1014         'class' => 'author'
1015     ];
1016     $output .= html_writer::tag('address', $bytext, $bytextoptions);
1017     // End topic column.
1018     $output .= html_writer::end_div();
1020     // End header row.
1021     $output .= html_writer::end_tag('header');
1023     // Row with the forum post content.
1024     $output .= html_writer::start_div('row maincontent clearfix');
1025     // Show if author is not hidden or we have groups.
1026     if (!$authorhidden || $groups) {
1027         $output .= html_writer::start_div('left');
1028         $groupoutput = '';
1029         if ($groups) {
1030             $groupoutput = print_group_picture($groups, $course->id, false, true, true);
1031         }
1032         if (empty($groupoutput)) {
1033             $groupoutput = '&nbsp;';
1034         }
1035         $output .= html_writer::div($groupoutput, 'grouppictures');
1036         $output .= html_writer::end_div(); // Left side.
1037     }
1039     $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
1040     $output .= html_writer::start_tag('div', array('class'=>'content'));
1042     $options = new stdClass;
1043     $options->para    = false;
1044     $options->trusted = $post->messagetrust;
1045     $options->context = $modcontext;
1046     if ($shortenpost) {
1047         // Prepare shortened version by filtering the text then shortening it.
1048         $postclass    = 'shortenedpost';
1049         $postcontent  = format_text($post->message, $post->messageformat, $options);
1050         $postcontent  = shorten_text($postcontent, $CFG->forum_shortpost);
1051         $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
1052         $postcontent .= html_writer::tag('div', '('.get_string('numwords', 'moodle', count_words($post->message)).')',
1053             array('class'=>'post-word-count'));
1054     } else {
1055         // Prepare whole post
1056         $postclass    = 'fullpost';
1057         $postcontent  = format_text($post->message, $post->messageformat, $options, $course->id);
1058         if (!empty($highlight)) {
1059             $postcontent = highlight($highlight, $postcontent);
1060         }
1061         if (!empty($forum->displaywordcount)) {
1062             $postcontent .= html_writer::tag('div', get_string('numwords', 'moodle', count_words($postcontent)),
1063                 array('class'=>'post-word-count'));
1064         }
1065         $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
1066     }
1068     if (\core_tag_tag::is_enabled('mod_forum', 'forum_posts')) {
1069         $postcontent .= $OUTPUT->tag_list(core_tag_tag::get_item_tags('mod_forum', 'forum_posts', $post->id), null, 'forum-tags');
1070     }
1072     // Output the post content
1073     $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
1074     $output .= html_writer::end_tag('div'); // Content
1075     $output .= html_writer::end_tag('div'); // Content mask
1076     $output .= html_writer::end_tag('div'); // Row
1078     $output .= html_writer::start_tag('nav', array('class' => 'row side'));
1079     $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
1080     $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
1082     if (!empty($attachments)) {
1083         $output .= html_writer::tag('div', $attachments, array('class' => 'attachments'));
1084     }
1086     // Output ratings
1087     if (!empty($post->rating)) {
1088         $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
1089     }
1091     // Output the commands
1092     $commandhtml = array();
1093     foreach ($commands as $command) {
1094         if (is_array($command)) {
1095             $attributes = ['class' => 'nav-item nav-link'];
1096             if (isset($command['attributes'])) {
1097                 $attributes = array_merge($attributes, $command['attributes']);
1098             }
1099             $commandhtml[] = html_writer::link($command['url'], $command['text'], $attributes);
1100         } else {
1101             $commandhtml[] = $command;
1102         }
1103     }
1104     $output .= html_writer::tag('div', implode(' ', $commandhtml), array('class' => 'commands nav'));
1106     // Output link to post if required
1107     if ($link) {
1108         if (forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext)) {
1109             $langstring = 'discussthistopic';
1110         } else {
1111             $langstring = 'viewthediscussion';
1112         }
1113         if ($post->replies == 1) {
1114             $replystring = get_string('repliesone', 'forum', $post->replies);
1115         } else {
1116             $replystring = get_string('repliesmany', 'forum', $post->replies);
1117         }
1118         if (!empty($discussion->unread) && $discussion->unread !== '-') {
1119             $replystring .= ' <span class="sep">/</span> <span class="unread">';
1120             $unreadlink = new moodle_url($discussionlink, null, 'unread');
1121             if ($discussion->unread == 1) {
1122                 $replystring .= html_writer::link($unreadlink, get_string('unreadpostsone', 'forum'));
1123             } else {
1124                 $replystring .= html_writer::link($unreadlink, get_string('unreadpostsnumber', 'forum', $discussion->unread));
1125             }
1126             $replystring .= '</span>';
1127         }
1129         $output .= html_writer::start_tag('div', array('class'=>'link'));
1130         $output .= html_writer::link($discussionlink, get_string($langstring, 'forum'));
1131         $output .= '&nbsp;('.$replystring.')';
1132         $output .= html_writer::end_tag('div'); // link
1133     }
1135     // Output footer if required
1136     if ($footer) {
1137         $output .= html_writer::tag('div', $footer, array('class'=>'footer'));
1138     }
1140     // Close remaining open divs
1141     $output .= html_writer::end_tag('div'); // content
1142     $output .= html_writer::end_tag('nav'); // row
1143     $output .= html_writer::end_tag('div'); // forumpost
1145     // Mark the forum post as read if required
1146     if ($istracked && !$CFG->forum_usermarksread && !$postisread) {
1147         forum_tp_mark_post_read($USER->id, $post);
1148     }
1150     if ($return) {
1151         return $output;
1152     }
1153     echo $output;
1154     return;
1157 /**
1158  * @global object
1159  * @global object
1160  * @uses FORUM_MODE_FLATNEWEST
1161  * @param object $course
1162  * @param object $cm
1163  * @param object $forum
1164  * @param object $discussion
1165  * @param object $post
1166  * @param object $mode
1167  * @param bool $reply
1168  * @param bool $forumtracked
1169  * @param array $posts
1170  * @return void
1171  * @deprecated since Moodle 3.7
1172  */
1173 function forum_print_posts_flat($course, &$cm, $forum, $discussion, $post, $mode, $reply, $forumtracked, $posts) {
1174     debugging('forum_print_posts_flat() has been deprecated, ' .
1175         'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
1176     global $USER, $CFG;
1178     $link  = false;
1180     foreach ($posts as $post) {
1181         if (!$post->parent) {
1182             continue;
1183         }
1184         $post->subject = format_string($post->subject);
1185         $ownpost = ($USER->id == $post->userid);
1187         $postread = !empty($post->postread);
1189         forum_print_post_start($post);
1190         forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
1191                              '', '', $postread, true, $forumtracked);
1192         forum_print_post_end($post);
1193     }
1196 /**
1197  * @todo Document this function
1198  *
1199  * @global object
1200  * @global object
1201  * @uses CONTEXT_MODULE
1202  * @return void
1203  * @deprecated since Moodle 3.7
1204  */
1205 function forum_print_posts_threaded($course, &$cm, $forum, $discussion, $parent, $depth, $reply, $forumtracked, $posts) {
1206     debugging('forum_print_posts_threaded() has been deprecated, ' .
1207         'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
1208     global $USER, $CFG;
1210     $link  = false;
1212     if (!empty($posts[$parent->id]->children)) {
1213         $posts = $posts[$parent->id]->children;
1215         $modcontext       = context_module::instance($cm->id);
1216         $canviewfullnames = has_capability('moodle/site:viewfullnames', $modcontext);
1218         foreach ($posts as $post) {
1220             echo '<div class="indent">';
1221             if ($depth > 0) {
1222                 $ownpost = ($USER->id == $post->userid);
1223                 $post->subject = format_string($post->subject);
1225                 $postread = !empty($post->postread);
1227                 forum_print_post_start($post);
1228                 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
1229                                      '', '', $postread, true, $forumtracked);
1230                 forum_print_post_end($post);
1231             } else {
1232                 if (!forum_user_can_see_post($forum, $discussion, $post, null, $cm, true)) {
1233                     if (forum_user_can_see_post($forum, $discussion, $post, null, $cm, false)) {
1234                         // This post has been deleted but still exists and may have children.
1235                         $subject = get_string('privacy:request:delete:post:subject', 'mod_forum');
1236                         $byline = '';
1237                     } else {
1238                         // The user can't see this post at all.
1239                         echo "</div>\n";
1240                         continue;
1241                     }
1242                 } else {
1243                     $by = new stdClass();
1244                     $by->name = fullname($post, $canviewfullnames);
1245                     $by->date = userdate_htmltime($post->modified);
1246                     $byline = ' ' . get_string("bynameondate", "forum", $by);
1247                     $subject = format_string($post->subject, true);
1248                 }
1250                 if ($forumtracked) {
1251                     if (!empty($post->postread)) {
1252                         $style = '<span class="forumthread read">';
1253                     } else {
1254                         $style = '<span class="forumthread unread">';
1255                     }
1256                 } else {
1257                     $style = '<span class="forumthread">';
1258                 }
1260                 echo $style;
1261                 echo "<a name='{$post->id}'></a>";
1262                 echo html_writer::link(new moodle_url('/mod/forum/discuss.php', [
1263                         'd' => $post->discussion,
1264                         'parent' => $post->id,
1265                     ]), $subject);
1266                 echo $byline;
1267                 echo "</span>";
1268             }
1270             forum_print_posts_threaded($course, $cm, $forum, $discussion, $post, $depth-1, $reply, $forumtracked, $posts);
1271             echo "</div>\n";
1272         }
1273     }
1276 /**
1277  * @todo Document this function
1278  * @global object
1279  * @global object
1280  * @return void
1281  * @deprecated since Moodle 3.7
1282  */
1283 function forum_print_posts_nested($course, &$cm, $forum, $discussion, $parent, $reply, $forumtracked, $posts) {
1284     debugging('forum_print_posts_nested() has been deprecated, ' .
1285         'please use \mod_forum\local\renderers\posts instead.', DEBUG_DEVELOPER);
1286     global $USER, $CFG;
1288     $link  = false;
1290     if (!empty($posts[$parent->id]->children)) {
1291         $posts = $posts[$parent->id]->children;
1293         foreach ($posts as $post) {
1295             echo '<div class="indent">';
1296             if (!isloggedin()) {
1297                 $ownpost = false;
1298             } else {
1299                 $ownpost = ($USER->id == $post->userid);
1300             }
1302             $post->subject = format_string($post->subject);
1303             $postread = !empty($post->postread);
1305             forum_print_post_start($post);
1306             forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
1307                                  '', '', $postread, true, $forumtracked);
1308             forum_print_posts_nested($course, $cm, $forum, $discussion, $post, $reply, $forumtracked, $posts);
1309             forum_print_post_end($post);
1310             echo "</div>\n";
1311         }
1312     }
1315 /**
1316  * Prints the discussion view screen for a forum.
1317  *
1318  * @param object $course The current course object.
1319  * @param object $forum Forum to be printed.
1320  * @param int $maxdiscussions
1321  * @param string $displayformat The display format to use (optional).
1322  * @param string $sort Sort arguments for database query (optional).
1323  * @param int $currentgroup
1324  * @param int $groupmode Group mode of the forum (optional).
1325  * @param int $page Page mode, page to display (optional).
1326  * @param int $perpage The maximum number of discussions per page(optional)
1327  * @param stdClass $cm
1328  * @deprecated since Moodle 3.7
1329  */
1330 function forum_print_latest_discussions($course, $forum, $maxdiscussions = -1, $displayformat = 'plain', $sort = '',
1331                                         $currentgroup = -1, $groupmode = -1, $page = -1, $perpage = 100, $cm = null) {
1332     debugging('forum_print_latest_discussions has been deprecated.', DEBUG_DEVELOPER);
1333     global $CFG, $USER, $OUTPUT;
1335     require_once($CFG->dirroot . '/course/lib.php');
1337     if (!$cm) {
1338         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
1339             print_error('invalidcoursemodule');
1340         }
1341     }
1342     $context = context_module::instance($cm->id);
1344     if (empty($sort)) {
1345         $sort = forum_get_default_sort_order();
1346     }
1348     $olddiscussionlink = false;
1350     // Sort out some defaults.
1351     if ($perpage <= 0) {
1352         $perpage = 0;
1353         $page    = -1;
1354     }
1356     if ($maxdiscussions == 0) {
1357         // All discussions - backwards compatibility.
1358         $page    = -1;
1359         $perpage = 0;
1360         if ($displayformat == 'plain') {
1361             $displayformat = 'header';  // Abbreviate display by default.
1362         }
1364     } else if ($maxdiscussions > 0) {
1365         $page    = -1;
1366         $perpage = $maxdiscussions;
1367     }
1369     $fullpost = false;
1370     if ($displayformat == 'plain') {
1371         $fullpost = true;
1372     }
1374     // Decide if current user is allowed to see ALL the current discussions or not.
1375     // First check the group stuff.
1376     if ($currentgroup == -1 or $groupmode == -1) {
1377         $groupmode    = groups_get_activity_groupmode($cm, $course);
1378         $currentgroup = groups_get_activity_group($cm);
1379     }
1381     // Cache.
1382     $groups = array();
1384     // If the user can post discussions, then this is a good place to put the
1385     // button for it. We do not show the button if we are showing site news
1386     // and the current user is a guest.
1388     $canstart = forum_user_can_post_discussion($forum, $currentgroup, $groupmode, $cm, $context);
1389     if (!$canstart and $forum->type !== 'news') {
1390         if (isguestuser() or !isloggedin()) {
1391             $canstart = true;
1392         }
1393         if (!is_enrolled($context) and !is_viewing($context)) {
1394             // Allow guests and not-logged-in to see the button - they are prompted to log in after clicking the link
1395             // normal users with temporary guest access see this button too, they are asked to enrol instead
1396             // do not show the button to users with suspended enrolments here.
1397             $canstart = enrol_selfenrol_available($course->id);
1398         }
1399     }
1401     if ($canstart) {
1402         switch ($forum->type) {
1403             case 'news':
1404             case 'blog':
1405                 $buttonadd = get_string('addanewtopic', 'forum');
1406                 break;
1407             case 'qanda':
1408                 $buttonadd = get_string('addanewquestion', 'forum');
1409                 break;
1410             default:
1411                 $buttonadd = get_string('addanewdiscussion', 'forum');
1412                 break;
1413         }
1414         $button = new single_button(new moodle_url('/mod/forum/post.php', ['forum' => $forum->id]), $buttonadd, 'get');
1415         $button->class = 'singlebutton forumaddnew';
1416         $button->formid = 'newdiscussionform';
1417         echo $OUTPUT->render($button);
1419     } else if (isguestuser() or !isloggedin() or $forum->type == 'news' or
1420         $forum->type == 'qanda' and !has_capability('mod/forum:addquestion', $context) or
1421         $forum->type != 'qanda' and !has_capability('mod/forum:startdiscussion', $context)) {
1422         // No button and no info.
1423         $ignore = true;
1424     } else if ($groupmode and !has_capability('moodle/site:accessallgroups', $context)) {
1425         // Inform users why they can not post new discussion.
1426         if (!$currentgroup) {
1427             if (!has_capability('mod/forum:canposttomygroups', $context)) {
1428                 echo $OUTPUT->notification(get_string('cannotadddiscussiongroup', 'forum'));
1429             } else {
1430                 echo $OUTPUT->notification(get_string('cannotadddiscussionall', 'forum'));
1431             }
1432         } else if (!groups_is_member($currentgroup)) {
1433             echo $OUTPUT->notification(get_string('cannotadddiscussion', 'forum'));
1434         }
1435     }
1437     // Get all the recent discussions we're allowed to see.
1439     $getuserlastmodified = ($displayformat == 'header');
1441     $discussions = forum_get_discussions($cm, $sort, $fullpost, null, $maxdiscussions, $getuserlastmodified, $page, $perpage);
1442     if (!$discussions) {
1443         echo '<div class="forumnodiscuss">';
1444         if ($forum->type == 'news') {
1445             echo '('.get_string('nonews', 'forum').')';
1446         } else if ($forum->type == 'qanda') {
1447             echo '('.get_string('noquestions', 'forum').')';
1448         } else {
1449             echo '('.get_string('nodiscussions', 'forum').')';
1450         }
1451         echo "</div>\n";
1452         return;
1453     }
1455     $canseeprivatereplies = has_capability('mod/forum:readprivatereplies', $context);
1456     // If we want paging.
1457     if ($page != -1) {
1458         // Get the number of discussions found.
1459         $numdiscussions = forum_get_discussions_count($cm);
1461         // Show the paging bar.
1462         echo $OUTPUT->paging_bar($numdiscussions, $page, $perpage, "view.php?f=$forum->id");
1463         if ($numdiscussions > 1000) {
1464             // Saves some memory on sites with very large forums.
1465             $replies = forum_count_discussion_replies($forum->id, $sort, $maxdiscussions, $page, $perpage, $canseeprivatereplies);
1466         } else {
1467             $replies = forum_count_discussion_replies($forum->id, "", -1, -1, 0, $canseeprivatereplies);
1468         }
1470     } else {
1471         $replies = forum_count_discussion_replies($forum->id, "", -1, -1, 0, $canseeprivatereplies);
1473         if ($maxdiscussions > 0 and $maxdiscussions <= count($discussions)) {
1474             $olddiscussionlink = true;
1475         }
1476     }
1478     $canviewparticipants = course_can_view_participants($context);
1479     $canviewhiddentimedposts = has_capability('mod/forum:viewhiddentimedposts', $context);
1481     $strdatestring = get_string('strftimerecentfull');
1483     // Check if the forum is tracked.
1484     if ($cantrack = forum_tp_can_track_forums($forum)) {
1485         $forumtracked = forum_tp_is_tracked($forum);
1486     } else {
1487         $forumtracked = false;
1488     }
1490     if ($forumtracked) {
1491         $unreads = forum_get_discussions_unread($cm);
1492     } else {
1493         $unreads = array();
1494     }
1496     if ($displayformat == 'header') {
1497         echo '<table cellspacing="0" class="forumheaderlist">';
1498         echo '<thead class="text-left">';
1499         echo '<tr>';
1500         echo '<th class="header topic" scope="col">'.get_string('discussion', 'forum').'</th>';
1501         echo '<th class="header author" scope="col">'.get_string('startedby', 'forum').'</th>';
1502         if ($groupmode > 0) {
1503             echo '<th class="header group" scope="col">'.get_string('group').'</th>';
1504         }
1505         if (has_capability('mod/forum:viewdiscussion', $context)) {
1506             echo '<th class="header replies" scope="col">'.get_string('replies', 'forum').'</th>';
1507             // If the forum can be tracked, display the unread column.
1508             if ($cantrack) {
1509                 echo '<th class="header replies" scope="col">'.get_string('unread', 'forum');
1510                 if ($forumtracked) {
1511                     echo '<a title="'.get_string('markallread', 'forum').
1512                          '" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
1513                          $forum->id.'&amp;mark=read&amp;return=/mod/forum/view.php&amp;sesskey=' . sesskey() . '">'.
1514                          $OUTPUT->pix_icon('t/markasread', get_string('markallread', 'forum')) . '</a>';
1515                 }
1516                 echo '</th>';
1517             }
1518         }
1519         echo '<th class="header lastpost" scope="col">'.get_string('lastpost', 'forum').'</th>';
1520         if ((!is_guest($context, $USER) && isloggedin()) && has_capability('mod/forum:viewdiscussion', $context)) {
1521             if (\mod_forum\subscriptions::is_subscribable($forum)) {
1522                 echo '<th class="header discussionsubscription" scope="col">';
1523                 echo forum_get_discussion_subscription_icon_preloaders();
1524                 echo '</th>';
1525             }
1526         }
1527         echo '</tr>';
1528         echo '</thead>';
1529         echo '<tbody>';
1530     }
1532     foreach ($discussions as $discussion) {
1533         if ($forum->type == 'qanda' && !has_capability('mod/forum:viewqandawithoutposting', $context) &&
1534             !forum_user_has_posted($forum->id, $discussion->discussion, $USER->id)) {
1535             $canviewparticipants = false;
1536         }
1538         if (!empty($replies[$discussion->discussion])) {
1539             $discussion->replies = $replies[$discussion->discussion]->replies;
1540             $discussion->lastpostid = $replies[$discussion->discussion]->lastpostid;
1541         } else {
1542             $discussion->replies = 0;
1543         }
1545         // SPECIAL CASE: The front page can display a news item post to non-logged in users.
1546         // All posts are read in this case.
1547         if (!$forumtracked) {
1548             $discussion->unread = '-';
1549         } else if (empty($USER)) {
1550             $discussion->unread = 0;
1551         } else {
1552             if (empty($unreads[$discussion->discussion])) {
1553                 $discussion->unread = 0;
1554             } else {
1555                 $discussion->unread = $unreads[$discussion->discussion];
1556             }
1557         }
1559         if (isloggedin()) {
1560             $ownpost = ($discussion->userid == $USER->id);
1561         } else {
1562             $ownpost = false;
1563         }
1564         // Use discussion name instead of subject of first post.
1565         $discussion->subject = $discussion->name;
1567         switch ($displayformat) {
1568             case 'header':
1569                 if ($groupmode > 0) {
1570                     if (isset($groups[$discussion->groupid])) {
1571                         $group = $groups[$discussion->groupid];
1572                     } else {
1573                         $group = $groups[$discussion->groupid] = groups_get_group($discussion->groupid);
1574                     }
1575                 } else {
1576                     $group = -1;
1577                 }
1578                 forum_print_discussion_header($discussion, $forum, $group, $strdatestring, $cantrack, $forumtracked,
1579                     $canviewparticipants, $context, $canviewhiddentimedposts);
1580             break;
1581             default:
1582                 $link = false;
1584                 if ($discussion->replies) {
1585                     $link = true;
1586                 } else {
1587                     $modcontext = context_module::instance($cm->id);
1588                     $link = forum_user_can_see_discussion($forum, $discussion, $modcontext, $USER);
1589                 }
1591                 $discussion->forum = $forum->id;
1593                 forum_print_post_start($discussion);
1594                 forum_print_post($discussion, $discussion, $forum, $cm, $course, $ownpost, 0, $link, false,
1595                         '', null, true, $forumtracked);
1596                 forum_print_post_end($discussion);
1597             break;
1598         }
1599     }
1601     if ($displayformat == "header") {
1602         echo '</tbody>';
1603         echo '</table>';
1604     }
1606     if ($olddiscussionlink) {
1607         if ($forum->type == 'news') {
1608             $strolder = get_string('oldertopics', 'forum');
1609         } else {
1610             $strolder = get_string('olderdiscussions', 'forum');
1611         }
1612         echo '<div class="forumolddiscuss">';
1613         echo '<a href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'&amp;showall=1">';
1614         echo $strolder.'</a> ...</div>';
1615     }
1617     if ($page != -1) {
1618         // Show the paging bar.
1619         echo $OUTPUT->paging_bar($numdiscussions, $page, $perpage, "view.php?f=$forum->id");
1620     }
1623 /**
1624  * Count the number of replies to the specified post.
1625  *
1626  * @param object $post
1627  * @param bool $children
1628  * @return int
1629  * @deprecated since Moodle 3.7
1630  * @todo MDL-65252 This will be removed in Moodle 3.11
1631  */
1632 function forum_count_replies($post, $children = true) {
1633     global $USER;
1634     debugging('forum_count_replies has been deprecated. Please use the Post vault instead.', DEBUG_DEVELOPER);
1636     if (!$children) {
1637         return $DB->count_records('forum_posts', array('parent' => $post->id));
1638     }
1640     $entityfactory = mod_forum\local\container::get_entity_factory();
1641     $postentity = $entityfactory->get_post_from_stdclass($post);
1643     $vaultfactory = mod_forum\local\container::get_vault_factory();
1644     $postvault = $vaultfactory->get_post_vault();
1646     return $postvault->get_reply_count_for_post_id_in_discussion_id(
1647             $USER,
1648             $postentity->get_id(),
1649             $postentity->get_discussion_id(),
1650             true
1651         );
1654 /**
1655  * @deprecated since Moodle 3.8
1656  */
1657 function forum_scale_used() {
1658     throw new coding_exception('forum_scale_used() can not be used anymore. Plugins can implement ' .
1659         '<modname>_scale_used_anywhere, all implementations of <modname>_scale_used are now ignored');
1662 /**
1663  * Return grade for given user or all users.
1664  *
1665  * @deprecated since Moodle 3.8
1666  * @param object $forum
1667  * @param int $userid optional user id, 0 means all users
1668  * @return array array of grades, false if none
1669  */
1670 function forum_get_user_grades($forum, $userid = 0) {
1671     global $CFG;
1673     require_once($CFG->dirroot.'/rating/lib.php');
1675     $ratingoptions = (object) [
1676         'component' => 'mod_forum',
1677         'ratingarea' => 'post',
1678         'contextid' => $contextid,
1680         'modulename' => 'forum',
1681         'moduleid  ' => $forum->id,
1682         'userid' => $userid,
1683         'aggregationmethod' => $forum->assessed,
1684         'scaleid' => $forum->scale,
1685         'itemtable' => 'forum_posts',
1686         'itemtableusercolumn' => 'userid',
1687     ];
1689     $rm = new rating_manager();
1690     return $rm->get_user_grades($ratingoptions);
1693 /**
1694  * Obtains the automatic completion state for this forum based on any conditions
1695  * in forum settings.
1696  *
1697  * @deprecated since Moodle 3.11
1698  * @todo MDL-71196 Final deprecation in Moodle 4.3
1699  * @see \mod_forum\completion\custom_completion
1700  * @global object
1701  * @global object
1702  * @param object $course Course
1703  * @param object $cm Course-module
1704  * @param int $userid User ID
1705  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
1706  * @return bool True if completed, false if not. (If no conditions, then return
1707  *   value depends on comparison type)
1708  */
1709 function forum_get_completion_state($course, $cm, $userid, $type) {
1710     global $DB;
1712     // No need to call debugging here. Deprecation debugging notice already being called in \completion_info::internal_get_state().
1714     // Get forum details.
1715     if (!($forum = $DB->get_record('forum', array('id' => $cm->instance)))) {
1716         throw new Exception("Can't find forum {$cm->instance}");
1717     }
1719     $result = $type; // Default return value.
1721     $postcountparams = array('userid' => $userid, 'forumid' => $forum->id);
1722     $postcountsql = "
1723 SELECT
1724     COUNT(1)
1725 FROM
1726     {forum_posts} fp
1727     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
1728 WHERE
1729     fp.userid=:userid AND fd.forum=:forumid";
1731     if ($forum->completiondiscussions) {
1732         $value = $forum->completiondiscussions <=
1733             $DB->count_records('forum_discussions', array('forum' => $forum->id, 'userid' => $userid));
1734         if ($type == COMPLETION_AND) {
1735             $result = $result && $value;
1736         } else {
1737             $result = $result || $value;
1738         }
1739     }
1740     if ($forum->completionreplies) {
1741         $value = $forum->completionreplies <=
1742             $DB->get_field_sql($postcountsql . ' AND fp.parent<>0', $postcountparams);
1743         if ($type == COMPLETION_AND) {
1744             $result = $result && $value;
1745         } else {
1746             $result = $result || $value;
1747         }
1748     }
1749     if ($forum->completionposts) {
1750         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql, $postcountparams);
1751         if ($type == COMPLETION_AND) {
1752             $result = $result && $value;
1753         } else {
1754             $result = $result || $value;
1755         }
1756     }
1758     return $result;