MDL-11517 reserved word MOD used in table alias in questions backup code
[moodle-pu.git] / mod / forum / lib.php
blob36915c0918eb33ee041f665ac241034cb26a6fb1
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;
1276 * Checks if scale is being used by any instance of forum
1278 * This is used to find out if scale used anywhere
1279 * @param $scaleid int
1280 * @return boolean True if the scale is used by any forum
1282 function forum_scale_used_anywhere($scaleid) {
1283 if ($scaleid and record_exists('forum', 'scale', -$scaleid)) {
1284 return true;
1285 } else {
1286 return false;
1290 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1293 * Gets a post with all info ready for forum_print_post
1294 * Most of these joins are just to get the forum id
1296 function forum_get_post_full($postid) {
1297 global $CFG;
1299 return get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture
1300 FROM {$CFG->prefix}forum_posts p
1301 LEFT JOIN {$CFG->prefix}forum_discussions d ON p.discussion = d.id
1302 LEFT JOIN {$CFG->prefix}user u ON p.userid = u.id
1303 WHERE p.id = '$postid'");
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_discussion_posts($discussion, $sort, $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.discussion = $discussion
1318 AND p.parent > 0 $sort");
1322 * Gets posts with all info ready for forum_print_post
1323 * We pass forumid in because we always know it so no need to make a
1324 * complicated join to find it out.
1326 function forum_get_child_posts($parent, $forumid) {
1327 global $CFG;
1329 return get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture
1330 FROM {$CFG->prefix}forum_posts p
1331 LEFT JOIN {$CFG->prefix}user u ON p.userid = u.id
1332 WHERE p.parent = '$parent'
1333 ORDER BY p.created ASC");
1337 * An array of forum objects that the user is allowed to read/search through.
1338 * @param $userid
1339 * @param $courseid - if 0, we look for forums throughout the whole site.
1340 * @return array of forum objects, or false if no matches
1341 * Forum objects have the following attributes:
1342 * id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1343 * viewhiddentimedposts
1345 function forum_get_readable_forums($userid, $courseid=0) {
1347 global $CFG, $USER;
1349 if (!$forummod = get_record('modules', 'name', 'forum')) {
1350 error('The forum module is not installed');
1353 if ($courseid) {
1354 $courses = get_records('course', 'id', $courseid);
1355 } else {
1356 // If no course is specified, then the user can see SITE + his courses.
1357 // And admins can see all courses, so pass the $doanything flag enabled
1358 $courses1 = get_records('course', 'id', SITEID);
1359 $courses2 = get_my_courses($userid, null, null, true);
1360 $courses = array_merge($courses1, $courses2);
1362 if (!$courses) {
1363 return false;
1366 $readableforums = array();
1368 foreach ($courses as $course) {
1370 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
1372 if (!has_capability('moodle/course:viewhiddenactivities', $coursecontext)) {
1373 $selecthidden = ' AND cm.visible = 1';
1374 } else {
1375 $selecthidden = '';
1378 $selectforums = "SELECT f.id AS id,
1379 f.name AS name,
1380 f.type AS type,
1381 f.course AS course,
1382 cm.id AS cmid,
1383 cm.visible AS cmvisible,
1384 cm.groupmode AS cmgroupmode,
1385 cm.groupingid AS cmgroupingid,
1386 cm.groupmembersonly AS cmgroupmembersonly
1387 FROM {$CFG->prefix}course_modules cm,
1388 {$CFG->prefix}forum f
1389 WHERE cm.instance = f.id
1390 AND cm.course = {$course->id}
1391 AND cm.module = {$forummod->id}
1392 $selecthidden
1393 ORDER BY f.name ASC";
1395 if ($forums = get_records_sql($selectforums)) {
1397 $group = groups_get_all_groups($course->id, $userid);
1399 foreach ($forums as $forum) {
1400 $forumcontext = get_context_instance(CONTEXT_MODULE, $forum->cmid);
1402 if (has_capability('mod/forum:viewdiscussion', $forumcontext)) {
1404 // Evaluate groupmode.
1405 $cm = new object;
1406 $cm->id = $forum->cmid;
1407 $cm->groupmode = $forum->cmgroupmode;
1408 $cm->groupingid = $forum->cmgroupingid;
1409 $cm->groupmembersonly = $forum->cmgroupmembersonly;
1410 $cm->course = $forum->course;
1411 $forum->cmgroupmode = groups_get_activity_groupmode($cm);
1412 if (!groups_course_module_visible($cm)) {
1413 continue;
1415 if ($forum->cmgroupmode == SEPARATEGROUPS
1416 && !has_capability('moodle/site:accessallgroups', $forumcontext)) {
1417 $forum->accessallgroups = false;
1418 $forum->accessgroup = $group->id; // The user can only access
1419 // discussions for this group.
1420 } else {
1421 $forum->accessallgroups = true;
1424 $forum->viewhiddentimedposts
1425 = has_capability('mod/forum:viewhiddentimedposts', $forumcontext);
1427 if ($forum->type == 'qanda'
1428 && !has_capability('mod/forum:viewqandawithoutposting', $forumcontext)) {
1430 // We need to check whether the user has posted in the qanda forum.
1431 $forum->onlydiscussions = array(); // Holds discussion ids for the discussions
1432 // the user is allowed to see in this forum.
1434 if ($discussionspostedin =
1435 forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1436 foreach ($discussionspostedin as $d) {
1437 array_push($forum->onlydiscussions, $d->id);
1441 array_push($readableforums, $forum);
1445 } // End foreach $courses
1447 //print_object($courses);
1448 //print_object($readableforums);
1450 return $readableforums;
1454 * Returns a list of posts found using an array of search terms.
1455 * @param $searchterms - array of search terms, e.g. word +word -word
1456 * @param $courseid - if 0, we search through the whole site
1457 * @param $page
1458 * @param $recordsperpage=50
1459 * @param &$totalcount
1460 * @param $extrasql
1461 * @return array of posts found
1463 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1464 &$totalcount, $extrasql='') {
1465 global $CFG, $USER;
1466 require_once($CFG->libdir.'/searchlib.php');
1468 $forums = forum_get_readable_forums($USER->id, $courseid);
1470 if (count($forums) == 0) {
1471 return false;
1474 for ($i=0; $i<count($forums); $i++) {
1475 if ($i == 0) {
1476 $selectdiscussion = " ((d.forum = {$forums[$i]->id}";
1477 } else {
1478 $selectdiscussion .= " OR (d.forum = {$forums[$i]->id}";
1480 if (!empty($CFG->forum_enabletimedposts) && !$forums[$i]->viewhiddentimedposts) {
1481 $now = time();
1482 $selectdiscussion .= " AND ( d.userid = {$USER->id}
1483 OR ((d.timestart = 0 OR d.timestart <= $now)
1484 AND (d.timeend = 0 OR d.timeend > $now)) )";
1486 if ($forums[$i]->type == 'qanda' && isset($forums[$i]->onlydiscussions)) {
1487 // This is a qanda forum.
1488 if (is_array($forums[$i]->onlydiscussions)) {
1489 // Show question posts as well as posts from discussions in
1490 // which the user has posted a reply.
1491 $onlydiscussions = implode(' OR d.id = ', $forums[$i]->onlydiscussions);
1492 $selectdiscussion .= " AND ((d.id = $onlydiscussions) OR p.parent = 0)";
1493 } else {
1494 // Show only the question posts.
1495 $selectdiscussion .= ' AND (p.parent = 0)';
1498 if (!$forums[$i]->accessallgroups) {
1499 if (!empty($forums[$i]->accessgroup)) {
1500 $selectdiscussion .= " AND (d.groupid = {$forums[$i]->accessgroup}";
1501 $selectdiscussion .= ' OR d.groupid = -1)'; // -1 means open for all groups.
1502 } else {
1503 // User isn't in any group. Only search discussions that are
1504 // open to all groups.
1505 $selectdiscussion .= ' AND d.groupid = -1';
1508 $selectdiscussion .= ")\n";
1510 $selectdiscussion .= ")";
1513 // Some differences SQL
1514 $LIKE = sql_ilike();
1515 $NOTLIKE = 'NOT ' . $LIKE;
1516 if ($CFG->dbfamily == 'postgres') {
1517 $REGEXP = '~*';
1518 $NOTREGEXP = '!~*';
1519 } else {
1520 $REGEXP = 'REGEXP';
1521 $NOTREGEXP = 'NOT REGEXP';
1524 $messagesearch = '';
1525 $searchstring = '';
1527 // Need to concat these back together for parser to work.
1528 foreach($searchterms as $searchterm){
1529 if ($searchstring != '') {
1530 $searchstring .= ' ';
1532 $searchstring .= $searchterm;
1535 // We need to allow quoted strings for the search. The quotes *should* be stripped
1536 // by the parser, but this should be examined carefully for security implications.
1537 $searchstring = str_replace("\\\"","\"",$searchstring);
1538 $parser = new search_parser();
1539 $lexer = new search_lexer($parser);
1541 if ($lexer->parse($searchstring)) {
1542 $parsearray = $parser->get_parsed_array();
1543 // Experimental feature under 1.8! MDL-8830
1544 // Use alternative text searches if defined
1545 // This feature only works under mysql until properly implemented for other DBs
1546 // Requires manual creation of text index for forum_posts before enabling it:
1547 // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
1548 // Experimental feature under 1.8! MDL-8830
1549 if (!empty($CFG->forum_usetextsearches)) {
1550 $messagesearch = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
1551 'p.userid', 'u.id', 'u.firstname',
1552 'u.lastname', 'p.modified', 'd.forum');
1553 } else {
1554 $messagesearch = search_generate_SQL($parsearray, 'p.message', 'p.subject',
1555 'p.userid', 'u.id', 'u.firstname',
1556 'u.lastname', 'p.modified', 'd.forum');
1560 $fromsql = "{$CFG->prefix}forum_posts p,
1561 {$CFG->prefix}forum_discussions d,
1562 {$CFG->prefix}user u";
1564 $selectsql = " $messagesearch
1565 AND p.discussion = d.id
1566 AND p.userid = u.id
1567 AND $selectdiscussion
1568 $extrasql";
1570 $countsql = "SELECT COUNT(*)
1571 FROM $fromsql
1572 WHERE $selectsql";
1574 $searchsql = "SELECT p.*,
1575 d.forum,
1576 u.firstname,
1577 u.lastname,
1578 u.email,
1579 u.picture
1580 FROM $fromsql
1581 WHERE $selectsql
1582 ORDER BY p.modified DESC";
1584 $totalcount = count_records_sql($countsql);
1586 return get_records_sql($searchsql, $limitfrom, $limitnum);
1590 * Returns a list of ratings for a particular post - sorted.
1592 function forum_get_ratings($postid, $sort="u.firstname ASC") {
1593 global $CFG;
1594 return get_records_sql("SELECT u.*, r.rating, r.time
1595 FROM {$CFG->prefix}forum_ratings r,
1596 {$CFG->prefix}user u
1597 WHERE r.post = '$postid'
1598 AND r.userid = u.id
1599 ORDER BY $sort");
1603 * Returns a list of all new posts that have not been mailed yet
1605 function forum_get_unmailed_posts($starttime, $endtime) {
1606 global $CFG;
1607 $now = time();
1608 return get_records_sql("SELECT p.*, d.course
1609 FROM {$CFG->prefix}forum_posts p,
1610 {$CFG->prefix}forum_discussions d
1611 WHERE p.mailed = 0
1612 AND (p.created >= '$starttime' OR d.timestart > 0)
1613 AND (p.created < '$endtime' OR p.mailnow = 1)
1614 AND p.discussion = d.id
1615 AND ((d.timestart = 0 OR d.timestart <= '$now')
1616 AND (d.timeend = 0 OR d.timeend > '$now'))
1617 ORDER BY p.modified ASC");
1621 * Marks posts before a certain time as being mailed already
1623 function forum_mark_old_posts_as_mailed($endtime) {
1624 global $CFG;
1625 // Find out posts those are not showing immediately so we can exclude them
1626 $now = time();
1627 $delayed_posts = get_records_sql("SELECT p.id, p.discussion
1628 FROM {$CFG->prefix}forum_posts p,
1629 {$CFG->prefix}forum_discussions d
1630 WHERE p.mailed = 0
1631 AND p.discussion = d.id
1632 AND d.timestart > '$now'");
1633 $delayed_ids = array();
1634 if ($delayed_posts) {
1635 foreach ($delayed_posts as $post) {
1636 $delayed_ids[] = $post->id;
1638 } else {
1639 $delayed_ids[] = 0;
1641 return execute_sql("UPDATE {$CFG->prefix}forum_posts
1642 SET mailed = '1'
1643 WHERE id NOT IN (".implode(',',$delayed_ids).")
1644 AND (created < '$endtime' OR mailnow = 1)
1645 AND mailed ='0'", false);
1649 * Get all the posts for a user in a forum suitable for forum_print_post
1651 function forum_get_user_posts($forumid, $userid) {
1652 global $CFG;
1654 return get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture
1655 FROM {$CFG->prefix}forum f,
1656 {$CFG->prefix}forum_discussions d,
1657 {$CFG->prefix}forum_posts p,
1658 {$CFG->prefix}user u
1659 WHERE f.id = '$forumid'
1660 AND d.forum = f.id
1661 AND p.discussion = d.id
1662 AND p.userid = '$userid'
1663 AND p.userid = u.id
1664 ORDER BY p.modified ASC");
1668 * Given a log entry, return the forum post details for it.
1670 function forum_get_post_from_log($log) {
1671 global $CFG;
1673 if ($log->action == "add post") {
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 p.id = '$log->info'
1682 AND d.id = p.discussion
1683 AND p.userid = u.id
1684 AND u.deleted <> '1'
1685 AND f.id = d.forum");
1688 } else if ($log->action == "add discussion") {
1690 return get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1691 u.firstname, u.lastname, u.email, u.picture
1692 FROM {$CFG->prefix}forum_discussions d,
1693 {$CFG->prefix}forum_posts p,
1694 {$CFG->prefix}forum f,
1695 {$CFG->prefix}user u
1696 WHERE d.id = '$log->info'
1697 AND d.firstpost = p.id
1698 AND p.userid = u.id
1699 AND u.deleted <> '1'
1700 AND f.id = d.forum");
1702 return NULL;
1706 * Given a discussion id, return the first post from the discussion
1708 function forum_get_firstpost_from_discussion($discussionid) {
1709 global $CFG;
1711 return get_record_sql("SELECT p.*
1712 FROM {$CFG->prefix}forum_discussions d,
1713 {$CFG->prefix}forum_posts p
1714 WHERE d.id = '$discussionid'
1715 AND d.firstpost = p.id ");
1719 * Returns an array of counts of replies to each discussion (optionally in one forum or course and/or user)
1721 function forum_count_discussion_replies($forum='0', $course='0', $user='0') {
1722 global $CFG;
1724 $forumselect = $courseselect = $userselect = '';
1726 if ($forum) {
1727 $forumselect = " AND d.forum = '$forum'";
1729 if ($course) {
1730 $courseselect = " AND d.course = '$course'";
1732 if ($user) {
1733 $userselect = " AND d.userid = '$user'";
1735 return get_records_sql("SELECT p.discussion, (count(*)) as replies, max(p.id) as lastpostid
1736 FROM {$CFG->prefix}forum_posts p,
1737 {$CFG->prefix}forum_discussions d
1738 WHERE p.parent > 0 $forumselect $courseselect $userselect
1739 AND p.discussion = d.id
1740 GROUP BY p.discussion");
1744 * How many unrated posts are in the given discussion for a given user?
1746 function forum_count_unrated_posts($discussionid, $userid) {
1747 global $CFG;
1748 if ($posts = get_record_sql("SELECT count(*) as num
1749 FROM {$CFG->prefix}forum_posts
1750 WHERE parent > 0
1751 AND discussion = '$discussionid'
1752 AND userid <> '$userid' ")) {
1754 if ($rated = get_record_sql("SELECT count(*) as num
1755 FROM {$CFG->prefix}forum_posts p,
1756 {$CFG->prefix}forum_ratings r
1757 WHERE p.discussion = '$discussionid'
1758 AND p.id = r.post
1759 AND r.userid = '$userid'")) {
1760 $difference = $posts->num - $rated->num;
1761 if ($difference > 0) {
1762 return $difference;
1763 } else {
1764 return 0; // Just in case there was a counting error
1766 } else {
1767 return $posts->num;
1769 } else {
1770 return 0;
1775 * Get all discussions in a forum
1777 function forum_get_discussions($forum="0", $forumsort="d.timemodified DESC",
1778 $user=0, $fullpost=true, $currentgroup=-1, $limit=0, $userlastmodified=false) {
1779 global $CFG, $USER;
1781 $timelimit = '';
1783 if (!$cm = get_coursemodule_from_instance('forum', $forum)) {
1784 error('Course Module ID was incorrect');
1787 if (!empty($CFG->forum_enabletimedposts)) {
1789 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
1791 if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
1792 $now = time();
1793 $timelimit = " AND ((d.timestart = 0 OR d.timestart <= '$now') AND (d.timeend = 0 OR d.timeend > '$now')";
1794 if (!empty($USER->id)) {
1795 $timelimit .= " OR d.userid = '$USER->id'";
1797 $timelimit .= ')';
1801 if ($user) {
1802 $userselect = " AND u.id = '$user' ";
1803 } else {
1804 $userselect = "";
1807 $limitfrom = 0;
1808 $limitnum = 0;
1809 if ($limit) {
1810 $limitnum = $limit;
1813 if ($currentgroup == -1) {
1814 $currentgroup = get_current_group($cm->course);
1817 if ($currentgroup) {
1818 $groupselect = " AND (d.groupid = '$currentgroup' OR d.groupid = -1) ";
1819 } else {
1820 $groupselect = "";
1823 if (empty($forumsort)) {
1824 $forumsort = "d.timemodified DESC";
1826 if (empty($fullpost)) {
1827 $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
1828 } else {
1829 $postdata = "p.*";
1832 if (empty($userlastmodified)) { // We don't need to know this
1833 $umfields = '';
1834 $umtable = '';
1835 } else {
1836 $umfields = ', um.firstname AS umfirstname, um.lastname AS umlastname';
1837 $umtable = ' LEFT JOIN '.$CFG->prefix.'user um on (d.usermodified = um.id)';
1840 //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.
1841 if ($CFG->dbfamily == 'postgres' || $CFG->dbfamily == 'mssql' || $CFG->dbfamily == 'oracle') {
1842 return get_records_sql("SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid,
1843 u.firstname, u.lastname, u.email, u.picture $umfields
1844 FROM {$CFG->prefix}forum_discussions d
1845 JOIN {$CFG->prefix}forum_posts p ON p.discussion = d.id
1846 JOIN {$CFG->prefix}user u ON p.userid = u.id
1847 $umtable
1848 WHERE d.forum = '$forum'
1849 AND p.parent = 0
1850 $timelimit $groupselect $userselect
1851 ORDER BY $forumsort", $limitfrom, $limitnum);
1852 } else { // MySQL query. TODO: Check if this is needed (MySQL 4.1 should work with the above query)
1853 return get_records_sql("SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid,
1854 u.firstname, u.lastname, u.email, u.picture $umfields
1855 FROM ({$CFG->prefix}forum_posts p,
1856 {$CFG->prefix}user u,
1857 {$CFG->prefix}forum_discussions d)
1858 $umtable
1859 WHERE d.forum = '$forum'
1860 AND p.discussion = d.id
1861 AND p.parent = 0
1862 AND p.userid = u.id $timelimit $groupselect $userselect
1863 ORDER BY $forumsort", $limitfrom, $limitnum);
1870 * Get all discussions started by a particular user in a course (or group)
1871 * This function no longer used ...
1873 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
1874 global $CFG;
1876 if ($groupid) {
1877 $groupselect = " AND d.groupid = '$groupid' ";
1878 } else {
1879 $groupselect = "";
1882 return get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture,
1883 f.type as forumtype, f.name as forumname, f.id as forumid
1884 FROM {$CFG->prefix}forum_discussions d,
1885 {$CFG->prefix}forum_posts p,
1886 {$CFG->prefix}user u,
1887 {$CFG->prefix}forum f
1888 WHERE d.course = '$courseid'
1889 AND p.discussion = d.id
1890 AND p.parent = 0
1891 AND p.userid = u.id
1892 AND u.id = '$userid'
1893 AND d.forum = f.id $groupselect
1894 ORDER BY p.created DESC");
1898 * Returns list of user objects that are subscribed to this forum
1900 function forum_subscribed_users($course, $forum, $groupid=0, $cache=false) {
1902 global $CFG;
1904 static $resultscache = array();
1906 if ($cache && isset($resultscache[$forum->id][$groupid])) {
1907 return $resultscache[$forum->id][$groupid];
1910 if ($groupid) {
1911 $grouptables = ", {$CFG->prefix}groups_members gm ";
1912 $groupselect = "AND gm.groupid = '$groupid' AND u.id = gm.userid";
1914 } else {
1915 $grouptables = '';
1916 $groupselect = '';
1919 if (forum_is_forcesubscribed($forum)) {
1920 $results = get_course_users($course->id); // Otherwise get everyone in the course
1921 } else {
1922 $results = get_records_sql("SELECT u.id, u.username, u.firstname, u.lastname, u.maildisplay, u.mailformat, u.maildigest, u.emailstop,
1923 u.email, u.city, u.country, u.lastaccess, u.lastlogin, u.picture, u.timezone, u.theme, u.lang, u.trackforums
1924 FROM {$CFG->prefix}user u,
1925 {$CFG->prefix}forum_subscriptions s $grouptables
1926 WHERE s.forum = '$forum->id'
1927 AND s.userid = u.id
1928 AND u.deleted <> 1 $groupselect
1929 ORDER BY u.email ASC");
1931 // Guest user should never be subscribed to a forum.
1932 if ($guest = guest_user()) {
1933 unset($results[$guest->id]);
1936 if ($cache) {
1937 $resultscache[$forum->id][$groupid] = $results;
1940 return $results;
1945 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
1948 function forum_get_course_forum($courseid, $type) {
1949 // How to set up special 1-per-course forums
1950 global $CFG;
1952 if ($forums = get_records_select("forum", "course = '$courseid' AND type = '$type'", "id ASC")) {
1953 // There should always only be ONE, but with the right combination of
1954 // errors there might be more. In this case, just return the oldest one (lowest ID).
1955 foreach ($forums as $forum) {
1956 return $forum; // ie the first one
1960 // Doesn't exist, so create one now.
1961 $forum->course = $courseid;
1962 $forum->type = "$type";
1963 switch ($forum->type) {
1964 case "news":
1965 $forum->name = addslashes(get_string("namenews", "forum"));
1966 $forum->intro = addslashes(get_string("intronews", "forum"));
1967 $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
1968 $forum->assessed = 0;
1969 if ($courseid == SITEID) {
1970 $forum->name = get_string("sitenews");
1971 $forum->forcesubscribe = 0;
1973 break;
1974 case "social":
1975 $forum->name = addslashes(get_string("namesocial", "forum"));
1976 $forum->intro = addslashes(get_string("introsocial", "forum"));
1977 $forum->assessed = 0;
1978 $forum->forcesubscribe = 0;
1979 break;
1980 default:
1981 notify("That forum type doesn't exist!");
1982 return false;
1983 break;
1986 $forum->timemodified = time();
1987 $forum->id = insert_record("forum", $forum);
1989 if (! $module = get_record("modules", "name", "forum")) {
1990 notify("Could not find forum module!!");
1991 return false;
1993 $mod->course = $courseid;
1994 $mod->module = $module->id;
1995 $mod->instance = $forum->id;
1996 $mod->section = 0;
1997 if (! $mod->coursemodule = add_course_module($mod) ) { // assumes course/lib.php is loaded
1998 notify("Could not add a new course module to the course '" . format_string($course->fullname) . "'");
1999 return false;
2001 if (! $sectionid = add_mod_to_section($mod) ) { // assumes course/lib.php is loaded
2002 notify("Could not add the new course module to that section");
2003 return false;
2005 if (! set_field("course_modules", "section", $sectionid, "id", $mod->coursemodule)) {
2006 notify("Could not update the course module with the correct section");
2007 return false;
2009 include_once("$CFG->dirroot/course/lib.php");
2010 rebuild_course_cache($courseid);
2012 return get_record("forum", "id", "$forum->id");
2017 * Given the data about a posting, builds up the HTML to display it and
2018 * returns the HTML in a string. This is designed for sending via HTML email.
2020 function forum_make_mail_post(&$post, $user, $touser, $course,
2021 $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
2024 global $CFG, $USER;
2026 // the old caching was removed for now, because it did not work due to recent changes in cron
2028 $post->forum = get_field('forum_discussions', 'forum', 'id', $post->discussion);
2030 if (!$cm = get_coursemodule_from_instance('forum', $post->forum)) {
2031 mtrace('Course Module ID was incorrect');
2033 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2035 // format the post body
2036 $options = new object();
2037 $options->para = true;
2038 $formattedtext = format_text(trusttext_strip($post->message), $post->format, $options, $course->id);
2040 $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
2042 $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
2043 $output .= print_user_picture($user->id, $course->id, $user->picture, false, true);
2044 $output .= '</td>';
2046 if ($post->parent) {
2047 $output .= '<td class="topic">';
2048 } else {
2049 $output .= '<td class="topic starter">';
2051 $output .= '<div class="subject">'.format_string($post->subject).'</div>';
2053 $fullname = fullname($user, has_capability('moodle/site:viewfullnames', $modcontext));
2054 $by = new object();
2055 $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$user->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
2056 $by->date = userdate($post->modified, '', $touser->timezone);
2057 $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
2059 $output .= '</td></tr>';
2061 $output .= '<tr><td class="left side" valign="top">';
2062 if ($group = groups_get_all_groups($course->id, $user->id)) {
2063 $output .= print_group_picture($group, $course->id, false, true, true);
2064 } else {
2065 $output .= '&nbsp;';
2068 $output .= '</td><td class="content">';
2070 if ($post->attachment) {
2071 $post->course = $course->id;
2072 $output .= '<div class="attachments">';
2073 $output .= forum_print_attachments($post, 'html');
2074 $output .= "</div>";
2077 $output .= $formattedtext;
2079 // Commands
2080 $commands = array();
2082 if ($post->parent) {
2083 $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
2084 $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
2087 if ($reply) {
2088 $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
2089 get_string('reply', 'forum').'</a>';
2092 $output .= '<div class="commands">';
2093 $output .= implode(' | ', $commands);
2094 $output .= '</div>';
2096 // Context link to post if required
2097 if ($link) {
2098 $output .= '<div class="link">';
2099 $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
2100 get_string('postincontext', 'forum').'</a>';
2101 $output .= '</div>';
2104 if ($footer) {
2105 $output .= '<div class="footer">'.$footer.'</div>';
2107 $output .= '</td></tr></table>'."\n\n";
2109 return $output;
2113 * Print a forum post
2115 * @param object $post The post to print.
2116 * @param integer $courseid The course this post belongs to.
2117 * @param boolean $ownpost Whether this post belongs to the current user.
2118 * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
2119 * @param boolean $link Just print a shortened version of the post as a link to the full post.
2120 * @param object $ratings -- I don't really know --
2121 * @param string $footer Extra stuff to print after the message.
2122 * @param string $highlight Space-separated list of terms to highlight.
2123 * @param int $post_read true, false or -99. If we already know whether this user
2124 * has read this post, pass that in, otherwise, pass in -99, and this
2125 * function will work it out.
2126 * @param boolean $dummyifcantsee When forum_user_can_see_post says that
2127 * the current user can't see this post, if this argument is true
2128 * (the default) then print a dummy 'you can't see this post' post.
2129 * If false, don't output anything at all.
2131 function forum_print_post(&$post, $courseid, $ownpost=false, $reply=false, $link=false,
2132 $ratings=NULL, $footer="", $highlight="", $post_read=-99, $dummyifcantsee=true) {
2134 global $USER, $CFG;
2136 static $stredit, $strdelete, $strreply, $strparent, $strprune;
2137 static $strpruneheading, $displaymode;
2138 static $strmarkread, $strmarkunread, $istracked;
2141 $discussion = get_record('forum_discussions', 'id', $post->discussion);
2142 if (!$cm = get_coursemodule_from_instance('forum', $discussion->forum)) {
2143 error('Course Module ID was incorrect');
2145 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2148 if (!forum_user_can_see_post($post->forum,$post->discussion,$post)) {
2149 if (!$dummyifcantsee) {
2150 return;
2152 echo '<a id="p'.$post->id.'"></a>';
2153 echo '<table cellspacing="0" class="forumpost">';
2154 echo '<tr class="header"><td class="picture left">';
2155 // print_user_picture($post->userid, $courseid, $post->picture);
2156 echo '</td>';
2157 if ($post->parent) {
2158 echo '<td class="topic">';
2159 } else {
2160 echo '<td class="topic starter">';
2162 echo '<div class="subject">'.get_string('forumsubjecthidden','forum').'</div>';
2163 echo '<div class="author">';
2164 print_string('forumauthorhidden','forum');
2165 echo '</div></td></tr>';
2167 echo '<tr><td class="left side">';
2168 echo '&nbsp;';
2170 // Actual content
2172 echo '</td><td class="content">'."\n";
2173 echo get_string('forumbodyhidden','forum');
2174 echo '</td></tr></table>';
2175 return;
2178 if (empty($stredit)) {
2179 $stredit = get_string('edit', 'forum');
2180 $strdelete = get_string('delete', 'forum');
2181 $strreply = get_string('reply', 'forum');
2182 $strparent = get_string('parent', 'forum');
2183 $strpruneheading = get_string('pruneheading', 'forum');
2184 $strprune = get_string('prune', 'forum');
2185 $displaymode = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
2186 $strmarkread = get_string('markread', 'forum');
2187 $strmarkunread = get_string('markunread', 'forum');
2189 if (!empty($post->forum)) {
2190 $istracked = (forum_tp_can_track_forums($post->forum) &&
2191 forum_tp_is_tracked($post->forum));
2192 } else {
2193 $istracked = false;
2197 if ($istracked) {
2198 if ($post_read == -99) { // If we don't know yet...
2199 // The front page can display a news item post to non-logged in users. This should
2200 // always appear as 'read'.
2201 $post_read = empty($USER) || forum_tp_is_post_read($USER->id, $post);
2203 if ($post_read) {
2204 $read_style = ' read';
2205 } else {
2206 $read_style = ' unread';
2207 echo '<a name="unread"></a>';
2209 } else {
2210 $read_style = '';
2213 echo '<a id="p'.$post->id.'"></a>';
2214 echo '<table cellspacing="0" class="forumpost'.$read_style.'">';
2216 echo '<tr class="header"><td class="picture left">';
2217 print_user_picture($post->userid, $courseid, $post->picture);
2218 echo '</td>';
2220 if ($post->parent) {
2221 echo '<td class="topic">';
2222 } else {
2223 echo '<td class="topic starter">';
2226 if (!empty($post->subjectnoformat)) {
2227 echo '<div class="subject">'.$post->subject.'</div>';
2228 } else {
2229 echo '<div class="subject">'.format_string($post->subject).'</div>';
2232 echo '<div class="author">';
2233 $fullname = fullname($post, has_capability('moodle/site:viewfullnames', $modcontext));
2234 $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.
2235 $post->userid.'&amp;course='.$courseid.'">'.$fullname.'</a>';
2236 $by->date = userdate($post->modified);
2237 print_string('bynameondate', 'forum', $by);
2238 echo '</div></td></tr>';
2240 echo '<tr><td class="left side">';
2241 if ($group = groups_get_all_groups($courseid, $post->userid)) {
2242 print_group_picture($group, $courseid, false, false, true);
2243 } else {
2244 echo '&nbsp;';
2247 // Actual content
2249 echo '</td><td class="content">'."\n";
2251 if ($post->attachment) {
2252 $post->course = $courseid;
2253 $post->forum = get_field('forum_discussions', 'forum', 'id', $post->discussion);
2254 echo '<div class="attachments">';
2255 $attachedimages = forum_print_attachments($post);
2256 echo '</div>';
2257 } else {
2258 $attachedimages = '';
2262 $options = new Object;
2263 $options->para = false;
2264 $options->trusttext = true;
2265 if ($link and (strlen(strip_tags($post->message)) > $CFG->forum_longpost)) {
2266 // Print shortened version
2267 echo format_text(forum_shorten_post($post->message), $post->format, $options, $courseid);
2268 $numwords = count_words(strip_tags($post->message));
2269 echo '<p><a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
2270 echo get_string('readtherest', 'forum');
2271 echo '</a> ('.get_string('numwords', '', $numwords).')...</p>';
2272 } else {
2273 // Print whole message
2274 if ($highlight) {
2275 echo highlight($highlight, format_text($post->message, $post->format, $options, $courseid));
2276 } else {
2277 echo format_text($post->message, $post->format, $options, $courseid);
2279 echo $attachedimages;
2283 // Commands
2285 $commands = array();
2287 if ($istracked) {
2288 // SPECIAL CASE: The front page can display a news item post to non-logged in users.
2289 // Don't display the mark read / unread controls in this case.
2290 if ($CFG->forum_usermarksread && !empty($USER)) {
2291 if ($post_read) {
2292 $mcmd = '&amp;mark=unread&amp;postid='.$post->id;
2293 $mtxt = $strmarkunread;
2294 } else {
2295 $mcmd = '&amp;mark=read&amp;postid='.$post->id;
2296 $mtxt = $strmarkread;
2298 if ($displaymode == FORUM_MODE_THREADED) {
2299 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
2300 $post->discussion.'&amp;parent='.$post->id.$mcmd.'">'.$mtxt.'</a>';
2301 } else {
2302 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
2303 $post->discussion.$mcmd.'#p'.$post->id.'">'.$mtxt.'</a>';
2308 if ($post->parent) { // Zoom in to the parent specifically
2309 if ($displaymode == FORUM_MODE_THREADED) {
2310 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
2311 $post->discussion.'&amp;parent='.$post->parent.'">'.$strparent.'</a>';
2312 } else {
2313 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
2314 $post->discussion.'#p'.$post->parent.'">'.$strparent.'</a>';
2318 $forumtype = get_field('forum', 'type', 'id', $post->forum);
2320 $age = time() - $post->created;
2321 // Hack for allow to edit news posts those are not displayed yet until they are displayed
2322 if (!$post->parent
2323 && $forumtype == 'news'
2324 && get_field_sql("SELECT id FROM {$CFG->prefix}forum_discussions WHERE id = $post->discussion AND timestart > ".time())) {
2325 $age = 0;
2327 $editanypost = has_capability('mod/forum:editanypost', $modcontext);
2331 if ($ownpost or $editanypost) {
2332 if (($age < $CFG->maxeditingtime) or $editanypost) {
2333 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?edit='.$post->id.'">'.$stredit.'</a>';
2337 if (has_capability('mod/forum:splitdiscussions', $modcontext)
2338 && $post->parent && $forumtype != 'single') {
2340 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?prune='.$post->id.
2341 '" title="'.$strpruneheading.'">'.$strprune.'</a>';
2344 if (($ownpost and $age < $CFG->maxeditingtime
2345 and has_capability('mod/forum:deleteownpost', $modcontext))
2346 or has_capability('mod/forum:deleteanypost', $modcontext)) {
2347 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?delete='.$post->id.'">'.$strdelete.'</a>';
2350 if ($reply) {
2351 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.$strreply.'</a>';
2354 echo '<div class="commands">';
2355 echo implode(' | ', $commands);
2356 echo '</div>';
2359 // Ratings
2361 $ratingsmenuused = false;
2362 if (!empty($ratings) and !empty($USER->id)) {
2363 echo '<div class="ratings">';
2364 $useratings = true;
2365 if ($ratings->assesstimestart and $ratings->assesstimefinish) {
2366 if ($post->created < $ratings->assesstimestart or $post->created > $ratings->assesstimefinish) {
2367 $useratings = false;
2370 if ($useratings) {
2371 $mypost = ($USER->id == $post->userid);
2373 $canviewallratings = has_capability('mod/forum:viewanyrating', $modcontext);
2375 if ($canviewallratings and !$mypost) {
2376 forum_print_ratings_mean($post->id, $ratings->scale, $canviewallratings);
2377 if (!empty($ratings->allow)) {
2378 echo '&nbsp;';
2379 forum_print_rating_menu($post->id, $USER->id, $ratings->scale);
2380 $ratingsmenuused = true;
2383 } else if ($mypost) {
2384 forum_print_ratings_mean($post->id, $ratings->scale, true);
2386 } else if (!empty($ratings->allow) ) {
2387 forum_print_rating_menu($post->id, $USER->id, $ratings->scale);
2388 $ratingsmenuused = true;
2391 echo '</div>';
2394 // Link to post if required
2396 if ($link) {
2397 echo '<div class="link">';
2398 if ($post->replies == 1) {
2399 $replystring = get_string('repliesone', 'forum', $post->replies);
2400 } else {
2401 $replystring = get_string('repliesmany', 'forum', $post->replies);
2403 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.
2404 get_string('discussthistopic', 'forum').'</a>&nbsp;('.$replystring.')';
2405 echo '</div>';
2408 if ($footer) {
2409 echo '<div class="footer">'.$footer.'</div>';
2411 echo '</td></tr></table>'."\n\n";
2413 if ($istracked && !$CFG->forum_usermarksread && !empty($post->forum)) {
2414 forum_tp_mark_post_read($USER->id, $post, $post->forum);
2417 return $ratingsmenuused;
2422 * This function prints the overview of a discussion in the forum listing.
2423 * It needs some discussion information and some post information, these
2424 * happen to be combined for efficiency in the $post parameter by the function
2425 * that calls this one: forum_print_latest_discussions()
2427 * @param object $post The post object (passed by reference for speed).
2428 * @param object $forum The forum object.
2429 * @param int $group Current group.
2430 * @param string $datestring Format to use for the dates.
2431 * @param boolean $cantrack Is tracking enabled for this forum.
2432 * @param boolean $forumtracked Is the user tracking this forum.
2433 * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
2435 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
2436 $cantrack=true, $forumtracked=true, $canviewparticipants=true) {
2438 global $USER, $CFG;
2440 static $rowcount;
2441 static $strmarkalldread;
2444 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
2445 error('Course Module ID was incorrect');
2447 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2450 if (!isset($rowcount)) {
2451 $rowcount = 0;
2452 $strmarkalldread = get_string('markalldread', 'forum');
2453 } else {
2454 $rowcount = ($rowcount + 1) % 2;
2457 $post->subject = format_string($post->subject,true);
2459 echo "\n\n";
2460 echo '<tr class="discussion r'.$rowcount.'">';
2462 // Topic
2463 echo '<td class="topic starter">';
2464 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.$post->subject.'</a>';
2465 echo "</td>\n";
2467 // Picture
2468 echo '<td class="picture">';
2469 print_user_picture($post->userid, $forum->course, $post->picture);
2470 echo "</td>\n";
2472 // User name
2473 $fullname = fullname($post, has_capability('moodle/site:viewfullnames', $modcontext));
2474 echo '<td class="author">';
2475 echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->userid.'&amp;course='.$forum->course.'">'.$fullname.'</a>';
2476 echo "</td>\n";
2478 // Group picture
2479 if ($group !== -1) { // Groups are active - group is a group data object or NULL
2480 echo '<td class="picture group">';
2481 if (!empty($group->picture) and empty($group->hidepicture)) {
2482 print_group_picture($group, $forum->course, false, false, true);
2483 } else if (isset($group->id)) {
2484 if($canviewparticipants) {
2485 echo '<a href="'.$CFG->wwwroot.'/user/index.php?id='.$forum->course.'&amp;group='.$group->id.'">'.$group->name.'</a>';
2486 } else {
2487 echo $group->name;
2490 echo "</td>\n";
2493 if (has_capability('mod/forum:viewdiscussion', $modcontext)) { // Show the column with replies
2494 echo '<td class="replies">';
2495 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
2496 echo $post->replies.'</a>';
2497 echo "</td>\n";
2499 if ($cantrack) {
2500 echo '<td class="replies">';
2501 if ($forumtracked) {
2502 if ($post->unread > 0) {
2503 echo '<span class="unread">';
2504 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#unread">';
2505 echo $post->unread;
2506 echo '</a>';
2507 echo '<a title="'.$strmarkalldread.'" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
2508 $forum->id.'&amp;d='.$post->discussion.'&amp;mark=read&amp;returnpage=view.php">' .
2509 '<img src="'.$CFG->pixpath.'/t/clear.gif" class="iconsmall" alt="'.$strmarkalldread.'" /></a>';
2510 echo '</span>';
2511 } else {
2512 echo '<span class="read">';
2513 echo $post->unread;
2514 echo '</span>';
2516 } else {
2517 echo '<span class="read">';
2518 echo '-';
2519 echo '</span>';
2521 echo "</td>\n";
2525 echo '<td class="lastpost">';
2526 $usedate = (empty($post->timemodified)) ? $post->modified : $post->timemodified; // Just in case
2527 $parenturl = (empty($post->lastpostid)) ? '' : '&amp;parent='.$post->lastpostid;
2528 $usermodified->id = $post->usermodified;
2529 $usermodified->firstname = $post->umfirstname;
2530 $usermodified->lastname = $post->umlastname;
2531 echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->usermodified.'&amp;course='.$forum->course.'">'.
2532 fullname($usermodified).'</a><br />';
2533 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.$parenturl.'">'.
2534 userdate($usedate, $datestring).'</a>';
2535 echo "</td>\n";
2537 echo "</tr>\n\n";
2543 * Given a post object that we already know has a long message
2544 * this function truncates the message nicely to the first
2545 * sane place between $CFG->forum_longpost and $CFG->forum_shortpost
2547 function forum_shorten_post($message) {
2549 global $CFG;
2551 $i = 0;
2552 $tag = false;
2553 $length = strlen($message);
2554 $count = 0;
2555 $stopzone = false;
2556 $truncate = 0;
2558 for ($i=0; $i<$length; $i++) {
2559 $char = $message[$i];
2561 switch ($char) {
2562 case "<":
2563 $tag = true;
2564 break;
2565 case ">":
2566 $tag = false;
2567 break;
2568 default:
2569 if (!$tag) {
2570 if ($stopzone) {
2571 if ($char == ".") {
2572 $truncate = $i+1;
2573 break 2;
2576 $count++;
2578 break;
2580 if (!$stopzone) {
2581 if ($count > $CFG->forum_shortpost) {
2582 $stopzone = true;
2587 if (!$truncate) {
2588 $truncate = $i;
2591 return substr($message, 0, $truncate);
2596 * Print the multiple ratings on a post given to the current user by others.
2597 * Scale is an array of ratings
2599 function forum_print_ratings_mean($postid, $scale, $link=true) {
2601 static $strrate;
2603 $mean = forum_get_ratings_mean($postid, $scale);
2605 if ($mean !== "") {
2607 if (empty($strratings)) {
2608 $strratings = get_string("ratings", "forum");
2611 echo "$strratings: ";
2612 if ($link) {
2613 link_to_popup_window ("/mod/forum/report.php?id=$postid", "ratings", $mean, 400, 600);
2614 } else {
2615 echo "$mean ";
2622 * Return the mean rating of a post given to the current user by others.
2623 * Scale is an array of possible ratings in the scale
2624 * Ratings is an optional simple array of actual ratings (just integers)
2626 function forum_get_ratings_mean($postid, $scale, $ratings=NULL) {
2628 if (!$ratings) {
2629 $ratings = array();
2630 if ($rates = get_records("forum_ratings", "post", $postid)) {
2631 foreach ($rates as $rate) {
2632 $ratings[] = $rate->rating;
2637 $count = count($ratings);
2639 if ($count == 0) {
2640 return "";
2642 } else if ($count == 1) {
2643 return $scale[$ratings[0]];
2645 } else {
2646 $total = 0;
2647 foreach ($ratings as $rating) {
2648 $total += $rating;
2650 $mean = round( ((float)$total/(float)$count) + 0.001); // Little fudge factor so that 0.5 goes UP
2652 if (isset($scale[$mean])) {
2653 return $scale[$mean]." ($count)";
2654 } else {
2655 return "$mean ($count)"; // Should never happen, hopefully
2661 * Return a summary of post ratings given to the current user by others.
2662 * Scale is an array of possible ratings in the scale
2663 * Ratings is an optional simple array of actual ratings (just integers)
2665 function forum_get_ratings_summary($postid, $scale, $ratings=NULL) {
2667 if (!$ratings) {
2668 $ratings = array();
2669 if ($rates = get_records("forum_ratings", "post", $postid)) {
2670 foreach ($rates as $rate) {
2671 $rating[] = $rate->rating;
2677 if (!$count = count($ratings)) {
2678 return "";
2682 foreach ($scale as $key => $scaleitem) {
2683 $sumrating[$key] = 0;
2686 foreach ($ratings as $rating) {
2687 $sumrating[$rating]++;
2690 $summary = "";
2691 foreach ($scale as $key => $scaleitem) {
2692 $summary = $sumrating[$key].$summary;
2693 if ($key > 1) {
2694 $summary = "/$summary";
2697 return $summary;
2701 * Print the menu of ratings as part of a larger form.
2702 * If the post has already been - set that value.
2703 * Scale is an array of ratings
2705 function forum_print_rating_menu($postid, $userid, $scale) {
2707 static $strrate;
2709 if (!$rating = get_record("forum_ratings", "userid", $userid, "post", $postid)) {
2710 $rating->rating = FORUM_UNSET_POST_RATING;
2713 if (empty($strrate)) {
2714 $strrate = get_string("rate", "forum");
2716 $scale = array(FORUM_UNSET_POST_RATING => $strrate.'...') + $scale;
2717 choose_from_menu($scale, $postid, $rating->rating, '');
2721 * Print the drop down that allows the user to select how they want to have
2722 * the discussion displayed.
2723 * @param $id - forum id if $forumtype is 'single',
2724 * discussion id for any other forum type
2725 * @param $mode - forum layout mode
2726 * @param $forumtype - optional
2728 function forum_print_mode_form($id, $mode, $forumtype='') {
2729 global $FORUM_LAYOUT_MODES;
2731 if ($forumtype == 'single') {
2732 popup_form("view.php?f=$id&amp;mode=", $FORUM_LAYOUT_MODES, "mode", $mode, "");
2733 } else {
2734 popup_form("discuss.php?d=$id&amp;mode=", $FORUM_LAYOUT_MODES, "mode", $mode, "");
2741 function forum_search_form($course, $search='') {
2742 global $CFG;
2744 $output = '<div class="forumsearch">';
2745 $output .= '<form action="'.$CFG->wwwroot.'/mod/forum/search.php" style="display:inline">';
2746 $output .= '<fieldset class="invisiblefieldset">';
2747 $output .= helpbutton('search', get_string('search'), 'moodle', true, false, '', true);
2748 $output .= '<input name="search" type="text" size="18" value="'.$search.'" alt="search" />';
2749 $output .= '<input value="'.get_string('searchforums', 'forum').'" type="submit" />';
2750 $output .= '<input name="id" type="hidden" value="'.$course->id.'" />';
2751 $output .= '</fieldset>';
2752 $output .= '</form>';
2753 $output .= '</div>';
2755 return $output;
2762 function forum_set_return() {
2763 global $CFG, $SESSION;
2765 if (! isset($SESSION->fromdiscussion)) {
2766 if (!empty($_SERVER['HTTP_REFERER'])) {
2767 $referer = $_SERVER['HTTP_REFERER'];
2768 } else {
2769 $referer = "";
2771 // If the referer is NOT a login screen then save it.
2772 if (! strncasecmp("$CFG->wwwroot/login", $referer, 300)) {
2773 $SESSION->fromdiscussion = $_SERVER["HTTP_REFERER"];
2782 function forum_go_back_to($default) {
2783 global $SESSION;
2785 if (!empty($SESSION->fromdiscussion)) {
2786 $returnto = $SESSION->fromdiscussion;
2787 unset($SESSION->fromdiscussion);
2788 return $returnto;
2789 } else {
2790 return $default;
2795 * Creates a directory file name, suitable for make_upload_directory()
2797 function forum_file_area_name($post) {
2798 global $CFG;
2800 return "$post->course/$CFG->moddata/forum/$post->forum/$post->id";
2806 function forum_file_area($post) {
2807 return make_upload_directory( forum_file_area_name($post) );
2813 function forum_delete_old_attachments($post, $exception="") {
2816 * Deletes all the user files in the attachments area for a post
2817 * EXCEPT for any file named $exception
2819 if ($basedir = forum_file_area($post)) {
2820 if ($files = get_directory_list($basedir)) {
2821 foreach ($files as $file) {
2822 if ($file != $exception) {
2823 unlink("$basedir/$file");
2824 notify("Existing file '$file' has been deleted!");
2828 if (!$exception) { // Delete directory as well, if empty
2829 rmdir("$basedir");
2835 * Given a discussion object that is being moved to forumid,
2836 * this function checks all posts in that discussion
2837 * for attachments, and if any are found, these are
2838 * moved to the new forum directory.
2840 function forum_move_attachments($discussion, $forumid) {
2842 global $CFG;
2844 require_once($CFG->dirroot.'/lib/uploadlib.php');
2846 $return = true;
2848 if ($posts = get_records_select("forum_posts", "discussion = '$discussion->id' AND attachment <> ''")) {
2849 foreach ($posts as $oldpost) {
2850 $oldpost->course = $discussion->course;
2851 $oldpost->forum = $discussion->forum;
2852 $oldpostdir = "$CFG->dataroot/".forum_file_area_name($oldpost);
2853 if (is_dir($oldpostdir)) {
2854 $newpost = $oldpost;
2855 $newpost->forum = $forumid;
2856 $newpostdir = forum_file_area_name($newpost);
2857 // take off the last directory because otherwise we're renaming to a directory that already exists
2858 // and this is unhappy in certain situations, eg over an nfs mount and potentially on windows too.
2859 make_upload_directory(substr($newpostdir,0,strrpos($newpostdir,'/')));
2860 $newpostdir = $CFG->dataroot.'/'.forum_file_area_name($newpost);
2861 $files = get_directory_list($oldpostdir); // get it before we rename it.
2862 if (! @rename($oldpostdir, $newpostdir)) {
2863 $return = false;
2865 foreach ($files as $file) {
2866 clam_change_log($oldpostdir.'/'.$file,$newpostdir.'/'.$file);
2871 return $return;
2875 * if return=html, then return a html string.
2876 * if return=text, then return a text-only string.
2877 * otherwise, print HTML for non-images, and return image HTML
2879 function forum_print_attachments($post, $return=NULL) {
2881 global $CFG;
2883 $filearea = forum_file_area_name($post);
2885 $imagereturn = "";
2886 $output = "";
2888 if ($basedir = forum_file_area($post)) {
2889 if ($files = get_directory_list($basedir)) {
2890 $strattachment = get_string("attachment", "forum");
2891 foreach ($files as $file) {
2892 $icon = mimeinfo("icon", $file);
2893 $type = mimeinfo("type", $file);
2894 if ($CFG->slasharguments) {
2895 $ffurl = "$CFG->wwwroot/file.php/$filearea/$file";
2896 } else {
2897 $ffurl = "$CFG->wwwroot/file.php?file=/$filearea/$file";
2899 $image = "<img src=\"$CFG->pixpath/f/$icon\" class=\"icon\" alt=\"\" />";
2901 if ($return == "html") {
2902 $output .= "<a href=\"$ffurl\">$image</a> ";
2903 $output .= "<a href=\"$ffurl\">$file</a><br />";
2905 } else if ($return == "text") {
2906 $output .= "$strattachment $file:\n$ffurl\n";
2908 } else {
2909 if (in_array($type, array('image/gif', 'image/jpeg', 'image/png'))) { // Image attachments don't get printed as links
2910 $imagereturn .= "<br /><img src=\"$ffurl\" alt=\"\" />";
2911 } else {
2912 echo "<a href=\"$ffurl\">$image</a> ";
2913 echo filter_text("<a href=\"$ffurl\">$file</a><br />");
2920 if ($return) {
2921 return $output;
2924 return $imagereturn;
2927 * If successful, this function returns the name of the file
2928 * @param $post is a full post record, including course and forum
2929 * @param $newfile is a full upload array from $_FILES
2930 * @param $message is a string to hold the messages.
2936 function forum_add_attachment($post, $inputname,&$message) {
2938 global $CFG;
2940 if (!$forum = get_record("forum", "id", $post->forum)) {
2941 return "";
2944 if (!$course = get_record("course", "id", $forum->course)) {
2945 return "";
2948 require_once($CFG->dirroot.'/lib/uploadlib.php');
2949 $um = new upload_manager($inputname,true,false,$course,false,$forum->maxbytes,true,true);
2950 $dir = forum_file_area_name($post);
2951 if ($um->process_file_uploads($dir)) {
2952 $message .= $um->get_errors();
2953 return $um->get_new_filename();
2955 $message .= $um->get_errors();
2956 return null;
2962 function forum_add_new_post($post,&$message) {
2964 global $USER, $CFG;
2966 $post->created = $post->modified = time();
2967 $post->mailed = "0";
2968 $post->userid = $USER->id;
2969 $post->attachment = "";
2971 if (! $post->id = insert_record("forum_posts", $post)) {
2972 return false;
2975 if ($post->attachment = forum_add_attachment($post, 'attachment',$message)) {
2976 set_field("forum_posts", "attachment", $post->attachment, "id", $post->id);
2979 // Update discussion modified date
2980 set_field("forum_discussions", "timemodified", $post->modified, "id", $post->discussion);
2981 set_field("forum_discussions", "usermodified", $post->userid, "id", $post->discussion);
2983 if (forum_tp_can_track_forums($post->forum) && forum_tp_is_tracked($post->forum)) {
2984 forum_tp_mark_post_read($post->userid, $post, $post->forum);
2987 return $post->id;
2993 function forum_update_post($post,&$message) {
2995 global $USER, $CFG;
2997 $post->modified = time();
2999 if (!$post->parent) { // Post is a discussion starter - update discussion title too
3000 set_field("forum_discussions", "name", $post->subject, "id", $post->discussion);
3003 if ($newfilename = forum_add_attachment($post, 'attachment',$message)) {
3004 $post->attachment = $newfilename;
3005 } else {
3006 unset($post->attachment);
3009 // Update discussion modified date
3010 set_field("forum_discussions", "timemodified", $post->modified, "id", $post->discussion);
3011 set_field("forum_discussions", "usermodified", $post->userid, "id", $post->discussion);
3013 if (forum_tp_can_track_forums($post->forum) && forum_tp_is_tracked($post->forum)) {
3014 forum_tp_mark_post_read($post->userid, $post, $post->forum);
3017 return update_record("forum_posts", $post);
3021 * Given an object containing all the necessary data,
3022 * create a new discussion and return the id
3024 function forum_add_discussion($discussion,&$message) {
3026 GLOBAL $USER, $CFG;
3028 $timenow = time();
3030 // The first post is stored as a real post, and linked
3031 // to from the discuss entry.
3033 $post->discussion = 0;
3034 $post->parent = 0;
3035 $post->userid = $USER->id;
3036 $post->created = $timenow;
3037 $post->modified = $timenow;
3038 $post->mailed = 0;
3039 $post->subject = $discussion->name;
3040 $post->message = $discussion->intro;
3041 $post->attachment = "";
3042 $post->forum = $discussion->forum;
3043 $post->course = $discussion->course;
3044 $post->format = $discussion->format;
3045 $post->mailnow = $discussion->mailnow;
3047 if (! $post->id = insert_record("forum_posts", $post) ) {
3048 return 0;
3051 if ($post->attachment = forum_add_attachment($post, 'attachment',$message)) {
3052 set_field("forum_posts", "attachment", $post->attachment, "id", $post->id); //ignore errors
3055 // Now do the main entry for the discussion,
3056 // linking to this first post
3058 $discussion->firstpost = $post->id;
3059 $discussion->timemodified = $timenow;
3060 $discussion->usermodified = $post->userid;
3061 $discussion->userid = $USER->id;
3063 if (! $post->discussion = insert_record("forum_discussions", $discussion) ) {
3064 delete_records("forum_posts", "id", $post->id);
3065 return 0;
3068 // Finally, set the pointer on the post.
3069 if (! set_field("forum_posts", "discussion", $post->discussion, "id", $post->id)) {
3070 delete_records("forum_posts", "id", $post->id);
3071 delete_records("forum_discussions", "id", $post->discussion);
3072 return 0;
3075 if (forum_tp_can_track_forums($post->forum) && forum_tp_is_tracked($post->forum)) {
3076 forum_tp_mark_post_read($post->userid, $post, $post->forum);
3079 return $post->discussion;
3086 function forum_delete_discussion($discussion, $fulldelete=false) {
3087 // $discussion is a discussion record object
3089 $result = true;
3091 if ($posts = get_records("forum_posts", "discussion", $discussion->id)) {
3092 foreach ($posts as $post) {
3093 $post->course = $discussion->course;
3094 $post->forum = $discussion->forum;
3095 if (! delete_records("forum_ratings", "post", "$post->id")) {
3096 $result = false;
3098 if (! forum_delete_post($post, $fulldelete)) {
3099 $result = false;
3104 forum_tp_delete_read_records(-1, -1, $discussion->id);
3106 if (! delete_records("forum_discussions", "id", "$discussion->id")) {
3107 $result = false;
3110 return $result;
3117 function forum_delete_post($post, $children=false) {
3118 if ($childposts = get_records('forum_posts', 'parent', $post->id)) {
3119 if ($children) {
3120 foreach ($childposts as $childpost) {
3121 forum_delete_post($childpost, true);
3123 } else {
3124 return false;
3127 if (delete_records("forum_posts", "id", $post->id)) {
3128 delete_records("forum_ratings", "post", $post->id); // Just in case
3130 forum_tp_delete_read_records(-1, $post->id);
3132 if ($post->attachment) {
3133 $discussion = get_record("forum_discussions", "id", $post->discussion);
3134 $post->course = $discussion->course;
3135 $post->forum = $discussion->forum;
3136 forum_delete_old_attachments($post);
3139 // Just in case we are deleting the last post
3140 forum_discussion_update_last_post($post->discussion);
3142 return true;
3144 return false;
3150 function forum_count_replies($post, $children=true) {
3151 $count = 0;
3153 if ($children) {
3154 if ($childposts = get_records('forum_posts', 'parent', $post->id)) {
3155 foreach ($childposts as $childpost) {
3156 $count ++; // For this child
3157 $count += forum_count_replies($childpost, true);
3160 } else {
3161 $count += count_records('forum_posts', 'parent', $post->id);
3164 return $count;
3171 function forum_forcesubscribe($forumid, $value=1) {
3172 return set_field("forum", "forcesubscribe", $value, "id", $forumid);
3178 function forum_is_forcesubscribed($forum) {
3179 if (isset($forum->forcesubscribe)) { // then we use that
3180 return ($forum->forcesubscribe == FORUM_FORCESUBSCRIBE);
3181 } else { // Check the database
3182 return (get_field('forum', 'forcesubscribe', 'id', $forum) == FORUM_FORCESUBSCRIBE);
3189 function forum_is_subscribed($userid, $forumid) {
3190 if (forum_is_forcesubscribed($forumid)) {
3191 return true;
3193 return record_exists("forum_subscriptions", "userid", $userid, "forum", $forumid);
3197 * Adds user to the subscriber list
3199 function forum_subscribe($userid, $forumid) {
3201 if (record_exists("forum_subscriptions", "userid", $userid, "forum", $forumid)) {
3202 return true;
3205 $sub->userid = $userid;
3206 $sub->forum = $forumid;
3208 return insert_record("forum_subscriptions", $sub);
3212 * Removes user from the subscriber list
3214 function forum_unsubscribe($userid, $forumid) {
3215 return delete_records("forum_subscriptions", "userid", $userid, "forum", $forumid);
3219 * Given a new post, subscribes or unsubscribes as appropriate.
3220 * Returns some text which describes what happened.
3222 function forum_post_subscription($post) {
3224 global $USER;
3226 $subscribed=forum_is_subscribed($USER->id, $post->forum);
3227 if ((isset($post->subscribe) && $post->subscribe && $subscribed)
3228 || (!$post->subscribe && !$subscribed)) {
3229 return "";
3232 if (!$forum = get_record("forum", "id", $post->forum)) {
3233 return "";
3236 $info->name = fullname($USER);
3237 $info->forum = $forum->name;
3239 if (!empty($post->subscribe)) {
3240 forum_subscribe($USER->id, $post->forum);
3241 return "<p>".get_string("nowsubscribed", "forum", $info)."</p>";
3244 forum_unsubscribe($USER->id, $post->forum);
3245 return "<p>".get_string("nownotsubscribed", "forum", $info)."</p>";
3249 * Generate and return the subscribe or unsubscribe link for a forum.
3250 * @param object $forum the forum. Fields used are $forum->id and $forum->forcesubscribe.
3251 * @param object $context the context object for this forum.
3252 * @param array $messages text used for the link in its various states
3253 * (subscribed, unsubscribed, forcesubscribed or cantsubscribe).
3254 * Any strings not passed in are taken from the $defaultmessages array
3255 * at the top of the function.
3256 * @param
3258 function forum_get_subscribe_link($forum, $context, $messages = array(), $cantaccessagroup = false, $fakelink=true) {
3259 global $CFG, $USER;
3260 $defaultmessages = array(
3261 'subscribed' => get_string('unsubscribe', 'forum'),
3262 'unsubscribed' => get_string('subscribe', 'forum'),
3263 'cantaccessgroup' => get_string('no'),
3264 'forcesubscribed' => get_string('everyoneissubscribed', 'forum'),
3265 'cantsubscribe' => get_string('disallowsubscribe','forum')
3267 $messages = $messages + $defaultmessages;
3269 if (forum_is_forcesubscribed($forum->id)) {
3270 return $messages['forcesubscribed'];
3271 } else if ($forum->forcesubscribe == FORUM_DISALLOWSUBSCRIBE && !has_capability('mod/forum:managesubscriptions', $context)) {
3272 return $messages['cantsubscribe'];
3273 } else if ($cantaccessagroup) {
3274 return $messages['cantaccessgroup'];
3275 } else {
3276 if (forum_is_subscribed($USER->id, $forum->id)) {
3277 $linktext = $messages['subscribed'];
3278 $linktitle = get_string('subscribestop', 'forum');
3279 } else {
3280 $linktext = $messages['unsubscribed'];
3281 $linktitle = get_string('subscribestart', 'forum');
3284 $link = '';
3285 if ($fakelink) {
3286 $link .= '<script type="text/javascript">';
3287 $link .= '//<![CDATA['."\n";
3288 $link .= 'document.getElementById("subscriptionlink").innerHTML = "<a title=\"' . $linktitle . '\" href=\"' . $CFG->wwwroot .
3289 '/mod/forum/subscribe.php?id=' . $forum->id . '\">' . $linktext . '<\/a>";';
3290 $link .= '//]]>';
3291 $link .= '</script>';
3292 // use <noscript> to print button in case javascript is not enabled
3293 $link .= '<noscript>';
3295 $link .= print_single_button($CFG->wwwroot . '/mod/forum/subscribe.php?id=' . $forum->id,
3296 '', $linktext, 'post', '_self', true, $linktitle);
3297 if ($fakelink) {
3298 $link .= '</noscript>';
3301 return $link;
3307 * Generate and return the track or no track link for a forum.
3308 * @param object $forum the forum. Fields used are $forum->id and $forum->forcesubscribe.
3310 function forum_get_tracking_link($forum, $messages=array(), $fakelink=true) {
3311 global $CFG, $USER;
3313 static $strnotrackforum, $strtrackforum;
3315 if (isset($messages['trackforum'])) {
3316 $strtrackforum = $messages['trackforum'];
3318 if (isset($messages['notrackforum'])) {
3319 $strnotrackforum = $messages['notrackforum'];
3321 if (empty($strtrackforum)) {
3322 $strtrackforum = get_string('trackforum', 'forum');
3324 if (empty($strnotrackforum)) {
3325 $strnotrackforum = get_string('notrackforum', 'forum');
3328 if (forum_tp_is_tracked($forum, $USER->id)) {
3329 $linktitle = $strnotrackforum;
3330 $linktext = $strtrackforum;
3331 } else {
3332 $linktitle = $strtrackforum;
3333 $linktext = $strnotrackforum;
3336 $link = '';
3337 if ($fakelink) {
3338 $link .= '<script type="text/javascript">';
3339 $link .= '//<![CDATA['."\n";
3340 $link .= 'document.getElementById("trackinglink").innerHTML = "<a title=\"' . $linktitle . '\" href=\"' . $CFG->wwwroot .
3341 '/mod/forum/settracking.php?id=' . $forum->id . '\">' . $linktext . '<\/a>";'."\n";
3342 $link .= '//]]>'."\n";
3343 $link .= '</script>';
3344 // use <noscript> to print button in case javascript is not enabled
3345 $link .= '<noscript>';
3347 $link .= print_single_button($CFG->wwwroot . '/mod/forum/settracking.php?id=' . $forum->id,
3348 '', $linktext, 'post', '_self', true, $linktitle);
3349 if ($fakelink) {
3350 $link .= '</noscript>';
3353 return $link;
3361 function forum_user_has_posted_discussion($forumid, $userid) {
3362 if ($discussions = forum_get_discussions($forumid, '', $userid)) {
3363 return true;
3364 } else {
3365 return false;
3372 function forum_discussions_user_has_posted_in($forumid, $userid) {
3373 global $CFG;
3375 $haspostedsql = "SELECT d.id AS id,
3377 FROM {$CFG->prefix}forum_posts p,
3378 {$CFG->prefix}forum_discussions d
3379 WHERE p.discussion = d.id
3380 AND d.forum = $forumid
3381 AND p.userid = $userid";
3383 return get_records_sql($haspostedsql);
3389 function forum_user_has_posted($forumid, $did, $userid) {
3390 return record_exists('forum_posts','discussion',$did,'userid',$userid);
3396 function forum_user_can_post_discussion($forum, $currentgroup=-1, $groupmode=-1, $cm=NULL, $context=NULL) {
3397 // $forum is an object
3398 global $USER, $SESSION;
3400 if (!$cm) {
3401 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3402 error('Course Module ID was incorrect');
3405 if (!$context) {
3406 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
3409 if ($currentgroup == -1) {
3410 $currentgroup = get_current_group($cm->course);
3413 if ($groupmode == -1) {
3414 if (!$course = get_record('course', 'id', $cm->course)) {
3415 error('Can not find course');
3417 $groupmode = groups_get_activity_groupmode($cm);
3420 if ($forum->type == 'news') {
3421 $capname = 'mod/forum:addnews';
3422 } else {
3423 $capname = 'mod/forum:startdiscussion';
3426 if (!has_capability($capname, $context)) {
3427 return false;
3430 if ($forum->type == 'eachuser') {
3431 if (forum_user_has_posted_discussion($forum->id, $USER->id)) {
3432 return false;
3436 if (!$groupmode or has_capability('moodle/site:accessallgroups', $context)) {
3437 return true;
3440 if ($currentgroup) {
3441 return groups_is_member($currentgroup);
3442 } else {
3443 //else it might be group 0 in visible mode
3444 if ($groupmode == VISIBLEGROUPS){
3445 return true;
3446 } else {
3447 return false;
3453 * This function checks whether the user can reply to posts in a forum
3454 * discussion. Use forum_user_can_post_discussion() to check whether the user
3455 * can start dicussions.
3456 * @param $forum - forum object
3457 * @param $user - user object
3459 function forum_user_can_post($forum, $user=NULL, $cm=NULL, $context=NULL) {
3461 if (!$cm) {
3462 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3463 error('Course Module ID was incorrect');
3466 if (!$context) {
3467 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
3470 if ($forum->type == 'news') {
3471 $capname = 'mod/forum:replynews';
3472 } else {
3473 $capname = 'mod/forum:replypost';
3476 if (!empty($user)) {
3477 $canreply = has_capability($capname, $context, $user->id, false)
3478 && !has_capability('moodle/legacy:guest', $context, $user->id, false);
3479 } else {
3480 $canreply = has_capability($capname, $context, NULL, false)
3481 && !has_capability('moodle/legacy:guest', $context, NULL, false);
3484 return $canreply;
3488 //checks to see if a user can view a particular post
3489 function forum_user_can_view_post($post, $course, $cm, $forum, $discussion, $user=NULL){
3491 global $CFG, $USER;
3493 if (!$user){
3494 $user = $USER;
3497 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3498 if (!has_capability('mod/forum:viewdiscussion', $modcontext)) {
3499 return false;
3502 // If it's a grouped discussion, make sure the user is a member
3503 if ($discussion->groupid > 0) {
3504 $groupmode = groups_get_activity_groupmode($cm);
3505 if ($groupmode == SEPARATEGROUPS) {
3506 return groups_is_member($discussion->groupid) || has_capability('moodle/site:accessallgroups', $modcontext);
3509 return true;
3516 function forum_user_can_see_discussion($forum, $discussion, $context, $user=NULL) {
3517 global $USER;
3519 if (empty($user) || empty($user->id)) {
3520 $user = $USER;
3523 // retrieve objects (yuk)
3524 if (is_numeric($forum)) {
3525 if (!$forum = get_record('forum','id',$forum)) {
3526 return false;
3529 if (is_numeric($discussion)) {
3530 if (!$discussion = get_record('forum_discussions','id',$discussion)) {
3531 return false;
3535 if (!has_capability('mod/forum:viewdiscussion', $context)) {
3536 return false;
3539 if ($forum->type == 'qanda' &&
3540 !forum_user_has_posted($forum->id, $discussion->id, $user->id) &&
3541 !has_capability('mod/forum:viewqandawithoutposting', $context)) {
3542 return false;
3544 return true;
3551 function forum_user_can_see_post($forum, $discussion, $post, $user=NULL) {
3552 global $USER;
3554 // retrieve objects (yuk)
3555 if (is_numeric($forum)) {
3556 if (!$forum = get_record('forum','id',$forum)) {
3557 return false;
3561 if (is_numeric($discussion)) {
3562 if (!$discussion = get_record('forum_discussions','id',$discussion)) {
3563 return false;
3566 if (is_numeric($post)) {
3567 if (!$post = get_record('forum_posts','id',$post)) {
3568 return false;
3571 if (!isset($post->id) && isset($post->parent)) {
3572 $post->id = $post->parent;
3575 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3576 error('Course Module ID was incorrect');
3578 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
3580 if (empty($user) || empty($user->id)) {
3581 $user = $USER;
3584 if (!has_capability('mod/forum:viewdiscussion', $context, $user->id)) {
3585 return false;
3588 if (!groups_course_module_visible($cm, $user->id)) {
3589 return false;
3592 if ($forum->type == 'qanda') {
3593 $firstpost = forum_get_firstpost_from_discussion($discussion->id);
3595 return (forum_user_has_posted($forum->id,$discussion->id,$user->id) ||
3596 $firstpost->id == $post->id ||
3597 has_capability('mod/forum:viewqandawithoutposting', $context, false, $user->id));
3599 return true;
3604 * Prints the discussion view screen for a forum.
3606 * @param object $course The current course object.
3607 * @param object $forum Forum to be printed.
3608 * @param int $maxdiscussions The maximum number of discussions per page(optional).
3609 * @param string $displayformat The display format to use (optional).
3610 * @param string $sort Sort arguments for database query (optional).
3611 * @param int $currentgroup Group to display discussions for (optional).
3612 * @param int $groupmode Group mode of the forum (optional).
3613 * @param int $page Page mode, page to display (optional).
3616 function forum_print_latest_discussions($course, $forum, $maxdiscussions=5, $displayformat='plain', $sort='',
3617 $currentgroup=-1, $groupmode=-1, $page=-1) {
3618 global $CFG, $USER;
3620 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3621 error('Course Module ID was incorrect');
3623 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
3626 // Sort out some defaults
3628 if ((!$maxdiscussions) && ($displayformat == 'plain')) {
3629 $displayformat = 'header'; // Abbreviate display by default
3632 $fullpost = false;
3633 if ($displayformat == 'plain') {
3634 $fullpost = true;
3638 // Decide if current user is allowed to see ALL the current discussions or not
3640 // First check the group stuff
3641 $groupmode = groups_get_activity_groupmode($cm);
3642 $currentgroup = groups_get_activity_group($cm);
3644 // If the user can post discussions, then this is a good place to put the
3645 // button for it. We do not show the button if we are showing site news
3646 // and the current user is a guest.
3648 if (forum_user_can_post_discussion($forum, $currentgroup, $groupmode, $cm, $context) ||
3649 ($forum->type != 'news' && has_capability('moodle/legacy:guest', $context, NULL, false)) ) {
3651 echo '<div class="singlebutton forumaddnew">';
3652 echo "<form id=\"newdiscussionform\" method=\"get\" action=\"$CFG->wwwroot/mod/forum/post.php\">";
3653 echo '<div>';
3654 echo "<input type=\"hidden\" name=\"forum\" value=\"$forum->id\" />";
3655 echo '<input type="submit" value="';
3656 echo ($forum->type == 'news') ? get_string('addanewtopic', 'forum')
3657 : (($forum->type == 'qanda')
3658 ? get_string('addanewquestion','forum')
3659 : get_string('addanewdiscussion', 'forum'));
3660 echo '" />';
3661 echo '</div>';
3662 echo '</form>';
3663 echo "</div>\n";
3664 } else if (!isguestuser() and isloggedin() and $forum->type != 'news' and $groupmode == SEPARATEGROUPS and !groups_is_member($currentgroup)) {
3665 notify(get_string('cannotadddiscussion', 'forum'));
3669 // Get all the recent discussions we're allowed to see
3671 $getuserlastmodified = ($displayformat == 'header');
3673 if (! $discussions = forum_get_discussions($forum->id, $sort, 0, $fullpost, $currentgroup,0,$getuserlastmodified) ) {
3674 echo '<div class="forumnodiscuss">';
3675 if ($forum->type == 'news') {
3676 echo '('.get_string('nonews', 'forum').')';
3677 } else if ($forum->type == 'qanda') {
3678 echo '('.get_string('noquestions','forum').')';
3679 } else {
3680 echo '('.get_string('nodiscussions', 'forum').')';
3682 echo "</div>\n";
3683 return;
3686 // If no discussions then don't use paging (to avoid some divide by 0 errors)
3688 if ($maxdiscussions <= 0) {
3689 $page = -1;
3690 $maxdiscussions = 0;
3693 // If we want paging
3695 if ($page != -1) {
3696 ///Get the number of discussions found
3697 $numdiscussions = count($discussions);
3699 ///Show the paging bar
3700 print_paging_bar($numdiscussions, $page, $maxdiscussions, "view.php?f=$forum->id&amp;");
3702 //Calculate the page "window"
3703 $pagestart = ($page * $maxdiscussions) + 1;
3704 $pageend = $pagestart + $maxdiscussions - 1;
3708 $replies = forum_count_discussion_replies($forum->id);
3710 $canreply = forum_user_can_post($forum);
3711 $canviewparticipants = has_capability('moodle/course:viewparticipants',$context);
3713 $discussioncount = 0;
3714 $olddiscussionlink = false;
3715 $strdatestring = get_string('strftimerecentfull');
3717 // Check if the forum is tracked.
3718 if ($cantrack = forum_tp_can_track_forums($forum)) {
3719 $forumtracked = forum_tp_is_tracked($forum);
3720 } else {
3721 $forumtracked = false;
3724 if ($displayformat == 'header') {
3725 echo '<table cellspacing="0" class="forumheaderlist">';
3726 echo '<thead>';
3727 echo '<tr>';
3728 echo '<th class="header topic" scope="col">'.get_string('discussion', 'forum').'</th>';
3729 echo '<th class="header author" colspan="2" scope="col">'.get_string('startedby', 'forum').'</th>';
3730 if ($groupmode > 0) {
3731 echo '<th class="header group" scope="col">'.get_string('group').'</th>';
3733 if (has_capability('mod/forum:viewdiscussion', $context)) {
3734 echo '<th class="header replies" scope="col">'.get_string('replies', 'forum').'</th>';
3735 // If the forum can be tracked, display the unread column.
3736 if ($cantrack) {
3737 echo '<th class="header replies" scope="col">'.get_string('unread', 'forum');
3738 if ($forumtracked) {
3739 echo '&nbsp;<a title="'.get_string('markallread', 'forum').
3740 '" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
3741 $forum->id.'&amp;mark=read&amp;returnpage=view.php">'.
3742 '<img src="'.$CFG->pixpath.'/t/clear.gif" class="iconsmall" alt="'.get_string('markallread', 'forum').'" /></a>';
3744 echo '</th>';
3747 echo '<th class="header lastpost" scope="col">'.get_string('lastpost', 'forum').'</th>';
3748 echo '</tr>';
3749 echo '</thead>';
3750 echo '<tbody>';
3753 foreach ($discussions as $discussion) {
3754 $discussioncount++;
3756 if ($page != -1) { // We are using paging
3757 if ($discussioncount < $pagestart) { // Not there yet
3758 continue;
3760 if ($discussioncount > $pageend) { // All done, finish the loop
3761 break;
3763 //Without paging, old approach
3764 } else if ($maxdiscussions && ($discussioncount > $maxdiscussions)) {
3765 $olddiscussionlink = true;
3766 break;
3769 if (!empty($replies[$discussion->discussion])) {
3770 $discussion->replies = $replies[$discussion->discussion]->replies;
3771 $discussion->lastpostid = $replies[$discussion->discussion]->lastpostid;
3772 } else {
3773 $discussion->replies = 0;
3776 // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3777 // All posts are read in this case.
3778 if (!$forumtracked) {
3779 $discussion->unread = '-';
3780 } else if (empty($USER)) {
3781 $discussion->unread = 0;
3782 } else {
3783 $discussion->unread = forum_tp_count_discussion_unread_posts($USER->id, $discussion->discussion);
3786 if (!empty($USER->id)) {
3787 $ownpost = ($discussion->userid == $USER->id);
3788 } else {
3789 $ownpost=false;
3791 // Use discussion name instead of subject of first post
3792 $discussion->subject = $discussion->name;
3794 switch ($displayformat) {
3795 case 'header':
3796 if ($groupmode > 0) {
3797 if (isset($groups[$discussion->groupid])) {
3798 $group = $groups[$discussion->groupid];
3799 } else {
3800 $group = $groups[$discussion->groupid] = groups_get_group($discussion->groupid); //TODO:
3802 } else {
3803 $group = -1;
3805 forum_print_discussion_header($discussion, $forum, $group, $strdatestring, $cantrack, $forumtracked,
3806 $canviewparticipants);
3807 break;
3808 default:
3809 if ($canreply or $discussion->replies) {
3810 $link = true;
3811 } else {
3812 $link = false;
3815 $discussion->forum = $forum->id;
3817 forum_print_post($discussion, $course->id, $ownpost, $reply=0, $link, $assessed=false);
3818 break;
3822 if ($displayformat == "header") {
3823 echo '</tbody>';
3824 echo '</table>';
3827 if ($olddiscussionlink) {
3828 echo '<div class="forumolddiscuss">';
3829 echo '<a href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'&amp;showall=1">';
3830 echo get_string('olderdiscussions', 'forum').'</a> ...</div>';
3833 if ($page != -1) { ///Show the paging bar
3834 print_paging_bar($numdiscussions, $page, $maxdiscussions, "view.php?f=$forum->id&amp;");
3842 function forum_print_discussion($course, $forum, $discussion, $post, $mode, $canreply=NULL, $canrate=false) {
3844 global $USER, $CFG;
3846 if (!empty($USER->id)) {
3847 $ownpost = ($USER->id == $post->userid);
3848 } else {
3849 $ownpost = false;
3851 if ($canreply === NULL) {
3852 $reply = forum_user_can_post($forum);
3853 } else {
3854 $reply = $canreply;
3857 $ratings = NULL;
3858 $ratingsmenuused = false;
3859 $ratingsformused = false;
3860 if ($forum->assessed and !empty($USER->id)) {
3861 if ($ratings->scale = make_grades_menu($forum->scale)) {
3862 $ratings->assesstimestart = $forum->assesstimestart;
3863 $ratings->assesstimefinish = $forum->assesstimefinish;
3864 $ratings->allow = $canrate;
3866 if ($ratings->allow) {
3867 echo '<form id="form" method="post" action="rate.php">';
3868 echo '<div class="ratingform">';
3869 echo '<input type="hidden" name="forumid" value="'.$forum->id.'" />';
3870 $ratingsformused = true;
3875 $post->forum = $forum->id; // Add the forum id to the post object, later used by forum_print_post
3876 $post->forumtype = $forum->type;
3878 $post->subject = format_string($post->subject);
3880 if (forum_tp_can_track_forums($forum)) {
3881 if ($forumtracked = forum_tp_is_tracked($forum)) {
3882 $user_read_array = forum_tp_get_discussion_read_records($USER->id, $post->discussion);
3883 } else {
3884 $user_read_array = array();
3886 } else {
3887 $forumtracked = false;
3888 $user_read_array = array();
3891 if (forum_print_post($post, $course->id, $ownpost, $reply, $link=false, $ratings,
3892 '', '', (!$forumtracked || isset($user_read_array[$post->id]) || forum_tp_is_post_old($post)))) {
3893 $ratingsmenuused = true;
3896 switch ($mode) {
3897 case FORUM_MODE_FLATOLDEST :
3898 case FORUM_MODE_FLATNEWEST :
3899 default:
3900 if (forum_print_posts_flat($post->discussion, $course->id, $mode, $ratings, $reply,
3901 $user_read_array, $post->forum)) {
3902 $ratingsmenuused = true;
3904 break;
3906 case FORUM_MODE_THREADED :
3907 if (forum_print_posts_threaded($post->id, $course->id, 0, $ratings, $reply,
3908 $user_read_array, $post->forum)) {
3909 $ratingsmenuused = true;
3911 break;
3913 case FORUM_MODE_NESTED :
3914 if (forum_print_posts_nested($post->id, $course->id, $ratings, $reply,
3915 $user_read_array, $post->forum)) {
3916 $ratingsmenuused = true;
3918 break;
3921 if ($ratingsformused) {
3922 if ($ratingsmenuused) {
3923 echo '<div class="ratingsubmit">';
3924 echo '<input type="submit" value="'.get_string('sendinratings', 'forum').'" />';
3925 if ($forum->scale < 0) {
3926 if ($scale = get_record("scale", "id", abs($forum->scale))) {
3927 print_scale_menu_helpbutton($course->id, $scale );
3930 echo '</div>';
3933 echo '</div>';
3934 echo '</form>';
3942 function forum_print_posts_flat($discussion, $courseid, $direction, $ratings, $reply, &$user_read_array, $forumid=0) {
3943 global $USER, $CFG;
3945 $link = false;
3946 $ratingsmenuused = false;
3948 if ($direction < 0) {
3949 $sort = "ORDER BY created DESC";
3950 } else {
3951 $sort = "ORDER BY created ASC";
3954 if ($posts = forum_get_discussion_posts($discussion, $sort, $forumid)) {
3955 foreach ($posts as $post) {
3957 $post->subject = format_string($post->subject);
3959 $ownpost = ($USER->id == $post->userid);
3960 if (forum_print_post($post, $courseid, $ownpost, $reply, $link, $ratings,
3961 '', '', (isset($user_read_array[$post->id]) || forum_tp_is_post_old($post)))) {
3962 $ratingsmenuused = true;
3967 return $ratingsmenuused;
3972 * TODO document
3974 function forum_print_posts_threaded($parent, $courseid, $depth, $ratings, $reply, &$user_read_array, $forumid=0) {
3975 global $USER, $CFG;
3977 $link = false;
3978 $ratingsmenuused = false;
3980 $istracking = forum_tp_can_track_forums($forumid) && forum_tp_is_tracked($forumid);
3982 if ($posts = forum_get_child_posts($parent, $forumid)) {
3984 if (!$cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
3985 error('Course Module ID was incorrect');
3987 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3988 $canviewfullnames = has_capability('moodle/site:viewfullnames', $modcontext);
3990 foreach ($posts as $post) {
3992 echo '<div class="indent">';
3993 if ($depth > 0) {
3994 $ownpost = ($USER->id == $post->userid);
3996 $post->subject = format_string($post->subject);
3998 if (forum_print_post($post, $courseid, $ownpost, $reply, $link, $ratings,
3999 '', '', (isset($user_read_array[$post->id]) || forum_tp_is_post_old($post)))) {
4000 $ratingsmenuused = true;
4002 } else {
4003 if (!forum_user_can_see_post($post->forum,$post->discussion,$post)) {
4004 continue;
4006 $by->name = fullname($post, $canviewfullnames);
4007 $by->date = userdate($post->modified);
4009 if ($istracking) {
4010 if (isset($user_read_array[$post->id]) || forum_tp_is_post_old($post)) {
4011 $style = '<span class="forumthread read">';
4012 } else {
4013 $style = '<span class="forumthread unread">';
4015 } else {
4016 $style = '<span class="forumthread">';
4018 echo $style."<a name=\"$post->id\"></a>".
4019 "<a href=\"discuss.php?d=$post->discussion&amp;parent=$post->id\">".format_string($post->subject,true)."</a> ";
4020 print_string("bynameondate", "forum", $by);
4021 echo "</span>";
4024 if (forum_print_posts_threaded($post->id, $courseid, $depth-1, $ratings, $reply,
4025 $user_read_array, $forumid)) {
4026 $ratingsmenuused = true;
4028 echo "</div>\n";
4031 return $ratingsmenuused;
4037 function forum_print_posts_nested($parent, $courseid, $ratings, $reply, &$user_read_array, $forumid=0) {
4038 global $USER, $CFG;
4040 $link = false;
4041 $ratingsmenuused = false;
4043 if ($posts = forum_get_child_posts($parent, $forumid)) {
4044 foreach ($posts as $post) {
4046 echo '<div class="indent">';
4047 if (empty($USER->id)) {
4048 $ownpost = false;
4049 } else {
4050 $ownpost = ($USER->id == $post->userid);
4053 $post->subject = format_string($post->subject);
4055 if (forum_print_post($post, $courseid, $ownpost, $reply, $link, $ratings,
4056 '', '', (isset($user_read_array[$post->id]) || forum_tp_is_post_old($post)))) {
4057 $ratingsmenuused = true;
4059 if (forum_print_posts_nested($post->id, $courseid, $ratings, $reply, $user_read_array, $forumid)) {
4060 $ratingsmenuused = true;
4062 echo "</div>\n";
4065 return $ratingsmenuused;
4069 * TODO document
4071 function forum_get_recent_mod_activity(&$activities, &$index, $sincetime, $courseid, $cmid="0", $user="", $groupid="") {
4072 // Returns all forum posts since a given time. If forum is specified then
4073 // this restricts the results
4075 global $CFG;
4077 if ($cmid) {
4078 $forumselect = " AND cm.id = '$cmid'";
4079 } else {
4080 $forumselect = "";
4083 if ($user) {
4084 $userselect = " AND u.id = '$user'";
4085 } else {
4086 $userselect = "";
4089 $posts = get_records_sql("SELECT p.*, d.name, u.firstname, u.lastname,
4090 u.picture, d.groupid, cm.instance, f.name,
4091 cm.section, cm.id AS cmid
4092 FROM {$CFG->prefix}forum_posts p,
4093 {$CFG->prefix}forum_discussions d,
4094 {$CFG->prefix}user u,
4095 {$CFG->prefix}course_modules cm,
4096 {$CFG->prefix}forum f
4097 WHERE p.modified > '$sincetime' $forumselect
4098 AND p.userid = u.id $userselect
4099 AND d.course = '$courseid'
4100 AND p.discussion = d.id
4101 AND cm.instance = f.id
4102 AND cm.course = d.course
4103 AND cm.course = f.course
4104 AND f.id = d.forum
4105 ORDER BY p.discussion ASC,p.created ASC");
4107 if (empty($posts)) {
4108 return;
4111 foreach ($posts as $post) {
4113 $modcontext = get_context_instance(CONTEXT_MODULE, $post->cmid);
4114 $canviewallgroups = has_capability('moodle/site:accessallgroups', $modcontext);
4116 if ($groupid and ($post->groupid != -1 and $groupid != $post->groupid and !$canviewallgroups)) {
4117 continue;
4119 if (!groups_course_module_visible($post->cmid)) {
4120 continue;
4123 $tmpactivity = new Object;
4125 $tmpactivity->type = "forum";
4126 $tmpactivity->defaultindex = $index;
4127 $tmpactivity->instance = $post->instance;
4128 $tmpactivity->name = $post->name;
4129 $tmpactivity->section = $post->section;
4131 $tmpactivity->content->id = $post->id;
4132 $tmpactivity->content->discussion = $post->discussion;
4133 $tmpactivity->content->subject = $post->subject;
4134 $tmpactivity->content->parent = $post->parent;
4136 $tmpactivity->user->userid = $post->userid;
4137 $tmpactivity->user->fullname = fullname($post);
4138 $tmpactivity->user->picture = $post->picture;
4140 $tmpactivity->timestamp = $post->modified;
4141 $activities[] = $tmpactivity;
4143 $index++;
4146 return;
4152 function forum_print_recent_mod_activity($activity, $course, $detail=false) {
4154 global $CFG;
4156 echo '<table border="0" cellpadding="3" cellspacing="0">';
4158 if ($activity->content->parent) {
4159 $openformat = "<font size=\"2\"><i>";
4160 $closeformat = "</i></font>";
4161 } else {
4162 $openformat = "<b>";
4163 $closeformat = "</b>";
4166 echo "<tr><td class=\"forumpostpicture\" width=\"35\" valign=\"top\">";
4167 print_user_picture($activity->user->userid, $course, $activity->user->picture);
4168 echo "</td><td>$openformat";
4170 if ($detail) {
4171 echo "<img src=\"$CFG->modpixpath/$activity->type/icon.gif\" ".
4172 "class=\"icon\" alt=\"".strip_tags(format_string($activity->name,true))."\" /> ";
4174 echo "<a href=\"$CFG->wwwroot/mod/forum/discuss.php?d=" . $activity->content->discussion
4175 . "#p" . $activity->content->id . "\">";
4177 echo format_string($activity->content->subject,true);
4178 echo "</a>$closeformat";
4180 echo "<br /><font size=\"2\">";
4181 echo "<a href=\"$CFG->wwwroot/user/view.php?id=" . $activity->user->userid . "&amp;course=" . "$course\">"
4182 . $activity->user->fullname . "</a>";
4183 echo " - " . userdate($activity->timestamp) . "</font></td></tr>";
4184 echo "</table>";
4186 return;
4190 * recursively sets the discussion field to $discussionid on $postid and all its children
4191 * used when pruning a post
4193 function forum_change_discussionid($postid, $discussionid) {
4194 set_field('forum_posts', 'discussion', $discussionid, 'id', $postid);
4195 if ($posts = get_records('forum_posts', 'parent', $postid)) {
4196 foreach ($posts as $post) {
4197 forum_change_discussionid($post->id, $discussionid);
4200 return true;
4204 * Prints the editing button on subscribers page
4206 function forum_update_subscriptions_button($courseid, $forumid) {
4207 global $CFG, $USER;
4209 if (!empty($USER->subscriptionsediting)) {
4210 $string = get_string('turneditingoff');
4211 $edit = "off";
4212 } else {
4213 $string = get_string('turneditingon');
4214 $edit = "on";
4217 return "<form $CFG->frametarget method=\"get\" action=\"$CFG->wwwroot/mod/forum/subscribers.php\">".
4218 "<input type=\"hidden\" name=\"id\" value=\"$forumid\" />".
4219 "<input type=\"hidden\" name=\"edit\" value=\"$edit\" />".
4220 "<input type=\"submit\" value=\"$string\" /></form>";
4224 * This function gets run whenever a role is assigned to a user in a context
4226 * @param integer $userid
4227 * @param object $context
4228 * @return bool
4230 function forum_role_assign($userid, $context, $roleid) {
4231 // check to see if this role comes with mod/forum:initialsubscriptions
4232 $cap = role_context_capabilities($roleid, $context, 'mod/forum:initialsubscriptions');
4233 $cap1 = role_context_capabilities($roleid, $context, 'moodle/course:view');
4234 // we are checking the role because has_capability() will pull this capability out
4235 // from other roles this user might have and resolve them, which is no good
4236 // the role needs course view to
4237 if (isset($cap['mod/forum:initialsubscriptions']) && $cap['mod/forum:initialsubscriptions'] == CAP_ALLOW &&
4238 isset($cap1['moodle/course:view']) && $cap1['moodle/course:view'] == CAP_ALLOW) {
4239 return forum_add_user_default_subscriptions($userid, $context);
4240 } else {
4241 // MDL-8981, do not subscribe to forum
4242 return true;
4248 * This function gets run whenever a role is assigned to a user in a context
4250 * @param integer $userid
4251 * @param object $context
4252 * @return bool
4254 function forum_role_unassign($userid, $context) {
4255 return forum_remove_user_subscriptions($userid, $context);
4260 * Add subscriptions for new users
4262 function forum_add_user_default_subscriptions($userid, $context) {
4264 if (empty($context->contextlevel)) {
4265 return false;
4268 switch ($context->contextlevel) {
4270 case CONTEXT_SYSTEM: // For the whole site
4271 if ($courses = get_records('course')) {
4272 foreach ($courses as $course) {
4273 $subcontext = get_context_instance(CONTEXT_COURSE, $course->id);
4274 forum_add_user_default_subscriptions($userid, $subcontext);
4277 break;
4279 case CONTEXT_COURSECAT: // For a whole category
4280 if ($courses = get_records('course', 'category', $context->instanceid)) {
4281 foreach ($courses as $course) {
4282 $subcontext = get_context_instance(CONTEXT_COURSE, $course->id);
4283 forum_add_user_default_subscriptions($userid, $subcontext);
4286 if ($categories = get_records('course_categories', 'parent', $context->instanceid)) {
4287 foreach ($categories as $category) {
4288 $subcontext = get_context_instance(CONTEXT_COURSECAT, $category->id);
4289 forum_add_user_default_subscriptions($userid, $subcontext);
4292 break;
4295 case CONTEXT_COURSE: // For a whole course
4296 if ($course = get_record('course', 'id', $context->instanceid)) {
4297 if ($forums = get_all_instances_in_course('forum', $course, $userid, false)) {
4298 foreach ($forums as $forum) {
4299 if ($forum->forcesubscribe != FORUM_INITIALSUBSCRIBE) {
4300 continue;
4302 if ($modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule)) {
4303 if (has_capability('mod/forum:viewdiscussion', $modcontext, $userid)) {
4304 forum_subscribe($userid, $forum->id);
4310 break;
4312 case CONTEXT_MODULE: // Just one forum
4313 if ($cm = get_coursemodule_from_id('forum', $context->instanceid)) {
4314 if ($forum = get_record('forum', 'id', $cm->instance)) {
4315 if ($forum->forcesubscribe != FORUM_INITIALSUBSCRIBE) {
4316 continue;
4318 if (has_capability('mod/forum:viewdiscussion', $context, $userid)) {
4319 forum_subscribe($userid, $forum->id);
4323 break;
4326 return true;
4331 * Remove subscriptions for a user in a context
4333 function forum_remove_user_subscriptions($userid, $context) {
4335 global $CFG;
4337 if (empty($context->contextlevel)) {
4338 return false;
4341 switch ($context->contextlevel) {
4343 case CONTEXT_SYSTEM: // For the whole site
4344 //if ($courses = get_my_courses($userid)) {
4345 // find all courses in which this user has a forum subscription
4346 if ($courses = get_records_sql("SELECT c.*
4347 FROM {$CFG->prefix}course c,
4348 {$CFG->prefix}forum_subscriptions fs,
4349 {$CFG->prefix}forum f
4350 WHERE c.id = f.course
4351 AND f.id = fs.forum
4352 AND fs.userid = $userid")) {
4354 foreach ($courses as $course) {
4355 $subcontext = get_context_instance(CONTEXT_COURSE, $course->id);
4356 forum_remove_user_subscriptions($userid, $subcontext);
4359 break;
4361 case CONTEXT_COURSECAT: // For a whole category
4362 if ($courses = get_records('course', 'category', $context->instanceid)) {
4363 foreach ($courses as $course) {
4364 $subcontext = get_context_instance(CONTEXT_COURSE, $course->id);
4365 forum_remove_user_subscriptions($userid, $subcontext);
4368 if ($categories = get_records('course_categories', 'parent', $context->instanceid)) {
4369 foreach ($categories as $category) {
4370 $subcontext = get_context_instance(CONTEXT_COURSECAT, $category->id);
4371 forum_remove_user_subscriptions($userid, $subcontext);
4374 break;
4376 case CONTEXT_COURSE: // For a whole course
4377 if ($course = get_record('course', 'id', $context->instanceid)) {
4378 // find all forums in which this user has a subscription, and its coursemodule id
4379 if ($forums = get_records_sql("SELECT f.id, cm.id as coursemodule
4380 FROM {$CFG->prefix}forum f,
4381 {$CFG->prefix}modules m,
4382 {$CFG->prefix}course_modules cm,
4383 {$CFG->prefix}forum_subscriptions fs
4384 WHERE fs.userid = $userid
4385 AND fs.forum = f.id
4386 AND f.course = $context->instanceid
4387 AND cm.instance = f.id
4388 AND cm.module = m.id
4389 AND m.name = 'forum'")) {
4391 //if ($forums = get_all_instances_in_course('forum', $course, $userid, true)) {
4392 foreach ($forums as $forum) {
4393 if ($modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule)) {
4394 if (!has_capability('mod/forum:viewdiscussion', $modcontext, $userid)) {
4395 forum_unsubscribe($userid, $forum->id);
4401 break;
4403 case CONTEXT_MODULE: // Just one forum
4404 if ($cm = get_coursemodule_from_id('forum', $context->instanceid)) {
4405 if ($forum = get_record('forum', 'id', $cm->instance)) {
4406 if (!has_capability('mod/forum:viewdiscussion', $context, $userid)) {
4407 forum_unsubscribe($userid, $forum->id);
4411 break;
4414 return true;
4417 // Functions to do with read tracking.
4421 function forum_tp_add_read_record($userid, $postid, $discussionid=-1, $forumid=-1) {
4422 if (($readrecord = forum_tp_get_read_records($userid, $postid)) === false) {
4423 // New read record
4424 unset($readrecord);
4425 $readrecord->userid = $userid;
4426 $readrecord->postid = $postid;
4427 $readrecord->discussionid = $discussionid;
4428 $readrecord->forumid = $forumid;
4429 $readrecord->firstread = time();
4430 $readrecord->lastread = $readrecord->firstread;
4431 return insert_record('forum_read', $readrecord, true);
4433 } else {
4434 // Update read record
4435 $readrecord = reset($readrecord);
4436 $readrecord->lastread = time();
4438 $update = NULL;
4439 $update->id = $readrecord->id;
4440 $update->lastread = $readrecord->lastread;
4442 // This shouldn't happen, but just in case...
4443 if (!$readrecord->firstread) {
4444 // Update the 'firstread' field.
4445 $update->firstread = $readrecord->lastread;
4447 if ($discussionid > -1) {
4448 // Update the 'discussionid' field.
4449 $update->discussionid = $discussionid;
4451 if ($forumid > -1) {
4452 // Update the 'forumid' field.
4453 $update->forumid = $forumid;
4456 return update_record('forum_read', $update);
4461 * Returns all records in the 'forum_read' table matching the passed keys, indexed
4462 * by userid.
4464 function forum_tp_get_read_records($userid=-1, $postid=-1, $discussionid=-1, $forumid=-1) {
4465 $select = '';
4466 if ($userid > -1) {
4467 if ($select != '') $select .= ' AND ';
4468 $select .= 'userid = \''.$userid.'\'';
4470 if ($postid > -1) {
4471 if ($select != '') $select .= ' AND ';
4472 $select .= 'postid = \''.$postid.'\'';
4474 if ($discussionid > -1) {
4475 if ($select != '') $select .= ' AND ';
4476 $select .= 'discussionid = \''.$discussionid.'\'';
4478 if ($forumid > -1) {
4479 if ($select != '') $select .= ' AND ';
4480 $select .= 'forumid = \''.$forumid.'\'';
4483 return get_records_select('forum_read', $select);
4487 * Returns all read records for the provided user and discussion, indexed by postid.
4489 function forum_tp_get_discussion_read_records($userid, $discussionid) {
4490 $select = 'userid = \''.$userid.'\' AND discussionid = \''.$discussionid.'\'';
4491 $fields = 'postid, firstread, lastread';
4492 return get_records_select('forum_read', $select, '', $fields);
4496 * If its an old post, do nothing. If the record exists, the maintenance will clear it up later.
4498 function forum_tp_mark_post_read($userid, &$post, $forumid) {
4499 if (!forum_tp_is_post_old($post)) {
4500 return forum_tp_add_read_record($userid, $post->id, $post->discussion, $forumid);
4501 } else {
4502 return true;
4507 * Marks a whole forum as read, for a given user
4509 function forum_tp_mark_forum_read($userid, $forumid, $groupid=false) {
4510 global $CFG;
4512 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
4514 $groupsel = '';
4515 if ($groupid !== false) {
4516 $groupsel = ' AND (d.groupid = '.$groupid.' OR d.groupid = -1)';
4519 $sql = 'SELECT p.id as postid, d.id as discussionid, d.forum as forumid '.
4520 'FROM '.$CFG->prefix.'forum_posts p '.
4521 'LEFT JOIN '.$CFG->prefix.'forum_discussions d ON p.discussion = d.id '.
4522 'LEFT JOIN '.$CFG->prefix.'forum_read r ON r.postid = p.id AND r.userid = '.$userid.' '.
4523 'WHERE d.forum = '.$forumid.$groupsel.
4524 ' AND p.modified >= '.$cutoffdate.' AND r.id is NULL';
4526 if ($posts = get_records_sql($sql)) {
4527 foreach ($posts as $post) {
4528 forum_tp_add_read_record($userid, $post->postid, $post->discussionid, $post->forumid);
4530 return true;
4535 * Marks a whole discussion as read, for a given user
4537 function forum_tp_mark_discussion_read($userid, $discussionid, $forumid) {
4538 global $CFG;
4540 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
4542 $sql = 'SELECT p.id as postid, p.discussion as discussionid '.
4543 'FROM '.$CFG->prefix.'forum_posts p '.
4544 'LEFT JOIN '.$CFG->prefix.'forum_read r ON r.postid = p.id AND r.userid = '.$userid.' '.
4545 'WHERE p.discussion = '.$discussionid.' '.
4546 'AND p.modified >= '.$cutoffdate.' AND r.id is NULL';
4548 if ($posts = get_records_sql($sql)) {
4549 foreach ($posts as $post) {
4550 forum_tp_add_read_record($userid, $post->postid, $post->discussionid, $forumid);
4552 return true;
4559 function forum_tp_is_post_read($userid, &$post) {
4560 return (forum_tp_is_post_old($post) ||
4561 (get_record('forum_read', 'userid', $userid, 'postid', $post->id) !== false));
4567 function forum_tp_is_post_old(&$post, $time=null) {
4568 global $CFG;
4570 if (is_null($time)) $time = time();
4571 return ($post->modified < ($time - ($CFG->forum_oldpostdays * 24 * 3600)));
4575 * Returns the count of records for the provided user and discussion.
4577 function forum_tp_count_discussion_read_records($userid, $discussionid) {
4578 global $CFG;
4580 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
4582 $sql = 'SELECT COUNT(DISTINCT p.id) '.
4583 'FROM '.$CFG->prefix.'forum_discussions d '.
4584 'LEFT JOIN '.$CFG->prefix.'forum_read r ON d.id = r.discussionid AND r.userid = '.$userid.' '.
4585 'LEFT JOIN '.$CFG->prefix.'forum_posts p ON p.discussion = d.id '.
4586 'AND (p.modified < '.$cutoffdate.' OR p.id = r.postid) '.
4587 'WHERE d.id = '.$discussionid;
4589 return (count_records_sql($sql));
4593 * Returns the count of records for the provided user and discussion.
4595 function forum_tp_count_discussion_unread_posts($userid, $discussionid) {
4596 global $CFG;
4598 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
4600 $sql = 'SELECT COUNT(p.id) '.
4601 'FROM '.$CFG->prefix.'forum_posts p '.
4602 'LEFT JOIN '.$CFG->prefix.'forum_read r ON r.postid = p.id AND r.userid = '.$userid.' '.
4603 'WHERE p.discussion = '.$discussionid.' '.
4604 'AND p.modified >= '.$cutoffdate.' AND r.id is NULL';
4606 return (count_records_sql($sql));
4610 * Returns the count of posts for the provided forum and [optionally] group.
4612 function forum_tp_count_forum_posts($forumid, $groupid=false) {
4613 global $CFG;
4615 $sql = 'SELECT COUNT(*) '.
4616 'FROM '.$CFG->prefix.'forum_posts fp,'.$CFG->prefix.'forum_discussions fd '.
4617 'WHERE fd.forum = '.$forumid.' AND fp.discussion = fd.id';
4618 if ($groupid !== false) {
4619 $sql .= ' AND (fd.groupid = '.$groupid.' OR fd.groupid = -1)';
4621 $count = count_records_sql($sql);
4624 return $count;
4628 * Returns the count of records for the provided user and forum and [optionally] group.
4630 function forum_tp_count_forum_read_records($userid, $forumid, $groupid=false) {
4631 global $CFG;
4633 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
4635 $groupsel = '';
4636 if ($groupid !== false) {
4637 $groupsel = ' AND (d.groupid = '.$groupid.' OR d.groupid = -1)';
4640 if ($CFG->dbfamily === 'postgres' || $CFG->dbfamily === 'mssql' || $CFG->dbfamily === 'oracle') {
4641 // this query takes 20ms, vs several minutes for the one below
4642 $sql = " SELECT COUNT (DISTINCT u.id ) "
4643 . " FROM ( "
4644 . " SELECT p.id "
4645 . " FROM {$CFG->prefix}forum_posts p "
4646 . " JOIN {$CFG->prefix}forum_discussions d ON p.discussion = d.id "
4647 . " JOIN {$CFG->prefix}forum_read r ON p.id = r.postid"
4648 . " WHERE d.forum = $forumid $groupsel "
4649 . " AND r.userid= $userid"
4650 . " UNION"
4651 . " SELECT p.id"
4652 . " FROM {$CFG->prefix}forum_posts p "
4653 . " JOIN {$CFG->prefix}forum_discussions d ON p.discussion = d.id "
4654 . " WHERE d.forum = $forumid $groupsel "
4655 . " AND p.modified < $cutoffdate"
4656 . ") u";
4657 } else { // This is for MySQL. TODO: Check if the above works for MySQL 4.1
4658 $sql = 'SELECT COUNT(DISTINCT p.id) '.
4659 'FROM '.$CFG->prefix.'forum_posts p,'.$CFG->prefix.'forum_read r,'.$CFG->prefix.'forum_discussions d '.
4660 'WHERE d.forum = '.$forumid.$groupsel.' AND p.discussion = d.id AND '.
4661 '((p.id = r.postid AND r.userid = '.$userid.') OR p.modified < '.$cutoffdate.' ) ';
4663 return (count_records_sql($sql));
4667 * Returns the count of records for the provided user and forum and [optionally] group.
4669 function forum_tp_count_forum_unread_posts($userid, $forumid, $groupid=false) {
4670 global $CFG;
4672 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
4674 $groupsel = '';
4675 if ($groupid !== false) {
4676 $groupsel = ' AND (d.groupid = '.$groupid.' OR d.groupid = -1)';
4679 $sql = 'SELECT COUNT(p.id) '.
4680 'FROM '.$CFG->prefix.'forum_posts p '.
4681 'LEFT JOIN '.$CFG->prefix.'forum_discussions d ON p.discussion = d.id '.
4682 'LEFT JOIN '.$CFG->prefix.'forum_read r ON r.postid = p.id AND r.userid = '.$userid.' '.
4683 'WHERE d.forum = '.$forumid.$groupsel.
4684 ' AND p.modified >= '.$cutoffdate.' AND r.id is NULL';
4686 return (count_records_sql($sql));
4690 * Deletes read records for the specified index. At least one parameter must be specified.
4692 function forum_tp_delete_read_records($userid=-1, $postid=-1, $discussionid=-1, $forumid=-1) {
4693 $select = '';
4694 if ($userid > -1) {
4695 if ($select != '') $select .= ' AND ';
4696 $select .= 'userid = \''.$userid.'\'';
4698 if ($postid > -1) {
4699 if ($select != '') $select .= ' AND ';
4700 $select .= 'postid = \''.$postid.'\'';
4702 if ($discussionid > -1) {
4703 if ($select != '') $select .= ' AND ';
4704 $select .= 'discussionid = \''.$discussionid.'\'';
4706 if ($forumid > -1) {
4707 if ($select != '') $select .= ' AND ';
4708 $select .= 'forumid = \''.$forumid.'\'';
4710 if ($select == '') {
4711 return false;
4713 else {
4714 return delete_records_select('forum_read', $select);
4718 * Get a list of forums not tracked by the user.
4720 * @param int $userid The id of the user to use.
4721 * @param int $courseid The id of the course being checked (optional).
4722 * @return mixed An array indexed by forum id, or false.
4724 function forum_tp_get_untracked_forums($userid, $courseid=false) {
4725 global $CFG;
4727 // If a course is specified, get the forums with tracking turned off.
4728 if ($courseid !== false) {
4729 $select = 'course = '.$courseid.' AND trackingtype = '.FORUM_TRACKING_OFF;
4730 $forced = get_records_select('forum', $select, '', 'id,course');
4731 } else {
4732 $forced = false;
4735 // Get the forums that the user has turned off.
4736 $sql = 'SELECT ft.forumid, ft.userid '.
4737 'FROM '.$CFG->prefix.'forum_track_prefs ft, '.$CFG->prefix.'forum f '.
4738 'WHERE ft.userid = '.$userid.' AND f.id = ft.forumid ' .
4739 'AND f.trackingtype != '.FORUM_TRACKING_ON;
4740 $useroff = get_records_sql($sql);
4741 if (!$forced) {
4742 return $useroff;
4743 } else if (!$useroff) {
4744 return $forced;
4745 } else {
4746 return ($useroff + $forced);
4751 * Determine if a user can track forums and optionally a particular forum.
4752 * Checks the site settings, the user settings and the forum settings (if
4753 * requested).
4755 * @param mixed $forum The forum object to test, or the int id (optional).
4756 * @param mixed $userid The user object to check for (optional).
4757 * @return boolean
4759 function forum_tp_can_track_forums($forum=false, $user=false) {
4760 global $USER, $CFG;
4762 // if possible, avoid expensive
4763 // queries
4764 if (empty($CFG->forum_trackreadposts)) {
4765 return false;
4768 if ($user === false) {
4769 // Must be logged in and not a guest.
4770 $isauser = isloggedin() && !isguest();
4771 $user = $USER;
4772 } else {
4773 $isauser = true;
4776 if ($forum === false) {
4777 $forumallows = true;
4778 $forumforced = false;
4779 } else {
4780 // Work toward always passing an object...
4781 if (is_numeric($forum)) {
4782 $forum = get_record('forum', 'id', $forum, '','','','', 'id,trackingtype');
4785 $forumallows = ($forum->trackingtype == FORUM_TRACKING_OPTIONAL);
4786 $forumforced = ($forum->trackingtype == FORUM_TRACKING_ON);
4789 return ($isauser && ($forumforced || ($forumallows && !empty($user->trackforums))));
4793 * Tells whether a specific forum is tracked by the user. A user can optionally
4794 * be specified. If not specified, the current user is assumed.
4796 * @param mixed $forum If int, the id of the forum being checked; if object, the forum object
4797 * @param int $userid The id of the user being checked (optional).
4798 * @return boolean
4800 function forum_tp_is_tracked($forum, $userid=false) {
4801 global $USER, $CFG;
4803 if ($userid === false) {
4804 if (empty($USER->id)) {
4805 return false;
4807 $userid = $USER->id;
4810 // Work toward always passing an object...
4811 if (is_numeric($forum)) {
4812 $forum = get_record('forum', 'id', $forum);
4815 return (($forum->trackingtype == FORUM_TRACKING_ON) ||
4816 ($forum->trackingtype == FORUM_TRACKING_OPTIONAL &&
4817 get_record('forum_track_prefs', 'userid', $userid, 'forumid', $forum->id) === false));
4823 function forum_tp_start_tracking($forumid, $userid=false) {
4824 global $USER;
4826 if ($userid === false) {
4827 $userid = $USER->id;
4830 return delete_records('forum_track_prefs', 'userid', $userid, 'forumid', $forumid);
4836 function forum_tp_stop_tracking($forumid, $userid=false) {
4837 global $USER;
4839 if ($userid === false) {
4840 $userid = $USER->id;
4843 $track_prefs = new stdClass;
4844 $track_prefs->userid = $userid;
4845 $track_prefs->forumid = $forumid;
4846 if (insert_record('forum_track_prefs', $track_prefs)) {
4847 return forum_tp_delete_read_records($userid, -1, -1, $forumid);
4848 } else {
4849 return false;
4854 /**
4855 * Clean old records from the forum_read table.
4857 function forum_tp_clean_read_records() {
4858 global $CFG;
4860 // Look for records older than the cutoffdate that are still in the forum_read table.
4861 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
4862 $sql = 'SELECT fr.id, fr.userid, fr.postid '.
4863 'FROM '.$CFG->prefix.'forum_posts fp, '.$CFG->prefix.'forum_read fr '.
4864 'WHERE fp.modified < '.$cutoffdate.' AND fp.id = fr.postid';
4865 if (($oldreadposts = get_records_sql($sql))) {
4866 foreach($oldreadposts as $oldreadpost) {
4867 delete_records('forum_read', 'id', $oldreadpost->id);
4873 * Sets the last post for a given discussion
4875 function forum_discussion_update_last_post($discussionid) {
4876 global $CFG, $db;
4878 // Check the given discussion exists
4879 if (!record_exists('forum_discussions', 'id', $discussionid)) {
4880 return false;
4883 // Use SQL to find the last post for this discussion
4884 $sql = 'SELECT id, userid, modified '.
4885 'FROM '.$CFG->prefix.'forum_posts '.
4886 'WHERE discussion='.$discussionid.' '.
4887 'ORDER BY modified DESC ';
4889 // Lets go find the last post
4890 if (($lastpost = get_record_sql($sql, true))) {
4891 $discussionobject = new Object;
4892 $discussionobject->id = $discussionid;
4893 $discussionobject->usermodified = $lastpost->userid;
4894 $discussionobject->timemodified = $lastpost->modified;
4895 if (update_record('forum_discussions', $discussionobject)) {
4896 return $lastpost->id;
4900 // To get here either we couldn't find a post for the discussion (weird)
4901 // or we couldn't update the discussion record (weird x2)
4902 return false;
4909 function forum_get_view_actions() {
4910 return array('view discussion','search','forum','forums','subscribers');
4916 function forum_get_post_actions() {
4917 return array('add discussion','add post','delete discussion','delete post','move discussion','prune post','update post');
4921 * this function returns all the separate forum ids, given a courseid
4922 * @param int $courseid
4923 * @return array
4925 function forum_get_separate_modules($courseid) {
4927 global $CFG,$db;
4928 $forummodule = get_record("modules", "name", "forum");
4930 $sql = 'SELECT f.id, f.id FROM '.$CFG->prefix.'forum f, '.$CFG->prefix.'course_modules cm WHERE
4931 f.id = cm.instance AND cm.module ='.$forummodule->id.' AND cm.visible = 1 AND cm.course = '.$courseid.'
4932 AND cm.groupmode ='.SEPARATEGROUPS;
4934 return get_records_sql($sql);
4941 function forum_check_throttling($forum) {
4942 global $USER, $CFG;
4944 if (is_numeric($forum)) {
4945 $forum = get_record('forum','id',$forum);
4947 if (!is_object($forum)) {
4948 return false; // this is broken.
4951 if (empty($forum->blockafter)) {
4952 return true;
4955 if (empty($forum->blockperiod)) {
4956 return true;
4959 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
4960 error('Course Module ID was incorrect');
4962 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
4963 if(!has_capability('mod/forum:throttlingapplies', $modcontext)) {
4964 return true;
4967 // get the number of posts in the last period we care about
4968 $timenow = time();
4969 $timeafter = $timenow - $forum->blockperiod;
4971 $numposts = count_records_sql('SELECT COUNT(p.id) FROM '.$CFG->prefix.'forum_posts p'
4972 .' JOIN '.$CFG->prefix.'forum_discussions d'
4973 .' ON p.discussion = d.id WHERE d.forum = '.$forum->id
4974 .' AND p.userid = '.$USER->id.' AND p.created > '.$timeafter);
4976 $a->blockafter = $forum->blockafter;
4977 $a->numposts = $numposts;
4978 $a->blockperiod = get_string('secondstotime'.$forum->blockperiod);
4980 if ($forum->blockafter <= $numposts) {
4981 error(get_string('forumblockingtoomanyposts','error',$a),$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id);
4983 if ($forum->warnafter <= $numposts) {
4984 notify(get_string('forumblockingalmosttoomanyposts','forum',$a));
4992 * This function is used by the remove_course_userdata function in moodlelib.
4993 * If this function exists, remove_course_userdata will execute it.
4994 * This function will remove all posts from the specified forum.
4996 function forum_delete_userdata($data, $showfeedback=true) {
4997 global $CFG;
4999 $sql = "DELETE FROM {$CFG->prefix}forum_posts
5000 WHERE discussion IN (
5001 SELECT fd.id FROM {$CFG->prefix}forum_discussions fd, {$CFG->prefix}forum f
5002 WHERE f.course={$data->courseid} AND f.id=fd.forum "; // closing ) added bellow
5004 $strreset = get_string('reset');
5006 if (!empty($data->reset_forum_news)) {
5007 $select = "$sql AND f.type = 'news' )";
5008 if (execute_sql($select, false) and $showfeedback) {
5009 notify($strreset.': '.get_string('namenews','forum'), 'notifysuccess');
5012 if (!empty($data->reset_forum_single)) {
5013 $select = "$sql AND f.type = 'single' ) AND parent <> 0";
5014 if (execute_sql($select, false) and $showfeedback) {
5015 notify($strreset.': '.get_string('singleforum','forum'), 'notifysuccess');
5018 if (!empty($data->reset_forum_eachuser)) {
5019 $select = "$sql AND f.type = 'eachuser' )";
5020 if (execute_sql($select, false) and $showfeedback) {
5021 notify($strreset.': '.get_string('eachuserforum','forum'), 'notifysuccess');
5024 if (!empty($data->reset_forum_general)) {
5025 $select = "$sql AND f.type = 'general' )";
5026 if (execute_sql($select, false) and $showfeedback) {
5027 notify($strreset.': '.get_string('generalforum','forum'), 'notifysuccess');
5030 if (!empty($data->reset_forum_subscriptions)) {
5031 $subscripsql = "DELETE FROM {$CFG->prefix}forum_subscriptions
5032 WHERE forum IN (
5033 SELECT id FROM {$CFG->prefix}forum
5034 WHERE course = {$data->courseid} )";
5036 if (execute_sql($subscripsql, false) and $showfeedback) {
5037 notify($strreset.': '.get_string('resetsubscriptions','forum'), 'notifysuccess');
5044 * Called by course/reset.php
5046 function forum_reset_course_form($course) {
5047 echo get_string('resetforums', 'forum'); echo ':<br />';
5048 print_checkbox('reset_forum_news', 1, true, get_string('namenews','forum'), '', ''); echo '<br />';
5049 print_checkbox('reset_forum_single', 1, true, get_string('singleforum','forum'), '', ''); echo '<br />';
5050 print_checkbox('reset_forum_eachuser', 1, true, get_string('eachuserforum','forum'), '', ''); echo '<br />';
5051 print_checkbox('reset_forum_general', 1, true, get_string('generalforum','forum'), '', ''); echo '<br />';
5052 echo '<p>';
5053 print_checkbox('reset_forum_subscriptions', 1, true, get_string('resetsubscriptions','forum'), '', '');
5054 echo '</p>';
5059 * Converts a forum to use the Roles System
5060 * @param $forum - a forum object with the same attributes as a record
5061 * from the forum database table
5062 * @param $forummodid - the id of the forum module, from the modules table
5063 * @param $teacherroles - array of roles that have moodle/legacy:teacher
5064 * @param $studentroles - array of roles that have moodle/legacy:student
5065 * @param $guestroles - array of roles that have moodle/legacy:guest
5066 * @param $cmid - the course_module id for this forum instance
5067 * @return boolean - forum was converted or not
5069 function forum_convert_to_roles($forum, $forummodid, $teacherroles=array(),
5070 $studentroles=array(), $guestroles=array(), $cmid=NULL) {
5072 global $CFG;
5074 if (!isset($forum->open) && !isset($forum->assesspublic)) {
5075 // We assume that this forum has already been converted to use the
5076 // Roles System. Columns forum.open and forum.assesspublic get dropped
5077 // once the forum module has been upgraded to use Roles.
5078 return false;
5081 if ($forum->type == 'teacher') {
5083 // Teacher forums should be converted to normal forums that
5084 // use the Roles System to implement the old behavior.
5085 // Note:
5086 // Seems that teacher forums were never backed up in 1.6 since they
5087 // didn't have an entry in the course_modules table.
5088 require_once($CFG->dirroot.'/course/lib.php');
5090 if (count_records('forum_discussions', 'forum', $forum->id) == 0) {
5091 // Delete empty teacher forums.
5092 delete_records('forum', 'id', $forum->id);
5093 } else {
5094 // Create a course module for the forum and assign it to
5095 // section 0 in the course.
5096 $mod = new object;
5097 $mod->course = $forum->course;
5098 $mod->module = $forummodid;
5099 $mod->instance = $forum->id;
5100 $mod->section = 0;
5101 $mod->visible = 0; // Hide the forum
5102 $mod->visibleold = 0; // Hide the forum
5103 $mod->groupmode = 0;
5105 if (!$cmid = add_course_module($mod)) {
5106 error('Could not create new course module instance for the teacher forum');
5107 } else {
5108 $mod->coursemodule = $cmid;
5109 if (!$sectionid = add_mod_to_section($mod)) {
5110 error('Could not add converted teacher forum instance to section 0 in the course');
5111 } else {
5112 if (!set_field('course_modules', 'section', $sectionid, 'id', $cmid)) {
5113 error('Could not update course module with section id');
5118 // Change the forum type to general.
5119 $forum->type = 'general';
5120 if (!update_record('forum', $forum)) {
5121 error('Could not change forum from type teacher to type general');
5124 $context = get_context_instance(CONTEXT_MODULE, $cmid);
5126 // Create overrides for default student and guest roles (prevent).
5127 foreach ($studentroles as $studentrole) {
5128 assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
5129 assign_capability('mod/forum:viewhiddentimedposts', CAP_PREVENT, $studentrole->id, $context->id);
5130 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
5131 assign_capability('mod/forum:replypost', CAP_PREVENT, $studentrole->id, $context->id);
5132 assign_capability('mod/forum:viewrating', CAP_PREVENT, $studentrole->id, $context->id);
5133 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $studentrole->id, $context->id);
5134 assign_capability('mod/forum:rate', CAP_PREVENT, $studentrole->id, $context->id);
5135 assign_capability('mod/forum:createattachment', CAP_PREVENT, $studentrole->id, $context->id);
5136 assign_capability('mod/forum:deleteownpost', CAP_PREVENT, $studentrole->id, $context->id);
5137 assign_capability('mod/forum:deleteanypost', CAP_PREVENT, $studentrole->id, $context->id);
5138 assign_capability('mod/forum:splitdiscussions', CAP_PREVENT, $studentrole->id, $context->id);
5139 assign_capability('mod/forum:movediscussions', CAP_PREVENT, $studentrole->id, $context->id);
5140 assign_capability('mod/forum:editanypost', CAP_PREVENT, $studentrole->id, $context->id);
5141 assign_capability('mod/forum:viewqandawithoutposting', CAP_PREVENT, $studentrole->id, $context->id);
5142 assign_capability('mod/forum:viewsubscribers', CAP_PREVENT, $studentrole->id, $context->id);
5143 assign_capability('mod/forum:managesubscriptions', CAP_PREVENT, $studentrole->id, $context->id);
5144 assign_capability('mod/forum:throttlingapplies', CAP_PREVENT, $studentrole->id, $context->id);
5146 foreach ($guestroles as $guestrole) {
5147 assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $guestrole->id, $context->id);
5148 assign_capability('mod/forum:viewhiddentimedposts', CAP_PREVENT, $guestrole->id, $context->id);
5149 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $guestrole->id, $context->id);
5150 assign_capability('mod/forum:replypost', CAP_PREVENT, $guestrole->id, $context->id);
5151 assign_capability('mod/forum:viewrating', CAP_PREVENT, $guestrole->id, $context->id);
5152 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $guestrole->id, $context->id);
5153 assign_capability('mod/forum:rate', CAP_PREVENT, $guestrole->id, $context->id);
5154 assign_capability('mod/forum:createattachment', CAP_PREVENT, $guestrole->id, $context->id);
5155 assign_capability('mod/forum:deleteownpost', CAP_PREVENT, $guestrole->id, $context->id);
5156 assign_capability('mod/forum:deleteanypost', CAP_PREVENT, $guestrole->id, $context->id);
5157 assign_capability('mod/forum:splitdiscussions', CAP_PREVENT, $guestrole->id, $context->id);
5158 assign_capability('mod/forum:movediscussions', CAP_PREVENT, $guestrole->id, $context->id);
5159 assign_capability('mod/forum:editanypost', CAP_PREVENT, $guestrole->id, $context->id);
5160 assign_capability('mod/forum:viewqandawithoutposting', CAP_PREVENT, $guestrole->id, $context->id);
5161 assign_capability('mod/forum:viewsubscribers', CAP_PREVENT, $guestrole->id, $context->id);
5162 assign_capability('mod/forum:managesubscriptions', CAP_PREVENT, $guestrole->id, $context->id);
5163 assign_capability('mod/forum:throttlingapplies', CAP_PREVENT, $guestrole->id, $context->id);
5166 } else {
5167 // Non-teacher forum.
5169 if (empty($cmid)) {
5170 // We were not given the course_module id. Try to find it.
5171 if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
5172 notify('Could not get the course module for the forum');
5173 return false;
5174 } else {
5175 $cmid = $cm->id;
5178 $context = get_context_instance(CONTEXT_MODULE, $cmid);
5180 // $forum->open defines what students can do:
5181 // 0 = No discussions, no replies
5182 // 1 = No discussions, but replies are allowed
5183 // 2 = Discussions and replies are allowed
5184 switch ($forum->open) {
5185 case 0:
5186 foreach ($studentroles as $studentrole) {
5187 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
5188 assign_capability('mod/forum:replypost', CAP_PREVENT, $studentrole->id, $context->id);
5190 break;
5191 case 1:
5192 foreach ($studentroles as $studentrole) {
5193 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
5194 assign_capability('mod/forum:replypost', CAP_ALLOW, $studentrole->id, $context->id);
5196 break;
5197 case 2:
5198 foreach ($studentroles as $studentrole) {
5199 assign_capability('mod/forum:startdiscussion', CAP_ALLOW, $studentrole->id, $context->id);
5200 assign_capability('mod/forum:replypost', CAP_ALLOW, $studentrole->id, $context->id);
5202 break;
5205 // $forum->assessed defines whether forum rating is turned
5206 // on (1 or 2) and who can rate posts:
5207 // 1 = Everyone can rate posts
5208 // 2 = Only teachers can rate posts
5209 switch ($forum->assessed) {
5210 case 1:
5211 foreach ($studentroles as $studentrole) {
5212 assign_capability('mod/forum:rate', CAP_ALLOW, $studentrole->id, $context->id);
5214 foreach ($teacherroles as $teacherrole) {
5215 assign_capability('mod/forum:rate', CAP_ALLOW, $teacherrole->id, $context->id);
5217 break;
5218 case 2:
5219 foreach ($studentroles as $studentrole) {
5220 assign_capability('mod/forum:rate', CAP_PREVENT, $studentrole->id, $context->id);
5222 foreach ($teacherroles as $teacherrole) {
5223 assign_capability('mod/forum:rate', CAP_ALLOW, $teacherrole->id, $context->id);
5225 break;
5228 // $forum->assesspublic defines whether students can see
5229 // everybody's ratings:
5230 // 0 = Students can only see their own ratings
5231 // 1 = Students can see everyone's ratings
5232 switch ($forum->assesspublic) {
5233 case 0:
5234 foreach ($studentroles as $studentrole) {
5235 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $studentrole->id, $context->id);
5237 foreach ($teacherroles as $teacherrole) {
5238 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $teacherrole->id, $context->id);
5240 break;
5241 case 1:
5242 foreach ($studentroles as $studentrole) {
5243 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $studentrole->id, $context->id);
5245 foreach ($teacherroles as $teacherrole) {
5246 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $teacherrole->id, $context->id);
5248 break;
5251 if (empty($cm)) {
5252 $cm = get_record('course_modules', 'id', $cmid);
5255 // $cm->groupmode:
5256 // 0 - No groups
5257 // 1 - Separate groups
5258 // 2 - Visible groups
5259 switch ($cm->groupmode) {
5260 case 0:
5261 break;
5262 case 1:
5263 foreach ($studentroles as $studentrole) {
5264 assign_capability('moodle/site:accessallgroups', CAP_PREVENT, $studentrole->id, $context->id);
5266 foreach ($teacherroles as $teacherrole) {
5267 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
5269 break;
5270 case 2:
5271 foreach ($studentroles as $studentrole) {
5272 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $studentrole->id, $context->id);
5274 foreach ($teacherroles as $teacherrole) {
5275 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
5277 break;
5280 return true;