Merge commit 'catalyst/MOODLE_19_STABLE' into mdl19-linuxchix
[moodle-linuxchix.git] / mod / forum / lib.php
blob771463197bb5bed1464cae11f08dcc73bd29d053
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 define ('FORUM_AGGREGATE_NONE', 0); //no ratings
23 define ('FORUM_AGGREGATE_AVG', 1);
24 define ('FORUM_AGGREGATE_COUNT', 2);
25 define ('FORUM_AGGREGATE_MAX', 3);
26 define ('FORUM_AGGREGATE_MIN', 4);
27 define ('FORUM_AGGREGATE_SUM', 5);
29 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
31 /**
32 * Given an object containing all the necessary data,
33 * (defined by the form in mod.html) this function
34 * will create a new instance and return the id number
35 * of the new instance.
36 * @param object $forum add forum instance (with magic quotes)
37 * @return int intance id
39 function forum_add_instance($forum) {
40 global $CFG;
42 $forum->timemodified = time();
44 if (empty($forum->assessed)) {
45 $forum->assessed = 0;
48 if (empty($forum->ratingtime) or empty($forum->assessed)) {
49 $forum->assesstimestart = 0;
50 $forum->assesstimefinish = 0;
53 if (!$forum->id = insert_record('forum', $forum)) {
54 return false;
57 if ($forum->type == 'single') { // Create related discussion.
58 $discussion = new object();
59 $discussion->course = $forum->course;
60 $discussion->forum = $forum->id;
61 $discussion->name = $forum->name;
62 $discussion->intro = $forum->intro;
63 $discussion->assessed = $forum->assessed;
64 $discussion->format = $forum->type;
65 $discussion->mailnow = false;
66 $discussion->groupid = -1;
68 if (! forum_add_discussion($discussion, $discussion->intro)) {
69 error('Could not add the discussion for this forum');
73 if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) { // all users should be subscribed initially
74 $users = get_course_users($forum->course);
75 foreach ($users as $user) {
76 forum_subscribe($user->id, $forum->id);
80 $forum = stripslashes_recursive($forum);
81 forum_grade_item_update($forum);
83 return $forum->id;
87 /**
88 * Given an object containing all the necessary data,
89 * (defined by the form in mod.html) this function
90 * will update an existing instance with new data.
91 * @param object $forum forum instance (with magic quotes)
92 * @return bool success
94 function forum_update_instance($forum) {
95 $forum->timemodified = time();
96 $forum->id = $forum->instance;
98 if (empty($forum->assessed)) {
99 $forum->assessed = 0;
102 if (empty($forum->ratingtime) or empty($forum->assessed)) {
103 $forum->assesstimestart = 0;
104 $forum->assesstimefinish = 0;
107 $oldforum = get_record('forum', 'id', $forum->id);
109 // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
110 // if scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
111 // for count and sum aggregation types the grade we check to make sure they do not exceed the scale (i.e. max score) when calculating the grade
112 if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
113 forum_update_grades($forum); // recalculate grades for the forum
116 if ($forum->type == 'single') { // Update related discussion and post.
117 if (! $discussion = get_record('forum_discussions', 'forum', $forum->id)) {
118 if ($discussions = get_records('forum_discussions', 'forum', $forum->id, 'timemodified ASC')) {
119 notify('Warning! There is more than one discussion in this forum - using the most recent');
120 $discussion = array_pop($discussions);
121 } else {
122 error('Could not find the discussion in this forum');
125 if (! $post = get_record('forum_posts', 'id', $discussion->firstpost)) {
126 error('Could not find the first post in this forum discussion');
129 $post->subject = $forum->name;
130 $post->message = $forum->intro;
131 $post->modified = $forum->timemodified;
133 if (! update_record('forum_posts', ($post))) {
134 error('Could not update the first post');
137 $discussion->name = $forum->name;
139 if (! update_record('forum_discussions', ($discussion))) {
140 error('Could not update the discussion');
144 if (!update_record('forum', $forum)) {
145 error('Can not update forum');
148 $forum = stripslashes_recursive($forum);
149 forum_grade_item_update($forum);
151 return true;
156 * Given an ID of an instance of this module,
157 * this function will permanently delete the instance
158 * and any data that depends on it.
159 * @param int forum instance id
160 * @return bool success
162 function forum_delete_instance($id) {
164 if (!$forum = get_record('forum', 'id', $id)) {
165 return false;
168 $result = true;
170 if ($discussions = get_records('forum_discussions', 'forum', $forum->id)) {
171 foreach ($discussions as $discussion) {
172 if (!forum_delete_discussion($discussion, true)) {
173 $result = false;
178 if (!delete_records('forum_subscriptions', 'forum', $forum->id)) {
179 $result = false;
182 forum_tp_delete_read_records(-1, -1, -1, $forum->id);
184 if (!delete_records('forum', 'id', $forum->id)) {
185 $result = false;
188 forum_grade_item_delete($forum);
190 return $result;
195 * Function to be run periodically according to the moodle cron
196 * Finds all posts that have yet to be mailed out, and mails them
197 * out to all subscribers
198 * @return void
200 function forum_cron() {
201 global $CFG, $USER;
203 $cronuser = clone($USER);
204 $site = get_site();
206 // all users that are subscribed to any post that needs sending
207 $users = array();
209 // status arrays
210 $mailcount = array();
211 $errorcount = array();
213 // caches
214 $discussions = array();
215 $forums = array();
216 $courses = array();
217 $coursemodules = array();
218 $subscribedusers = array();
221 // Posts older than 2 days will not be mailed. This is to avoid the problem where
222 // cron has not been running for a long time, and then suddenly people are flooded
223 // with mail from the past few weeks or months
224 $timenow = time();
225 $endtime = $timenow - $CFG->maxeditingtime;
226 $starttime = $endtime - 48 * 3600; // Two days earlier
228 if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
229 // Mark them all now as being mailed. It's unlikely but possible there
230 // might be an error later so that a post is NOT actually mailed out,
231 // but since mail isn't crucial, we can accept this risk. Doing it now
232 // prevents the risk of duplicated mails, which is a worse problem.
234 if (!forum_mark_old_posts_as_mailed($endtime)) {
235 mtrace('Errors occurred while trying to mark some posts as being mailed.');
236 return false; // Don't continue trying to mail them, in case we are in a cron loop
239 // checking post validity, and adding users to loop through later
240 foreach ($posts as $pid => $post) {
242 $discussionid = $post->discussion;
243 if (!isset($discussions[$discussionid])) {
244 if ($discussion = get_record('forum_discussions', 'id', $post->discussion)) {
245 $discussions[$discussionid] = $discussion;
246 } else {
247 mtrace('Could not find discussion '.$discussionid);
248 unset($posts[$pid]);
249 continue;
252 $forumid = $discussions[$discussionid]->forum;
253 if (!isset($forums[$forumid])) {
254 if ($forum = get_record('forum', 'id', $forumid)) {
255 $forums[$forumid] = $forum;
256 } else {
257 mtrace('Could not find forum '.$forumid);
258 unset($posts[$pid]);
259 continue;
262 $courseid = $forums[$forumid]->course;
263 if (!isset($courses[$courseid])) {
264 if ($course = get_record('course', 'id', $courseid)) {
265 $courses[$courseid] = $course;
266 } else {
267 mtrace('Could not find course '.$courseid);
268 unset($posts[$pid]);
269 continue;
272 if (!isset($coursemodules[$forumid])) {
273 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
274 $coursemodules[$forumid] = $cm;
275 } else {
276 mtrace('Could not course module for forum '.$forumid);
277 unset($posts[$pid]);
278 continue;
283 // caching subscribed users of each forum
284 if (!isset($subscribedusers[$forumid])) {
285 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, false)) {
286 foreach ($subusers as $postuser) {
287 // do not try to mail users with stopped email
288 if ($postuser->emailstop) {
289 if (!empty($CFG->forum_logblocked)) {
290 add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
292 continue;
294 // this user is subscribed to this forum
295 $subscribedusers[$forumid][$postuser->id] = $postuser->id;
296 // this user is a user we have to process later
297 $users[$postuser->id] = $postuser;
299 unset($subusers); // release memory
303 $mailcount[$pid] = 0;
304 $errorcount[$pid] = 0;
308 if ($users && $posts) {
310 $urlinfo = parse_url($CFG->wwwroot);
311 $hostname = $urlinfo['host'];
313 foreach ($users as $userto) {
315 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
317 // set this so that the capabilities are cached, and environment matches receiving user
318 $USER = $userto;
320 mtrace('Processing user '.$userto->id);
322 // init caches
323 $userto->viewfullnames = array();
324 $userto->canpost = array();
325 $userto->markposts = array();
326 $userto->enrolledin = array();
328 // reset the caches
329 foreach ($coursemodules as $forumid=>$unused) {
330 $coursemodules[$forumid]->cache = new object();
331 $coursemodules[$forumid]->cache->caps = array();
332 unset($coursemodules[$forumid]->uservisible);
335 foreach ($posts as $pid => $post) {
337 // Set up the environment for the post, discussion, forum, course
338 $discussion = $discussions[$post->discussion];
339 $forum = $forums[$discussion->forum];
340 $course = $courses[$forum->course];
341 $cm =& $coursemodules[$forum->id];
343 // Do some checks to see if we can bail out now
344 if (!isset($subscribedusers[$forum->id][$userto->id])) {
345 continue; // user does not subscribe to this forum
348 // Verify user is enrollend in course - if not do not send any email
349 if (!isset($userto->enrolledin[$course->id])) {
350 $userto->enrolledin[$course->id] = has_capability('moodle/course:view', get_context_instance(CONTEXT_COURSE, $course->id));
352 if (!$userto->enrolledin[$course->id]) {
353 // oops - this user should not receive anything from this course
354 continue;
357 // Get info about the sending user
358 if (array_key_exists($post->userid, $users)) { // we might know him/her already
359 $userfrom = $users[$post->userid];
360 } else if ($userfrom = get_record('user', 'id', $post->userid)) {
361 $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
362 } else {
363 mtrace('Could not find user '.$post->userid);
364 continue;
367 // setup global $COURSE properly - needed for roles and languages
368 course_setup($course); // More environment
370 // Fill caches
371 if (!isset($userto->viewfullnames[$forum->id])) {
372 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
373 $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
375 if (!isset($userto->canpost[$discussion->id])) {
376 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
377 $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
379 if (!isset($userfrom->groups[$forum->id])) {
380 if (!isset($userfrom->groups)) {
381 $userfrom->groups = array();
382 $users[$userfrom->id]->groups = array();
384 $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
385 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
388 // Make sure groups allow this user to see this email
389 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used
390 if (!groups_group_exists($discussion->groupid)) { // Can't find group
391 continue; // Be safe and don't send it to anyone
394 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
395 // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
396 continue;
400 // Make sure we're allowed to see it...
401 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
402 mtrace('user '.$userto->id. ' can not see '.$post->id);
403 continue;
406 // OK so we need to send the email.
408 // Does the user want this post in a digest? If so postpone it for now.
409 if ($userto->maildigest > 0) {
410 // This user wants the mails to be in digest form
411 $queue = new object();
412 $queue->userid = $userto->id;
413 $queue->discussionid = $discussion->id;
414 $queue->postid = $post->id;
415 $queue->timemodified = $post->created;
416 if (!insert_record('forum_queue', $queue)) {
417 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.");
419 continue;
423 // Prepare to actually send the post now, and build up the content
425 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
427 $userfrom->customheaders = array ( // Headers to make emails easier to track
428 'Precedence: Bulk',
429 'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
430 'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
431 'Message-ID: <moodlepost'.$post->id.'@'.$hostname.'>',
432 'In-Reply-To: <moodlepost'.$post->parent.'@'.$hostname.'>',
433 'References: <moodlepost'.$post->parent.'@'.$hostname.'>',
434 'X-Course-Id: '.$course->id,
435 'X-Course-Name: '.format_string($course->fullname, true)
439 $postsubject = "$course->shortname: ".format_string($post->subject,true);
440 $posttext = forum_make_mail_text($course, $forum, $discussion, $post, $userfrom, $userto);
441 $posthtml = forum_make_mail_html($course, $forum, $discussion, $post, $userfrom, $userto);
443 // Send the post now!
445 mtrace('Sending ', '');
447 if (!$mailresult = email_to_user($userto, $userfrom, $postsubject, $posttext,
448 $posthtml, '', '', $CFG->forum_replytouser)) {
449 mtrace("Error: mod/forum/cron.php: Could not send out mail for id $post->id to user $userto->id".
450 " ($userto->email) .. not trying again.");
451 add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
452 substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
453 $errorcount[$post->id]++;
454 } else if ($mailresult === 'emailstop') {
455 // should not be reached anymore - see check above
456 } else {
457 $mailcount[$post->id]++;
459 // Mark post as read if forum_usermarksread is set off
460 if (!$CFG->forum_usermarksread) {
461 $userto->markposts[$post->id] = $post->id;
465 mtrace('post '.$post->id. ': '.$post->subject);
468 // mark processed posts as read
469 forum_tp_mark_posts_read($userto, $userto->markposts);
473 if ($posts) {
474 foreach ($posts as $post) {
475 mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
476 if ($errorcount[$post->id]) {
477 set_field("forum_posts", "mailed", "2", "id", "$post->id");
482 // release some memory
483 unset($subscribedusers);
484 unset($mailcount);
485 unset($errorcount);
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 mtrace('Starting digest processing...');
496 @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
498 if (!isset($CFG->digestmailtimelast)) { // To catch the first time
499 set_config('digestmailtimelast', 0);
502 $timenow = time();
503 $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
505 // Delete any really old ones (normally there shouldn't be any)
506 $weekago = $timenow - (7 * 24 * 3600);
507 delete_records_select('forum_queue', "timemodified < $weekago");
508 mtrace ('Cleaned old digest records');
510 if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
512 mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
514 $digestposts_rs = get_recordset_select('forum_queue', "timemodified < $digesttime");
516 if (!rs_EOF($digestposts_rs)) {
518 // We have work to do
519 $usermailcount = 0;
521 //caches - reuse the those filled before too
522 $discussionposts = array();
523 $userdiscussions = array();
525 while ($digestpost = rs_fetch_next_record($digestposts_rs)) {
526 if (!isset($users[$digestpost->userid])) {
527 if ($user = get_record('user', 'id', $digestpost->userid)) {
528 $users[$digestpost->userid] = $user;
529 } else {
530 continue;
533 $postuser = $users[$digestpost->userid];
534 if ($postuser->emailstop) {
535 if (!empty($CFG->forum_logblocked)) {
536 add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
538 continue;
541 if (!isset($posts[$digestpost->postid])) {
542 if ($post = get_record('forum_posts', 'id', $digestpost->postid)) {
543 $posts[$digestpost->postid] = $post;
544 } else {
545 continue;
548 $discussionid = $digestpost->discussionid;
549 if (!isset($discussions[$discussionid])) {
550 if ($discussion = get_record('forum_discussions', 'id', $discussionid)) {
551 $discussions[$discussionid] = $discussion;
552 } else {
553 continue;
556 $forumid = $discussions[$discussionid]->forum;
557 if (!isset($forums[$forumid])) {
558 if ($forum = get_record('forum', 'id', $forumid)) {
559 $forums[$forumid] = $forum;
560 } else {
561 continue;
565 $courseid = $forums[$forumid]->course;
566 if (!isset($courses[$courseid])) {
567 if ($course = get_record('course', 'id', $courseid)) {
568 $courses[$courseid] = $course;
569 } else {
570 continue;
574 if (!isset($coursemodules[$forumid])) {
575 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
576 $coursemodules[$forumid] = $cm;
577 } else {
578 continue;
581 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
582 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
584 rs_close($digestposts_rs); /// Finished iteration, let's close the resultset
586 // Data collected, start sending out emails to each user
587 foreach ($userdiscussions as $userid => $thesediscussions) {
589 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
591 $USER = $cronuser;
592 course_setup(SITEID); // reset cron user language, theme and timezone settings
594 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
596 // First of all delete all the queue entries for this user
597 delete_records_select('forum_queue', "userid = $userid AND timemodified < $digesttime");
598 $userto = $users[$userid];
600 // Override the language and timezone of the "current" user, so that
601 // mail is customised for the receiver.
602 $USER = $userto;
603 course_setup(SITEID);
605 // init caches
606 $userto->viewfullnames = array();
607 $userto->canpost = array();
608 $userto->markposts = array();
610 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
612 $headerdata = new object();
613 $headerdata->sitename = format_string($site->fullname, true);
614 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
616 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
617 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
619 $posthtml = "<head>";
620 foreach ($CFG->stylesheets as $stylesheet) {
621 $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
623 $posthtml .= "</head>\n<body id=\"email\">\n";
624 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
626 foreach ($thesediscussions as $discussionid) {
628 @set_time_limit(120); // to be reset for each post
630 $discussion = $discussions[$discussionid];
631 $forum = $forums[$discussion->forum];
632 $course = $courses[$forum->course];
633 $cm = $coursemodules[$forum->id];
635 //override language
636 course_setup($course);
638 // Fill caches
639 if (!isset($userto->viewfullnames[$forum->id])) {
640 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
641 $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
643 if (!isset($userto->canpost[$discussion->id])) {
644 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
645 $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
648 $strforums = get_string('forums', 'forum');
649 $canunsubscribe = ! forum_is_forcesubscribed($forum);
650 $canreply = $userto->canpost[$discussion->id];
652 $posttext .= "\n \n";
653 $posttext .= '=====================================================================';
654 $posttext .= "\n \n";
655 $posttext .= "$course->shortname -> $strforums -> ".format_string($forum->name,true);
656 if ($discussion->name != $forum->name) {
657 $posttext .= " -> ".format_string($discussion->name,true);
659 $posttext .= "\n";
661 $posthtml .= "<p><font face=\"sans-serif\">".
662 "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> -> ".
663 "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
664 "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
665 if ($discussion->name == $forum->name) {
666 $posthtml .= "</font></p>";
667 } else {
668 $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
670 $posthtml .= '<p>';
672 $postsarray = $discussionposts[$discussionid];
673 sort($postsarray);
675 foreach ($postsarray as $postid) {
676 $post = $posts[$postid];
678 if (array_key_exists($post->userid, $users)) { // we might know him/her already
679 $userfrom = $users[$post->userid];
680 } else if ($userfrom = get_record('user', 'id', $post->userid)) {
681 $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
682 } else {
683 mtrace('Could not find user '.$post->userid);
684 continue;
687 if (!isset($userfrom->groups[$forum->id])) {
688 if (!isset($userfrom->groups)) {
689 $userfrom->groups = array();
690 $users[$userfrom->id]->groups = array();
692 $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
693 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
696 $userfrom->customheaders = array ("Precedence: Bulk");
698 if ($userto->maildigest == 2) {
699 // Subjects only
700 $by = new object();
701 $by->name = fullname($userfrom);
702 $by->date = userdate($post->modified);
703 $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
704 $posttext .= "\n---------------------------------------------------------------------";
706 $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
707 $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>';
709 } else {
710 // The full treatment
711 $posttext .= forum_make_mail_text($course, $forum, $discussion, $post, $userfrom, $userto, true);
712 $posthtml .= forum_make_mail_post($course, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
714 // Create an array of postid's for this user to mark as read.
715 if (!$CFG->forum_usermarksread) {
716 $userto->markposts[$post->id] = $post->id;
720 if ($canunsubscribe) {
721 $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>";
722 } else {
723 $posthtml .= "\n<div align=\"right\"><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
725 $posthtml .= '<hr size="1" noshade="noshade" /></p>';
727 $posthtml .= '</body>';
729 if ($userto->mailformat != 1) {
730 // This user DOESN'T want to receive HTML
731 $posthtml = '';
734 if (!$mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml,
735 '', '', $CFG->forum_replytouser)) {
736 mtrace("ERROR!");
737 echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
738 add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
739 } else if ($mailresult === 'emailstop') {
740 // should not happen anymore - see check above
741 } else {
742 mtrace("success.");
743 $usermailcount++;
745 // Mark post as read if forum_usermarksread is set off
746 forum_tp_mark_posts_read($userto, $userto->markposts);
750 /// We have finishied all digest emails, update $CFG->digestmailtimelast
751 set_config('digestmailtimelast', $timenow);
754 $USER = $cronuser;
755 course_setup(SITEID); // reset cron user language, theme and timezone settings
757 if (!empty($usermailcount)) {
758 mtrace(get_string('digestsentusers', 'forum', $usermailcount));
761 if (!empty($CFG->forum_lastreadclean)) {
762 $timenow = time();
763 if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
764 set_config('forum_lastreadclean', $timenow);
765 mtrace('Removing old forum read tracking info...');
766 forum_tp_clean_read_records();
768 } else {
769 set_config('forum_lastreadclean', time());
773 return true;
777 * Builds and returns the body of the email notification in plain text.
779 * @param object $course
780 * @param object $forum
781 * @param object $discussion
782 * @param object $post
783 * @param object $userfrom
784 * @param object $userto
785 * @param boolean $bare
786 * @return string The email body in plain text format.
788 function forum_make_mail_text($course, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
789 global $CFG, $USER;
791 if (!isset($userto->viewfullnames[$forum->id])) {
792 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
793 error('Course Module ID was incorrect');
795 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
796 $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
797 } else {
798 $viewfullnames = $userto->viewfullnames[$forum->id];
801 if (!isset($userto->canpost[$discussion->id])) {
802 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
803 $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
804 } else {
805 $canreply = $userto->canpost[$discussion->id];
808 $by = New stdClass;
809 $by->name = fullname($userfrom, $viewfullnames);
810 $by->date = userdate($post->modified, "", $userto->timezone);
812 $strbynameondate = get_string('bynameondate', 'forum', $by);
814 $strforums = get_string('forums', 'forum');
816 $canunsubscribe = ! forum_is_forcesubscribed($forum);
818 $posttext = '';
820 if (!$bare) {
821 $posttext = "$course->shortname -> $strforums -> ".format_string($forum->name,true);
823 if ($discussion->name != $forum->name) {
824 $posttext .= " -> ".format_string($discussion->name,true);
828 $posttext .= "\n---------------------------------------------------------------------\n";
829 $posttext .= format_string($post->subject,true);
830 if ($bare) {
831 $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
833 $posttext .= "\n".$strbynameondate."\n";
834 $posttext .= "---------------------------------------------------------------------\n";
835 $posttext .= format_text_email(trusttext_strip($post->message), $post->format);
836 $posttext .= "\n\n";
837 if ($post->attachment) {
838 $post->course = $course->id;
839 $post->forum = $forum->id;
840 $posttext .= forum_print_attachments($post, "text");
842 if (!$bare && $canreply) {
843 $posttext .= "---------------------------------------------------------------------\n";
844 $posttext .= get_string("postmailinfo", "forum", $course->shortname)."\n";
845 $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
847 if (!$bare && $canunsubscribe) {
848 $posttext .= "\n---------------------------------------------------------------------\n";
849 $posttext .= get_string("unsubscribe", "forum");
850 $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
853 return $posttext;
857 * Builds and returns the body of the email notification in html format.
859 * @param object $course
860 * @param object $forum
861 * @param object $discussion
862 * @param object $post
863 * @param object $userfrom
864 * @param object $userto
865 * @return string The email text in HTML format
867 function forum_make_mail_html($course, $forum, $discussion, $post, $userfrom, $userto) {
868 global $CFG;
870 if ($userto->mailformat != 1) { // Needs to be HTML
871 return '';
874 if (!isset($userto->canpost[$discussion->id])) {
875 $canreply = forum_user_can_post($forum, $discussion, $userto);
876 } else {
877 $canreply = $userto->canpost[$discussion->id];
880 $strforums = get_string('forums', 'forum');
881 $canunsubscribe = ! forum_is_forcesubscribed($forum);
883 $posthtml = '<head>';
884 foreach ($CFG->stylesheets as $stylesheet) {
885 $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
887 $posthtml .= '</head>';
888 $posthtml .= "\n<body id=\"email\">\n\n";
890 $posthtml .= '<div class="navbar">'.
891 '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$course->shortname.'</a> &raquo; '.
892 '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
893 '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
894 if ($discussion->name == $forum->name) {
895 $posthtml .= '</div>';
896 } else {
897 $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
898 format_string($discussion->name,true).'</a></div>';
900 $posthtml .= forum_make_mail_post($course, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
902 if ($canunsubscribe) {
903 $posthtml .= '<hr /><div align="center" class="unsubscribelink">
904 <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
905 <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
908 $posthtml .= '</body>';
910 return $posthtml;
916 * @param object $course
917 * @param object $user
918 * @param object $mod TODO this is not used in this function, refactor
919 * @param object $forum
920 * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
922 function forum_user_outline($course, $user, $mod, $forum) {
923 if ($count = forum_count_user_posts($forum->id, $user->id)) {
924 if ($count->postcount > 0) {
925 $result = new object();
926 $result->info = get_string("numposts", "forum", $count->postcount);
927 $result->time = $count->lastpost;
928 return $result;
931 return NULL;
938 function forum_user_complete($course, $user, $mod, $forum) {
939 global $CFG;
941 if ($posts = forum_get_user_posts($forum->id, $user->id)) {
943 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
944 error('Course Module ID was incorrect');
946 $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
948 foreach ($posts as $post) {
949 if (!isset($discussions[$post->discussion])) {
950 continue;
952 $discussion = $discussions[$post->discussion];
954 $ratings = null;
956 if ($forum->assessed) {
957 if ($scale = make_grades_menu($forum->scale)) {
958 $ratings =new object();
959 $ratings->scale = $scale;
960 $ratings->assesstimestart = $forum->assesstimestart;
961 $ratings->assesstimefinish = $forum->assesstimefinish;
962 $ratings->allow = false;
966 pre_load_all_ratings($cm, $discussion);
968 forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false, $ratings);
971 } else {
972 echo "<p>".get_string("noposts", "forum")."</p>";
977 * Preload all ratings of a discussion into course module
978 * Use this function to optimize post display with ratings:
979 * one query only and minimal memory
980 * @param object $cm course module (passed by reference as cache attribut is modified)
981 * @param object $discussion the discussion for which the ratings are cached
983 function pre_load_all_ratings(&$cm, $discussion) {
984 global $CFG,$USER;
985 $cm->cache->ratings = array();
986 $cm->cache->myratings = array();
987 if ($postratings = forum_get_all_discussion_ratings($discussion)) {
988 foreach ($postratings as $pr) {
989 if (!isset($cm->cache->ratings[$pr->postid])) {
990 $cm->cache->ratings[$pr->postid] = array();
992 $cm->cache->ratings[$pr->postid][$pr->id] = $pr->rating;
993 if ($pr->userid == $USER->id) {
994 $cm->cache->myratings[$pr->postid] = $pr->rating;
1003 function forum_print_overview($courses,&$htmlarray) {
1004 global $USER, $CFG;
1005 $LIKE = sql_ilike();
1007 if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1008 return array();
1011 if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1012 return;
1016 // get all forum logs in ONE query (much better!)
1017 $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {$CFG->prefix}log l "
1018 ." JOIN {$CFG->prefix}course_modules cm ON cm.id = cmid "
1019 ." WHERE (";
1020 foreach ($courses as $course) {
1021 $sql .= '(l.course = '.$course->id.' AND l.time > '.$course->lastaccess.') OR ';
1023 $sql = substr($sql,0,-3); // take off the last OR
1025 $sql .= ") AND l.module = 'forum' AND action $LIKE 'add post%' "
1026 ." AND userid != ".$USER->id." GROUP BY cmid,l.course,instance";
1028 if (!$new = get_records_sql($sql)) {
1029 $new = array(); // avoid warnings
1032 // also get all forum tracking stuff ONCE.
1033 $trackingforums = array();
1034 foreach ($forums as $forum) {
1035 if (forum_tp_can_track_forums($forum)) {
1036 $trackingforums[$forum->id] = $forum;
1040 if (count($trackingforums) > 0) {
1041 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1042 $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1043 ' FROM '.$CFG->prefix.'forum_posts p '.
1044 ' JOIN '.$CFG->prefix.'forum_discussions d ON p.discussion = d.id '.
1045 ' LEFT JOIN '.$CFG->prefix.'forum_read r ON r.postid = p.id AND r.userid = '.$USER->id.' WHERE (';
1046 foreach ($trackingforums as $track) {
1047 $sql .= '(d.forum = '.$track->id.' AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = '.get_current_group($track->course).')) OR ';
1049 $sql = substr($sql,0,-3); // take off the last OR
1050 $sql .= ') AND p.modified >= '.$cutoffdate.' AND r.id is NULL GROUP BY d.forum,d.course';
1052 if (!$unread = get_records_sql($sql)) {
1053 $unread = array();
1055 } else {
1056 $unread = array();
1059 if (empty($unread) and empty($new)) {
1060 return;
1063 $strforum = get_string('modulename','forum');
1064 $strnumunread = get_string('overviewnumunread','forum');
1065 $strnumpostssince = get_string('overviewnumpostssince','forum');
1067 foreach ($forums as $forum) {
1068 $str = '';
1069 $count = 0;
1070 $thisunread = 0;
1071 $showunread = false;
1072 // either we have something from logs, or trackposts, or nothing.
1073 if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1074 $count = $new[$forum->id]->count;
1076 if (array_key_exists($forum->id,$unread)) {
1077 $thisunread = $unread[$forum->id]->count;
1078 $showunread = true;
1080 if ($count > 0 || $thisunread > 0) {
1081 $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1082 $forum->name.'</a></div>';
1083 $str .= '<div class="info">';
1084 $str .= $count.' '.$strnumpostssince;
1085 if (!empty($showunread)) {
1086 $str .= '<br />'.$thisunread .' '.$strnumunread;
1088 $str .= '</div></div>';
1090 if (!empty($str)) {
1091 if (!array_key_exists($forum->course,$htmlarray)) {
1092 $htmlarray[$forum->course] = array();
1094 if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1095 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1097 $htmlarray[$forum->course]['forum'] .= $str;
1103 * Given a course and a date, prints a summary of all the new
1104 * messages posted in the course since that date
1105 * @param object $course
1106 * @param bool $viewfullnames capability
1107 * @param int $timestart
1108 * @return bool success
1110 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1111 global $CFG, $USER;
1113 // do not use log table if possible, it may be huge and is expensive to join with other tables
1115 if (!$posts = get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1116 d.timestart, d.timeend, d.userid AS duserid,
1117 u.firstname, u.lastname, u.email, u.picture
1118 FROM {$CFG->prefix}forum_posts p
1119 JOIN {$CFG->prefix}forum_discussions d ON d.id = p.discussion
1120 JOIN {$CFG->prefix}forum f ON f.id = d.forum
1121 JOIN {$CFG->prefix}user u ON u.id = p.userid
1122 WHERE p.created > $timestart AND f.course = {$course->id}
1123 ORDER BY p.id ASC")) { // order by initial posting date
1124 return false;
1127 $modinfo =& get_fast_modinfo($course);
1129 $groupmodes = array();
1130 $cms = array();
1132 $strftimerecent = get_string('strftimerecent');
1134 $printposts = array();
1135 foreach ($posts as $post) {
1136 if (!isset($modinfo->instances['forum'][$post->forum])) {
1137 // not visible
1138 continue;
1140 $cm = $modinfo->instances['forum'][$post->forum];
1141 if (!$cm->uservisible) {
1142 continue;
1144 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1146 if (!has_capability('mod/forum:viewdiscussion', $context)) {
1147 continue;
1150 if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1151 and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1152 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1153 continue;
1157 $groupmode = groups_get_activity_groupmode($cm, $course);
1159 if ($groupmode) {
1160 if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1161 // oki (Open discussions have groupid -1)
1162 } else {
1163 // separate mode
1164 if (isguestuser()) {
1165 // shortcut
1166 continue;
1169 if (is_null($modinfo->groups)) {
1170 $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1173 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1174 continue;
1179 $printposts[] = $post;
1181 unset($posts);
1183 if (!$printposts) {
1184 return false;
1187 print_headline(get_string('newforumposts', 'forum').':', 3);
1188 echo "\n<ul class='unlist'>\n";
1190 foreach ($printposts as $post) {
1191 $subjectclass = empty($post->parent) ? ' bold' : '';
1193 echo '<li><div class="head">'.
1194 '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1195 '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1196 '</div>';
1197 echo '<div class="info'.$subjectclass.'">';
1198 if (empty($post->parent)) {
1199 echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1200 } else {
1201 echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1203 $post->subject = break_up_long_words(format_string($post->subject, true));
1204 echo $post->subject;
1205 echo "</a>\"</div></li>\n";
1208 echo "</ul>\n";
1210 return true;
1214 * Return grade for given user or all users.
1216 * @param int $forumid id of forum
1217 * @param int $userid optional user id, 0 means all users
1218 * @return array array of grades, false if none
1220 function forum_get_user_grades($forum, $userid=0) {
1221 global $CFG;
1223 $user = $userid ? "AND u.id = $userid" : "";
1225 $aggtype = $forum->assessed;
1226 switch ($aggtype) {
1227 case FORUM_AGGREGATE_COUNT :
1228 $sql = "SELECT u.id, u.id AS userid, COUNT(fr.rating) AS rawgrade
1229 FROM {$CFG->prefix}user u, {$CFG->prefix}forum_posts fp,
1230 {$CFG->prefix}forum_ratings fr, {$CFG->prefix}forum_discussions fd
1231 WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1232 AND fr.userid != u.id AND fd.forum = $forum->id
1233 $user
1234 GROUP BY u.id";
1235 break;
1236 case FORUM_AGGREGATE_MAX :
1237 $sql = "SELECT u.id, u.id AS userid, MAX(fr.rating) AS rawgrade
1238 FROM {$CFG->prefix}user u, {$CFG->prefix}forum_posts fp,
1239 {$CFG->prefix}forum_ratings fr, {$CFG->prefix}forum_discussions fd
1240 WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1241 AND fr.userid != u.id AND fd.forum = $forum->id
1242 $user
1243 GROUP BY u.id";
1244 break;
1245 case FORUM_AGGREGATE_MIN :
1246 $sql = "SELECT u.id, u.id AS userid, MIN(fr.rating) AS rawgrade
1247 FROM {$CFG->prefix}user u, {$CFG->prefix}forum_posts fp,
1248 {$CFG->prefix}forum_ratings fr, {$CFG->prefix}forum_discussions fd
1249 WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1250 AND fr.userid != u.id AND fd.forum = $forum->id
1251 $user
1252 GROUP BY u.id";
1253 break;
1254 case FORUM_AGGREGATE_SUM :
1255 $sql = "SELECT u.id, u.id AS userid, SUM(fr.rating) AS rawgrade
1256 FROM {$CFG->prefix}user u, {$CFG->prefix}forum_posts fp,
1257 {$CFG->prefix}forum_ratings fr, {$CFG->prefix}forum_discussions fd
1258 WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1259 AND fr.userid != u.id AND fd.forum = $forum->id
1260 $user
1261 GROUP BY u.id";
1262 break;
1263 default : //avg
1264 $sql = "SELECT u.id, u.id AS userid, AVG(fr.rating) AS rawgrade
1265 FROM {$CFG->prefix}user u, {$CFG->prefix}forum_posts fp,
1266 {$CFG->prefix}forum_ratings fr, {$CFG->prefix}forum_discussions fd
1267 WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1268 AND fr.userid != u.id AND fd.forum = $forum->id
1269 $user
1270 GROUP BY u.id";
1271 break;
1274 if ($results = get_records_sql($sql)) {
1275 // it could throw off the grading if count and sum returned a rawgrade higher than scale
1276 // so to prevent it we review the results and ensure that rawgrade does not exceed the scale, if it does we set rawgrade = scale (i.e. full credit)
1277 foreach ($results as $rid=>$result) {
1278 if ($forum->scale >= 0) {
1279 //numeric
1280 if ($result->rawgrade > $forum->scale) {
1281 $results[$rid]->rawgrade = $forum->scale;
1283 } else {
1284 //scales
1285 if ($scale = get_record('scale', 'id', -$forum->scale)) {
1286 $scale = explode(',', $scale->scale);
1287 $max = count($scale);
1288 if ($result->rawgrade > $max) {
1289 $results[$rid]->rawgrade = $max;
1296 return $results;
1300 * Update grades by firing grade_updated event
1302 * @param object $forum null means all forums
1303 * @param int $userid specific user only, 0 mean all
1304 * @param boolean $nullifnone return null if grade does not exist
1305 * @return void
1307 function forum_update_grades($forum=null, $userid=0, $nullifnone=true) {
1308 global $CFG;
1310 if ($forum != null) {
1311 require_once($CFG->libdir.'/gradelib.php');
1312 if ($grades = forum_get_user_grades($forum, $userid)) {
1313 forum_grade_item_update($forum, $grades);
1315 } else if ($userid and $nullifnone) {
1316 $grade = new object();
1317 $grade->userid = $userid;
1318 $grade->rawgrade = NULL;
1319 forum_grade_item_update($forum, $grade);
1321 } else {
1322 forum_grade_item_update($forum);
1325 } else {
1326 $sql = "SELECT f.*, cm.idnumber as cmidnumber
1327 FROM {$CFG->prefix}forum f, {$CFG->prefix}course_modules cm, {$CFG->prefix}modules m
1328 WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1329 if ($rs = get_recordset_sql($sql)) {
1330 while ($forum = rs_fetch_next_record($rs)) {
1331 if ($forum->assessed) {
1332 forum_update_grades($forum, 0, false);
1333 } else {
1334 forum_grade_item_update($forum);
1337 rs_close($rs);
1343 * Create/update grade item for given forum
1345 * @param object $forum object with extra cmidnumber
1346 * @param mixed optional array/object of grade(s); 'reset' means reset grades in gradebook
1347 * @return int 0 if ok
1349 function forum_grade_item_update($forum, $grades=NULL) {
1350 global $CFG;
1351 if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1352 require_once($CFG->libdir.'/gradelib.php');
1355 $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1357 if (!$forum->assessed or $forum->scale == 0) {
1358 $params['gradetype'] = GRADE_TYPE_NONE;
1360 } else if ($forum->scale > 0) {
1361 $params['gradetype'] = GRADE_TYPE_VALUE;
1362 $params['grademax'] = $forum->scale;
1363 $params['grademin'] = 0;
1365 } else if ($forum->scale < 0) {
1366 $params['gradetype'] = GRADE_TYPE_SCALE;
1367 $params['scaleid'] = -$forum->scale;
1370 if ($grades === 'reset') {
1371 $params['reset'] = true;
1372 $grades = NULL;
1375 return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1379 * Delete grade item for given forum
1381 * @param object $forum object
1382 * @return object grade_item
1384 function forum_grade_item_delete($forum) {
1385 global $CFG;
1386 require_once($CFG->libdir.'/gradelib.php');
1388 return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1393 * Returns the users with data in one forum
1394 * (users with records in forum_subscriptions, forum_posts and forum_ratings, students)
1395 * @param int $forumid
1396 * @return mixed array or false if none
1398 function forum_get_participants($forumid) {
1400 global $CFG;
1402 //Get students from forum_subscriptions
1403 $st_subscriptions = get_records_sql("SELECT DISTINCT u.id, u.id
1404 FROM {$CFG->prefix}user u,
1405 {$CFG->prefix}forum_subscriptions s
1406 WHERE s.forum = '$forumid' and
1407 u.id = s.userid");
1408 //Get students from forum_posts
1409 $st_posts = get_records_sql("SELECT DISTINCT u.id, u.id
1410 FROM {$CFG->prefix}user u,
1411 {$CFG->prefix}forum_discussions d,
1412 {$CFG->prefix}forum_posts p
1413 WHERE d.forum = '$forumid' and
1414 p.discussion = d.id and
1415 u.id = p.userid");
1417 //Get students from forum_ratings
1418 $st_ratings = get_records_sql("SELECT DISTINCT u.id, u.id
1419 FROM {$CFG->prefix}user u,
1420 {$CFG->prefix}forum_discussions d,
1421 {$CFG->prefix}forum_posts p,
1422 {$CFG->prefix}forum_ratings r
1423 WHERE d.forum = '$forumid' and
1424 p.discussion = d.id and
1425 r.post = p.id and
1426 u.id = r.userid");
1428 //Add st_posts to st_subscriptions
1429 if ($st_posts) {
1430 foreach ($st_posts as $st_post) {
1431 $st_subscriptions[$st_post->id] = $st_post;
1434 //Add st_ratings to st_subscriptions
1435 if ($st_ratings) {
1436 foreach ($st_ratings as $st_rating) {
1437 $st_subscriptions[$st_rating->id] = $st_rating;
1440 //Return st_subscriptions array (it contains an array of unique users)
1441 return ($st_subscriptions);
1445 * This function returns if a scale is being used by one forum
1446 * @param int $forumid
1447 * @param int $scaleid negative number
1448 * @return bool
1450 function forum_scale_used ($forumid,$scaleid) {
1452 $return = false;
1454 $rec = get_record("forum","id","$forumid","scale","-$scaleid");
1456 if (!empty($rec) && !empty($scaleid)) {
1457 $return = true;
1460 return $return;
1464 * Checks if scale is being used by any instance of forum
1466 * This is used to find out if scale used anywhere
1467 * @param $scaleid int
1468 * @return boolean True if the scale is used by any forum
1470 function forum_scale_used_anywhere($scaleid) {
1471 if ($scaleid and record_exists('forum', 'scale', -$scaleid)) {
1472 return true;
1473 } else {
1474 return false;
1478 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1481 * Gets a post with all info ready for forum_print_post
1482 * Most of these joins are just to get the forum id
1483 * @param int $postid
1484 * @return mixed array of posts or false
1486 function forum_get_post_full($postid) {
1487 global $CFG;
1489 return get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1490 FROM {$CFG->prefix}forum_posts p
1491 JOIN {$CFG->prefix}forum_discussions d ON p.discussion = d.id
1492 LEFT JOIN {$CFG->prefix}user u ON p.userid = u.id
1493 WHERE p.id = '$postid'");
1497 * Gets posts with all info ready for forum_print_post
1498 * We pass forumid in because we always know it so no need to make a
1499 * complicated join to find it out.
1500 * @return mixed array of posts or false
1502 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1503 global $CFG;
1505 return get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1506 FROM {$CFG->prefix}forum_posts p
1507 LEFT JOIN {$CFG->prefix}user u ON p.userid = u.id
1508 WHERE p.discussion = $discussion
1509 AND p.parent > 0 $sort");
1513 * Gets all posts in discussion including top parent.
1514 * @param int $discussionid
1515 * @param string $sort
1516 * @param bool $tracking does user track the forum?
1517 * @return array of posts
1519 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1520 global $CFG, $USER;
1522 $tr_sel = "";
1523 $tr_join = "";
1525 if ($tracking) {
1526 $now = time();
1527 $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1528 $tr_sel = ", fr.id AS postread";
1529 $tr_join = "LEFT JOIN {$CFG->prefix}forum_read fr ON (fr.postid = p.id AND fr.userid = $USER->id)";
1532 if (!$posts = get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1533 FROM {$CFG->prefix}forum_posts p
1534 LEFT JOIN {$CFG->prefix}user u ON p.userid = u.id
1535 $tr_join
1536 WHERE p.discussion = $discussionid
1537 ORDER BY $sort")) {
1538 return array();
1541 foreach ($posts as $pid=>$p) {
1542 if ($tracking) {
1543 if (forum_tp_is_post_old($p)) {
1544 $posts[$pid]->postread = true;
1547 if (!$p->parent) {
1548 continue;
1550 if (!isset($posts[$p->parent])) {
1551 continue; // parent does not exist??
1553 if (!isset($posts[$p->parent]->children)) {
1554 $posts[$p->parent]->children = array();
1556 $posts[$p->parent]->children[$pid] =& $posts[$pid];
1559 return $posts;
1563 * Gets posts with all info ready for forum_print_post
1564 * We pass forumid in because we always know it so no need to make a
1565 * complicated join to find it out.
1567 function forum_get_child_posts($parent, $forumid) {
1568 global $CFG;
1570 return get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1571 FROM {$CFG->prefix}forum_posts p
1572 LEFT JOIN {$CFG->prefix}user u ON p.userid = u.id
1573 WHERE p.parent = '$parent'
1574 ORDER BY p.created ASC");
1578 * An array of forum objects that the user is allowed to read/search through.
1579 * @param $userid
1580 * @param $courseid - if 0, we look for forums throughout the whole site.
1581 * @return array of forum objects, or false if no matches
1582 * Forum objects have the following attributes:
1583 * id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1584 * viewhiddentimedposts
1586 function forum_get_readable_forums($userid, $courseid=0) {
1588 global $CFG, $USER;
1589 require_once($CFG->dirroot.'/course/lib.php');
1591 if (!$forummod = get_record('modules', 'name', 'forum')) {
1592 error('The forum module is not installed');
1595 if ($courseid) {
1596 $courses = get_records('course', 'id', $courseid);
1597 } else {
1598 // If no course is specified, then the user can see SITE + his courses.
1599 // And admins can see all courses, so pass the $doanything flag enabled
1600 $courses1 = get_records('course', 'id', SITEID);
1601 $courses2 = get_my_courses($userid, null, null, true);
1602 $courses = array_merge($courses1, $courses2);
1604 if (!$courses) {
1605 return array();
1608 $readableforums = array();
1610 foreach ($courses as $course) {
1612 $modinfo =& get_fast_modinfo($course);
1613 if (is_null($modinfo->groups)) {
1614 $modinfo->groups = groups_get_user_groups($course->id, $userid);
1617 if (empty($modinfo->instances['forum'])) {
1618 // hmm, no forums?
1619 continue;
1622 $courseforums = get_records('forum', 'course', $course->id);
1624 foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1625 if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1626 continue;
1628 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1629 $forum = $courseforums[$forumid];
1631 if (!has_capability('mod/forum:viewdiscussion', $context)) {
1632 continue;
1635 /// group access
1636 if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1637 if (is_null($modinfo->groups)) {
1638 $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1640 if (empty($CFG->enablegroupings)) {
1641 $forum->onlygroups = $modinfo->groups[0];
1642 $forum->onlygroups[] = -1;
1643 } else if (isset($modinfo->groups[$cm->groupingid])) {
1644 $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1645 $forum->onlygroups[] = -1;
1646 } else {
1647 $forum->onlygroups = array(-1);
1651 /// hidden timed discussions
1652 $forum->viewhiddentimedposts = true;
1653 if (!empty($CFG->forum_enabletimedposts)) {
1654 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1655 $forum->viewhiddentimedposts = false;
1659 /// qanda access
1660 if ($forum->type == 'qanda'
1661 && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1663 // We need to check whether the user has posted in the qanda forum.
1664 $forum->onlydiscussions = array(); // Holds discussion ids for the discussions
1665 // the user is allowed to see in this forum.
1666 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1667 foreach ($discussionspostedin as $d) {
1668 $forum->onlydiscussions[] = $d->id;
1673 $readableforums[$forum->id] = $forum;
1676 unset($modinfo);
1678 } // End foreach $courses
1680 //print_object($courses);
1681 //print_object($readableforums);
1683 return $readableforums;
1687 * Returns a list of posts found using an array of search terms.
1688 * @param $searchterms - array of search terms, e.g. word +word -word
1689 * @param $courseid - if 0, we search through the whole site
1690 * @param $page
1691 * @param $recordsperpage=50
1692 * @param &$totalcount
1693 * @param $extrasql
1694 * @return array of posts found
1696 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1697 &$totalcount, $extrasql='') {
1698 global $CFG, $USER;
1699 require_once($CFG->libdir.'/searchlib.php');
1701 $forums = forum_get_readable_forums($USER->id, $courseid);
1703 if (count($forums) == 0) {
1704 $totalcount = 0;
1705 return false;
1708 $now = round(time(), -2); // db friendly
1710 $fullaccess = array();
1711 $where = array();
1713 foreach ($forums as $forumid => $forum) {
1714 $select = array();
1716 if (!$forum->viewhiddentimedposts) {
1717 $select[] = "(d.userid = {$USER->id} OR (d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)))";
1720 if ($forum->type == 'qanda') {
1721 if (!empty($forum->onlydiscussions)) {
1722 $discussionsids = implode(',', $forum->onlydiscussions);
1723 $select[] = "(d.id IN ($discussionsids) OR p.parent = 0)";
1724 } else {
1725 $select[] = "p.parent = 0";
1729 if (!empty($forum->onlygroups)) {
1730 $groupids = implode(',', $forum->onlygroups);
1731 $select[] = "d.groupid IN ($groupids)";
1734 if ($select) {
1735 $selects = implode(" AND ", $select);
1736 $where[] = "(d.forum = $forumid AND $selects)";
1737 } else {
1738 $fullaccess[] = $forumid;
1742 if ($fullaccess) {
1743 $fullids = implode(',', $fullaccess);
1744 $where[] = "(d.forum IN ($fullids))";
1747 $selectdiscussion = "(".implode(" OR ", $where).")";
1749 // Some differences SQL
1750 $LIKE = sql_ilike();
1751 $NOTLIKE = 'NOT ' . $LIKE;
1752 if ($CFG->dbfamily == 'postgres') {
1753 $REGEXP = '~*';
1754 $NOTREGEXP = '!~*';
1755 } else {
1756 $REGEXP = 'REGEXP';
1757 $NOTREGEXP = 'NOT REGEXP';
1760 $messagesearch = '';
1761 $searchstring = '';
1763 // Need to concat these back together for parser to work.
1764 foreach($searchterms as $searchterm){
1765 if ($searchstring != '') {
1766 $searchstring .= ' ';
1768 $searchstring .= $searchterm;
1771 // We need to allow quoted strings for the search. The quotes *should* be stripped
1772 // by the parser, but this should be examined carefully for security implications.
1773 $searchstring = str_replace("\\\"","\"",$searchstring);
1774 $parser = new search_parser();
1775 $lexer = new search_lexer($parser);
1777 if ($lexer->parse($searchstring)) {
1778 $parsearray = $parser->get_parsed_array();
1779 // Experimental feature under 1.8! MDL-8830
1780 // Use alternative text searches if defined
1781 // This feature only works under mysql until properly implemented for other DBs
1782 // Requires manual creation of text index for forum_posts before enabling it:
1783 // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
1784 // Experimental feature under 1.8! MDL-8830
1785 if (!empty($CFG->forum_usetextsearches)) {
1786 $messagesearch = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
1787 'p.userid', 'u.id', 'u.firstname',
1788 'u.lastname', 'p.modified', 'd.forum');
1789 } else {
1790 $messagesearch = search_generate_SQL($parsearray, 'p.message', 'p.subject',
1791 'p.userid', 'u.id', 'u.firstname',
1792 'u.lastname', 'p.modified', 'd.forum');
1796 $fromsql = "{$CFG->prefix}forum_posts p,
1797 {$CFG->prefix}forum_discussions d,
1798 {$CFG->prefix}user u";
1800 $selectsql = " $messagesearch
1801 AND p.discussion = d.id
1802 AND p.userid = u.id
1803 AND $selectdiscussion
1804 $extrasql";
1806 $countsql = "SELECT COUNT(*)
1807 FROM $fromsql
1808 WHERE $selectsql";
1810 $searchsql = "SELECT p.*,
1811 d.forum,
1812 u.firstname,
1813 u.lastname,
1814 u.email,
1815 u.picture,
1816 u.imagealt
1817 FROM $fromsql
1818 WHERE $selectsql
1819 ORDER BY p.modified DESC";
1821 $totalcount = count_records_sql($countsql);
1823 return get_records_sql($searchsql, $limitfrom, $limitnum);
1827 * Returns a list of ratings for all posts in discussion
1828 * @param object $discussion
1829 * @return array of ratings or false
1831 function forum_get_all_discussion_ratings($discussion) {
1832 global $CFG;
1833 return get_records_sql("SELECT r.id, r.userid, p.id AS postid, r.rating
1834 FROM {$CFG->prefix}forum_ratings r,
1835 {$CFG->prefix}forum_posts p
1836 WHERE r.post = p.id AND p.discussion = $discussion->id
1837 ORDER BY p.id ASC");
1841 * Returns a list of ratings for a particular post - sorted.
1842 * @param int $postid
1843 * @param string $sort
1844 * @return array of ratings or false
1846 function forum_get_ratings($postid, $sort="u.firstname ASC") {
1847 global $CFG;
1848 return get_records_sql("SELECT u.*, r.rating, r.time
1849 FROM {$CFG->prefix}forum_ratings r,
1850 {$CFG->prefix}user u
1851 WHERE r.post = '$postid'
1852 AND r.userid = u.id
1853 ORDER BY $sort");
1857 * Returns a list of all new posts that have not been mailed yet
1858 * @param int $starttime - posts created after this time
1859 * @param int $endtime - posts created before this
1860 * @param int $now - used for timed discussions only
1862 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
1863 global $CFG;
1865 if (!empty($CFG->forum_enabletimedposts)) {
1866 if (empty($now)) {
1867 $now = time();
1869 $timedsql = "AND (d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now))";
1870 } else {
1871 $timedsql = "";
1874 return get_records_sql("SELECT p.*, d.course, d.forum
1875 FROM {$CFG->prefix}forum_posts p
1876 JOIN {$CFG->prefix}forum_discussions d ON d.id = p.discussion
1877 WHERE p.mailed = 0
1878 AND p.created >= $starttime
1879 AND (p.created < $endtime OR p.mailnow = 1)
1880 $timedsql
1881 ORDER BY p.modified ASC");
1885 * Marks posts before a certain time as being mailed already
1887 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
1888 global $CFG;
1889 if (empty($now)) {
1890 $now = time();
1893 if (empty($CFG->forum_enabletimedposts)) {
1894 return execute_sql("UPDATE {$CFG->prefix}forum_posts
1895 SET mailed = '1'
1896 WHERE (created < $endtime OR mailnow = 1)
1897 AND mailed = 0", false);
1899 } else {
1900 return execute_sql("UPDATE {$CFG->prefix}forum_posts
1901 SET mailed = '1'
1902 WHERE discussion NOT IN (SELECT d.id
1903 FROM {$CFG->prefix}forum_discussions d
1904 WHERE d.timestart > $now)
1905 AND (created < $endtime OR mailnow = 1)
1906 AND mailed = 0", false);
1911 * Get all the posts for a user in a forum suitable for forum_print_post
1913 function forum_get_user_posts($forumid, $userid) {
1914 global $CFG;
1916 $timedsql = "";
1917 if (!empty($CFG->forum_enabletimedposts)) {
1918 $cm = get_coursemodule_from_instance('forum', $forumid);
1919 if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
1920 $now = time();
1921 $timedsql = "AND (d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now))";
1925 return get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1926 FROM {$CFG->prefix}forum f
1927 JOIN {$CFG->prefix}forum_discussions d ON d.forum = f.id
1928 JOIN {$CFG->prefix}forum_posts p ON p.discussion = d.id
1929 JOIN {$CFG->prefix}user u ON u.id = p.userid
1930 WHERE f.id = $forumid
1931 AND p.userid = $userid
1932 $timedsql
1933 ORDER BY p.modified ASC");
1937 * Get all the discussions user participated in
1938 * @param int $forumid
1939 * @param int $userid
1940 * @return array or false
1942 function forum_get_user_involved_discussions($forumid, $userid) {
1943 global $CFG;
1945 $timedsql = "";
1946 if (!empty($CFG->forum_enabletimedposts)) {
1947 $cm = get_coursemodule_from_instance('forum', $forumid);
1948 if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
1949 $now = time();
1950 $timedsql = "AND (d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now))";
1954 return get_records_sql("SELECT DISTINCT d.*
1955 FROM {$CFG->prefix}forum f
1956 JOIN {$CFG->prefix}forum_discussions d ON d.forum = f.id
1957 JOIN {$CFG->prefix}forum_posts p ON p.discussion = d.id
1958 WHERE f.id = $forumid
1959 AND p.userid = $userid
1960 $timedsql");
1964 * Get all the posts for a user in a forum suitable for forum_print_post
1965 * @param int $forumid
1966 * @param int $userid
1967 * @return array of counts or false
1969 function forum_count_user_posts($forumid, $userid) {
1970 global $CFG;
1972 $timedsql = "";
1973 if (!empty($CFG->forum_enabletimedposts)) {
1974 $cm = get_coursemodule_from_instance('forum', $forumid);
1975 if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
1976 $now = time();
1977 $timedsql = "AND (d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now))";
1981 return get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
1982 FROM {$CFG->prefix}forum f
1983 JOIN {$CFG->prefix}forum_discussions d ON d.forum = f.id
1984 JOIN {$CFG->prefix}forum_posts p ON p.discussion = d.id
1985 JOIN {$CFG->prefix}user u ON u.id = p.userid
1986 WHERE f.id = $forumid
1987 AND p.userid = $userid
1988 $timedsql");
1992 * Given a log entry, return the forum post details for it.
1994 function forum_get_post_from_log($log) {
1995 global $CFG;
1997 if ($log->action == "add post") {
1999 return get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2000 u.firstname, u.lastname, u.email, u.picture
2001 FROM {$CFG->prefix}forum_discussions d,
2002 {$CFG->prefix}forum_posts p,
2003 {$CFG->prefix}forum f,
2004 {$CFG->prefix}user u
2005 WHERE p.id = '$log->info'
2006 AND d.id = p.discussion
2007 AND p.userid = u.id
2008 AND u.deleted <> '1'
2009 AND f.id = d.forum");
2012 } else if ($log->action == "add discussion") {
2014 return get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2015 u.firstname, u.lastname, u.email, u.picture
2016 FROM {$CFG->prefix}forum_discussions d,
2017 {$CFG->prefix}forum_posts p,
2018 {$CFG->prefix}forum f,
2019 {$CFG->prefix}user u
2020 WHERE d.id = '$log->info'
2021 AND d.firstpost = p.id
2022 AND p.userid = u.id
2023 AND u.deleted <> '1'
2024 AND f.id = d.forum");
2026 return NULL;
2030 * Given a discussion id, return the first post from the discussion
2032 function forum_get_firstpost_from_discussion($discussionid) {
2033 global $CFG;
2035 return get_record_sql("SELECT p.*
2036 FROM {$CFG->prefix}forum_discussions d,
2037 {$CFG->prefix}forum_posts p
2038 WHERE d.id = '$discussionid'
2039 AND d.firstpost = p.id ");
2043 * Returns an array of counts of replies to each discussion
2045 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2046 global $CFG;
2048 if ($limit > 0) {
2049 $limitfrom = 0;
2050 $limitnum = $limit;
2051 } else if ($page != -1) {
2052 $limitfrom = $page*$perpage;
2053 $limitnum = $perpage;
2054 } else {
2055 $limitfrom = 0;
2056 $limitnum = 0;
2059 if ($forumsort == "") {
2060 $orderby = "";
2061 $groupby = "";
2063 } else {
2064 $orderby = "ORDER BY $forumsort";
2065 $groupby = ", ".strtolower($forumsort);
2066 $groupby = str_replace('desc', '', $groupby);
2067 $groupby = str_replace('asc', '', $groupby);
2070 if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2071 $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2072 FROM {$CFG->prefix}forum_posts p
2073 JOIN {$CFG->prefix}forum_discussions d ON p.discussion = d.id
2074 WHERE p.parent > 0 AND d.forum = $forumid
2075 GROUP BY p.discussion";
2076 return get_records_sql($sql);
2078 } else {
2079 $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2080 FROM {$CFG->prefix}forum_posts p
2081 JOIN {$CFG->prefix}forum_discussions d ON p.discussion = d.id
2082 WHERE d.forum = $forumid
2083 GROUP BY p.discussion $groupby
2084 $orderby";
2085 return get_records_sql("SELECT * FROM ($sql) sq", $limitfrom, $limitnum);
2089 function forum_count_discussions($forum, $cm, $course) {
2090 global $CFG, $USER;
2092 static $cache = array();
2094 $now = round(time(), -2); // db cache friendliness
2096 if (!isset($cache[$course->id])) {
2097 if (!empty($CFG->forum_enabletimedposts)) {
2098 $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2099 } else {
2100 $timedsql = "";
2103 $sql = "SELECT f.id, COUNT(d.id) as dcount
2104 FROM {$CFG->prefix}forum f
2105 JOIN {$CFG->prefix}forum_discussions d ON d.forum = f.id
2106 WHERE f.course = $course->id
2107 $timedsql
2108 GROUP BY f.id";
2110 if ($counts = get_records_sql($sql)) {
2111 foreach ($counts as $count) {
2112 $counts[$count->id] = $count->dcount;
2114 $cache[$course->id] = $counts;
2115 } else {
2116 $cache[$course->id] = array();
2120 if (empty($cache[$course->id][$forum->id])) {
2121 return 0;
2124 $groupmode = groups_get_activity_groupmode($cm, $course);
2126 if ($groupmode != SEPARATEGROUPS) {
2127 return $cache[$course->id][$forum->id];
2130 if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2131 return $cache[$course->id][$forum->id];
2134 require_once($CFG->dirroot.'/course/lib.php');
2136 $modinfo =& get_fast_modinfo($course);
2137 if (is_null($modinfo->groups)) {
2138 $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2141 if (empty($CFG->enablegroupings)) {
2142 $mygroups = $modinfo->groups[0];
2143 } else {
2144 $mygroups = $modinfo->groups[$cm->groupingid];
2147 // add all groups posts
2148 if (empty($mygroups)) {
2149 $mygroups = array(-1=>-1);
2150 } else {
2151 $mygroups[-1] = -1;
2153 $mygroups = implode(',', $mygroups);
2155 if (!empty($CFG->forum_enabletimedposts)) {
2156 $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2157 } else {
2158 $timedsql = "";
2161 $sql = "SELECT COUNT(d.id)
2162 FROM {$CFG->prefix}forum_discussions d
2163 WHERE d.forum = $forum->id AND d.groupid IN ($mygroups)
2164 $timedsql";
2166 return get_field_sql($sql);
2170 * How many unrated posts are in the given discussion for a given user?
2172 function forum_count_unrated_posts($discussionid, $userid) {
2173 global $CFG;
2174 if ($posts = get_record_sql("SELECT count(*) as num
2175 FROM {$CFG->prefix}forum_posts
2176 WHERE parent > 0
2177 AND discussion = '$discussionid'
2178 AND userid <> '$userid' ")) {
2180 if ($rated = get_record_sql("SELECT count(*) as num
2181 FROM {$CFG->prefix}forum_posts p,
2182 {$CFG->prefix}forum_ratings r
2183 WHERE p.discussion = '$discussionid'
2184 AND p.id = r.post
2185 AND r.userid = '$userid'")) {
2186 $difference = $posts->num - $rated->num;
2187 if ($difference > 0) {
2188 return $difference;
2189 } else {
2190 return 0; // Just in case there was a counting error
2192 } else {
2193 return $posts->num;
2195 } else {
2196 return 0;
2201 * Get all discussions in a forum
2203 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2204 global $CFG, $USER;
2206 $timelimit = '';
2208 $modcontext = null;
2210 $now = round(time(), -2);
2212 if (!empty($CFG->forum_enabletimedposts)) {
2214 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2216 if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2217 $timelimit = " AND ((d.timestart <= $now AND (d.timeend = 0 OR d.timeend > $now))";
2218 if (isloggedin()) {
2219 $timelimit .= " OR d.userid = $USER->id";
2221 $timelimit .= ")";
2225 if ($limit > 0) {
2226 $limitfrom = 0;
2227 $limitnum = $limit;
2228 } else if ($page != -1) {
2229 $limitfrom = $page*$perpage;
2230 $limitnum = $perpage;
2231 } else {
2232 $limitfrom = 0;
2233 $limitnum = 0;
2236 $groupmode = groups_get_activity_groupmode($cm);
2237 $currentgroup = groups_get_activity_group($cm);
2239 if ($groupmode) {
2240 if (empty($modcontext)) {
2241 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2244 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2245 if ($currentgroup) {
2246 $groupselect = "AND (d.groupid = $currentgroup OR d.groupid = -1)";
2247 } else {
2248 $groupselect = "";
2251 } else {
2252 //seprate groups without access all
2253 if ($currentgroup) {
2254 $groupselect = "AND (d.groupid = $currentgroup OR d.groupid = -1)";
2255 } else {
2256 $groupselect = "AND d.groupid = -1";
2259 } else {
2260 $groupselect = "";
2264 if (empty($forumsort)) {
2265 $forumsort = "d.timemodified DESC";
2267 if (empty($fullpost)) {
2268 $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2269 } else {
2270 $postdata = "p.*";
2273 if (empty($userlastmodified)) { // We don't need to know this
2274 $umfields = "";
2275 $umtable = "";
2276 } else {
2277 $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2278 $umtable = " LEFT JOIN {$CFG->prefix}user um ON (d.usermodified = um.id)";
2281 $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2282 u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2283 FROM {$CFG->prefix}forum_discussions d
2284 JOIN {$CFG->prefix}forum_posts p ON p.discussion = d.id
2285 JOIN {$CFG->prefix}user u ON p.userid = u.id
2286 $umtable
2287 WHERE d.forum = {$cm->instance} AND p.parent = 0
2288 $timelimit $groupselect
2289 ORDER BY $forumsort";
2290 return get_records_sql($sql, $limitfrom, $limitnum);
2293 function forum_get_discussions_unread($cm) {
2294 global $CFG, $USER;
2296 $now = round(time(), -2);
2298 $groupmode = groups_get_activity_groupmode($cm);
2299 $currentgroup = groups_get_activity_group($cm);
2301 if ($groupmode) {
2302 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2304 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2305 if ($currentgroup) {
2306 $groupselect = "AND (d.groupid = $currentgroup OR d.groupid = -1)";
2307 } else {
2308 $groupselect = "";
2311 } else {
2312 //seprate groups without access all
2313 if ($currentgroup) {
2314 $groupselect = "AND (d.groupid = $currentgroup OR d.groupid = -1)";
2315 } else {
2316 $groupselect = "AND d.groupid = -1";
2319 } else {
2320 $groupselect = "";
2323 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2325 if (!empty($CFG->forum_enabletimedposts)) {
2326 $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2327 } else {
2328 $timedsql = "";
2331 $sql = "SELECT d.id, COUNT(p.id) AS unread
2332 FROM {$CFG->prefix}forum_discussions d
2333 JOIN {$CFG->prefix}forum_posts p ON p.discussion = d.id
2334 LEFT JOIN {$CFG->prefix}forum_read r ON (r.postid = p.id AND r.userid = $USER->id)
2335 WHERE d.forum = {$cm->instance}
2336 AND p.modified >= $cutoffdate AND r.id is NULL
2337 $timedsql
2338 $groupselect
2339 GROUP BY d.id";
2340 if ($unreads = get_records_sql($sql)) {
2341 foreach ($unreads as $unread) {
2342 $unreads[$unread->id] = $unread->unread;
2344 return $unreads;
2345 } else {
2346 return array();
2350 function forum_get_discussions_count($cm) {
2351 global $CFG, $USER;
2353 $now = round(time(), -2);
2355 $groupmode = groups_get_activity_groupmode($cm);
2356 $currentgroup = groups_get_activity_group($cm);
2358 if ($groupmode) {
2359 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2361 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2362 if ($currentgroup) {
2363 $groupselect = "AND (d.groupid = $currentgroup OR d.groupid = -1)";
2364 } else {
2365 $groupselect = "";
2368 } else {
2369 //seprate groups without access all
2370 if ($currentgroup) {
2371 $groupselect = "AND (d.groupid = $currentgroup OR d.groupid = -1)";
2372 } else {
2373 $groupselect = "AND d.groupid = -1";
2376 } else {
2377 $groupselect = "";
2380 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2382 $timelimit = "";
2384 if (!empty($CFG->forum_enabletimedposts)) {
2386 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2388 if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2389 $timelimit = " AND ((d.timestart <= $now AND (d.timeend = 0 OR d.timeend > $now))";
2390 if (isloggedin()) {
2391 $timelimit .= " OR d.userid = $USER->id";
2393 $timelimit .= ")";
2397 $sql = "SELECT COUNT(d.id)
2398 FROM {$CFG->prefix}forum_discussions d
2399 JOIN {$CFG->prefix}forum_posts p ON p.discussion = d.id
2400 WHERE d.forum = {$cm->instance} AND p.parent = 0
2401 $timelimit $groupselect";
2403 return get_field_sql($sql);
2408 * Get all discussions started by a particular user in a course (or group)
2409 * This function no longer used ...
2411 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2412 global $CFG;
2414 if ($groupid) {
2415 $groupselect = " AND d.groupid = '$groupid' ";
2416 } else {
2417 $groupselect = "";
2420 return get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2421 f.type as forumtype, f.name as forumname, f.id as forumid
2422 FROM {$CFG->prefix}forum_discussions d,
2423 {$CFG->prefix}forum_posts p,
2424 {$CFG->prefix}user u,
2425 {$CFG->prefix}forum f
2426 WHERE d.course = '$courseid'
2427 AND p.discussion = d.id
2428 AND p.parent = 0
2429 AND p.userid = u.id
2430 AND u.id = '$userid'
2431 AND d.forum = f.id $groupselect
2432 ORDER BY p.created DESC");
2436 * Returns list of user objects that are subscribed to this forum
2438 function forum_subscribed_users($course, $forum, $groupid=0) {
2440 global $CFG;
2442 if ($groupid) {
2443 $grouptables = ", {$CFG->prefix}groups_members gm ";
2444 $groupselect = "AND gm.groupid = $groupid AND u.id = gm.userid";
2446 } else {
2447 $grouptables = '';
2448 $groupselect = '';
2451 if (forum_is_forcesubscribed($forum)) {
2452 $context = get_context_instance(CONTEXT_COURSE, $course->id);
2453 $sort = "u.email ASC";
2454 $fields ="u.id, u.username, u.firstname, u.lastname, u.maildisplay, u.mailformat, u.maildigest, u.emailstop, u.imagealt,
2455 u.email, u.city, u.country, u.lastaccess, u.lastlogin, u.picture, u.timezone, u.theme, u.lang, u.trackforums, u.mnethostid";
2456 $results = get_users_by_capability($context, 'mod/forum:initialsubscriptions', $fields, $sort, '','','','', false, true);
2457 } else {
2458 $results = get_records_sql("SELECT u.id, u.username, u.firstname, u.lastname, u.maildisplay, u.mailformat, u.maildigest, u.emailstop, u.imagealt,
2459 u.email, u.city, u.country, u.lastaccess, u.lastlogin, u.picture, u.timezone, u.theme, u.lang, u.trackforums, u.mnethostid
2460 FROM {$CFG->prefix}user u,
2461 {$CFG->prefix}forum_subscriptions s $grouptables
2462 WHERE s.forum = '$forum->id'
2463 AND s.userid = u.id
2464 AND u.deleted = 0 $groupselect
2465 ORDER BY u.email ASC");
2468 static $guestid = null;
2470 if (is_null($guestid)) {
2471 if ($guest = guest_user()) {
2472 $guestid = $guest->id;
2473 } else {
2474 $guestid = 0;
2478 // Guest user should never be subscribed to a forum.
2479 unset($results[$guestid]);
2481 return $results;
2486 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2489 function forum_get_course_forum($courseid, $type) {
2490 // How to set up special 1-per-course forums
2491 global $CFG;
2493 if ($forums = get_records_select("forum", "course = '$courseid' AND type = '$type'", "id ASC")) {
2494 // There should always only be ONE, but with the right combination of
2495 // errors there might be more. In this case, just return the oldest one (lowest ID).
2496 foreach ($forums as $forum) {
2497 return $forum; // ie the first one
2501 // Doesn't exist, so create one now.
2502 $forum->course = $courseid;
2503 $forum->type = "$type";
2504 switch ($forum->type) {
2505 case "news":
2506 $forum->name = addslashes(get_string("namenews", "forum"));
2507 $forum->intro = addslashes(get_string("intronews", "forum"));
2508 $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2509 $forum->assessed = 0;
2510 if ($courseid == SITEID) {
2511 $forum->name = get_string("sitenews");
2512 $forum->forcesubscribe = 0;
2514 break;
2515 case "social":
2516 $forum->name = addslashes(get_string("namesocial", "forum"));
2517 $forum->intro = addslashes(get_string("introsocial", "forum"));
2518 $forum->assessed = 0;
2519 $forum->forcesubscribe = 0;
2520 break;
2521 default:
2522 notify("That forum type doesn't exist!");
2523 return false;
2524 break;
2527 $forum->timemodified = time();
2528 $forum->id = insert_record("forum", $forum);
2530 if (! $module = get_record("modules", "name", "forum")) {
2531 notify("Could not find forum module!!");
2532 return false;
2534 $mod = new object();
2535 $mod->course = $courseid;
2536 $mod->module = $module->id;
2537 $mod->instance = $forum->id;
2538 $mod->section = 0;
2539 if (! $mod->coursemodule = add_course_module($mod) ) { // assumes course/lib.php is loaded
2540 notify("Could not add a new course module to the course '" . format_string($course->fullname) . "'");
2541 return false;
2543 if (! $sectionid = add_mod_to_section($mod) ) { // assumes course/lib.php is loaded
2544 notify("Could not add the new course module to that section");
2545 return false;
2547 if (! set_field("course_modules", "section", $sectionid, "id", $mod->coursemodule)) {
2548 notify("Could not update the course module with the correct section");
2549 return false;
2551 include_once("$CFG->dirroot/course/lib.php");
2552 rebuild_course_cache($courseid);
2554 return get_record("forum", "id", "$forum->id");
2559 * Given the data about a posting, builds up the HTML to display it and
2560 * returns the HTML in a string. This is designed for sending via HTML email.
2562 function forum_make_mail_post($course, $forum, $discussion, $post, $userfrom, $userto,
2563 $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
2565 global $CFG;
2567 if (!isset($userto->viewfullnames[$forum->id])) {
2568 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
2569 error('Course Module ID was incorrect');
2571 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2572 $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
2573 } else {
2574 $viewfullnames = $userto->viewfullnames[$forum->id];
2577 // format the post body
2578 $options = new object();
2579 $options->para = true;
2580 $formattedtext = format_text(trusttext_strip($post->message), $post->format, $options, $course->id);
2582 $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
2584 $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
2585 $output .= print_user_picture($userfrom, $course->id, $userfrom->picture, false, true);
2586 $output .= '</td>';
2588 if ($post->parent) {
2589 $output .= '<td class="topic">';
2590 } else {
2591 $output .= '<td class="topic starter">';
2593 $output .= '<div class="subject">'.format_string($post->subject).'</div>';
2595 $fullname = fullname($userfrom, $viewfullnames);
2596 $by = new object();
2597 $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
2598 $by->date = userdate($post->modified, '', $userto->timezone);
2599 $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
2601 $output .= '</td></tr>';
2603 $output .= '<tr><td class="left side" valign="top">';
2605 if (isset($userfrom->groups)) {
2606 $groups = $userfrom->groups[$forum->id];
2607 } else {
2608 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
2609 error('Course Module ID was incorrect');
2611 $group = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
2614 if ($groups) {
2615 $output .= print_group_picture($groups, $course->id, false, true, true);
2616 } else {
2617 $output .= '&nbsp;';
2620 $output .= '</td><td class="content">';
2622 if ($post->attachment) {
2623 $post->course = $course->id;
2624 $output .= '<div class="attachments">';
2625 $output .= forum_print_attachments($post, 'html');
2626 $output .= "</div>";
2629 $output .= $formattedtext;
2631 // Commands
2632 $commands = array();
2634 if ($post->parent) {
2635 $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
2636 $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
2639 if ($reply) {
2640 $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
2641 get_string('reply', 'forum').'</a>';
2644 $output .= '<div class="commands">';
2645 $output .= implode(' | ', $commands);
2646 $output .= '</div>';
2648 // Context link to post if required
2649 if ($link) {
2650 $output .= '<div class="link">';
2651 $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
2652 get_string('postincontext', 'forum').'</a>';
2653 $output .= '</div>';
2656 if ($footer) {
2657 $output .= '<div class="footer">'.$footer.'</div>';
2659 $output .= '</td></tr></table>'."\n\n";
2661 return $output;
2665 * Print a forum post
2667 * @param object $post The post to print.
2668 * @param integer $courseid The course this post belongs to.
2669 * @param boolean $ownpost Whether this post belongs to the current user.
2670 * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
2671 * @param boolean $link Just print a shortened version of the post as a link to the full post.
2672 * @param object $ratings -- I don't really know --
2673 * @param string $footer Extra stuff to print after the message.
2674 * @param string $highlight Space-separated list of terms to highlight.
2675 * @param int $post_read true, false or -99. If we already know whether this user
2676 * has read this post, pass that in, otherwise, pass in -99, and this
2677 * function will work it out.
2678 * @param boolean $dummyifcantsee When forum_user_can_see_post says that
2679 * the current user can't see this post, if this argument is true
2680 * (the default) then print a dummy 'you can't see this post' post.
2681 * If false, don't output anything at all.
2683 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
2684 $ratings=NULL, $footer="", $highlight="", $post_read=null, $dummyifcantsee=true, $istracked=null) {
2686 global $USER, $CFG;
2688 static $stredit, $strdelete, $strreply, $strparent, $strprune;
2689 static $strpruneheading, $displaymode;
2690 static $strmarkread, $strmarkunread;
2692 $post->course = $course->id;
2693 $post->forum = $forum->id;
2695 // caching
2696 if (!isset($cm->cache)) {
2697 $cm->cache = new object();
2700 if (!isset($cm->cache->caps)) {
2701 $cm->cache->caps = array();
2702 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2703 $cm->cache->caps['mod/forum:viewdiscussion'] = has_capability('mod/forum:viewdiscussion', $modcontext);
2704 $cm->cache->caps['moodle/site:viewfullnames'] = has_capability('moodle/site:viewfullnames', $modcontext);
2705 $cm->cache->caps['mod/forum:editanypost'] = has_capability('mod/forum:editanypost', $modcontext);
2706 $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
2707 $cm->cache->caps['mod/forum:deleteownpost'] = has_capability('mod/forum:deleteownpost', $modcontext);
2708 $cm->cache->caps['mod/forum:deleteanypost'] = has_capability('mod/forum:deleteanypost', $modcontext);
2709 $cm->cache->caps['mod/forum:viewanyrating'] = has_capability('mod/forum:viewanyrating', $modcontext);
2712 if (!isset($cm->uservisible)) {
2713 $cm->uservisible = coursemodule_visible_for_user($cm);
2716 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
2717 if (!$dummyifcantsee) {
2718 return;
2720 echo '<a id="p'.$post->id.'"></a>';
2721 echo '<table cellspacing="0" class="forumpost">';
2722 echo '<tr class="header"><td class="picture left">';
2723 // print_user_picture($post->userid, $courseid, $post->picture);
2724 echo '</td>';
2725 if ($post->parent) {
2726 echo '<td class="topic">';
2727 } else {
2728 echo '<td class="topic starter">';
2730 echo '<div class="subject">'.get_string('forumsubjecthidden','forum').'</div>';
2731 echo '<div class="author">';
2732 print_string('forumauthorhidden','forum');
2733 echo '</div></td></tr>';
2735 echo '<tr><td class="left side">';
2736 echo '&nbsp;';
2738 // Actual content
2740 echo '</td><td class="content">'."\n";
2741 echo get_string('forumbodyhidden','forum');
2742 echo '</td></tr></table>';
2743 return;
2746 if (empty($stredit)) {
2747 $stredit = get_string('edit', 'forum');
2748 $strdelete = get_string('delete', 'forum');
2749 $strreply = get_string('reply', 'forum');
2750 $strparent = get_string('parent', 'forum');
2751 $strpruneheading = get_string('pruneheading', 'forum');
2752 $strprune = get_string('prune', 'forum');
2753 $displaymode = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
2754 $strmarkread = get_string('markread', 'forum');
2755 $strmarkunread = get_string('markunread', 'forum');
2759 $read_style = '';
2760 // ignore trackign status if not tracked or tracked param missing
2761 if ($istracked) {
2762 if (is_null($post_read)) {
2763 debugging('fetching post_read info');
2764 $post_read = forum_tp_is_post_read($USER->id, $post);
2767 if ($post_read) {
2768 $read_style = ' read';
2769 } else {
2770 $read_style = ' unread';
2771 echo '<a name="unread"></a>';
2775 echo '<a id="p'.$post->id.'"></a>';
2776 echo '<table cellspacing="0" class="forumpost'.$read_style.'">';
2778 // Picture
2779 $postuser = new object();
2780 $postuser->id = $post->userid;
2781 $postuser->firstname = $post->firstname;
2782 $postuser->lastname = $post->lastname;
2783 $postuser->imagealt = $post->imagealt;
2784 $postuser->picture = $post->picture;
2786 echo '<tr class="header"><td class="picture left">';
2787 print_user_picture($postuser, $course->id);
2788 echo '</td>';
2790 if ($post->parent) {
2791 echo '<td class="topic">';
2792 } else {
2793 echo '<td class="topic starter">';
2796 if (!empty($post->subjectnoformat)) {
2797 echo '<div class="subject">'.$post->subject.'</div>';
2798 } else {
2799 echo '<div class="subject">'.format_string($post->subject).'</div>';
2802 echo '<div class="author">';
2803 $fullname = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
2804 $by = new object();
2805 $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.
2806 $post->userid.'&amp;course='.$course->id.'">'.$fullname.'</a>';
2807 $by->date = userdate($post->modified);
2808 print_string('bynameondate', 'forum', $by);
2809 echo '</div></td></tr>';
2811 echo '<tr><td class="left side">';
2812 if (isset($cm->cache->usersgroups)) {
2813 $groups = array();
2814 if (isset($cm->cache->usersgroups[$post->userid])) {
2815 foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
2816 $groups[$gid] = $cm->cache->groups[$gid];
2819 } else {
2820 $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
2823 if ($groups) {
2824 print_group_picture($groups, $course->id, false, false, true);
2825 } else {
2826 echo '&nbsp;';
2829 // Actual content
2831 echo '</td><td class="content">'."\n";
2833 if ($post->attachment) {
2834 echo '<div class="attachments">';
2835 $attachedimages = forum_print_attachments($post);
2836 echo '</div>';
2837 } else {
2838 $attachedimages = '';
2842 $options = new object();
2843 $options->para = false;
2844 $options->trusttext = true;
2845 if ($link and (strlen(strip_tags($post->message)) > $CFG->forum_longpost)) {
2846 // Print shortened version
2847 echo format_text(forum_shorten_post($post->message), $post->format, $options, $course->id);
2848 $numwords = count_words(strip_tags($post->message));
2849 echo '<p><a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
2850 echo get_string('readtherest', 'forum');
2851 echo '</a> ('.get_string('numwords', '', $numwords).')...</p>';
2852 } else {
2853 // Print whole message
2854 if ($highlight) {
2855 echo highlight($highlight, format_text($post->message, $post->format, $options, $course->id));
2856 } else {
2857 echo format_text($post->message, $post->format, $options, $course->id);
2859 echo $attachedimages;
2863 // Commands
2865 $commands = array();
2867 if ($istracked) {
2868 // SPECIAL CASE: The front page can display a news item post to non-logged in users.
2869 // Don't display the mark read / unread controls in this case.
2870 if ($CFG->forum_usermarksread and isloggedin()) {
2871 if ($post_read) {
2872 $mcmd = '&amp;mark=unread&amp;postid='.$post->id;
2873 $mtxt = $strmarkunread;
2874 } else {
2875 $mcmd = '&amp;mark=read&amp;postid='.$post->id;
2876 $mtxt = $strmarkread;
2878 if ($displaymode == FORUM_MODE_THREADED) {
2879 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
2880 $post->discussion.'&amp;parent='.$post->id.$mcmd.'">'.$mtxt.'</a>';
2881 } else {
2882 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
2883 $post->discussion.$mcmd.'#p'.$post->id.'">'.$mtxt.'</a>';
2888 if ($post->parent) { // Zoom in to the parent specifically
2889 if ($displaymode == FORUM_MODE_THREADED) {
2890 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
2891 $post->discussion.'&amp;parent='.$post->parent.'">'.$strparent.'</a>';
2892 } else {
2893 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
2894 $post->discussion.'#p'.$post->parent.'">'.$strparent.'</a>';
2898 $age = time() - $post->created;
2899 // Hack for allow to edit news posts those are not displayed yet until they are displayed
2900 if (!$post->parent and $forum->type == 'news' and $discussion->timestart > time()) {
2901 $age = 0;
2903 $editanypost = $cm->cache->caps['mod/forum:editanypost'];
2905 if ($ownpost or $editanypost) {
2906 if (($age < $CFG->maxeditingtime) or $editanypost) {
2907 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?edit='.$post->id.'">'.$stredit.'</a>';
2911 if ($cm->cache->caps['mod/forum:splitdiscussions']
2912 && $post->parent && $forum->type != 'single') {
2914 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?prune='.$post->id.
2915 '" title="'.$strpruneheading.'">'.$strprune.'</a>';
2918 if (($ownpost and $age < $CFG->maxeditingtime
2919 and $cm->cache->caps['mod/forum:deleteownpost'])
2920 or $cm->cache->caps['mod/forum:deleteanypost']) {
2921 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?delete='.$post->id.'">'.$strdelete.'</a>';
2924 if ($reply) {
2925 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.$strreply.'</a>';
2928 echo '<div class="commands">';
2929 echo implode(' | ', $commands);
2930 echo '</div>';
2933 // Ratings
2935 $ratingsmenuused = false;
2936 if (!empty($ratings) and isloggedin()) {
2937 echo '<div class="ratings">';
2938 $useratings = true;
2939 if ($ratings->assesstimestart and $ratings->assesstimefinish) {
2940 if ($post->created < $ratings->assesstimestart or $post->created > $ratings->assesstimefinish) {
2941 $useratings = false;
2944 if ($useratings) {
2945 $mypost = ($USER->id == $post->userid);
2947 $canviewallratings = $cm->cache->caps['mod/forum:viewanyrating'];
2949 if (isset($cm->cache->ratings)) {
2950 if (isset($cm->cache->ratings[$post->id])) {
2951 $allratings = $cm->cache->ratings[$post->id];
2952 } else {
2953 $allratings = array(); // no reatings present yet
2955 } else {
2956 $allratings = NULL; // not preloaded
2959 if (isset($cm->cache->myratings)) {
2960 if (isset($cm->cache->myratings[$post->id])) {
2961 $myrating = $cm->cache->myratings[$post->id];
2962 } else {
2963 $myrating = FORUM_UNSET_POST_RATING; // no reatings present yet
2965 } else {
2966 $myrating = NULL; // not preloaded
2969 if ($canviewallratings and !$mypost) {
2970 forum_print_ratings($post->id, $ratings->scale, $forum->assessed, $canviewallratings, $allratings);
2971 if (!empty($ratings->allow)) {
2972 echo '&nbsp;';
2973 forum_print_rating_menu($post->id, $USER->id, $ratings->scale, $myrating);
2974 $ratingsmenuused = true;
2977 } else if ($mypost) {
2978 forum_print_ratings($post->id, $ratings->scale, $forum->assessed, true, $allratings);
2980 } else if (!empty($ratings->allow) ) {
2981 forum_print_rating_menu($post->id, $USER->id, $ratings->scale, $myrating);
2982 $ratingsmenuused = true;
2985 echo '</div>';
2988 // Link to post if required
2990 if ($link) {
2991 echo '<div class="link">';
2992 if ($post->replies == 1) {
2993 $replystring = get_string('repliesone', 'forum', $post->replies);
2994 } else {
2995 $replystring = get_string('repliesmany', 'forum', $post->replies);
2997 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.
2998 get_string('discussthistopic', 'forum').'</a>&nbsp;('.$replystring.')';
2999 echo '</div>';
3002 if ($footer) {
3003 echo '<div class="footer">'.$footer.'</div>';
3005 echo '</td></tr></table>'."\n\n";
3007 if ($istracked && !$CFG->forum_usermarksread && !$post_read) {
3008 forum_tp_mark_post_read($USER->id, $post, $forum->id);
3011 return $ratingsmenuused;
3016 * This function prints the overview of a discussion in the forum listing.
3017 * It needs some discussion information and some post information, these
3018 * happen to be combined for efficiency in the $post parameter by the function
3019 * that calls this one: forum_print_latest_discussions()
3021 * @param object $post The post object (passed by reference for speed).
3022 * @param object $forum The forum object.
3023 * @param int $group Current group.
3024 * @param string $datestring Format to use for the dates.
3025 * @param boolean $cantrack Is tracking enabled for this forum.
3026 * @param boolean $forumtracked Is the user tracking this forum.
3027 * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
3029 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
3030 $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) {
3032 global $USER, $CFG;
3034 static $rowcount;
3035 static $strmarkalldread;
3037 if (empty($modcontext)) {
3038 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3039 error('Course Module ID was incorrect');
3041 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3044 if (!isset($rowcount)) {
3045 $rowcount = 0;
3046 $strmarkalldread = get_string('markalldread', 'forum');
3047 } else {
3048 $rowcount = ($rowcount + 1) % 2;
3051 $post->subject = format_string($post->subject,true);
3053 echo "\n\n";
3054 echo '<tr class="discussion r'.$rowcount.'">';
3056 // Topic
3057 echo '<td class="topic starter">';
3058 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.$post->subject.'</a>';
3059 echo "</td>\n";
3061 // Picture
3062 $postuser = new object;
3063 $postuser->id = $post->userid;
3064 $postuser->firstname = $post->firstname;
3065 $postuser->lastname = $post->lastname;
3066 $postuser->imagealt = $post->imagealt;
3067 $postuser->picture = $post->picture;
3069 echo '<td class="picture">';
3070 print_user_picture($postuser, $forum->course);
3071 echo "</td>\n";
3073 // User name
3074 $fullname = fullname($post, has_capability('moodle/site:viewfullnames', $modcontext));
3075 echo '<td class="author">';
3076 echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->userid.'&amp;course='.$forum->course.'">'.$fullname.'</a>';
3077 echo "</td>\n";
3079 // Group picture
3080 if ($group !== -1) { // Groups are active - group is a group data object or NULL
3081 echo '<td class="picture group">';
3082 if (!empty($group->picture) and empty($group->hidepicture)) {
3083 print_group_picture($group, $forum->course, false, false, true);
3084 } else if (isset($group->id)) {
3085 if($canviewparticipants) {
3086 echo '<a href="'.$CFG->wwwroot.'/user/index.php?id='.$forum->course.'&amp;group='.$group->id.'">'.$group->name.'</a>';
3087 } else {
3088 echo $group->name;
3091 echo "</td>\n";
3094 if (has_capability('mod/forum:viewdiscussion', $modcontext)) { // Show the column with replies
3095 echo '<td class="replies">';
3096 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3097 echo $post->replies.'</a>';
3098 echo "</td>\n";
3100 if ($cantrack) {
3101 echo '<td class="replies">';
3102 if ($forumtracked) {
3103 if ($post->unread > 0) {
3104 echo '<span class="unread">';
3105 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#unread">';
3106 echo $post->unread;
3107 echo '</a>';
3108 echo '<a title="'.$strmarkalldread.'" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
3109 $forum->id.'&amp;d='.$post->discussion.'&amp;mark=read&amp;returnpage=view.php">' .
3110 '<img src="'.$CFG->pixpath.'/t/clear.gif" class="iconsmall" alt="'.$strmarkalldread.'" /></a>';
3111 echo '</span>';
3112 } else {
3113 echo '<span class="read">';
3114 echo $post->unread;
3115 echo '</span>';
3117 } else {
3118 echo '<span class="read">';
3119 echo '-';
3120 echo '</span>';
3122 echo "</td>\n";
3126 echo '<td class="lastpost">';
3127 $usedate = (empty($post->timemodified)) ? $post->modified : $post->timemodified; // Just in case
3128 $parenturl = (empty($post->lastpostid)) ? '' : '&amp;parent='.$post->lastpostid;
3129 $usermodified = new object();
3130 $usermodified->id = $post->usermodified;
3131 $usermodified->firstname = $post->umfirstname;
3132 $usermodified->lastname = $post->umlastname;
3133 echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->usermodified.'&amp;course='.$forum->course.'">'.
3134 fullname($usermodified).'</a><br />';
3135 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.$parenturl.'">'.
3136 userdate($usedate, $datestring).'</a>';
3137 echo "</td>\n";
3139 echo "</tr>\n\n";
3145 * Given a post object that we already know has a long message
3146 * this function truncates the message nicely to the first
3147 * sane place between $CFG->forum_longpost and $CFG->forum_shortpost
3149 function forum_shorten_post($message) {
3151 global $CFG;
3153 $i = 0;
3154 $tag = false;
3155 $length = strlen($message);
3156 $count = 0;
3157 $stopzone = false;
3158 $truncate = 0;
3160 for ($i=0; $i<$length; $i++) {
3161 $char = $message[$i];
3163 switch ($char) {
3164 case "<":
3165 $tag = true;
3166 break;
3167 case ">":
3168 $tag = false;
3169 break;
3170 default:
3171 if (!$tag) {
3172 if ($stopzone) {
3173 if ($char == ".") {
3174 $truncate = $i+1;
3175 break 2;
3178 $count++;
3180 break;
3182 if (!$stopzone) {
3183 if ($count > $CFG->forum_shortpost) {
3184 $stopzone = true;
3189 if (!$truncate) {
3190 $truncate = $i;
3193 return substr($message, 0, $truncate);
3198 * Print the multiple ratings on a post given to the current user by others.
3199 * Forumid prevents the double lookup of the forumid in discussion to determine the aggregate type
3200 * Scale is an array of ratings
3202 function forum_print_ratings($postid, $scale, $aggregatetype, $link=true, $ratings=null) {
3204 $strratings = '';
3206 switch ($aggregatetype) {
3207 case FORUM_AGGREGATE_AVG :
3208 $agg = forum_get_ratings_mean($postid, $scale, $ratings);
3209 $strratings = get_string("aggregateavg", "forum");
3210 break;
3211 case FORUM_AGGREGATE_COUNT :
3212 $agg = forum_get_ratings_count($postid, $scale, $ratings);
3213 $strratings = get_string("aggregatecount", "forum");
3214 break;
3215 case FORUM_AGGREGATE_MAX :
3216 $agg = forum_get_ratings_max($postid, $scale, $ratings);
3217 $strratings = get_string("aggregatemax", "forum");
3218 break;
3219 case FORUM_AGGREGATE_MIN :
3220 $agg = forum_get_ratings_min($postid, $scale, $ratings);
3221 $strratings = get_string("aggregatemin", "forum");
3222 break;
3223 case FORUM_AGGREGATE_SUM :
3224 $agg = forum_get_ratings_sum($postid, $scale, $ratings);
3225 $strratings = get_string("aggregatesum", "forum");
3226 break;
3229 if ($agg !== "") {
3231 if (empty($strratings)) {
3232 $strratings = get_string("ratings", "forum");
3235 echo "$strratings: ";
3236 if ($link) {
3237 link_to_popup_window ("/mod/forum/report.php?id=$postid", "ratings", $agg, 400, 600);
3238 } else {
3239 echo "$agg ";
3246 * Return the mean rating of a post given to the current user by others.
3247 * Scale is an array of possible ratings in the scale
3248 * Ratings is an optional simple array of actual ratings (just integers)
3249 * Forumid is the forum id field needed - passing it avoids a double query of lookup up the discusion and then the forum id to get the aggregate type
3251 function forum_get_ratings_mean($postid, $scale, $ratings=NULL) {
3253 if (is_null($ratings)) {
3254 $ratings = array();
3255 if ($rates = get_records("forum_ratings", "post", $postid)) {
3256 foreach ($rates as $rate) {
3257 $ratings[] = $rate->rating;
3262 $count = count($ratings);
3264 if ($count == 0 ) {
3265 return "";
3267 } else if ($count == 1) {
3268 $rating = reset($ratings);
3269 return $scale[$rating];
3271 } else {
3272 $total = 0;
3273 foreach ($ratings as $rating) {
3274 $total += $rating;
3276 $mean = round( ((float)$total/(float)$count) + 0.001); // Little fudge factor so that 0.5 goes UP
3278 if (isset($scale[$mean])) {
3279 return $scale[$mean]." ($count)";
3280 } else {
3281 return "$mean ($count)"; // Should never happen, hopefully
3287 * Return the count of the ratings of a post given to the current user by others.
3288 * Scale is an array of possible ratings in the scale - the end of the scale is the highest or max grade
3289 * Ratings is an optional simple array of actual ratings (just integers)
3291 function forum_get_ratings_count($postid, $scale, $ratings=NULL) {
3293 if (is_null($ratings)) {
3294 $ratings = array();
3295 if ($rates = get_records("forum_ratings", "post", $postid)) {
3296 foreach ($rates as $rate) {
3297 $ratings[] = $rate->rating;
3302 $count = count($ratings);
3303 $scalecount = count($scale)-1; //this should give us the last element of the scale aka the max grade with $scale[$scalecount]
3305 if ($count > $scale[$scalecount]) { //if the count exceeds the forum scale (i.e. max grade then set the score to the max grade
3306 $count = $scale[$scalecount];
3308 return $scale[$count];
3312 * Return the max rating of a post given to the current user by others.
3313 * Scale is an array of possible ratings in the scale
3314 * Ratings is an optional simple array of actual ratings (just integers)
3316 function forum_get_ratings_max($postid, $scale, $ratings=NULL) {
3318 if (is_null($ratings)) {
3319 $ratings = array();
3320 if ($rates = get_records("forum_ratings", "post", $postid)) {
3321 foreach ($rates as $rate) {
3322 $ratings[] = $rate->rating;
3327 $count = count($ratings);
3328 $max = max($ratings);
3330 if ($count == 0 ) {
3331 return "";
3333 } else if ($count == 1) { //this works for max
3334 $rating = reset($ratings);
3335 return $scale[$rating];
3337 } else {
3339 if (isset($scale[$max])) {
3340 return $scale[$max]." ($count)";
3341 } else {
3342 return "$max ($count)"; // Should never happen, hopefully
3348 * Return the min rating of a post given to the current user by others.
3349 * Scale is an array of possible ratings in the scale
3350 * Ratings is an optional simple array of actual ratings (just integers)
3352 function forum_get_ratings_min($postid, $scale, $ratings=NULL) {
3354 if (is_null($ratings)) {
3355 $ratings = array();
3356 if ($rates = get_records("forum_ratings", "post", $postid)) {
3357 foreach ($rates as $rate) {
3358 $ratings[] = $rate->rating;
3363 $count = count($ratings);
3364 $min = min($ratings);
3366 if ($count == 0 ) {
3367 return "";
3369 } else if ($count == 1) {
3370 $rating = reset($ratings);
3371 return $scale[$rating]; //this works for min
3373 } else {
3375 if (isset($scale[$min])) {
3376 return $scale[$min]." ($count)";
3377 } else {
3378 return "$min ($count)"; // Should never happen, hopefully
3385 * Return the sum or total of ratings of a post given to the current user by others.
3386 * Scale is an array of possible ratings in the scale
3387 * Ratings is an optional simple array of actual ratings (just integers)
3389 function forum_get_ratings_sum($postid, $scale, $ratings=NULL) {
3391 if (is_null($ratings)) {
3392 $ratings = array();
3393 if ($rates = get_records("forum_ratings", "post", $postid)) {
3394 foreach ($rates as $rate) {
3395 $ratings[] = $rate->rating;
3400 $count = count($ratings);
3401 $scalecount = count($scale)-1; //this should give us the last element of the scale aka the max grade with $scale[$scalecount]
3403 if ($count == 0 ) {
3404 return "";
3406 } else if ($count == 1) { //this works for max.
3407 $rating = reset($ratings);
3408 return $scale[$rating];
3410 } else {
3411 $total = 0;
3412 foreach ($ratings as $rating) {
3413 $total += $rating;
3415 if ($total > $scale[$scalecount]) { //if the total exceeds the max grade then set it to the max grade
3416 $total = $scale[$scalecount];
3418 if (isset($scale[$total])) {
3419 return $scale[$total]." ($count)";
3420 } else {
3421 return "$total ($count)"; // Should never happen, hopefully
3427 * Return a summary of post ratings given to the current user by others.
3428 * Scale is an array of possible ratings in the scale
3429 * Ratings is an optional simple array of actual ratings (just integers)
3431 function forum_get_ratings_summary($postid, $scale, $ratings=NULL) {
3433 if (is_null($ratings)) {
3434 $ratings = array();
3435 if ($rates = get_records("forum_ratings", "post", $postid)) {
3436 foreach ($rates as $rate) {
3437 $rating[] = $rate->rating;
3443 if (!$count = count($ratings)) {
3444 return "";
3448 foreach ($scale as $key => $scaleitem) {
3449 $sumrating[$key] = 0;
3452 foreach ($ratings as $rating) {
3453 $sumrating[$rating]++;
3456 $summary = "";
3457 foreach ($scale as $key => $scaleitem) {
3458 $summary = $sumrating[$key].$summary;
3459 if ($key > 1) {
3460 $summary = "/$summary";
3463 return $summary;
3467 * Print the menu of ratings as part of a larger form.
3468 * If the post has already been - set that value.
3469 * Scale is an array of ratings
3471 function forum_print_rating_menu($postid, $userid, $scale, $myrating=NULL) {
3473 static $strrate;
3475 if (is_null($myrating)) {
3476 if (!$rating = get_record("forum_ratings", "userid", $userid, "post", $postid)) {
3477 $myrating = FORUM_UNSET_POST_RATING;
3478 } else {
3479 $myrating = $rating->rating;
3483 if (empty($strrate)) {
3484 $strrate = get_string("rate", "forum");
3486 $scale = array(FORUM_UNSET_POST_RATING => $strrate.'...') + $scale;
3487 choose_from_menu($scale, $postid, $myrating, '');
3491 * Print the drop down that allows the user to select how they want to have
3492 * the discussion displayed.
3493 * @param $id - forum id if $forumtype is 'single',
3494 * discussion id for any other forum type
3495 * @param $mode - forum layout mode
3496 * @param $forumtype - optional
3498 function forum_print_mode_form($id, $mode, $forumtype='') {
3499 if ($forumtype == 'single') {
3500 echo '<div class="forummode">';
3501 popup_form("view.php?f=$id&amp;mode=", forum_get_layout_modes(), "mode", $mode, "");
3502 echo '</div>';
3503 } else {
3504 popup_form("discuss.php?d=$id&amp;mode=", forum_get_layout_modes(), "mode", $mode, "");
3511 function forum_search_form($course, $search='') {
3512 global $CFG;
3514 $output = '<div class="forumsearch">';
3515 $output .= '<form action="'.$CFG->wwwroot.'/mod/forum/search.php" style="display:inline">';
3516 $output .= '<fieldset class="invisiblefieldset">';
3517 $output .= helpbutton('search', get_string('search'), 'moodle', true, false, '', true);
3518 $output .= '<input name="search" type="text" size="18" value="'.s($search, true).'" alt="search" />';
3519 $output .= '<input value="'.get_string('searchforums', 'forum').'" type="submit" />';
3520 $output .= '<input name="id" type="hidden" value="'.$course->id.'" />';
3521 $output .= '</fieldset>';
3522 $output .= '</form>';
3523 $output .= '</div>';
3525 return $output;
3532 function forum_set_return() {
3533 global $CFG, $SESSION;
3535 if (! isset($SESSION->fromdiscussion)) {
3536 if (!empty($_SERVER['HTTP_REFERER'])) {
3537 $referer = $_SERVER['HTTP_REFERER'];
3538 } else {
3539 $referer = "";
3541 // If the referer is NOT a login screen then save it.
3542 if (! strncasecmp("$CFG->wwwroot/login", $referer, 300)) {
3543 $SESSION->fromdiscussion = $_SERVER["HTTP_REFERER"];
3552 function forum_go_back_to($default) {
3553 global $SESSION;
3555 if (!empty($SESSION->fromdiscussion)) {
3556 $returnto = $SESSION->fromdiscussion;
3557 unset($SESSION->fromdiscussion);
3558 return $returnto;
3559 } else {
3560 return $default;
3565 * Creates a directory file name, suitable for make_upload_directory()
3567 function forum_file_area_name($post) {
3568 global $CFG;
3570 if (!isset($post->forum) or !isset($post->course)) {
3571 debugging('missing forum or course', DEBUG_DEVELOPER);
3572 if (!$discussion = get_record('forum_discussions', 'id', $post->discussion)) {
3573 return false;
3575 if (!$forum = get_record('forum', 'id', $discussion->forum)) {
3576 return false;
3578 $forumid = $forum->id;
3579 $courseid = $forum->course;
3580 } else {
3581 $forumid = $post->forum;
3582 $courseid = $post->course;
3585 return "$courseid/$CFG->moddata/forum/$forumid/$post->id";
3591 function forum_file_area($post) {
3592 return make_upload_directory( forum_file_area_name($post) );
3598 function forum_delete_old_attachments($post, $exception="") {
3601 * Deletes all the user files in the attachments area for a post
3602 * EXCEPT for any file named $exception
3604 if ($basedir = forum_file_area($post)) {
3605 if ($files = get_directory_list($basedir)) {
3606 foreach ($files as $file) {
3607 if ($file != $exception) {
3608 unlink("$basedir/$file");
3609 notify("Existing file '$file' has been deleted!");
3613 if (!$exception) { // Delete directory as well, if empty
3614 rmdir("$basedir");
3620 * Given a discussion object that is being moved to forumid,
3621 * this function checks all posts in that discussion
3622 * for attachments, and if any are found, these are
3623 * moved to the new forum directory.
3625 function forum_move_attachments($discussion, $forumid) {
3627 global $CFG;
3629 require_once($CFG->dirroot.'/lib/uploadlib.php');
3631 $return = true;
3633 if ($posts = get_records_select("forum_posts", "discussion = '$discussion->id' AND attachment <> ''")) {
3634 foreach ($posts as $oldpost) {
3635 $oldpost->course = $discussion->course;
3636 $oldpost->forum = $discussion->forum;
3637 $oldpostdir = "$CFG->dataroot/".forum_file_area_name($oldpost);
3638 if (is_dir($oldpostdir)) {
3639 $newpost = $oldpost;
3640 $newpost->forum = $forumid;
3641 $newpostdir = forum_file_area_name($newpost);
3642 // take off the last directory because otherwise we're renaming to a directory that already exists
3643 // and this is unhappy in certain situations, eg over an nfs mount and potentially on windows too.
3644 make_upload_directory(substr($newpostdir,0,strrpos($newpostdir,'/')));
3645 $newpostdir = $CFG->dataroot.'/'.forum_file_area_name($newpost);
3646 $files = get_directory_list($oldpostdir); // get it before we rename it.
3647 if (! @rename($oldpostdir, $newpostdir)) {
3648 $return = false;
3650 foreach ($files as $file) {
3651 clam_change_log($oldpostdir.'/'.$file,$newpostdir.'/'.$file);
3656 return $return;
3660 * if return=html, then return a html string.
3661 * if return=text, then return a text-only string.
3662 * otherwise, print HTML for non-images, and return image HTML
3664 function forum_print_attachments($post, $return=NULL) {
3666 global $CFG;
3668 $filearea = forum_file_area_name($post);
3670 $imagereturn = "";
3671 $output = "";
3673 if ($basedir = forum_file_area($post)) {
3674 if ($files = get_directory_list($basedir)) {
3675 $strattachment = get_string("attachment", "forum");
3676 foreach ($files as $file) {
3677 $icon = mimeinfo("icon", $file);
3678 $type = mimeinfo("type", $file);
3679 $ffurl = get_file_url("$filearea/$file");
3680 $image = "<img src=\"$CFG->pixpath/f/$icon\" class=\"icon\" alt=\"\" />";
3682 if ($return == "html") {
3683 $output .= "<a href=\"$ffurl\">$image</a> ";
3684 $output .= "<a href=\"$ffurl\">$file</a><br />";
3686 } else if ($return == "text") {
3687 $output .= "$strattachment $file:\n$ffurl\n";
3689 } else {
3690 if (in_array($type, array('image/gif', 'image/jpeg', 'image/png'))) { // Image attachments don't get printed as links
3691 $imagereturn .= "<br /><img src=\"$ffurl\" alt=\"\" />";
3692 } else {
3693 echo "<a href=\"$ffurl\">$image</a> ";
3694 echo filter_text("<a href=\"$ffurl\">$file</a><br />");
3701 if ($return) {
3702 return $output;
3705 return $imagereturn;
3708 * If successful, this function returns the name of the file
3709 * @param $post is a full post record, including course and forum
3710 * @param $newfile is a full upload array from $_FILES
3711 * @param $message is a string to hold the messages.
3717 function forum_add_attachment($post, $inputname,&$message) {
3719 global $CFG;
3721 if (!$forum = get_record("forum", "id", $post->forum)) {
3722 return "";
3725 if (!$course = get_record("course", "id", $forum->course)) {
3726 return "";
3729 require_once($CFG->dirroot.'/lib/uploadlib.php');
3730 $um = new upload_manager($inputname,true,false,$course,false,$forum->maxbytes,true,true);
3731 $dir = forum_file_area_name($post);
3732 if ($um->process_file_uploads($dir)) {
3733 $message .= $um->get_errors();
3734 return $um->get_new_filename();
3736 $message .= $um->get_errors();
3737 return null;
3743 function forum_add_new_post($post,&$message) {
3745 global $USER, $CFG;
3747 $discussion = get_record('forum_discussions', 'id', $post->discussion);
3748 $forum = get_record('forum', 'id', $discussion->forum);
3750 $post->created = $post->modified = time();
3751 $post->mailed = "0";
3752 $post->userid = $USER->id;
3753 $post->attachment = "";
3754 $post->forum = $forum->id; // speedup
3755 $post->course = $forum->course; // speedup
3757 if (! $post->id = insert_record("forum_posts", $post)) {
3758 return false;
3761 if ($post->attachment = forum_add_attachment($post, 'attachment',$message)) {
3762 set_field("forum_posts", "attachment", $post->attachment, "id", $post->id);
3765 // Update discussion modified date
3766 set_field("forum_discussions", "timemodified", $post->modified, "id", $post->discussion);
3767 set_field("forum_discussions", "usermodified", $post->userid, "id", $post->discussion);
3769 if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
3770 forum_tp_mark_post_read($post->userid, $post, $post->forum);
3773 return $post->id;
3779 function forum_update_post($post,&$message) {
3781 global $USER, $CFG;
3783 $forum = get_record('forum', 'id', $post->forum);
3785 $post->modified = time();
3787 $updatediscussion = new object();
3788 $updatediscussion->id = $post->discussion;
3789 $updatediscussion->timemodified = $post->modified; // last modified tracking
3790 $updatediscussion->usermodified = $post->userid; // last modified tracking
3792 if (!$post->parent) { // Post is a discussion starter - update discussion title and times too
3793 $updatediscussion->name = $post->subject;
3794 $updatediscussion->timestart = $post->timestart;
3795 $updatediscussion->timeend = $post->timeend;
3798 if (!update_record('forum_discussions', $updatediscussion)) {
3799 return false;
3802 if ($newfilename = forum_add_attachment($post, 'attachment',$message)) {
3803 $post->attachment = $newfilename;
3804 } else {
3805 unset($post->attachment);
3808 if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
3809 forum_tp_mark_post_read($post->userid, $post, $post->forum);
3812 return update_record('forum_posts', $post);
3816 * Given an object containing all the necessary data,
3817 * create a new discussion and return the id
3819 function forum_add_discussion($discussion,&$message) {
3821 global $USER, $CFG;
3823 $timenow = time();
3825 // The first post is stored as a real post, and linked
3826 // to from the discuss entry.
3828 $forum = get_record('forum', 'id', $discussion->forum);
3830 $post = new object();
3831 $post->discussion = 0;
3832 $post->parent = 0;
3833 $post->userid = $USER->id;
3834 $post->created = $timenow;
3835 $post->modified = $timenow;
3836 $post->mailed = 0;
3837 $post->subject = $discussion->name;
3838 $post->message = $discussion->intro;
3839 $post->attachment = "";
3840 $post->forum = $forum->id; // speedup
3841 $post->course = $forum->course; // speedup
3842 $post->format = $discussion->format;
3843 $post->mailnow = $discussion->mailnow;
3845 if (! $post->id = insert_record("forum_posts", $post) ) {
3846 return 0;
3849 if ($post->attachment = forum_add_attachment($post, 'attachment',$message)) {
3850 set_field("forum_posts", "attachment", $post->attachment, "id", $post->id); //ignore errors
3853 // Now do the main entry for the discussion,
3854 // linking to this first post
3856 $discussion->firstpost = $post->id;
3857 $discussion->timemodified = $timenow;
3858 $discussion->usermodified = $post->userid;
3859 $discussion->userid = $USER->id;
3861 if (! $post->discussion = insert_record("forum_discussions", $discussion) ) {
3862 delete_records("forum_posts", "id", $post->id);
3863 return 0;
3866 // Finally, set the pointer on the post.
3867 if (! set_field("forum_posts", "discussion", $post->discussion, "id", $post->id)) {
3868 delete_records("forum_posts", "id", $post->id);
3869 delete_records("forum_discussions", "id", $post->discussion);
3870 return 0;
3873 if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
3874 forum_tp_mark_post_read($post->userid, $post, $post->forum);
3877 return $post->discussion;
3884 function forum_delete_discussion($discussion, $fulldelete=false) {
3885 // $discussion is a discussion record object
3887 $result = true;
3889 if ($posts = get_records("forum_posts", "discussion", $discussion->id)) {
3890 foreach ($posts as $post) {
3891 $post->course = $discussion->course;
3892 $post->forum = $discussion->forum;
3893 if (! delete_records("forum_ratings", "post", "$post->id")) {
3894 $result = false;
3896 if (! forum_delete_post($post, $fulldelete)) {
3897 $result = false;
3902 forum_tp_delete_read_records(-1, -1, $discussion->id);
3904 if (! delete_records("forum_discussions", "id", "$discussion->id")) {
3905 $result = false;
3908 return $result;
3915 function forum_delete_post($post, $children=false) {
3916 if ($childposts = get_records('forum_posts', 'parent', $post->id)) {
3917 if ($children) {
3918 foreach ($childposts as $childpost) {
3919 forum_delete_post($childpost, true);
3921 } else {
3922 return false;
3925 if (delete_records("forum_posts", "id", $post->id)) {
3926 delete_records("forum_ratings", "post", $post->id); // Just in case
3928 forum_tp_delete_read_records(-1, $post->id);
3930 if ($post->attachment) {
3931 $discussion = get_record("forum_discussions", "id", $post->discussion);
3932 $post->course = $discussion->course;
3933 $post->forum = $discussion->forum;
3934 forum_delete_old_attachments($post);
3937 // Just in case we are deleting the last post
3938 forum_discussion_update_last_post($post->discussion);
3940 return true;
3942 return false;
3948 function forum_count_replies($post, $children=true) {
3949 $count = 0;
3951 if ($children) {
3952 if ($childposts = get_records('forum_posts', 'parent', $post->id)) {
3953 foreach ($childposts as $childpost) {
3954 $count ++; // For this child
3955 $count += forum_count_replies($childpost, true);
3958 } else {
3959 $count += count_records('forum_posts', 'parent', $post->id);
3962 return $count;
3969 function forum_forcesubscribe($forumid, $value=1) {
3970 return set_field("forum", "forcesubscribe", $value, "id", $forumid);
3976 function forum_is_forcesubscribed($forum) {
3977 if (isset($forum->forcesubscribe)) { // then we use that
3978 return ($forum->forcesubscribe == FORUM_FORCESUBSCRIBE);
3979 } else { // Check the database
3980 return (get_field('forum', 'forcesubscribe', 'id', $forum) == FORUM_FORCESUBSCRIBE);
3987 function forum_is_subscribed($userid, $forum) {
3988 if (is_numeric($forum)) {
3989 $forum = get_record('forum', 'id', $forum);
3991 if (forum_is_forcesubscribed($forum)) {
3992 return true;
3994 return record_exists("forum_subscriptions", "userid", $userid, "forum", $forum->id);
3997 function forum_get_subscribed_forums($course) {
3998 global $USER, $CFG;
3999 $sql = "SELECT f.id
4000 FROM {$CFG->prefix}forum f
4001 LEFT JOIN {$CFG->prefix}forum_subscriptions fs ON (fs.forum = f.id AND fs.userid = $USER->id)
4002 WHERE f.forcesubscribe <> ".FORUM_DISALLOWSUBSCRIBE."
4003 AND (f.forcesubscribe = ".FORUM_FORCESUBSCRIBE." OR fs.id IS NOT NULL)";
4004 if ($subscribed = get_records_sql($sql)) {
4005 foreach ($subscribed as $s) {
4006 $subscribed[$s->id] = $s->id;
4008 return $subscribed;
4009 } else {
4010 return array();
4015 * Adds user to the subscriber list
4017 function forum_subscribe($userid, $forumid) {
4019 if (record_exists("forum_subscriptions", "userid", $userid, "forum", $forumid)) {
4020 return true;
4023 $sub = new object();
4024 $sub->userid = $userid;
4025 $sub->forum = $forumid;
4027 return insert_record("forum_subscriptions", $sub);
4031 * Removes user from the subscriber list
4033 function forum_unsubscribe($userid, $forumid) {
4034 return delete_records("forum_subscriptions", "userid", $userid, "forum", $forumid);
4038 * Given a new post, subscribes or unsubscribes as appropriate.
4039 * Returns some text which describes what happened.
4041 function forum_post_subscription($post) {
4043 global $USER;
4045 $subscribed=forum_is_subscribed($USER->id, $post->forum);
4046 if ((isset($post->subscribe) && $post->subscribe && $subscribed)
4047 || (!$post->subscribe && !$subscribed)) {
4048 return "";
4051 if (!$forum = get_record("forum", "id", $post->forum)) {
4052 return "";
4055 $info = new object();
4056 $info->name = fullname($USER);
4057 $info->forum = $forum->name;
4059 if (!empty($post->subscribe)) {
4060 forum_subscribe($USER->id, $post->forum);
4061 return "<p>".get_string("nowsubscribed", "forum", $info)."</p>";
4064 forum_unsubscribe($USER->id, $post->forum);
4065 return "<p>".get_string("nownotsubscribed", "forum", $info)."</p>";
4069 * Generate and return the subscribe or unsubscribe link for a forum.
4070 * @param object $forum the forum. Fields used are $forum->id and $forum->forcesubscribe.
4071 * @param object $context the context object for this forum.
4072 * @param array $messages text used for the link in its various states
4073 * (subscribed, unsubscribed, forcesubscribed or cantsubscribe).
4074 * Any strings not passed in are taken from the $defaultmessages array
4075 * at the top of the function.
4076 * @param
4078 function forum_get_subscribe_link($forum, $context, $messages = array(), $cantaccessagroup = false, $fakelink=true, $backtoindex=false, $subscribed_forums=null) {
4079 global $CFG, $USER;
4080 $defaultmessages = array(
4081 'subscribed' => get_string('unsubscribe', 'forum'),
4082 'unsubscribed' => get_string('subscribe', 'forum'),
4083 'cantaccessgroup' => get_string('no'),
4084 'forcesubscribed' => get_string('everyoneissubscribed', 'forum'),
4085 'cantsubscribe' => get_string('disallowsubscribe','forum')
4087 $messages = $messages + $defaultmessages;
4089 if (forum_is_forcesubscribed($forum)) {
4090 return $messages['forcesubscribed'];
4091 } else if ($forum->forcesubscribe == FORUM_DISALLOWSUBSCRIBE && !has_capability('mod/forum:managesubscriptions', $context)) {
4092 return $messages['cantsubscribe'];
4093 } else if ($cantaccessagroup) {
4094 return $messages['cantaccessgroup'];
4095 } else {
4096 if (is_null($subscribed_forums)) {
4097 $subscribed = forum_is_subscribed($USER->id, $forum);
4098 } else {
4099 $subscribed = !empty($subscribed_forums[$forum->id]);
4101 if ($subscribed) {
4102 $linktext = $messages['subscribed'];
4103 $linktitle = get_string('subscribestop', 'forum');
4104 } else {
4105 $linktext = $messages['unsubscribed'];
4106 $linktitle = get_string('subscribestart', 'forum');
4109 $options = array();
4110 if ($backtoindex) {
4111 $backtoindexlink = '&amp;backtoindex=1';
4112 $options['backtoindex'] = 1;
4113 } else {
4114 $backtoindexlink = '';
4116 $link = '';
4118 if ($fakelink) {
4119 $link .= '<script type="text/javascript">';
4120 $link .= '//<![CDATA['."\n";
4121 $link .= 'document.getElementById("subscriptionlink").innerHTML = "<a title=\"' . $linktitle . '\" href=\"' . $CFG->wwwroot .
4122 '/mod/forum/subscribe.php?id=' . $forum->id . $backtoindexlink.'\">' . $linktext . '<\/a>";';
4123 $link .= '//]]>';
4124 $link .= '</script>';
4125 // use <noscript> to print button in case javascript is not enabled
4126 $link .= '<noscript>';
4128 $options ['id'] = $forum->id;
4129 $link .= print_single_button($CFG->wwwroot . '/mod/forum/subscribe.php',
4130 $options, $linktext, 'post', '_self', true, $linktitle);
4131 if ($fakelink) {
4132 $link .= '</noscript>';
4135 return $link;
4141 * Generate and return the track or no track link for a forum.
4142 * @param object $forum the forum. Fields used are $forum->id and $forum->forcesubscribe.
4144 function forum_get_tracking_link($forum, $messages=array(), $fakelink=true) {
4145 global $CFG, $USER;
4147 static $strnotrackforum, $strtrackforum;
4149 if (isset($messages['trackforum'])) {
4150 $strtrackforum = $messages['trackforum'];
4152 if (isset($messages['notrackforum'])) {
4153 $strnotrackforum = $messages['notrackforum'];
4155 if (empty($strtrackforum)) {
4156 $strtrackforum = get_string('trackforum', 'forum');
4158 if (empty($strnotrackforum)) {
4159 $strnotrackforum = get_string('notrackforum', 'forum');
4162 if (forum_tp_is_tracked($forum)) {
4163 $linktitle = $strnotrackforum;
4164 $linktext = $strtrackforum;
4165 } else {
4166 $linktitle = $strtrackforum;
4167 $linktext = $strnotrackforum;
4170 $link = '';
4171 if ($fakelink) {
4172 $link .= '<script type="text/javascript">';
4173 $link .= '//<![CDATA['."\n";
4174 $link .= 'document.getElementById("trackinglink").innerHTML = "<a title=\"' . $linktitle . '\" href=\"' . $CFG->wwwroot .
4175 '/mod/forum/settracking.php?id=' . $forum->id . '\">' . $linktext . '<\/a>";'."\n";
4176 $link .= '//]]>'."\n";
4177 $link .= '</script>';
4178 // use <noscript> to print button in case javascript is not enabled
4179 $link .= '<noscript>';
4181 $link .= print_single_button($CFG->wwwroot . '/mod/forum/settracking.php?id=' . $forum->id,
4182 '', $linktext, 'post', '_self', true, $linktitle);
4183 if ($fakelink) {
4184 $link .= '</noscript>';
4187 return $link;
4193 * Returns true if user created new discussion already
4194 * @param int $forumid
4195 * @param int $userid
4196 * @return bool
4198 function forum_user_has_posted_discussion($forumid, $userid) {
4199 global $CFG;
4201 $sql = "SELECT 'x'
4202 FROM {$CFG->prefix}forum_discussions d, {$CFG->prefix}forum_posts p
4203 WHERE d.forum = $forumid AND p.discussion = d.id AND p.parent = 0 and p.userid = $userid";
4205 return record_exists_sql($sql);
4211 function forum_discussions_user_has_posted_in($forumid, $userid) {
4212 global $CFG;
4214 $haspostedsql = "SELECT d.id AS id,
4216 FROM {$CFG->prefix}forum_posts p,
4217 {$CFG->prefix}forum_discussions d
4218 WHERE p.discussion = d.id
4219 AND d.forum = $forumid
4220 AND p.userid = $userid";
4222 return get_records_sql($haspostedsql);
4228 function forum_user_has_posted($forumid, $did, $userid) {
4229 global $CFG;
4231 if (empty($did)) {
4232 // posted in any forum discussion?
4233 $sql = "SELECT 'x'
4234 FROM {$CFG->prefix}forum_posts p
4235 JOIN {$CFG->prefix}forum_discussions d ON d.id = p.discussion
4236 WHERE p.userid = $userid AND d.forum = $forumid";
4237 return record_exists_sql($sql);
4238 } else {
4239 // started discussion?
4240 return record_exists('forum_posts','discussion',$did,'userid',$userid);
4247 function forum_user_can_post_discussion($forum, $currentgroup=null, $unused=-1, $cm=NULL, $context=NULL) {
4248 // $forum is an object
4249 global $USER;
4251 // shortcut - guest and not-logged-in users can not post
4252 if (isguestuser() or !isloggedin()) {
4253 return false;
4256 if (!$cm) {
4257 debugging('missing cm', DEBUG_DEVELOPER);
4258 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
4259 error('Course Module ID was incorrect');
4263 if (!$context) {
4264 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4267 // normal users with temporary guest access can not add discussions
4268 if (has_capability('moodle/legacy:guest', $context, $USER->id, false)) {
4269 return false;
4272 if ($currentgroup === null) {
4273 $currentgroup = groups_get_activity_group($cm);
4276 $groupmode = groups_get_activity_groupmode($cm);
4278 if ($forum->type == 'news') {
4279 $capname = 'mod/forum:addnews';
4280 } else {
4281 $capname = 'mod/forum:startdiscussion';
4284 if (!has_capability($capname, $context)) {
4285 return false;
4288 if ($forum->type == 'eachuser') {
4289 if (forum_user_has_posted_discussion($forum->id, $USER->id)) {
4290 return false;
4294 if (!$groupmode or has_capability('moodle/site:accessallgroups', $context)) {
4295 return true;
4298 if ($currentgroup) {
4299 return groups_is_member($currentgroup);
4300 } else {
4301 // no group membership and no accessallgroups means no new discussions
4302 // reverted to 1.7 behaviour in 1.9+, buggy in 1.8.0-1.9.0
4303 return false;
4308 * This function checks whether the user can reply to posts in a forum
4309 * discussion. Use forum_user_can_post_discussion() to check whether the user
4310 * can start dicussions.
4311 * @param $forum - forum object
4312 * @param $user - user object
4314 function forum_user_can_post($forum, $discussion, $user=NULL, $cm=NULL, $course=NULL, $context=NULL) {
4315 global $USER;
4316 if (empty($user)) {
4317 $user = $USER;
4320 // shortcut - guest and not-logged-in users can not post
4321 if (isguestuser($user) or empty($user->id)) {
4322 return false;
4325 if (!isset($discussion->groupid)) {
4326 debugging('incorrect discussion parameter', DEBUG_DEVELOPER);
4327 return false;
4330 if (!$cm) {
4331 debugging('missing cm', DEBUG_DEVELOPER);
4332 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
4333 error('Course Module ID was incorrect');
4337 if (!$course) {
4338 debugging('missing course', DEBUG_DEVELOPER);
4339 if (!$course = get_record('course', 'id', $forum->course)) {
4340 error('Incorrect course id');
4344 if (!$context) {
4345 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4348 // normal users with temporary guest access can not post
4349 if (has_capability('moodle/legacy:guest', $context, $user->id, false)) {
4350 return false;
4353 if ($forum->type == 'news') {
4354 $capname = 'mod/forum:replynews';
4355 } else {
4356 $capname = 'mod/forum:replypost';
4359 if (!has_capability($capname, $context, $user->id, false)) {
4360 return false;
4363 if (!$groupmode = groups_get_activity_groupmode($cm, $course)) {
4364 return true;
4367 if (has_capability('moodle/site:accessallgroups', $context)) {
4368 return true;
4371 if ($groupmode == VISIBLEGROUPS) {
4372 if ($discussion->groupid == -1) {
4373 // allow students to reply to all participants discussions - this was not possible in Moodle <1.8
4374 return true;
4376 return groups_is_member($discussion->groupid);
4378 } else {
4379 //separate groups
4380 if ($discussion->groupid == -1) {
4381 return false;
4383 return groups_is_member($discussion->groupid);
4388 //checks to see if a user can view a particular post
4389 function forum_user_can_view_post($post, $course, $cm, $forum, $discussion, $user=NULL){
4391 global $CFG, $USER;
4393 if (!$user){
4394 $user = $USER;
4397 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
4398 if (!has_capability('mod/forum:viewdiscussion', $modcontext)) {
4399 return false;
4402 // If it's a grouped discussion, make sure the user is a member
4403 if ($discussion->groupid > 0) {
4404 $groupmode = groups_get_activity_groupmode($cm);
4405 if ($groupmode == SEPARATEGROUPS) {
4406 return groups_is_member($discussion->groupid) || has_capability('moodle/site:accessallgroups', $modcontext);
4409 return true;
4416 function forum_user_can_see_discussion($forum, $discussion, $context, $user=NULL) {
4417 global $USER;
4419 if (empty($user) || empty($user->id)) {
4420 $user = $USER;
4423 // retrieve objects (yuk)
4424 if (is_numeric($forum)) {
4425 debugging('missing full forum', DEBUG_DEVELOPER);
4426 if (!$forum = get_record('forum','id',$forum)) {
4427 return false;
4430 if (is_numeric($discussion)) {
4431 debugging('missing full discussion', DEBUG_DEVELOPER);
4432 if (!$discussion = get_record('forum_discussions','id',$discussion)) {
4433 return false;
4437 if (!has_capability('mod/forum:viewdiscussion', $context)) {
4438 return false;
4441 if ($forum->type == 'qanda' &&
4442 !forum_user_has_posted($forum->id, $discussion->id, $user->id) &&
4443 !has_capability('mod/forum:viewqandawithoutposting', $context)) {
4444 return false;
4446 return true;
4453 function forum_user_can_see_post($forum, $discussion, $post, $user=NULL, $cm=NULL) {
4454 global $USER;
4456 // retrieve objects (yuk)
4457 if (is_numeric($forum)) {
4458 debugging('missinf full forum', DEBUG_DEVELOPER);
4459 if (!$forum = get_record('forum','id',$forum)) {
4460 return false;
4464 if (is_numeric($discussion)) {
4465 debugging('missinf full discussion', DEBUG_DEVELOPER);
4466 if (!$discussion = get_record('forum_discussions','id',$discussion)) {
4467 return false;
4470 if (is_numeric($post)) {
4471 debugging('missinf full post', DEBUG_DEVELOPER);
4472 if (!$post = get_record('forum_posts','id',$post)) {
4473 return false;
4476 if (!isset($post->id) && isset($post->parent)) {
4477 $post->id = $post->parent;
4480 if (!$cm) {
4481 debugging('missing cm', DEBUG_DEVELOPER);
4482 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
4483 error('Course Module ID was incorrect');
4487 if (empty($user) || empty($user->id)) {
4488 $user = $USER;
4491 if (isset($cm->cache->caps['mod/forum:viewdiscussion'])) {
4492 if (!$cm->cache->caps['mod/forum:viewdiscussion']) {
4493 return false;
4495 } else {
4496 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
4497 if (!has_capability('mod/forum:viewdiscussion', $modcontext, $user->id)) {
4498 return false;
4502 if (isset($cm->uservisible)) {
4503 if (!$cm->uservisible) {
4504 return false;
4506 } else {
4507 if (!coursemodule_visible_for_user($cm, $user->id)) {
4508 return false;
4512 if ($forum->type == 'qanda') {
4513 $firstpost = forum_get_firstpost_from_discussion($discussion->id);
4514 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
4516 return (forum_user_has_posted($forum->id,$discussion->id,$user->id) ||
4517 $firstpost->id == $post->id ||
4518 has_capability('mod/forum:viewqandawithoutposting', $modcontext, $user->id, false));
4520 return true;
4525 * Prints the discussion view screen for a forum.
4527 * @param object $course The current course object.
4528 * @param object $forum Forum to be printed.
4529 * @param int $maxdiscussions .
4530 * @param string $displayformat The display format to use (optional).
4531 * @param string $sort Sort arguments for database query (optional).
4532 * @param int $groupmode Group mode of the forum (optional).
4533 * @param void $unused (originally current group)
4534 * @param int $page Page mode, page to display (optional).
4535 * @param int perpage The maximum number of discussions per page(optional)
4538 function forum_print_latest_discussions($course, $forum, $maxdiscussions=-1, $displayformat='plain', $sort='',
4539 $currentgroup=-1, $groupmode=-1, $page=-1, $perpage=100, $cm=NULL) {
4540 global $CFG, $USER;
4542 if (!$cm) {
4543 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
4544 error('Course Module ID was incorrect');
4547 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4549 if (empty($sort)) {
4550 $sort = "d.timemodified DESC";
4553 $olddiscussionlink = false;
4555 // Sort out some defaults
4556 if ($perpage <= 0) {
4557 $perpage = 0;
4558 $page = -1;
4561 if ($maxdiscussions == 0) {
4562 // all discussions - backwards compatibility
4563 $page = -1;
4564 $perpage = 0;
4565 if ($displayformat == 'plain') {
4566 $displayformat = 'header'; // Abbreviate display by default
4569 } else if ($maxdiscussions > 0) {
4570 $page = -1;
4571 $perpage = $maxdiscussions;
4574 $fullpost = false;
4575 if ($displayformat == 'plain') {
4576 $fullpost = true;
4580 // Decide if current user is allowed to see ALL the current discussions or not
4582 // First check the group stuff
4583 if ($currentgroup == -1 or $groupmode == -1) {
4584 $groupmode = groups_get_activity_groupmode($cm, $course);
4585 $currentgroup = groups_get_activity_group($cm);
4588 // If the user can post discussions, then this is a good place to put the
4589 // button for it. We do not show the button if we are showing site news
4590 // and the current user is a guest.
4592 if (forum_user_can_post_discussion($forum, $currentgroup, $groupmode, $cm, $context) ||
4593 ($forum->type != 'news'
4594 and (isguestuser() or !isloggedin() or has_capability('moodle/legacy:guest', $context, NULL, false))) ) {
4596 echo '<div class="singlebutton forumaddnew">';
4597 echo "<form id=\"newdiscussionform\" method=\"get\" action=\"$CFG->wwwroot/mod/forum/post.php\">";
4598 echo '<div>';
4599 echo "<input type=\"hidden\" name=\"forum\" value=\"$forum->id\" />";
4600 echo '<input type="submit" value="';
4601 echo ($forum->type == 'news') ? get_string('addanewtopic', 'forum')
4602 : (($forum->type == 'qanda')
4603 ? get_string('addanewquestion','forum')
4604 : get_string('addanewdiscussion', 'forum'));
4605 echo '" />';
4606 echo '</div>';
4607 echo '</form>';
4608 echo "</div>\n";
4610 } else if (isguestuser() or !isloggedin() or $forum->type == 'news') {
4611 // no button and no info
4613 } else if ($groupmode and has_capability('mod/forum:startdiscussion', $context)) {
4614 // inform users why they can not post new discussion
4615 if ($currentgroup) {
4616 notify(get_string('cannotadddiscussion', 'forum'));
4617 } else {
4618 notify(get_string('cannotadddiscussionall', 'forum'));
4622 // Get all the recent discussions we're allowed to see
4624 $getuserlastmodified = ($displayformat == 'header');
4626 if (! $discussions = forum_get_discussions($cm, $sort, $fullpost, null, $maxdiscussions, $getuserlastmodified, $page, $perpage) ) {
4627 echo '<div class="forumnodiscuss">';
4628 if ($forum->type == 'news') {
4629 echo '('.get_string('nonews', 'forum').')';
4630 } else if ($forum->type == 'qanda') {
4631 echo '('.get_string('noquestions','forum').')';
4632 } else {
4633 echo '('.get_string('nodiscussions', 'forum').')';
4635 echo "</div>\n";
4636 return;
4639 // If we want paging
4640 if ($page != -1) {
4641 ///Get the number of discussions found
4642 $numdiscussions = forum_get_discussions_count($cm);
4644 ///Show the paging bar
4645 print_paging_bar($numdiscussions, $page, $perpage, "view.php?f=$forum->id&amp;");
4646 if ($numdiscussions > 1000) {
4647 // saves some memory on sites with very large forums
4648 $replies = forum_count_discussion_replies($forum->id, $sort, $maxdiscussions, $page, $perpage);
4649 } else {
4650 $replies = forum_count_discussion_replies($forum->id);
4653 } else {
4654 $replies = forum_count_discussion_replies($forum->id);
4656 if ($maxdiscussions > 0 and $maxdiscussions <= count($discussions)) {
4657 $olddiscussionlink = true;
4661 $canviewparticipants = has_capability('moodle/course:viewparticipants',$context);
4663 $strdatestring = get_string('strftimerecentfull');
4665 // Check if the forum is tracked.
4666 if ($cantrack = forum_tp_can_track_forums($forum)) {
4667 $forumtracked = forum_tp_is_tracked($forum);
4668 } else {
4669 $forumtracked = false;
4672 if ($forumtracked) {
4673 $unreads = forum_get_discussions_unread($cm);
4674 } else {
4675 $unreads = array();
4678 if ($displayformat == 'header') {
4679 echo '<table cellspacing="0" class="forumheaderlist">';
4680 echo '<thead>';
4681 echo '<tr>';
4682 echo '<th class="header topic" scope="col">'.get_string('discussion', 'forum').'</th>';
4683 echo '<th class="header author" colspan="2" scope="col">'.get_string('startedby', 'forum').'</th>';
4684 if ($groupmode > 0) {
4685 echo '<th class="header group" scope="col">'.get_string('group').'</th>';
4687 if (has_capability('mod/forum:viewdiscussion', $context)) {
4688 echo '<th class="header replies" scope="col">'.get_string('replies', 'forum').'</th>';
4689 // If the forum can be tracked, display the unread column.
4690 if ($cantrack) {
4691 echo '<th class="header replies" scope="col">'.get_string('unread', 'forum');
4692 if ($forumtracked) {
4693 echo '&nbsp;<a title="'.get_string('markallread', 'forum').
4694 '" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
4695 $forum->id.'&amp;mark=read&amp;returnpage=view.php">'.
4696 '<img src="'.$CFG->pixpath.'/t/clear.gif" class="iconsmall" alt="'.get_string('markallread', 'forum').'" /></a>';
4698 echo '</th>';
4701 echo '<th class="header lastpost" scope="col">'.get_string('lastpost', 'forum').'</th>';
4702 echo '</tr>';
4703 echo '</thead>';
4704 echo '<tbody>';
4707 foreach ($discussions as $discussion) {
4708 if (!empty($replies[$discussion->discussion])) {
4709 $discussion->replies = $replies[$discussion->discussion]->replies;
4710 $discussion->lastpostid = $replies[$discussion->discussion]->lastpostid;
4711 } else {
4712 $discussion->replies = 0;
4715 // SPECIAL CASE: The front page can display a news item post to non-logged in users.
4716 // All posts are read in this case.
4717 if (!$forumtracked) {
4718 $discussion->unread = '-';
4719 } else if (empty($USER)) {
4720 $discussion->unread = 0;
4721 } else {
4722 if (empty($unreads[$discussion->discussion])) {
4723 $discussion->unread = 0;
4724 } else {
4725 $discussion->unread = $unreads[$discussion->discussion];
4729 if (!empty($USER->id)) {
4730 $ownpost = ($discussion->userid == $USER->id);
4731 } else {
4732 $ownpost=false;
4734 // Use discussion name instead of subject of first post
4735 $discussion->subject = $discussion->name;
4737 switch ($displayformat) {
4738 case 'header':
4739 if ($groupmode > 0) {
4740 if (isset($groups[$discussion->groupid])) {
4741 $group = $groups[$discussion->groupid];
4742 } else {
4743 $group = $groups[$discussion->groupid] = groups_get_group($discussion->groupid);
4745 } else {
4746 $group = -1;
4748 forum_print_discussion_header($discussion, $forum, $group, $strdatestring, $cantrack, $forumtracked,
4749 $canviewparticipants, $context);
4750 break;
4751 default:
4752 $link = false;
4754 if ($discussion->replies) {
4755 $link = true;
4756 } else {
4757 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
4758 $link = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
4761 $discussion->forum = $forum->id;
4763 forum_print_post($discussion, $discussion, $forum, $cm, $course, $ownpost, 0, $link, false);
4764 break;
4768 if ($displayformat == "header") {
4769 echo '</tbody>';
4770 echo '</table>';
4773 if ($olddiscussionlink) {
4774 if ($forum->type == 'news') {
4775 $strolder = get_string('oldertopics', 'forum');
4776 } else {
4777 $strolder = get_string('olderdiscussions', 'forum');
4779 echo '<div class="forumolddiscuss">';
4780 echo '<a href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'&amp;showall=1">';
4781 echo $strolder.'</a> ...</div>';
4784 if ($page != -1) { ///Show the paging bar
4785 print_paging_bar($numdiscussions, $page, $perpage, "view.php?f=$forum->id&amp;");
4793 function forum_print_discussion($course, $cm, $forum, $discussion, $post, $mode, $canreply=NULL, $canrate=false) {
4795 global $USER, $CFG;
4797 if (!empty($USER->id)) {
4798 $ownpost = ($USER->id == $post->userid);
4799 } else {
4800 $ownpost = false;
4802 if ($canreply === NULL) {
4803 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
4804 $reply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
4805 } else {
4806 $reply = $canreply;
4809 // $cm holds general cache for forum functions
4810 $cm->cache = new object();
4811 $cm->cache->groups = groups_get_all_groups($course->id, 0, $cm->groupingid);
4812 $cm->cache->usersgroups = array();
4814 $posters = array();
4816 // preload all posts - TODO: improve...
4817 if ($mode == FORUM_MODE_FLATNEWEST) {
4818 $sort = "p.created DESC";
4819 } else {
4820 $sort = "p.created ASC";
4823 $forumtracked = forum_tp_is_tracked($forum);
4824 $posts = forum_get_all_discussion_posts($discussion->id, $sort, $forumtracked);
4825 $post = $posts[$post->id];
4827 foreach ($posts as $pid=>$p) {
4828 $posters[$p->userid] = $p->userid;
4831 // preload all groups of ppl that posted in this discussion
4832 if ($postersgroups = groups_get_all_groups($course->id, $posters, $cm->groupingid, 'gm.id, gm.groupid, gm.userid')) {
4833 foreach($postersgroups as $pg) {
4834 if (!isset($cm->cache->usersgroups[$pg->userid])) {
4835 $cm->cache->usersgroups[$pg->userid] = array();
4837 $cm->cache->usersgroups[$pg->userid][$pg->groupid] = $pg->groupid;
4839 unset($postersgroups);
4842 $ratings = NULL;
4843 $ratingsmenuused = false;
4844 $ratingsformused = false;
4845 if ($forum->assessed and isloggedin()) {
4846 if ($ratings->scale = make_grades_menu($forum->scale)) {
4847 $ratings->assesstimestart = $forum->assesstimestart;
4848 $ratings->assesstimefinish = $forum->assesstimefinish;
4849 $ratings->allow = $canrate;
4851 if ($ratings->allow) {
4852 echo '<form id="form" method="post" action="rate.php">';
4853 echo '<div class="ratingform">';
4854 echo '<input type="hidden" name="forumid" value="'.$forum->id.'" />';
4855 $ratingsformused = true;
4859 pre_load_all_ratings($cm, $discussion);
4866 $post->forum = $forum->id; // Add the forum id to the post object, later used by forum_print_post
4867 $post->forumtype = $forum->type;
4869 $post->subject = format_string($post->subject);
4871 $postread = !empty($post->postread);
4873 if (forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, false, $ratings,
4874 '', '', $postread, true, $forumtracked)) {
4875 $ratingsmenuused = true;
4878 switch ($mode) {
4879 case FORUM_MODE_FLATOLDEST :
4880 case FORUM_MODE_FLATNEWEST :
4881 default:
4882 if (forum_print_posts_flat($course, $cm, $forum, $discussion, $post, $mode, $ratings, $reply, $forumtracked, $posts)) {
4883 $ratingsmenuused = true;
4885 break;
4887 case FORUM_MODE_THREADED :
4888 if (forum_print_posts_threaded($course, $cm, $forum, $discussion, $post, 0, $ratings, $reply, $forumtracked, $posts)) {
4889 $ratingsmenuused = true;
4891 break;
4893 case FORUM_MODE_NESTED :
4894 if (forum_print_posts_nested($course, $cm, $forum, $discussion, $post, $ratings, $reply, $forumtracked, $posts)) {
4895 $ratingsmenuused = true;
4897 break;
4900 if ($ratingsformused) {
4901 if ($ratingsmenuused) {
4902 echo '<div class="ratingsubmit">';
4903 echo '<input type="submit" value="'.get_string('sendinratings', 'forum').'" />';
4904 if ($forum->scale < 0) {
4905 if ($scale = get_record("scale", "id", abs($forum->scale))) {
4906 print_scale_menu_helpbutton($course->id, $scale );
4909 echo '</div>';
4912 echo '</div>';
4913 echo '</form>';
4921 function forum_print_posts_flat($course, &$cm, $forum, $discussion, $post, $mode, $ratings, $reply, $forumtracked, $posts) {
4922 global $USER, $CFG;
4924 $link = false;
4925 $ratingsmenuused = false;
4927 if ($mode == FORUM_MODE_FLATNEWEST) {
4928 $sort = "ORDER BY created DESC";
4929 } else {
4930 $sort = "ORDER BY created ASC";
4933 foreach ($posts as $post) {
4934 if (!$post->parent) {
4935 continue;
4937 $post->subject = format_string($post->subject);
4938 $ownpost = ($USER->id == $post->userid);
4940 $postread = !empty($post->postread);
4942 if (forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link, $ratings,
4943 '', '', $postread, true, $forumtracked)) {
4944 $ratingsmenuused = true;
4948 return $ratingsmenuused;
4953 * TODO document
4955 function forum_print_posts_threaded($course, &$cm, $forum, $discussion, $parent, $depth, $ratings, $reply, $forumtracked, $posts) {
4956 global $USER, $CFG;
4958 $link = false;
4959 $ratingsmenuused = false;
4961 if (!empty($posts[$parent->id]->children)) {
4962 $posts = $posts[$parent->id]->children;
4964 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
4965 $canviewfullnames = has_capability('moodle/site:viewfullnames', $modcontext);
4967 foreach ($posts as $post) {
4969 echo '<div class="indent">';
4970 if ($depth > 0) {
4971 $ownpost = ($USER->id == $post->userid);
4972 $post->subject = format_string($post->subject);
4974 $postread = !empty($post->postread);
4976 if (forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link, $ratings,
4977 '', '', $postread, true, $forumtracked)) {
4978 $ratingsmenuused = true;
4980 } else {
4981 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
4982 continue;
4984 $by = new object();
4985 $by->name = fullname($post, $canviewfullnames);
4986 $by->date = userdate($post->modified);
4988 if ($forumtracked) {
4989 if (!empty($post->postread)) {
4990 $style = '<span class="forumthread read">';
4991 } else {
4992 $style = '<span class="forumthread unread">';
4994 } else {
4995 $style = '<span class="forumthread">';
4997 echo $style."<a name=\"$post->id\"></a>".
4998 "<a href=\"discuss.php?d=$post->discussion&amp;parent=$post->id\">".format_string($post->subject,true)."</a> ";
4999 print_string("bynameondate", "forum", $by);
5000 echo "</span>";
5003 if (forum_print_posts_threaded($course, $cm, $forum, $discussion, $post, $depth-1, $ratings, $reply, $forumtracked, $posts)) {
5004 $ratingsmenuused = true;
5006 echo "</div>\n";
5009 return $ratingsmenuused;
5015 function forum_print_posts_nested($course, &$cm, $forum, $discussion, $parent, $ratings, $reply, $forumtracked, $posts) {
5016 global $USER, $CFG;
5018 $link = false;
5019 $ratingsmenuused = false;
5021 if (!empty($posts[$parent->id]->children)) {
5022 $posts = $posts[$parent->id]->children;
5024 foreach ($posts as $post) {
5026 echo '<div class="indent">';
5027 if (empty($USER->id)) {
5028 $ownpost = false;
5029 } else {
5030 $ownpost = ($USER->id == $post->userid);
5033 $post->subject = format_string($post->subject);
5034 $postread = !empty($post->postread);
5036 if (forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link, $ratings,
5037 '', '', $postread, true, $forumtracked)) {
5038 $ratingsmenuused = true;
5040 if (forum_print_posts_nested($course, $cm, $forum, $discussion, $post, $ratings, $reply, $forumtracked, $posts)) {
5041 $ratingsmenuused = true;
5043 echo "</div>\n";
5046 return $ratingsmenuused;
5050 * Returns all forum posts since a given time in specified forum.
5052 function forum_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
5053 global $CFG, $COURSE, $USER;
5055 if ($COURSE->id == $courseid) {
5056 $course = $COURSE;
5057 } else {
5058 $course = get_record('course', 'id', $courseid);
5061 $modinfo =& get_fast_modinfo($course);
5063 $cm = $modinfo->cms[$cmid];
5065 if ($userid) {
5066 $userselect = "AND u.id = $userid";
5067 } else {
5068 $userselect = "";
5071 if ($groupid) {
5072 $groupselect = "AND gm.groupid = $groupid";
5073 $groupjoin = "JOIN {$CFG->prefix}groups_members gm ON gm.userid=u.id";
5074 } else {
5075 $groupselect = "";
5076 $groupjoin = "";
5079 if (!$posts = get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
5080 d.timestart, d.timeend, d.userid AS duserid,
5081 u.firstname, u.lastname, u.email, u.picture, u.imagealt
5082 FROM {$CFG->prefix}forum_posts p
5083 JOIN {$CFG->prefix}forum_discussions d ON d.id = p.discussion
5084 JOIN {$CFG->prefix}forum f ON f.id = d.forum
5085 JOIN {$CFG->prefix}user u ON u.id = p.userid
5086 $groupjoin
5087 WHERE p.created > $timestart AND f.id = $cm->instance
5088 $userselect $groupselect
5089 ORDER BY p.id ASC")) { // order by initial posting date
5090 return;
5093 $groupmode = groups_get_activity_groupmode($cm, $course);
5094 $cm_context = get_context_instance(CONTEXT_MODULE, $cm->id);
5095 $viewhiddentimed = has_capability('mod/forum:viewhiddentimedposts', $cm_context);
5096 $accessallgroups = has_capability('moodle/site:accessallgroups', $cm_context);
5098 if (is_null($modinfo->groups)) {
5099 $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
5102 $printposts = array();
5103 foreach ($posts as $post) {
5105 if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
5106 and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
5107 if (!$viewhiddentimed) {
5108 continue;
5112 if ($groupmode) {
5113 if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or $accessallgroups) {
5114 // oki (Open discussions have groupid -1)
5115 } else {
5116 // separate mode
5117 if (isguestuser()) {
5118 // shortcut
5119 continue;
5122 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
5123 continue;
5128 $printposts[] = $post;
5131 if (!$printposts) {
5132 return;
5135 $aname = format_string($cm->name,true);
5137 foreach ($printposts as $post) {
5138 $tmpactivity = new object();
5140 $tmpactivity->type = 'forum';
5141 $tmpactivity->cmid = $cm->id;
5142 $tmpactivity->name = $aname;
5143 $tmpactivity->sectionnum = $cm->sectionnum;
5144 $tmpactivity->timestamp = $post->modified;
5146 $tmpactivity->content = new object();
5147 $tmpactivity->content->id = $post->id;
5148 $tmpactivity->content->discussion = $post->discussion;
5149 $tmpactivity->content->subject = format_string($post->subject);
5150 $tmpactivity->content->parent = $post->parent;
5152 $tmpactivity->user = new object();
5153 $tmpactivity->user->id = $post->userid;
5154 $tmpactivity->user->firstname = $post->firstname;
5155 $tmpactivity->user->lastname = $post->lastname;
5156 $tmpactivity->user->picture = $post->picture;
5157 $tmpactivity->user->imagealt = $post->imagealt;
5158 $tmpactivity->user->email = $post->email;
5160 $activities[$index++] = $tmpactivity;
5163 return;
5169 function forum_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
5170 global $CFG;
5172 if ($activity->content->parent) {
5173 $class = 'reply';
5174 } else {
5175 $class = 'discussion';
5178 echo '<table border="0" cellpadding="3" cellspacing="0" class="forum-recent">';
5180 echo "<tr><td class=\"userpicture\" valign=\"top\">";
5181 print_user_picture($activity->user, $courseid);
5182 echo "</td><td class=\"$class\">";
5184 echo '<div class="title">';
5185 if ($detail) {
5186 $aname = s($activity->name);
5187 echo "<img src=\"$CFG->modpixpath/$activity->type/icon.gif\" ".
5188 "class=\"icon\" alt=\"{$aname}\" />";
5190 echo "<a href=\"$CFG->wwwroot/mod/forum/discuss.php?d={$activity->content->discussion}"
5191 ."#p{$activity->content->id}\">{$activity->content->subject}</a>";
5192 echo '</div>';
5194 echo '<div class="user">';
5195 $fullname = fullname($activity->user, $viewfullnames);
5196 echo "<a href=\"$CFG->wwwroot/user/view.php?id={$activity->user->id}&amp;course=$courseid\">"
5197 ."{$fullname}</a> - ".userdate($activity->timestamp);
5198 echo '</div>';
5199 echo "</td></tr></table>";
5201 return;
5205 * recursively sets the discussion field to $discussionid on $postid and all its children
5206 * used when pruning a post
5208 function forum_change_discussionid($postid, $discussionid) {
5209 set_field('forum_posts', 'discussion', $discussionid, 'id', $postid);
5210 if ($posts = get_records('forum_posts', 'parent', $postid)) {
5211 foreach ($posts as $post) {
5212 forum_change_discussionid($post->id, $discussionid);
5215 return true;
5219 * Prints the editing button on subscribers page
5221 function forum_update_subscriptions_button($courseid, $forumid) {
5222 global $CFG, $USER;
5224 if (!empty($USER->subscriptionsediting)) {
5225 $string = get_string('turneditingoff');
5226 $edit = "off";
5227 } else {
5228 $string = get_string('turneditingon');
5229 $edit = "on";
5232 return "<form $CFG->frametarget method=\"get\" action=\"$CFG->wwwroot/mod/forum/subscribers.php\">".
5233 "<input type=\"hidden\" name=\"id\" value=\"$forumid\" />".
5234 "<input type=\"hidden\" name=\"edit\" value=\"$edit\" />".
5235 "<input type=\"submit\" value=\"$string\" /></form>";
5239 * This function gets run whenever a role is assigned to a user in a context
5241 * @param integer $userid
5242 * @param object $context
5243 * @return bool
5245 function forum_role_assign($userid, $context, $roleid) {
5246 // check to see if this role comes with mod/forum:initialsubscriptions
5247 $cap = role_context_capabilities($roleid, $context, 'mod/forum:initialsubscriptions');
5248 $cap1 = role_context_capabilities($roleid, $context, 'moodle/course:view');
5249 // we are checking the role because has_capability() will pull this capability out
5250 // from other roles this user might have and resolve them, which is no good
5251 // the role needs course view to
5252 if (isset($cap['mod/forum:initialsubscriptions']) && $cap['mod/forum:initialsubscriptions'] == CAP_ALLOW &&
5253 isset($cap1['moodle/course:view']) && $cap1['moodle/course:view'] == CAP_ALLOW) {
5254 return forum_add_user_default_subscriptions($userid, $context);
5255 } else {
5256 // MDL-8981, do not subscribe to forum
5257 return true;
5263 * This function gets run whenever a role is assigned to a user in a context
5265 * @param integer $userid
5266 * @param object $context
5267 * @return bool
5269 function forum_role_unassign($userid, $context) {
5270 if (empty($context->contextlevel)) {
5271 return false;
5274 forum_remove_user_subscriptions($userid, $context);
5275 forum_remove_user_tracking($userid, $context);
5277 return true;
5282 * Add subscriptions for new users
5284 function forum_add_user_default_subscriptions($userid, $context) {
5286 if (empty($context->contextlevel)) {
5287 return false;
5290 switch ($context->contextlevel) {
5292 case CONTEXT_SYSTEM: // For the whole site
5293 if ($courses = get_records('course')) {
5294 foreach ($courses as $course) {
5295 $subcontext = get_context_instance(CONTEXT_COURSE, $course->id);
5296 forum_add_user_default_subscriptions($userid, $subcontext);
5299 break;
5301 case CONTEXT_COURSECAT: // For a whole category
5302 if ($courses = get_records('course', 'category', $context->instanceid)) {
5303 foreach ($courses as $course) {
5304 $subcontext = get_context_instance(CONTEXT_COURSE, $course->id);
5305 forum_add_user_default_subscriptions($userid, $subcontext);
5308 if ($categories = get_records('course_categories', 'parent', $context->instanceid)) {
5309 foreach ($categories as $category) {
5310 $subcontext = get_context_instance(CONTEXT_COURSECAT, $category->id);
5311 forum_add_user_default_subscriptions($userid, $subcontext);
5314 break;
5317 case CONTEXT_COURSE: // For a whole course
5318 if ($course = get_record('course', 'id', $context->instanceid)) {
5319 if ($forums = get_all_instances_in_course('forum', $course, $userid, false)) {
5320 foreach ($forums as $forum) {
5321 if ($forum->forcesubscribe != FORUM_INITIALSUBSCRIBE) {
5322 continue;
5324 if ($modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule)) {
5325 if (has_capability('mod/forum:viewdiscussion', $modcontext, $userid)) {
5326 forum_subscribe($userid, $forum->id);
5332 break;
5334 case CONTEXT_MODULE: // Just one forum
5335 if ($cm = get_coursemodule_from_id('forum', $context->instanceid)) {
5336 if ($forum = get_record('forum', 'id', $cm->instance)) {
5337 if ($forum->forcesubscribe != FORUM_INITIALSUBSCRIBE) {
5338 continue;
5340 if (has_capability('mod/forum:viewdiscussion', $context, $userid)) {
5341 forum_subscribe($userid, $forum->id);
5345 break;
5348 return true;
5353 * Remove subscriptions for a user in a context
5355 function forum_remove_user_subscriptions($userid, $context) {
5357 global $CFG;
5359 if (empty($context->contextlevel)) {
5360 return false;
5363 switch ($context->contextlevel) {
5365 case CONTEXT_SYSTEM: // For the whole site
5366 //if ($courses = get_my_courses($userid)) {
5367 // find all courses in which this user has a forum subscription
5368 if ($courses = get_records_sql("SELECT c.id
5369 FROM {$CFG->prefix}course c,
5370 {$CFG->prefix}forum_subscriptions fs,
5371 {$CFG->prefix}forum f
5372 WHERE c.id = f.course AND f.id = fs.forum AND fs.userid = $userid
5373 GROUP BY c.id")) {
5375 foreach ($courses as $course) {
5376 $subcontext = get_context_instance(CONTEXT_COURSE, $course->id);
5377 forum_remove_user_subscriptions($userid, $subcontext);
5380 break;
5382 case CONTEXT_COURSECAT: // For a whole category
5383 if ($courses = get_records('course', 'category', $context->instanceid, '', 'id')) {
5384 foreach ($courses as $course) {
5385 $subcontext = get_context_instance(CONTEXT_COURSE, $course->id);
5386 forum_remove_user_subscriptions($userid, $subcontext);
5389 if ($categories = get_records('course_categories', 'parent', $context->instanceid, '', 'id')) {
5390 foreach ($categories as $category) {
5391 $subcontext = get_context_instance(CONTEXT_COURSECAT, $category->id);
5392 forum_remove_user_subscriptions($userid, $subcontext);
5395 break;
5397 case CONTEXT_COURSE: // For a whole course
5398 if ($course = get_record('course', 'id', $context->instanceid, '', '', '', '', 'id')) {
5399 // find all forums in which this user has a subscription, and its coursemodule id
5400 if ($forums = get_records_sql("SELECT f.id, cm.id as coursemodule
5401 FROM {$CFG->prefix}forum f,
5402 {$CFG->prefix}modules m,
5403 {$CFG->prefix}course_modules cm,
5404 {$CFG->prefix}forum_subscriptions fs
5405 WHERE fs.userid = $userid AND f.course = $context->instanceid
5406 AND fs.forum = f.id AND cm.instance = f.id
5407 AND cm.module = m.id AND m.name = 'forum'")) {
5409 foreach ($forums as $forum) {
5410 if ($modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule)) {
5411 if (!has_capability('mod/forum:viewdiscussion', $modcontext, $userid)) {
5412 forum_unsubscribe($userid, $forum->id);
5418 break;
5420 case CONTEXT_MODULE: // Just one forum
5421 if ($cm = get_coursemodule_from_id('forum', $context->instanceid)) {
5422 if ($forum = get_record('forum', 'id', $cm->instance)) {
5423 if (!has_capability('mod/forum:viewdiscussion', $context, $userid)) {
5424 forum_unsubscribe($userid, $forum->id);
5428 break;
5431 return true;
5434 // Functions to do with read tracking.
5437 * Remove post tracking for a user in a context
5439 function forum_remove_user_tracking($userid, $context) {
5441 global $CFG;
5443 if (empty($context->contextlevel)) {
5444 return false;
5447 switch ($context->contextlevel) {
5449 case CONTEXT_SYSTEM: // For the whole site
5450 // find all courses in which this user has tracking info
5451 $allcourses = array();
5452 if ($courses = get_records_sql("SELECT c.id
5453 FROM {$CFG->prefix}course c,
5454 {$CFG->prefix}forum_read fr,
5455 {$CFG->prefix}forum f
5456 WHERE c.id = f.course AND f.id = fr.forumid AND fr.userid = $userid
5457 GROUP BY c.id")) {
5459 $allcourses = $allcourses + $courses;
5461 if ($courses = get_records_sql("SELECT c.id
5462 FROM {$CFG->prefix}course c,
5463 {$CFG->prefix}forum_track_prefs ft,
5464 {$CFG->prefix}forum f
5465 WHERE c.id = f.course AND f.id = ft.forumid AND ft.userid = $userid")) {
5467 $allcourses = $allcourses + $courses;
5469 foreach ($allcourses as $course) {
5470 $subcontext = get_context_instance(CONTEXT_COURSE, $course->id);
5471 forum_remove_user_tracking($userid, $subcontext);
5473 break;
5475 case CONTEXT_COURSECAT: // For a whole category
5476 if ($courses = get_records('course', 'category', $context->instanceid, '', 'id')) {
5477 foreach ($courses as $course) {
5478 $subcontext = get_context_instance(CONTEXT_COURSE, $course->id);
5479 forum_remove_user_tracking($userid, $subcontext);
5482 if ($categories = get_records('course_categories', 'parent', $context->instanceid, '', 'id')) {
5483 foreach ($categories as $category) {
5484 $subcontext = get_context_instance(CONTEXT_COURSECAT, $category->id);
5485 forum_remove_user_tracking($userid, $subcontext);
5488 break;
5490 case CONTEXT_COURSE: // For a whole course
5491 if ($course = get_record('course', 'id', $context->instanceid, '', '', '', '', 'id')) {
5492 // find all forums in which this user has reading tracked
5493 if ($forums = get_records_sql("SELECT f.id, cm.id as coursemodule
5494 FROM {$CFG->prefix}forum f,
5495 {$CFG->prefix}modules m,
5496 {$CFG->prefix}course_modules cm,
5497 {$CFG->prefix}forum_read fr
5498 WHERE fr.userid = $userid AND f.course = $context->instanceid
5499 AND fr.forumid = f.id AND cm.instance = f.id
5500 AND cm.module = m.id AND m.name = 'forum'")) {
5502 foreach ($forums as $forum) {
5503 if ($modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule)) {
5504 if (!has_capability('mod/forum:viewdiscussion', $modcontext, $userid)) {
5505 forum_tp_delete_read_records($userid, -1, -1, $forum->id);
5511 // find all forums in which this user has a disabled tracking
5512 if ($forums = get_records_sql("SELECT f.id, cm.id as coursemodule
5513 FROM {$CFG->prefix}forum f,
5514 {$CFG->prefix}modules m,
5515 {$CFG->prefix}course_modules cm,
5516 {$CFG->prefix}forum_track_prefs ft
5517 WHERE ft.userid = $userid AND f.course = $context->instanceid
5518 AND ft.forumid = f.id AND cm.instance = f.id
5519 AND cm.module = m.id AND m.name = 'forum'")) {
5521 foreach ($forums as $forum) {
5522 if ($modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule)) {
5523 if (!has_capability('mod/forum:viewdiscussion', $modcontext, $userid)) {
5524 delete_records('forum_track_prefs', 'userid', $userid, 'forumid', $forum->id);
5530 break;
5532 case CONTEXT_MODULE: // Just one forum
5533 if ($cm = get_coursemodule_from_id('forum', $context->instanceid)) {
5534 if ($forum = get_record('forum', 'id', $cm->instance)) {
5535 if (!has_capability('mod/forum:viewdiscussion', $context, $userid)) {
5536 delete_records('forum_track_prefs', 'userid', $userid, 'forumid', $forum->id);
5537 forum_tp_delete_read_records($userid, -1, -1, $forum->id);
5541 break;
5544 return true;
5548 * Mark posts as read.
5549 * @param object $user object
5550 * @param array $postids array of post ids
5551 * @return boolean success
5553 function forum_tp_mark_posts_read($user, $postids) {
5554 global $CFG;
5556 if (!forum_tp_can_track_forums(false, $user)) {
5557 return true;
5560 $status = true;
5562 $now = time();
5563 $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
5565 if (empty($postids)) {
5566 return true;
5568 } else if (count($postids) > 200) {
5569 while ($part = array_splice($postids, 0, 200)) {
5570 $status = forum_tp_mark_posts_read($user, $part) && $status;
5572 return $status;
5575 $sql = "SELECT id
5576 FROM {$CFG->prefix}forum_read
5577 WHERE userid = $user->id AND postid IN (".implode(',', $postids).")";
5578 if ($existing = get_records_sql($sql)) {
5579 $existing = array_keys($existing);
5580 } else {
5581 $existing = array();
5584 $new = array_diff($postids, $existing);
5586 if ($new) {
5587 $sql = "INSERT INTO {$CFG->prefix}forum_read (userid, postid, discussionid, forumid, firstread, lastread)
5589 SELECT $user->id, p.id, p.discussion, d.forum, $now, $now
5590 FROM {$CFG->prefix}forum_posts p
5591 JOIN {$CFG->prefix}forum_discussions d ON d.id = p.discussion
5592 JOIN {$CFG->prefix}forum f ON f.id = d.forum
5593 LEFT JOIN {$CFG->prefix}forum_track_prefs tf ON (tf.userid = $user->id AND tf.forumid = f.id)
5594 WHERE p.id IN (".implode(',', $new).")
5595 AND p.modified >= $cutoffdate
5596 AND (f.trackingtype = ".FORUM_TRACKING_ON."
5597 OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND tf.id IS NULL))";
5598 $status = execute_sql($sql, false) && $status;
5601 if ($existing) {
5602 $sql = "UPDATE {$CFG->prefix}forum_read
5603 SET lastread = $now
5604 WHERE userid = $user->id AND postid IN (".implode(',', $existing).")";
5605 $status = execute_sql($sql, false) && $status;
5608 return $status;
5612 * Mark post as read.
5614 function forum_tp_add_read_record($userid, $postid) {
5615 global $CFG;
5617 $now = time();
5618 $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
5620 if (!record_exists('forum_read', 'userid', $userid, 'postid', $postid)) {
5621 $sql = "INSERT INTO {$CFG->prefix}forum_read (userid, postid, discussionid, forumid, firstread, lastread)
5623 SELECT $userid, p.id, p.discussion, d.forum, $now, $now
5624 FROM {$CFG->prefix}forum_posts p
5625 JOIN {$CFG->prefix}forum_discussions d ON d.id = p.discussion
5626 WHERE p.id = $postid AND p.modified >= $cutoffdate";
5627 return execute_sql($sql, false);
5629 } else {
5630 $sql = "UPDATE {$CFG->prefix}forum_read
5631 SET lastread = $now
5632 WHERE userid = $userid AND postid = $userid";
5633 return execute_sql($sql, false);
5638 * Returns all records in the 'forum_read' table matching the passed keys, indexed
5639 * by userid.
5641 function forum_tp_get_read_records($userid=-1, $postid=-1, $discussionid=-1, $forumid=-1) {
5642 $select = '';
5643 if ($userid > -1) {
5644 if ($select != '') $select .= ' AND ';
5645 $select .= 'userid = \''.$userid.'\'';
5647 if ($postid > -1) {
5648 if ($select != '') $select .= ' AND ';
5649 $select .= 'postid = \''.$postid.'\'';
5651 if ($discussionid > -1) {
5652 if ($select != '') $select .= ' AND ';
5653 $select .= 'discussionid = \''.$discussionid.'\'';
5655 if ($forumid > -1) {
5656 if ($select != '') $select .= ' AND ';
5657 $select .= 'forumid = \''.$forumid.'\'';
5660 return get_records_select('forum_read', $select);
5664 * Returns all read records for the provided user and discussion, indexed by postid.
5666 function forum_tp_get_discussion_read_records($userid, $discussionid) {
5667 $select = 'userid = \''.$userid.'\' AND discussionid = \''.$discussionid.'\'';
5668 $fields = 'postid, firstread, lastread';
5669 return get_records_select('forum_read', $select, '', $fields);
5673 * If its an old post, do nothing. If the record exists, the maintenance will clear it up later.
5675 function forum_tp_mark_post_read($userid, $post, $forumid) {
5676 if (!forum_tp_is_post_old($post)) {
5677 return forum_tp_add_read_record($userid, $post->id);
5678 } else {
5679 return true;
5684 * Marks a whole forum as read, for a given user
5686 function forum_tp_mark_forum_read($user, $forumid, $groupid=false) {
5687 global $CFG;
5689 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
5691 $groupsel = "";
5692 if ($groupid !== false) {
5693 $groupsel = " AND (d.groupid = $groupid OR d.groupid = -1)";
5696 $sql = "SELECT p.id
5697 FROM {$CFG->prefix}forum_posts p
5698 LEFT JOIN {$CFG->prefix}forum_discussions d ON d.id = p.discussion
5699 LEFT JOIN {$CFG->prefix}forum_read r ON (r.postid = p.id AND r.userid = $user->id)
5700 WHERE d.forum = $forumid
5701 AND p.modified >= $cutoffdate AND r.id is NULL
5702 $groupsel";
5704 if ($posts = get_records_sql($sql)) {
5705 $postids = array_keys($posts);
5706 return forum_tp_mark_posts_read($user, $postids);
5709 return true;
5713 * Marks a whole discussion as read, for a given user
5715 function forum_tp_mark_discussion_read($user, $discussionid) {
5716 global $CFG;
5718 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
5720 $sql = "SELECT p.id
5721 FROM {$CFG->prefix}forum_posts p
5722 LEFT JOIN {$CFG->prefix}forum_read r ON (r.postid = p.id AND r.userid = $user->id)
5723 WHERE p.discussion = $discussionid
5724 AND p.modified >= $cutoffdate AND r.id is NULL";
5726 if ($posts = get_records_sql($sql)) {
5727 $postids = array_keys($posts);
5728 return forum_tp_mark_posts_read($user, $postids);
5731 return true;
5737 function forum_tp_is_post_read($userid, $post) {
5738 return (forum_tp_is_post_old($post) ||
5739 record_exists('forum_read', 'userid', $userid, 'postid', $post->id));
5745 function forum_tp_is_post_old($post, $time=null) {
5746 global $CFG;
5748 if (is_null($time)) {
5749 $time = time();
5751 return ($post->modified < ($time - ($CFG->forum_oldpostdays * 24 * 3600)));
5755 * Returns the count of records for the provided user and discussion.
5757 function forum_tp_count_discussion_read_records($userid, $discussionid) {
5758 global $CFG;
5760 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
5762 $sql = 'SELECT COUNT(DISTINCT p.id) '.
5763 'FROM '.$CFG->prefix.'forum_discussions d '.
5764 'LEFT JOIN '.$CFG->prefix.'forum_read r ON d.id = r.discussionid AND r.userid = '.$userid.' '.
5765 'LEFT JOIN '.$CFG->prefix.'forum_posts p ON p.discussion = d.id '.
5766 'AND (p.modified < '.$cutoffdate.' OR p.id = r.postid) '.
5767 'WHERE d.id = '.$discussionid;
5769 return (count_records_sql($sql));
5773 * Returns the count of records for the provided user and discussion.
5775 function forum_tp_count_discussion_unread_posts($userid, $discussionid) {
5776 global $CFG;
5778 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
5780 $sql = 'SELECT COUNT(p.id) '.
5781 'FROM '.$CFG->prefix.'forum_posts p '.
5782 'LEFT JOIN '.$CFG->prefix.'forum_read r ON r.postid = p.id AND r.userid = '.$userid.' '.
5783 'WHERE p.discussion = '.$discussionid.' '.
5784 'AND p.modified >= '.$cutoffdate.' AND r.id is NULL';
5786 return (count_records_sql($sql));
5790 * Returns the count of posts for the provided forum and [optionally] group.
5792 function forum_tp_count_forum_posts($forumid, $groupid=false) {
5793 global $CFG;
5795 $sql = 'SELECT COUNT(*) '.
5796 'FROM '.$CFG->prefix.'forum_posts fp,'.$CFG->prefix.'forum_discussions fd '.
5797 'WHERE fd.forum = '.$forumid.' AND fp.discussion = fd.id';
5798 if ($groupid !== false) {
5799 $sql .= ' AND (fd.groupid = '.$groupid.' OR fd.groupid = -1)';
5801 $count = count_records_sql($sql);
5804 return $count;
5808 * Returns the count of records for the provided user and forum and [optionally] group.
5810 function forum_tp_count_forum_read_records($userid, $forumid, $groupid=false) {
5811 global $CFG;
5813 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
5815 $groupsel = '';
5816 if ($groupid !== false) {
5817 $groupsel = "AND (d.groupid = $groupid OR d.groupid = -1)";
5820 $sql = "SELECT COUNT(p.id)
5821 FROM {$CFG->prefix}forum_posts p
5822 JOIN {$CFG->prefix}forum_discussions d ON d.id = p.discussion
5823 LEFT JOIN {$CFG->prefix}forum_read r ON (r.postid = p.id AND r.userid= $userid)
5824 WHERE d.forum = $forumid
5825 AND (p.modified < $cutoffdate OR (p.modified >= $cutoffdate AND r.id IS NOT NULL))
5826 $groupsel";
5828 return get_field_sql($sql);
5832 * Returns the count of records for the provided user and course.
5833 * Please note that group access is ignored!
5835 function forum_tp_get_course_unread_posts($userid, $courseid) {
5836 global $CFG;
5838 $now = round(time(), -2); // db cache friendliness
5839 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
5841 if (!empty($CFG->forum_enabletimedposts)) {
5842 $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
5843 } else {
5844 $timedsql = "";
5847 $sql = "SELECT f.id, COUNT(p.id) AS unread
5848 FROM {$CFG->prefix}forum_posts p
5849 JOIN {$CFG->prefix}forum_discussions d ON d.id = p.discussion
5850 JOIN {$CFG->prefix}forum f ON f.id = d.forum
5851 JOIN {$CFG->prefix}course c ON c.id = f.course
5852 LEFT JOIN {$CFG->prefix}forum_read r ON (r.postid = p.id AND r.userid = $userid)
5853 LEFT JOIN {$CFG->prefix}forum_track_prefs tf ON (tf.userid = $userid AND tf.forumid = f.id)
5854 WHERE f.course = $courseid
5855 AND p.modified >= $cutoffdate AND r.id is NULL
5856 AND (f.trackingtype = ".FORUM_TRACKING_ON."
5857 OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND tf.id IS NULL))
5858 $timedsql
5859 GROUP BY f.id";
5861 if ($return = get_records_sql($sql)) {
5862 return $return;
5865 return array();
5869 * Returns the count of records for the provided user and forum and [optionally] group.
5871 function forum_tp_count_forum_unread_posts($cm, $course) {
5872 global $CFG, $USER;
5874 static $readcache = array();
5876 $forumid = $cm->instance;
5878 if (!isset($readcache[$course->id])) {
5879 $readcache[$course->id] = array();
5880 if ($counts = forum_tp_get_course_unread_posts($USER->id, $course->id)) {
5881 foreach ($counts as $count) {
5882 $readcache[$course->id][$count->id] = $count->unread;
5887 if (empty($readcache[$course->id][$forumid])) {
5888 // no need to check group mode ;-)
5889 return 0;
5892 $groupmode = groups_get_activity_groupmode($cm, $course);
5894 if ($groupmode != SEPARATEGROUPS) {
5895 return $readcache[$course->id][$forumid];
5898 if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
5899 return $readcache[$course->id][$forumid];
5902 require_once($CFG->dirroot.'/course/lib.php');
5904 $modinfo =& get_fast_modinfo($course);
5905 if (is_null($modinfo->groups)) {
5906 $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
5909 if (empty($CFG->enablegroupings)) {
5910 $mygroups = $modinfo->groups[0];
5911 } else {
5912 $mygroups = $modinfo->groups[$cm->groupingid];
5915 // add all groups posts
5916 if (empty($mygroups)) {
5917 $mygroups = array(-1=>-1);
5918 } else {
5919 $mygroups[-1] = -1;
5921 $mygroups = implode(',', $mygroups);
5924 $now = round(time(), -2); // db cache friendliness
5925 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
5927 if (!empty($CFG->forum_enabletimedposts)) {
5928 $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
5929 } else {
5930 $timedsql = "";
5933 $sql = "SELECT COUNT(p.id)
5934 FROM {$CFG->prefix}forum_posts p
5935 JOIN {$CFG->prefix}forum_discussions d ON p.discussion = d.id
5936 LEFT JOIN {$CFG->prefix}forum_read r ON (r.postid = p.id AND r.userid = $USER->id)
5937 WHERE d.forum = $forumid
5938 AND p.modified >= $cutoffdate AND r.id is NULL
5939 $timedsql
5940 AND d.groupid IN ($mygroups)";
5942 return get_field_sql($sql);
5946 * Deletes read records for the specified index. At least one parameter must be specified.
5948 function forum_tp_delete_read_records($userid=-1, $postid=-1, $discussionid=-1, $forumid=-1) {
5949 $select = '';
5950 if ($userid > -1) {
5951 if ($select != '') $select .= ' AND ';
5952 $select .= 'userid = \''.$userid.'\'';
5954 if ($postid > -1) {
5955 if ($select != '') $select .= ' AND ';
5956 $select .= 'postid = \''.$postid.'\'';
5958 if ($discussionid > -1) {
5959 if ($select != '') $select .= ' AND ';
5960 $select .= 'discussionid = \''.$discussionid.'\'';
5962 if ($forumid > -1) {
5963 if ($select != '') $select .= ' AND ';
5964 $select .= 'forumid = \''.$forumid.'\'';
5966 if ($select == '') {
5967 return false;
5969 else {
5970 return delete_records_select('forum_read', $select);
5974 * Get a list of forums not tracked by the user.
5976 * @param int $userid The id of the user to use.
5977 * @param int $courseid The id of the course being checked.
5978 * @return mixed An array indexed by forum id, or false.
5980 function forum_tp_get_untracked_forums($userid, $courseid) {
5981 global $CFG;
5983 $sql = "SELECT f.id
5984 FROM {$CFG->prefix}forum f
5985 LEFT JOIN {$CFG->prefix}forum_track_prefs ft ON (ft.forumid = f.id AND ft.userid = $userid)
5986 WHERE f.course = $courseid
5987 AND (f.trackingtype = ".FORUM_TRACKING_OFF."
5988 OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND ft.id IS NOT NULL))";
5990 if ($forums = get_records_sql($sql)) {
5991 foreach ($forums as $forum) {
5992 $forums[$forum->id] = $forum;
5994 return $forums;
5996 } else {
5997 return array();
6002 * Determine if a user can track forums and optionally a particular forum.
6003 * Checks the site settings, the user settings and the forum settings (if
6004 * requested).
6006 * @param mixed $forum The forum object to test, or the int id (optional).
6007 * @param mixed $userid The user object to check for (optional).
6008 * @return boolean
6010 function forum_tp_can_track_forums($forum=false, $user=false) {
6011 global $USER, $CFG;
6013 // if possible, avoid expensive
6014 // queries
6015 if (empty($CFG->forum_trackreadposts)) {
6016 return false;
6019 if ($user === false) {
6020 $user = $USER;
6023 if (isguestuser($user) or empty($user->id)) {
6024 return false;
6027 if ($forum === false) {
6028 // general abitily to track forums
6029 return (bool)$user->trackforums;
6033 // Work toward always passing an object...
6034 if (is_numeric($forum)) {
6035 debugging('Better use proper forum object.', DEBUG_DEVELOPER);
6036 $forum = get_record('forum', 'id', $forum, '','','','', 'id,trackingtype');
6039 $forumallows = ($forum->trackingtype == FORUM_TRACKING_OPTIONAL);
6040 $forumforced = ($forum->trackingtype == FORUM_TRACKING_ON);
6042 return ($forumforced || $forumallows) && !empty($user->trackforums);
6046 * Tells whether a specific forum is tracked by the user. A user can optionally
6047 * be specified. If not specified, the current user is assumed.
6049 * @param mixed $forum If int, the id of the forum being checked; if object, the forum object
6050 * @param int $userid The id of the user being checked (optional).
6051 * @return boolean
6053 function forum_tp_is_tracked($forum, $user=false) {
6054 global $USER, $CFG;
6056 if ($user === false) {
6057 $user = $USER;
6060 if (isguestuser($user) or empty($user->id)) {
6061 return false;
6064 // Work toward always passing an object...
6065 if (is_numeric($forum)) {
6066 debugging('Better use proper forum object.', DEBUG_DEVELOPER);
6067 $forum = get_record('forum', 'id', $forum);
6070 if (!forum_tp_can_track_forums($forum, $user)) {
6071 return false;
6074 $forumallows = ($forum->trackingtype == FORUM_TRACKING_OPTIONAL);
6075 $forumforced = ($forum->trackingtype == FORUM_TRACKING_ON);
6077 return $forumforced ||
6078 ($forumallows && get_record('forum_track_prefs', 'userid', $user->id, 'forumid', $forum->id) === false);
6084 function forum_tp_start_tracking($forumid, $userid=false) {
6085 global $USER;
6087 if ($userid === false) {
6088 $userid = $USER->id;
6091 return delete_records('forum_track_prefs', 'userid', $userid, 'forumid', $forumid);
6097 function forum_tp_stop_tracking($forumid, $userid=false) {
6098 global $USER;
6100 if ($userid === false) {
6101 $userid = $USER->id;
6104 if (!record_exists('forum_track_prefs', 'userid', $userid, 'forumid', $forumid)) {
6105 $track_prefs = new object();
6106 $track_prefs->userid = $userid;
6107 $track_prefs->forumid = $forumid;
6108 insert_record('forum_track_prefs', $track_prefs);
6111 return forum_tp_delete_read_records($userid, -1, -1, $forumid);
6116 * Clean old records from the forum_read table.
6118 function forum_tp_clean_read_records() {
6119 global $CFG;
6121 if (!isset($CFG->forum_oldpostdays)) {
6122 return;
6124 // Look for records older than the cutoffdate that are still in the forum_read table.
6125 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6127 //first get the oldest tracking present - we need tis to speedup the next delete query
6128 $sql = "SELECT MIN(fp.modified) AS first
6129 FROM {$CFG->prefix}forum_posts fp
6130 JOIN {$CFG->prefix}forum_read fr ON fr.postid=fp.id";
6131 if (!$first = get_field_sql($sql)) {
6132 // nothing to delete;
6133 return;
6136 // now delete old tracking info
6137 $sql = "DELETE
6138 FROM {$CFG->prefix}forum_read
6139 WHERE postid IN (SELECT fp.id
6140 FROM {$CFG->prefix}forum_posts fp
6141 WHERE fp.modified >= $first AND fp.modified < $cutoffdate)";
6142 execute_sql($sql, false);
6146 * Sets the last post for a given discussion
6148 function forum_discussion_update_last_post($discussionid) {
6149 global $CFG, $db;
6151 // Check the given discussion exists
6152 if (!record_exists('forum_discussions', 'id', $discussionid)) {
6153 return false;
6156 // Use SQL to find the last post for this discussion
6157 $sql = 'SELECT id, userid, modified '.
6158 'FROM '.$CFG->prefix.'forum_posts '.
6159 'WHERE discussion='.$discussionid.' '.
6160 'ORDER BY modified DESC ';
6162 // Lets go find the last post
6163 if (($lastpost = get_record_sql($sql, true))) {
6164 $discussionobject = new Object;
6165 $discussionobject->id = $discussionid;
6166 $discussionobject->usermodified = $lastpost->userid;
6167 $discussionobject->timemodified = $lastpost->modified;
6168 if (update_record('forum_discussions', $discussionobject)) {
6169 return $lastpost->id;
6173 // To get here either we couldn't find a post for the discussion (weird)
6174 // or we couldn't update the discussion record (weird x2)
6175 return false;
6182 function forum_get_view_actions() {
6183 return array('view discussion','search','forum','forums','subscribers');
6189 function forum_get_post_actions() {
6190 return array('add discussion','add post','delete discussion','delete post','move discussion','prune post','update post');
6194 * this function returns all the separate forum ids, given a courseid
6195 * @param int $courseid
6196 * @return array
6198 function forum_get_separate_modules($courseid) {
6200 global $CFG,$db;
6201 $forummodule = get_record("modules", "name", "forum");
6203 $sql = 'SELECT f.id, f.id FROM '.$CFG->prefix.'forum f, '.$CFG->prefix.'course_modules cm WHERE
6204 f.id = cm.instance AND cm.module ='.$forummodule->id.' AND cm.visible = 1 AND cm.course = '.$courseid.'
6205 AND cm.groupmode ='.SEPARATEGROUPS;
6207 return get_records_sql($sql);
6214 function forum_check_throttling($forum, $cm=null) {
6215 global $USER, $CFG;
6217 if (is_numeric($forum)) {
6218 $forum = get_record('forum','id',$forum);
6220 if (!is_object($forum)) {
6221 return false; // this is broken.
6224 if (empty($forum->blockafter)) {
6225 return true;
6228 if (empty($forum->blockperiod)) {
6229 return true;
6232 if (!$cm) {
6233 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
6234 error('Course Module ID was incorrect');
6238 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
6239 if(!has_capability('mod/forum:throttlingapplies', $modcontext)) {
6240 return true;
6243 // get the number of posts in the last period we care about
6244 $timenow = time();
6245 $timeafter = $timenow - $forum->blockperiod;
6247 $numposts = count_records_sql('SELECT COUNT(p.id) FROM '.$CFG->prefix.'forum_posts p'
6248 .' JOIN '.$CFG->prefix.'forum_discussions d'
6249 .' ON p.discussion = d.id WHERE d.forum = '.$forum->id
6250 .' AND p.userid = '.$USER->id.' AND p.created > '.$timeafter);
6252 $a = new object();
6253 $a->blockafter = $forum->blockafter;
6254 $a->numposts = $numposts;
6255 $a->blockperiod = get_string('secondstotime'.$forum->blockperiod);
6257 if ($forum->blockafter <= $numposts) {
6258 print_error('forumblockingtoomanyposts', 'error', $CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id, $a);
6260 if ($forum->warnafter <= $numposts) {
6261 notify(get_string('forumblockingalmosttoomanyposts','forum',$a));
6269 * Removes all grades from gradebook
6270 * @param int $courseid
6271 * @param string optional type
6273 function forum_reset_gradebook($courseid, $type='') {
6274 global $CFG;
6276 $type = $type ? "AND f.type='$type'" : '';
6278 $sql = "SELECT f.*, cm.idnumber as cmidnumber, f.course as courseid
6279 FROM {$CFG->prefix}forum f, {$CFG->prefix}course_modules cm, {$CFG->prefix}modules m
6280 WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id AND f.course=$courseid $type";
6282 if ($forums = get_records_sql($sql)) {
6283 foreach ($forums as $forum) {
6284 forum_grade_item_update($forum, 'reset');
6290 * This function is used by the reset_course_userdata function in moodlelib.
6291 * This function will remove all posts from the specified forum
6292 * and clean up any related data.
6293 * @param $data the data submitted from the reset course.
6294 * @return array status array
6296 function forum_reset_userdata($data) {
6297 global $CFG;
6298 require_once($CFG->libdir.'/filelib.php');
6300 $componentstr = get_string('modulenameplural', 'forum');
6301 $status = array();
6303 $removeposts = false;
6304 if (!empty($data->reset_forum_all)) {
6305 $removeposts = true;
6306 $typesql = "";
6307 $typesstr = get_string('resetforumsall', 'forum');
6308 $types = array();
6310 } else if (!empty($data->reset_forum_types)){
6311 $removeposts = true;
6312 $typesql = "";
6313 $types = array();
6314 $forum_types_all = forum_get_forum_types_all();
6315 foreach ($data->reset_forum_types as $type) {
6316 if (!array_key_exists($type, $forum_types_all)) {
6317 continue;
6319 $typesql .= " AND f.type='$type'";
6320 $types[] = $forum_types_all[$type];
6322 $typesstr = get_string('resetforums', 'forum').': '.implode(', ', $types);
6326 $alldiscussionssql = "SELECT fd.id
6327 FROM {$CFG->prefix}forum_discussions fd, {$CFG->prefix}forum f
6328 WHERE f.course={$data->courseid} AND f.id=fd.forum";
6330 $allforumssql = "SELECT f.id
6331 FROM {$CFG->prefix}forum f
6332 WHERE f.course={$data->courseid}";
6334 $allpostssql = "SELECT fp.id
6335 FROM {$CFG->prefix}forum_posts fp, {$CFG->prefix}forum_discussions fd, {$CFG->prefix}forum f
6336 WHERE f.course={$data->courseid} AND f.id=fd.forum AND fd.id=fp.discussion";
6338 if ($removeposts) {
6339 $discussionssql = "$alldiscussionssql $typesql";
6340 $forumssql = "$allforumssql $typesql";
6341 $postssql = "$allpostssql $typesql";
6343 // first delete all read flags
6344 delete_records_select('forum_read', "forumid IN ($forumssql)");
6346 // remove tracking prefs
6347 delete_records_select('forum_track_prefs', "forumid IN ($forumssql)");
6349 // remove posts from queue
6350 delete_records_select('forum_queue', "discussionid IN ($discussionssql)");
6352 // remove ratings
6353 delete_records_select('forum_ratings', "post IN ($postssql)");
6355 // all posts
6356 delete_records_select('forum_posts', "discussion IN ($discussionssql)");
6358 // finally all discussions
6359 delete_records_select('forum_discussions', "forum IN ($forumssql)");
6361 // now get rid of all attachments
6362 if ($forums = get_records_sql($forumssql)) {
6363 foreach ($forums as $forumid=>$unused) {
6364 fulldelete($CFG->dataroot.'/'.$data->courseid.'/moddata/forum/'.$forumid);
6368 // remove all grades from gradebook
6369 if (empty($data->reset_gradebook_grades)) {
6370 if (empty($types)) {
6371 forum_reset_gradebook($data->courseid);
6372 } else {
6373 foreach ($types as $type) {
6374 forum_reset_gradebook($data->courseid, $type);
6379 $status[] = array('component'=>$componentstr, 'item'=>$typesstr, 'error'=>false);
6382 // remove all ratings
6383 if (!empty($data->reset_forum_ratings)) {
6384 delete_records_select('forum_ratings', "post IN ($allpostssql)");
6385 // remove all grades from gradebook
6386 if (empty($data->reset_gradebook_grades)) {
6387 forum_reset_gradebook($data->courseid);
6391 // remove all subscriptions unconditionally - even for users still enrolled in course
6392 if (!empty($data->reset_forum_subscriptions)) {
6393 delete_records_select('forum_subscriptions', "forum IN ($allforumssql)");
6394 $status[] = array('component'=>$componentstr, 'item'=>get_string('resetsubscriptions','forum'), 'error'=>false);
6397 // remove all tracking prefs unconditionally - even for users still enrolled in course
6398 if (!empty($data->reset_forum_track_prefs)) {
6399 delete_records_select('forum_track_prefs', "forumid IN ($allforumssql)");
6400 $status[] = array('component'=>$componentstr, 'item'=>get_string('resettrackprefs','forum'), 'error'=>false);
6403 /// updating dates - shift may be negative too
6404 if ($data->timeshift) {
6405 shift_course_mod_dates('forum', array('assesstimestart', 'assesstimefinish'), $data->timeshift, $data->courseid);
6406 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
6409 return $status;
6413 * Called by course/reset.php
6414 * @param $mform form passed by reference
6416 function forum_reset_course_form_definition(&$mform) {
6417 $mform->addElement('header', 'forumheader', get_string('modulenameplural', 'forum'));
6419 $mform->addElement('checkbox', 'reset_forum_all', get_string('resetforumsall','forum'));
6421 $mform->addElement('select', 'reset_forum_types', get_string('resetforums', 'forum'), forum_get_forum_types_all(), array('multiple' => 'multiple'));
6422 $mform->setAdvanced('reset_forum_types');
6423 $mform->disabledIf('reset_forum_types', 'reset_forum_all', 'checked');
6425 $mform->addElement('checkbox', 'reset_forum_subscriptions', get_string('resetsubscriptions','forum'));
6426 $mform->setAdvanced('reset_forum_subscriptions');
6428 $mform->addElement('checkbox', 'reset_forum_track_prefs', get_string('resettrackprefs','forum'));
6429 $mform->setAdvanced('reset_forum_track_prefs');
6430 $mform->disabledIf('reset_forum_track_prefs', 'reset_forum_all', 'checked');
6432 $mform->addElement('checkbox', 'reset_forum_ratings', get_string('deleteallratings'));
6433 $mform->disabledIf('reset_forum_ratings', 'reset_forum_all', 'checked');
6437 * Course reset form defaults.
6439 function forum_reset_course_form_defaults($course) {
6440 return array('reset_forum_all'=>1, 'reset_forum_subscriptions'=>0, 'reset_forum_track_prefs'=>0, 'reset_forum_ratings'=>1);
6444 * Converts a forum to use the Roles System
6445 * @param $forum - a forum object with the same attributes as a record
6446 * from the forum database table
6447 * @param $forummodid - the id of the forum module, from the modules table
6448 * @param $teacherroles - array of roles that have moodle/legacy:teacher
6449 * @param $studentroles - array of roles that have moodle/legacy:student
6450 * @param $guestroles - array of roles that have moodle/legacy:guest
6451 * @param $cmid - the course_module id for this forum instance
6452 * @return boolean - forum was converted or not
6454 function forum_convert_to_roles($forum, $forummodid, $teacherroles=array(),
6455 $studentroles=array(), $guestroles=array(), $cmid=NULL) {
6457 global $CFG;
6459 if (!isset($forum->open) && !isset($forum->assesspublic)) {
6460 // We assume that this forum has already been converted to use the
6461 // Roles System. Columns forum.open and forum.assesspublic get dropped
6462 // once the forum module has been upgraded to use Roles.
6463 return false;
6466 if ($forum->type == 'teacher') {
6468 // Teacher forums should be converted to normal forums that
6469 // use the Roles System to implement the old behavior.
6470 // Note:
6471 // Seems that teacher forums were never backed up in 1.6 since they
6472 // didn't have an entry in the course_modules table.
6473 require_once($CFG->dirroot.'/course/lib.php');
6475 if (count_records('forum_discussions', 'forum', $forum->id) == 0) {
6476 // Delete empty teacher forums.
6477 delete_records('forum', 'id', $forum->id);
6478 } else {
6479 // Create a course module for the forum and assign it to
6480 // section 0 in the course.
6481 $mod = new object;
6482 $mod->course = $forum->course;
6483 $mod->module = $forummodid;
6484 $mod->instance = $forum->id;
6485 $mod->section = 0;
6486 $mod->visible = 0; // Hide the forum
6487 $mod->visibleold = 0; // Hide the forum
6488 $mod->groupmode = 0;
6490 if (!$cmid = add_course_module($mod)) {
6491 error('Could not create new course module instance for the teacher forum');
6492 } else {
6493 $mod->coursemodule = $cmid;
6494 if (!$sectionid = add_mod_to_section($mod)) {
6495 error('Could not add converted teacher forum instance to section 0 in the course');
6496 } else {
6497 if (!set_field('course_modules', 'section', $sectionid, 'id', $cmid)) {
6498 error('Could not update course module with section id');
6503 // Change the forum type to general.
6504 $forum->type = 'general';
6505 if (!update_record('forum', $forum)) {
6506 error('Could not change forum from type teacher to type general');
6509 $context = get_context_instance(CONTEXT_MODULE, $cmid);
6511 // Create overrides for default student and guest roles (prevent).
6512 foreach ($studentroles as $studentrole) {
6513 assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
6514 assign_capability('mod/forum:viewhiddentimedposts', CAP_PREVENT, $studentrole->id, $context->id);
6515 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
6516 assign_capability('mod/forum:replypost', CAP_PREVENT, $studentrole->id, $context->id);
6517 assign_capability('mod/forum:viewrating', CAP_PREVENT, $studentrole->id, $context->id);
6518 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $studentrole->id, $context->id);
6519 assign_capability('mod/forum:rate', CAP_PREVENT, $studentrole->id, $context->id);
6520 assign_capability('mod/forum:createattachment', CAP_PREVENT, $studentrole->id, $context->id);
6521 assign_capability('mod/forum:deleteownpost', CAP_PREVENT, $studentrole->id, $context->id);
6522 assign_capability('mod/forum:deleteanypost', CAP_PREVENT, $studentrole->id, $context->id);
6523 assign_capability('mod/forum:splitdiscussions', CAP_PREVENT, $studentrole->id, $context->id);
6524 assign_capability('mod/forum:movediscussions', CAP_PREVENT, $studentrole->id, $context->id);
6525 assign_capability('mod/forum:editanypost', CAP_PREVENT, $studentrole->id, $context->id);
6526 assign_capability('mod/forum:viewqandawithoutposting', CAP_PREVENT, $studentrole->id, $context->id);
6527 assign_capability('mod/forum:viewsubscribers', CAP_PREVENT, $studentrole->id, $context->id);
6528 assign_capability('mod/forum:managesubscriptions', CAP_PREVENT, $studentrole->id, $context->id);
6529 assign_capability('mod/forum:throttlingapplies', CAP_PREVENT, $studentrole->id, $context->id);
6531 foreach ($guestroles as $guestrole) {
6532 assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $guestrole->id, $context->id);
6533 assign_capability('mod/forum:viewhiddentimedposts', CAP_PREVENT, $guestrole->id, $context->id);
6534 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $guestrole->id, $context->id);
6535 assign_capability('mod/forum:replypost', CAP_PREVENT, $guestrole->id, $context->id);
6536 assign_capability('mod/forum:viewrating', CAP_PREVENT, $guestrole->id, $context->id);
6537 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $guestrole->id, $context->id);
6538 assign_capability('mod/forum:rate', CAP_PREVENT, $guestrole->id, $context->id);
6539 assign_capability('mod/forum:createattachment', CAP_PREVENT, $guestrole->id, $context->id);
6540 assign_capability('mod/forum:deleteownpost', CAP_PREVENT, $guestrole->id, $context->id);
6541 assign_capability('mod/forum:deleteanypost', CAP_PREVENT, $guestrole->id, $context->id);
6542 assign_capability('mod/forum:splitdiscussions', CAP_PREVENT, $guestrole->id, $context->id);
6543 assign_capability('mod/forum:movediscussions', CAP_PREVENT, $guestrole->id, $context->id);
6544 assign_capability('mod/forum:editanypost', CAP_PREVENT, $guestrole->id, $context->id);
6545 assign_capability('mod/forum:viewqandawithoutposting', CAP_PREVENT, $guestrole->id, $context->id);
6546 assign_capability('mod/forum:viewsubscribers', CAP_PREVENT, $guestrole->id, $context->id);
6547 assign_capability('mod/forum:managesubscriptions', CAP_PREVENT, $guestrole->id, $context->id);
6548 assign_capability('mod/forum:throttlingapplies', CAP_PREVENT, $guestrole->id, $context->id);
6551 } else {
6552 // Non-teacher forum.
6554 if (empty($cmid)) {
6555 // We were not given the course_module id. Try to find it.
6556 if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
6557 notify('Could not get the course module for the forum');
6558 return false;
6559 } else {
6560 $cmid = $cm->id;
6563 $context = get_context_instance(CONTEXT_MODULE, $cmid);
6565 // $forum->open defines what students can do:
6566 // 0 = No discussions, no replies
6567 // 1 = No discussions, but replies are allowed
6568 // 2 = Discussions and replies are allowed
6569 switch ($forum->open) {
6570 case 0:
6571 foreach ($studentroles as $studentrole) {
6572 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
6573 assign_capability('mod/forum:replypost', CAP_PREVENT, $studentrole->id, $context->id);
6575 break;
6576 case 1:
6577 foreach ($studentroles as $studentrole) {
6578 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
6579 assign_capability('mod/forum:replypost', CAP_ALLOW, $studentrole->id, $context->id);
6581 break;
6582 case 2:
6583 foreach ($studentroles as $studentrole) {
6584 assign_capability('mod/forum:startdiscussion', CAP_ALLOW, $studentrole->id, $context->id);
6585 assign_capability('mod/forum:replypost', CAP_ALLOW, $studentrole->id, $context->id);
6587 break;
6590 // $forum->assessed defines whether forum rating is turned
6591 // on (1 or 2) and who can rate posts:
6592 // 1 = Everyone can rate posts
6593 // 2 = Only teachers can rate posts
6594 switch ($forum->assessed) {
6595 case 1:
6596 foreach ($studentroles as $studentrole) {
6597 assign_capability('mod/forum:rate', CAP_ALLOW, $studentrole->id, $context->id);
6599 foreach ($teacherroles as $teacherrole) {
6600 assign_capability('mod/forum:rate', CAP_ALLOW, $teacherrole->id, $context->id);
6602 break;
6603 case 2:
6604 foreach ($studentroles as $studentrole) {
6605 assign_capability('mod/forum:rate', CAP_PREVENT, $studentrole->id, $context->id);
6607 foreach ($teacherroles as $teacherrole) {
6608 assign_capability('mod/forum:rate', CAP_ALLOW, $teacherrole->id, $context->id);
6610 break;
6613 // $forum->assesspublic defines whether students can see
6614 // everybody's ratings:
6615 // 0 = Students can only see their own ratings
6616 // 1 = Students can see everyone's ratings
6617 switch ($forum->assesspublic) {
6618 case 0:
6619 foreach ($studentroles as $studentrole) {
6620 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $studentrole->id, $context->id);
6622 foreach ($teacherroles as $teacherrole) {
6623 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $teacherrole->id, $context->id);
6625 break;
6626 case 1:
6627 foreach ($studentroles as $studentrole) {
6628 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $studentrole->id, $context->id);
6630 foreach ($teacherroles as $teacherrole) {
6631 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $teacherrole->id, $context->id);
6633 break;
6636 if (empty($cm)) {
6637 $cm = get_record('course_modules', 'id', $cmid);
6640 // $cm->groupmode:
6641 // 0 - No groups
6642 // 1 - Separate groups
6643 // 2 - Visible groups
6644 switch ($cm->groupmode) {
6645 case 0:
6646 break;
6647 case 1:
6648 foreach ($studentroles as $studentrole) {
6649 assign_capability('moodle/site:accessallgroups', CAP_PREVENT, $studentrole->id, $context->id);
6651 foreach ($teacherroles as $teacherrole) {
6652 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
6654 break;
6655 case 2:
6656 foreach ($studentroles as $studentrole) {
6657 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $studentrole->id, $context->id);
6659 foreach ($teacherroles as $teacherrole) {
6660 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
6662 break;
6665 return true;
6669 * Returns array of forum aggregate types
6671 function forum_get_aggregate_types() {
6672 return array (FORUM_AGGREGATE_NONE => get_string('aggregatenone', 'forum'),
6673 FORUM_AGGREGATE_AVG => get_string('aggregateavg', 'forum'),
6674 FORUM_AGGREGATE_COUNT => get_string('aggregatecount', 'forum'),
6675 FORUM_AGGREGATE_MAX => get_string('aggregatemax', 'forum'),
6676 FORUM_AGGREGATE_MIN => get_string('aggregatemin', 'forum'),
6677 FORUM_AGGREGATE_SUM => get_string('aggregatesum', 'forum'));
6681 * Returns array of forum layout modes
6683 function forum_get_layout_modes() {
6684 return array (FORUM_MODE_FLATOLDEST => get_string('modeflatoldestfirst', 'forum'),
6685 FORUM_MODE_FLATNEWEST => get_string('modeflatnewestfirst', 'forum'),
6686 FORUM_MODE_THREADED => get_string('modethreaded', 'forum'),
6687 FORUM_MODE_NESTED => get_string('modenested', 'forum'));
6691 * Returns array of forum types
6693 function forum_get_forum_types() {
6694 return array ('general' => get_string('generalforum', 'forum'),
6695 'eachuser' => get_string('eachuserforum', 'forum'),
6696 'single' => get_string('singleforum', 'forum'),
6697 'qanda' => get_string('qandaforum', 'forum'));
6701 * Returns array of all forum layout modes
6703 function forum_get_forum_types_all() {
6704 return array ('news' => get_string('namenews','forum'),
6705 'social' => get_string('namesocial','forum'),
6706 'general' => get_string('generalforum', 'forum'),
6707 'eachuser' => get_string('eachuserforum', 'forum'),
6708 'single' => get_string('singleforum', 'forum'),
6709 'qanda' => get_string('qandaforum', 'forum'));
6713 * Returns array of forum open modes
6715 function forum_get_open_modes() {
6716 return array ('2' => get_string('openmode2', 'forum'),
6717 '1' => get_string('openmode1', 'forum'),
6718 '0' => get_string('openmode0', 'forum') );
6722 * Returns all other caps used in module
6724 function forum_get_extra_capabilities() {
6725 return array('moodle/site:accessallgroups', 'moodle/site:viewfullnames', 'moodle/site:trustcontent');