MDL-11082 Improved groups upgrade performance 1.8x -> 1.9; thanks Eloy for telling...
[moodle-pu.git] / mod / forum / lib.php
blob8da6ce802535280a42ab0eca9762fe9c8126dcb6
1 <?php // $Id$
3 require_once($CFG->libdir.'/filelib.php');
5 /// CONSTANTS ///////////////////////////////////////////////////////////
7 define('FORUM_MODE_FLATOLDEST', 1);
8 define('FORUM_MODE_FLATNEWEST', -1);
9 define('FORUM_MODE_THREADED', 2);
10 define('FORUM_MODE_NESTED', 3);
12 define('FORUM_FORCESUBSCRIBE', 1);
13 define('FORUM_INITIALSUBSCRIBE', 2);
14 define('FORUM_DISALLOWSUBSCRIBE',3);
16 define('FORUM_TRACKING_OFF', 0);
17 define('FORUM_TRACKING_OPTIONAL', 1);
18 define('FORUM_TRACKING_ON', 2);
20 define('FORUM_UNSET_POST_RATING', -999);
22 $FORUM_LAYOUT_MODES = array ( FORUM_MODE_FLATOLDEST => get_string('modeflatoldestfirst', 'forum'),
23 FORUM_MODE_FLATNEWEST => get_string('modeflatnewestfirst', 'forum'),
24 FORUM_MODE_THREADED => get_string('modethreaded', 'forum'),
25 FORUM_MODE_NESTED => get_string('modenested', 'forum') );
27 // These are course content forums that can be added to the course manually
28 $FORUM_TYPES = array ('general' => get_string('generalforum', 'forum'),
29 'eachuser' => get_string('eachuserforum', 'forum'),
30 'single' => get_string('singleforum', 'forum'),
31 'qanda' => get_string('qandaforum', 'forum')
34 $FORUM_OPEN_MODES = array ('2' => get_string('openmode2', 'forum'),
35 '1' => get_string('openmode1', 'forum'),
36 '0' => get_string('openmode0', 'forum') );
38 if (!isset($CFG->forum_displaymode)) {
39 set_config('forum_displaymode', FORUM_MODE_NESTED);
42 if (!isset($CFG->forum_shortpost)) {
43 set_config('forum_shortpost', 300); // Less non-HTML characters than this is short
46 if (!isset($CFG->forum_longpost)) {
47 set_config('forum_longpost', 600); // More non-HTML characters than this is long
50 if (!isset($CFG->forum_manydiscussions)) {
51 set_config('forum_manydiscussions', 100); // Number of discussions on a page
54 if (!isset($CFG->forum_maxbytes)) {
55 set_config('forum_maxbytes', 512000); // Default maximum size for all forums
58 if (!isset($CFG->forum_trackreadposts)) {
59 set_config('forum_trackreadposts', true); // Default whether user needs to mark a post as read
62 if (!isset($CFG->forum_oldpostdays)) {
63 set_config('forum_oldpostdays', 14); // Default number of days that a post is considered old
66 if (!isset($CFG->forum_usermarksread)) {
67 set_config('forum_usermarksread', false); // Default whether user needs to mark a post as read
70 if (!isset($CFG->forum_cleanreadtime)) {
71 set_config('forum_cleanreadtime', 2); // Default time (hour) to execute 'clean_read_records' cron
74 if (!isset($CFG->forum_replytouser)) {
75 set_config('forum_replytouser', true); // Default maximum size for all forums
78 if (empty($USER->id) or isguest()) {
79 $CFG->forum_trackreadposts = false; // This feature never works when a user isn't logged in
82 if (!isset($CFG->forum_enabletimedposts)) { // Newish feature that is not quite ready for production in 1.6
83 $CFG->forum_enabletimedposts = false;
87 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
89 /**
90 * Given an object containing all the necessary data,
91 * (defined by the form in mod.html) this function
92 * will create a new instance and return the id number
93 * of the new instance.
95 function forum_add_instance($forum) {
96 global $CFG;
98 $forum->timemodified = time();
100 if (empty($forum->assessed)) {
101 $forum->assessed = 0;
104 if (empty($forum->ratingtime) or empty($forum->assessed)) {
105 $forum->assesstimestart = 0;
106 $forum->assesstimefinish = 0;
109 if (!$forum->id = insert_record('forum', $forum)) {
110 return false;
113 if ($forum->type == 'single') { // Create related discussion.
114 $discussion = new object();
115 $discussion->course = $forum->course;
116 $discussion->forum = $forum->id;
117 $discussion->name = $forum->name;
118 $discussion->intro = $forum->intro;
119 $discussion->assessed = $forum->assessed;
120 $discussion->format = $forum->type;
121 $discussion->mailnow = false;
123 if (! forum_add_discussion($discussion, $discussion->intro)) {
124 error('Could not add the discussion for this forum');
128 if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) { // all users should be subscribed initially
129 $users = get_course_users($forum->course);
130 foreach ($users as $user) {
131 forum_subscribe($user->id, $forum->id);
135 $forum = stripslashes_recursive($forum);
136 forum_grade_item_update($forum);
138 return $forum->id;
142 /**
143 * Given an object containing all the necessary data,
144 * (defined by the form in mod.html) this function
145 * will update an existing instance with new data.
147 function forum_update_instance($forum) {
148 $forum->timemodified = time();
149 $forum->id = $forum->instance;
151 if (empty($forum->assessed)) {
152 $forum->assessed = 0;
155 if (empty($forum->ratingtime) or empty($forum->assessed)) {
156 $forum->assesstimestart = 0;
157 $forum->assesstimefinish = 0;
160 if ($forum->type == 'single') { // Update related discussion and post.
161 if (! $discussion = get_record('forum_discussions', 'forum', $forum->id)) {
162 if ($discussions = get_records('forum_discussions', 'forum', $forum->id, 'timemodified ASC')) {
163 notify('Warning! There is more than one discussion in this forum - using the most recent');
164 $discussion = array_pop($discussions);
165 } else {
166 error('Could not find the discussion in this forum');
169 if (! $post = get_record('forum_posts', 'id', $discussion->firstpost)) {
170 error('Could not find the first post in this forum discussion');
173 $post->subject = $forum->name;
174 $post->message = $forum->intro;
175 $post->modified = $forum->timemodified;
177 if (! update_record('forum_posts', ($post))) {
178 error('Could not update the first post');
181 $discussion->name = $forum->name;
183 if (! update_record('forum_discussions', ($discussion))) {
184 error('Could not update the discussion');
188 if (!update_record('forum', $forum)) {
189 error('Can not update forum');
192 $forum = stripslashes_recursive($forum);
193 forum_grade_item_update($forum);
195 return true;
200 * Given an ID of an instance of this module,
201 * this function will permanently delete the instance
202 * and any data that depends on it.
204 function forum_delete_instance($id) {
206 if (!$forum = get_record('forum', 'id', $id)) {
207 return false;
210 $result = true;
212 if ($discussions = get_records('forum_discussions', 'forum', $forum->id)) {
213 foreach ($discussions as $discussion) {
214 if (!forum_delete_discussion($discussion, true)) {
215 $result = false;
220 if (!delete_records('forum_subscriptions', 'forum', $forum->id)) {
221 $result = false;
224 forum_tp_delete_read_records(-1, -1, -1, $forum->id);
226 if (!delete_records('forum', 'id', $forum->id)) {
227 $result = false;
230 forum_grade_item_delete($forum);
232 return $result;
237 * Function to be run periodically according to the moodle cron
238 * Finds all posts that have yet to be mailed out, and mails them
239 * out to all subscribers
241 function forum_cron() {
242 global $CFG, $USER;
244 $CFG->enablerecordcache = true; // We want all the caching we can get
246 $cronuser = clone($USER);
247 $site = get_site();
249 // all users that are subscribed to any post that needs sending
250 $users = array();
252 // status arrays
253 $mailcount = array();
254 $errorcount = array();
256 // caches
257 $discussions = array();
258 $forums = array();
259 $courses = array();
260 $coursemodules = array();
261 $postinfos = array();
262 $subscribedusers = array();
265 // Posts older than 2 days will not be mailed. This is to avoid the problem where
266 // cron has not been running for a long time, and then suddenly people are flooded
267 // with mail from the past few weeks or months
268 $timenow = time();
269 $endtime = $timenow - $CFG->maxeditingtime;
270 $starttime = $endtime - 48 * 3600; // Two days earlier
272 if ($posts = forum_get_unmailed_posts($starttime, $endtime)) {
273 // Mark them all now as being mailed. It's unlikely but possible there
274 // might be an error later so that a post is NOT actually mailed out,
275 // but since mail isn't crucial, we can accept this risk. Doing it now
276 // prevents the risk of duplicated mails, which is a worse problem.
278 if (!forum_mark_old_posts_as_mailed($endtime)) {
279 mtrace('Errors occurred while trying to mark some posts as being mailed.');
280 return false; // Don't continue trying to mail them, in case we are in a cron loop
283 // checking post validity, and adding users to loop through later
284 foreach ($posts as $pid => $post) {
286 $discussionid = $post->discussion;
287 if (!isset($discussions[$discussionid])) {
288 if ($discussion = get_record('forum_discussions', 'id', $post->discussion)) {
289 $discussions[$discussionid] = $discussion;
290 } else {
291 mtrace('Could not find discussion '.$discussionid);
292 unset($posts[$pid]);
293 continue;
296 $forumid = $discussions[$discussionid]->forum;
297 if (!isset($forums[$forumid])) {
298 if ($forum = get_record('forum', 'id', $forumid)) {
299 $forums[$forumid] = $forum;
300 } else {
301 mtrace('Could not find forum '.$forumid);
302 unset($posts[$pid]);
303 continue;
306 $courseid = $forums[$forumid]->course;
307 if (!isset($courses[$courseid])) {
308 if ($course = get_record('course', 'id', $courseid)) {
309 $courses[$courseid] = $course;
310 } else {
311 mtrace('Could not find course '.$courseid);
312 unset($posts[$pid]);
313 continue;
316 if (!isset($coursemodules[$forumid])) {
317 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
318 $coursemodules[$forumid] = $cm;
319 } else {
320 mtrace('Could not course module for forum '.$forumid);
321 unset($posts[$pid]);
322 continue;
327 // caching subscribed users of each forum
328 if (!isset($subscribedusers[$forumid])) {
329 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, true)) {
330 foreach ($subusers as $postuser) {
331 // do not try to mail users with stopped email
332 if ($postuser->emailstop) {
333 add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
334 continue;
336 // this user is subscribed to this forum
337 $subscribedusers[$forumid][] = $postuser->id;
338 // this user is a user we have to process later
339 $users[$postuser->id] = $postuser;
344 $mailcount[$pid] = 0;
345 $errorcount[$pid] = 0;
349 if ($users && $posts) {
351 $urlinfo = parse_url($CFG->wwwroot);
352 $hostname = $urlinfo['host'];
354 foreach ($users as $userto) {
356 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
358 // set this so that the capabilities are cached, and environment matches receiving user
359 $USER = $userto;
361 mtrace('Processing user '.$userto->id);
363 // we might want to add another layer - forums here (by checking array_keys($subscribedusers))
364 // so that we can skip many posts
366 foreach ($posts as $pid => $post) {
368 // Get info about the sending user
369 if (array_key_exists($post->userid, $users)) { // we might know him/her already
370 $userfrom = $users[$post->userid];
371 } else if (!$userfrom = get_record('user', 'id', $post->userid)) {
372 mtrace('Could not find user '.$post->userid);
373 continue;
376 // Set up the environment for the post, discussion, forum, course
377 $discussion = $discussions[$post->discussion];
378 $forum = $forums[$discussion->forum];
379 $course = $courses[$forum->course];
380 $cm = $coursemodules[$forum->id];
382 // Do some checks to see if we can bail out now
383 if (empty($subscribedusers[$forum->id]) || !in_array($userto->id, $subscribedusers[$forum->id])) {
384 continue; // user does not subscribe to this forum
387 // Get the context (from cache)
388 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id); // Cached already
390 // setup global $COURSE properly - needed for roles and languages
391 course_setup($course); // More environment
393 // Make sure groups allow this user to see this email
394 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm)) { // Groups are being used
395 if (! groups_group_exists($discussion->groupid)) { // Can't find group
396 continue; // Be safe and don't send it to anyone
399 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
400 // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
401 continue;
405 // Make sure we're allowed to see it...
406 if (!forum_user_can_see_post($forum, $discussion, $post)) {
407 mtrace('user '.$userto->id. ' can not see '.$post->id);
408 continue;
411 // OK so we need to send the email.
413 // Does the user want this post in a digest? If so postpone it for now.
414 if ($userto->maildigest > 0) {
415 // This user wants the mails to be in digest form
416 $queue = new object();
417 $queue->userid = $userto->id;
418 $queue->discussionid = $discussion->id;
419 $queue->postid = $post->id;
420 if (!insert_record('forum_queue', $queue)) {
421 mtrace("Error: mod/forum/cron.php: Could not queue for digest mail for id $post->id to user $userto->id ($userto->email) .. not trying again.");
423 continue;
427 // Prepare to actually send the post now, and build up the content
429 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
431 $userfrom->customheaders = array ( // Headers to make emails easier to track
432 'Precedence: Bulk',
433 'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
434 'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
435 'Message-ID: <moodlepost'.$post->id.'@'.$hostname.'>',
436 'In-Reply-To: <moodlepost'.$post->parent.'@'.$hostname.'>',
437 'References: <moodlepost'.$post->parent.'@'.$hostname.'>',
438 'X-Course-Id: '.$course->id,
439 'X-Course-Name: '.format_string($course->fullname, true)
443 $postsubject = "$course->shortname: ".format_string($post->subject,true);
444 $posttext = forum_make_mail_text($course, $forum, $discussion, $post, $userfrom, $userto);
445 $posthtml = forum_make_mail_html($course, $forum, $discussion, $post, $userfrom, $userto);
447 // Send the post now!
449 mtrace('Sending ', '');
451 if (!$mailresult = email_to_user($userto, $userfrom, $postsubject, $posttext,
452 $posthtml, '', '', $CFG->forum_replytouser)) {
453 mtrace("Error: mod/forum/cron.php: Could not send out mail for id $post->id to user $userto->id".
454 " ($userto->email) .. not trying again.");
455 add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
456 substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
457 $errorcount[$post->id]++;
458 } else if ($mailresult === 'emailstop') {
459 // should not be reached anymore - see check above
460 } else {
461 $mailcount[$post->id]++;
463 // Mark post as read if forum_usermarksread is set off
464 if (!$CFG->forum_usermarksread && forum_tp_can_track_forums($forum, $userto) &&
465 forum_tp_is_tracked($forum, $userto->id)) {
466 if (!forum_tp_mark_post_read($userto->id, $post, $forum->id)) {
467 mtrace("Error: mod/forum/cron.php: Could not mark post $post->id read for user $userto->id".
468 " while sending email.");
473 mtrace('post '.$post->id. ': '.$post->subject);
478 if ($posts) {
479 foreach ($posts as $post) {
480 mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
481 if ($errorcount[$post->id]) {
482 set_field("forum_posts", "mailed", "2", "id", "$post->id");
487 $USER = clone($cronuser);
488 course_setup(SITEID);
490 $sitetimezone = $CFG->timezone;
492 // Now see if there are any digest mails waiting to be sent, and if we should send them
494 if (!isset($CFG->digestmailtimelast)) { // To catch the first time
495 set_config('digestmailtimelast', 0);
498 $timenow = time();
499 $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
501 if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
503 set_config('digestmailtimelast', $timenow);
505 mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
507 if ($digestposts = get_records('forum_queue')) {
509 // We have work to do
510 $usermailcount = 0;
512 //caches - reuse the those filled before too
513 $discussionposts = array();
514 $userdiscussions = array();
516 foreach ($digestposts as $digestpost) {
517 if (!isset($users[$digestpost->userid])) {
518 if ($user = get_record('user', 'id', $digestpost->userid)) {
519 $users[$digestpost->userid] = $user;
520 } else {
521 continue;
524 $postuser = $users[$digestpost->userid];
525 if ($postuser->emailstop) {
526 add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
527 continue;
530 if (!isset($posts[$digestpost->postid])) {
531 if ($post = get_record('forum_posts', 'id', $digestpost->postid)) {
532 $posts[$digestpost->postid] = $post;
533 } else {
534 continue;
537 $discussionid = $digestpost->discussionid;
538 if (!isset($discussions[$discussionid])) {
539 if ($discussion = get_record('forum_discussions', 'id', $discussionid)) {
540 $discussions[$discussionid] = $discussion;
541 } else {
542 continue;
545 $forumid = $discussions[$discussionid]->forum;
546 if (!isset($forums[$forumid])) {
547 if ($forum = get_record('forum', 'id', $forumid)) {
548 $forums[$forumid] = $forum;
549 } else {
550 continue;
554 $courseid = $forums[$forumid]->course;
555 if (!isset($courses[$courseid])) {
556 if ($course = get_record('course', 'id', $courseid)) {
557 $courses[$courseid] = $course;
558 } else {
559 continue;
563 if (!isset($coursemodules[$forumid])) {
564 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
565 $coursemodules[$forumid] = $cm;
566 } else {
567 continue;
570 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
571 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
574 // Data collected, start sending out emails to each user
575 foreach ($userdiscussions as $userid => $thesediscussions) {
577 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
579 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
581 // First of all delete all the queue entries for this user
582 delete_records('forum_queue', 'userid', $userid);
583 $userto = $users[$userid];
585 // Override the language and timezone of the "current" user, so that
586 // mail is customised for the receiver.
587 $USER = $userto;
588 course_setup(SITEID);
590 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
592 $headerdata = new object();
593 $headerdata->sitename = format_string($site->fullname, true);
594 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
596 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
597 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
599 $posthtml = "<head>";
600 foreach ($CFG->stylesheets as $stylesheet) {
601 $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
603 $posthtml .= "</head>\n<body>\n";
604 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
606 foreach ($thesediscussions as $discussionid) {
608 @set_time_limit(120); // to be reset for each post
610 $discussion = $discussions[$discussionid];
611 $forum = $forums[$discussion->forum];
612 $course = $courses[$forum->course];
613 $cm = $coursemodules[$forum->id];
615 //override language
616 course_setup($course);
618 $strforums = get_string('forums', 'forum');
619 $canunsubscribe = ! forum_is_forcesubscribed($forum);
620 $canreply = forum_user_can_post($forum, $userto);
623 $posttext .= "\n \n";
624 $posttext .= '=====================================================================';
625 $posttext .= "\n \n";
626 $posttext .= "$course->shortname -> $strforums -> ".format_string($forum->name,true);
627 if ($discussion->name != $forum->name) {
628 $posttext .= " -> ".format_string($discussion->name,true);
630 $posttext .= "\n";
632 $posthtml .= "<p><font face=\"sans-serif\">".
633 "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> -> ".
634 "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
635 "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
636 if ($discussion->name == $forum->name) {
637 $posthtml .= "</font></p>";
638 } else {
639 $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
641 $posthtml .= '<p>';
643 $postsarray = $discussionposts[$discussionid];
644 sort($postsarray);
646 // Create an empty array to use for marking read posts.
647 // (I'm sure there's already a structure I can use here, but I can't be sure.)
648 $markread = array();
650 foreach ($postsarray as $postid) {
651 $post = $posts[$postid];
653 if (array_key_exists($post->userid, $users)) { // we might know him/her already
654 $userfrom = $users[$post->userid];
655 } else if (!$userfrom = get_record('user', 'id', $post->userid)) {
656 mtrace('Could not find user '.$post->userid);
657 continue;
660 $userfrom->customheaders = array ("Precedence: Bulk");
662 if ($userto->maildigest == 2) {
663 // Subjects only
664 $by = new object();
665 $by->name = fullname($userfrom);
666 $by->date = userdate($post->modified);
667 $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
668 $posttext .= "\n---------------------------------------------------------------------";
670 $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
671 $posthtml .= '<div><a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'#p'.$post->id.'">'.format_string($post->subject,true).'</a> '.get_string("bynameondate", "forum", $by).'</div>';
673 } else {
674 // The full treatment
675 $posttext .= forum_make_mail_text($course, $forum, $discussion, $post, $userfrom, $userto, true);
676 $posthtml .= forum_make_mail_post($post, $userfrom, $userto, $course, false, $canreply, true, false);
678 // Create an array of postid's for this user to mark as read.
679 if (!$CFG->forum_usermarksread &&
680 forum_tp_can_track_forums($forum, $userto) &&
681 forum_tp_is_tracked($forum, $userto->id)) {
682 $markread[$post->id]->post = $post;
683 $markread[$post->id]->forumid = $forum->id;
687 if ($canunsubscribe) {
688 $posthtml .= "\n<div align=\"right\"><font size=\"1\"><a href=\"$CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\">".get_string("unsubscribe", "forum")."</a></font></div>";
689 } else {
690 $posthtml .= "\n<div align=\"right\"><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
692 $posthtml .= '<hr size="1" noshade="noshade" /></p>';
694 $posthtml .= '</body>';
696 if ($userto->mailformat != 1) {
697 // This user DOESN'T want to receive HTML
698 $posthtml = '';
701 if (!$mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml,
702 '', '', $CFG->forum_replytouser)) {
703 mtrace("ERROR!");
704 echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
705 add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
706 } else if ($mailresult === 'emailstop') {
707 // should not happen anymore - see check above
708 } else {
709 mtrace("success.");
710 $usermailcount++;
712 // Mark post as read if forum_usermarksread is set off
713 if (!$CFG->forum_usermarksread &&
714 forum_tp_can_track_forums($forum->id, $userto) &&
715 forum_tp_is_tracked($forum->id, $userto->id)) {
716 foreach ($markread as $postinfo) {
717 if (!forum_tp_mark_post_read($userto->id, $postinfo->post, $postinfo->forumid)) {
718 mtrace("Error: mod/forum/cron.php: Could not mark post $postid read for user $userto->id".
719 " while sending digest email.");
728 if (!empty($usermailcount)) {
729 mtrace(get_string('digestsentusers', 'forum', $usermailcount));
732 $USER = $cronuser;
733 course_setup(SITEID); // reset cron user language, theme and timezone settings
735 if (!empty($CFG->forum_lastreadclean)) {
736 $timenow = time();
737 if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
738 set_config('forum_lastreadclean', $timenow);
739 forum_tp_clean_read_records();
741 } else {
742 set_config('forum_lastreadclean', time());
746 return true;
750 * Builds and returns the body of the email notification in plain text.
752 * @param object $course
753 * @param object $forum
754 * @param object $discussion
755 * @param object $post
756 * @param object $userfrom
757 * @param object $userto
758 * @param boolean $bare
759 * @return string The email body in plain text format.
761 function forum_make_mail_text($course, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
762 global $CFG, $USER;
764 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
765 error('Course Module ID was incorrect');
767 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
768 $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
770 $by = New stdClass;
771 $by->name = fullname($userfrom, $viewfullnames);
772 $by->date = userdate($post->modified, "", $userto->timezone);
774 $strbynameondate = get_string('bynameondate', 'forum', $by);
776 $strforums = get_string('forums', 'forum');
778 $canunsubscribe = ! forum_is_forcesubscribed($forum);
779 $canreply = forum_user_can_post($forum, $userto);
781 $posttext = '';
783 if (!$bare) {
784 $posttext = "$course->shortname -> $strforums -> ".format_string($forum->name,true);
786 if ($discussion->name != $forum->name) {
787 $posttext .= " -> ".format_string($discussion->name,true);
791 $posttext .= "\n---------------------------------------------------------------------\n";
792 $posttext .= format_string($post->subject,true);
793 if ($bare) {
794 $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
796 $posttext .= "\n".$strbynameondate."\n";
797 $posttext .= "---------------------------------------------------------------------\n";
798 $posttext .= format_text_email(trusttext_strip($post->message), $post->format);
799 $posttext .= "\n\n";
800 if ($post->attachment) {
801 $post->course = $course->id;
802 $post->forum = $forum->id;
803 $posttext .= forum_print_attachments($post, "text");
805 if (!$bare && $canreply) {
806 $posttext .= "---------------------------------------------------------------------\n";
807 $posttext .= get_string("postmailinfo", "forum", $course->shortname)."\n";
808 $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
810 if (!$bare && $canunsubscribe) {
811 $posttext .= "\n---------------------------------------------------------------------\n";
812 $posttext .= get_string("unsubscribe", "forum");
813 $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
816 return $posttext;
820 * Builds and returns the body of the email notification in html format.
822 * @param object $course
823 * @param object $forum
824 * @param object $discussion
825 * @param object $post
826 * @param object $userfrom
827 * @param object $userto
828 * @return string The email text in HTML format
830 function forum_make_mail_html($course, $forum, $discussion, $post, $userfrom, $userto) {
831 global $CFG;
833 if ($userto->mailformat != 1) { // Needs to be HTML
834 return '';
837 $strforums = get_string('forums', 'forum');
838 $canreply = forum_user_can_post($forum, $userto);
839 $canunsubscribe = ! forum_is_forcesubscribed($forum);
841 $posthtml = '<head>';
842 foreach ($CFG->stylesheets as $stylesheet) {
843 $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
845 $posthtml .= '</head>';
846 $posthtml .= "\n<body id=\"email\">\n\n";
848 $posthtml .= '<div class="navbar">'.
849 '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$course->shortname.'</a> &raquo; '.
850 '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
851 '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
852 if ($discussion->name == $forum->name) {
853 $posthtml .= '</div>';
854 } else {
855 $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
856 format_string($discussion->name,true).'</a></div>';
858 $posthtml .= forum_make_mail_post($post, $userfrom, $userto, $course, false, $canreply, true, false);
860 if ($canunsubscribe) {
861 $posthtml .= '<hr /><div align="center" class="unsubscribelink"><a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.
862 get_string('unsubscribe', 'forum').'</a></div>';
865 $posthtml .= '</body>';
867 return $posthtml;
873 * @param object $course
874 * @param object $user
875 * @param object $mod TODO this is not used in this function, refactor
876 * @param object $forum
877 * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
879 function forum_user_outline($course, $user, $mod, $forum) {
881 if ($posts = forum_get_user_posts($forum->id, $user->id)) {
882 $result->info = get_string("numposts", "forum", count($posts));
884 $lastpost = array_pop($posts);
885 $result->time = $lastpost->modified;
886 return $result;
888 return NULL;
895 function forum_user_complete($course, $user, $mod, $forum) {
896 global $CFG;
898 if ($posts = forum_get_user_posts($forum->id, $user->id)) {
899 foreach ($posts as $post) {
901 $post->forum = $forum->id;
902 forum_print_post($post, $course->id, $ownpost=false, $reply=false, $link=false, $rate=false);
905 } else {
906 echo "<p>".get_string("noposts", "forum")."</p>";
913 function forum_print_overview($courses,&$htmlarray) {
914 global $USER, $CFG;
915 $LIKE = sql_ilike();
917 if (empty($courses) || !is_array($courses) || count($courses) == 0) {
918 return array();
921 if (!$forums = get_all_instances_in_courses('forum',$courses)) {
922 return;
926 // get all forum logs in ONE query (much better!)
927 $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {$CFG->prefix}log l "
928 ." JOIN {$CFG->prefix}course_modules cm ON cm.id = cmid "
929 ." WHERE (";
930 foreach ($courses as $course) {
931 $sql .= '(l.course = '.$course->id.' AND l.time > '.$course->lastaccess.') OR ';
933 $sql = substr($sql,0,-3); // take off the last OR
935 $sql .= ") AND l.module = 'forum' AND action $LIKE 'add post%' "
936 ." AND userid != ".$USER->id." GROUP BY cmid,l.course,instance";
938 if (!$new = get_records_sql($sql)) {
939 $new = array(); // avoid warnings
942 // also get all forum tracking stuff ONCE.
943 $trackingforums = array();
944 foreach ($forums as $forum) {
945 if (forum_tp_can_track_forums($forum)) {
946 $trackingforums[$forum->id] = $forum;
950 if (count($trackingforums) > 0) {
951 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
952 $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
953 ' FROM '.$CFG->prefix.'forum_posts p '.
954 ' JOIN '.$CFG->prefix.'forum_discussions d ON p.discussion = d.id '.
955 ' LEFT JOIN '.$CFG->prefix.'forum_read r ON r.postid = p.id AND r.userid = '.$USER->id.' WHERE (';
956 foreach ($trackingforums as $track) {
957 $sql .= '(d.forum = '.$track->id.' AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = '.get_current_group($track->course).')) OR ';
959 $sql = substr($sql,0,-3); // take off the last OR
960 $sql .= ') AND p.modified >= '.$cutoffdate.' AND r.id is NULL GROUP BY d.forum,d.course';
962 if (!$unread = get_records_sql($sql)) {
963 $unread = array();
965 } else {
966 $unread = array();
969 if (empty($unread) and empty($new)) {
970 return;
973 $strforum = get_string('modulename','forum');
974 $strnumunread = get_string('overviewnumunread','forum');
975 $strnumpostssince = get_string('overviewnumpostssince','forum');
977 foreach ($forums as $forum) {
978 $str = '';
979 $count = 0;
980 $thisunread = 0;
981 $showunread = false;
982 // either we have something from logs, or trackposts, or nothing.
983 if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
984 $count = $new[$forum->id]->count;
986 if (array_key_exists($forum->id,$unread)) {
987 $thisunread = $unread[$forum->id]->count;
988 $showunread = true;
990 if ($count > 0 || $thisunread > 0) {
991 $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
992 $forum->name.'</a></div>';
993 $str .= '<div class="info">';
994 $str .= $count.' '.$strnumpostssince;
995 if (!empty($showunread)) {
996 $str .= '<br />'.$thisunread .' '.$strnumunread;
998 $str .= '</div></div>';
1000 if (!empty($str)) {
1001 if (!array_key_exists($forum->course,$htmlarray)) {
1002 $htmlarray[$forum->course] = array();
1004 if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1005 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1007 $htmlarray[$forum->course]['forum'] .= $str;
1013 * Given a course and a date, prints a summary of all the new
1014 * messages posted in the course since that date
1016 function forum_print_recent_activity($course, $isteacher, $timestart) {
1018 global $CFG;
1019 $LIKE = sql_ilike();
1021 $heading = false;
1022 $content = false;
1024 if (!$logs = get_records_select('log', 'time > \''.$timestart.'\' AND '.
1025 'course = \''.$course->id.'\' AND '.
1026 'module = \'forum\' AND '.
1027 'action '.$LIKE.' \'add %\' ', 'time ASC')){
1028 return false;
1031 $strftimerecent = get_string('strftimerecent');
1033 $mygroupid = mygroupid($course->id);
1034 $groupmode = array(); // To cache group modes
1036 $count = 0;
1037 foreach ($logs as $log) {
1038 //Get post info, I'll need it later
1039 if ($post = forum_get_post_from_log($log)) {
1040 //Create a temp valid module structure (course,id)
1041 $tempmod = new object;
1042 $tempmod->course = $log->course;
1043 $tempmod->id = $post->forum;
1044 //Obtain the visible property from the instance
1045 $coursecontext = get_context_instance(CONTEXT_COURSE, $tempmod->course);
1046 $modvisible = instance_is_visible('forum', $tempmod)
1047 || has_capability('moodle/course:viewhiddenactivities', $coursecontext);
1050 //Only if the post exists and mod is visible
1051 if ($post && $modvisible) {
1053 if (!isset($cm[$post->forum])) {
1054 $cm[$post->forum] = get_coursemodule_from_instance('forum', $post->forum, $course->id);
1056 $modcontext = get_context_instance(CONTEXT_MODULE, $cm[$post->forum]->id);
1058 // Check whether this is belongs to a discussion in a group that
1059 // should NOT be accessible to the current user
1060 if (!has_capability('moodle/site:accessallgroups', $modcontext)
1061 && $post->groupid != -1) { // Open discussions have groupid -1
1063 $groupmode[$post->forum] = groups_get_activity_groupmode($cm[$post->forum]);
1065 if ($groupmode[$post->forum]) {
1066 //hope i didn't break anything
1067 if (!@in_array($mygroupid, $post->groupid))/*$mygroupid != $post->groupid*/{
1068 continue;
1073 if (! $heading) {
1074 print_headline(get_string('newforumposts', 'forum').':', 3);
1075 $heading = true;
1076 $content = true;
1078 $date = userdate($post->modified, $strftimerecent);
1080 $subjectclass = ($log->action == 'add discussion') ? ' bold' : '';
1082 //Accessibility: markup as a list.
1083 if ($count < 1) {
1084 echo "\n<ul class='unlist'>\n";
1086 $count++;
1087 echo '<li><div class="head">'.
1088 '<div class="date">'.$date.'</div>'.
1089 '<div class="name">'.fullname($post, has_capability('moodle/site:viewfullnames', $coursecontext)).'</div>'.
1090 '</div>';
1091 echo '<div class="info'.$subjectclass.'">';
1092 echo '"<a href="'.$CFG->wwwroot.'/mod/forum/'.str_replace('&', '&amp;', $log->url).'">';
1093 $post->subject = break_up_long_words(format_string($post->subject,true));
1094 echo $post->subject;
1095 echo "</a>\"</div></li>\n";
1098 echo "</ul>\n";
1099 return $content;
1103 * Return grade for given user or all users.
1105 * @param int $forumid id of forum
1106 * @param int $userid optional user id, 0 means all users
1107 * @return array array of grades, false if none
1109 function forum_get_user_grades($forum, $userid=0) {
1110 global $CFG;
1112 $user = $userid ? "AND u.id = $userid" : "";
1114 $sql = "SELECT u.id, u.id AS userid, avg(fr.rating) AS rawgrade
1115 FROM {$CFG->prefix}user u, {$CFG->prefix}forum_posts fp,
1116 {$CFG->prefix}forum_ratings fr, {$CFG->prefix}forum_discussions fd
1117 WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1118 AND fr.userid != u.id AND fd.forum = $forum->id
1119 $user
1120 GROUP BY u.id";
1122 return get_records_sql($sql);
1126 * Update grades by firing grade_updated event
1128 * @param object $forum null means all forums
1129 * @param int $userid specific user only, 0 mean all
1131 function forum_update_grades($forum=null, $userid=0, $nullifnone=true) {
1132 global $CFG;
1134 if ($forum != null) {
1135 require_once($CFG->libdir.'/gradelib.php');
1136 if ($grades = forum_get_user_grades($forum, $userid)) {
1137 grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades);
1139 } else if ($userid and $nullifnone) {
1140 $grade = new object();
1141 $grade->userid = $userid;
1142 $grade->rawgrade = NULL;
1143 grade_update('mod/forum', $data->course, 'mod', 'forum', $forum->id, 0, $grade);
1146 } else {
1147 $sql = "SELECT f.*, cm.idnumber as cmidnumber
1148 FROM {$CFG->prefix}forum f, {$CFG->prefix}course_modules cm, {$CFG->prefix}modules m
1149 WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1150 if ($rs = get_recordset_sql($sql)) {
1151 if ($rs->RecordCount() > 0) {
1152 while ($forum = rs_fetch_next_record($rs)) {
1153 forum_grade_item_update($forum);
1154 if ($forum->assessed) {
1155 forum_update_grades($forum, 0, false);
1159 rs_close($rs);
1165 * Create/update grade item for given forum
1167 * @param object $forum object with extra cmidnumber
1168 * @return int 0 if ok
1170 function forum_grade_item_update($forum) {
1171 global $CFG;
1172 if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1173 require_once($CFG->libdir.'/gradelib.php');
1176 $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1178 if (!$forum->assessed or $forum->scale == 0) {
1179 $params['gradetype'] = GRADE_TYPE_NONE;
1181 } else if ($forum->scale > 0) {
1182 $params['gradetype'] = GRADE_TYPE_VALUE;
1183 $params['grademax'] = $forum->scale;
1184 $params['grademin'] = 0;
1186 } else if ($forum->scale < 0) {
1187 $params['gradetype'] = GRADE_TYPE_SCALE;
1188 $params['scaleid'] = -$forum->scale;
1191 return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, $params);
1195 * Delete grade item for given forum
1197 * @param object $forum object
1198 * @return object grade_item
1200 function forum_grade_item_delete($forum) {
1201 global $CFG;
1202 require_once($CFG->libdir.'/gradelib.php');
1204 return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1209 * Returns the users with data in one forum
1210 * (users with records in forum_subscriptions, forum_posts and forum_ratings, students)
1212 function forum_get_participants($forumid) {
1214 global $CFG;
1216 //Get students from forum_subscriptions
1217 $st_subscriptions = get_records_sql("SELECT DISTINCT u.id, u.id
1218 FROM {$CFG->prefix}user u,
1219 {$CFG->prefix}forum_subscriptions s
1220 WHERE s.forum = '$forumid' and
1221 u.id = s.userid");
1222 //Get students from forum_posts
1223 $st_posts = get_records_sql("SELECT DISTINCT u.id, u.id
1224 FROM {$CFG->prefix}user u,
1225 {$CFG->prefix}forum_discussions d,
1226 {$CFG->prefix}forum_posts p
1227 WHERE d.forum = '$forumid' and
1228 p.discussion = d.id and
1229 u.id = p.userid");
1231 //Get students from forum_ratings
1232 $st_ratings = get_records_sql("SELECT DISTINCT u.id, u.id
1233 FROM {$CFG->prefix}user u,
1234 {$CFG->prefix}forum_discussions d,
1235 {$CFG->prefix}forum_posts p,
1236 {$CFG->prefix}forum_ratings r
1237 WHERE d.forum = '$forumid' and
1238 p.discussion = d.id and
1239 r.post = p.id and
1240 u.id = r.userid");
1242 //Add st_posts to st_subscriptions
1243 if ($st_posts) {
1244 foreach ($st_posts as $st_post) {
1245 $st_subscriptions[$st_post->id] = $st_post;
1248 //Add st_ratings to st_subscriptions
1249 if ($st_ratings) {
1250 foreach ($st_ratings as $st_rating) {
1251 $st_subscriptions[$st_rating->id] = $st_rating;
1254 //Return st_subscriptions array (it contains an array of unique users)
1255 return ($st_subscriptions);
1261 function forum_scale_used ($forumid,$scaleid) {
1262 //This function returns if a scale is being used by one forum
1264 $return = false;
1266 $rec = get_record("forum","id","$forumid","scale","-$scaleid");
1268 if (!empty($rec) && !empty($scaleid)) {
1269 $return = true;
1272 return $return;
1275 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1278 * Gets a post with all info ready for forum_print_post
1279 * Most of these joins are just to get the forum id
1281 function forum_get_post_full($postid) {
1282 global $CFG;
1284 return get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture
1285 FROM {$CFG->prefix}forum_posts p
1286 LEFT JOIN {$CFG->prefix}forum_discussions d ON p.discussion = d.id
1287 LEFT JOIN {$CFG->prefix}user u ON p.userid = u.id
1288 WHERE p.id = '$postid'");
1292 * Gets posts with all info ready for forum_print_post
1293 * We pass forumid in because we always know it so no need to make a
1294 * complicated join to find it out.
1296 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1297 global $CFG;
1299 return get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture
1300 FROM {$CFG->prefix}forum_posts p
1301 LEFT JOIN {$CFG->prefix}user u ON p.userid = u.id
1302 WHERE p.discussion = $discussion
1303 AND p.parent > 0 $sort");
1307 * Gets posts with all info ready for forum_print_post
1308 * We pass forumid in because we always know it so no need to make a
1309 * complicated join to find it out.
1311 function forum_get_child_posts($parent, $forumid) {
1312 global $CFG;
1314 return get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture
1315 FROM {$CFG->prefix}forum_posts p
1316 LEFT JOIN {$CFG->prefix}user u ON p.userid = u.id
1317 WHERE p.parent = '$parent'
1318 ORDER BY p.created ASC");
1322 * An array of forum objects that the user is allowed to read/search through.
1323 * @param $userid
1324 * @param $courseid - if 0, we look for forums throughout the whole site.
1325 * @return array of forum objects, or false if no matches
1326 * Forum objects have the following attributes:
1327 * id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1328 * viewhiddentimedposts
1330 function forum_get_readable_forums($userid, $courseid=0) {
1332 global $CFG, $USER;
1334 if (!$forummod = get_record('modules', 'name', 'forum')) {
1335 error('The forum module is not installed');
1338 if ($courseid) {
1339 $courses = get_records('course', 'id', $courseid);
1340 } else {
1341 // If no course is specified, then the user can see SITE + his courses.
1342 // And admins can see all courses, so pass the $doanything flag enabled
1343 $courses1 = get_records('course', 'id', SITEID);
1344 $courses2 = get_my_courses($userid, null, null, true);
1345 $courses = array_merge($courses1, $courses2);
1347 if (!$courses) {
1348 return false;
1351 $readableforums = array();
1353 foreach ($courses as $course) {
1355 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
1357 if (!has_capability('moodle/course:viewhiddenactivities', $coursecontext)) {
1358 $selecthidden = ' AND cm.visible = 1';
1359 } else {
1360 $selecthidden = '';
1363 $selectforums = "SELECT f.id AS id,
1364 f.name AS name,
1365 f.type AS type,
1366 f.course AS course,
1367 cm.id AS cmid,
1368 cm.visible AS cmvisible,
1369 cm.groupmode AS cmgroupmode,
1370 cm.groupingid AS cmgroupingid,
1371 cm.groupmembersonly AS cmgroupmembersonly
1372 FROM {$CFG->prefix}course_modules cm,
1373 {$CFG->prefix}forum f
1374 WHERE cm.instance = f.id
1375 AND cm.course = {$course->id}
1376 AND cm.module = {$forummod->id}
1377 $selecthidden
1378 ORDER BY f.name ASC";
1380 if ($forums = get_records_sql($selectforums)) {
1382 $group = groups_get_all_groups($course->id, $userid);
1384 foreach ($forums as $forum) {
1385 $forumcontext = get_context_instance(CONTEXT_MODULE, $forum->cmid);
1387 if (has_capability('mod/forum:viewdiscussion', $forumcontext)) {
1389 // Evaluate groupmode.
1390 $cm = new object;
1391 $cm->id = $forum->cmid;
1392 $cm->groupmode = $forum->cmgroupmode;
1393 $cm->groupingid = $forum->cmgroupingid;
1394 $cm->groupmembersonly = $forum->cmgroupmembersonly;
1395 $cm->course = $forum->course;
1396 $forum->cmgroupmode = groups_get_activity_groupmode($cm);
1397 if (!groups_course_module_visible($cm)) {
1398 continue;
1400 if ($forum->cmgroupmode == SEPARATEGROUPS
1401 && !has_capability('moodle/site:accessallgroups', $forumcontext)) {
1402 $forum->accessallgroups = false;
1403 $forum->accessgroup = $group->id; // The user can only access
1404 // discussions for this group.
1405 } else {
1406 $forum->accessallgroups = true;
1409 $forum->viewhiddentimedposts
1410 = has_capability('mod/forum:viewhiddentimedposts', $forumcontext);
1412 if ($forum->type == 'qanda'
1413 && !has_capability('mod/forum:viewqandawithoutposting', $forumcontext)) {
1415 // We need to check whether the user has posted in the qanda forum.
1416 $forum->onlydiscussions = array(); // Holds discussion ids for the discussions
1417 // the user is allowed to see in this forum.
1419 if ($discussionspostedin =
1420 forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1421 foreach ($discussionspostedin as $d) {
1422 array_push($forum->onlydiscussions, $d->id);
1426 array_push($readableforums, $forum);
1430 } // End foreach $courses
1432 //print_object($courses);
1433 //print_object($readableforums);
1435 return $readableforums;
1439 * Returns a list of posts found using an array of search terms.
1440 * @param $searchterms - array of search terms, e.g. word +word -word
1441 * @param $courseid - if 0, we search through the whole site
1442 * @param $page
1443 * @param $recordsperpage=50
1444 * @param &$totalcount
1445 * @param $extrasql
1446 * @return array of posts found
1448 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1449 &$totalcount, $extrasql='') {
1450 global $CFG, $USER;
1451 require_once($CFG->libdir.'/searchlib.php');
1453 $forums = forum_get_readable_forums($USER->id, $courseid);
1455 if (count($forums) == 0) {
1456 return false;
1459 for ($i=0; $i<count($forums); $i++) {
1460 if ($i == 0) {
1461 $selectdiscussion = " ((d.forum = {$forums[$i]->id}";
1462 } else {
1463 $selectdiscussion .= " OR (d.forum = {$forums[$i]->id}";
1465 if (!empty($CFG->forum_enabletimedposts) && !$forums[$i]->viewhiddentimedposts) {
1466 $now = time();
1467 $selectdiscussion .= " AND ( d.userid = {$USER->id}
1468 OR ((d.timestart = 0 OR d.timestart <= $now)
1469 AND (d.timeend = 0 OR d.timeend > $now)) )";
1471 if ($forums[$i]->type == 'qanda' && isset($forums[$i]->onlydiscussions)) {
1472 // This is a qanda forum.
1473 if (is_array($forums[$i]->onlydiscussions)) {
1474 // Show question posts as well as posts from discussions in
1475 // which the user has posted a reply.
1476 $onlydiscussions = implode(' OR d.id = ', $forums[$i]->onlydiscussions);
1477 $selectdiscussion .= " AND ((d.id = $onlydiscussions) OR p.parent = 0)";
1478 } else {
1479 // Show only the question posts.
1480 $selectdiscussion .= ' AND (p.parent = 0)';
1483 if (!$forums[$i]->accessallgroups) {
1484 if (!empty($forums[$i]->accessgroup)) {
1485 $selectdiscussion .= " AND (d.groupid = {$forums[$i]->accessgroup}";
1486 $selectdiscussion .= ' OR d.groupid = -1)'; // -1 means open for all groups.
1487 } else {
1488 // User isn't in any group. Only search discussions that are
1489 // open to all groups.
1490 $selectdiscussion .= ' AND d.groupid = -1';
1493 $selectdiscussion .= ")\n";
1495 $selectdiscussion .= ")";
1498 // Some differences SQL
1499 $LIKE = sql_ilike();
1500 $NOTLIKE = 'NOT ' . $LIKE;
1501 if ($CFG->dbfamily == 'postgres') {
1502 $REGEXP = '~*';
1503 $NOTREGEXP = '!~*';
1504 } else {
1505 $REGEXP = 'REGEXP';
1506 $NOTREGEXP = 'NOT REGEXP';
1509 $messagesearch = '';
1510 $searchstring = '';
1512 // Need to concat these back together for parser to work.
1513 foreach($searchterms as $searchterm){
1514 if ($searchstring != '') {
1515 $searchstring .= ' ';
1517 $searchstring .= $searchterm;
1520 // We need to allow quoted strings for the search. The quotes *should* be stripped
1521 // by the parser, but this should be examined carefully for security implications.
1522 $searchstring = str_replace("\\\"","\"",$searchstring);
1523 $parser = new search_parser();
1524 $lexer = new search_lexer($parser);
1526 if ($lexer->parse($searchstring)) {
1527 $parsearray = $parser->get_parsed_array();
1528 // Experimental feature under 1.8! MDL-8830
1529 // Use alternative text searches if defined
1530 // This feature only works under mysql until properly implemented for other DBs
1531 // Requires manual creation of text index for forum_posts before enabling it:
1532 // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
1533 // Experimental feature under 1.8! MDL-8830
1534 if (!empty($CFG->forum_usetextsearches)) {
1535 $messagesearch = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
1536 'p.userid', 'u.id', 'u.firstname',
1537 'u.lastname', 'p.modified', 'd.forum');
1538 } else {
1539 $messagesearch = search_generate_SQL($parsearray, 'p.message', 'p.subject',
1540 'p.userid', 'u.id', 'u.firstname',
1541 'u.lastname', 'p.modified', 'd.forum');
1545 $fromsql = "{$CFG->prefix}forum_posts p,
1546 {$CFG->prefix}forum_discussions d,
1547 {$CFG->prefix}user u";
1549 $selectsql = " $messagesearch
1550 AND p.discussion = d.id
1551 AND p.userid = u.id
1552 AND $selectdiscussion
1553 $extrasql";
1555 $countsql = "SELECT COUNT(*)
1556 FROM $fromsql
1557 WHERE $selectsql";
1559 $searchsql = "SELECT p.*,
1560 d.forum,
1561 u.firstname,
1562 u.lastname,
1563 u.email,
1564 u.picture
1565 FROM $fromsql
1566 WHERE $selectsql
1567 ORDER BY p.modified DESC";
1569 $totalcount = count_records_sql($countsql);
1571 return get_records_sql($searchsql, $limitfrom, $limitnum);
1575 * Returns a list of ratings for a particular post - sorted.
1577 function forum_get_ratings($postid, $sort="u.firstname ASC") {
1578 global $CFG;
1579 return get_records_sql("SELECT u.*, r.rating, r.time
1580 FROM {$CFG->prefix}forum_ratings r,
1581 {$CFG->prefix}user u
1582 WHERE r.post = '$postid'
1583 AND r.userid = u.id
1584 ORDER BY $sort");
1588 * Returns a list of all new posts that have not been mailed yet
1590 function forum_get_unmailed_posts($starttime, $endtime) {
1591 global $CFG;
1592 $now = time();
1593 return get_records_sql("SELECT p.*, d.course
1594 FROM {$CFG->prefix}forum_posts p,
1595 {$CFG->prefix}forum_discussions d
1596 WHERE p.mailed = 0
1597 AND (p.created >= '$starttime' OR d.timestart > 0)
1598 AND (p.created < '$endtime' OR p.mailnow = 1)
1599 AND p.discussion = d.id
1600 AND ((d.timestart = 0 OR d.timestart <= '$now')
1601 AND (d.timeend = 0 OR d.timeend > '$now'))
1602 ORDER BY p.modified ASC");
1606 * Marks posts before a certain time as being mailed already
1608 function forum_mark_old_posts_as_mailed($endtime) {
1609 global $CFG;
1610 // Find out posts those are not showing immediately so we can exclude them
1611 $now = time();
1612 $delayed_posts = get_records_sql("SELECT p.id, p.discussion
1613 FROM {$CFG->prefix}forum_posts p,
1614 {$CFG->prefix}forum_discussions d
1615 WHERE p.mailed = 0
1616 AND p.discussion = d.id
1617 AND d.timestart > '$now'");
1618 $delayed_ids = array();
1619 if ($delayed_posts) {
1620 foreach ($delayed_posts as $post) {
1621 $delayed_ids[] = $post->id;
1623 } else {
1624 $delayed_ids[] = 0;
1626 return execute_sql("UPDATE {$CFG->prefix}forum_posts
1627 SET mailed = '1'
1628 WHERE id NOT IN (".implode(',',$delayed_ids).")
1629 AND (created < '$endtime' OR mailnow = 1)
1630 AND mailed ='0'", false);
1634 * Get all the posts for a user in a forum suitable for forum_print_post
1636 function forum_get_user_posts($forumid, $userid) {
1637 global $CFG;
1639 return get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture
1640 FROM {$CFG->prefix}forum f,
1641 {$CFG->prefix}forum_discussions d,
1642 {$CFG->prefix}forum_posts p,
1643 {$CFG->prefix}user u
1644 WHERE f.id = '$forumid'
1645 AND d.forum = f.id
1646 AND p.discussion = d.id
1647 AND p.userid = '$userid'
1648 AND p.userid = u.id
1649 ORDER BY p.modified ASC");
1653 * Given a log entry, return the forum post details for it.
1655 function forum_get_post_from_log($log) {
1656 global $CFG;
1658 if ($log->action == "add post") {
1660 return get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1661 u.firstname, u.lastname, u.email, u.picture
1662 FROM {$CFG->prefix}forum_discussions d,
1663 {$CFG->prefix}forum_posts p,
1664 {$CFG->prefix}forum f,
1665 {$CFG->prefix}user u
1666 WHERE p.id = '$log->info'
1667 AND d.id = p.discussion
1668 AND p.userid = u.id
1669 AND u.deleted <> '1'
1670 AND f.id = d.forum");
1673 } else if ($log->action == "add discussion") {
1675 return get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1676 u.firstname, u.lastname, u.email, u.picture
1677 FROM {$CFG->prefix}forum_discussions d,
1678 {$CFG->prefix}forum_posts p,
1679 {$CFG->prefix}forum f,
1680 {$CFG->prefix}user u
1681 WHERE d.id = '$log->info'
1682 AND d.firstpost = p.id
1683 AND p.userid = u.id
1684 AND u.deleted <> '1'
1685 AND f.id = d.forum");
1687 return NULL;
1691 * Given a discussion id, return the first post from the discussion
1693 function forum_get_firstpost_from_discussion($discussionid) {
1694 global $CFG;
1696 return get_record_sql("SELECT p.*
1697 FROM {$CFG->prefix}forum_discussions d,
1698 {$CFG->prefix}forum_posts p
1699 WHERE d.id = '$discussionid'
1700 AND d.firstpost = p.id ");
1704 * Returns an array of counts of replies to each discussion (optionally in one forum or course and/or user)
1706 function forum_count_discussion_replies($forum='0', $course='0', $user='0') {
1707 global $CFG;
1709 $forumselect = $courseselect = $userselect = '';
1711 if ($forum) {
1712 $forumselect = " AND d.forum = '$forum'";
1714 if ($course) {
1715 $courseselect = " AND d.course = '$course'";
1717 if ($user) {
1718 $userselect = " AND d.userid = '$user'";
1720 return get_records_sql("SELECT p.discussion, (count(*)) as replies, max(p.id) as lastpostid
1721 FROM {$CFG->prefix}forum_posts p,
1722 {$CFG->prefix}forum_discussions d
1723 WHERE p.parent > 0 $forumselect $courseselect $userselect
1724 AND p.discussion = d.id
1725 GROUP BY p.discussion");
1729 * How many unrated posts are in the given discussion for a given user?
1731 function forum_count_unrated_posts($discussionid, $userid) {
1732 global $CFG;
1733 if ($posts = get_record_sql("SELECT count(*) as num
1734 FROM {$CFG->prefix}forum_posts
1735 WHERE parent > 0
1736 AND discussion = '$discussionid'
1737 AND userid <> '$userid' ")) {
1739 if ($rated = get_record_sql("SELECT count(*) as num
1740 FROM {$CFG->prefix}forum_posts p,
1741 {$CFG->prefix}forum_ratings r
1742 WHERE p.discussion = '$discussionid'
1743 AND p.id = r.post
1744 AND r.userid = '$userid'")) {
1745 $difference = $posts->num - $rated->num;
1746 if ($difference > 0) {
1747 return $difference;
1748 } else {
1749 return 0; // Just in case there was a counting error
1751 } else {
1752 return $posts->num;
1754 } else {
1755 return 0;
1760 * Get all discussions in a forum
1762 function forum_get_discussions($forum="0", $forumsort="d.timemodified DESC",
1763 $user=0, $fullpost=true, $currentgroup=-1, $limit=0, $userlastmodified=false) {
1764 global $CFG, $USER;
1766 $timelimit = '';
1768 if (!$cm = get_coursemodule_from_instance('forum', $forum)) {
1769 error('Course Module ID was incorrect');
1772 if (!empty($CFG->forum_enabletimedposts)) {
1774 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
1776 if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
1777 $now = time();
1778 $timelimit = " AND ((d.timestart = 0 OR d.timestart <= '$now') AND (d.timeend = 0 OR d.timeend > '$now')";
1779 if (!empty($USER->id)) {
1780 $timelimit .= " OR d.userid = '$USER->id'";
1782 $timelimit .= ')';
1786 if ($user) {
1787 $userselect = " AND u.id = '$user' ";
1788 } else {
1789 $userselect = "";
1792 $limitfrom = 0;
1793 $limitnum = 0;
1794 if ($limit) {
1795 $limitnum = $limit;
1798 if ($currentgroup == -1) {
1799 $currentgroup = get_current_group($cm->course);
1802 if ($currentgroup) {
1803 $groupselect = " AND (d.groupid = '$currentgroup' OR d.groupid = -1) ";
1804 } else {
1805 $groupselect = "";
1808 if (empty($forumsort)) {
1809 $forumsort = "d.timemodified DESC";
1811 if (empty($fullpost)) {
1812 $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
1813 } else {
1814 $postdata = "p.*";
1817 if (empty($userlastmodified)) { // We don't need to know this
1818 $umfields = '';
1819 $umtable = '';
1820 } else {
1821 $umfields = ', um.firstname AS umfirstname, um.lastname AS umlastname';
1822 $umtable = ' LEFT JOIN '.$CFG->prefix.'user um on (d.usermodified = um.id)';
1825 //TODO: there must be a nice way to do this that keeps both postgres and mysql 3.2x happy but I can't find it right now.
1826 if ($CFG->dbfamily == 'postgres' || $CFG->dbfamily == 'mssql' || $CFG->dbfamily == 'oracle') {
1827 return get_records_sql("SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid,
1828 u.firstname, u.lastname, u.email, u.picture $umfields
1829 FROM {$CFG->prefix}forum_discussions d
1830 JOIN {$CFG->prefix}forum_posts p ON p.discussion = d.id
1831 JOIN {$CFG->prefix}user u ON p.userid = u.id
1832 $umtable
1833 WHERE d.forum = '$forum'
1834 AND p.parent = 0
1835 $timelimit $groupselect $userselect
1836 ORDER BY $forumsort", $limitfrom, $limitnum);
1837 } else { // MySQL query. TODO: Check if this is needed (MySQL 4.1 should work with the above query)
1838 return get_records_sql("SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid,
1839 u.firstname, u.lastname, u.email, u.picture $umfields
1840 FROM ({$CFG->prefix}forum_posts p,
1841 {$CFG->prefix}user u,
1842 {$CFG->prefix}forum_discussions d)
1843 $umtable
1844 WHERE d.forum = '$forum'
1845 AND p.discussion = d.id
1846 AND p.parent = 0
1847 AND p.userid = u.id $timelimit $groupselect $userselect
1848 ORDER BY $forumsort", $limitfrom, $limitnum);
1855 * Get all discussions started by a particular user in a course (or group)
1856 * This function no longer used ...
1858 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
1859 global $CFG;
1861 if ($groupid) {
1862 $groupselect = " AND d.groupid = '$groupid' ";
1863 } else {
1864 $groupselect = "";
1867 return get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture,
1868 f.type as forumtype, f.name as forumname, f.id as forumid
1869 FROM {$CFG->prefix}forum_discussions d,
1870 {$CFG->prefix}forum_posts p,
1871 {$CFG->prefix}user u,
1872 {$CFG->prefix}forum f
1873 WHERE d.course = '$courseid'
1874 AND p.discussion = d.id
1875 AND p.parent = 0
1876 AND p.userid = u.id
1877 AND u.id = '$userid'
1878 AND d.forum = f.id $groupselect
1879 ORDER BY p.created DESC");
1883 * Returns list of user objects that are subscribed to this forum
1885 function forum_subscribed_users($course, $forum, $groupid=0, $cache=false) {
1887 global $CFG;
1889 static $resultscache = array();
1891 if ($cache && isset($resultscache[$forum->id][$groupid])) {
1892 return $resultscache[$forum->id][$groupid];
1895 if ($groupid) {
1896 $grouptables = ", {$CFG->prefix}groups_members gm ";
1897 $groupselect = "AND gm.groupid = '$groupid' AND u.id = gm.userid";
1899 } else {
1900 $grouptables = '';
1901 $groupselect = '';
1904 if (forum_is_forcesubscribed($forum)) {
1905 $results = get_course_users($course->id); // Otherwise get everyone in the course
1906 } else {
1907 $results = get_records_sql("SELECT u.id, u.username, u.firstname, u.lastname, u.maildisplay, u.mailformat, u.maildigest, u.emailstop,
1908 u.email, u.city, u.country, u.lastaccess, u.lastlogin, u.picture, u.timezone, u.theme, u.lang, u.trackforums
1909 FROM {$CFG->prefix}user u,
1910 {$CFG->prefix}forum_subscriptions s $grouptables
1911 WHERE s.forum = '$forum->id'
1912 AND s.userid = u.id
1913 AND u.deleted <> 1 $groupselect
1914 ORDER BY u.email ASC");
1916 // Guest user should never be subscribed to a forum.
1917 if ($guest = guest_user()) {
1918 unset($results[$guest->id]);
1921 if ($cache) {
1922 $resultscache[$forum->id][$groupid] = $results;
1925 return $results;
1930 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
1933 function forum_get_course_forum($courseid, $type) {
1934 // How to set up special 1-per-course forums
1935 global $CFG;
1937 if ($forums = get_records_select("forum", "course = '$courseid' AND type = '$type'", "id ASC")) {
1938 // There should always only be ONE, but with the right combination of
1939 // errors there might be more. In this case, just return the oldest one (lowest ID).
1940 foreach ($forums as $forum) {
1941 return $forum; // ie the first one
1945 // Doesn't exist, so create one now.
1946 $forum->course = $courseid;
1947 $forum->type = "$type";
1948 switch ($forum->type) {
1949 case "news":
1950 $forum->name = addslashes(get_string("namenews", "forum"));
1951 $forum->intro = addslashes(get_string("intronews", "forum"));
1952 $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
1953 $forum->assessed = 0;
1954 if ($courseid == SITEID) {
1955 $forum->name = get_string("sitenews");
1956 $forum->forcesubscribe = 0;
1958 break;
1959 case "social":
1960 $forum->name = addslashes(get_string("namesocial", "forum"));
1961 $forum->intro = addslashes(get_string("introsocial", "forum"));
1962 $forum->assessed = 0;
1963 $forum->forcesubscribe = 0;
1964 break;
1965 default:
1966 notify("That forum type doesn't exist!");
1967 return false;
1968 break;
1971 $forum->timemodified = time();
1972 $forum->id = insert_record("forum", $forum);
1974 if (! $module = get_record("modules", "name", "forum")) {
1975 notify("Could not find forum module!!");
1976 return false;
1978 $mod->course = $courseid;
1979 $mod->module = $module->id;
1980 $mod->instance = $forum->id;
1981 $mod->section = 0;
1982 if (! $mod->coursemodule = add_course_module($mod) ) { // assumes course/lib.php is loaded
1983 notify("Could not add a new course module to the course '" . format_string($course->fullname) . "'");
1984 return false;
1986 if (! $sectionid = add_mod_to_section($mod) ) { // assumes course/lib.php is loaded
1987 notify("Could not add the new course module to that section");
1988 return false;
1990 if (! set_field("course_modules", "section", $sectionid, "id", $mod->coursemodule)) {
1991 notify("Could not update the course module with the correct section");
1992 return false;
1994 include_once("$CFG->dirroot/course/lib.php");
1995 rebuild_course_cache($courseid);
1997 return get_record("forum", "id", "$forum->id");
2002 * Given the data about a posting, builds up the HTML to display it and
2003 * returns the HTML in a string. This is designed for sending via HTML email.
2005 function forum_make_mail_post(&$post, $user, $touser, $course,
2006 $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
2009 global $CFG, $USER;
2011 // the old caching was removed for now, because it did not work due to recent changes in cron
2013 $post->forum = get_field('forum_discussions', 'forum', 'id', $post->discussion);
2015 if (!$cm = get_coursemodule_from_instance('forum', $post->forum)) {
2016 mtrace('Course Module ID was incorrect');
2018 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2020 // format the post body
2021 $options = new object();
2022 $options->para = true;
2023 $formattedtext = format_text(trusttext_strip($post->message), $post->format, $options, $course->id);
2025 $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
2027 $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
2028 $output .= print_user_picture($user->id, $course->id, $user->picture, false, true);
2029 $output .= '</td>';
2031 if ($post->parent) {
2032 $output .= '<td class="topic">';
2033 } else {
2034 $output .= '<td class="topic starter">';
2036 $output .= '<div class="subject">'.format_string($post->subject).'</div>';
2038 $fullname = fullname($user, has_capability('moodle/site:viewfullnames', $modcontext));
2039 $by = new object();
2040 $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$user->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
2041 $by->date = userdate($post->modified, '', $touser->timezone);
2042 $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
2044 $output .= '</td></tr>';
2046 $output .= '<tr><td class="left side" valign="top">';
2047 if ($group = groups_get_all_groups($course->id, $user->id)) {
2048 $output .= print_group_picture($group, $course->id, false, true, true);
2049 } else {
2050 $output .= '&nbsp;';
2053 $output .= '</td><td class="content">';
2055 if ($post->attachment) {
2056 $post->course = $course->id;
2057 $output .= '<div class="attachments">';
2058 $output .= forum_print_attachments($post, 'html');
2059 $output .= "</div>";
2062 $output .= $formattedtext;
2064 // Commands
2065 $commands = array();
2067 if ($post->parent) {
2068 $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
2069 $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
2072 if ($reply) {
2073 $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
2074 get_string('reply', 'forum').'</a>';
2077 $output .= '<div class="commands">';
2078 $output .= implode(' | ', $commands);
2079 $output .= '</div>';
2081 // Context link to post if required
2082 if ($link) {
2083 $output .= '<div class="link">';
2084 $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
2085 get_string('postincontext', 'forum').'</a>';
2086 $output .= '</div>';
2089 if ($footer) {
2090 $output .= '<div class="footer">'.$footer.'</div>';
2092 $output .= '</td></tr></table>'."\n\n";
2094 return $output;
2098 * Print a forum post
2100 * @param object $post The post to print.
2101 * @param integer $courseid The course this post belongs to.
2102 * @param boolean $ownpost Whether this post belongs to the current user.
2103 * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
2104 * @param boolean $link Just print a shortened version of the post as a link to the full post.
2105 * @param object $ratings -- I don't really know --
2106 * @param string $footer Extra stuff to print after the message.
2107 * @param string $highlight Space-separated list of terms to highlight.
2108 * @param int $post_read true, false or -99. If we already know whether this user
2109 * has read this post, pass that in, otherwise, pass in -99, and this
2110 * function will work it out.
2111 * @param boolean $dummyifcantsee When forum_user_can_see_post says that
2112 * the current user can't see this post, if this argument is true
2113 * (the default) then print a dummy 'you can't see this post' post.
2114 * If false, don't output anything at all.
2116 function forum_print_post(&$post, $courseid, $ownpost=false, $reply=false, $link=false,
2117 $ratings=NULL, $footer="", $highlight="", $post_read=-99, $dummyifcantsee=true) {
2119 global $USER, $CFG;
2121 static $stredit, $strdelete, $strreply, $strparent, $strprune;
2122 static $strpruneheading, $displaymode;
2123 static $strmarkread, $strmarkunread, $istracked;
2126 $discussion = get_record('forum_discussions', 'id', $post->discussion);
2127 if (!$cm = get_coursemodule_from_instance('forum', $discussion->forum)) {
2128 error('Course Module ID was incorrect');
2130 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2133 if (!forum_user_can_see_post($post->forum,$post->discussion,$post)) {
2134 if (!$dummyifcantsee) {
2135 return;
2137 echo '<a id="p'.$post->id.'"></a>';
2138 echo '<table cellspacing="0" class="forumpost">';
2139 echo '<tr class="header"><td class="picture left">';
2140 // print_user_picture($post->userid, $courseid, $post->picture);
2141 echo '</td>';
2142 if ($post->parent) {
2143 echo '<td class="topic">';
2144 } else {
2145 echo '<td class="topic starter">';
2147 echo '<div class="subject">'.get_string('forumsubjecthidden','forum').'</div>';
2148 echo '<div class="author">';
2149 print_string('forumauthorhidden','forum');
2150 echo '</div></td></tr>';
2152 echo '<tr><td class="left side">';
2153 echo '&nbsp;';
2155 // Actual content
2157 echo '</td><td class="content">'."\n";
2158 echo get_string('forumbodyhidden','forum');
2159 echo '</td></tr></table>';
2160 return;
2163 if (empty($stredit)) {
2164 $stredit = get_string('edit', 'forum');
2165 $strdelete = get_string('delete', 'forum');
2166 $strreply = get_string('reply', 'forum');
2167 $strparent = get_string('parent', 'forum');
2168 $strpruneheading = get_string('pruneheading', 'forum');
2169 $strprune = get_string('prune', 'forum');
2170 $displaymode = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
2171 $strmarkread = get_string('markread', 'forum');
2172 $strmarkunread = get_string('markunread', 'forum');
2174 if (!empty($post->forum)) {
2175 $istracked = (forum_tp_can_track_forums($post->forum) &&
2176 forum_tp_is_tracked($post->forum));
2177 } else {
2178 $istracked = false;
2182 if ($istracked) {
2183 if ($post_read == -99) { // If we don't know yet...
2184 // The front page can display a news item post to non-logged in users. This should
2185 // always appear as 'read'.
2186 $post_read = empty($USER) || forum_tp_is_post_read($USER->id, $post);
2188 if ($post_read) {
2189 $read_style = ' read';
2190 } else {
2191 $read_style = ' unread';
2192 echo '<a name="unread"></a>';
2194 } else {
2195 $read_style = '';
2198 echo '<a id="p'.$post->id.'"></a>';
2199 echo '<table cellspacing="0" class="forumpost'.$read_style.'">';
2201 echo '<tr class="header"><td class="picture left">';
2202 print_user_picture($post->userid, $courseid, $post->picture);
2203 echo '</td>';
2205 if ($post->parent) {
2206 echo '<td class="topic">';
2207 } else {
2208 echo '<td class="topic starter">';
2211 echo '<div class="subject">'.format_string($post->subject).'</div>';
2213 echo '<div class="author">';
2214 $fullname = fullname($post, has_capability('moodle/site:viewfullnames', $modcontext));
2215 $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.
2216 $post->userid.'&amp;course='.$courseid.'">'.$fullname.'</a>';
2217 $by->date = userdate($post->modified);
2218 print_string('bynameondate', 'forum', $by);
2219 echo '</div></td></tr>';
2221 echo '<tr><td class="left side">';
2222 if ($group = groups_get_all_groups($courseid, $post->userid)) {
2223 print_group_picture($group, $courseid, false, false, true);
2224 } else {
2225 echo '&nbsp;';
2228 // Actual content
2230 echo '</td><td class="content">'."\n";
2232 if ($post->attachment) {
2233 $post->course = $courseid;
2234 $post->forum = get_field('forum_discussions', 'forum', 'id', $post->discussion);
2235 echo '<div class="attachments">';
2236 $attachedimages = forum_print_attachments($post);
2237 echo '</div>';
2238 } else {
2239 $attachedimages = '';
2243 $options = new Object;
2244 $options->para = false;
2245 $options->trusttext = true;
2246 if ($link and (strlen(strip_tags($post->message)) > $CFG->forum_longpost)) {
2247 // Print shortened version
2248 echo format_text(forum_shorten_post($post->message), $post->format, $options, $courseid);
2249 $numwords = count_words(strip_tags($post->message));
2250 echo '<p><a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
2251 echo get_string('readtherest', 'forum');
2252 echo '</a> ('.get_string('numwords', '', $numwords).')...</p>';
2253 } else {
2254 // Print whole message
2255 if ($highlight) {
2256 echo highlight($highlight, format_text($post->message, $post->format, $options, $courseid));
2257 } else {
2258 echo format_text($post->message, $post->format, $options, $courseid);
2260 echo $attachedimages;
2264 // Commands
2266 $commands = array();
2268 if ($istracked) {
2269 // SPECIAL CASE: The front page can display a news item post to non-logged in users.
2270 // Don't display the mark read / unread controls in this case.
2271 if ($CFG->forum_usermarksread && !empty($USER)) {
2272 if ($post_read) {
2273 $mcmd = '&amp;mark=unread&amp;postid='.$post->id;
2274 $mtxt = $strmarkunread;
2275 } else {
2276 $mcmd = '&amp;mark=read&amp;postid='.$post->id;
2277 $mtxt = $strmarkread;
2279 if ($displaymode == FORUM_MODE_THREADED) {
2280 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
2281 $post->discussion.'&amp;parent='.$post->id.$mcmd.'">'.$mtxt.'</a>';
2282 } else {
2283 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
2284 $post->discussion.$mcmd.'#p'.$post->id.'">'.$mtxt.'</a>';
2289 if ($post->parent) { // Zoom in to the parent specifically
2290 if ($displaymode == FORUM_MODE_THREADED) {
2291 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
2292 $post->discussion.'&amp;parent='.$post->parent.'">'.$strparent.'</a>';
2293 } else {
2294 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
2295 $post->discussion.'#p'.$post->parent.'">'.$strparent.'</a>';
2299 $forumtype = get_field('forum', 'type', 'id', $post->forum);
2301 $age = time() - $post->created;
2302 // Hack for allow to edit news posts those are not displayed yet until they are displayed
2303 if (!$post->parent
2304 && $forumtype == 'news'
2305 && get_field_sql("SELECT id FROM {$CFG->prefix}forum_discussions WHERE id = $post->discussion AND timestart > ".time())) {
2306 $age = 0;
2308 $editanypost = has_capability('mod/forum:editanypost', $modcontext);
2312 if ($ownpost or $editanypost) {
2313 if (($age < $CFG->maxeditingtime) or $editanypost) {
2314 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?edit='.$post->id.'">'.$stredit.'</a>';
2318 if (has_capability('mod/forum:splitdiscussions', $modcontext)
2319 && $post->parent && $forumtype != 'single') {
2321 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?prune='.$post->id.
2322 '" title="'.$strpruneheading.'">'.$strprune.'</a>';
2325 if (($ownpost and $age < $CFG->maxeditingtime
2326 and has_capability('mod/forum:deleteownpost', $modcontext))
2327 or has_capability('mod/forum:deleteanypost', $modcontext)) {
2328 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?delete='.$post->id.'">'.$strdelete.'</a>';
2331 if ($reply) {
2332 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.$strreply.'</a>';
2335 echo '<div class="commands">';
2336 echo implode(' | ', $commands);
2337 echo '</div>';
2340 // Ratings
2342 $ratingsmenuused = false;
2343 if (!empty($ratings) and !empty($USER->id)) {
2344 echo '<div class="ratings">';
2345 $useratings = true;
2346 if ($ratings->assesstimestart and $ratings->assesstimefinish) {
2347 if ($post->created < $ratings->assesstimestart or $post->created > $ratings->assesstimefinish) {
2348 $useratings = false;
2351 if ($useratings) {
2352 $mypost = ($USER->id == $post->userid);
2354 $canviewallratings = has_capability('mod/forum:viewanyrating', $modcontext);
2356 if ($canviewallratings and !$mypost) {
2357 forum_print_ratings_mean($post->id, $ratings->scale, $canviewallratings);
2358 if (!empty($ratings->allow)) {
2359 echo '&nbsp;';
2360 forum_print_rating_menu($post->id, $USER->id, $ratings->scale);
2361 $ratingsmenuused = true;
2364 } else if ($mypost) {
2365 forum_print_ratings_mean($post->id, $ratings->scale, true);
2367 } else if (!empty($ratings->allow) ) {
2368 forum_print_rating_menu($post->id, $USER->id, $ratings->scale);
2369 $ratingsmenuused = true;
2372 echo '</div>';
2375 // Link to post if required
2377 if ($link) {
2378 echo '<div class="link">';
2379 if ($post->replies == 1) {
2380 $replystring = get_string('repliesone', 'forum', $post->replies);
2381 } else {
2382 $replystring = get_string('repliesmany', 'forum', $post->replies);
2384 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.
2385 get_string('discussthistopic', 'forum').'</a>&nbsp;('.$replystring.')';
2386 echo '</div>';
2389 if ($footer) {
2390 echo '<div class="footer">'.$footer.'</div>';
2392 echo '</td></tr></table>'."\n\n";
2394 if ($istracked && !$CFG->forum_usermarksread && !empty($post->forum)) {
2395 forum_tp_mark_post_read($USER->id, $post, $post->forum);
2398 return $ratingsmenuused;
2403 * This function prints the overview of a discussion in the forum listing.
2404 * It needs some discussion information and some post information, these
2405 * happen to be combined for efficiency in the $post parameter by the function
2406 * that calls this one: forum_print_latest_discussions()
2408 * @param object $post The post object (passed by reference for speed).
2409 * @param object $forum The forum object.
2410 * @param int $group Current group.
2411 * @param string $datestring Format to use for the dates.
2412 * @param boolean $cantrack Is tracking enabled for this forum.
2413 * @param boolean $forumtracked Is the user tracking this forum.
2414 * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
2416 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
2417 $cantrack=true, $forumtracked=true, $canviewparticipants=true) {
2419 global $USER, $CFG;
2421 static $rowcount;
2422 static $strmarkalldread;
2425 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
2426 error('Course Module ID was incorrect');
2428 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2431 if (!isset($rowcount)) {
2432 $rowcount = 0;
2433 $strmarkalldread = get_string('markalldread', 'forum');
2434 } else {
2435 $rowcount = ($rowcount + 1) % 2;
2438 $post->subject = format_string($post->subject,true);
2440 echo "\n\n";
2441 echo '<tr class="discussion r'.$rowcount.'">';
2443 // Topic
2444 echo '<td class="topic starter">';
2445 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.$post->subject.'</a>';
2446 echo "</td>\n";
2448 // Picture
2449 echo '<td class="picture">';
2450 print_user_picture($post->userid, $forum->course, $post->picture);
2451 echo "</td>\n";
2453 // User name
2454 $fullname = fullname($post, has_capability('moodle/site:viewfullnames', $modcontext));
2455 echo '<td class="author">';
2456 echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->userid.'&amp;course='.$forum->course.'">'.$fullname.'</a>';
2457 echo "</td>\n";
2459 // Group picture
2460 if ($group !== -1) { // Groups are active - group is a group data object or NULL
2461 echo '<td class="picture group">';
2462 if (!empty($group->picture) and empty($group->hidepicture)) {
2463 print_group_picture($group, $forum->course, false, false, true);
2464 } else if (isset($group->id)) {
2465 if($canviewparticipants) {
2466 echo '<a href="'.$CFG->wwwroot.'/user/index.php?id='.$forum->course.'&amp;group='.$group->id.'">'.$group->name.'</a>';
2467 } else {
2468 echo $group->name;
2471 echo "</td>\n";
2474 if (has_capability('mod/forum:viewdiscussion', $modcontext)) { // Show the column with replies
2475 echo '<td class="replies">';
2476 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
2477 echo $post->replies.'</a>';
2478 echo "</td>\n";
2480 if ($cantrack) {
2481 echo '<td class="replies">';
2482 if ($forumtracked) {
2483 if ($post->unread > 0) {
2484 echo '<span class="unread">';
2485 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#unread">';
2486 echo $post->unread;
2487 echo '</a>';
2488 echo '<a title="'.$strmarkalldread.'" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
2489 $forum->id.'&amp;d='.$post->discussion.'&amp;mark=read&amp;returnpage=view.php">' .
2490 '<img src="'.$CFG->pixpath.'/t/clear.gif" class="iconsmall" alt="'.$strmarkalldread.'" /></a>';
2491 echo '</span>';
2492 } else {
2493 echo '<span class="read">';
2494 echo $post->unread;
2495 echo '</span>';
2497 } else {
2498 echo '<span class="read">';
2499 echo '-';
2500 echo '</span>';
2502 echo "</td>\n";
2506 echo '<td class="lastpost">';
2507 $usedate = (empty($post->timemodified)) ? $post->modified : $post->timemodified; // Just in case
2508 $parenturl = (empty($post->lastpostid)) ? '' : '&amp;parent='.$post->lastpostid;
2509 $usermodified->id = $post->usermodified;
2510 $usermodified->firstname = $post->umfirstname;
2511 $usermodified->lastname = $post->umlastname;
2512 echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->usermodified.'&amp;course='.$forum->course.'">'.
2513 fullname($usermodified).'</a><br />';
2514 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.$parenturl.'">'.
2515 userdate($usedate, $datestring).'</a>';
2516 echo "</td>\n";
2518 echo "</tr>\n\n";
2524 * Given a post object that we already know has a long message
2525 * this function truncates the message nicely to the first
2526 * sane place between $CFG->forum_longpost and $CFG->forum_shortpost
2528 function forum_shorten_post($message) {
2530 global $CFG;
2532 $i = 0;
2533 $tag = false;
2534 $length = strlen($message);
2535 $count = 0;
2536 $stopzone = false;
2537 $truncate = 0;
2539 for ($i=0; $i<$length; $i++) {
2540 $char = $message[$i];
2542 switch ($char) {
2543 case "<":
2544 $tag = true;
2545 break;
2546 case ">":
2547 $tag = false;
2548 break;
2549 default:
2550 if (!$tag) {
2551 if ($stopzone) {
2552 if ($char == ".") {
2553 $truncate = $i+1;
2554 break 2;
2557 $count++;
2559 break;
2561 if (!$stopzone) {
2562 if ($count > $CFG->forum_shortpost) {
2563 $stopzone = true;
2568 if (!$truncate) {
2569 $truncate = $i;
2572 return substr($message, 0, $truncate);
2577 * Print the multiple ratings on a post given to the current user by others.
2578 * Scale is an array of ratings
2580 function forum_print_ratings_mean($postid, $scale, $link=true) {
2582 static $strrate;
2584 $mean = forum_get_ratings_mean($postid, $scale);
2586 if ($mean !== "") {
2588 if (empty($strratings)) {
2589 $strratings = get_string("ratings", "forum");
2592 echo "$strratings: ";
2593 if ($link) {
2594 link_to_popup_window ("/mod/forum/report.php?id=$postid", "ratings", $mean, 400, 600);
2595 } else {
2596 echo "$mean ";
2603 * Return the mean rating of a post given to the current user by others.
2604 * Scale is an array of possible ratings in the scale
2605 * Ratings is an optional simple array of actual ratings (just integers)
2607 function forum_get_ratings_mean($postid, $scale, $ratings=NULL) {
2609 if (!$ratings) {
2610 $ratings = array();
2611 if ($rates = get_records("forum_ratings", "post", $postid)) {
2612 foreach ($rates as $rate) {
2613 $ratings[] = $rate->rating;
2618 $count = count($ratings);
2620 if ($count == 0) {
2621 return "";
2623 } else if ($count == 1) {
2624 return $scale[$ratings[0]];
2626 } else {
2627 $total = 0;
2628 foreach ($ratings as $rating) {
2629 $total += $rating;
2631 $mean = round( ((float)$total/(float)$count) + 0.001); // Little fudge factor so that 0.5 goes UP
2633 if (isset($scale[$mean])) {
2634 return $scale[$mean]." ($count)";
2635 } else {
2636 return "$mean ($count)"; // Should never happen, hopefully
2642 * Return a summary of post ratings given to the current user by others.
2643 * Scale is an array of possible ratings in the scale
2644 * Ratings is an optional simple array of actual ratings (just integers)
2646 function forum_get_ratings_summary($postid, $scale, $ratings=NULL) {
2648 if (!$ratings) {
2649 $ratings = array();
2650 if ($rates = get_records("forum_ratings", "post", $postid)) {
2651 foreach ($rates as $rate) {
2652 $rating[] = $rate->rating;
2658 if (!$count = count($ratings)) {
2659 return "";
2663 foreach ($scale as $key => $scaleitem) {
2664 $sumrating[$key] = 0;
2667 foreach ($ratings as $rating) {
2668 $sumrating[$rating]++;
2671 $summary = "";
2672 foreach ($scale as $key => $scaleitem) {
2673 $summary = $sumrating[$key].$summary;
2674 if ($key > 1) {
2675 $summary = "/$summary";
2678 return $summary;
2682 * Print the menu of ratings as part of a larger form.
2683 * If the post has already been - set that value.
2684 * Scale is an array of ratings
2686 function forum_print_rating_menu($postid, $userid, $scale) {
2688 static $strrate;
2690 if (!$rating = get_record("forum_ratings", "userid", $userid, "post", $postid)) {
2691 $rating->rating = FORUM_UNSET_POST_RATING;
2694 if (empty($strrate)) {
2695 $strrate = get_string("rate", "forum");
2697 $scale = array(FORUM_UNSET_POST_RATING => $strrate.'...') + $scale;
2698 choose_from_menu($scale, $postid, $rating->rating, '');
2702 * Print the drop down that allows the user to select how they want to have
2703 * the discussion displayed.
2704 * @param $id - forum id if $forumtype is 'single',
2705 * discussion id for any other forum type
2706 * @param $mode - forum layout mode
2707 * @param $forumtype - optional
2709 function forum_print_mode_form($id, $mode, $forumtype='') {
2710 global $FORUM_LAYOUT_MODES;
2712 if ($forumtype == 'single') {
2713 popup_form("view.php?f=$id&amp;mode=", $FORUM_LAYOUT_MODES, "mode", $mode, "");
2714 } else {
2715 popup_form("discuss.php?d=$id&amp;mode=", $FORUM_LAYOUT_MODES, "mode", $mode, "");
2722 function forum_search_form($course, $search='') {
2723 global $CFG;
2725 $output = '<div class="forumsearch">';
2726 $output .= '<form action="'.$CFG->wwwroot.'/mod/forum/search.php" style="display:inline">';
2727 $output .= '<fieldset class="invisiblefieldset">';
2728 $output .= helpbutton('search', get_string('search'), 'moodle', true, false, '', true);
2729 $output .= '<input name="search" type="text" size="18" value="'.$search.'" alt="search" />';
2730 $output .= '<input value="'.get_string('searchforums', 'forum').'" type="submit" />';
2731 $output .= '<input name="id" type="hidden" value="'.$course->id.'" />';
2732 $output .= '</fieldset>';
2733 $output .= '</form>';
2734 $output .= '</div>';
2736 return $output;
2743 function forum_set_return() {
2744 global $CFG, $SESSION;
2746 if (! isset($SESSION->fromdiscussion)) {
2747 if (!empty($_SERVER['HTTP_REFERER'])) {
2748 $referer = $_SERVER['HTTP_REFERER'];
2749 } else {
2750 $referer = "";
2752 // If the referer is NOT a login screen then save it.
2753 if (! strncasecmp("$CFG->wwwroot/login", $referer, 300)) {
2754 $SESSION->fromdiscussion = $_SERVER["HTTP_REFERER"];
2763 function forum_go_back_to($default) {
2764 global $SESSION;
2766 if (!empty($SESSION->fromdiscussion)) {
2767 $returnto = $SESSION->fromdiscussion;
2768 unset($SESSION->fromdiscussion);
2769 return $returnto;
2770 } else {
2771 return $default;
2776 * Creates a directory file name, suitable for make_upload_directory()
2778 function forum_file_area_name($post) {
2779 global $CFG;
2781 return "$post->course/$CFG->moddata/forum/$post->forum/$post->id";
2787 function forum_file_area($post) {
2788 return make_upload_directory( forum_file_area_name($post) );
2794 function forum_delete_old_attachments($post, $exception="") {
2797 * Deletes all the user files in the attachments area for a post
2798 * EXCEPT for any file named $exception
2800 if ($basedir = forum_file_area($post)) {
2801 if ($files = get_directory_list($basedir)) {
2802 foreach ($files as $file) {
2803 if ($file != $exception) {
2804 unlink("$basedir/$file");
2805 notify("Existing file '$file' has been deleted!");
2809 if (!$exception) { // Delete directory as well, if empty
2810 rmdir("$basedir");
2816 * Given a discussion object that is being moved to forumid,
2817 * this function checks all posts in that discussion
2818 * for attachments, and if any are found, these are
2819 * moved to the new forum directory.
2821 function forum_move_attachments($discussion, $forumid) {
2823 global $CFG;
2825 require_once($CFG->dirroot.'/lib/uploadlib.php');
2827 $return = true;
2829 if ($posts = get_records_select("forum_posts", "discussion = '$discussion->id' AND attachment <> ''")) {
2830 foreach ($posts as $oldpost) {
2831 $oldpost->course = $discussion->course;
2832 $oldpost->forum = $discussion->forum;
2833 $oldpostdir = "$CFG->dataroot/".forum_file_area_name($oldpost);
2834 if (is_dir($oldpostdir)) {
2835 $newpost = $oldpost;
2836 $newpost->forum = $forumid;
2837 $newpostdir = forum_file_area_name($newpost);
2838 // take off the last directory because otherwise we're renaming to a directory that already exists
2839 // and this is unhappy in certain situations, eg over an nfs mount and potentially on windows too.
2840 make_upload_directory(substr($newpostdir,0,strrpos($newpostdir,'/')));
2841 $newpostdir = $CFG->dataroot.'/'.forum_file_area_name($newpost);
2842 $files = get_directory_list($oldpostdir); // get it before we rename it.
2843 if (! @rename($oldpostdir, $newpostdir)) {
2844 $return = false;
2846 foreach ($files as $file) {
2847 clam_change_log($oldpostdir.'/'.$file,$newpostdir.'/'.$file);
2852 return $return;
2856 * if return=html, then return a html string.
2857 * if return=text, then return a text-only string.
2858 * otherwise, print HTML for non-images, and return image HTML
2860 function forum_print_attachments($post, $return=NULL) {
2862 global $CFG;
2864 $filearea = forum_file_area_name($post);
2866 $imagereturn = "";
2867 $output = "";
2869 if ($basedir = forum_file_area($post)) {
2870 if ($files = get_directory_list($basedir)) {
2871 $strattachment = get_string("attachment", "forum");
2872 foreach ($files as $file) {
2873 $icon = mimeinfo("icon", $file);
2874 $type = mimeinfo("type", $file);
2875 if ($CFG->slasharguments) {
2876 $ffurl = "$CFG->wwwroot/file.php/$filearea/$file";
2877 } else {
2878 $ffurl = "$CFG->wwwroot/file.php?file=/$filearea/$file";
2880 $image = "<img src=\"$CFG->pixpath/f/$icon\" class=\"icon\" alt=\"\" />";
2882 if ($return == "html") {
2883 $output .= "<a href=\"$ffurl\">$image</a> ";
2884 $output .= "<a href=\"$ffurl\">$file</a><br />";
2886 } else if ($return == "text") {
2887 $output .= "$strattachment $file:\n$ffurl\n";
2889 } else {
2890 if (in_array($type, array('image/gif', 'image/jpeg', 'image/png'))) { // Image attachments don't get printed as links
2891 $imagereturn .= "<br /><img src=\"$ffurl\" alt=\"\" />";
2892 } else {
2893 echo "<a href=\"$ffurl\">$image</a> ";
2894 echo filter_text("<a href=\"$ffurl\">$file</a><br />");
2901 if ($return) {
2902 return $output;
2905 return $imagereturn;
2908 * If successful, this function returns the name of the file
2909 * @param $post is a full post record, including course and forum
2910 * @param $newfile is a full upload array from $_FILES
2911 * @param $message is a string to hold the messages.
2917 function forum_add_attachment($post, $inputname,&$message) {
2919 global $CFG;
2921 if (!$forum = get_record("forum", "id", $post->forum)) {
2922 return "";
2925 if (!$course = get_record("course", "id", $forum->course)) {
2926 return "";
2929 require_once($CFG->dirroot.'/lib/uploadlib.php');
2930 $um = new upload_manager($inputname,true,false,$course,false,$forum->maxbytes,true,true);
2931 $dir = forum_file_area_name($post);
2932 if ($um->process_file_uploads($dir)) {
2933 $message .= $um->get_errors();
2934 return $um->get_new_filename();
2936 $message .= $um->get_errors();
2937 return null;
2943 function forum_add_new_post($post,&$message) {
2945 global $USER, $CFG;
2947 $post->created = $post->modified = time();
2948 $post->mailed = "0";
2949 $post->userid = $USER->id;
2950 $post->attachment = "";
2952 if (! $post->id = insert_record("forum_posts", $post)) {
2953 return false;
2956 if ($post->attachment = forum_add_attachment($post, 'attachment',$message)) {
2957 set_field("forum_posts", "attachment", $post->attachment, "id", $post->id);
2960 // Update discussion modified date
2961 set_field("forum_discussions", "timemodified", $post->modified, "id", $post->discussion);
2962 set_field("forum_discussions", "usermodified", $post->userid, "id", $post->discussion);
2964 if (forum_tp_can_track_forums($post->forum) && forum_tp_is_tracked($post->forum)) {
2965 forum_tp_mark_post_read($post->userid, $post, $post->forum);
2968 return $post->id;
2974 function forum_update_post($post,&$message) {
2976 global $USER, $CFG;
2978 $post->modified = time();
2980 if (!$post->parent) { // Post is a discussion starter - update discussion title too
2981 set_field("forum_discussions", "name", $post->subject, "id", $post->discussion);
2984 if ($newfilename = forum_add_attachment($post, 'attachment',$message)) {
2985 $post->attachment = $newfilename;
2986 } else {
2987 unset($post->attachment);
2990 // Update discussion modified date
2991 set_field("forum_discussions", "timemodified", $post->modified, "id", $post->discussion);
2992 set_field("forum_discussions", "usermodified", $post->userid, "id", $post->discussion);
2994 if (forum_tp_can_track_forums($post->forum) && forum_tp_is_tracked($post->forum)) {
2995 forum_tp_mark_post_read($post->userid, $post, $post->forum);
2998 return update_record("forum_posts", $post);
3002 * Given an object containing all the necessary data,
3003 * create a new discussion and return the id
3005 function forum_add_discussion($discussion,&$message) {
3007 GLOBAL $USER, $CFG;
3009 $timenow = time();
3011 // The first post is stored as a real post, and linked
3012 // to from the discuss entry.
3014 $post->discussion = 0;
3015 $post->parent = 0;
3016 $post->userid = $USER->id;
3017 $post->created = $timenow;
3018 $post->modified = $timenow;
3019 $post->mailed = 0;
3020 $post->subject = $discussion->name;
3021 $post->message = $discussion->intro;
3022 $post->attachment = "";
3023 $post->forum = $discussion->forum;
3024 $post->course = $discussion->course;
3025 $post->format = $discussion->format;
3026 $post->mailnow = $discussion->mailnow;
3028 if (! $post->id = insert_record("forum_posts", $post) ) {
3029 return 0;
3032 if ($post->attachment = forum_add_attachment($post, 'attachment',$message)) {
3033 set_field("forum_posts", "attachment", $post->attachment, "id", $post->id); //ignore errors
3036 // Now do the main entry for the discussion,
3037 // linking to this first post
3039 $discussion->firstpost = $post->id;
3040 $discussion->timemodified = $timenow;
3041 $discussion->usermodified = $post->userid;
3042 $discussion->userid = $USER->id;
3044 if (! $post->discussion = insert_record("forum_discussions", $discussion) ) {
3045 delete_records("forum_posts", "id", $post->id);
3046 return 0;
3049 // Finally, set the pointer on the post.
3050 if (! set_field("forum_posts", "discussion", $post->discussion, "id", $post->id)) {
3051 delete_records("forum_posts", "id", $post->id);
3052 delete_records("forum_discussions", "id", $post->discussion);
3053 return 0;
3056 if (forum_tp_can_track_forums($post->forum) && forum_tp_is_tracked($post->forum)) {
3057 forum_tp_mark_post_read($post->userid, $post, $post->forum);
3060 return $post->discussion;
3067 function forum_delete_discussion($discussion, $fulldelete=false) {
3068 // $discussion is a discussion record object
3070 $result = true;
3072 if ($posts = get_records("forum_posts", "discussion", $discussion->id)) {
3073 foreach ($posts as $post) {
3074 $post->course = $discussion->course;
3075 $post->forum = $discussion->forum;
3076 if (! delete_records("forum_ratings", "post", "$post->id")) {
3077 $result = false;
3079 if (! forum_delete_post($post, $fulldelete)) {
3080 $result = false;
3085 forum_tp_delete_read_records(-1, -1, $discussion->id);
3087 if (! delete_records("forum_discussions", "id", "$discussion->id")) {
3088 $result = false;
3091 return $result;
3098 function forum_delete_post($post, $children=false) {
3099 if ($childposts = get_records('forum_posts', 'parent', $post->id)) {
3100 if ($children) {
3101 foreach ($childposts as $childpost) {
3102 forum_delete_post($childpost, true);
3104 } else {
3105 return false;
3108 if (delete_records("forum_posts", "id", $post->id)) {
3109 delete_records("forum_ratings", "post", $post->id); // Just in case
3111 forum_tp_delete_read_records(-1, $post->id);
3113 if ($post->attachment) {
3114 $discussion = get_record("forum_discussions", "id", $post->discussion);
3115 $post->course = $discussion->course;
3116 $post->forum = $discussion->forum;
3117 forum_delete_old_attachments($post);
3120 // Just in case we are deleting the last post
3121 forum_discussion_update_last_post($post->discussion);
3123 return true;
3125 return false;
3131 function forum_count_replies($post, $children=true) {
3132 $count = 0;
3134 if ($children) {
3135 if ($childposts = get_records('forum_posts', 'parent', $post->id)) {
3136 foreach ($childposts as $childpost) {
3137 $count ++; // For this child
3138 $count += forum_count_replies($childpost, true);
3141 } else {
3142 $count += count_records('forum_posts', 'parent', $post->id);
3145 return $count;
3152 function forum_forcesubscribe($forumid, $value=1) {
3153 return set_field("forum", "forcesubscribe", $value, "id", $forumid);
3159 function forum_is_forcesubscribed($forum) {
3160 if (isset($forum->forcesubscribe)) { // then we use that
3161 return ($forum->forcesubscribe == FORUM_FORCESUBSCRIBE);
3162 } else { // Check the database
3163 return (get_field('forum', 'forcesubscribe', 'id', $forum) == FORUM_FORCESUBSCRIBE);
3170 function forum_is_subscribed($userid, $forumid) {
3171 if (forum_is_forcesubscribed($forumid)) {
3172 return true;
3174 return record_exists("forum_subscriptions", "userid", $userid, "forum", $forumid);
3178 * Adds user to the subscriber list
3180 function forum_subscribe($userid, $forumid) {
3182 if (record_exists("forum_subscriptions", "userid", $userid, "forum", $forumid)) {
3183 return true;
3186 $sub->userid = $userid;
3187 $sub->forum = $forumid;
3189 return insert_record("forum_subscriptions", $sub);
3193 * Removes user from the subscriber list
3195 function forum_unsubscribe($userid, $forumid) {
3196 return delete_records("forum_subscriptions", "userid", $userid, "forum", $forumid);
3200 * Given a new post, subscribes or unsubscribes as appropriate.
3201 * Returns some text which describes what happened.
3203 function forum_post_subscription($post) {
3205 global $USER;
3207 $subscribed=forum_is_subscribed($USER->id, $post->forum);
3208 if ((isset($post->subscribe) && $post->subscribe && $subscribed)
3209 || (!$post->subscribe && !$subscribed)) {
3210 return "";
3213 if (!$forum = get_record("forum", "id", $post->forum)) {
3214 return "";
3217 $info->name = fullname($USER);
3218 $info->forum = $forum->name;
3220 if (!empty($post->subscribe)) {
3221 forum_subscribe($USER->id, $post->forum);
3222 return "<p>".get_string("nowsubscribed", "forum", $info)."</p>";
3225 forum_unsubscribe($USER->id, $post->forum);
3226 return "<p>".get_string("nownotsubscribed", "forum", $info)."</p>";
3230 * Generate and return the subscribe or unsubscribe link for a forum.
3231 * @param object $forum the forum. Fields used are $forum->id and $forum->forcesubscribe.
3232 * @param object $context the context object for this forum.
3233 * @param array $messages text used for the link in its various states
3234 * (subscribed, unsubscribed, forcesubscribed or cantsubscribe).
3235 * Any strings not passed in are taken from the $defaultmessages array
3236 * at the top of the function.
3237 * @param
3239 function forum_get_subscribe_link($forum, $context, $messages = array(), $cantaccessagroup = false) {
3240 global $CFG, $USER;
3241 $defaultmessages = array(
3242 'subscribed' => get_string('unsubscribe', 'forum'),
3243 'unsubscribed' => get_string('subscribe', 'forum'),
3244 'cantaccessgroup' => get_string('no'),
3245 'forcesubscribed' => get_string('everyoneissubscribed', 'forum'),
3246 'cantsubscribe' => get_string('disallowsubscribe','forum')
3248 $messages = $messages + $defaultmessages;
3250 if (forum_is_forcesubscribed($forum->id)) {
3251 return $messages['forcesubscribed'];
3252 } else if ($forum->forcesubscribe == FORUM_DISALLOWSUBSCRIBE && !has_capability('mod/forum:managesubscriptions', $context)) {
3253 return $messages['cantsubscribe'];
3254 } else if ($cantaccessagroup) {
3255 return $messages['cantaccessgroup'];
3256 } else {
3257 if (forum_is_subscribed($USER->id, $forum->id)) {
3258 $linktext = $messages['subscribed'];
3259 $linktitle = get_string('subscribestop', 'forum');
3260 } else {
3261 $linktext = $messages['unsubscribed'];
3262 $linktitle = get_string('subscribestart', 'forum');
3265 $link = '<script type="text/javascript">';
3266 $link .= 'document.getElementById("subscriptionlink").innerHTML = "<a title=\"' . $linktitle . '\" href=\"' . $CFG->wwwroot .
3267 '/mod/forum/subscribe.php?id=' . $forum->id . '\">' . $linktext . '</a>";';
3268 $link .= '</script>';
3269 // use <noscript> to print button in case javascript is not enabled
3270 $link .= '<noscript>';
3271 $link .= print_single_button($CFG->wwwroot . '/mod/forum/subscribe.php?id=' . $forum->id,
3272 '', $linktext, 'post', '_self', true, $linktitle);
3273 $link .= '</noscript>';
3275 return $link;
3282 function forum_user_has_posted_discussion($forumid, $userid) {
3283 if ($discussions = forum_get_discussions($forumid, '', $userid)) {
3284 return true;
3285 } else {
3286 return false;
3293 function forum_discussions_user_has_posted_in($forumid, $userid) {
3294 global $CFG;
3296 $haspostedsql = "SELECT d.id AS id,
3298 FROM {$CFG->prefix}forum_posts p,
3299 {$CFG->prefix}forum_discussions d
3300 WHERE p.discussion = d.id
3301 AND d.forum = $forumid
3302 AND p.userid = $userid";
3304 return get_records_sql($haspostedsql);
3310 function forum_user_has_posted($forumid, $did, $userid) {
3311 return record_exists('forum_posts','discussion',$did,'userid',$userid);
3317 function forum_user_can_post_discussion($forum, $currentgroup=-1, $groupmode=-1, $cm=NULL, $context=NULL) {
3318 // $forum is an object
3319 global $USER, $SESSION;
3321 if (!$cm) {
3322 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3323 error('Course Module ID was incorrect');
3326 if (!$context) {
3327 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
3330 if ($currentgroup == -1) {
3331 $currentgroup = get_current_group($cm->course);
3334 if ($groupmode == -1) {
3335 if (!$course = get_record('course', 'id', $cm->course)) {
3336 error('Can not find course');
3338 $groupmode = groups_get_activity_groupmode($cm);
3341 if ($forum->type == 'news') {
3342 $capname = 'mod/forum:addnews';
3343 } else {
3344 $capname = 'mod/forum:startdiscussion';
3347 if (!has_capability($capname, $context)) {
3348 return false;
3351 if ($forum->type == 'eachuser') {
3352 if (forum_user_has_posted_discussion($forum->id, $USER->id)) {
3353 return false;
3357 if (!$groupmode or has_capability('moodle/site:accessallgroups', $context)) {
3358 return true;
3361 if ($currentgroup) {
3362 return groups_is_member($currentgroup);
3363 } else {
3364 //else it might be group 0 in visible mode
3365 if ($groupmode == VISIBLEGROUPS){
3366 return true;
3367 } else {
3368 return false;
3374 * This function checks whether the user can reply to posts in a forum
3375 * discussion. Use forum_user_can_post_discussion() to check whether the user
3376 * can start dicussions.
3377 * @param $forum - forum object
3378 * @param $user - user object
3380 function forum_user_can_post($forum, $user=NULL, $cm=NULL, $context=NULL) {
3382 if (!$cm) {
3383 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3384 error('Course Module ID was incorrect');
3387 if (!$context) {
3388 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
3391 if ($forum->type == 'news') {
3392 $capname = 'mod/forum:replynews';
3393 } else {
3394 $capname = 'mod/forum:replypost';
3397 if (!empty($user)) {
3398 $canreply = has_capability($capname, $context, $user->id, false)
3399 && !has_capability('moodle/legacy:guest', $context, $user->id, false);
3400 } else {
3401 $canreply = has_capability($capname, $context, NULL, false)
3402 && !has_capability('moodle/legacy:guest', $context, NULL, false);
3405 return $canreply;
3409 //checks to see if a user can view a particular post
3410 function forum_user_can_view_post($post, $course, $cm, $forum, $discussion, $user=NULL){
3412 global $CFG, $USER;
3414 if (!$user){
3415 $user = $USER;
3418 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3419 if (!has_capability('mod/forum:viewdiscussion', $modcontext)) {
3420 return false;
3423 // If it's a grouped discussion, make sure the user is a member
3424 if ($discussion->groupid > 0) {
3425 $groupmode = groups_get_activity_groupmode($cm);
3426 if ($groupmode == SEPARATEGROUPS) {
3427 return groups_is_member($discussion->groupid) || has_capability('moodle/site:accessallgroups', $modcontext);
3430 return true;
3437 function forum_user_can_see_discussion($forum, $discussion, $context, $user=NULL) {
3438 global $USER;
3440 if (empty($user) || empty($user->id)) {
3441 $user = $USER;
3444 // retrieve objects (yuk)
3445 if (is_numeric($forum)) {
3446 if (!$forum = get_record('forum','id',$forum)) {
3447 return false;
3450 if (is_numeric($discussion)) {
3451 if (!$discussion = get_record('forum_discussions','id',$discussion)) {
3452 return false;
3456 if (!has_capability('mod/forum:viewdiscussion', $context)) {
3457 return false;
3460 if ($forum->type == 'qanda' &&
3461 !forum_user_has_posted($forum->id, $discussion->id, $user->id) &&
3462 !has_capability('mod/forum:viewqandawithoutposting', $context)) {
3463 return false;
3465 return true;
3472 function forum_user_can_see_post($forum, $discussion, $post, $user=NULL) {
3473 global $USER;
3475 // retrieve objects (yuk)
3476 if (is_numeric($forum)) {
3477 if (!$forum = get_record('forum','id',$forum)) {
3478 return false;
3482 if (is_numeric($discussion)) {
3483 if (!$discussion = get_record('forum_discussions','id',$discussion)) {
3484 return false;
3487 if (is_numeric($post)) {
3488 if (!$post = get_record('forum_posts','id',$post)) {
3489 return false;
3492 if (!isset($post->id) && isset($post->parent)) {
3493 $post->id = $post->parent;
3496 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3497 error('Course Module ID was incorrect');
3499 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
3501 if (empty($user) || empty($user->id)) {
3502 $user = $USER;
3505 if (!has_capability('mod/forum:viewdiscussion', $context, $user->id)) {
3506 return false;
3509 if (!groups_course_module_visible($cm, $user->id)) {
3510 return false;
3513 if ($forum->type == 'qanda') {
3514 $firstpost = forum_get_firstpost_from_discussion($discussion->id);
3516 return (forum_user_has_posted($forum->id,$discussion->id,$user->id) ||
3517 $firstpost->id == $post->id ||
3518 has_capability('mod/forum:viewqandawithoutposting', $context, false, $user->id));
3520 return true;
3525 * Prints the discussion view screen for a forum.
3527 * @param object $course The current course object.
3528 * @param object $forum Forum to be printed.
3529 * @param int $maxdiscussions The maximum number of discussions per page(optional).
3530 * @param string $displayformat The display format to use (optional).
3531 * @param string $sort Sort arguments for database query (optional).
3532 * @param int $currentgroup Group to display discussions for (optional).
3533 * @param int $groupmode Group mode of the forum (optional).
3534 * @param int $page Page mode, page to display (optional).
3537 function forum_print_latest_discussions($course, $forum, $maxdiscussions=5, $displayformat='plain', $sort='',
3538 $currentgroup=-1, $groupmode=-1, $page=-1) {
3539 global $CFG, $USER;
3541 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3542 error('Course Module ID was incorrect');
3544 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
3547 // Sort out some defaults
3549 if ((!$maxdiscussions) && ($displayformat == 'plain')) {
3550 $displayformat = 'header'; // Abbreviate display by default
3553 $fullpost = false;
3554 if ($displayformat == 'plain') {
3555 $fullpost = true;
3559 // Decide if current user is allowed to see ALL the current discussions or not
3561 // First check the group stuff
3562 $groupmode = groups_get_activity_groupmode($cm);
3563 $currentgroup = groups_get_activity_group($cm);
3565 // If the user can post discussions, then this is a good place to put the
3566 // button for it. We do not show the button if we are showing site news
3567 // and the current user is a guest.
3569 if (forum_user_can_post_discussion($forum, $currentgroup, $groupmode, $cm, $context) ||
3570 ($forum->type != 'news' && has_capability('moodle/legacy:guest', $context, NULL, false)) ) {
3572 echo '<div class="singlebutton forumaddnew">';
3573 echo "<form id=\"newdiscussionform\" method=\"get\" action=\"$CFG->wwwroot/mod/forum/post.php\">";
3574 echo '<div>';
3575 echo "<input type=\"hidden\" name=\"forum\" value=\"$forum->id\" />";
3576 echo '<input type="submit" value="';
3577 echo ($forum->type == 'news') ? get_string('addanewtopic', 'forum')
3578 : (($forum->type == 'qanda')
3579 ? get_string('addanewquestion','forum')
3580 : get_string('addanewdiscussion', 'forum'));
3581 echo '" />';
3582 echo '</div>';
3583 echo '</form>';
3584 echo "</div>\n";
3585 } else if (!isguestuser() and isloggedin() and $forum->type != 'news' and $groupmode == SEPARATEGROUPS and !groups_is_member($currentgroup)) {
3586 notify(get_string('cannotadddiscussion', 'forum'));
3590 // Get all the recent discussions we're allowed to see
3592 $getuserlastmodified = ($displayformat == 'header');
3594 if (! $discussions = forum_get_discussions($forum->id, $sort, 0, $fullpost, $currentgroup,0,$getuserlastmodified) ) {
3595 echo '<div class="forumnodiscuss">';
3596 if ($forum->type == 'news') {
3597 echo '('.get_string('nonews', 'forum').')';
3598 } else if ($forum->type == 'qanda') {
3599 echo '('.get_string('noquestions','forum').')';
3600 } else {
3601 echo '('.get_string('nodiscussions', 'forum').')';
3603 echo "</div>\n";
3604 return;
3607 // If no discussions then don't use paging (to avoid some divide by 0 errors)
3609 if ($maxdiscussions <= 0) {
3610 $page = -1;
3611 $maxdiscussions = 0;
3614 // If we want paging
3616 if ($page != -1) {
3617 ///Get the number of discussions found
3618 $numdiscussions = count($discussions);
3620 ///Show the paging bar
3621 print_paging_bar($numdiscussions, $page, $maxdiscussions, "view.php?f=$forum->id&amp;");
3623 //Calculate the page "window"
3624 $pagestart = ($page * $maxdiscussions) + 1;
3625 $pageend = $pagestart + $maxdiscussions - 1;
3629 $replies = forum_count_discussion_replies($forum->id);
3631 $canreply = forum_user_can_post($forum);
3632 $canviewparticipants = has_capability('moodle/course:viewparticipants',$context);
3634 $discussioncount = 0;
3635 $olddiscussionlink = false;
3636 $strdatestring = get_string('strftimerecentfull');
3638 // Check if the forum is tracked.
3639 if ($cantrack = forum_tp_can_track_forums($forum)) {
3640 $forumtracked = forum_tp_is_tracked($forum);
3641 } else {
3642 $forumtracked = false;
3645 if ($displayformat == 'header') {
3646 echo '<table cellspacing="0" class="forumheaderlist">';
3647 echo '<thead>';
3648 echo '<tr>';
3649 echo '<th class="header topic" scope="col">'.get_string('discussion', 'forum').'</th>';
3650 echo '<th class="header author" colspan="2" scope="col">'.get_string('startedby', 'forum').'</th>';
3651 if ($groupmode > 0) {
3652 echo '<th class="header group" scope="col">'.get_string('group').'</th>';
3654 if (has_capability('mod/forum:viewdiscussion', $context)) {
3655 echo '<th class="header replies" scope="col">'.get_string('replies', 'forum').'</th>';
3656 // If the forum can be tracked, display the unread column.
3657 if ($cantrack) {
3658 echo '<th class="header replies" scope="col">'.get_string('unread', 'forum');
3659 if ($forumtracked) {
3660 echo '&nbsp;<a title="'.get_string('markallread', 'forum').
3661 '" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
3662 $forum->id.'&amp;mark=read&amp;returnpage=view.php">'.
3663 '<img src="'.$CFG->pixpath.'/t/clear.gif" class="iconsmall" alt="'.get_string('markallread', 'forum').'" /></a>';
3665 echo '</th>';
3668 echo '<th class="header lastpost" scope="col">'.get_string('lastpost', 'forum').'</th>';
3669 echo '</tr>';
3670 echo '</thead>';
3671 echo '<tbody>';
3674 foreach ($discussions as $discussion) {
3675 $discussioncount++;
3677 if ($page != -1) { // We are using paging
3678 if ($discussioncount < $pagestart) { // Not there yet
3679 continue;
3681 if ($discussioncount > $pageend) { // All done, finish the loop
3682 break;
3684 //Without paging, old approach
3685 } else if ($maxdiscussions && ($discussioncount > $maxdiscussions)) {
3686 $olddiscussionlink = true;
3687 break;
3690 if (!empty($replies[$discussion->discussion])) {
3691 $discussion->replies = $replies[$discussion->discussion]->replies;
3692 $discussion->lastpostid = $replies[$discussion->discussion]->lastpostid;
3693 } else {
3694 $discussion->replies = 0;
3697 // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3698 // All posts are read in this case.
3699 if (!$forumtracked) {
3700 $discussion->unread = '-';
3701 } else if (empty($USER)) {
3702 $discussion->unread = 0;
3703 } else {
3704 $discussion->unread = forum_tp_count_discussion_unread_posts($USER->id, $discussion->discussion);
3707 if (!empty($USER->id)) {
3708 $ownpost = ($discussion->userid == $USER->id);
3709 } else {
3710 $ownpost=false;
3712 // Use discussion name instead of subject of first post
3713 $discussion->subject = $discussion->name;
3715 switch ($displayformat) {
3716 case 'header':
3717 if ($groupmode > 0) {
3718 if (isset($groups[$discussion->groupid])) {
3719 $group = $groups[$discussion->groupid];
3720 } else {
3721 $group = $groups[$discussion->groupid] = groups_get_group($discussion->groupid); //TODO:
3723 } else {
3724 $group = -1;
3726 forum_print_discussion_header($discussion, $forum, $group, $strdatestring, $cantrack, $forumtracked,
3727 $canviewparticipants);
3728 break;
3729 default:
3730 if ($canreply or $discussion->replies) {
3731 $link = true;
3732 } else {
3733 $link = false;
3736 $discussion->forum = $forum->id;
3738 forum_print_post($discussion, $course->id, $ownpost, $reply=0, $link, $assessed=false);
3739 break;
3743 if ($displayformat == "header") {
3744 echo '</tbody>';
3745 echo '</table>';
3748 if ($olddiscussionlink) {
3749 echo '<div class="forumolddiscuss">';
3750 echo '<a href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'&amp;showall=1">';
3751 echo get_string('olderdiscussions', 'forum').'</a> ...</div>';
3754 if ($page != -1) { ///Show the paging bar
3755 print_paging_bar($numdiscussions, $page, $maxdiscussions, "view.php?f=$forum->id&amp;");
3763 function forum_print_discussion($course, $forum, $discussion, $post, $mode, $canreply=NULL, $canrate=false) {
3765 global $USER, $CFG;
3767 if (!empty($USER->id)) {
3768 $ownpost = ($USER->id == $post->userid);
3769 } else {
3770 $ownpost = false;
3772 if ($canreply === NULL) {
3773 $reply = forum_user_can_post($forum);
3774 } else {
3775 $reply = $canreply;
3778 $ratings = NULL;
3779 $ratingsmenuused = false;
3780 $ratingsformused = false;
3781 if ($forum->assessed and !empty($USER->id)) {
3782 if ($ratings->scale = make_grades_menu($forum->scale)) {
3783 $ratings->assesstimestart = $forum->assesstimestart;
3784 $ratings->assesstimefinish = $forum->assesstimefinish;
3785 $ratings->allow = $canrate;
3787 if ($ratings->allow) {
3788 echo '<form id="form" method="post" action="rate.php">';
3789 echo '<div class="ratingform">';
3790 echo '<input type="hidden" name="forumid" value="'.$forum->id.'" />';
3791 $ratingsformused = true;
3796 $post->forum = $forum->id; // Add the forum id to the post object, later used by forum_print_post
3797 $post->forumtype = $forum->type;
3799 $post->subject = format_string($post->subject);
3801 if (forum_tp_can_track_forums($forum)) {
3802 if ($forumtracked = forum_tp_is_tracked($forum)) {
3803 $user_read_array = forum_tp_get_discussion_read_records($USER->id, $post->discussion);
3804 } else {
3805 $user_read_array = array();
3807 } else {
3808 $forumtracked = false;
3809 $user_read_array = array();
3812 if (forum_print_post($post, $course->id, $ownpost, $reply, $link=false, $ratings,
3813 '', '', (!$forumtracked || isset($user_read_array[$post->id]) || forum_tp_is_post_old($post)))) {
3814 $ratingsmenuused = true;
3817 switch ($mode) {
3818 case FORUM_MODE_FLATOLDEST :
3819 case FORUM_MODE_FLATNEWEST :
3820 default:
3821 if (forum_print_posts_flat($post->discussion, $course->id, $mode, $ratings, $reply,
3822 $user_read_array, $post->forum)) {
3823 $ratingsmenuused = true;
3825 break;
3827 case FORUM_MODE_THREADED :
3828 if (forum_print_posts_threaded($post->id, $course->id, 0, $ratings, $reply,
3829 $user_read_array, $post->forum)) {
3830 $ratingsmenuused = true;
3832 break;
3834 case FORUM_MODE_NESTED :
3835 if (forum_print_posts_nested($post->id, $course->id, $ratings, $reply,
3836 $user_read_array, $post->forum)) {
3837 $ratingsmenuused = true;
3839 break;
3842 if ($ratingsformused) {
3843 if ($ratingsmenuused) {
3844 echo '<div class="ratingsubmit">';
3845 echo '<input type="submit" value="'.get_string('sendinratings', 'forum').'" />';
3846 if ($forum->scale < 0) {
3847 if ($scale = get_record("scale", "id", abs($forum->scale))) {
3848 print_scale_menu_helpbutton($course->id, $scale );
3851 echo '</div>';
3854 echo '</div>';
3855 echo '</form>';
3863 function forum_print_posts_flat($discussion, $courseid, $direction, $ratings, $reply, &$user_read_array, $forumid=0) {
3864 global $USER, $CFG;
3866 $link = false;
3867 $ratingsmenuused = false;
3869 if ($direction < 0) {
3870 $sort = "ORDER BY created DESC";
3871 } else {
3872 $sort = "ORDER BY created ASC";
3875 if ($posts = forum_get_discussion_posts($discussion, $sort, $forumid)) {
3876 foreach ($posts as $post) {
3878 $post->subject = format_string($post->subject);
3880 $ownpost = ($USER->id == $post->userid);
3881 if (forum_print_post($post, $courseid, $ownpost, $reply, $link, $ratings,
3882 '', '', (isset($user_read_array[$post->id]) || forum_tp_is_post_old($post)))) {
3883 $ratingsmenuused = true;
3888 return $ratingsmenuused;
3893 * TODO document
3895 function forum_print_posts_threaded($parent, $courseid, $depth, $ratings, $reply, &$user_read_array, $forumid=0) {
3896 global $USER, $CFG;
3898 $link = false;
3899 $ratingsmenuused = false;
3901 $istracking = forum_tp_can_track_forums($forumid) && forum_tp_is_tracked($forumid);
3903 if ($posts = forum_get_child_posts($parent, $forumid)) {
3905 if (!$cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
3906 error('Course Module ID was incorrect');
3908 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3909 $canviewfullnames = has_capability('moodle/site:viewfullnames', $modcontext);
3911 foreach ($posts as $post) {
3913 echo '<div class="indent">';
3914 if ($depth > 0) {
3915 $ownpost = ($USER->id == $post->userid);
3917 $post->subject = format_string($post->subject);
3919 if (forum_print_post($post, $courseid, $ownpost, $reply, $link, $ratings,
3920 '', '', (isset($user_read_array[$post->id]) || forum_tp_is_post_old($post)))) {
3921 $ratingsmenuused = true;
3923 } else {
3924 if (!forum_user_can_see_post($post->forum,$post->discussion,$post)) {
3925 continue;
3927 $by->name = fullname($post, $canviewfullnames);
3928 $by->date = userdate($post->modified);
3930 if ($istracking) {
3931 if (isset($user_read_array[$post->id]) || forum_tp_is_post_old($post)) {
3932 $style = '<span class="forumthread read">';
3933 } else {
3934 $style = '<span class="forumthread unread">';
3936 } else {
3937 $style = '<span class="forumthread">';
3939 echo $style."<a name=\"$post->id\"></a>".
3940 "<a href=\"discuss.php?d=$post->discussion&amp;parent=$post->id\">".format_string($post->subject,true)."</a> ";
3941 print_string("bynameondate", "forum", $by);
3942 echo "</span>";
3945 if (forum_print_posts_threaded($post->id, $courseid, $depth-1, $ratings, $reply,
3946 $user_read_array, $forumid)) {
3947 $ratingsmenuused = true;
3949 echo "</div>\n";
3952 return $ratingsmenuused;
3958 function forum_print_posts_nested($parent, $courseid, $ratings, $reply, &$user_read_array, $forumid=0) {
3959 global $USER, $CFG;
3961 $link = false;
3962 $ratingsmenuused = false;
3964 if ($posts = forum_get_child_posts($parent, $forumid)) {
3965 foreach ($posts as $post) {
3967 echo '<div class="indent">';
3968 if (empty($USER->id)) {
3969 $ownpost = false;
3970 } else {
3971 $ownpost = ($USER->id == $post->userid);
3974 $post->subject = format_string($post->subject);
3976 if (forum_print_post($post, $courseid, $ownpost, $reply, $link, $ratings,
3977 '', '', (isset($user_read_array[$post->id]) || forum_tp_is_post_old($post)))) {
3978 $ratingsmenuused = true;
3980 if (forum_print_posts_nested($post->id, $courseid, $ratings, $reply, $user_read_array, $forumid)) {
3981 $ratingsmenuused = true;
3983 echo "</div>\n";
3986 return $ratingsmenuused;
3990 * TODO document
3992 function forum_get_recent_mod_activity(&$activities, &$index, $sincetime, $courseid, $cmid="0", $user="", $groupid="") {
3993 // Returns all forum posts since a given time. If forum is specified then
3994 // this restricts the results
3996 global $CFG;
3998 if ($cmid) {
3999 $forumselect = " AND cm.id = '$cmid'";
4000 } else {
4001 $forumselect = "";
4004 if ($user) {
4005 $userselect = " AND u.id = '$user'";
4006 } else {
4007 $userselect = "";
4010 $posts = get_records_sql("SELECT p.*, d.name, u.firstname, u.lastname,
4011 u.picture, d.groupid, cm.instance, f.name,
4012 cm.section, cm.id AS cmid
4013 FROM {$CFG->prefix}forum_posts p,
4014 {$CFG->prefix}forum_discussions d,
4015 {$CFG->prefix}user u,
4016 {$CFG->prefix}course_modules cm,
4017 {$CFG->prefix}forum f
4018 WHERE p.modified > '$sincetime' $forumselect
4019 AND p.userid = u.id $userselect
4020 AND d.course = '$courseid'
4021 AND p.discussion = d.id
4022 AND cm.instance = f.id
4023 AND cm.course = d.course
4024 AND cm.course = f.course
4025 AND f.id = d.forum
4026 ORDER BY p.discussion ASC,p.created ASC");
4028 if (empty($posts)) {
4029 return;
4032 foreach ($posts as $post) {
4034 $modcontext = get_context_instance(CONTEXT_MODULE, $post->cmid);
4035 $canviewallgroups = has_capability('moodle/site:accessallgroups', $modcontext);
4037 if ($groupid and ($post->groupid != -1 and $groupid != $post->groupid and !$canviewallgroups)) {
4038 continue;
4040 if (!groups_course_module_visible($post->cmid)) {
4041 continue;
4044 $tmpactivity = new Object;
4046 $tmpactivity->type = "forum";
4047 $tmpactivity->defaultindex = $index;
4048 $tmpactivity->instance = $post->instance;
4049 $tmpactivity->name = $post->name;
4050 $tmpactivity->section = $post->section;
4052 $tmpactivity->content->id = $post->id;
4053 $tmpactivity->content->discussion = $post->discussion;
4054 $tmpactivity->content->subject = $post->subject;
4055 $tmpactivity->content->parent = $post->parent;
4057 $tmpactivity->user->userid = $post->userid;
4058 $tmpactivity->user->fullname = fullname($post);
4059 $tmpactivity->user->picture = $post->picture;
4061 $tmpactivity->timestamp = $post->modified;
4062 $activities[] = $tmpactivity;
4064 $index++;
4067 return;
4073 function forum_print_recent_mod_activity($activity, $course, $detail=false) {
4075 global $CFG;
4077 echo '<table border="0" cellpadding="3" cellspacing="0">';
4079 if ($activity->content->parent) {
4080 $openformat = "<font size=\"2\"><i>";
4081 $closeformat = "</i></font>";
4082 } else {
4083 $openformat = "<b>";
4084 $closeformat = "</b>";
4087 echo "<tr><td class=\"forumpostpicture\" width=\"35\" valign=\"top\">";
4088 print_user_picture($activity->user->userid, $course, $activity->user->picture);
4089 echo "</td><td>$openformat";
4091 if ($detail) {
4092 echo "<img src=\"$CFG->modpixpath/$activity->type/icon.gif\" ".
4093 "class=\"icon\" alt=\"".strip_tags(format_string($activity->name,true))."\" /> ";
4095 echo "<a href=\"$CFG->wwwroot/mod/forum/discuss.php?d=" . $activity->content->discussion
4096 . "#p" . $activity->content->id . "\">";
4098 echo format_string($activity->content->subject,true);
4099 echo "</a>$closeformat";
4101 echo "<br /><font size=\"2\">";
4102 echo "<a href=\"$CFG->wwwroot/user/view.php?id=" . $activity->user->userid . "&amp;course=" . "$course\">"
4103 . $activity->user->fullname . "</a>";
4104 echo " - " . userdate($activity->timestamp) . "</font></td></tr>";
4105 echo "</table>";
4107 return;
4111 * recursively sets the discussion field to $discussionid on $postid and all its children
4112 * used when pruning a post
4114 function forum_change_discussionid($postid, $discussionid) {
4115 set_field('forum_posts', 'discussion', $discussionid, 'id', $postid);
4116 if ($posts = get_records('forum_posts', 'parent', $postid)) {
4117 foreach ($posts as $post) {
4118 forum_change_discussionid($post->id, $discussionid);
4121 return true;
4125 * Prints the editing button on subscribers page
4127 function forum_update_subscriptions_button($courseid, $forumid) {
4128 global $CFG, $USER;
4130 if (!empty($USER->subscriptionsediting)) {
4131 $string = get_string('turneditingoff');
4132 $edit = "off";
4133 } else {
4134 $string = get_string('turneditingon');
4135 $edit = "on";
4138 return "<form $CFG->frametarget method=\"get\" action=\"$CFG->wwwroot/mod/forum/subscribers.php\">".
4139 "<input type=\"hidden\" name=\"id\" value=\"$forumid\" />".
4140 "<input type=\"hidden\" name=\"edit\" value=\"$edit\" />".
4141 "<input type=\"submit\" value=\"$string\" /></form>";
4145 * This function gets run whenever a role is assigned to a user in a context
4147 * @param integer $userid
4148 * @param object $context
4149 * @return bool
4151 function forum_role_assign($userid, $context, $roleid) {
4152 // check to see if this role comes with mod/forum:initialsubscriptions
4153 $cap = role_context_capabilities($roleid, $context, 'mod/forum:initialsubscriptions');
4154 $cap1 = role_context_capabilities($roleid, $context, 'moodle/course:view');
4155 // we are checking the role because has_capability() will pull this capability out
4156 // from other roles this user might have and resolve them, which is no good
4157 // the role needs course view to
4158 if (isset($cap['mod/forum:initialsubscriptions']) && $cap['mod/forum:initialsubscriptions'] == CAP_ALLOW &&
4159 isset($cap1['moodle/course:view']) && $cap1['moodle/course:view'] == CAP_ALLOW) {
4160 return forum_add_user_default_subscriptions($userid, $context);
4161 } else {
4162 // MDL-8981, do not subscribe to forum
4163 return true;
4169 * This function gets run whenever a role is assigned to a user in a context
4171 * @param integer $userid
4172 * @param object $context
4173 * @return bool
4175 function forum_role_unassign($userid, $context) {
4176 return forum_remove_user_subscriptions($userid, $context);
4181 * Add subscriptions for new users
4183 function forum_add_user_default_subscriptions($userid, $context) {
4185 if (empty($context->contextlevel)) {
4186 return false;
4189 switch ($context->contextlevel) {
4191 case CONTEXT_SYSTEM: // For the whole site
4192 if ($courses = get_records('course')) {
4193 foreach ($courses as $course) {
4194 $subcontext = get_context_instance(CONTEXT_COURSE, $course->id);
4195 forum_add_user_default_subscriptions($userid, $subcontext);
4198 break;
4200 case CONTEXT_COURSECAT: // For a whole category
4201 if ($courses = get_records('course', 'category', $context->instanceid)) {
4202 foreach ($courses as $course) {
4203 $subcontext = get_context_instance(CONTEXT_COURSE, $course->id);
4204 forum_add_user_default_subscriptions($userid, $subcontext);
4207 if ($categories = get_records('course_categories', 'parent', $context->instanceid)) {
4208 foreach ($categories as $category) {
4209 $subcontext = get_context_instance(CONTEXT_COURSECAT, $category->id);
4210 forum_add_user_default_subscriptions($userid, $subcontext);
4213 break;
4216 case CONTEXT_COURSE: // For a whole course
4217 if ($course = get_record('course', 'id', $context->instanceid)) {
4218 if ($forums = get_all_instances_in_course('forum', $course, $userid, false)) {
4219 foreach ($forums as $forum) {
4220 if ($forum->forcesubscribe != FORUM_INITIALSUBSCRIBE) {
4221 continue;
4223 if ($modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule)) {
4224 if (has_capability('mod/forum:viewdiscussion', $modcontext, $userid)) {
4225 forum_subscribe($userid, $forum->id);
4231 break;
4233 case CONTEXT_MODULE: // Just one forum
4234 if ($cm = get_coursemodule_from_id('forum', $context->instanceid)) {
4235 if ($forum = get_record('forum', 'id', $cm->instance)) {
4236 if ($forum->forcesubscribe != FORUM_INITIALSUBSCRIBE) {
4237 continue;
4239 if (has_capability('mod/forum:viewdiscussion', $context, $userid)) {
4240 forum_subscribe($userid, $forum->id);
4244 break;
4247 return true;
4252 * Remove subscriptions for a user in a context
4254 function forum_remove_user_subscriptions($userid, $context) {
4256 global $CFG;
4258 if (empty($context->contextlevel)) {
4259 return false;
4262 switch ($context->contextlevel) {
4264 case CONTEXT_SYSTEM: // For the whole site
4265 //if ($courses = get_my_courses($userid)) {
4266 // find all courses in which this user has a forum subscription
4267 if ($courses = get_records_sql("SELECT c.*
4268 FROM {$CFG->prefix}course c,
4269 {$CFG->prefix}forum_subscriptions fs,
4270 {$CFG->prefix}forum f
4271 WHERE c.id = f.course
4272 AND f.id = fs.forum
4273 AND fs.userid = $userid")) {
4275 foreach ($courses as $course) {
4276 $subcontext = get_context_instance(CONTEXT_COURSE, $course->id);
4277 forum_remove_user_subscriptions($userid, $subcontext);
4280 break;
4282 case CONTEXT_COURSECAT: // For a whole category
4283 if ($courses = get_records('course', 'category', $context->instanceid)) {
4284 foreach ($courses as $course) {
4285 $subcontext = get_context_instance(CONTEXT_COURSE, $course->id);
4286 forum_remove_user_subscriptions($userid, $subcontext);
4289 if ($categories = get_records('course_categories', 'parent', $context->instanceid)) {
4290 foreach ($categories as $category) {
4291 $subcontext = get_context_instance(CONTEXT_COURSECAT, $category->id);
4292 forum_remove_user_subscriptions($userid, $subcontext);
4295 break;
4297 case CONTEXT_COURSE: // For a whole course
4298 if ($course = get_record('course', 'id', $context->instanceid)) {
4299 // find all forums in which this user has a subscription, and its coursemodule id
4300 if ($forums = get_records_sql("SELECT f.id, cm.id as coursemodule
4301 FROM {$CFG->prefix}forum f,
4302 {$CFG->prefix}modules m,
4303 {$CFG->prefix}course_modules cm,
4304 {$CFG->prefix}forum_subscriptions fs
4305 WHERE fs.userid = $userid
4306 AND fs.forum = f.id
4307 AND f.course = $context->instanceid
4308 AND cm.instance = f.id
4309 AND cm.module = m.id
4310 AND m.name = 'forum'")) {
4312 //if ($forums = get_all_instances_in_course('forum', $course, $userid, true)) {
4313 foreach ($forums as $forum) {
4314 if ($modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule)) {
4315 if (!has_capability('mod/forum:viewdiscussion', $modcontext, $userid)) {
4316 forum_unsubscribe($userid, $forum->id);
4322 break;
4324 case CONTEXT_MODULE: // Just one forum
4325 if ($cm = get_coursemodule_from_id('forum', $context->instanceid)) {
4326 if ($forum = get_record('forum', 'id', $cm->instance)) {
4327 if (!has_capability('mod/forum:viewdiscussion', $context, $userid)) {
4328 forum_unsubscribe($userid, $forum->id);
4332 break;
4335 return true;
4338 // Functions to do with read tracking.
4342 function forum_tp_add_read_record($userid, $postid, $discussionid=-1, $forumid=-1) {
4343 if (($readrecord = forum_tp_get_read_records($userid, $postid)) === false) {
4344 // New read record
4345 unset($readrecord);
4346 $readrecord->userid = $userid;
4347 $readrecord->postid = $postid;
4348 $readrecord->discussionid = $discussionid;
4349 $readrecord->forumid = $forumid;
4350 $readrecord->firstread = time();
4351 $readrecord->lastread = $readrecord->firstread;
4352 return insert_record('forum_read', $readrecord, true);
4354 } else {
4355 // Update read record
4356 $readrecord = reset($readrecord);
4357 $readrecord->lastread = time();
4359 $update = NULL;
4360 $update->id = $readrecord->id;
4361 $update->lastread = $readrecord->lastread;
4363 // This shouldn't happen, but just in case...
4364 if (!$readrecord->firstread) {
4365 // Update the 'firstread' field.
4366 $update->firstread = $readrecord->lastread;
4368 if ($discussionid > -1) {
4369 // Update the 'discussionid' field.
4370 $update->discussionid = $discussionid;
4372 if ($forumid > -1) {
4373 // Update the 'forumid' field.
4374 $update->forumid = $forumid;
4377 return update_record('forum_read', $update);
4382 * Returns all records in the 'forum_read' table matching the passed keys, indexed
4383 * by userid.
4385 function forum_tp_get_read_records($userid=-1, $postid=-1, $discussionid=-1, $forumid=-1) {
4386 $select = '';
4387 if ($userid > -1) {
4388 if ($select != '') $select .= ' AND ';
4389 $select .= 'userid = \''.$userid.'\'';
4391 if ($postid > -1) {
4392 if ($select != '') $select .= ' AND ';
4393 $select .= 'postid = \''.$postid.'\'';
4395 if ($discussionid > -1) {
4396 if ($select != '') $select .= ' AND ';
4397 $select .= 'discussionid = \''.$discussionid.'\'';
4399 if ($forumid > -1) {
4400 if ($select != '') $select .= ' AND ';
4401 $select .= 'forumid = \''.$forumid.'\'';
4404 return get_records_select('forum_read', $select);
4408 * Returns all read records for the provided user and discussion, indexed by postid.
4410 function forum_tp_get_discussion_read_records($userid, $discussionid) {
4411 $select = 'userid = \''.$userid.'\' AND discussionid = \''.$discussionid.'\'';
4412 $fields = 'postid, firstread, lastread';
4413 return get_records_select('forum_read', $select, '', $fields);
4417 * If its an old post, do nothing. If the record exists, the maintenance will clear it up later.
4419 function forum_tp_mark_post_read($userid, &$post, $forumid) {
4420 if (!forum_tp_is_post_old($post)) {
4421 return forum_tp_add_read_record($userid, $post->id, $post->discussion, $forumid);
4422 } else {
4423 return true;
4428 * Marks a whole forum as read, for a given user
4430 function forum_tp_mark_forum_read($userid, $forumid, $groupid=false) {
4431 global $CFG;
4433 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
4435 $groupsel = '';
4436 if ($groupid !== false) {
4437 $groupsel = ' AND (d.groupid = '.$groupid.' OR d.groupid = -1)';
4440 $sql = 'SELECT p.id as postid, d.id as discussionid, d.forum as forumid '.
4441 'FROM '.$CFG->prefix.'forum_posts p '.
4442 'LEFT JOIN '.$CFG->prefix.'forum_discussions d ON p.discussion = d.id '.
4443 'LEFT JOIN '.$CFG->prefix.'forum_read r ON r.postid = p.id AND r.userid = '.$userid.' '.
4444 'WHERE d.forum = '.$forumid.$groupsel.
4445 ' AND p.modified >= '.$cutoffdate.' AND r.id is NULL';
4447 if ($posts = get_records_sql($sql)) {
4448 foreach ($posts as $post) {
4449 forum_tp_add_read_record($userid, $post->postid, $post->discussionid, $post->forumid);
4451 return true;
4456 * Marks a whole discussion as read, for a given user
4458 function forum_tp_mark_discussion_read($userid, $discussionid, $forumid) {
4459 global $CFG;
4461 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
4463 $sql = 'SELECT p.id as postid, p.discussion as discussionid '.
4464 'FROM '.$CFG->prefix.'forum_posts p '.
4465 'LEFT JOIN '.$CFG->prefix.'forum_read r ON r.postid = p.id AND r.userid = '.$userid.' '.
4466 'WHERE p.discussion = '.$discussionid.' '.
4467 'AND p.modified >= '.$cutoffdate.' AND r.id is NULL';
4469 if ($posts = get_records_sql($sql)) {
4470 foreach ($posts as $post) {
4471 forum_tp_add_read_record($userid, $post->postid, $post->discussionid, $forumid);
4473 return true;
4480 function forum_tp_is_post_read($userid, &$post) {
4481 return (forum_tp_is_post_old($post) ||
4482 (get_record('forum_read', 'userid', $userid, 'postid', $post->id) !== false));
4488 function forum_tp_is_post_old(&$post, $time=null) {
4489 global $CFG;
4491 if (is_null($time)) $time = time();
4492 return ($post->modified < ($time - ($CFG->forum_oldpostdays * 24 * 3600)));
4496 * Returns the count of records for the provided user and discussion.
4498 function forum_tp_count_discussion_read_records($userid, $discussionid) {
4499 global $CFG;
4501 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
4503 $sql = 'SELECT COUNT(DISTINCT p.id) '.
4504 'FROM '.$CFG->prefix.'forum_discussions d '.
4505 'LEFT JOIN '.$CFG->prefix.'forum_read r ON d.id = r.discussionid AND r.userid = '.$userid.' '.
4506 'LEFT JOIN '.$CFG->prefix.'forum_posts p ON p.discussion = d.id '.
4507 'AND (p.modified < '.$cutoffdate.' OR p.id = r.postid) '.
4508 'WHERE d.id = '.$discussionid;
4510 return (count_records_sql($sql));
4514 * Returns the count of records for the provided user and discussion.
4516 function forum_tp_count_discussion_unread_posts($userid, $discussionid) {
4517 global $CFG;
4519 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
4521 $sql = 'SELECT COUNT(p.id) '.
4522 'FROM '.$CFG->prefix.'forum_posts p '.
4523 'LEFT JOIN '.$CFG->prefix.'forum_read r ON r.postid = p.id AND r.userid = '.$userid.' '.
4524 'WHERE p.discussion = '.$discussionid.' '.
4525 'AND p.modified >= '.$cutoffdate.' AND r.id is NULL';
4527 return (count_records_sql($sql));
4531 * Returns the count of posts for the provided forum and [optionally] group.
4533 function forum_tp_count_forum_posts($forumid, $groupid=false) {
4534 global $CFG;
4536 $sql = 'SELECT COUNT(*) '.
4537 'FROM '.$CFG->prefix.'forum_posts fp,'.$CFG->prefix.'forum_discussions fd '.
4538 'WHERE fd.forum = '.$forumid.' AND fp.discussion = fd.id';
4539 if ($groupid !== false) {
4540 $sql .= ' AND (fd.groupid = '.$groupid.' OR fd.groupid = -1)';
4542 $count = count_records_sql($sql);
4545 return $count;
4549 * Returns the count of records for the provided user and forum and [optionally] group.
4551 function forum_tp_count_forum_read_records($userid, $forumid, $groupid=false) {
4552 global $CFG;
4554 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
4556 $groupsel = '';
4557 if ($groupid !== false) {
4558 $groupsel = ' AND (d.groupid = '.$groupid.' OR d.groupid = -1)';
4561 if ($CFG->dbfamily === 'postgres' || $CFG->dbfamily === 'mssql' || $CFG->dbfamily === 'oracle') {
4562 // this query takes 20ms, vs several minutes for the one below
4563 $sql = " SELECT COUNT (DISTINCT u.id ) "
4564 . " FROM ( "
4565 . " SELECT p.id "
4566 . " FROM {$CFG->prefix}forum_posts p "
4567 . " JOIN {$CFG->prefix}forum_discussions d ON p.discussion = d.id "
4568 . " JOIN {$CFG->prefix}forum_read r ON p.id = r.postid"
4569 . " WHERE d.forum = $forumid $groupsel "
4570 . " AND r.userid= $userid"
4571 . " UNION"
4572 . " SELECT p.id"
4573 . " FROM {$CFG->prefix}forum_posts p "
4574 . " JOIN {$CFG->prefix}forum_discussions d ON p.discussion = d.id "
4575 . " WHERE d.forum = $forumid $groupsel "
4576 . " AND p.modified < $cutoffdate"
4577 . ") u";
4578 } else { // This is for MySQL. TODO: Check if the above works for MySQL 4.1
4579 $sql = 'SELECT COUNT(DISTINCT p.id) '.
4580 'FROM '.$CFG->prefix.'forum_posts p,'.$CFG->prefix.'forum_read r,'.$CFG->prefix.'forum_discussions d '.
4581 'WHERE d.forum = '.$forumid.$groupsel.' AND p.discussion = d.id AND '.
4582 '((p.id = r.postid AND r.userid = '.$userid.') OR p.modified < '.$cutoffdate.' ) ';
4584 return (count_records_sql($sql));
4588 * Returns the count of records for the provided user and forum and [optionally] group.
4590 function forum_tp_count_forum_unread_posts($userid, $forumid, $groupid=false) {
4591 global $CFG;
4593 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
4595 $groupsel = '';
4596 if ($groupid !== false) {
4597 $groupsel = ' AND (d.groupid = '.$groupid.' OR d.groupid = -1)';
4600 $sql = 'SELECT COUNT(p.id) '.
4601 'FROM '.$CFG->prefix.'forum_posts p '.
4602 'LEFT JOIN '.$CFG->prefix.'forum_discussions d ON p.discussion = d.id '.
4603 'LEFT JOIN '.$CFG->prefix.'forum_read r ON r.postid = p.id AND r.userid = '.$userid.' '.
4604 'WHERE d.forum = '.$forumid.$groupsel.
4605 ' AND p.modified >= '.$cutoffdate.' AND r.id is NULL';
4607 return (count_records_sql($sql));
4611 * Deletes read records for the specified index. At least one parameter must be specified.
4613 function forum_tp_delete_read_records($userid=-1, $postid=-1, $discussionid=-1, $forumid=-1) {
4614 $select = '';
4615 if ($userid > -1) {
4616 if ($select != '') $select .= ' AND ';
4617 $select .= 'userid = \''.$userid.'\'';
4619 if ($postid > -1) {
4620 if ($select != '') $select .= ' AND ';
4621 $select .= 'postid = \''.$postid.'\'';
4623 if ($discussionid > -1) {
4624 if ($select != '') $select .= ' AND ';
4625 $select .= 'discussionid = \''.$discussionid.'\'';
4627 if ($forumid > -1) {
4628 if ($select != '') $select .= ' AND ';
4629 $select .= 'forumid = \''.$forumid.'\'';
4631 if ($select == '') {
4632 return false;
4634 else {
4635 return delete_records_select('forum_read', $select);
4639 * Get a list of forums not tracked by the user.
4641 * @param int $userid The id of the user to use.
4642 * @param int $courseid The id of the course being checked (optional).
4643 * @return mixed An array indexed by forum id, or false.
4645 function forum_tp_get_untracked_forums($userid, $courseid=false) {
4646 global $CFG;
4648 // If a course is specified, get the forums with tracking turned off.
4649 if ($courseid !== false) {
4650 $select = 'course = '.$courseid.' AND trackingtype = '.FORUM_TRACKING_OFF;
4651 $forced = get_records_select('forum', $select, '', 'id,course');
4652 } else {
4653 $forced = false;
4656 // Get the forums that the user has turned off.
4657 $sql = 'SELECT ft.forumid, ft.userid '.
4658 'FROM '.$CFG->prefix.'forum_track_prefs ft, '.$CFG->prefix.'forum f '.
4659 'WHERE ft.userid = '.$userid.' AND f.id = ft.forumid ' .
4660 'AND f.trackingtype != '.FORUM_TRACKING_ON;
4661 $useroff = get_records_sql($sql);
4662 if (!$forced) {
4663 return $useroff;
4664 } else if (!$useroff) {
4665 return $forced;
4666 } else {
4667 return ($useroff + $forced);
4672 * Determine if a user can track forums and optionally a particular forum.
4673 * Checks the site settings, the user settings and the forum settings (if
4674 * requested).
4676 * @param mixed $forum The forum object to test, or the int id (optional).
4677 * @param mixed $userid The user object to check for (optional).
4678 * @return boolean
4680 function forum_tp_can_track_forums($forum=false, $user=false) {
4681 global $USER, $CFG;
4683 // if possible, avoid expensive
4684 // queries
4685 if (empty($CFG->forum_trackreadposts)) {
4686 return false;
4689 if ($user === false) {
4690 // Must be logged in and not a guest.
4691 $isauser = isloggedin() && !isguest();
4692 $user = $USER;
4693 } else {
4694 $isauser = true;
4697 if ($forum === false) {
4698 $forumallows = true;
4699 $forumforced = false;
4700 } else {
4701 // Work toward always passing an object...
4702 if (is_numeric($forum)) {
4703 $forum = get_record('forum', 'id', $forum, '','','','', 'id,trackingtype');
4706 $forumallows = ($forum->trackingtype == FORUM_TRACKING_OPTIONAL);
4707 $forumforced = ($forum->trackingtype == FORUM_TRACKING_ON);
4710 return ($isauser && ($forumforced || ($forumallows && !empty($user->trackforums))));
4714 * Tells whether a specific forum is tracked by the user. A user can optionally
4715 * be specified. If not specified, the current user is assumed.
4717 * @param mixed $forum If int, the id of the forum being checked; if object, the forum object
4718 * @param int $userid The id of the user being checked (optional).
4719 * @return boolean
4721 function forum_tp_is_tracked($forum, $userid=false) {
4722 global $USER, $CFG;
4724 if ($userid === false) {
4725 if (empty($USER->id)) {
4726 return false;
4728 $userid = $USER->id;
4731 // Work toward always passing an object...
4732 if (is_numeric($forum)) {
4733 $forum = get_record('forum', 'id', $forum);
4736 return (($forum->trackingtype == FORUM_TRACKING_ON) ||
4737 ($forum->trackingtype == FORUM_TRACKING_OPTIONAL &&
4738 get_record('forum_track_prefs', 'userid', $userid, 'forumid', $forum->id) === false));
4744 function forum_tp_start_tracking($forumid, $userid=false) {
4745 global $USER;
4747 if ($userid === false) {
4748 $userid = $USER->id;
4751 return delete_records('forum_track_prefs', 'userid', $userid, 'forumid', $forumid);
4757 function forum_tp_stop_tracking($forumid, $userid=false) {
4758 global $USER;
4760 if ($userid === false) {
4761 $userid = $USER->id;
4764 $track_prefs = new stdClass;
4765 $track_prefs->userid = $userid;
4766 $track_prefs->forumid = $forumid;
4767 if (insert_record('forum_track_prefs', $track_prefs)) {
4768 return forum_tp_delete_read_records($userid, -1, -1, $forumid);
4769 } else {
4770 return false;
4775 /**
4776 * Clean old records from the forum_read table.
4778 function forum_tp_clean_read_records() {
4779 global $CFG;
4781 // Look for records older than the cutoffdate that are still in the forum_read table.
4782 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
4783 $sql = 'SELECT fr.id, fr.userid, fr.postid '.
4784 'FROM '.$CFG->prefix.'forum_posts fp, '.$CFG->prefix.'forum_read fr '.
4785 'WHERE fp.modified < '.$cutoffdate.' AND fp.id = fr.postid';
4786 if (($oldreadposts = get_records_sql($sql))) {
4787 foreach($oldreadposts as $oldreadpost) {
4788 delete_records('forum_read', 'id', $oldreadpost->id);
4794 * Sets the last post for a given discussion
4796 function forum_discussion_update_last_post($discussionid) {
4797 global $CFG, $db;
4799 // Check the given discussion exists
4800 if (!record_exists('forum_discussions', 'id', $discussionid)) {
4801 return false;
4804 // Use SQL to find the last post for this discussion
4805 $sql = 'SELECT id, userid, modified '.
4806 'FROM '.$CFG->prefix.'forum_posts '.
4807 'WHERE discussion='.$discussionid.' '.
4808 'ORDER BY modified DESC ';
4810 // Lets go find the last post
4811 if (($lastpost = get_record_sql($sql, true))) {
4812 $discussionobject = new Object;
4813 $discussionobject->id = $discussionid;
4814 $discussionobject->usermodified = $lastpost->userid;
4815 $discussionobject->timemodified = $lastpost->modified;
4816 if (update_record('forum_discussions', $discussionobject)) {
4817 return $lastpost->id;
4821 // To get here either we couldn't find a post for the discussion (weird)
4822 // or we couldn't update the discussion record (weird x2)
4823 return false;
4830 function forum_get_view_actions() {
4831 return array('view discussion','search','forum','forums','subscribers');
4837 function forum_get_post_actions() {
4838 return array('add discussion','add post','delete discussion','delete post','move discussion','prune post','update post');
4842 * this function returns all the separate forum ids, given a courseid
4843 * @param int $courseid
4844 * @return array
4846 function forum_get_separate_modules($courseid) {
4848 global $CFG,$db;
4849 $forummodule = get_record("modules", "name", "forum");
4851 $sql = 'SELECT f.id, f.id FROM '.$CFG->prefix.'forum f, '.$CFG->prefix.'course_modules cm WHERE
4852 f.id = cm.instance AND cm.module ='.$forummodule->id.' AND cm.visible = 1 AND cm.course = '.$courseid.'
4853 AND cm.groupmode ='.SEPARATEGROUPS;
4855 return get_records_sql($sql);
4862 function forum_check_throttling($forum) {
4863 global $USER, $CFG;
4865 if (is_numeric($forum)) {
4866 $forum = get_record('forum','id',$forum);
4868 if (!is_object($forum)) {
4869 return false; // this is broken.
4872 if (empty($forum->blockafter)) {
4873 return true;
4876 if (empty($forum->blockperiod)) {
4877 return true;
4880 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
4881 error('Course Module ID was incorrect');
4883 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
4884 if(!has_capability('mod/forum:throttlingapplies', $modcontext)) {
4885 return true;
4888 // get the number of posts in the last period we care about
4889 $timenow = time();
4890 $timeafter = $timenow - $forum->blockperiod;
4892 $numposts = count_records_sql('SELECT COUNT(p.id) FROM '.$CFG->prefix.'forum_posts p'
4893 .' JOIN '.$CFG->prefix.'forum_discussions d'
4894 .' ON p.discussion = d.id WHERE d.forum = '.$forum->id
4895 .' AND p.userid = '.$USER->id.' AND p.created > '.$timeafter);
4897 $a->blockafter = $forum->blockafter;
4898 $a->numposts = $numposts;
4899 $a->blockperiod = get_string('secondstotime'.$forum->blockperiod);
4901 if ($forum->blockafter <= $numposts) {
4902 error(get_string('forumblockingtoomanyposts','error',$a),$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id);
4904 if ($forum->warnafter <= $numposts) {
4905 notify(get_string('forumblockingalmosttoomanyposts','forum',$a));
4913 * This function is used by the remove_course_userdata function in moodlelib.
4914 * If this function exists, remove_course_userdata will execute it.
4915 * This function will remove all posts from the specified forum.
4917 function forum_delete_userdata($data, $showfeedback=true) {
4918 global $CFG;
4920 $sql = "DELETE FROM {$CFG->prefix}forum_posts
4921 WHERE discussion IN (
4922 SELECT fd.id FROM {$CFG->prefix}forum_discussions fd, {$CFG->prefix}forum f
4923 WHERE f.course={$data->courseid} AND f.id=fd.forum "; // closing ) added bellow
4925 $strreset = get_string('reset');
4927 if (!empty($data->reset_forum_news)) {
4928 $select = "$sql AND f.type = 'news' )";
4929 if (execute_sql($select, false) and $showfeedback) {
4930 notify($strreset.': '.get_string('namenews','forum'), 'notifysuccess');
4933 if (!empty($data->reset_forum_single)) {
4934 $select = "$sql AND f.type = 'single' ) AND parent <> 0";
4935 if (execute_sql($select, false) and $showfeedback) {
4936 notify($strreset.': '.get_string('singleforum','forum'), 'notifysuccess');
4939 if (!empty($data->reset_forum_eachuser)) {
4940 $select = "$sql AND f.type = 'eachuser' )";
4941 if (execute_sql($select, false) and $showfeedback) {
4942 notify($strreset.': '.get_string('eachuserforum','forum'), 'notifysuccess');
4945 if (!empty($data->reset_forum_general)) {
4946 $select = "$sql AND f.type = 'general' )";
4947 if (execute_sql($select, false) and $showfeedback) {
4948 notify($strreset.': '.get_string('generalforum','forum'), 'notifysuccess');
4951 if (!empty($data->reset_forum_subscriptions)) {
4952 $subscripsql = "DELETE FROM {$CFG->prefix}forum_subscriptions
4953 WHERE forum IN (
4954 SELECT id FROM {$CFG->prefix}forum
4955 WHERE course = {$data->courseid} )";
4957 if (execute_sql($subscripsql, false) and $showfeedback) {
4958 notify($strreset.': '.get_string('resetsubscriptions','forum'), 'notifysuccess');
4965 * Called by course/reset.php
4967 function forum_reset_course_form($course) {
4968 echo get_string('resetforums', 'forum'); echo ':<br />';
4969 print_checkbox('reset_forum_news', 1, true, get_string('namenews','forum'), '', ''); echo '<br />';
4970 print_checkbox('reset_forum_single', 1, true, get_string('singleforum','forum'), '', ''); echo '<br />';
4971 print_checkbox('reset_forum_eachuser', 1, true, get_string('eachuserforum','forum'), '', ''); echo '<br />';
4972 print_checkbox('reset_forum_general', 1, true, get_string('generalforum','forum'), '', ''); echo '<br />';
4973 echo '<p>';
4974 print_checkbox('reset_forum_subscriptions', 1, true, get_string('resetsubscriptions','forum'), '', '');
4975 echo '</p>';
4980 * Converts a forum to use the Roles System
4981 * @param $forum - a forum object with the same attributes as a record
4982 * from the forum database table
4983 * @param $forummodid - the id of the forum module, from the modules table
4984 * @param $teacherroles - array of roles that have moodle/legacy:teacher
4985 * @param $studentroles - array of roles that have moodle/legacy:student
4986 * @param $guestroles - array of roles that have moodle/legacy:guest
4987 * @param $cmid - the course_module id for this forum instance
4988 * @return boolean - forum was converted or not
4990 function forum_convert_to_roles($forum, $forummodid, $teacherroles=array(),
4991 $studentroles=array(), $guestroles=array(), $cmid=NULL) {
4993 global $CFG;
4995 if (!isset($forum->open) && !isset($forum->assesspublic)) {
4996 // We assume that this forum has already been converted to use the
4997 // Roles System. Columns forum.open and forum.assesspublic get dropped
4998 // once the forum module has been upgraded to use Roles.
4999 return false;
5002 if ($forum->type == 'teacher') {
5004 // Teacher forums should be converted to normal forums that
5005 // use the Roles System to implement the old behavior.
5006 // Note:
5007 // Seems that teacher forums were never backed up in 1.6 since they
5008 // didn't have an entry in the course_modules table.
5009 require_once($CFG->dirroot.'/course/lib.php');
5011 if (count_records('forum_discussions', 'forum', $forum->id) == 0) {
5012 // Delete empty teacher forums.
5013 delete_records('forum', 'id', $forum->id);
5014 } else {
5015 // Create a course module for the forum and assign it to
5016 // section 0 in the course.
5017 $mod = new object;
5018 $mod->course = $forum->course;
5019 $mod->module = $forummodid;
5020 $mod->instance = $forum->id;
5021 $mod->section = 0;
5022 $mod->visible = 0; // Hide the forum
5023 $mod->visibleold = 0; // Hide the forum
5024 $mod->groupmode = 0;
5026 if (!$cmid = add_course_module($mod)) {
5027 error('Could not create new course module instance for the teacher forum');
5028 } else {
5029 $mod->coursemodule = $cmid;
5030 if (!$sectionid = add_mod_to_section($mod)) {
5031 error('Could not add converted teacher forum instance to section 0 in the course');
5032 } else {
5033 if (!set_field('course_modules', 'section', $sectionid, 'id', $cmid)) {
5034 error('Could not update course module with section id');
5039 // Change the forum type to general.
5040 $forum->type = 'general';
5041 if (!update_record('forum', $forum)) {
5042 error('Could not change forum from type teacher to type general');
5045 $context = get_context_instance(CONTEXT_MODULE, $cmid);
5047 // Create overrides for default student and guest roles (prevent).
5048 foreach ($studentroles as $studentrole) {
5049 assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
5050 assign_capability('mod/forum:viewhiddentimedposts', CAP_PREVENT, $studentrole->id, $context->id);
5051 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
5052 assign_capability('mod/forum:replypost', CAP_PREVENT, $studentrole->id, $context->id);
5053 assign_capability('mod/forum:viewrating', CAP_PREVENT, $studentrole->id, $context->id);
5054 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $studentrole->id, $context->id);
5055 assign_capability('mod/forum:rate', CAP_PREVENT, $studentrole->id, $context->id);
5056 assign_capability('mod/forum:createattachment', CAP_PREVENT, $studentrole->id, $context->id);
5057 assign_capability('mod/forum:deleteownpost', CAP_PREVENT, $studentrole->id, $context->id);
5058 assign_capability('mod/forum:deleteanypost', CAP_PREVENT, $studentrole->id, $context->id);
5059 assign_capability('mod/forum:splitdiscussions', CAP_PREVENT, $studentrole->id, $context->id);
5060 assign_capability('mod/forum:movediscussions', CAP_PREVENT, $studentrole->id, $context->id);
5061 assign_capability('mod/forum:editanypost', CAP_PREVENT, $studentrole->id, $context->id);
5062 assign_capability('mod/forum:viewqandawithoutposting', CAP_PREVENT, $studentrole->id, $context->id);
5063 assign_capability('mod/forum:viewsubscribers', CAP_PREVENT, $studentrole->id, $context->id);
5064 assign_capability('mod/forum:managesubscriptions', CAP_PREVENT, $studentrole->id, $context->id);
5065 assign_capability('mod/forum:throttlingapplies', CAP_PREVENT, $studentrole->id, $context->id);
5067 foreach ($guestroles as $guestrole) {
5068 assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $guestrole->id, $context->id);
5069 assign_capability('mod/forum:viewhiddentimedposts', CAP_PREVENT, $guestrole->id, $context->id);
5070 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $guestrole->id, $context->id);
5071 assign_capability('mod/forum:replypost', CAP_PREVENT, $guestrole->id, $context->id);
5072 assign_capability('mod/forum:viewrating', CAP_PREVENT, $guestrole->id, $context->id);
5073 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $guestrole->id, $context->id);
5074 assign_capability('mod/forum:rate', CAP_PREVENT, $guestrole->id, $context->id);
5075 assign_capability('mod/forum:createattachment', CAP_PREVENT, $guestrole->id, $context->id);
5076 assign_capability('mod/forum:deleteownpost', CAP_PREVENT, $guestrole->id, $context->id);
5077 assign_capability('mod/forum:deleteanypost', CAP_PREVENT, $guestrole->id, $context->id);
5078 assign_capability('mod/forum:splitdiscussions', CAP_PREVENT, $guestrole->id, $context->id);
5079 assign_capability('mod/forum:movediscussions', CAP_PREVENT, $guestrole->id, $context->id);
5080 assign_capability('mod/forum:editanypost', CAP_PREVENT, $guestrole->id, $context->id);
5081 assign_capability('mod/forum:viewqandawithoutposting', CAP_PREVENT, $guestrole->id, $context->id);
5082 assign_capability('mod/forum:viewsubscribers', CAP_PREVENT, $guestrole->id, $context->id);
5083 assign_capability('mod/forum:managesubscriptions', CAP_PREVENT, $guestrole->id, $context->id);
5084 assign_capability('mod/forum:throttlingapplies', CAP_PREVENT, $guestrole->id, $context->id);
5087 } else {
5088 // Non-teacher forum.
5090 if (empty($cmid)) {
5091 // We were not given the course_module id. Try to find it.
5092 if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
5093 notify('Could not get the course module for the forum');
5094 return false;
5095 } else {
5096 $cmid = $cm->id;
5099 $context = get_context_instance(CONTEXT_MODULE, $cmid);
5101 // $forum->open defines what students can do:
5102 // 0 = No discussions, no replies
5103 // 1 = No discussions, but replies are allowed
5104 // 2 = Discussions and replies are allowed
5105 switch ($forum->open) {
5106 case 0:
5107 foreach ($studentroles as $studentrole) {
5108 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
5109 assign_capability('mod/forum:replypost', CAP_PREVENT, $studentrole->id, $context->id);
5111 break;
5112 case 1:
5113 foreach ($studentroles as $studentrole) {
5114 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
5115 assign_capability('mod/forum:replypost', CAP_ALLOW, $studentrole->id, $context->id);
5117 break;
5118 case 2:
5119 foreach ($studentroles as $studentrole) {
5120 assign_capability('mod/forum:startdiscussion', CAP_ALLOW, $studentrole->id, $context->id);
5121 assign_capability('mod/forum:replypost', CAP_ALLOW, $studentrole->id, $context->id);
5123 break;
5126 // $forum->assessed defines whether forum rating is turned
5127 // on (1 or 2) and who can rate posts:
5128 // 1 = Everyone can rate posts
5129 // 2 = Only teachers can rate posts
5130 switch ($forum->assessed) {
5131 case 1:
5132 foreach ($studentroles as $studentrole) {
5133 assign_capability('mod/forum:rate', CAP_ALLOW, $studentrole->id, $context->id);
5135 foreach ($teacherroles as $teacherrole) {
5136 assign_capability('mod/forum:rate', CAP_ALLOW, $teacherrole->id, $context->id);
5138 break;
5139 case 2:
5140 foreach ($studentroles as $studentrole) {
5141 assign_capability('mod/forum:rate', CAP_PREVENT, $studentrole->id, $context->id);
5143 foreach ($teacherroles as $teacherrole) {
5144 assign_capability('mod/forum:rate', CAP_ALLOW, $teacherrole->id, $context->id);
5146 break;
5149 // $forum->assesspublic defines whether students can see
5150 // everybody's ratings:
5151 // 0 = Students can only see their own ratings
5152 // 1 = Students can see everyone's ratings
5153 switch ($forum->assesspublic) {
5154 case 0:
5155 foreach ($studentroles as $studentrole) {
5156 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $studentrole->id, $context->id);
5158 foreach ($teacherroles as $teacherrole) {
5159 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $teacherrole->id, $context->id);
5161 break;
5162 case 1:
5163 foreach ($studentroles as $studentrole) {
5164 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $studentrole->id, $context->id);
5166 foreach ($teacherroles as $teacherrole) {
5167 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $teacherrole->id, $context->id);
5169 break;
5172 if (empty($cm)) {
5173 $cm = get_record('course_modules', 'id', $cmid);
5176 // $cm->groupmode:
5177 // 0 - No groups
5178 // 1 - Separate groups
5179 // 2 - Visible groups
5180 switch ($cm->groupmode) {
5181 case 0:
5182 break;
5183 case 1:
5184 foreach ($studentroles as $studentrole) {
5185 assign_capability('moodle/site:accessallgroups', CAP_PREVENT, $studentrole->id, $context->id);
5187 foreach ($teacherroles as $teacherrole) {
5188 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
5190 break;
5191 case 2:
5192 foreach ($studentroles as $studentrole) {
5193 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $studentrole->id, $context->id);
5195 foreach ($teacherroles as $teacherrole) {
5196 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
5198 break;
5201 return true;