41ca909eef06b7abd66357719c6d57b8367c5e30
[moodle.git] / mod / forum / lib.php
1 <?php  // $Id$
3 require_once($CFG->libdir.'/filelib.php');
5 /// CONSTANTS ///////////////////////////////////////////////////////////
7 define('FORUM_MODE_FLATOLDEST', 1);
8 define('FORUM_MODE_FLATNEWEST', -1);
9 define('FORUM_MODE_THREADED', 2);
10 define('FORUM_MODE_NESTED', 3);
12 define('FORUM_FORCESUBSCRIBE', 1);
13 define('FORUM_INITIALSUBSCRIBE', 2);
14 define('FORUM_DISALLOWSUBSCRIBE',3);
16 define('FORUM_TRACKING_OFF', 0);
17 define('FORUM_TRACKING_OPTIONAL', 1);
18 define('FORUM_TRACKING_ON', 2);
20 define('FORUM_UNSET_POST_RATING', -999);
22 $FORUM_LAYOUT_MODES = array ( FORUM_MODE_FLATOLDEST => get_string('modeflatoldestfirst', 'forum'),
23                               FORUM_MODE_FLATNEWEST => get_string('modeflatnewestfirst', 'forum'),
24                               FORUM_MODE_THREADED   => get_string('modethreaded', 'forum'),
25                               FORUM_MODE_NESTED     => get_string('modenested', 'forum') );
27 // These are course content forums that can be added to the course manually
28 $FORUM_TYPES   = array ('general'    => get_string('generalforum', 'forum'),
29                         'eachuser'   => get_string('eachuserforum', 'forum'),
30                         'single'     => get_string('singleforum', 'forum'),
31                         'qanda'      => get_string('qandaforum', 'forum')
32                         );
34 $FORUM_OPEN_MODES   = array ('2' => get_string('openmode2', 'forum'),
35                              '1' => get_string('openmode1', 'forum'),
36                              '0' => get_string('openmode0', 'forum') );
38 if (!isset($CFG->forum_displaymode)) {
39     set_config('forum_displaymode', FORUM_MODE_NESTED);
40 }
42 if (!isset($CFG->forum_shortpost)) {
43     set_config('forum_shortpost', 300);  // Less non-HTML characters than this is short
44 }
46 if (!isset($CFG->forum_longpost)) {
47     set_config('forum_longpost', 600);  // More non-HTML characters than this is long
48 }
50 if (!isset($CFG->forum_manydiscussions)) {
51     set_config('forum_manydiscussions', 100);  // Number of discussions on a page
52 }
54 if (!isset($CFG->forum_maxbytes)) {
55     set_config('forum_maxbytes', 512000);  // Default maximum size for all forums
56 }
58 if (!isset($CFG->forum_trackreadposts)) {
59     set_config('forum_trackreadposts', true);  // Default whether user needs to mark a post as read
60 }
62 if (!isset($CFG->forum_oldpostdays)) {
63     set_config('forum_oldpostdays', 14);  // Default number of days that a post is considered old
64 }
66 if (!isset($CFG->forum_usermarksread)) {
67     set_config('forum_usermarksread', false);  // Default whether user needs to mark a post as read
68 }
70 if (!isset($CFG->forum_cleanreadtime)) {
71     set_config('forum_cleanreadtime', 2);  // Default time (hour) to execute 'clean_read_records' cron
72 }
74 if (!isset($CFG->forum_replytouser)) {
75     set_config('forum_replytouser', true);  // Default maximum size for all forums
76 }
78 if (empty($USER->id) or isguest()) {
79     $CFG->forum_trackreadposts = false;  // This feature never works when a user isn't logged in
80 }
82 if (!isset($CFG->forum_enabletimedposts)) {   // Newish feature that is not quite ready for production in 1.6
83     $CFG->forum_enabletimedposts = false;
84 }
87 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
89 function forum_add_instance($forum) {
90 // Given an object containing all the necessary data,
91 // (defined by the form in mod.html) this function
92 // will create a new instance and return the id number
93 // of the new instance.
95     global $CFG;
97     $forum->timemodified = time();
99     if (!isset($forum->assessed)) {
100         $forum->assessed = 0;
101     }
103     if (empty($forum->ratingtime)) {
104         $forum->assesstimestart  = 0;
105         $forum->assesstimefinish = 0;
106     }
108     if (!$forum->id = insert_record('forum', $forum)) {
109         return false;
110     }
112     if ($forum->type == 'single') {  // Create related discussion.
113         $discussion->course   = $forum->course;
114         $discussion->forum    = $forum->id;
115         $discussion->name     = $forum->name;
116         $discussion->intro    = $forum->intro;
117         $discussion->assessed = $forum->assessed;
118         $discussion->format   = $forum->format;
119         $discussion->mailnow  = false;
121         if (! forum_add_discussion($discussion, $discussion->intro)) {
122             error('Could not add the discussion for this forum');
123         }
124     }
126     if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) { // all users should be subscribed initially
127         $users = get_course_users($forum->course);
128         foreach ($users as $user) {
129             forum_subscribe($user->id, $forum->id);
130         }
131     }
133     return $forum->id;
137 function forum_update_instance($forum) {
138 // Given an object containing all the necessary data,
139 // (defined by the form in mod.html) this function
140 // will update an existing instance with new data.
142     $forum->timemodified = time();
143     $forum->id = $forum->instance;
145     if (empty($forum->assessed)) {
146         $forum->assessed = 0;
147     }
149     if (empty($forum->ratingtime)) {
150         $forum->assesstimestart  = 0;
151         $forum->assesstimefinish = 0;
152     }
154     if ($forum->type == 'single') {  // Update related discussion and post.
155         if (! $discussion = get_record('forum_discussions', 'forum', $forum->id)) {
156             if ($discussions = get_records('forum_discussions', 'forum', $forum->id, 'timemodified ASC')) {
157                 notify('Warning! There is more than one discussion in this forum - using the most recent');
158                 $discussion = array_pop($discussions);
159             } else {
160                 error('Could not find the discussion in this forum');
161             }
162         }
163         if (! $post = get_record('forum_posts', 'id', $discussion->firstpost)) {
164             error('Could not find the first post in this forum discussion');
165         }
167         $post->subject  = $forum->name;
168         $post->message  = $forum->intro;
169         $post->modified = $forum->timemodified;
171         if (! update_record('forum_posts', $post)) {
172             error('Could not update the first post');
173         }
175         $discussion->name = $forum->name;
177         if (! update_record('forum_discussions', $discussion)) {
178             error('Could not update the discussion');
179         }
180     }
182     return update_record('forum', $forum);
186 function forum_delete_instance($id) {
187 // Given an ID of an instance of this module,
188 // this function will permanently delete the instance
189 // and any data that depends on it.
191     if (!$forum = get_record('forum', 'id', $id)) {
192         return false;
193     }
195     $result = true;
197     if ($discussions = get_records('forum_discussions', 'forum', $forum->id)) {
198         foreach ($discussions as $discussion) {
199             if (!forum_delete_discussion($discussion, true)) {
200                 $result = false;
201             }
202         }
203     }
205     if (!delete_records('forum_subscriptions', 'forum', $forum->id)) {
206         $result = false;
207     }
209     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
211     if (!delete_records('forum', 'id', $forum->id)) {
212         $result = false;
213     }
215     return $result;
219 function forum_cron() {
220 /// Function to be run periodically according to the moodle cron
221 /// Finds all posts that have yet to be mailed out, and mails them
222 /// out to all subscribers
224     global $CFG, $USER;
225     static $strforums = NULL;
227     static $cachecm = array();
229     if ($strforums === NULL) {
230         $strforums = get_string('forums', 'forum');
231     }
233     if (!empty($USER->id)) { // Remember real USER account if necessary
234         $realuser = $USER;
235     }
237     /// Posts older than 2 days will not be mailed.  This is to avoid the problem where
238     /// cron has not been running for a long time, and then suddenly people are flooded
239     /// with mail from the past few weeks or months
241     $timenow   = time();
242     $endtime   = $timenow - $CFG->maxeditingtime;
243     $starttime = $endtime - 48 * 3600;   /// Two days earlier
245     $CFG->enablerecordcache = true;      // We want all the caching we can get
247     if ($posts = forum_get_unmailed_posts($starttime, $endtime)) {
249         /// Mark them all now as being mailed.  It's unlikely but possible there
250         /// might be an error later so that a post is NOT actually mailed out,
251         /// but since mail isn't crucial, we can accept this risk.  Doing it now
252         /// prevents the risk of duplicated mails, which is a worse problem.
254         if (!forum_mark_old_posts_as_mailed($endtime)) {
255             mtrace('Errors occurred while trying to mark some posts as being mailed.');
256             return false;  // Don't continue trying to mail them, in case we are in a cron loop
257         }
259         @set_time_limit(0);   /// so that script does not get timed out when posting to many users
261         $urlinfo = parse_url($CFG->wwwroot);
262         $hostname = $urlinfo['host'];
264         foreach ($posts as $post) {
266             mtrace(get_string('processingpost', 'forum', $post->id), '');
268             /// Check the consistency of the data first
270             if (! $userfrom = get_record('user', 'id', $post->userid)) {
271                 mtrace('Could not find user '.$post->userid);
272                 continue;
273             }
275             if (! $discussion = get_record('forum_discussions', 'id', $post->discussion)) {
276                 mtrace('Could not find discussion '.$post->discussion);
277                 continue;
278             }
280             if (! $forum = get_record('forum', 'id', $discussion->forum)) {
281                 mtrace('Could not find forum '.$discussion->forum);
282                 continue;
283             }
285             if (! $course = get_record('course', 'id', $forum->course)) {
286                 mtrace('Could not find course '.$forum->course);
287                 continue;
288             }
290             $cleanforumname = str_replace('"', "'", strip_tags($forum->name));
292             $userfrom->customheaders = array (  // Headers to make emails easier to track
293                        'Precedence: Bulk',
294                        'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
295                        'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
296                        'Message-Id: <moodlepost'.$post->id.'@'.$hostname.'>',
297                        'In-Reply-To: <moodlepost'.$post->parent.'@'.$hostname.'>',
298                        'References: <moodlepost'.$post->parent.'@'.$hostname.'>',
299                        'X-Course-Id: '.$course->id,
300                        'X-Course-Name: '.strip_tags($course->fullname)
301             );
304             if (!empty($course->lang)) {
305                 $CFG->courselang = $course->lang;
306             } else {
307                 unset($CFG->courselang);
308             }
310             // Get coursemodule record (and cache these)
312             if (!empty($cachecm[$forum->id])) {
313                 $cm = $cachecm[$forum->id];
315             } else if ($cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
316                 $cachecm[$forum->id] = $cm;
318             } else {
319                 $cm = new object;
320                 $cm->id = 0;
321                 $cachecm[$forum->id] = $cm;
322             }
324             $groupmode = false;
325             if (!empty($cm->id)) {
326                 if ($groupmode = groupmode($course, $cm) and $discussion->groupid > 0) {   // Groups are being used
327                     if (! groups_group_exists($discussion->groupid)) { // Can't find group //TODO:
328                         continue;                                            // Be safe and don't send it to anyone
329                     }
330                 }
331             }
333             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);   // Cached already
335             if ($users = forum_subscribed_users($course, $forum, 0, true)) {
337                 $mailcount=0;
338                 $errorcount=0;
339                 foreach ($users as $userto) {
340                     if ($groupmode) {    // Look for a reason not to send this email
341                         if (!empty($group->id)) {
342                             if (!ismember($group->id, $userto->id)) {
343                                 if (!has_capability('moodle/site:accessallgroups', $modcontext, false, $userto->id)) {
344                                     continue;
345                                 }
346                             }
347                         }
348                     }
350                     // make sure we're allowed to see it...
351                     if (!forum_user_can_see_post($forum, $discussion, $post, $userto)) {
352                         continue;
353                     }
355                     if ($userto->maildigest > 0) {
356                         // This user wants the mails to be in digest form
357                         $queue = New stdClass;
358                         $queue->userid = $userto->id;
359                         $queue->discussionid = $discussion->id;
360                         $queue->postid = $post->id;
361                         if (!insert_record('forum_queue', $queue)) {
362                             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.");
363                         }
364                         continue;
365                     }
367                     /// Override the language and timezone of the "current" user, so that
368                     /// mail is customised for the receiver.
369                     $USER->lang     = $userto->lang;
370                     $USER->timezone = $userto->timezone;
372                     $postsubject = "$course->shortname: ".format_string($post->subject,true);
373                     $posttext = forum_make_mail_text($course, $forum, $discussion, $post, $userfrom, $userto);
374                     $posthtml = forum_make_mail_html($course, $forum, $discussion, $post, $userfrom, $userto);
376                     if (!$mailresult = email_to_user($userto, $userfrom, $postsubject, $posttext,
377                                                      $posthtml, '', '', $CFG->forum_replytouser)) {
378                         mtrace("Error: mod/forum/cron.php: Could not send out mail for id $post->id to user $userto->id".
379                              " ($userto->email) .. not trying again.");
380                         add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
381                                    substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
382                         $errorcount++;
383                     } else if ($mailresult === 'emailstop') {
384                         add_to_log($course->id, 'forum', 'mail blocked', "discuss.php?d=$discussion->id#p$post->id",
385                                    substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
386                     } else {
387                         $mailcount++;
389                     /// Mark post as read if forum_usermarksread is set off
390                         if (!$CFG->forum_usermarksread && forum_tp_can_track_forums($forum, $userto) &&
391                             forum_tp_is_tracked($forum, $userto->id)) {
392                             if (!forum_tp_mark_post_read($userto->id, $post, $forum->id)) {
393                                 mtrace("Error: mod/forum/cron.php: Could not mark post $post->id read for user $userto->id".
394                                      " while sending email.");
395                             }
396                         }
397                     }
398                 }
400                 mtrace(".... mailed to $mailcount users.");
401                 if ($errorcount) {
402                     set_field("forum_posts", "mailed", "2", "id", "$post->id");
403                 }
404             }
405         }
406     }
408     unset($CFG->courselang);
410     if (!empty($realuser)) {   // Restore real USER timezone if necessary
411         $sitetimezone = $realuser->timezone;
412         $USER->lang   = $realuser->lang;
413     } else {
414         $sitetimezone = $CFG->timezone;
415         $USER->lang   = $CFG->lang;
416     }
418     /// Now see if there are any digest mails waiting to be sent, and if we should send them
420     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
421         set_config('digestmailtimelast', 0);
422     }
424     $timenow = time();
425     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
427     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
429         set_config('digestmailtimelast', $timenow);
431         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
433         $digestposts = get_records('forum_queue');
434         if(!empty($digestposts)) {
436             @set_time_limit(0);   /// so that script does not get timed out when posting to many users
438             // We have work to do
439             $usermailcount = 0;
440             $site = get_site();
442             $users = array();
443             $posts = array();
444             $discussions = array();
445             $discussionposts = array();
446             $userdiscussions = array();
447             foreach($digestposts as $digestpost) {
448                 if(!isset($users[$digestpost->userid])) {
449                     $users[$digestpost->userid] = get_record('user', 'id', $digestpost->userid);
450                 }
451                 if(!isset($discussions[$digestpost->discussionid])) {
452                     $discussions[$digestpost->discussionid] = get_record('forum_discussions', 'id', $digestpost->discussionid);
453                 }
454                 if(!isset($posts[$digestpost->postid])) {
455                     $posts[$digestpost->postid] = get_record('forum_posts', 'id', $digestpost->postid);
456                 }
457                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
458                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
459             }
461             // Data collected, start sending out emails to each user
463             foreach($userdiscussions as $userid => $thesediscussions) {
465                 mtrace(get_string('processingdigest', 'forum', $userid),'... ');
467                 // First of all delete all the queue entries for this user
468                 delete_records('forum_queue', 'userid', $userid);
469                 $userto = $users[$userid];
471                 /// Override the language and timezone of the "current" user, so that
472                 /// mail is customised for the receiver.
473                 $USER->lang     = $userto->lang;
474                 $USER->timezone = $userto->timezone;
477                 $postsubject = get_string('digestmailsubject', 'forum', $site->shortname);
478                 $headerdata = New stdClass;
479                 $headerdata->sitename = $site->fullname;
480                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
482                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
483                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
485                 $posthtml = "<head>";
486                 foreach ($CFG->stylesheets as $stylesheet) {
487                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
488                 }
489                 $posthtml .= "</head>\n<body>\n";
490                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
492                 foreach($thesediscussions as $discussionid) {
493                     $discussion = $discussions[$discussionid];
494                     if(empty($discussion)) {
495                         mtrace("Error: Could not find discussion $discussionid");
496                         continue;
497                     }
499                     if (! $forum = get_record("forum", "id", "$discussion->forum")) {
500                         mtrace("Could not find forum $discussion->forum");
501                         continue;
502                     }
503                     if (! $course = get_record("course", "id", "$forum->course")) {
504                         mtrace("Could not find course $forum->course");
505                         continue;
506                     }
508                     $canunsubscribe = ! forum_is_forcesubscribed($forum->id);
509                     $canreply = forum_user_can_post($forum, $userto);
512                     $posttext .= "\n \n";
513                     $posttext .= '=====================================================================';
514                     $posttext .= "\n \n";
515                     $posttext .= "$course->shortname -> $strforums -> ".format_string($forum->name,true);
516                     if ($discussion->name != $forum->name) {
517                         $posttext  .= " -> ".format_string($discussion->name,true);
518                     }
519                     $posttext .= "\n";
521                     $posthtml .= "<p><font face=\"sans-serif\">".
522                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> -> ".
523                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
524                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
525                     if ($discussion->name == $forum->name) {
526                         $posthtml .= "</font></p>";
527                     } else {
528                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
529                     }
530                     $posthtml .= '<p>';
532                     $postsarray = $discussionposts[$discussionid];
533                     sort($postsarray);
535                 /// Create an empty array to use for marking read posts.
536                 /// (I'm sure there's already a structure I can use here, but I can't be sure.)
537                     $markread = array();
539                     foreach ($postsarray as $postid) {
540                         if (! $post = get_record("forum_posts", "id", "$postid")) {
541                             mtrace("Error: Could not find post $postid");
542                             continue;
543                         }
544                         if (! $userfrom = get_record("user", "id", "$post->userid")) {
545                             mtrace("Error: Could not find user $post->userid");
546                             continue;
547                         }
549                         $userfrom->customheaders = array ("Precedence: Bulk");
551                         if ($userto->maildigest == 2) {
552                             // Subjects only
553                             $by = New stdClass;
554                             $by->name = fullname($userfrom);
555                             $by->date = userdate($post->modified);
556                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
557                             $posttext .= "\n---------------------------------------------------------------------";
559                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
560                             $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>';
562                         } else {
563                             // The full treatment
564                             $posttext .= forum_make_mail_text($course, $forum, $discussion, $post, $userfrom, $userto, true);
565                             $posthtml .= forum_make_mail_post($post, $userfrom, $userto, $course, false, $canreply, true, false);
567                         /// Create an array of postid's for this user to mark as read.
568                             if (!$CFG->forum_usermarksread &&
569                                 forum_tp_can_track_forums($forum, $userto) &&
570                                 forum_tp_is_tracked($forum, $userto->id)) {
571                                 $markread[$post->id]->post = $post;
572                                 $markread[$post->id]->forumid = $forum->id;
573                             }
574                         }
575                     }
576                     if ($canunsubscribe) {
577                         $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>";
578                     } else {
579                         $posthtml .= "\n<div align=\"right\"><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
580                     }
581                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
582                 }
583                 $posthtml .= '</body>';
585                 if($userto->mailformat != 1) {
586                     // This user DOESN'T want to receive HTML
587                     $posthtml = '';
588                 }
590                 if (!$mailresult =  email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml,
591                                                   '', '', $CFG->forum_replytouser)) {
592                     mtrace("ERROR!");
593                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
594                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
595                 } else if ($mailresult === 'emailstop') {
596                     add_to_log($course->id, 'forum', 'mail digest blocked', '', '', $cm->id, $userto->id);
597                 } else {
598                     mtrace("success.");
599                     $usermailcount++;
601                 /// Mark post as read if forum_usermarksread is set off
602                     if (!$CFG->forum_usermarksread &&
603                         forum_tp_can_track_forums($forum->id, $userto) &&
604                         forum_tp_is_tracked($forum->id, $userto->id)) {
605                         foreach ($markread as $postinfo) {
606                             if (!forum_tp_mark_post_read($userto->id, $postinfo->post, $postinfo->forumid)) {
607                                 mtrace("Error: mod/forum/cron.php: Could not mark post $postid read for user $userto->id".
608                                      " while sending digest email.");
609                             }
610                         }
611                     }
612                 }
613             }
614         }
615     }
617     if(!empty($usermailcount)) {
618         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
619     }
621     if (!empty($realuser)) {   // Restore real USER if necessary
622         $USER = $realuser;
623     }
625     if (!empty($CFG->forum_lastreadclean)) {
626         $timenow = time();
627         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
628             set_config('forum_lastreadclean', $timenow);
629             forum_tp_clean_read_records();
630         }
631     } else {
632         set_config('forum_lastreadclean', time());
633     }
636     return true;
639 function forum_make_mail_text($course, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
640     global $CFG;
642     if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
643         error('Course Module ID was incorrect');
644     }
645     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
646     $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext);
648     $by = New stdClass;
649     $by->name = fullname($userfrom, $viewfullnames);
650     $by->date = userdate($post->modified, "", $userto->timezone);
652     $strbynameondate = get_string('bynameondate', 'forum', $by);
654     $strforums = get_string('forums', 'forum');
656     $canunsubscribe = ! forum_is_forcesubscribed($forum->id);
657     $canreply = forum_user_can_post($forum, $userto);
659     $posttext = '';
661     if (!$bare) {
662         $posttext  = "$course->shortname -> $strforums -> ".format_string($forum->name,true);
664         if ($discussion->name != $forum->name) {
665             $posttext  .= " -> ".format_string($discussion->name,true);
666         }
667     }
669     $posttext .= "\n---------------------------------------------------------------------\n";
670     $posttext .= format_string($post->subject,true);
671     if ($bare) {
672         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
673     }
674     $posttext .= "\n".$strbynameondate."\n";
675     $posttext .= "---------------------------------------------------------------------\n";
676     $posttext .= format_text_email(trusttext_strip($post->message), $post->format);
677     $posttext .= "\n\n";
678     if ($post->attachment) {
679         $post->course = $course->id;
680         $post->forum = $forum->id;
681         $posttext .= forum_print_attachments($post, "text");
682     }
683     if (!$bare && $canreply) {
684         $posttext .= "---------------------------------------------------------------------\n";
685         $posttext .= get_string("postmailinfo", "forum", $course->shortname)."\n";
686         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
687     }
688     if (!$bare && $canunsubscribe) {
689         $posttext .= "\n---------------------------------------------------------------------\n";
690         $posttext .= get_string("unsubscribe", "forum");
691         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
692     }
694     return $posttext;
697 function forum_make_mail_html($course, $forum, $discussion, $post, $userfrom, $userto) {
698     global $CFG;
700     if ($userto->mailformat != 1) {  // Needs to be HTML
701         return '';
702     }
704     $strforums = get_string('forums', 'forum');
705     $canreply = forum_user_can_post($forum, $userto);
706     $canunsubscribe = ! forum_is_forcesubscribed($forum->id);
708     $posthtml = '<head>';
709     foreach ($CFG->stylesheets as $stylesheet) {
710         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
711     }
712     $posthtml .= '</head>';
713     $posthtml .= "\n<body id=\"email\">\n\n";
715     $posthtml .= '<div class="navbar">'.
716     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$course->shortname.'</a> &raquo; '.
717     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
718     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
719     if ($discussion->name == $forum->name) {
720         $posthtml .= '</div>';
721     } else {
722         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
723                      format_string($discussion->name,true).'</a></div>';
724     }
725     $posthtml .= forum_make_mail_post($post, $userfrom, $userto, $course, false, $canreply, true, false);
727     if ($canunsubscribe) {
728         $posthtml .= '<br /><div class="unsubscribelink"><a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.
729                      get_string('unsubscribe', 'forum').'</a></div>';
730     }
732     $posthtml .= '</body>';
734     return $posthtml;
737 function forum_user_outline($course, $user, $mod, $forum) {
739     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
740         $result->info = get_string("numposts", "forum", count($posts));
742         $lastpost = array_pop($posts);
743         $result->time = $lastpost->modified;
744         return $result;
745     }
746     return NULL;
750 function forum_user_complete($course, $user, $mod, $forum) {
751     global $CFG;
753     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
754         foreach ($posts as $post) {
756             $post->forum = $forum->id;
757             forum_print_post($post, $course->id, $ownpost=false, $reply=false, $link=false, $rate=false);
758         }
760     } else {
761         echo "<p>".get_string("noposts", "forum")."</p>";
762     }
765 function forum_print_overview($courses,&$htmlarray) {
766     global $USER, $CFG;
767     $LIKE = sql_ilike();
769     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
770         return array();
771     }
773     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
774         return;
775     }
778     // get all forum logs in ONE query (much better!)
779     $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {$CFG->prefix}log l "
780         ." JOIN {$CFG->prefix}course_modules cm ON cm.id = cmid "
781         ." WHERE (";
782     foreach ($courses as $course) {
783         $sql .= '(l.course = '.$course->id.' AND l.time > '.$course->lastaccess.') OR ';
784     }
785     $sql = substr($sql,0,-3); // take off the last OR
787     $sql .= ") AND l.module = 'forum' AND action $LIKE 'add post%' "
788         ." AND userid != ".$USER->id." GROUP BY cmid,l.course,instance";
790     if (!$new = get_records_sql($sql)) {
791         $new = array(); // avoid warnings
792     }
794     // also get all forum tracking stuff ONCE.
795     $trackingforums = array();
796     foreach ($forums as $forum) {
797         if (forum_tp_can_track_forums($forum)) {
798             $trackingforums[$forum->id] = $forum;
799         }
800     }
802     if (count($trackingforums) > 0) {
803         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
804         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
805             ' FROM '.$CFG->prefix.'forum_posts p '.
806             ' JOIN '.$CFG->prefix.'forum_discussions d ON p.discussion = d.id '.
807             ' LEFT JOIN '.$CFG->prefix.'forum_read r ON r.postid = p.id AND r.userid = '.$USER->id.' WHERE (';
808         foreach ($trackingforums as $track) {
809             $sql .= '(d.forum = '.$track->id.' AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = '.get_current_group($track->course,false).')) OR ';
810         }
811         $sql = substr($sql,0,-3); // take off the last OR
812         $sql .= ') AND p.modified >= '.$cutoffdate.' AND r.id is NULL GROUP BY d.forum,d.course';
814         if (!$unread = get_records_sql($sql)) {
815             $unread = array();
816         }
817     } else {
818         $unread = array();
819     }
821     if (empty($unread) and empty($new)) {
822         return;
823     }
825     $strforum = get_string('modulename','forum');
826     $strnumunread = get_string('overviewnumunread','forum');
827     $strnumpostssince = get_string('overviewnumpostssince','forum');
829     foreach ($forums as $forum) {
830         $str = '';
831         $count = 0;
832         $thisunread = 0;
833         $showunread = false;
834         // either we have something from logs, or trackposts, or nothing.
835         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
836             $count = $new[$forum->id]->count;
837         }
838         if (array_key_exists($forum->id,$unread)) {
839             $thisunread = $unread[$forum->id]->count;
840             $showunread = true;
841         }
842         if ($count > 0 || $thisunread > 0) {
843             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
844                 $forum->name.'</a></div>';
845             $str .= '<div class="info">';
846             $str .= $count.' '.$strnumpostssince;
847             if (!empty($showunread)) {
848                 $str .= '<br />'.$thisunread .' '.$strnumunread;
849             }
850             $str .= '</div></div>';
851         }
852         if (!empty($str)) {
853             if (!array_key_exists($forum->course,$htmlarray)) {
854                 $htmlarray[$forum->course] = array();
855             }
856             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
857                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
858             }
859             $htmlarray[$forum->course]['forum'] .= $str;
860         }
861     }
864 function forum_print_recent_activity($course, $isteacher, $timestart) {
865 /// Given a course and a date, prints a summary of all the new
866 /// messages posted in the course since that date
868     global $CFG;
869     $LIKE = sql_ilike();
871     $heading = false;
872     $content = false;
874     if (!$logs = get_records_select('log', 'time > \''.$timestart.'\' AND '.
875                                            'course = \''.$course->id.'\' AND '.
876                                            'module = \'forum\' AND '.
877                                            'action '.$LIKE.' \'add %\' ', 'time ASC')){
878         return false;
879     }
881     $strftimerecent = get_string('strftimerecent');
883     $mygroupid = mygroupid($course->id);
884     $groupmode = array();   /// To cache group modes
886     foreach ($logs as $log) {
887         //Get post info, I'll need it later
888         if ($post = forum_get_post_from_log($log)) {
889             //Create a temp valid module structure (course,id)
890             $tempmod->course = $log->course;
891             $tempmod->id = $post->forum;
892             //Obtain the visible property from the instance
893             $coursecontext = get_context_instance(CONTEXT_COURSE, $tempmod->course);
894             $modvisible = instance_is_visible('forum', $tempmod)
895                             || has_capability('moodle/course:viewhiddenactivities', $coursecontext);
896         }
898         //Only if the post exists and mod is visible
899         if ($post && $modvisible) {
901             if (!isset($cm[$post->forum])) {
902                 $cm[$post->forum] = get_coursemodule_from_instance('forum', $post->forum, $course->id);
903             }
904             $modcontext = get_context_instance(CONTEXT_MODULE, $cm[$post->forum]->id);
906             /// Check whether this is belongs to a discussion in a group that
907             /// should NOT be accessible to the current user
908             if (!has_capability('moodle/site:accessallgroups', $modcontext)
909                     && $post->groupid != -1) {   /// Open discussions have groupid -1
911                 $groupmode[$post->forum] = groupmode($course, $cm[$post->forum]);
913                 if ($groupmode[$post->forum]) {
914                     //hope i didn't break anything
915                     if (!@in_array($mygroupid, $post->groupid))/*$mygroupid != $post->groupid*/{
916                         continue;
917                     }
918                 }
919             }
921             if (! $heading) {
922                 print_headline(get_string('newforumposts', 'forum').':');
923                 $heading = true;
924                 $content = true;
925             }
926             $date = userdate($post->modified, $strftimerecent);
928             $subjectclass = ($log->action == 'add discussion') ? ' bold' : '';
930             echo '<div class="head">'.
931                    '<div class="date">'.$date.'</div>'.
932                    '<div class="name">'.fullname($post, has_capability('moodle/site:viewfullnames', $coursecontext)).'</div>'.
933                  '</div>';
934             echo '<div class="info'.$subjectclass.'">';
935             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/'.str_replace('&', '&amp;', $log->url).'">';
936             $post->subject = break_up_long_words(format_string($post->subject,true));
937             echo $post->subject;
938             echo '</a>"</div>';
939         }
940     }
941     return $content;
945 function forum_grades($forumid) {
946 /// Must return an array of grades, indexed by user, and a max grade.
948     if (!$forum = get_record("forum", "id", $forumid)) {
949         return false;
950     }
951     if (!$forum->assessed) {
952         return false;
953     }
954     $scalemenu = make_grades_menu($forum->scale);
956     $currentuser = 0;
957     $ratingsuser = array();
959     if ($ratings = forum_get_user_grades($forumid)) {
960         foreach ($ratings as $rating) {     // Ordered by user
961             if ($currentuser and $rating->userid != $currentuser) {
962                 if (!empty($ratingsuser)) {
963                     if ($forum->scale < 0) {
964                         $return->grades[$currentuser] = forum_get_ratings_mean(0, $scalemenu, $ratingsuser);
965                         $return->grades[$currentuser] .= "<br />".forum_get_ratings_summary(0, $scalemenu, $ratingsuser);
966                     } else {
967                         $total = 0;
968                         $count = 0;
969                         foreach ($ratingsuser as $ra) {
970                             $total += $ra;
971                             $count ++;
972                         }
973                         $return->grades[$currentuser] = format_float($total/$count, 2);
974                     }
975                 } else {
976                     $return->grades[$currentuser] = "";
977                 }
978                 $ratingsuser = array();
979             }
980             $ratingsuser[] = $rating->rating;
981             $currentuser = $rating->userid;
982         }
983         if (!empty($ratingsuser)) {
984             if ($forum->scale < 0) {
985                 $return->grades[$currentuser] = forum_get_ratings_mean(0, $scalemenu, $ratingsuser);
986                 $return->grades[$currentuser] .= "<br />".forum_get_ratings_summary(0, $scalemenu, $ratingsuser);
987             } else {
988                 $total = 0;
989                 $count = 0;
990                 foreach ($ratingsuser as $ra) {
991                     $total += $ra;
992                     $count ++;
993                 }
994                 $return->grades[$currentuser] = format_float((float)$total/(float)$count, 2);
995             }
996         } else {
997             $return->grades[$currentuser] = "";
998         }
999     } else {
1000         $return->grades = array();
1001     }
1003     if ($forum->scale < 0) {
1004         $return->maxgrade = "";
1005     } else {
1006         $return->maxgrade = $forum->scale;
1007     }
1008     return $return;
1011 function forum_get_participants($forumid) {
1012 //Returns the users with data in one forum
1013 //(users with records in forum_subscriptions, forum_posts and forum_ratings, students)
1015     global $CFG;
1017     //Get students from forum_subscriptions
1018     $st_subscriptions = get_records_sql("SELECT DISTINCT u.id, u.id
1019                                          FROM {$CFG->prefix}user u,
1020                                               {$CFG->prefix}forum_subscriptions s
1021                                          WHERE s.forum = '$forumid' and
1022                                                u.id = s.userid");
1023     //Get students from forum_posts
1024     $st_posts = get_records_sql("SELECT DISTINCT u.id, u.id
1025                                  FROM {$CFG->prefix}user u,
1026                                       {$CFG->prefix}forum_discussions d,
1027                                       {$CFG->prefix}forum_posts p
1028                                  WHERE d.forum = '$forumid' and
1029                                        p.discussion = d.id and
1030                                        u.id = p.userid");
1032     //Get students from forum_ratings
1033     $st_ratings = get_records_sql("SELECT DISTINCT u.id, u.id
1034                                    FROM {$CFG->prefix}user u,
1035                                         {$CFG->prefix}forum_discussions d,
1036                                         {$CFG->prefix}forum_posts p,
1037                                         {$CFG->prefix}forum_ratings r
1038                                    WHERE d.forum = '$forumid' and
1039                                          p.discussion = d.id and
1040                                          r.post = p.id and
1041                                          u.id = r.userid");
1043     //Add st_posts to st_subscriptions
1044     if ($st_posts) {
1045         foreach ($st_posts as $st_post) {
1046             $st_subscriptions[$st_post->id] = $st_post;
1047         }
1048     }
1049     //Add st_ratings to st_subscriptions
1050     if ($st_ratings) {
1051         foreach ($st_ratings as $st_rating) {
1052             $st_subscriptions[$st_rating->id] = $st_rating;
1053         }
1054     }
1055     //Return st_subscriptions array (it contains an array of unique users)
1056     return ($st_subscriptions);
1059 function forum_scale_used ($forumid,$scaleid) {
1060 //This function returns if a scale is being used by one forum
1062     $return = false;
1064     $rec = get_record("forum","id","$forumid","scale","-$scaleid");
1066     if (!empty($rec) && !empty($scaleid)) {
1067         $return = true;
1068     }
1070     return $return;
1073 /// SQL FUNCTIONS ///////////////////////////////////////////////////////////
1075 function forum_get_post_full($postid) {
1076 /// Gets a post with all info ready for forum_print_post
1077 /// Most of these joins are just to get the forum id
1078     global $CFG;
1080     return get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture
1081                             FROM {$CFG->prefix}forum_posts p
1082                        LEFT JOIN {$CFG->prefix}forum_discussions d ON p.discussion = d.id
1083                        LEFT JOIN {$CFG->prefix}user u ON p.userid = u.id
1084                            WHERE p.id = '$postid'");
1087 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1088 /// Gets posts with all info ready for forum_print_post
1089 /// We pass forumid in because we always know it so no need to make a
1090 /// complicated join to find it out.
1091     global $CFG;
1093     return get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture
1094                               FROM {$CFG->prefix}forum_posts p
1095                          LEFT JOIN {$CFG->prefix}user u ON p.userid = u.id
1096                              WHERE p.discussion = $discussion
1097                                AND p.parent > 0 $sort");
1100 function forum_get_child_posts($parent, $forumid) {
1101 /// Gets posts with all info ready for forum_print_post
1102 /// We pass forumid in because we always know it so no need to make a
1103 /// complicated join to find it out.
1104     global $CFG;
1106     return get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture
1107                               FROM {$CFG->prefix}forum_posts p
1108                          LEFT JOIN {$CFG->prefix}user u ON p.userid = u.id
1109                              WHERE p.parent = '$parent'
1110                           ORDER BY p.created ASC");
1113 /**
1114  * An array of forum objects that the user is allowed to read/search through.
1115  * @param $userid
1116  * @param $courseid - if 0, we look for forums throughout the whole site.
1117  * @return array of forum objects, or false if no matches
1118  *         Forum objects have the following attributes:
1119  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1120  *         viewhiddentimedposts
1121  */
1122 function forum_get_readable_forums($userid, $courseid=0) {
1124     global $CFG, $USER;
1126     if (!$forummod = get_record('modules', 'name', 'forum')) {
1127         error('The forum module is not installed');
1128     }
1130     if ($courseid) {
1131         $courses = get_records('course', 'id', $courseid);
1132     } else {
1133         $courses = get_records('course');
1134     }
1135     if (!$courses) {
1136         return false;
1137     }
1139     $readableforums = array();
1141     foreach($courses as $course) {
1142         $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
1144         if (has_capability('moodle/course:viewhiddenactivities', $coursecontext)) {
1145             $selecthidden = ' AND cm.visible = 1';
1146         } else {
1147             $selecthidden = '';
1148         }
1150         $selectforums = "SELECT f.id AS id,
1151                                 f.name AS name,
1152                                 f.type AS type,
1153                                 f.course AS course,
1154                                 cm.id AS cmid,
1155                                 cm.visible AS cmvisible,
1156                                 cm.groupmode AS cmgroupmode
1157                            FROM {$CFG->prefix}course_modules cm,
1158                                 {$CFG->prefix}forum f
1159                           WHERE cm.instance = f.id
1160                             AND cm.course = {$course->id}
1161                             AND cm.module = {$forummod->id}
1162                                 $selecthidden
1163                                 ORDER BY f.name ASC";
1165         if ($forums = get_records_sql($selectforums)) {
1167             $group = user_group($course->id, $userid);
1169             foreach ($forums as $forum) {
1170                 $forumcontext = get_context_instance(CONTEXT_MODULE, $forum->cmid);
1172                 // Evaluate groupmode.
1173                 $cm = new object;
1174                 $cm->id = $forum->cmid;
1175                 $cm->groupmode = $forum->cmgroupmode;
1176                 $forum->cmgroupmode = groupmode($course, $cm);
1178                 if ($forum->cmgroupmode == SEPARATEGROUPS
1179                         && !has_capability('moodle/site:accessallgroups', $forumcontext)) {
1180                     $forum->accessallgroups = false;
1181                     $forum->accessgroup = $group->id;  // The user can only access
1182                                                        // discussions for this group.
1183                 } else {
1184                     $forum->accessallgroups = true;
1185                 }
1187                 if (has_capability('mod/forum:viewdiscussion', $forumcontext)) {
1189                     $forum->viewhiddentimedposts
1190                         = has_capability('mod/forum:viewhiddentimedposts', $forumcontext);
1192                     if ($forum->type == 'qanda'
1193                             && !has_capability('mod/forum:viewqandawithoutposting', $forumcontext)) {
1195                         // We need to check whether the user has posted in the qanda forum.
1196                         $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1197                                                             // the user is allowed to see in this forum.
1199                         if ($discussionspostedin =
1200                                     forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1201                             foreach ($discussionspostedin as $d) {
1202                                 array_push($forum->onlydiscussions, $d->id);
1203                             }
1204                         }
1205                     }
1206                     array_push($readableforums, $forum);
1207                 }
1208             }
1209         }
1210     } // End foreach $courses
1212     //print_object($courses);
1213     //print_object($readableforums);
1215     return $readableforums;
1218 /**
1219  * Returns a list of posts found using an array of search terms.
1220  * @param $searchterms - array of search terms, e.g. word +word -word
1221  * @param $courseid - if 0, we search through the whole site
1222  * @param $page
1223  * @param $recordsperpage=50
1224  * @param &$totalcount
1225  * @param $extrasql
1226  * @return array of posts found
1227  */
1228 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1229                             &$totalcount, $extrasql='') {
1230     global $CFG, $USER;
1231     require_once($CFG->libdir.'/searchlib.php');
1233     $forums = forum_get_readable_forums($USER->id, $courseid);
1235     if (count($forums) == 0) {
1236         return false;
1237     }
1239     for ($i=0; $i<count($forums); $i++) {
1240         if ($i == 0) {
1241             $selectdiscussion = " ((d.forum = {$forums[$i]->id}";
1242         } else {
1243             $selectdiscussion .= " OR (d.forum = {$forums[$i]->id}";
1244         }
1245         if (!empty($CFG->forum_enabletimedposts) && !$forums[$i]->viewhiddentimedposts) {
1246             $now = time();
1247             $selectdiscussion .= " AND ( d.userid = {$USER->id}
1248                                    OR ((d.timestart = 0 OR d.timestart <= $now)
1249                                    AND (d.timeend = 0 OR d.timeend > $now)) )";
1250         }
1251         if ($forums[$i]->type == 'qanda' && isset($forums[$i]->onlydiscussions)) {
1252             // This is a qanda forum.
1253             if (is_array($forums[$i]->onlydiscussions)) {
1254                 // Show question posts as well as posts from discussions in
1255                 // which the user has posted a reply.
1256                 $onlydiscussions = implode(' OR d.id = ', $forums[$i]->onlydiscussions);
1257                 $selectdiscussion .= " AND ((d.id = $onlydiscussions) OR p.parent = 0)";
1258             } else {
1259                 // Show only the question posts.
1260                 $selectdiscussion .= ' AND (p.parent = 0)';
1261             }
1262         }
1263         if (!$forums[$i]->accessallgroups) {
1264             if (!empty($forums[$i]->accessgroup)) {
1265                 $selectdiscussion .= " AND (d.groupid = {$forums[$i]->accessgroup}";
1266                 $selectdiscussion .= ' OR d.groupid = -1)';  // -1 means open for all groups.
1267             } else {
1268                 // User isn't in any group. Only search discussions that are
1269                 // open to all groups.
1270                 $selectdiscussion .= ' AND d.groupid = -1';
1271             }
1272         }
1273         $selectdiscussion .= ")\n";
1274     }
1275     $selectdiscussion .= ")";
1278     // Some differences SQL
1279     $LIKE = sql_ilike();
1280     $NOTLIKE = 'NOT ' . $LIKE;
1281     if ($CFG->dbfamily == 'postgres') {
1282         $REGEXP = '~*';
1283         $NOTREGEXP = '!~*';
1284     } else {
1285         $REGEXP = 'REGEXP';
1286         $NOTREGEXP = 'NOT REGEXP';
1287     }
1289     $messagesearch = '';
1290     $searchstring = '';
1292     // Need to concat these back together for parser to work.
1293     foreach($searchterms as $searchterm){
1294         if ($searchstring != '') {
1295             $searchstring .= ' ';
1296         }
1297         $searchstring .= $searchterm;
1298     }
1300     // We need to allow quoted strings for the search. The quotes *should* be stripped
1301     // by the parser, but this should be examined carefully for security implications.
1302     $searchstring = str_replace("\\\"","\"",$searchstring);
1303     $parser = new search_parser();
1304     $lexer = new search_lexer($parser);
1306     if ($lexer->parse($searchstring)) {
1307         $parsearray = $parser->get_parsed_array();
1308         $messagesearch = search_generate_SQL($parsearray, 'p.message', 'p.subject',
1309                                              'p.userid', 'u.id', 'u.firstname',
1310                                              'u.lastname', 'p.modified', 'd.forum');
1311     }
1313     $fromsql = "{$CFG->prefix}forum_posts p,
1314                   {$CFG->prefix}forum_discussions d,
1315                   {$CFG->prefix}user u";
1317     $selectsql = " $messagesearch
1318                AND p.discussion = d.id
1319                AND p.userid = u.id
1320                AND $selectdiscussion
1321                    $extrasql";
1323     $countsql = "SELECT COUNT(*)
1324                    FROM $fromsql
1325                   WHERE $selectsql";
1327     $searchsql = "SELECT p.*,
1328                          d.forum,
1329                          u.firstname,
1330                          u.lastname,
1331                          u.email,
1332                          u.picture
1333                     FROM $fromsql
1334                    WHERE $selectsql
1335                 ORDER BY p.modified DESC";
1337     $totalcount = count_records_sql($countsql);
1339     return get_records_sql($searchsql, $limitfrom, $limitnum);
1342 function forum_get_ratings($postid, $sort="u.firstname ASC") {
1343 /// Returns a list of ratings for a particular post - sorted.
1344     global $CFG;
1345     return get_records_sql("SELECT u.*, r.rating, r.time
1346                               FROM {$CFG->prefix}forum_ratings r,
1347                                    {$CFG->prefix}user u
1348                              WHERE r.post = '$postid'
1349                                AND r.userid = u.id
1350                              ORDER BY $sort");
1353 function forum_get_unmailed_posts($starttime, $endtime) {
1354 /// Returns a list of all new posts that have not been mailed yet
1355     global $CFG;
1356     $now = time();
1357     return get_records_sql("SELECT p.*, d.course
1358                               FROM {$CFG->prefix}forum_posts p,
1359                                    {$CFG->prefix}forum_discussions d
1360                              WHERE p.mailed = 0
1361                                AND (p.created >= '$starttime' OR d.timestart > 0)
1362                                AND (p.created < '$endtime' OR p.mailnow = 1)
1363                                AND p.discussion = d.id
1364                                AND ((d.timestart = 0 OR d.timestart <= '$now')
1365                                AND (d.timeend = 0 OR d.timeend > '$now'))
1366                           ORDER BY p.modified ASC");
1369 function forum_mark_old_posts_as_mailed($endtime) {
1370 /// Marks posts before a certain time as being mailed already
1371     global $CFG;
1372 /// Find out posts those are not showing immediately so we can exclude them
1373     $now = time();
1374     $delayed_posts = get_records_sql("SELECT p.id, p.discussion
1375                                         FROM {$CFG->prefix}forum_posts p,
1376                                              {$CFG->prefix}forum_discussions d
1377                                        WHERE p.mailed = 0
1378                                          AND p.discussion = d.id
1379                                          AND d.timestart > '$now'");
1380     $delayed_ids = array();
1381     if ($delayed_posts) {
1382         foreach ($delayed_posts as $post) {
1383             $delayed_ids[] = $post->id;
1384         }
1385     } else {
1386         $delayed_ids[] = 0;
1387     }
1388     return execute_sql("UPDATE {$CFG->prefix}forum_posts
1389                            SET mailed = '1'
1390                          WHERE id NOT IN (".implode(',',$delayed_ids).")
1391                            AND (created < '$endtime' OR mailnow = 1)
1392                            AND mailed ='0'", false);
1395 function forum_get_user_posts($forumid, $userid) {
1396 /// Get all the posts for a user in a forum suitable for forum_print_post
1397     global $CFG;
1399     return get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture
1400                               FROM {$CFG->prefix}forum f,
1401                                    {$CFG->prefix}forum_discussions d,
1402                                    {$CFG->prefix}forum_posts p,
1403                                    {$CFG->prefix}user u
1404                              WHERE f.id = '$forumid'
1405                                AND d.forum = f.id
1406                                AND p.discussion = d.id
1407                                AND p.userid = '$userid'
1408                                AND p.userid = u.id
1409                           ORDER BY p.modified ASC");
1412 function forum_get_post_from_log($log) {
1413 /// Given a log entry, return the forum post details for it.
1414     global $CFG;
1416     if ($log->action == "add post") {
1418         return get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1419                                            u.firstname, u.lastname, u.email, u.picture
1420                                  FROM {$CFG->prefix}forum_discussions d,
1421                                       {$CFG->prefix}forum_posts p,
1422                                       {$CFG->prefix}forum f,
1423                                       {$CFG->prefix}user u
1424                                 WHERE p.id = '$log->info'
1425                                   AND d.id = p.discussion
1426                                   AND p.userid = u.id
1427                                   AND u.deleted <> '1'
1428                                   AND f.id = d.forum");
1431     } else if ($log->action == "add discussion") {
1433         return get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1434                                            u.firstname, u.lastname, u.email, u.picture
1435                                  FROM {$CFG->prefix}forum_discussions d,
1436                                       {$CFG->prefix}forum_posts p,
1437                                       {$CFG->prefix}forum f,
1438                                       {$CFG->prefix}user u
1439                                 WHERE d.id = '$log->info'
1440                                   AND d.firstpost = p.id
1441                                   AND p.userid = u.id
1442                                   AND u.deleted <> '1'
1443                                   AND f.id = d.forum");
1444     }
1445     return NULL;
1448 function forum_get_firstpost_from_discussion($discussionid) {
1449 /// Given a discussion id, return the first post from the discussion
1450     global $CFG;
1452     return get_record_sql("SELECT p.*
1453                              FROM {$CFG->prefix}forum_discussions d,
1454                                   {$CFG->prefix}forum_posts p
1455                             WHERE d.id = '$discussionid'
1456                               AND d.firstpost = p.id ");
1460 function forum_get_user_grades($forumid) {
1461 /// Get all user grades for a forum
1462     global $CFG;
1464     return get_records_sql("SELECT r.id, p.userid, r.rating
1465                               FROM {$CFG->prefix}forum_discussions d,
1466                                    {$CFG->prefix}forum_posts p,
1467                                    {$CFG->prefix}forum_ratings r
1468                              WHERE d.forum = '$forumid'
1469                                AND p.discussion = d.id
1470                                AND r.post = p.id
1471                              ORDER by p.userid ");
1475 function forum_count_discussion_replies($forum='0', $course='0', $user='0') {
1476 // Returns an array of counts of replies to each discussion (optionally in one forum or course and/or user)
1477     global $CFG;
1479     $forumselect = $courseselect = $userselect = '';
1481     if ($forum) {
1482         $forumselect = " AND d.forum = '$forum'";
1483     }
1484     if ($course) {
1485         $courseselect = " AND d.course = '$course'";
1486     }
1487     if ($user) {
1488         $userselect = " AND d.userid = '$user'";
1489     }
1490     return get_records_sql("SELECT p.discussion, (count(*)) as replies, max(p.id) as lastpostid
1491                               FROM {$CFG->prefix}forum_posts p,
1492                                    {$CFG->prefix}forum_discussions d
1493                              WHERE p.parent > 0 $forumselect $courseselect $userselect
1494                                AND p.discussion = d.id
1495                           GROUP BY p.discussion");
1498 function forum_count_unrated_posts($discussionid, $userid) {
1499 // How many unrated posts are in the given discussion for a given user?
1500     global $CFG;
1501     if ($posts = get_record_sql("SELECT count(*) as num
1502                                    FROM {$CFG->prefix}forum_posts
1503                                   WHERE parent > 0
1504                                     AND discussion = '$discussionid'
1505                                     AND userid <> '$userid' ")) {
1507         if ($rated = get_record_sql("SELECT count(*) as num
1508                                        FROM {$CFG->prefix}forum_posts p,
1509                                             {$CFG->prefix}forum_ratings r
1510                                       WHERE p.discussion = '$discussionid'
1511                                         AND p.id = r.post
1512                                         AND r.userid = '$userid'")) {
1513             $difference = $posts->num - $rated->num;
1514             if ($difference > 0) {
1515                 return $difference;
1516             } else {
1517                 return 0;    // Just in case there was a counting error
1518             }
1519         } else {
1520             return $posts->num;
1521         }
1522     } else {
1523         return 0;
1524     }
1527 function forum_get_discussions($forum="0", $forumsort="d.timemodified DESC",
1528                                $user=0, $fullpost=true, $visiblegroups=-1, $limit=0, $userlastmodified=false) {
1529 /// Get all discussions in a forum
1530     global $CFG, $USER;
1532     $timelimit = '';
1534     if (!empty($CFG->forum_enabletimedposts)) {
1536         if (!$cm = get_coursemodule_from_instance('forum', $forum)) {
1537             error('Course Module ID was incorrect');
1538         }
1539         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
1541         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
1542             $now = time();
1543             $timelimit = " AND ((d.timestart = 0 OR d.timestart <= '$now') AND (d.timeend = 0 OR d.timeend > '$now')";
1544             if (!empty($USER->id)) {
1545                 $timelimit .= " OR d.userid = '$USER->id'";
1546             }
1547             $timelimit .= ')';
1548         }
1549     }
1551     if ($user) {
1552         $userselect = " AND u.id = '$user' ";
1553     } else {
1554         $userselect = "";
1555     }
1557     $limitfrom = 0;
1558     $limitnum = 0;
1559     if ($limit) {
1560         $limitnum = $limit;
1561     }
1563     if ($visiblegroups == -1) {
1564         $groupselect = "";
1565     } else  {
1566         $groupselect = " AND (d.groupid = '$visiblegroups' OR d.groupid = '-1') ";
1567     }
1569     if (empty($forumsort)) {
1570         $forumsort = "d.timemodified DESC";
1571     }
1572     if (empty($fullpost)) {
1573         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
1574     } else {
1575         $postdata = "p.*";
1576     }
1578     if (empty($userlastmodified)) {  // We don't need to know this
1579         $umfields = '';
1580         $umtable = '';
1581     } else {
1582         $umfields = ', um.firstname AS umfirstname, um.lastname AS umlastname';
1583         $umtable = ' LEFT JOIN '.$CFG->prefix.'user um on (d.usermodified = um.id)';
1584     }
1586     //TODO: there must be a nice way to do this that keeps both postgres and mysql 3.2x happy but I can't find it right now.
1587     if ($CFG->dbfamily == 'postgres' || $CFG->dbfamily == 'mssql' || $CFG->dbfamily == 'oracle') {
1588         return get_records_sql("SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid,
1589                                    u.firstname, u.lastname, u.email, u.picture $umfields
1590                               FROM {$CFG->prefix}forum_discussions d
1591                               JOIN {$CFG->prefix}forum_posts p ON p.discussion = d.id
1592                               JOIN {$CFG->prefix}user u ON p.userid = u.id
1593                                    $umtable
1594                              WHERE d.forum = '$forum'
1595                                AND p.parent = 0
1596                                    $timelimit $groupselect $userselect
1597                           ORDER BY $forumsort", $limitfrom, $limitnum);
1598     } else { // MySQL query. TODO: Check if this is needed (MySQL 4.1 should work with the above query)
1599         return get_records_sql("SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid,
1600                                    u.firstname, u.lastname, u.email, u.picture $umfields
1601                               FROM ({$CFG->prefix}forum_posts p,
1602                                    {$CFG->prefix}user u,
1603                                    {$CFG->prefix}forum_discussions d)
1604                                    $umtable
1605                              WHERE d.forum = '$forum'
1606                                AND p.discussion = d.id
1607                                AND p.parent = 0
1608                                AND p.userid = u.id $timelimit $groupselect $userselect
1609                           ORDER BY $forumsort", $limitfrom, $limitnum);
1610     }
1615 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
1616 /// Get all discussions started by a particular user in a course (or group)
1617 /// This function no longer used ...
1618     global $CFG;
1620     if ($groupid) {
1621         $groupselect = " AND d.groupid = '$groupid' ";
1622     } else  {
1623         $groupselect = "";
1624     }
1626     return get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture,
1627                                    f.type as forumtype, f.name as forumname, f.id as forumid
1628                               FROM {$CFG->prefix}forum_discussions d,
1629                                    {$CFG->prefix}forum_posts p,
1630                                    {$CFG->prefix}user u,
1631                                    {$CFG->prefix}forum f
1632                              WHERE d.course = '$courseid'
1633                                AND p.discussion = d.id
1634                                AND p.parent = 0
1635                                AND p.userid = u.id
1636                                AND u.id = '$userid'
1637                                AND d.forum = f.id $groupselect
1638                           ORDER BY p.created DESC");
1641 function forum_subscribed_users($course, $forum, $groupid=0, $cache=false) {
1642 /// Returns list of user objects that are subscribed to this forum
1643     global $CFG;
1645     static $resultscache = array();
1647     if ($cache && isset($resultscache[$forum->id][$groupid])) {
1648         return $resultscache[$forum->id][$groupid];
1649     }
1651     if ($groupid) {
1652         $grouptables = ', '. groups_members_from_sql();
1653         $groupselect = 'AND'.groups_members_where_sql($groupid, 'u.id');
1654     } else  {
1655         $grouptables = '';
1656         $groupselect = '';
1657     }
1659     if (forum_is_forcesubscribed($forum->id)) {
1660         $results = get_course_users($course->id);     // Otherwise get everyone in the course
1661     } else {
1662         $results = get_records_sql("SELECT u.id, u.username, u.firstname, u.lastname, u.maildisplay, u.mailformat, u.maildigest, u.emailstop,
1663                                    u.email, u.city, u.country, u.lastaccess, u.lastlogin, u.picture, u.timezone, u.lang, u.trackforums
1664                               FROM {$CFG->prefix}user u,
1665                                    {$CFG->prefix}forum_subscriptions s $grouptables
1666                              WHERE s.forum = '$forum->id'
1667                                AND s.userid = u.id
1668                                AND u.deleted <> 1  $groupselect
1669                           ORDER BY u.email ASC");
1670     }
1671         // Guest user should never be subscribed to a forum.
1672         if ($guest = guest_user()) {
1673                 unset($results[$guest->id]);
1674         }
1676     if ($cache) {
1677         $resultscache[$forum->id][$groupid] = $results;
1678     }
1680     return $results;
1685 /// OTHER FUNCTIONS ///////////////////////////////////////////////////////////
1688 function forum_get_course_forum($courseid, $type) {
1689 // How to set up special 1-per-course forums
1690     global $CFG;
1692     if ($forums = get_records_select("forum", "course = '$courseid' AND type = '$type'", "id ASC")) {
1693         // There should always only be ONE, but with the right combination of
1694         // errors there might be more.  In this case, just return the oldest one (lowest ID).
1695         foreach ($forums as $forum) {
1696             return $forum;   // ie the first one
1697         }
1698     }
1700     // Doesn't exist, so create one now.
1701     $forum->course = $courseid;
1702     $forum->type = "$type";
1703     switch ($forum->type) {
1704         case "news":
1705             $forum->name  = addslashes(get_string("namenews", "forum"));
1706             $forum->intro = addslashes(get_string("intronews", "forum"));
1707             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
1708             $forum->assessed = 0;
1709             if ($courseid == SITEID) {
1710                 $forum->name  = get_string("sitenews");
1711                 $forum->forcesubscribe = 0;
1712             }
1713             break;
1714         case "social":
1715             $forum->name  = addslashes(get_string("namesocial", "forum"));
1716             $forum->intro = addslashes(get_string("introsocial", "forum"));
1717             $forum->assessed = 0;
1718             $forum->forcesubscribe = 0;
1719             break;
1720         default:
1721             notify("That forum type doesn't exist!");
1722             return false;
1723             break;
1724     }
1726     $forum->timemodified = time();
1727     $forum->id = insert_record("forum", $forum);
1729     if (! $module = get_record("modules", "name", "forum")) {
1730         notify("Could not find forum module!!");
1731         return false;
1732     }
1733     $mod->course = $courseid;
1734     $mod->module = $module->id;
1735     $mod->instance = $forum->id;
1736     $mod->section = 0;
1737     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
1738         notify("Could not add a new course module to the course '$course->fullname'");
1739         return false;
1740     }
1741     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
1742         notify("Could not add the new course module to that section");
1743         return false;
1744     }
1745     if (! set_field("course_modules", "section", $sectionid, "id", $mod->coursemodule)) {
1746         notify("Could not update the course module with the correct section");
1747         return false;
1748     }
1749     include_once("$CFG->dirroot/course/lib.php");
1750     rebuild_course_cache($courseid);
1752     return get_record("forum", "id", "$forum->id");
1756 function forum_make_mail_post(&$post, $user, $touser, $course,
1757                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
1759     // Given the data about a posting, builds up the HTML to display it and
1760     // returns the HTML in a string.  This is designed for sending via HTML email.
1762     global $CFG;
1764     static $formattedtext;        // Cached version of formatted text for a post
1765     static $formattedtextid;      // The ID number of the post
1767     $post->forum = get_field('forum_discussions', 'forum', 'id', $post->discussion);
1769     if (!$cm = get_coursemodule_from_instance('forum', $post->forum)) {
1770         mtrace('Course Module ID was incorrect');
1771     }
1772     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
1775     if (empty($formattedtextid) or $formattedtextid != $post->id) {    // Recalculate the formatting
1776         $options = new Object;
1777         $options->para = true;
1778         $formattedtext = format_text(trusttext_strip($post->message), $post->format, $options, $course->id);
1779         $formattedtextid = $post->id;
1780     }
1782     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
1784     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
1785     $output .= print_user_picture($user->id, $course->id, $user->picture, false, true);
1786     $output .= '</td>';
1788     if ($post->parent) {
1789         $output .= '<td class="topic">';
1790     } else {
1791         $output .= '<td class="topic starter">';
1792     }
1793     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
1795     $fullname = fullname($user, has_capability('moodle/site:viewfullnames', $modcontext, $touser->id));
1796     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$user->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
1797     $by->date = userdate($post->modified, '', $touser->timezone);
1798     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
1800     $output .= '</td></tr>';
1802     $output .= '<tr><td class="left side" valign="top">';
1803     if ($group = user_group($course->id, $user->id)) {
1804         $output .= print_group_picture($group, $course->id, false, true, true);
1805     } else {
1806         $output .= '&nbsp;';
1807     }
1809     $output .= '</td><td class="content">';
1811     if ($post->attachment) {
1812         $post->course = $course->id;
1813         $output .= '<div class="attachments">';
1814         $output .= forum_print_attachments($post, 'html');
1815         $output .= "</div>";
1816     }
1818     $output .= $formattedtext;
1820 /// Commands
1821     $commands = array();
1823     if ($post->parent) {
1824         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
1825                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
1826     }
1828     if ($reply) {
1829         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
1830                       get_string('reply', 'forum').'</a>';
1831     }
1833     $output .= '<div class="commands">';
1834     $output .= implode(' | ', $commands);
1835     $output .= '</div>';
1837 /// Context link to post if required
1838     if ($link) {
1839         $output .= '<div class="link">';
1840         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
1841                      get_string('postincontext', 'forum').'</a>';
1842         $output .= '</div>';
1843     }
1845     if ($footer) {
1846         $output .= '<div class="footer">'.$footer.'</div>';
1847     }
1848     $output .= '</td></tr></table>'."\n\n";
1850     return $output;
1854 function forum_print_post(&$post, $courseid, $ownpost=false, $reply=false, $link=false,
1855                           $ratings=NULL, $footer="", $highlight="", $post_read=-99) {
1857     global $USER, $CFG, $SESSION;
1859     static $stredit, $strdelete, $strreply, $strparent, $strprune;
1860     static $strpruneheading, $threadedmode;
1861     static $strmarkread, $strmarkunread, $istracked;
1864     $discussion = get_record('forum_discussions', 'id', $post->discussion);
1865     if (!$cm = get_coursemodule_from_instance('forum', $discussion->forum)) {
1866         error('Course Module ID was incorrect');
1867     }
1868     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
1871     if (!forum_user_can_see_post($post->forum,$post->discussion,$post)) {
1872         if (empty($SESSION->forum_search)) {
1873             // just viewing, return
1874             return;
1875         }
1876         echo '<a id="p'.$post->id.'"></a>';
1877         echo '<table cellspacing="0" class="forumpost">';
1878         echo '<tr class="header"><td class="picture left">';
1879         //        print_user_picture($post->userid, $courseid, $post->picture);
1880         echo '</td>';
1881         if ($post->parent) {
1882             echo '<td class="topic">';
1883         } else {
1884             echo '<td class="topic starter">';
1885         }
1886         echo '<div class="subject">'.get_string('forumsubjecthidden','forum').'</div>';
1887         echo '<div class="author">';
1888         print_string('forumauthorhidden','forum');
1889         echo '</div></td></tr>';
1891         echo '<tr><td class="left side">';
1892         echo '&nbsp;';
1894         /// Actual content
1896         echo '</td><td class="content">'."\n";
1897         echo get_string('forumbodyhidden','forum');
1898         echo '</td></tr></table>';
1899         return;
1900     }
1902     if (empty($stredit)) {
1903         $stredit = get_string('edit', 'forum');
1904         $strdelete = get_string('delete', 'forum');
1905         $strreply = get_string('reply', 'forum');
1906         $strparent = get_string('parent', 'forum');
1907         $strpruneheading = get_string('pruneheading', 'forum');
1908         $strprune = get_string('prune', 'forum');
1909         $threadedmode = (!empty($USER->mode) and ($USER->mode == FORUM_MODE_THREADED));
1910         $strmarkread = get_string('markread', 'forum');
1911         $strmarkunread = get_string('markunread', 'forum');
1913         if (!empty($post->forum)) {
1914             $istracked = (forum_tp_can_track_forums($post->forum) &&
1915                           forum_tp_is_tracked($post->forum));
1916         } else {
1917             $istracked = false;
1918         }
1919     }
1921     if ($istracked) {
1922         if ($post_read == -99) {    // If we don't know yet...
1923         /// The front page can display a news item post to non-logged in users. This should
1924         /// always appear as 'read'.
1925             $post_read = empty($USER) || forum_tp_is_post_read($USER->id, $post);
1926         }
1927         if ($post_read) {
1928             $read_style = ' read';
1929         } else {
1930             $read_style = ' unread';
1931             echo '<a name="unread"></a>';
1932         }
1933     } else {
1934         $read_style = '';
1935     }
1937     echo '<a id="p'.$post->id.'"></a>';
1938     echo '<table cellspacing="0" class="forumpost'.$read_style.'">';
1940     echo '<tr class="header"><td class="picture left">';
1941     print_user_picture($post->userid, $courseid, $post->picture);
1942     echo '</td>';
1944     if ($post->parent) {
1945         echo '<td class="topic">';
1946     } else {
1947         echo '<td class="topic starter">';
1948     }
1950     echo '<div class="subject">'.format_string($post->subject).'</div>';
1952     echo '<div class="author">';
1953     $fullname = fullname($post, has_capability('moodle/site:viewfullnames', $modcontext));
1954     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.
1955                 $post->userid.'&amp;course='.$courseid.'">'.$fullname.'</a>';
1956     $by->date = userdate($post->modified);
1957     print_string('bynameondate', 'forum', $by);
1958     echo '</div></td></tr>';
1960     echo '<tr><td class="left side">';
1961     if ($group = user_group($courseid, $post->userid)) {
1962         print_group_picture($group, $courseid, false, false, true);
1963     } else {
1964         echo '&nbsp;';
1965     }
1967 /// Actual content
1969     echo '</td><td class="content">'."\n";
1971     if ($post->attachment) {
1972         $post->course = $courseid;
1973         $post->forum = get_field('forum_discussions', 'forum', 'id', $post->discussion);
1974         echo '<div class="attachments">';
1975         $attachedimages = forum_print_attachments($post);
1976         echo '</div>';
1977     } else {
1978         $attachedimages = '';
1979     }
1982     $options = new Object;
1983     $options->para = false;
1984     $options->trusttext = true;
1985     if ($link and (strlen(strip_tags($post->message)) > $CFG->forum_longpost)) {
1986         // Print shortened version
1987         echo format_text(forum_shorten_post($post->message), $post->format, $options, $courseid);
1988         $numwords = count_words(strip_tags($post->message));
1989         echo '<p><a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1990         echo get_string('readtherest', 'forum');
1991         echo '</a> ('.get_string('numwords', '', $numwords).')...</p>';
1992     } else {
1993         // Print whole message
1994         if ($highlight) {
1995             echo highlight($highlight, format_text($post->message, $post->format, $options, $courseid));
1996         } else {
1997             echo format_text($post->message, $post->format, $options, $courseid);
1998         }
1999         echo $attachedimages;
2000     }
2003 /// Commands
2005     $commands = array();
2007     if ($istracked) {
2008         /// SPECIAL CASE: The front page can display a news item post to non-logged in users.
2009         /// Don't display the mark read / unread controls in this case.
2010         if ($CFG->forum_usermarksread && !empty($USER)) {
2011             if ($post_read) {
2012                 $mcmd = '&amp;mark=unread&amp;postid='.$post->id;
2013                 $mtxt = $strmarkunread;
2014             } else {
2015                 $mcmd = '&amp;mark=read&amp;postid='.$post->id;
2016                 $mtxt = $strmarkread;
2017             }
2018             if ($threadedmode) {
2019                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
2020                               $post->discussion.'&amp;parent='.$post->id.$mcmd.'">'.$mtxt.'</a>';
2021             } else {
2022                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
2023                               $post->discussion.$mcmd.'#p'.$post->id.'">'.$mtxt.'</a>';
2024             }
2025         }
2026     }
2028     if ($post->parent) {
2029         if ($threadedmode) {
2030             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
2031                           $post->discussion.'&amp;parent='.$post->parent.'">'.$strparent.'</a>';
2032         } else {
2033             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
2034                           $post->discussion.'#p'.$post->parent.'">'.$strparent.'</a>';
2035         }
2036     }
2038     $forumtype = get_field('forum', 'type', 'id', $post->forum);
2040     $age = time() - $post->created;
2041     /// Hack for allow to edit news posts those are not displayed yet until they are displayed
2042     if (!$post->parent
2043         && $forumtype == 'news'
2044         && get_field_sql("SELECT id FROM {$CFG->prefix}forum_discussions WHERE id = $post->discussion AND timestart > ".time())) {
2045         $age = 0;
2046     }
2047     $editanypost = has_capability('mod/forum:editanypost', $modcontext);
2051     if ($ownpost or $editanypost) {
2052         if (($age < $CFG->maxeditingtime) or $editanypost) {
2053             $commands[] =  '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?edit='.$post->id.'">'.$stredit.'</a>';
2054         }
2055     }
2057     if (has_capability('mod/forum:splitdiscussions', $modcontext)
2058                 && $post->parent && $forumtype != 'single') {
2060         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?prune='.$post->id.
2061                       '" title="'.$strpruneheading.'">'.$strprune.'</a>';
2062     }
2064     if (($ownpost and $age < $CFG->maxeditingtime
2065                 and has_capability('mod/forum:deleteownpost', $modcontext))
2066                 or has_capability('mod/forum:deleteanypost', $modcontext)) {
2067         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?delete='.$post->id.'">'.$strdelete.'</a>';
2068     }
2070     if ($reply) {
2071         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.$strreply.'</a>';
2072     }
2074     echo '<div class="commands">';
2075     echo implode(' | ', $commands);
2076     echo '</div>';
2079 /// Ratings
2081     $ratingsmenuused = false;
2082     if (!empty($ratings) and !empty($USER->id)) {
2083         echo '<div class="ratings">';
2084         $useratings = true;
2085         if ($ratings->assesstimestart and $ratings->assesstimefinish) {
2086             if ($post->created < $ratings->assesstimestart or $post->created > $ratings->assesstimefinish) {
2087                 $useratings = false;
2088             }
2089         }
2090         if ($useratings) {
2091             $mypost = ($USER->id == $post->userid);
2093             $canviewallratings = has_capability('mod/forum:viewanyrating', $modcontext);
2095             if ($canviewallratings and !$mypost) {
2096                 forum_print_ratings_mean($post->id, $ratings->scale, $canviewallratings);
2097                 if (!empty($ratings->allow)) {
2098                     echo '&nbsp;';
2099                     forum_print_rating_menu($post->id, $USER->id, $ratings->scale);
2100                     $ratingsmenuused = true;
2101                 }
2103             } else if ($mypost) {
2104                 forum_print_ratings_mean($post->id, $ratings->scale, true);
2106             } else if (!empty($ratings->allow) ) {
2107                 forum_print_rating_menu($post->id, $USER->id, $ratings->scale);
2108                 $ratingsmenuused = true;
2109             }
2110         }
2111         echo '</div>';
2112     }
2114 /// Link to post if required
2116     if ($link) {
2117         echo '<div class="link">';
2118         if ($post->replies == 1) {
2119             $replystring = get_string('repliesone', 'forum', $post->replies);
2120         } else {
2121             $replystring = get_string('repliesmany', 'forum', $post->replies);
2122         }
2123         echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.
2124              get_string('discussthistopic', 'forum').'</a>&nbsp;('.$replystring.')';
2125         echo '</div>';
2126     }
2128     if ($footer) {
2129         echo '<div class="footer">'.$footer.'</div>';
2130     }
2131     echo '</td></tr></table>'."\n\n";
2133     if ($istracked && !$CFG->forum_usermarksread && !empty($post->forum)) {
2134         forum_tp_mark_post_read($USER->id, $post, $post->forum);
2135     }
2137     return $ratingsmenuused;
2141 /**
2142 * This function prints the overview of a discussion in the forum listing.
2143 * It needs some discussion information and some post information, these
2144 * happen to be combined for efficiency in the $post parameter by the function
2145 * that calls this one: forum_print_latest_discussions()
2147 * @param object $post The post object (passed by reference for speed).
2148 * @param object $forum The forum object.
2149 * @param int $group Current group.
2150 * @param string $datestring Format to use for the dates.
2151 * @param boolean $cantrack Is tracking enabled for this forum.
2152 * @param boolean $forumtracked Is the user tracking this forum.
2153 */
2154 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
2155                                         $cantrack=true, $forumtracked=true) {
2157     global $USER, $CFG;
2159     static $rowcount;
2160     static $strmarkalldread;
2163     if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
2164         error('Course Module ID was incorrect');
2165     }
2166     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2169     if (!isset($rowcount)) {
2170         $rowcount = 0;
2171         $strmarkalldread = get_string('markalldread', 'forum');
2172     } else {
2173         $rowcount = ($rowcount + 1) % 2;
2174     }
2176     $post->subject = format_string($post->subject,true);
2178     echo "\n\n";
2179     echo '<tr class="discussion r'.$rowcount.'">';
2181     // Topic
2182     echo '<td class="topic starter">';
2183     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.$post->subject.'</a>';
2184     echo "</td>\n";
2186     // Picture
2187     echo '<td class="picture">';
2188     print_user_picture($post->userid, $forum->course, $post->picture);
2189     echo "</td>\n";
2191     // User name
2192     $fullname = fullname($post, has_capability('moodle/site:viewfullnames', $modcontext));
2193     echo '<td class="author">';
2194     echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->userid.'&amp;course='.$forum->course.'">'.$fullname.'</a>';
2195     echo "</td>\n";
2197     // Group picture
2198     if ($group !== -1) {  // Groups are active - group is a group data object or NULL
2199         echo '<td class="picture group">';
2200         if (!empty($group->picture) and empty($group->hidepicture)) {
2201             print_group_picture($group, $forum->course, false, false, true);
2202         } else if (isset($group->id)) {
2203             echo '<a href="'.$CFG->wwwroot.'/user/index.php?id='.$forum->course.'&amp;group='.$group->id.'">'.$group->name.'</a>';
2204         }
2205         echo "</td>\n";
2206     }
2208     if (has_capability('mod/forum:viewdiscussion', $modcontext)) {   // Show the column with replies
2209         echo '<td class="replies">';
2210         echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
2211         echo $post->replies.'</a>';
2212         echo "</td>\n";
2214         if ($cantrack) {
2215             echo '<td class="replies">';
2216             if ($forumtracked) {
2217                 if ($post->unread > 0) {
2218                     echo '<span class="unread">';
2219                     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#unread">';
2220                     echo $post->unread;
2221                     echo '</a>';
2222                     echo '<a title="'.$strmarkalldread.'" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
2223                          $forum->id.'&amp;d='.$post->discussion.'&amp;mark=read&amp;returnpage=view.php">' .
2224                          '<img src="'.$CFG->pixpath.'/t/clear.gif" class="iconsmall" alt="'.$strmarkalldread.'" /></a>';
2225                     echo '</span>';
2226                 } else {
2227                     echo '<span class="read">';
2228                     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#unread">';
2229                     echo $post->unread;
2230                     echo '</a>';
2231                     echo '</span>';
2232                 }
2233             } else {
2234                 echo '<span class="read">';
2235                 echo '-';
2236                 echo '</span>';
2237             }
2238             echo "</td>\n";
2239         }
2240     }
2242     echo '<td class="lastpost">';
2243     $usedate = (empty($post->timemodified)) ? $post->modified : $post->timemodified;  // Just in case
2244     $parenturl = (empty($post->lastpostid)) ? '' : '&amp;parent='.$post->lastpostid;
2245     $usermodified->id        = $post->usermodified;
2246     $usermodified->firstname = $post->umfirstname;
2247     $usermodified->lastname  = $post->umlastname;
2248     echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->usermodified.'&amp;course='.$forum->course.'">'.
2249          fullname($usermodified).'</a><br />';
2250     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.$parenturl.'">'.
2251           userdate($usedate, $datestring).'</a>';
2252     echo "</td>\n";
2254     echo "</tr>\n\n";
2259 function forum_shorten_post($message) {
2260 // Given a post object that we already know has a long message
2261 // this function truncates the message nicely to the first
2262 // sane place between $CFG->forum_longpost and $CFG->forum_shortpost
2264    global $CFG;
2266    $i = 0;
2267    $tag = false;
2268    $length = strlen($message);
2269    $count = 0;
2270    $stopzone = false;
2271    $truncate = 0;
2273    for ($i=0; $i<$length; $i++) {
2274        $char = $message[$i];
2276        switch ($char) {
2277            case "<":
2278                $tag = true;
2279                break;
2280            case ">":
2281                $tag = false;
2282                break;
2283            default:
2284                if (!$tag) {
2285                    if ($stopzone) {
2286                        if ($char == ".") {
2287                            $truncate = $i+1;
2288                            break 2;
2289                        }
2290                    }
2291                    $count++;
2292                }
2293                break;
2294        }
2295        if (!$stopzone) {
2296            if ($count > $CFG->forum_shortpost) {
2297                $stopzone = true;
2298            }
2299        }
2300    }
2302    if (!$truncate) {
2303        $truncate = $i;
2304    }
2306    return substr($message, 0, $truncate);
2310 function forum_print_ratings_mean($postid, $scale, $link=true) {
2311 /// Print the multiple ratings on a post given to the current user by others.
2312 /// Scale is an array of ratings
2314     static $strrate;
2316     $mean = forum_get_ratings_mean($postid, $scale);
2318     if ($mean !== "") {
2320         if (empty($strratings)) {
2321             $strratings = get_string("ratings", "forum");
2322         }
2324         echo "$strratings: ";
2325         if ($link) {
2326             link_to_popup_window ("/mod/forum/report.php?id=$postid", "ratings", $mean, 400, 600);
2327         } else {
2328             echo "$mean ";
2329         }
2330     }
2334 function forum_get_ratings_mean($postid, $scale, $ratings=NULL) {
2335 /// Return the mean rating of a post given to the current user by others.
2336 /// Scale is an array of possible ratings in the scale
2337 /// Ratings is an optional simple array of actual ratings (just integers)
2339     if (!$ratings) {
2340         $ratings = array();
2341         if ($rates = get_records("forum_ratings", "post", $postid)) {
2342             foreach ($rates as $rate) {
2343                 $ratings[] = $rate->rating;
2344             }
2345         }
2346     }
2348     $count = count($ratings);
2350     if ($count == 0) {
2351         return "";
2353     } else if ($count == 1) {
2354         return $scale[$ratings[0]];
2356     } else {
2357         $total = 0;
2358         foreach ($ratings as $rating) {
2359             $total += $rating;
2360         }
2361         $mean = round( ((float)$total/(float)$count) + 0.001);  // Little fudge factor so that 0.5 goes UP
2363         if (isset($scale[$mean])) {
2364             return $scale[$mean]." ($count)";
2365         } else {
2366             return "$mean ($count)";    // Should never happen, hopefully
2367         }
2368     }
2371 function forum_get_ratings_summary($postid, $scale, $ratings=NULL) {
2372 /// Return a summary of post ratings given to the current user by others.
2373 /// Scale is an array of possible ratings in the scale
2374 /// Ratings is an optional simple array of actual ratings (just integers)
2376     if (!$ratings) {
2377         $ratings = array();
2378         if ($rates = get_records("forum_ratings", "post", $postid)) {
2379             foreach ($rates as $rate) {
2380                 $rating[] = $rate->rating;
2381             }
2382         }
2383     }
2386     if (!$count = count($ratings)) {
2387         return "";
2388     }
2391     foreach ($scale as $key => $scaleitem) {
2392         $sumrating[$key] = 0;
2393     }
2395     foreach ($ratings as $rating) {
2396         $sumrating[$rating]++;
2397     }
2399     $summary = "";
2400     foreach ($scale as $key => $scaleitem) {
2401         $summary = $sumrating[$key].$summary;
2402         if ($key > 1) {
2403             $summary = "/$summary";
2404         }
2405     }
2406     return $summary;
2409 function forum_print_rating_menu($postid, $userid, $scale) {
2410 /// Print the menu of ratings as part of a larger form.
2411 /// If the post has already been - set that value.
2412 /// Scale is an array of ratings
2414     static $strrate;
2416     if (!$rating = get_record("forum_ratings", "userid", $userid, "post", $postid)) {
2417         $rating->rating = FORUM_UNSET_POST_RATING;
2418     }
2420     if (empty($strrate)) {
2421         $strrate = get_string("rate", "forum");
2422     }
2423     $scale = array(FORUM_UNSET_POST_RATING => $strrate.'...') + $scale;
2424     choose_from_menu($scale, $postid, $rating->rating, '');
2427 /**
2428  * Print the drop down that allows the user to select how they want to have
2429  * the discussion displayed.
2430  * @param $id - forum id if $forumtype is 'single',
2431  *              discussion id for any other forum type
2432  * @param $mode - forum layout mode
2433  * @param $forumtype - optional
2434  */
2435 function forum_print_mode_form($id, $mode, $forumtype='') {
2436     global $FORUM_LAYOUT_MODES;
2438     if ($forumtype == 'single') {
2439         popup_form("view.php?f=$id&amp;mode=", $FORUM_LAYOUT_MODES, "mode", $mode, "");
2440     } else {
2441         popup_form("discuss.php?d=$id&amp;mode=", $FORUM_LAYOUT_MODES, "mode", $mode, "");
2442     }
2445 function forum_search_form($course, $search='') {
2446     global $CFG;
2448     $output  = '<div class="forumsearch">';
2449     $output .= '<form action="'.$CFG->wwwroot.'/mod/forum/search.php" style="display:inline">';
2450     $output .= '<fieldset class="invisiblefieldset">';
2451     $output .= helpbutton('search', get_string('search'), 'moodle', true, false, '', true);
2452     $output .= '<input name="search" type="text" size="18" value="'.$search.'" alt="search" />';
2453     $output .= '<input value="'.get_string('searchforums', 'forum').'" type="submit" />';
2454     $output .= '<input name="id" type="hidden" value="'.$course->id.'" />';
2455     $output .= '</fieldset>';
2456     $output .= '</form>';
2457     $output .= '</div>';
2459     return $output;
2463 function forum_set_return() {
2464     global $CFG, $SESSION;
2466     if (! isset($SESSION->fromdiscussion)) {
2467         if (!empty($_SERVER['HTTP_REFERER'])) {
2468             $referer = $_SERVER['HTTP_REFERER'];
2469         } else {
2470             $referer = "";
2471         }
2472         // If the referer is NOT a login screen then save it.
2473         if (! strncasecmp("$CFG->wwwroot/login", $referer, 300)) {
2474             $SESSION->fromdiscussion = $_SERVER["HTTP_REFERER"];
2475         }
2476     }
2480 function forum_go_back_to($default) {
2481     global $SESSION;
2483     if (!empty($SESSION->fromdiscussion)) {
2484         $returnto = $SESSION->fromdiscussion;
2485         unset($SESSION->fromdiscussion);
2486         return $returnto;
2487     } else {
2488         return $default;
2489     }
2492 function forum_file_area_name($post) {
2493 //  Creates a directory file name, suitable for make_upload_directory()
2494     global $CFG;
2496     return "$post->course/$CFG->moddata/forum/$post->forum/$post->id";
2499 function forum_file_area($post) {
2500     return make_upload_directory( forum_file_area_name($post) );
2503 function forum_delete_old_attachments($post, $exception="") {
2504 // Deletes all the user files in the attachments area for a post
2505 // EXCEPT for any file named $exception
2507     if ($basedir = forum_file_area($post)) {
2508         if ($files = get_directory_list($basedir)) {
2509             foreach ($files as $file) {
2510                 if ($file != $exception) {
2511                     unlink("$basedir/$file");
2512                     notify("Existing file '$file' has been deleted!");
2513                 }
2514             }
2515         }
2516         if (!$exception) {  // Delete directory as well, if empty
2517             rmdir("$basedir");
2518         }
2519     }
2522 function forum_move_attachments($discussion, $forumid) {
2523 /// Given a discussion object that is being moved to forumid,
2524 /// this function checks all posts in that discussion
2525 /// for attachments, and if any are found, these are
2526 /// moved to the new forum directory.
2528     global $CFG;
2530     require_once($CFG->dirroot.'/lib/uploadlib.php');
2532     $return = true;
2534     if ($posts = get_records_select("forum_posts", "discussion = '$discussion->id' AND attachment <> ''")) {
2535         foreach ($posts as $oldpost) {
2536             $oldpost->course = $discussion->course;
2537             $oldpost->forum = $discussion->forum;
2538             $oldpostdir = "$CFG->dataroot/".forum_file_area_name($oldpost);
2539             if (is_dir($oldpostdir)) {
2540                 $newpost = $oldpost;
2541                 $newpost->forum = $forumid;
2542                 $newpostdir = forum_file_area_name($newpost);
2543                 // take off the last directory because otherwise we're renaming to a directory that already exists
2544                 // and this is unhappy in certain situations, eg over an nfs mount and potentially on windows too.
2545                 make_upload_directory(substr($newpostdir,0,strrpos($newpostdir,'/')));
2546                 $newpostdir = $CFG->dataroot.'/'.forum_file_area_name($newpost);
2547                 $files = get_directory_list($oldpostdir); // get it before we rename it.
2548                 if (! @rename($oldpostdir, $newpostdir)) {
2549                     $return = false;
2550                 }
2551                 foreach ($files as $file) {
2552                     clam_change_log($oldpostdir.'/'.$file,$newpostdir.'/'.$file);
2553                 }
2554             }
2555         }
2556     }
2557     return $return;
2560 function forum_print_attachments($post, $return=NULL) {
2561 // if return=html, then return a html string.
2562 // if return=text, then return a text-only string.
2563 // otherwise, print HTML for non-images, and return image HTML
2565     global $CFG;
2567     $filearea = forum_file_area_name($post);
2569     $imagereturn = "";
2570     $output = "";
2572     if ($basedir = forum_file_area($post)) {
2573         if ($files = get_directory_list($basedir)) {
2574             $strattachment = get_string("attachment", "forum");
2575             foreach ($files as $file) {
2576                 $icon = mimeinfo("icon", $file);
2577                 $type = mimeinfo("type", $file);
2578                 if ($CFG->slasharguments) {
2579                     $ffurl = "$CFG->wwwroot/file.php/$filearea/$file";
2580                 } else {
2581                     $ffurl = "$CFG->wwwroot/file.php?file=/$filearea/$file";
2582                 }
2583                 $image = "<img src=\"$CFG->pixpath/f/$icon\" class=\"icon\" alt=\"\" />";
2585                 if ($return == "html") {
2586                     $output .= "<a href=\"$ffurl\">$image</a> ";
2587                     $output .= "<a href=\"$ffurl\">$file</a><br />";
2589                 } else if ($return == "text") {
2590                     $output .= "$strattachment $file:\n$ffurl\n";
2592                 } else {
2593                     if (in_array($type, array('image/gif', 'image/jpeg', 'image/png'))) {    // Image attachments don't get printed as links
2594                         $imagereturn .= "<br /><img src=\"$ffurl\" alt=\"\" />";
2595                     } else {
2596                         echo "<a href=\"$ffurl\">$image</a> ";
2597                         echo filter_text("<a href=\"$ffurl\">$file</a><br />");
2598                     }
2599                 }
2600             }
2601         }
2602     }
2604     if ($return) {
2605         return $output;
2606     }
2608     return $imagereturn;
2610 /**
2611  * If successful, this function returns the name of the file
2612  * @param $post is a full post record, including course and forum
2613  * @param $newfile is a full upload array from $_FILES
2614  * @param $message is a string to hold the messages.
2615  */
2617 function forum_add_attachment($post, $inputname,&$message) {
2619     global $CFG;
2621     if (!$forum = get_record("forum", "id", $post->forum)) {
2622         return "";
2623     }
2625     if (!$course = get_record("course", "id", $forum->course)) {
2626         return "";
2627     }
2629     require_once($CFG->dirroot.'/lib/uploadlib.php');
2630     $um = new upload_manager($inputname,true,false,$course,false,$forum->maxbytes,true,true);
2631     $dir = forum_file_area_name($post);
2632     if ($um->process_file_uploads($dir)) {
2633         $message .= $um->get_errors();
2634         return $um->get_new_filename();
2635     }
2636     $message .= $um->get_errors();
2637     return null;
2640 function forum_add_new_post($post,&$message) {
2642     global $USER, $CFG;
2644     $post->created = $post->modified = time();
2645     $post->mailed = "0";
2646     $post->userid = $USER->id;
2647     $post->attachment = "";
2649     if (! $post->id = insert_record("forum_posts", $post)) {
2650         return false;
2651     }
2653     if ($post->attachment = forum_add_attachment($post, 'attachment',$message)) {
2654         set_field("forum_posts", "attachment", $post->attachment, "id", $post->id);
2655     }
2657     // Update discussion modified date
2658     set_field("forum_discussions", "timemodified", $post->modified, "id", $post->discussion);
2659     set_field("forum_discussions", "usermodified", $post->userid, "id", $post->discussion);
2661     if (forum_tp_can_track_forums($post->forum) && forum_tp_is_tracked($post->forum)) {
2662         forum_tp_mark_post_read($post->userid, $post, $post->forum);
2663     }
2665     return $post->id;
2668 function forum_update_post($post,&$message) {
2670     global $USER, $CFG;
2672     $post->modified = time();
2674     if (!$post->parent) {   // Post is a discussion starter - update discussion title too
2675         set_field("forum_discussions", "name", $post->subject, "id", $post->discussion);
2676     }
2678     if ($newfilename = forum_add_attachment($post, 'attachment',$message)) {
2679         $post->attachment = $newfilename;
2680     } else {
2681         unset($post->attachment);
2682     }
2684     // Update discussion modified date
2685     set_field("forum_discussions", "timemodified", $post->modified, "id", $post->discussion);
2686     set_field("forum_discussions", "usermodified", $post->userid, "id", $post->discussion);
2688     if (forum_tp_can_track_forums($post->forum) && forum_tp_is_tracked($post->forum)) {
2689         forum_tp_mark_post_read($post->userid, $post, $post->forum);
2690     }
2692     return update_record("forum_posts", $post);
2695 function forum_add_discussion($discussion,&$message) {
2696 // Given an object containing all the necessary data,
2697 // create a new discussion and return the id
2699     GLOBAL $USER, $CFG;
2701     $timenow = time();
2703     // The first post is stored as a real post, and linked
2704     // to from the discuss entry.
2706     $post->discussion  = 0;
2707     $post->parent      = 0;
2708     $post->userid      = $USER->id;
2709     $post->created     = $timenow;
2710     $post->modified    = $timenow;
2711     $post->mailed      = 0;
2712     $post->subject     = $discussion->name;
2713     $post->message     = $discussion->intro;
2714     $post->attachment  = "";
2715     $post->forum       = $discussion->forum;
2716     $post->course      = $discussion->course;
2717     $post->format      = $discussion->format;
2718     $post->mailnow     = $discussion->mailnow;
2720     if (! $post->id = insert_record("forum_posts", $post) ) {
2721         return 0;
2722     }
2724     if ($post->attachment = forum_add_attachment($post, 'attachment',$message)) {
2725         set_field("forum_posts", "attachment", $post->attachment, "id", $post->id); //ignore errors
2726     }
2728     // Now do the main entry for the discussion,
2729     // linking to this first post
2731     $discussion->firstpost    = $post->id;
2732     $discussion->timemodified = $timenow;
2733     $discussion->usermodified = $post->userid;
2734     $discussion->userid = $USER->id;
2736     if (! $post->discussion = insert_record("forum_discussions", $discussion) ) {
2737         delete_records("forum_posts", "id", $post->id);
2738         return 0;
2739     }
2741     // Finally, set the pointer on the post.
2742     if (! set_field("forum_posts", "discussion", $post->discussion, "id", $post->id)) {
2743         delete_records("forum_posts", "id", $post->id);
2744         delete_records("forum_discussions", "id", $post->discussion);
2745         return 0;
2746     }
2748     if (forum_tp_can_track_forums($post->forum) && forum_tp_is_tracked($post->forum)) {
2749         forum_tp_mark_post_read($post->userid, $post, $post->forum);
2750     }
2752     return $post->discussion;
2756 function forum_delete_discussion($discussion, $fulldelete=false) {
2757 // $discussion is a discussion record object
2759     $result = true;
2761     if ($posts = get_records("forum_posts", "discussion", $discussion->id)) {
2762         foreach ($posts as $post) {
2763             $post->course = $discussion->course;
2764             $post->forum  = $discussion->forum;
2765             if (! delete_records("forum_ratings", "post", "$post->id")) {
2766                 $result = false;
2767             }
2768             if (! forum_delete_post($post, $fulldelete)) {
2769                 $result = false;
2770             }
2771         }
2772     }
2774     forum_tp_delete_read_records(-1, -1, $discussion->id);
2776     if (! delete_records("forum_discussions", "id", "$discussion->id")) {
2777         $result = false;
2778     }
2780     return $result;
2784 function forum_delete_post($post, $children=false) {
2785    if ($childposts = get_records('forum_posts', 'parent', $post->id)) {
2786        if ($children) {
2787            foreach ($childposts as $childpost) {
2788                forum_delete_post($childpost, true);
2789            }
2790        } else {
2791            return false;
2792        }
2793    }
2794    if (delete_records("forum_posts", "id", $post->id)) {
2795        delete_records("forum_ratings", "post", $post->id);  // Just in case
2797        forum_tp_delete_read_records(-1, $post->id);
2799        if ($post->attachment) {
2800            $discussion = get_record("forum_discussions", "id", $post->discussion);
2801            $post->course = $discussion->course;
2802            $post->forum  = $discussion->forum;
2803            forum_delete_old_attachments($post);
2804        }
2806    /// Just in case we are deleting the last post
2807        forum_discussion_update_last_post($post->discussion);
2809        return true;
2810    }
2811    return false;
2814 function forum_count_replies($post, $children=true) {
2815     $count = 0;
2817     if ($children) {
2818         if ($childposts = get_records('forum_posts', 'parent', $post->id)) {
2819            foreach ($childposts as $childpost) {
2820                $count ++;                   // For this child
2821                $count += forum_count_replies($childpost, true);
2822            }
2823         }
2824     } else {
2825         $count += count_records('forum_posts', 'parent', $post->id);
2826     }
2828     return $count;
2832 function forum_forcesubscribe($forumid, $value=1) {
2833     return set_field("forum", "forcesubscribe", $value, "id", $forumid);
2836 function forum_is_forcesubscribed($forumid) {
2837     return (get_field("forum", "forcesubscribe", "id", $forumid) == 1);
2840 function forum_is_subscribed($userid, $forumid) {
2841     if (forum_is_forcesubscribed($forumid)) {
2842         return true;
2843     }
2844     return record_exists("forum_subscriptions", "userid", $userid, "forum", $forumid);
2847 function forum_subscribe($userid, $forumid) {
2848 /// Adds user to the subscriber list
2850     if (record_exists("forum_subscriptions", "userid", $userid, "forum", $forumid)) {
2851         return true;
2852     }
2854     $sub->userid  = $userid;
2855     $sub->forum = $forumid;
2857     return insert_record("forum_subscriptions", $sub);
2860 function forum_unsubscribe($userid, $forumid) {
2861 /// Removes user from the subscriber list
2862     return delete_records("forum_subscriptions", "userid", $userid, "forum", $forumid);
2865 function forum_post_subscription($post) {
2866 /// Given a new post, subscribes or unsubscribes as appropriate.
2867 /// Returns some text which describes what happened.
2869     global $USER;
2871     $subscribed=forum_is_subscribed($USER->id, $post->forum);
2872     if ((isset($post->subscribe) && $post->subscribe && $subscribed)
2873         || (!$post->subscribe && !$subscribed)) {
2874         return "";
2875     }
2877     if (!$forum = get_record("forum", "id", $post->forum)) {
2878         return "";
2879     }
2881     $info->name  = fullname($USER);
2882     $info->forum = $forum->name;
2884     if (!empty($post->subscribe)) {
2885         forum_subscribe($USER->id, $post->forum);
2886         return "<p>".get_string("nowsubscribed", "forum", $info)."</p>";
2887     }
2889     forum_unsubscribe($USER->id, $post->forum);
2890     return "<p>".get_string("nownotsubscribed", "forum", $info)."</p>";
2894 function forum_user_has_posted_discussion($forumid, $userid) {
2895     if ($discussions = forum_get_discussions($forumid, '', $userid)) {
2896         return true;
2897     } else {
2898         return false;
2899     }
2902 function forum_discussions_user_has_posted_in($forumid, $userid) {
2903     global $CFG;
2905     $haspostedsql = "SELECT d.id AS id,
2906                             d.*
2907                        FROM {$CFG->prefix}forum_posts p,
2908                             {$CFG->prefix}forum_discussions d
2909                       WHERE p.discussion = d.id
2910                         AND d.forum = $forumid
2911                         AND p.userid = $userid";
2913     return get_records_sql($haspostedsql);
2916 function forum_user_has_posted($forumid, $did, $userid) {
2917     return record_exists('forum_posts','discussion',$did,'userid',$userid);
2920 function forum_user_can_post_discussion($forum, $currentgroup=false, $groupmode='') {
2921 // $forum is an object
2922     global $USER, $SESSION;
2924     if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
2925         error('Course Module ID was incorrect');
2926     }
2927     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2929     if (!has_capability('mod/forum:startdiscussion', $context)) {
2930         return false;
2931     }
2933     if ($forum->type == "eachuser") {
2934         return (!forum_user_has_posted_discussion($forum->id, $USER->id));
2935     } else if ($currentgroup) {
2936         return (has_capability('moodle/site:accessallgroups', $context)
2937                 or ismember($currentgroup));
2938     } else {
2939         //else it might be group 0 in visible mode
2940         if ($groupmode == VISIBLEGROUPS){
2941             return (ismember($currentgroup));
2942         }
2943         else {
2944             return true;
2945         }
2946     }
2949 /**
2950  * This function checks whether the user can reply to posts in a forum
2951  * discussion. Use forum_user_can_post_discussion() to check whether the user
2952  * can start dicussions.
2953  * @param $forum - forum object
2954  * @param $user - user object
2955  */
2956 function forum_user_can_post($forum, $user=NULL) {
2958     if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
2959         error('Course Module ID was incorrect');
2960     }
2961     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2963     if (isset($user)) {
2964         $canreply = has_capability('mod/forum:replypost', $context, $user->id, false)
2965                 && !has_capability('moodle/legacy:guest', $context, $user->id, false);
2966     } else {
2967         $canreply = has_capability('mod/forum:replypost', $context, NULL, false)
2968                 && !has_capability('moodle/legacy:guest', $context, NULL, false);
2969     }
2971     return $canreply;
2975 //checks to see if a user can view a particular post
2976 function forum_user_can_view_post($post, $course, $cm, $forum, $discussion, $user=NULL){
2978     global $CFG, $USER;
2980     if (!$user){
2981         $user = $USER;
2982     }
2984     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2985     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) {
2986         return false;
2987     }
2989 /// If it's a grouped discussion, make sure the user is a member
2990     if ($discussion->groupid > 0) {
2991         if ($cm->groupmode == SEPARATEGROUPS) {
2992             return ismember($discussion->groupid) ||
2993                     has_capability('moodle/site:accessallgroups', $modcontext);
2994         }
2995     }
2996     return true;
3000 function forum_user_can_see_discussion($forum, $discussion, $context, $user=NULL) {
3001     global $USER;
3003     if (empty($user) || empty($user->id)) {
3004         $user = $USER;
3005     }
3007     // retrieve objects (yuk)
3008     if (is_numeric($forum)) {
3009         if (!$forum = get_record('forum','id',$forum)) {
3010             return false;
3011         }
3012     }
3013     if (is_numeric($discussion)) {
3014         if (!$discussion = get_record('forum_discussions','id',$discussion)) {
3015             return false;
3016         }
3017     }
3019     if (!has_capability('mod/forum:viewdiscussion', $context)) {
3020         return false;
3021     }
3023     if ($forum->type == 'qanda' &&
3024             !forum_user_has_posted($forum->id, $discussion->id, $user->id) &&
3025             !has_capability('mod/forum:viewqandawithoutposting', $context)) {
3026         return false;
3027     }
3028     return true;
3032 function forum_user_can_see_post($forum, $discussion, $post, $user=NULL) {
3033     global $USER;
3035     // retrieve objects (yuk)
3036     if (is_numeric($forum)) {
3037         if (!$forum = get_record('forum','id',$forum)) {
3038             return false;
3039         }
3040     }
3042     if (is_numeric($discussion)) {
3043         if (!$discussion = get_record('forum_discussions','id',$discussion)) {
3044             return false;
3045         }
3046     }
3047     if (is_numeric($post)) {
3048         if (!$post = get_record('forum_posts','id',$post)) {
3049             return false;
3050         }
3051     }
3052     if (!isset($post->id) && isset($post->parent)) {
3053         $post->id = $post->parent;
3054     }
3056     if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3057         error('Course Module ID was incorrect');
3058     }
3059     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
3061     if (empty($user) || empty($user->id)) {
3062         $user = $USER;
3063     }
3065     if (!has_capability('mod/forum:viewdiscussion', $context, $user->id, false)) {
3066         return false;
3067     }
3069     if ($forum->type == 'qanda') {
3070         $firstpost = forum_get_firstpost_from_discussion($discussion->id);
3072         return (forum_user_has_posted($forum->id,$discussion->id,$user->id) ||
3073                 $firstpost->id == $post->id ||
3074                 has_capability('mod/forum:viewqandawithoutposting', $context, false, $user->id));
3075     }
3076     return true;
3080 /**
3081 * Prints the discussion view screen for a forum.
3083 * @param object $course The current course object.
3084 * @param object $forum Forum to be printed.
3085 * @param int $maxdiscussions The maximum number of discussions per page(optional).
3086 * @param string $displayformat The display format to use (optional).
3087 * @param string $sort Sort arguments for database query (optional).
3088 * @param int $currentgroup Group to display discussions for (optional).
3089 * @param int $groupmode Group mode of the forum (optional).
3090 * @param int $page Page mode, page to display (optional).
3092 */
3093 function forum_print_latest_discussions($course, $forum, $maxdiscussions=5, $displayformat='plain', $sort='',
3094                                         $currentgroup=-1, $groupmode=-1, $page=-1) {
3095     global $CFG, $USER;
3097     if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3098         error('Course Module ID was incorrect');
3099     }
3100     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
3103 /// Sort out some defaults
3105     if ((!$maxdiscussions) && ($displayformat == 'plain')) {
3106         $displayformat = 'header';  // Abbreviate display by default
3107     }
3109     $fullpost = false;
3110     if ($displayformat == 'plain') {
3111         $fullpost = true;
3112     }
3115 /// Decide if current user is allowed to see ALL the current discussions or not
3117 /// First check the group stuff
3119     if ($groupmode == -1) {    /// We need to reconstruct groupmode because none was given
3120         if ($cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
3121             $groupmode = groupmode($course, $cm);
3122         } else {
3123             $groupmode = SEPARATEGROUPS;
3124         }
3125     }
3127     if ($currentgroup == -1) {    /// We need to reconstruct currentgroup because none was given
3128         $currentgroup = get_current_group($course->id);
3129     }
3131     if (!$currentgroup and ($groupmode != SEPARATEGROUPS or
3132                 has_capability('moodle/site:accessallgroups', $context)) ) {
3133         $visiblegroups = -1;
3134     } else {
3135         $visiblegroups = $currentgroup;
3136     }
3138 /// If the user can post discussions, then this is a good place to put the
3139 /// button for it. We do not show the button if we are showing site news
3140 /// and the current user is a guest.
3142     // TODO: Add group mode in there, to test for visible group.
3143     if (forum_user_can_post_discussion($forum, $currentgroup, $groupmode)
3144             || (has_capability('moodle/legacy:guest', $context, NULL, false)
3145             && $course->id != SITEID)) {
3147         echo '<div class="singlebutton forumaddnew">';
3148         echo "<form id=\"newdiscussionform\" method=\"get\" action=\"$CFG->wwwroot/mod/forum/post.php\">";
3149         echo '<fieldset class="invisiblefieldset">';
3150         echo "<input type=\"hidden\" name=\"forum\" value=\"$forum->id\" />";
3151         echo '<input type="submit" value="';
3152         echo ($forum->type == 'news') ? get_string('addanewtopic', 'forum')
3153             : (($forum->type == 'qanda')
3154                ? get_string('addanewquestion','forum')
3155                : get_string('addanewdiscussion', 'forum'));
3156         echo '" />';
3157         echo '</fieldset>';
3158         echo '</form>';
3159         echo "</div>\n";
3160     }
3163 /// Get all the recent discussions we're allowed to see
3165     $getuserlastmodified = ($displayformat == 'header');
3167     if (! $discussions = forum_get_discussions($forum->id, $sort, 0, $fullpost, $visiblegroups,0,$getuserlastmodified) ) {
3168         echo '<div class="forumnodiscuss">';
3169         if ($forum->type == 'news') {
3170             echo '('.get_string('nonews', 'forum').')';
3171         } else if ($forum->type == 'qanda') {
3172             echo '('.get_string('noquestions','forum').')';
3173         } else {
3174             echo '('.get_string('nodiscussions', 'forum').')';
3175         }
3176         echo "</div>\n";
3177         return;
3178     }
3180 /// If no discussions then don't use paging (to avoid some divide by 0 errors)
3182     if ($maxdiscussions <= 0) {
3183         $page = -1;
3184         $maxdiscussions = 0;
3185     }
3187 /// If we want paging
3189     if ($page != -1) {
3190         ///Get the number of discussions found
3191         $numdiscussions = count($discussions);
3193         ///Show the paging bar
3194         print_paging_bar($numdiscussions, $page, $maxdiscussions, "view.php?f=$forum->id&amp;");
3196         //Calculate the page "window"
3197         $pagestart = ($page * $maxdiscussions) + 1;
3198         $pageend  = $pagestart + $maxdiscussions - 1;
3199     }
3202     $replies = forum_count_discussion_replies($forum->id);
3204     $canreply = forum_user_can_post($forum);
3207     $discussioncount = 0;
3208     $olddiscussionlink = false;
3209     $strdatestring = get_string('strftimerecentfull');
3211     /// Check if the forum is tracked.
3212     if ($cantrack = forum_tp_can_track_forums($forum)) {
3213         $forumtracked = forum_tp_is_tracked($forum);
3214     } else {
3215         $forumtracked = false;
3216     }
3218     if ($displayformat == 'header') {
3219         echo '<table cellspacing="0" class="forumheaderlist">';
3220         echo '<thead>';
3221         echo '<tr>';
3222         echo '<th class="header topic" scope="col">'.get_string('discussion', 'forum').'</th>';
3223         echo '<th class="header author" colspan="2" scope="col">'.get_string('startedby', 'forum').'</th>';
3224         if ($groupmode > 0) {
3225             echo '<th class="header group" scope="col">'.get_string('group').'</th>';
3226         }
3227         if (has_capability('mod/forum:viewdiscussion', $context)) {
3228             echo '<th class="header replies" scope="col">'.get_string('replies', 'forum').'</th>';
3229             /// If the forum can be tracked, display the unread column.
3230             if ($cantrack) {
3231                 echo '<th class="header replies" scope="col">'.get_string('unread', 'forum');
3232                 if ($forumtracked) {
3233                     echo '&nbsp;<a title="'.get_string('markallread', 'forum').
3234                          '" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
3235                          $forum->id.'&amp;mark=read&amp;returnpage=view.php">'.
3236                          '<img src="'.$CFG->pixpath.'/t/clear.gif" class="iconsmall" alt="'.get_string('markallread', 'forum').'" /></a>';
3237                 }
3238                 echo '</th>';
3239             }
3240         }
3241         echo '<th class="header lastpost" scope="col">'.get_string('lastpost', 'forum').'</th>';
3242         echo '</tr>';
3243         echo '</thead>';
3244         echo '<tbody>';
3245     }
3247     foreach ($discussions as $discussion) {
3248         $discussioncount++;
3250         if ($page != -1) {     // We are using paging
3251             if ($discussioncount < $pagestart) {  // Not there yet
3252                 continue;
3253             }
3254             if ($discussioncount > $pageend) {    // All done, finish the loop
3255                 break;
3256             }
3257         //Without paging, old approach
3258         } else if ($maxdiscussions && ($discussioncount > $maxdiscussions)) {
3259             $olddiscussionlink = true;
3260             break;
3261         }
3263         if (!empty($replies[$discussion->discussion])) {
3264             $discussion->replies = $replies[$discussion->discussion]->replies;
3265             $discussion->lastpostid = $replies[$discussion->discussion]->lastpostid;
3266         } else {
3267             $discussion->replies = 0;
3268         }
3270         /// SPECIAL CASE: The front page can display a news item post to non-logged in users.
3271         /// All posts are read in this case.
3272         if (!$forumtracked) {
3273             $discussion->unread = '-';
3274         } else if (empty($USER)) {
3275             $discussion->unread = 0;
3276         } else {
3277             $discussion->unread = forum_tp_count_discussion_unread_posts($USER->id, $discussion->discussion);
3278         }
3280         if (!empty($USER->id)) {
3281             $ownpost = ($discussion->userid == $USER->id);
3282         } else {
3283             $ownpost=false;
3284         }
3285         // Use discussion name instead of subject of first post
3286         $discussion->subject = $discussion->name;
3288         switch ($displayformat) {
3289             case 'header':
3290                 if ($groupmode > 0) {
3291                     if (isset($groups[$discussion->groupid])) {
3292                         $group = $groups[$discussion->groupid];
3293                     } else {
3294                         $group = $groups[$discussion->groupid] = groups_get_group($discussion->groupid); //TODO:
3295                     }
3296                 } else {
3297                     $group = -1;
3298                 }
3299                 forum_print_discussion_header($discussion, $forum, $group, $strdatestring, $cantrack, $forumtracked);
3300             break;
3301             default:
3302                 if ($canreply or $discussion->replies) {
3303                     $link = true;
3304                 } else {
3305                     $link = false;
3306                 }
3308                 $discussion->forum = $forum->id;
3310                 forum_print_post($discussion, $course->id, $ownpost, $reply=0, $link, $assessed=false);
3311             break;
3312         }
3313     }
3315     if ($displayformat == "header") {
3316         echo '</tbody>';
3317         echo '</table>';
3318     }
3320     if ($olddiscussionlink) {
3321         echo '<div class="forumolddiscuss">';
3322         echo '<a href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'&amp;showall=1">';
3323         echo get_string('olderdiscussions', 'forum').'</a> ...</div>';
3324     }
3326     if ($page != -1) { ///Show the paging bar
3327         print_paging_bar($numdiscussions, $page, $maxdiscussions, "view.php?f=$forum->id&amp;");
3328     }
3332 function forum_print_discussion($course, $forum, $discussion, $post, $mode, $canreply=NULL, $canrate=false) {
3334     global $USER, $CFG;
3336     if (!empty($USER->id)) {
3337         $ownpost = ($USER->id == $post->userid);
3338     } else {
3339         $ownpost = false;
3340     }
3341     if ($canreply === NULL) {
3342         $reply = forum_user_can_post($forum);
3343     } else {
3344         $reply = $canreply;
3345     }
3347     $ratings = NULL;
3348     $ratingsmenuused = false;
3349     $ratingsformused = false;
3350     if ($forum->assessed and !empty($USER->id)) {
3351         if ($ratings->scale = make_grades_menu($forum->scale)) {
3352             $ratings->assesstimestart = $forum->assesstimestart;
3353             $ratings->assesstimefinish = $forum->assesstimefinish;
3354             $ratings->allow = $canrate;
3356             if ($ratings->allow) {
3357                 echo '<form id="form" method="post" action="rate.php">';
3358                 echo '<div class="ratingform">';
3359                 echo '<input type="hidden" name="id" value="'.$course->id.'" />';
3360                 echo '<input type="hidden" name="forumid" value="'.$forum->id.'" />';
3361                 $ratingsformused = true;
3362             }
3363         }
3364     }
3366     $post->forum = $forum->id;   // Add the forum id to the post object, later used by forum_print_post
3367     $post->forumtype = $forum->type;
3369     $post->subject = format_string($post->subject);
3371     if (forum_tp_can_track_forums($forum)) {
3372         if ($forumtracked = forum_tp_is_tracked($forum)) {
3373             $user_read_array = forum_tp_get_discussion_read_records($USER->id, $post->discussion);
3374         } else {
3375             $user_read_array = array();
3376         }
3377     } else {
3378         $forumtracked = false;
3379         $user_read_array = array();
3380     }
3382     if (forum_print_post($post, $course->id, $ownpost, $reply, $link=false, $ratings,
3383                          '', '', (!$forumtracked || isset($user_read_array[$post->id]) || forum_tp_is_post_old($post)))) {
3384         $ratingsmenuused = true;
3385     }
3387     switch ($mode) {
3388         case FORUM_MODE_FLATOLDEST :
3389         case FORUM_MODE_FLATNEWEST :
3390         default:
3391             if (forum_print_posts_flat($post->discussion, $course->id, $mode, $ratings, $reply,
3392                                        $user_read_array, $post->forum)) {
3393                 $ratingsmenuused = true;
3394             }
3395             break;
3397         case FORUM_MODE_THREADED :
3398             if (forum_print_posts_threaded($post->id, $course->id, 0, $ratings, $reply,
3399                                            $user_read_array, $post->forum)) {
3400                 $ratingsmenuused = true;
3401             }
3402             break;
3404         case FORUM_MODE_NESTED :
3405             if (forum_print_posts_nested($post->id, $course->id, $ratings, $reply,
3406                                          $user_read_array, $post->forum)) {
3407                 $ratingsmenuused = true;
3408             }
3409             break;
3410     }
3412     if ($ratingsformused) {
3413         if ($ratingsmenuused) {
3414             echo '<div class="ratingsubmit">';
3415             echo '<input type="submit" value="'.get_string('sendinratings', 'forum').'" />';
3416             if ($forum->scale < 0) {
3417                 if ($scale = get_record("scale", "id", abs($forum->scale))) {
3418                     print_scale_menu_helpbutton($course->id, $scale );
3419                 }
3420             }
3421             echo '</div>';
3422         }
3424         echo '</div>';
3425         echo '</form>';
3426     }
3430 function forum_print_posts_flat($discussion, $courseid, $direction, $ratings, $reply, &$user_read_array, $forumid=0) {
3431     global $USER, $CFG;
3433     $link  = false;
3434     $ratingsmenuused = false;
3436     if ($direction < 0) {
3437         $sort = "ORDER BY created DESC";
3438     } else {
3439         $sort = "ORDER BY created ASC";
3440     }
3442     if ($posts = forum_get_discussion_posts($discussion, $sort, $forumid)) {
3443         foreach ($posts as $post) {
3445             $post->subject = format_string($post->subject);
3447             $ownpost = ($USER->id == $post->userid);
3448             if (forum_print_post($post, $courseid, $ownpost, $reply, $link, $ratings,
3449                                  '', '', (isset($user_read_array[$post->id]) || forum_tp_is_post_old($post)))) {
3450                 $ratingsmenuused = true;
3451             }
3452         }
3453     }
3455     return $ratingsmenuused;
3459 function forum_print_posts_threaded($parent, $courseid, $depth, $ratings, $reply, &$user_read_array, $forumid=0) {
3460     global $USER, $CFG;
3462     $link  = false;
3463     $ratingsmenuused = false;
3465     $istracking = forum_tp_can_track_forums($forumid) && forum_tp_is_tracked($forumid);
3467     if ($posts = forum_get_child_posts($parent, $forumid)) {
3469         if (!$cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
3470             error('Course Module ID was incorrect');
3471         }
3472         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3473         $canviewfullnames = has_capability('moodle/site:viewfullnames', $modcontext);
3475         foreach ($posts as $post) {
3477             echo '<div class="indent">';
3478             if ($depth > 0) {
3479                 $ownpost = ($USER->id == $post->userid);
3481                 $post->subject = format_string($post->subject);
3483                 if (forum_print_post($post, $courseid, $ownpost, $reply, $link, $ratings,
3484                                      '', '', (isset($user_read_array[$post->id]) || forum_tp_is_post_old($post)))) {
3485                     $ratingsmenuused = true;
3486                 }
3487             } else {
3488                 if (!forum_user_can_see_post($post->forum,$post->discussion,$post)) {
3489                     continue;
3490                 }
3491                 $by->name = fullname($post, $canviewfullnames);
3492                 $by->date = userdate($post->modified);
3494                 if ($istracking) {
3495                     if (isset($user_read_array[$post->id]) || forum_tp_is_post_old($post)) {
3496                         $style = '<span class="forumthread read">';
3497                     } else {
3498                         $style = '<span class="forumthread unread">';
3499                     }
3500                 } else {
3501                     $style = '<span class="forumthread">';
3502                 }
3503                 echo $style."<a name=\"$post->id\"></a>".
3504                      "<a href=\"discuss.php?d=$post->discussion&amp;parent=$post->id\">".format_string($post->subject,true)."</a> ";
3505                 print_string("bynameondate", "forum", $by);
3506                 echo "</span>";
3507             }
3509             if (forum_print_posts_threaded($post->id, $courseid, $depth-1, $ratings, $reply,
3510                                            $user_read_array, $forumid)) {
3511                 $ratingsmenuused = true;
3512             }
3513             echo "</div>\n";
3514         }
3515     }
3516     return $ratingsmenuused;
3519 function forum_print_posts_nested($parent, $courseid, $ratings, $reply, &$user_read_array, $forumid=0) {
3520     global $USER, $CFG;
3522     $link  = false;
3523     $ratingsmenuused = false;
3525     if ($posts = forum_get_child_posts($parent, $forumid)) {
3526         foreach ($posts as $post) {
3528             echo '<div class="indent">';
3529             if (empty($USER->id)) {
3530                 $ownpost = false;
3531             } else {
3532                 $ownpost = ($USER->id == $post->userid);
3533             }
3535             $post->subject = format_string($post->subject);
3537             if (forum_print_post($post, $courseid, $ownpost, $reply, $link, $ratings,
3538                                  '', '', (isset($user_read_array[$post->id]) || forum_tp_is_post_old($post)))) {
3539                 $ratingsmenuused = true;
3540             }
3541             if (forum_print_posts_nested($post->id, $courseid, $ratings, $reply, $user_read_array, $forumid)) {
3542                 $ratingsmenuused = true;
3543             }
3544             echo "</div>\n";
3545         }
3546     }
3547     return $ratingsmenuused;
3550 function forum_get_recent_mod_activity(&$activities, &$index, $sincetime, $courseid, $cmid="0", $user="", $groupid="") {
3551 // Returns all forum posts since a given time.  If forum is specified then
3552 // this restricts the results
3554     global $CFG;
3556     if ($cmid) {
3557         $forumselect = " AND cm.id = '$cmid'";
3558     } else {
3559         $forumselect = "";
3560     }
3562     if ($user) {
3563         $userselect = " AND u.id = '$user'";
3564     } else {
3565         $userselect = "";
3566     }
3568     $posts = get_records_sql("SELECT p.*, d.name, u.firstname, u.lastname,
3569                                      u.picture, d.groupid, cm.instance, f.name,
3570                                      cm.section, cm.id AS cmid
3571                                FROM {$CFG->prefix}forum_posts p,
3572                                     {$CFG->prefix}forum_discussions d,
3573                                     {$CFG->prefix}user u,
3574                                     {$CFG->prefix}course_modules cm,
3575                                     {$CFG->prefix}forum f
3576                               WHERE p.modified > '$sincetime' $forumselect
3577                                 AND p.userid = u.id $userselect
3578                                 AND d.course = '$courseid'
3579                                 AND p.discussion = d.id
3580                                 AND cm.instance = f.id
3581                                 AND cm.course = d.course
3582                                 AND cm.course = f.course
3583                                 AND f.id = d.forum
3584                               ORDER BY p.discussion ASC,p.created ASC");
3586     if (empty($posts)) {
3587         return;
3588     }
3590     foreach ($posts as $post) {
3592         $modcontext = get_context_instance(CONTEXT_MODULE, $post->cmid);
3593         $canviewallgroups = has_capability('moodle/site:accessallgroups', $modcontext);
3595         if ($groupid and ($post->groupid != -1 and $groupid != $post->groupid and !$canviewallgroups)) {
3596             continue;
3597         }
3599         $tmpactivity = new Object;
3601         $tmpactivity->type = "forum";
3602         $tmpactivity->defaultindex = $index;
3603         $tmpactivity->instance = $post->instance;
3604         $tmpactivity->name = $post->name;
3605         $tmpactivity->section = $post->section;
3607         $tmpactivity->content->id = $post->id;
3608         $tmpactivity->content->discussion = $post->discussion;
3609         $tmpactivity->content->subject = $post->subject;
3610         $tmpactivity->content->parent = $post->parent;
3612         $tmpactivity->user->userid = $post->userid;
3613         $tmpactivity->user->fullname = fullname($post);
3614         $tmpactivity->user->picture = $post->picture;
3616         $tmpactivity->timestamp = $post->modified;
3617         $activities[] = $tmpactivity;
3619         $index++;
3620     }
3622     return;
3625 function forum_print_recent_mod_activity($activity, $course, $detail=false) {
3627     global $CFG;
3629     echo '<table border="0" cellpadding="3" cellspacing="0">';
3631     if ($activity->content->parent) {
3632         $openformat = "<font size=\"2\"><i>";
3633         $closeformat = "</i></font>";
3634     } else {
3635         $openformat = "<b>";
3636         $closeformat = "</b>";
3637     }
3639     echo "<tr><td class=\"forumpostpicture\" width=\"35\" valign=\"top\">";
3640     print_user_picture($activity->user->userid, $course, $activity->user->picture);
3641     echo "</td><td>$openformat";
3643     if ($detail) {
3644         echo "<img src=\"$CFG->modpixpath/$activity->type/icon.gif\" ".
3645              "class=\"icon\" alt=\"".strip_tags(format_string($activity->name,true))."\" />  ";
3646     }
3647     echo "<a href=\"$CFG->wwwroot/mod/forum/discuss.php?d=" . $activity->content->discussion
3648          . "#p" . $activity->content->id . "\">";
3650     echo format_string($activity->content->subject,true);
3651     echo "</a>$closeformat";
3653     echo "<br /><font size=\"2\">";
3654     echo "<a href=\"$CFG->wwwroot/user/view.php?id=" . $activity->user->userid . "&amp;course=" . "$course\">"
3655          . $activity->user->fullname . "</a>";
3656     echo " - " . userdate($activity->timestamp) . "</font></td></tr>";
3657     echo "</table>";
3659     return;
3662 function forum_change_discussionid($postid, $discussionid) {
3663 /// recursively sets the discussion field to $discussionid on $postid and all its children
3664 /// used when pruning a post
3665     set_field('forum_posts', 'discussion', $discussionid, 'id', $postid);
3666     if ($posts = get_records('forum_posts', 'parent', $postid)) {
3667         foreach ($posts as $post) {
3668             forum_change_discussionid($post->id, $discussionid);
3669         }
3670     }
3671     return true;
3674 function forum_update_subscriptions_button($courseid, $forumid) {
3675 // Prints the editing button on subscribers page
3676     global $CFG, $USER;
3678     if (!empty($USER->subscriptionsediting)) {
3679         $string = get_string('turneditingoff');
3680         $edit = "off";
3681     } else {
3682         $string = get_string('turneditingon');
3683         $edit = "on";
3684     }
3686     return "<form $CFG->frametarget method=\"get\" action=\"$CFG->wwwroot/mod/forum/subscribers.php\">".
3687            "<input type=\"hidden\" name=\"id\" value=\"$forumid\" />".
3688            "<input type=\"hidden\" name=\"edit\" value=\"$edit\" />".
3689            "<input type=\"submit\" value=\"$string\" /></form>";
3692 /*
3693  * This function gets run whenever a role is assigned to a user in a context
3694  *
3695  * @param integer $userid
3696  * @param object $context
3697  * @return bool
3698  */
3699 function forum_role_assign($userid, $context) {
3700     return forum_add_user_default_subscriptions($userid, $context);
3704 /*
3705  * This function gets run whenever a role is assigned to a user in a context
3706  *
3707  * @param integer $userid
3708  * @param object $context
3709  * @return bool
3710  */
3711 function forum_role_unassign($userid, $context) {
3712     return forum_remove_user_subscriptions($userid, $context);
3716 function forum_add_user_default_subscriptions($userid, $context) {
3717 /// Add subscriptions for new users
3719     if (empty($context->contextlevel)) {
3720         return false;
3721     }
3723     switch ($context->contextlevel) {
3725         case CONTEXT_SYSTEM:   // For the whole site
3726              if ($courses = get_records('course')) {
3727                  foreach ($courses as $course) {
3728                      if ($course->id == SITEID) {
3729                          // temporary workaround for bug MDL-7114
3730                          if ($forums = get_all_instances_in_course('forum', $course, $userid, false)) {
3731                              foreach ($forums as $forum) {
3732                                  if ($forum->forcesubscribe != FORUM_INITIALSUBSCRIBE) {
3733                                      continue;
3734                                  }
3735                                  if ($modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule)) {
3736                                      if (has_capability('mod/forum:viewdiscussion', $modcontext, $userid)) {
3737                                          forum_subscribe($userid, $forum->id);
3738                                      }
3739                                  }
3740                              }
3741                          }
3742                          continue;
3743                      }
3744                      $subcontext = get_context_instance(CONTEXT_COURSE, $course->id);
3745                      forum_add_user_default_subscriptions($userid, $subcontext);
3746                  }
3747              }
3748              break;
3750         case CONTEXT_COURSECAT:   // For a whole category
3751              if ($courses = get_records('course', 'category', $context->instanceid)) {
3752                  foreach ($courses as $course) {
3753                      if ($course->id == SITEID) {
3754                         continue; // temporary workaround for bug MDL-7114
3755                      }
3756                      $subcontext = get_context_instance(CONTEXT_COURSE, $course->id);
3757                      forum_add_user_default_subscriptions($userid, $subcontext);
3758                  }
3759              }
3760              if ($categories = get_records('course_categories', 'parent', $context->instanceid)) {
3761                  foreach ($categories as $category) {
3762                      $subcontext = get_context_instance(CONTEXT_COURSECAT, $category->id);
3763                      forum_add_user_default_subscriptions($userid, $subcontext);
3764                  }
3765              }
3766              break;
3769         case CONTEXT_COURSE:   // For a whole course
3770              if ($course = get_record('course', 'id', $context->instanceid)) {
3771                  if ($forums = get_all_instances_in_course('forum', $course, $userid, false)) {
3772                      foreach ($forums as $forum) {
3773                          if ($forum->forcesubscribe != FORUM_INITIALSUBSCRIBE) {
3774                              continue;
3775                          }
3776                          if ($modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule)) {
3777                              if (has_capability('mod/forum:viewdiscussion', $modcontext, $userid)) {
3778                                  forum_subscribe($userid, $forum->id);
3779                              }
3780                          }
3781                      }
3782                  }
3783              }
3784              break;
3786         case CONTEXT_MODULE:   // Just one forum
3787              if ($cm = get_coursemodule_from_id('forum', $context->instanceid)) {
3788                  if ($forum = get_record('forum', 'id', $cm->instance)) {
3789                      if ($forum->forcesubscribe != FORUM_INITIALSUBSCRIBE) {
3790                          continue;
3791                      }
3792                      if (has_capability('mod/forum:viewdiscussion', $context, $userid)) {
3793                          forum_subscribe($userid, $forum->id);
3794                      }
3795                  }
3796              }
3797              break;
3798     }
3800     return true;
3804 function forum_remove_user_subscriptions($userid, $context) {
3805 /// Remove subscriptions for a user in a context
3807     if (empty($context->contextlevel)) {
3808         return false;
3809     }
3811     switch ($context->contextlevel) {
3813         case CONTEXT_SYSTEM:   // For the whole site
3814             if ($courses = get_records('course')) {
3815                 foreach ($courses as $course) {
3816                     if ($course->id == SITEID) {
3817                         if ($course = get_records('course', 'id', $context->instanceid)) {
3818                              if ($forums = get_all_instances_in_course('forum', $course, $userid, true)) {
3819                                 foreach ($forums as $forum) {
3820                                      if ($modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule)) {
3821                                          if (!has_capability('mod/forum:viewdiscussion', $modcontext, $userid)) {
3822                                              forum_unsubscribe($userid, $forum->id);
3823                                          }
3824                                      }
3825                                 }
3826                             }
3827                         }
3828                         continue;
3829                     }
3830                     $subcontext = get_context_instance(CONTEXT_COURSE, $course->id);
3831                     forum_remove_user_subscriptions($userid, $subcontext);
3832                 }
3833             }
3834             break;
3836         case CONTEXT_COURSECAT:   // For a whole category
3837              if ($courses = get_records('course', 'category', $context->instanceid)) {
3838                  foreach ($courses as $course) {
3839                      $subcontext = get_context_instance(CONTEXT_COURSE, $course->id);
3840                      forum_remove_user_subscriptions($userid, $subcontext);
3841                  }
3842              }
3843              if ($categories = get_records('course_categories', 'parent', $context->instanceid)) {
3844                  foreach ($categories as $category) {
3845                      $subcontext = get_context_instance(CONTEXT_COURSECAT, $category->id);
3846                      forum_remove_user_subscriptions($userid, $subcontext);
3847                  }
3848              }
3849              break;
3851         case CONTEXT_COURSE:   // For a whole course
3852              if ($course = get_record('course', 'id', $context->instanceid)) {
3853                  if ($forums = get_all_instances_in_course('forum', $course, $userid, true)) {
3854                      foreach ($forums as $forum) {
3855                          if ($modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule)) {
3856                              if (!has_capability('mod/forum:viewdiscussion', $modcontext, $userid)) {
3857                                  forum_unsubscribe($userid, $forum->id);
3858                              }
3859                          }
3860                      }
3861                  }
3862              }
3863              break;
3865         case CONTEXT_MODULE:   // Just one forum
3866              if ($cm = get_coursemodule_from_id('forum', $context->instanceid)) {
3867                  if ($forum = get_record('forum', 'id', $cm->instance)) {
3868                      if (!has_capability('mod/forum:viewdiscussion', $context, $userid)) {
3869                          forum_unsubscribe($userid, $forum->id);
3870                      }
3871                  }
3872              }
3873              break;
3874     }
3876     return true;
3879 /// Functions to do with read tracking.
3881 function forum_tp_add_read_record($userid, $postid, $discussionid=-1, $forumid=-1) {
3882     if (($readrecord = forum_tp_get_read_records($userid, $postid)) === false) {
3883         /// New read record
3884         unset($readrecord);
3885         $readrecord->userid = $userid;
3886         $readrecord->postid = $postid;
3887         $readrecord->discussionid = $discussionid;
3888         $readrecord->forumid = $forumid;
3889         $readrecord->firstread = time();
3890         $readrecord->lastread = $readrecord->firstread;
3891         return insert_record('forum_read', $readrecord, true);
3893     } else {
3894         /// Update read record
3895         $readrecord = reset($readrecord);
3896         $readrecord->lastread = time();
3898         $update = NULL;
3899         $update->id = $readrecord->id;
3900         $update->lastread = $readrecord->lastread;
3902         /// This shouldn't happen, but just in case...
3903         if (!$readrecord->firstread) {
3904             /// Update the 'firstread' field.
3905             $update->firstread = $readrecord->lastread;
3906         }
3907         if ($discussionid > -1) {
3908             /// Update the 'discussionid' field.
3909             $update->discussionid = $discussionid;
3910         }
3911         if ($forumid > -1) {
3912             /// Update the 'forumid' field.
3913             $update->forumid = $forumid;
3914         }
3916         return update_record('forum_read', $update);
3917     }
3920 function forum_tp_get_read_records($userid=-1, $postid=-1, $discussionid=-1, $forumid=-1) {
3921     /// Returns all records in the 'forum_read' table matching the passed keys, indexed
3922     /// by userid.
3923     $select = '';
3924     if ($userid > -1) {
3925         if ($select != '') $select .= ' AND ';
3926         $select .= 'userid = \''.$userid.'\'';
3927     }
3928     if ($postid > -1) {
3929         if ($select != '') $select .= ' AND ';
3930         $select .= 'postid = \''.$postid.'\'';
3931     }
3932     if ($discussionid > -1) {
3933         if ($select != '') $select .= ' AND ';
3934         $select .= 'discussionid = \''.$discussionid.'\'';
3935     }
3936     if ($forumid > -1) {
3937         if ($select != '') $select .= ' AND ';
3938         $select .= 'forumid = \''.$forumid.'\'';
3939     }
3941     return get_records_select('forum_read', $select);
3944 function forum_tp_get_discussion_read_records($userid, $discussionid) {
3945     /// Returns all read records for the provided user and discussion, indexed by postid.
3946     $select = 'userid = \''.$userid.'\' AND discussionid = \''.$discussionid.'\'';
3947     $fields = 'postid, firstread, lastread';
3948     return get_records_select('forum_read', $select, '', $fields);
3951 function forum_tp_mark_post_read($userid, &$post, $forumid) {
3952 /// If its an old post, do nothing. If the record exists, the maintenance will clear it up later.
3953     if (!forum_tp_is_post_old($post)) {
3954         return forum_tp_add_read_record($userid, $post->id, $post->discussion, $forumid);
3955     } else {
3956         return true;
3957     }
3960 function forum_tp_mark_forum_read($userid, $forumid, $groupid=false) {
3961 /// Marks a whole forum as read, for a given user
3962     global $CFG;
3964     $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
3966     $groupsel = '';
3967     if ($groupid !== false) {
3968         $groupsel = ' AND (d.groupid = '.$groupid.' OR d.groupid = -1)';
3969     }
3971     $sql = 'SELECT p.id as postid, d.id as discussionid, d.forum as forumid '.
3972            'FROM '.$CFG->prefix.'forum_posts p '.
3973            'LEFT JOIN '.$CFG->prefix.'forum_discussions d ON p.discussion = d.id '.
3974            'LEFT JOIN '.$CFG->prefix.'forum_read r ON r.postid = p.id AND r.userid = '.$userid.' '.
3975            'WHERE d.forum = '.$forumid.$groupsel.
3976                 ' AND p.modified >= '.$cutoffdate.' AND r.id is NULL';
3978     if ($posts = get_records_sql($sql)) {
3979         foreach ($posts as $post) {
3980             forum_tp_add_read_record($userid, $post->postid, $post->discussionid, $post->forumid);
3981         }
3982         return true;
3983     }
3986 function forum_tp_mark_discussion_read($userid, $discussionid, $forumid) {
3987 /// Marks a whole discussion as read, for a given user
3988     global $CFG;
3990     $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
3992     $sql = 'SELECT p.id as postid, p.discussion as discussionid '.
3993            'FROM '.$CFG->prefix.'forum_posts p '.
3994            'LEFT JOIN '.$CFG->prefix.'forum_read r ON r.postid = p.id AND r.userid = '.$userid.' '.
3995            'WHERE p.discussion = '.$discussionid.' '.
3996                 'AND p.modified >= '.$cutoffdate.' AND r.id is NULL';
3998     if ($posts = get_records_sql($sql)) {
3999         foreach ($posts as $post) {
4000             forum_tp_add_read_record($userid, $post->postid, $post->discussionid, $forumid);
4001         }
4002         return true;
4003     }
4006 function forum_tp_is_post_read($userid, &$post) {
4007     return (forum_tp_is_post_old($post) ||
4008             (get_record('forum_read', 'userid', $userid, 'postid', $post->id) !== false));
4011 function forum_tp_is_post_old(&$post, $time=null) {
4012     global $CFG;
4014     if (is_null($time)) $time = time();
4015     return ($post->modified < ($time - ($CFG->forum_oldpostdays * 24 * 3600)));
4018 function forum_tp_count_discussion_read_records($userid, $discussionid) {
4019     /// Returns the count of records for the provided user and discussion.
4020     global $CFG;
4022     $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
4024     $sql = 'SELECT COUNT(DISTINCT p.id) '.
4025            'FROM '.$CFG->prefix.'forum_discussions d '.
4026            'LEFT JOIN '.$CFG->prefix.'forum_read r ON d.id = r.discussionid AND r.userid = '.$userid.' '.
4027            'LEFT JOIN '.$CFG->prefix.'forum_posts p ON p.discussion = d.id '.
4028                 'AND (p.modified < '.$cutoffdate.' OR p.id = r.postid) '.
4029            'WHERE d.id = '.$discussionid;
4031     return (count_records_sql($sql));
4034 function forum_tp_count_discussion_unread_posts($userid, $discussionid) {
4035     /// Returns the count of records for the provided user and discussion.
4036     global $CFG;
4038     $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
4040     $sql = 'SELECT COUNT(p.id) '.
4041            'FROM '.$CFG->prefix.'forum_posts p '.
4042            'LEFT JOIN '.$CFG->prefix.'forum_read r ON r.postid = p.id AND r.userid = '.$userid.' '.
4043            'WHERE p.discussion = '.$discussionid.' '.
4044                 'AND p.modified >= '.$cutoffdate.' AND r.id is NULL';
4046     return (count_records_sql($sql));
4049 function forum_tp_count_forum_posts($forumid, $groupid=false) {
4050     /// Returns the count of posts for the provided forum and [optionally] group.
4051     global $CFG;