MDL-15892 Added some phpdocs to explain the module parameter better
[moodle.git] / mod / forum / lib.php
1 <?php  // $Id$
3 require_once($CFG->libdir.'/filelib.php');
4 require_once($CFG->libdir.'/eventslib.php');
6 /// CONSTANTS ///////////////////////////////////////////////////////////
8 define('FORUM_MODE_FLATOLDEST', 1);
9 define('FORUM_MODE_FLATNEWEST', -1);
10 define('FORUM_MODE_THREADED', 2);
11 define('FORUM_MODE_NESTED', 3);
13 define('FORUM_FORCESUBSCRIBE', 1);
14 define('FORUM_INITIALSUBSCRIBE', 2);
15 define('FORUM_DISALLOWSUBSCRIBE',3);
17 define('FORUM_TRACKING_OFF', 0);
18 define('FORUM_TRACKING_OPTIONAL', 1);
19 define('FORUM_TRACKING_ON', 2);
21 define('FORUM_UNSET_POST_RATING', -999);
23 define ('FORUM_AGGREGATE_NONE', 0); //no ratings
24 define ('FORUM_AGGREGATE_AVG', 1);
25 define ('FORUM_AGGREGATE_COUNT', 2);
26 define ('FORUM_AGGREGATE_MAX', 3);
27 define ('FORUM_AGGREGATE_MIN', 4);
28 define ('FORUM_AGGREGATE_SUM', 5);
30 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
32 /**
33  * Code to be executed when a module is installed
34  * now is just used to register the module as message provider
35  */ 
36 function forum_install() {
37     $eventdata = new object();
38     $eventdata->modulename = 'forum';
39     $eventdata->modulefile = 'mod/forum/index.php';
40     events_trigger('message_provider_register', $eventdata); 
42     return true; 
43 }
46 /**
47  * Given an object containing all the necessary data,
48  * (defined by the form in mod_form.php) this function
49  * will create a new instance and return the id number
50  * of the new instance.
51  * @param object $forum add forum instance (with magic quotes)
52  * @return int intance id
53  */
54 function forum_add_instance($forum) {
55     global $CFG, $DB;
57     $forum->timemodified = time();
59     if (empty($forum->assessed)) {
60         $forum->assessed = 0;
61     }
63     if (empty($forum->ratingtime) or empty($forum->assessed)) {
64         $forum->assesstimestart  = 0;
65         $forum->assesstimefinish = 0;
66     }
68     if (!$forum->id = $DB->insert_record('forum', $forum)) {
69         return false;
70     }
72     if ($forum->type == 'single') {  // Create related discussion.
73         $discussion = new object();
74         $discussion->course   = $forum->course;
75         $discussion->forum    = $forum->id;
76         $discussion->name     = $forum->name;
77         $discussion->intro    = $forum->intro;
78         $discussion->assessed = $forum->assessed;
79         $discussion->format   = $forum->type;
80         $discussion->mailnow  = false;
81         $discussion->groupid  = -1;
83         if (! forum_add_discussion($discussion, $discussion->intro)) {
84             error('Could not add the discussion for this forum');
85         }
86     }
88     if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
89         // all users should be subscribed initially
90         $users = get_users_by_capability(get_context_instance(CONTEXT_COURSE, $forum->course),
91                                          'mod/forum:initialsubscriptions', 'u.id', '', '','','',null, false);
92         foreach ($users as $user) {
93             forum_subscribe($user->id, $forum->id);
94         }
95     }
97     forum_grade_item_update($forum);
99     return $forum->id;
103 /**
104  * Given an object containing all the necessary data,
105  * (defined by the form in mod_form.php) this function
106  * will update an existing instance with new data.
107  * @param object $forum forum instance (with magic quotes)
108  * @return bool success
109  */
110 function forum_update_instance($forum) {
111     global $DB;
113     $forum->timemodified = time();
114     $forum->id           = $forum->instance;
116     if (empty($forum->assessed)) {
117         $forum->assessed = 0;
118     }
120     if (empty($forum->ratingtime) or empty($forum->assessed)) {
121         $forum->assesstimestart  = 0;
122         $forum->assesstimefinish = 0;
123     }
125     $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
127     // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
128     // if  scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
129     // for count and sum aggregation types the grade we check to make sure they do not exceed the scale (i.e. max score) when calculating the grade
130     if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
131         forum_update_grades($forum); // recalculate grades for the forum
132     }
134     if ($forum->type == 'single') {  // Update related discussion and post.
135         if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
136             if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC')) {
137                 notify('Warning! There is more than one discussion in this forum - using the most recent');
138                 $discussion = array_pop($discussions);
139             } else {
140                 error('Could not find the discussion in this forum');
141             }
142         }
143         if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
144             error('Could not find the first post in this forum discussion');
145         }
147         $post->subject  = $forum->name;
148         $post->message  = $forum->intro;
149         $post->modified = $forum->timemodified;
151         if (! $DB->update_record('forum_posts', ($post))) {
152             error('Could not update the first post');
153         }
155         $discussion->name = $forum->name;
157         if (! $DB->update_record('forum_discussions', ($discussion))) {
158             error('Could not update the discussion');
159         }
160     }
162     if (!$DB->update_record('forum', $forum)) {
163         error('Can not update forum');
164     }
166     forum_grade_item_update($forum);
168     return true;
172 /**
173  * Given an ID of an instance of this module,
174  * this function will permanently delete the instance
175  * and any data that depends on it.
176  * @param int forum instance id
177  * @return bool success
178  */
179 function forum_delete_instance($id) {
180     global $DB;
182     if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
183         return false;
184     }
186     $result = true;
188     if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
189         foreach ($discussions as $discussion) {
190             if (!forum_delete_discussion($discussion, true)) {
191                 $result = false;
192             }
193         }
194     }
196     if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
197         $result = false;
198     }
200     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
202     if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
203         $result = false;
204     }
206     forum_grade_item_delete($forum);
208     return $result;
212 /**
213  * Indicates API features that the forum supports.
214  *
215  * @param string $feature
216  * @return mixed True if yes (some features may use other values)
217  */
218 function forum_supports($feature) {
219     switch($feature) {
220         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
221         case FEATURE_COMPLETION_HAS_RULES: return true;
222         default: return null;
223     }
227 /**
228  * Obtains the automatic completion state for this forum based on any conditions
229  * in forum settings.
230  *
231  * @param object $course Course
232  * @param object $cm Course-module
233  * @param int $userid User ID
234  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
235  * @return bool True if completed, false if not. (If no conditions, then return
236  *   value depends on comparison type)
237  */
238 function forum_get_completion_state($course,$cm,$userid,$type) {
239     global $CFG,$DB;
241     // Get forum details
242     if(!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
243         throw new Exception("Can't find forum {$cm->instance}");
244     }
246     $result=$type; // Default return value
248     $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
249     $postcountsql="
250 SELECT 
251     COUNT(1) 
252 FROM 
253     {forum_posts} fp 
254     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
255 WHERE
256     fp.userid=:userid AND fd.forum=:forumid";
258     if($forum->completiondiscussions) {
259         $value = $forum->completiondiscussions <=
260           $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
261           if($type==COMPLETION_AND) {
262             $result=$result && $value;
263         } else {
264             $result=$result || $value;
265         }
266     }
267     if($forum->completionreplies) {
268         $value = $forum->completionreplies <= 
269             $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
270         if($type==COMPLETION_AND) {
271             $result=$result && $value;
272         } else {
273             $result=$result || $value;
274         }
275     }
276     if($forum->completionposts) {
277         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
278         if($type==COMPLETION_AND) {
279             $result=$result && $value;
280         } else {
281             $result=$result || $value;
282         }
283     }
285     return $result; 
289 /**
290  * Function to be run periodically according to the moodle cron
291  * Finds all posts that have yet to be mailed out, and mails them
292  * out to all subscribers
293  * @return void
294  */
295 function forum_cron() {
296     global $CFG, $USER, $DB;
298     $cronuser = clone($USER);
299     $site = get_site();
301     // all users that are subscribed to any post that needs sending
302     $users = array();
304     // status arrays
305     $mailcount  = array();
306     $errorcount = array();
308     // caches
309     $discussions     = array();
310     $forums          = array();
311     $courses         = array();
312     $coursemodules   = array();
313     $subscribedusers = array();
316     // Posts older than 2 days will not be mailed.  This is to avoid the problem where
317     // cron has not been running for a long time, and then suddenly people are flooded
318     // with mail from the past few weeks or months
319     $timenow   = time();
320     $endtime   = $timenow - $CFG->maxeditingtime;
321     $starttime = $endtime - 48 * 3600;   // Two days earlier
323     if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
324         // Mark them all now as being mailed.  It's unlikely but possible there
325         // might be an error later so that a post is NOT actually mailed out,
326         // but since mail isn't crucial, we can accept this risk.  Doing it now
327         // prevents the risk of duplicated mails, which is a worse problem.
329         if (!forum_mark_old_posts_as_mailed($endtime)) {
330             mtrace('Errors occurred while trying to mark some posts as being mailed.');
331             return false;  // Don't continue trying to mail them, in case we are in a cron loop
332         }
334         // checking post validity, and adding users to loop through later
335         foreach ($posts as $pid => $post) {
337             $discussionid = $post->discussion;
338             if (!isset($discussions[$discussionid])) {
339                 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
340                     $discussions[$discussionid] = $discussion;
341                 } else {
342                     mtrace('Could not find discussion '.$discussionid);
343                     unset($posts[$pid]);
344                     continue;
345                 }
346             }
347             $forumid = $discussions[$discussionid]->forum;
348             if (!isset($forums[$forumid])) {
349                 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
350                     $forums[$forumid] = $forum;
351                 } else {
352                     mtrace('Could not find forum '.$forumid);
353                     unset($posts[$pid]);
354                     continue;
355                 }
356             }
357             $courseid = $forums[$forumid]->course;
358             if (!isset($courses[$courseid])) {
359                 if ($course = $DB->get_record('course', array('id' => $courseid))) {
360                     $courses[$courseid] = $course;
361                 } else {
362                     mtrace('Could not find course '.$courseid);
363                     unset($posts[$pid]);
364                     continue;
365                 }
366             }
367             if (!isset($coursemodules[$forumid])) {
368                 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
369                     $coursemodules[$forumid] = $cm;
370                 } else {
371                     mtrace('Could not course module for forum '.$forumid);
372                     unset($posts[$pid]);
373                     continue;
374                 }
375             }
378             // caching subscribed users of each forum
379             if (!isset($subscribedusers[$forumid])) {
380                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, false)) {
381                     foreach ($subusers as $postuser) {
382                         // do not try to mail users with stopped email
383                         if ($postuser->emailstop) {
384                             if (!empty($CFG->forum_logblocked)) {
385                                 add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
386                             }
387                             continue;
388                         }
389                         // this user is subscribed to this forum
390                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
391                         // this user is a user we have to process later
392                         $users[$postuser->id] = $postuser;
393                     }
394                     unset($subusers); // release memory
395                 }
396             }
398             $mailcount[$pid] = 0;
399             $errorcount[$pid] = 0;
400         }
401     }
403     if ($users && $posts) {
405         $urlinfo = parse_url($CFG->wwwroot);
406         $hostname = $urlinfo['host'];
408         foreach ($users as $userto) {
410             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
412             // set this so that the capabilities are cached, and environment matches receiving user
413             $USER = $userto;
415             mtrace('Processing user '.$userto->id);
417             // init caches
418             $userto->viewfullnames = array();
419             $userto->canpost       = array();
420             $userto->markposts     = array();
421             $userto->enrolledin    = array();
423             // reset the caches
424             foreach ($coursemodules as $forumid=>$unused) {
425                 $coursemodules[$forumid]->cache       = new object();
426                 $coursemodules[$forumid]->cache->caps = array();
427                 unset($coursemodules[$forumid]->uservisible);
428             }
430             foreach ($posts as $pid => $post) {
432                 // Set up the environment for the post, discussion, forum, course
433                 $discussion = $discussions[$post->discussion];
434                 $forum      = $forums[$discussion->forum];
435                 $course     = $courses[$forum->course];
436                 $cm         =& $coursemodules[$forum->id];
438                 // Do some checks  to see if we can bail out now
439                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
440                     continue; // user does not subscribe to this forum
441                 }
443                 // Verify user is enrollend in course - if not do not send any email
444                 if (!isset($userto->enrolledin[$course->id])) {
445                     $userto->enrolledin[$course->id] = has_capability('moodle/course:view', get_context_instance(CONTEXT_COURSE, $course->id));
446                 }
447                 if (!$userto->enrolledin[$course->id]) {
448                     // oops - this user should not receive anything from this course
449                     continue;
450                 }
452                 // Get info about the sending user
453                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
454                     $userfrom = $users[$post->userid];
455                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
456                     $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
457                 } else {
458                     mtrace('Could not find user '.$post->userid);
459                     continue;
460                 }
462                 // setup global $COURSE properly - needed for roles and languages
463                 course_setup($course);   // More environment
465                 // Fill caches
466                 if (!isset($userto->viewfullnames[$forum->id])) {
467                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
468                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
469                 }
470                 if (!isset($userto->canpost[$discussion->id])) {
471                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
472                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
473                 }
474                 if (!isset($userfrom->groups[$forum->id])) {
475                     if (!isset($userfrom->groups)) {
476                         $userfrom->groups = array();
477                         $users[$userfrom->id]->groups = array();
478                     }
479                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
480                     $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
481                 }
483                 // Make sure groups allow this user to see this email
484                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
485                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
486                         continue;                           // Be safe and don't send it to anyone
487                     }
489                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
490                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
491                         continue;
492                     }
493                 }
495                 // Make sure we're allowed to see it...
496                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
497                     mtrace('user '.$userto->id. ' can not see '.$post->id);
498                     continue;
499                 }
501                 // OK so we need to send the email.
503                 // Does the user want this post in a digest?  If so postpone it for now.
504                 if ($userto->maildigest > 0) {
505                     // This user wants the mails to be in digest form
506                     $queue = new object();
507                     $queue->userid       = $userto->id;
508                     $queue->discussionid = $discussion->id;
509                     $queue->postid       = $post->id;
510                     $queue->timemodified = $post->created;
511                     if (!$DB->insert_record('forum_queue', $queue)) {
512                         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.");
513                     }
514                     continue;
515                 }
518                 // Prepare to actually send the post now, and build up the content
520                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
522                 $userfrom->customheaders = array (  // Headers to make emails easier to track
523                            'Precedence: Bulk',
524                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
525                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
526                            'Message-ID: <moodlepost'.$post->id.'@'.$hostname.'>',
527                            'In-Reply-To: <moodlepost'.$post->parent.'@'.$hostname.'>',
528                            'References: <moodlepost'.$post->parent.'@'.$hostname.'>',
529                            'X-Course-Id: '.$course->id,
530                            'X-Course-Name: '.format_string($course->fullname, true)
531                 );
534                 $postsubject = "$course->shortname: ".format_string($post->subject,true);
535                 $posttext = forum_make_mail_text($course, $forum, $discussion, $post, $userfrom, $userto);
536                 $posthtml = forum_make_mail_html($course, $forum, $discussion, $post, $userfrom, $userto);
538                 // Send the post now!
540                 mtrace('Sending ', '');
541                 /*
542                 if (!$mailresult = email_to_user($userto, $userfrom, $postsubject, $posttext,
543                                                  $posthtml, '', '', $CFG->forum_replytouser)) {
544                 */
545                 $eventdata = new object();
546                 $eventdata->modulename       = 'forum';
547                 $eventdata->userfrom         = $userfrom;
548                 $eventdata->userto           = $userto;
549                 $eventdata->subject          = $postsubject;
550                 $eventdata->fullmessage      = $posttext;
551                 $eventdata->fullmessageformat = FORMAT_PLAIN;
552                 $eventdata->fullmessagehtml  = $posthtml;
553                 $eventdata->smallmessage     = '';
554                 if ( events_trigger('message_send', $eventdata) > 0){
556                     mtrace("Error: mod/forum/cron.php: Could not send out mail for id $post->id to user $userto->id".
557                          " ($userto->email) .. not trying again.");
558                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
559                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
560                     $errorcount[$post->id]++;
561                 } else if ($mailresult === 'emailstop') {
562                     // should not be reached anymore - see check above
563                 } else {
564                     $mailcount[$post->id]++;
566                 // Mark post as read if forum_usermarksread is set off
567                     if (!$CFG->forum_usermarksread) {
568                         $userto->markposts[$post->id] = $post->id;
569                     }
570                 }
572                 mtrace('post '.$post->id. ': '.$post->subject);
573             }
575             // mark processed posts as read
576             forum_tp_mark_posts_read($userto, $userto->markposts);
577         }
578     }
580     if ($posts) {
581         foreach ($posts as $post) {
582             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
583             if ($errorcount[$post->id]) {
584                 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
585             }
586         }
587     }
589     // release some memory
590     unset($subscribedusers);
591     unset($mailcount);
592     unset($errorcount);
594     $USER = clone($cronuser);
595     course_setup(SITEID);
597     $sitetimezone = $CFG->timezone;
599     // Now see if there are any digest mails waiting to be sent, and if we should send them
601     mtrace('Starting digest processing...');
603     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
605     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
606         set_config('digestmailtimelast', 0);
607     }
609     $timenow = time();
610     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
612     // Delete any really old ones (normally there shouldn't be any)
613     $weekago = $timenow - (7 * 24 * 3600);
614     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
615     mtrace ('Cleaned old digest records');
617     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
619         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
621         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
623         if ($digestposts_rs->valid()) {
625             // We have work to do
626             $usermailcount = 0;
628             //caches - reuse the those filled before too
629             $discussionposts = array();
630             $userdiscussions = array();
632             foreach ($digestposts_rs as $digestpost) {
633                 if (!isset($users[$digestpost->userid])) {
634                     if ($user = $DB->get_record('user', array('id' => $digestpost->userid))) {
635                         $users[$digestpost->userid] = $user;
636                     } else {
637                         continue;
638                     }
639                 }
640                 $postuser = $users[$digestpost->userid];
641                 if ($postuser->emailstop) {
642                     if (!empty($CFG->forum_logblocked)) {
643                         add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
644                     }
645                     continue;
646                 }
648                 if (!isset($posts[$digestpost->postid])) {
649                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
650                         $posts[$digestpost->postid] = $post;
651                     } else {
652                         continue;
653                     }
654                 }
655                 $discussionid = $digestpost->discussionid;
656                 if (!isset($discussions[$discussionid])) {
657                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
658                         $discussions[$discussionid] = $discussion;
659                     } else {
660                         continue;
661                     }
662                 }
663                 $forumid = $discussions[$discussionid]->forum;
664                 if (!isset($forums[$forumid])) {
665                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
666                         $forums[$forumid] = $forum;
667                     } else {
668                         continue;
669                     }
670                 }
672                 $courseid = $forums[$forumid]->course;
673                 if (!isset($courses[$courseid])) {
674                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
675                         $courses[$courseid] = $course;
676                     } else {
677                         continue;
678                     }
679                 }
681                 if (!isset($coursemodules[$forumid])) {
682                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
683                         $coursemodules[$forumid] = $cm;
684                     } else {
685                         continue;
686                     }
687                 }
688                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
689                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
690             }
691             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
693             // Data collected, start sending out emails to each user
694             foreach ($userdiscussions as $userid => $thesediscussions) {
696                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
698                 $USER = $cronuser;
699                 course_setup(SITEID); // reset cron user language, theme and timezone settings
701                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
703                 // First of all delete all the queue entries for this user
704                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
705                 $userto = $users[$userid];
707                 // Override the language and timezone of the "current" user, so that
708                 // mail is customised for the receiver.
709                 $USER = $userto;
710                 course_setup(SITEID);
712                 // init caches
713                 $userto->viewfullnames = array();
714                 $userto->canpost       = array();
715                 $userto->markposts     = array();
717                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
719                 $headerdata = new object();
720                 $headerdata->sitename = format_string($site->fullname, true);
721                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
723                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
724                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
726                 $posthtml = "<head>";
727                 foreach ($CFG->stylesheets as $stylesheet) {
728                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
729                 }
730                 $posthtml .= "</head>\n<body id=\"email\">\n";
731                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
733                 foreach ($thesediscussions as $discussionid) {
735                     @set_time_limit(120);   // to be reset for each post
737                     $discussion = $discussions[$discussionid];
738                     $forum      = $forums[$discussion->forum];
739                     $course     = $courses[$forum->course];
740                     $cm         = $coursemodules[$forum->id];
742                     //override language
743                     course_setup($course);
745                     // Fill caches
746                     if (!isset($userto->viewfullnames[$forum->id])) {
747                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
748                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
749                     }
750                     if (!isset($userto->canpost[$discussion->id])) {
751                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
752                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
753                     }
755                     $strforums      = get_string('forums', 'forum');
756                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
757                     $canreply       = $userto->canpost[$discussion->id];
759                     $posttext .= "\n \n";
760                     $posttext .= '=====================================================================';
761                     $posttext .= "\n \n";
762                     $posttext .= "$course->shortname -> $strforums -> ".format_string($forum->name,true);
763                     if ($discussion->name != $forum->name) {
764                         $posttext  .= " -> ".format_string($discussion->name,true);
765                     }
766                     $posttext .= "\n";
768                     $posthtml .= "<p><font face=\"sans-serif\">".
769                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> -> ".
770                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
771                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
772                     if ($discussion->name == $forum->name) {
773                         $posthtml .= "</font></p>";
774                     } else {
775                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
776                     }
777                     $posthtml .= '<p>';
779                     $postsarray = $discussionposts[$discussionid];
780                     sort($postsarray);
782                     foreach ($postsarray as $postid) {
783                         $post = $posts[$postid];
785                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
786                             $userfrom = $users[$post->userid];
787                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
788                             $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
789                         } else {
790                             mtrace('Could not find user '.$post->userid);
791                             continue;
792                         }
794                         if (!isset($userfrom->groups[$forum->id])) {
795                             if (!isset($userfrom->groups)) {
796                                 $userfrom->groups = array();
797                                 $users[$userfrom->id]->groups = array();
798                             }
799                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
800                             $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
801                         }
803                         $userfrom->customheaders = array ("Precedence: Bulk");
805                         if ($userto->maildigest == 2) {
806                             // Subjects only
807                             $by = new object();
808                             $by->name = fullname($userfrom);
809                             $by->date = userdate($post->modified);
810                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
811                             $posttext .= "\n---------------------------------------------------------------------";
813                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
814                             $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>';
816                         } else {
817                             // The full treatment
818                             $posttext .= forum_make_mail_text($course, $forum, $discussion, $post, $userfrom, $userto, true);
819                             $posthtml .= forum_make_mail_post($course, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
821                         // Create an array of postid's for this user to mark as read.
822                             if (!$CFG->forum_usermarksread) {
823                                 $userto->markposts[$post->id] = $post->id;
824                             }
825                         }
826                     }
827                     if ($canunsubscribe) {
828                         $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>";
829                     } else {
830                         $posthtml .= "\n<div align=\"right\"><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
831                     }
832                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
833                 }
834                 $posthtml .= '</body>';
836                 if ($userto->mailformat != 1) {
837                     // This user DOESN'T want to receive HTML
838                     $posthtml = '';
839                 }
841                 /*
842                 if (!$mailresult =  email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml,
843                                                   '', '', $CFG->forum_replytouser)) {
844                 */
845                 $eventdata = new object();
846                 $eventdata->modulename       = 'forum';
847                 $eventdata->userfrom         = $site->shortname;
848                 $eventdata->userto           = $userto;
849                 $eventdata->subject          = $postsubject;
850                 $eventdata->fullmessage      = $posttext;
851                 $eventdata->fullmessageformat = FORMAT_PLAIN;
852                 $eventdata->fullmessagehtml  = $posthtml;
853                 $eventdata->smallmessage     = '';
854                 if ( events_trigger('message_send', $eventdata) > 0){
855                     mtrace("ERROR!");
856                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
857                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
858                 } else if ($mailresult === 'emailstop') {
859                     // should not happen anymore - see check above
860                 } else {
861                     mtrace("success.");
862                     $usermailcount++;
864                     // Mark post as read if forum_usermarksread is set off
865                     forum_tp_mark_posts_read($userto, $userto->markposts);
866                 }
867             }
868         }
869     /// We have finishied all digest emails, update $CFG->digestmailtimelast
870         set_config('digestmailtimelast', $timenow);
871     }
873     $USER = $cronuser;
874     course_setup(SITEID); // reset cron user language, theme and timezone settings
876     if (!empty($usermailcount)) {
877         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
878     }
880     if (!empty($CFG->forum_lastreadclean)) {
881         $timenow = time();
882         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
883             set_config('forum_lastreadclean', $timenow);
884             mtrace('Removing old forum read tracking info...');
885             forum_tp_clean_read_records();
886         }
887     } else {
888         set_config('forum_lastreadclean', time());
889     }
892     return true;
895 /**
896  * Builds and returns the body of the email notification in plain text.
897  *
898  * @param object $course
899  * @param object $forum
900  * @param object $discussion
901  * @param object $post
902  * @param object $userfrom
903  * @param object $userto
904  * @param boolean $bare
905  * @return string The email body in plain text format.
906  */
907 function forum_make_mail_text($course, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
908     global $CFG, $USER;
910     if (!isset($userto->viewfullnames[$forum->id])) {
911         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
912             error('Course Module ID was incorrect');
913         }
914         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
915         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
916     } else {
917         $viewfullnames = $userto->viewfullnames[$forum->id];
918     }
920     if (!isset($userto->canpost[$discussion->id])) {
921         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
922         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
923     } else {
924         $canreply = $userto->canpost[$discussion->id];
925     }
927     $by = New stdClass;
928     $by->name = fullname($userfrom, $viewfullnames);
929     $by->date = userdate($post->modified, "", $userto->timezone);
931     $strbynameondate = get_string('bynameondate', 'forum', $by);
933     $strforums = get_string('forums', 'forum');
935     $canunsubscribe = ! forum_is_forcesubscribed($forum);
937     $posttext = '';
939     if (!$bare) {
940         $posttext  = "$course->shortname -> $strforums -> ".format_string($forum->name,true);
942         if ($discussion->name != $forum->name) {
943             $posttext  .= " -> ".format_string($discussion->name,true);
944         }
945     }
947     $posttext .= "\n---------------------------------------------------------------------\n";
948     $posttext .= format_string($post->subject,true);
949     if ($bare) {
950         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
951     }
952     $posttext .= "\n".$strbynameondate."\n";
953     $posttext .= "---------------------------------------------------------------------\n";
954     $posttext .= format_text_email(trusttext_strip($post->message), $post->format);
955     $posttext .= "\n\n";
956     if ($post->attachment) {
957         $post->course = $course->id;
958         $post->forum = $forum->id;
959         $posttext .= forum_print_attachments($post, "text");
960     }
961     if (!$bare && $canreply) {
962         $posttext .= "---------------------------------------------------------------------\n";
963         $posttext .= get_string("postmailinfo", "forum", $course->shortname)."\n";
964         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
965     }
966     if (!$bare && $canunsubscribe) {
967         $posttext .= "\n---------------------------------------------------------------------\n";
968         $posttext .= get_string("unsubscribe", "forum");
969         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
970     }
972     return $posttext;
975 /**
976  * Builds and returns the body of the email notification in html format.
977  *
978  * @param object $course
979  * @param object $forum
980  * @param object $discussion
981  * @param object $post
982  * @param object $userfrom
983  * @param object $userto
984  * @return string The email text in HTML format
985  */
986 function forum_make_mail_html($course, $forum, $discussion, $post, $userfrom, $userto) {
987     global $CFG;
989     if ($userto->mailformat != 1) {  // Needs to be HTML
990         return '';
991     }
993     if (!isset($userto->canpost[$discussion->id])) {
994         $canreply = forum_user_can_post($forum, $discussion, $userto);
995     } else {
996         $canreply = $userto->canpost[$discussion->id];
997     }
999     $strforums = get_string('forums', 'forum');
1000     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1002     $posthtml = '<head>';
1003     foreach ($CFG->stylesheets as $stylesheet) {
1004         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1005     }
1006     $posthtml .= '</head>';
1007     $posthtml .= "\n<body id=\"email\">\n\n";
1009     $posthtml .= '<div class="navbar">'.
1010     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$course->shortname.'</a> &raquo; '.
1011     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1012     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1013     if ($discussion->name == $forum->name) {
1014         $posthtml .= '</div>';
1015     } else {
1016         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1017                      format_string($discussion->name,true).'</a></div>';
1018     }
1019     $posthtml .= forum_make_mail_post($course, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1021     if ($canunsubscribe) {
1022         $posthtml .= '<hr /><div align="center" class="unsubscribelink">
1023                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1024                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1025     }
1027     $posthtml .= '</body>';
1029     return $posthtml;
1033 /**
1034  *
1035  * @param object $course
1036  * @param object $user
1037  * @param object $mod TODO this is not used in this function, refactor
1038  * @param object $forum
1039  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1040  */
1041 function forum_user_outline($course, $user, $mod, $forum) {
1042     if ($count = forum_count_user_posts($forum->id, $user->id)) {
1043         if ($count->postcount > 0) {
1044             $result = new object();
1045             $result->info = get_string("numposts", "forum", $count->postcount);
1046             $result->time = $count->lastpost;
1047             return $result;
1048         }
1049     }
1050     return NULL;
1054 /**
1055  *
1056  */
1057 function forum_user_complete($course, $user, $mod, $forum) {
1058     global $CFG;
1060     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1062         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1063             error('Course Module ID was incorrect');
1064         }
1065         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1067         foreach ($posts as $post) {
1068             if (!isset($discussions[$forum->discussion])) {
1069                 continue;
1070             }
1071             $discussion = $discussions[$forum->discussion];
1072             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false, false);
1073         }
1075     } else {
1076         echo "<p>".get_string("noposts", "forum")."</p>";
1077     }
1080 /**
1081  *
1082  */
1083 function forum_print_overview($courses,&$htmlarray) {
1084     global $USER, $CFG;
1085     $LIKE = $DB->sql_ilike();
1087     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1088         return array();
1089     }
1091     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1092         return;
1093     }
1096     // get all forum logs in ONE query (much better!)
1097     $params = array();
1098     $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {log} l "
1099         ." JOIN {course_modules} cm ON cm.id = cmid "
1100         ." WHERE (";
1101     foreach ($courses as $course) {
1102         $sql .= '(l.course = ? AND l.time > ?) OR ';
1103         $params[] = $course->id;
1104         $params[] = $course->lastaccess;
1105     }
1106     $sql = substr($sql,0,-3); // take off the last OR
1108     $sql .= ") AND l.module = 'forum' AND action $LIKE 'add post%' "
1109         ." AND userid != ? GROUP BY cmid,l.course,instance";
1111     $params[] = $USER->id;
1113     if (!$new = $DB->get_records_sql($sql, $params)) {
1114         $new = array(); // avoid warnings
1115     }
1117     // also get all forum tracking stuff ONCE.
1118     $trackingforums = array();
1119     foreach ($forums as $forum) {
1120         if (forum_tp_can_track_forums($forum)) {
1121             $trackingforums[$forum->id] = $forum;
1122         }
1123     }
1125     if (count($trackingforums) > 0) {
1126         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1127         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1128             ' FROM {forum_posts} p '.
1129             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1130             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1131         $params = array($USER->id);
1133         foreach ($trackingforums as $track) {
1134             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1135             $params[] = $track->id;
1136             $params[] = get_current_group($track->course);
1137         }
1138         $sql = substr($sql,0,-3); // take off the last OR
1139         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1140         $params[] = $cutoffdate;
1142         if (!$unread = $DB->get_records_sql($sql, $params)) {
1143             $unread = array();
1144         }
1145     } else {
1146         $unread = array();
1147     }
1149     if (empty($unread) and empty($new)) {
1150         return;
1151     }
1153     $strforum = get_string('modulename','forum');
1154     $strnumunread = get_string('overviewnumunread','forum');
1155     $strnumpostssince = get_string('overviewnumpostssince','forum');
1157     foreach ($forums as $forum) {
1158         $str = '';
1159         $count = 0;
1160         $thisunread = 0;
1161         $showunread = false;
1162         // either we have something from logs, or trackposts, or nothing.
1163         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1164             $count = $new[$forum->id]->count;
1165         }
1166         if (array_key_exists($forum->id,$unread)) {
1167             $thisunread = $unread[$forum->id]->count;
1168             $showunread = true;
1169         }
1170         if ($count > 0 || $thisunread > 0) {
1171             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1172                 $forum->name.'</a></div>';
1173             $str .= '<div class="info">';
1174             $str .= $count.' '.$strnumpostssince;
1175             if (!empty($showunread)) {
1176                 $str .= '<br />'.$thisunread .' '.$strnumunread;
1177             }
1178             $str .= '</div></div>';
1179         }
1180         if (!empty($str)) {
1181             if (!array_key_exists($forum->course,$htmlarray)) {
1182                 $htmlarray[$forum->course] = array();
1183             }
1184             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1185                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1186             }
1187             $htmlarray[$forum->course]['forum'] .= $str;
1188         }
1189     }
1192 /**
1193  * Given a course and a date, prints a summary of all the new
1194  * messages posted in the course since that date
1195  * @param object $course
1196  * @param bool $viewfullnames capability
1197  * @param int $timestart
1198  * @return bool success
1199  */
1200 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1201     global $CFG, $USER, $DB;
1203     // do not use log table if possible, it may be huge and is expensive to join with other tables
1205     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1206                                               d.timestart, d.timeend, d.userid AS duserid,
1207                                               u.firstname, u.lastname, u.email, u.picture
1208                                          FROM {forum_posts} p
1209                                               JOIN {forum_discussions} d ON d.id = p.discussion
1210                                               JOIN {forum} f             ON f.id = d.forum
1211                                               JOIN {user} u              ON u.id = p.userid
1212                                         WHERE p.created > ? AND f.course = ?
1213                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1214          return false;
1215     }
1217     $modinfo =& get_fast_modinfo($course);
1219     $groupmodes = array();
1220     $cms    = array();
1222     $strftimerecent = get_string('strftimerecent');
1224     $printposts = array();
1225     foreach ($posts as $post) {
1226         if (!isset($modinfo->instances['forum'][$post->forum])) {
1227             // not visible
1228             continue;
1229         }
1230         $cm = $modinfo->instances['forum'][$post->forum];
1231         if (!$cm->uservisible) {
1232             continue;
1233         }
1234         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1236         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1237             continue;
1238         }
1240         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1241           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1242             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1243                 continue;
1244             }
1245         }
1247         $groupmode = groups_get_activity_groupmode($cm, $course);
1249         if ($groupmode) {
1250             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1251                 // oki (Open discussions have groupid -1)
1252             } else {
1253                 // separate mode
1254                 if (isguestuser()) {
1255                     // shortcut
1256                     continue;
1257                 }
1259                 if (is_null($modinfo->groups)) {
1260                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1261                 }
1263                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1264                     continue;
1265                 }
1266             }
1267         }
1269         $printposts[] = $post;
1270     }
1271     unset($posts);
1273     if (!$printposts) {
1274         return false;
1275     }
1277     print_headline(get_string('newforumposts', 'forum').':', 3);
1278     echo "\n<ul class='unlist'>\n";
1280     foreach ($printposts as $post) {
1281         $subjectclass = empty($post->parent) ? ' bold' : '';
1283         echo '<li><div class="head">'.
1284                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1285                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1286              '</div>';
1287         echo '<div class="info'.$subjectclass.'">';
1288         if (empty($post->parent)) {
1289             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1290         } else {
1291             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1292         }
1293         $post->subject = break_up_long_words(format_string($post->subject, true));
1294         echo $post->subject;
1295         echo "</a>\"</div></li>\n";
1296     }
1298     echo "</ul>\n";
1300     return true;
1303 /**
1304  * Return grade for given user or all users.
1305  *
1306  * @param int $forumid id of forum
1307  * @param int $userid optional user id, 0 means all users
1308  * @return array array of grades, false if none
1309  */
1310 function forum_get_user_grades($forum, $userid=0) {
1311     global $CFG, $DB;
1313     $params= array();
1314     if ($userid) {
1315         $params[] = $userid;
1316         $user = "AND u.id = ?";
1317     } else {
1318         $user = "";
1319     }
1321     $params[] = $forum->id;
1323     $aggtype = $forum->assessed;
1324     switch ($aggtype) {
1325         case FORUM_AGGREGATE_COUNT :
1326             $sql = "SELECT u.id, u.id AS userid, COUNT(fr.rating) AS rawgrade
1327                       FROM {user} u, {forum_posts} fp,
1328                            {forum_ratings} fr, {forum_discussions} fd
1329                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1330                            AND fr.userid != u.id AND fd.forum = ?
1331                            $user
1332                   GROUP BY u.id";
1333             break;
1334         case FORUM_AGGREGATE_MAX :
1335             $sql = "SELECT u.id, u.id AS userid, MAX(fr.rating) AS rawgrade
1336                       FROM {user} u, {forum_posts} fp,
1337                            {forum_ratings} fr, {forum_discussions} fd
1338                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1339                            AND fr.userid != u.id AND fd.forum = ?
1340                            $user
1341                   GROUP BY u.id";
1342             break;
1343         case FORUM_AGGREGATE_MIN :
1344             $sql = "SELECT u.id, u.id AS userid, MIN(fr.rating) AS rawgrade
1345                       FROM {user} u, {forum_posts} fp,
1346                            {forum_ratings} fr, {forum_discussions} fd
1347                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1348                            AND fr.userid != u.id AND fd.forum = ?
1349                            $user
1350                   GROUP BY u.id";
1351             break;
1352         case FORUM_AGGREGATE_SUM :
1353             $sql = "SELECT u.id, u.id AS userid, SUM(fr.rating) AS rawgrade
1354                      FROM {user} u, {forum_posts} fp,
1355                           {forum_ratings} fr, {forum_discussions} fd
1356                     WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1357                           AND fr.userid != u.id AND fd.forum = ?
1358                           $user
1359                  GROUP BY u.id";
1360             break;
1361         default : //avg
1362             $sql = "SELECT u.id, u.id AS userid, AVG(fr.rating) AS rawgrade
1363                       FROM {user} u, {forum_posts} fp,
1364                            {forum_ratings} fr, {forum_discussions} fd
1365                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1366                            AND fr.userid != u.id AND fd.forum = ?
1367                            $user
1368                   GROUP BY u.id";
1369             break;
1370     }
1372     if ($results = $DB->get_records_sql($sql, $params)) {
1373         // it could throw off the grading if count and sum returned a rawgrade higher than scale
1374         // so to prevent it we review the results and ensure that rawgrade does not exceed the scale, if it does we set rawgrade = scale (i.e. full credit)
1375         foreach ($results as $rid=>$result) {
1376             if ($forum->scale >= 0) {
1377                 //numeric
1378                 if ($result->rawgrade > $forum->scale) {
1379                     $results[$rid]->rawgrade = $forum->scale;
1380                 }
1381             } else {
1382                 //scales
1383                 if ($scale = $DB->get_record('scale', array('id' => -$forum->scale))) {
1384                     $scale = explode(',', $scale->scale);
1385                     $max = count($scale);
1386                     if ($result->rawgrade > $max) {
1387                         $results[$rid]->rawgrade = $max;
1388                     }
1389                 }
1390             }
1391         }
1392     }
1394     return $results;
1397 /**
1398  * Update grades by firing grade_updated event
1399  *
1400  * @param object $forum null means all forums
1401  * @param int $userid specific user only, 0 mean all
1402  * @param boolean $nullifnone return null if grade does not exist
1403  * @return void
1404  */
1405 function forum_update_grades($forum=null, $userid=0, $nullifnone=true) {
1406     global $CFG, $DB;
1408     if ($forum != null) {
1409         require_once($CFG->libdir.'/gradelib.php');
1410         if ($grades = forum_get_user_grades($forum, $userid)) {
1411             forum_grade_item_update($forum, $grades);
1413         } else if ($userid and $nullifnone) {
1414             $grade = new object();
1415             $grade->userid   = $userid;
1416             $grade->rawgrade = NULL;
1417             forum_grade_item_update($forum, $grade);
1419         } else {
1420             forum_grade_item_update($forum);
1421         }
1423     } else {
1424         $sql = "SELECT f.*, cm.idnumber as cmidnumber
1425                   FROM {forum} f, {course_modules} cm, {modules} m
1426                  WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1427         if ($rs = $DB->get_recordset_sql($sql)) {
1428             foreach ($rs as $forum) {
1429                 if ($forum->assessed) {
1430                     forum_update_grades($forum, 0, false);
1431                 } else {
1432                     forum_grade_item_update($forum);
1433                 }
1434             }
1435             $rs->close();
1436         }
1437     }
1440 /**
1441  * Create/update grade item for given forum
1442  *
1443  * @param object $forum object with extra cmidnumber
1444  * @param mixed optional array/object of grade(s); 'reset' means reset grades in gradebook
1445  * @return int 0 if ok
1446  */
1447 function forum_grade_item_update($forum, $grades=NULL) {
1448     global $CFG;
1449     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1450         require_once($CFG->libdir.'/gradelib.php');
1451     }
1453     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1455     if (!$forum->assessed or $forum->scale == 0) {
1456         $params['gradetype'] = GRADE_TYPE_NONE;
1458     } else if ($forum->scale > 0) {
1459         $params['gradetype'] = GRADE_TYPE_VALUE;
1460         $params['grademax']  = $forum->scale;
1461         $params['grademin']  = 0;
1463     } else if ($forum->scale < 0) {
1464         $params['gradetype'] = GRADE_TYPE_SCALE;
1465         $params['scaleid']   = -$forum->scale;
1466     }
1468     if ($grades  === 'reset') {
1469         $params['reset'] = true;
1470         $grades = NULL;
1471     }
1473     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1476 /**
1477  * Delete grade item for given forum
1478  *
1479  * @param object $forum object
1480  * @return object grade_item
1481  */
1482 function forum_grade_item_delete($forum) {
1483     global $CFG;
1484     require_once($CFG->libdir.'/gradelib.php');
1486     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1490 /**
1491  * Returns the users with data in one forum
1492  * (users with records in forum_subscriptions, forum_posts and forum_ratings, students)
1493  * @param int $forumid
1494  * @return mixed array or false if none
1495  */
1496 function forum_get_participants($forumid) {
1498     global $CFG, $DB;
1500     //Get students from forum_subscriptions
1501     $st_subscriptions = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1502                                          FROM {user} u,
1503                                               {forum_subscriptions} s
1504                                          WHERE s.forum = ? AND
1505                                                u.id = s.userid", array($forumid));
1506     //Get students from forum_posts
1507     $st_posts = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1508                                  FROM {user} u,
1509                                       {forum_discussions} d,
1510                                       {forum_posts} p
1511                                  WHERE d.forum = ? AND
1512                                        p.discussion = d.id AND
1513                                        u.id = p.userid", array($forumid));
1515     //Get students from forum_ratings
1516     $st_ratings = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1517                                    FROM {user} u,
1518                                         {forum_discussions} d,
1519                                         {forum_posts} p,
1520                                         {forum_ratings} r
1521                                    WHERE d.forum = ? AND
1522                                          p.discussion = d.id AND
1523                                          r.post = p.id AND
1524                                          u.id = r.userid", array($forumid));
1526     //Add st_posts to st_subscriptions
1527     if ($st_posts) {
1528         foreach ($st_posts as $st_post) {
1529             $st_subscriptions[$st_post->id] = $st_post;
1530         }
1531     }
1532     //Add st_ratings to st_subscriptions
1533     if ($st_ratings) {
1534         foreach ($st_ratings as $st_rating) {
1535             $st_subscriptions[$st_rating->id] = $st_rating;
1536         }
1537     }
1538     //Return st_subscriptions array (it contains an array of unique users)
1539     return ($st_subscriptions);
1542 /**
1543  * This function returns if a scale is being used by one forum
1544  * @param int $forumid
1545  * @param int $scaleid negative number
1546  * @return bool
1547  */
1548 function forum_scale_used ($forumid,$scaleid) {
1549     global $DB;
1550     $return = false;
1552     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1554     if (!empty($rec) && !empty($scaleid)) {
1555         $return = true;
1556     }
1558     return $return;
1561 /**
1562  * Checks if scale is being used by any instance of forum
1563  *
1564  * This is used to find out if scale used anywhere
1565  * @param $scaleid int
1566  * @return boolean True if the scale is used by any forum
1567  */
1568 function forum_scale_used_anywhere($scaleid) {
1569     global $DB;
1570     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1571         return true;
1572     } else {
1573         return false;
1574     }
1577 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1579 /**
1580  * Gets a post with all info ready for forum_print_post
1581  * Most of these joins are just to get the forum id
1582  * @param int $postid
1583  * @return mixed array of posts or false
1584  */
1585 function forum_get_post_full($postid) {
1586     global $CFG, $DB;
1588     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1589                              FROM {forum_posts} p
1590                                   JOIN {forum_discussions} d ON p.discussion = d.id
1591                                   LEFT JOIN {user} u ON p.userid = u.id
1592                             WHERE p.id = ?", array($postid));
1595 /**
1596  * Gets posts with all info ready for forum_print_post
1597  * We pass forumid in because we always know it so no need to make a
1598  * complicated join to find it out.
1599  * @return mixed array of posts or false
1600  */
1601 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1602     global $CFG, $DB;
1604     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1605                               FROM {forum_posts} p
1606                          LEFT JOIN {user} u ON p.userid = u.id
1607                              WHERE p.discussion = ?
1608                                AND p.parent > 0 $sort", array($discussion));
1611 /**
1612  * Gets all posts in discussion including top parent.
1613  * @param int $discussionid
1614  * @param string $sort
1615  * @param bool $tracking does user track the forum?
1616  * @return array of posts
1617  */
1618 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1619     global $CFG, $DB, $USER;
1621     $tr_sel  = "";
1622     $tr_join = "";
1623     $params = array();
1625     if ($tracking) {
1626         $now = time();
1627         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1628         $tr_sel  = ", fr.id AS postread";
1629         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1630         $params[] = $USER->id;
1631     }
1633     $params[] = $discussionid;
1634     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1635                                      FROM {forum_posts} p
1636                                           LEFT JOIN {user} u ON p.userid = u.id
1637                                           $tr_join
1638                                     WHERE p.discussion = ?
1639                                  ORDER BY $sort", $params)) {
1640         return array();
1641     }
1643     foreach ($posts as $pid=>$p) {
1644         if ($tracking) {
1645             if (forum_tp_is_post_old($p)) {
1646                  $posts[$pid]->postread = true;
1647             }
1648         }
1649         if (!$p->parent) {
1650             continue;
1651         }
1652         if (!isset($posts[$p->parent])) {
1653             continue; // parent does not exist??
1654         }
1655         if (!isset($posts[$p->parent]->children)) {
1656             $posts[$p->parent]->children = array();
1657         }
1658         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1659     }
1661     return $posts;
1664 /**
1665  * Gets posts with all info ready for forum_print_post
1666  * We pass forumid in because we always know it so no need to make a
1667  * complicated join to find it out.
1668  */
1669 function forum_get_child_posts($parent, $forumid) {
1670     global $CFG, $DB;
1672     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1673                               FROM {forum_posts} p
1674                          LEFT JOIN {user} u ON p.userid = u.id
1675                              WHERE p.parent = ?
1676                           ORDER BY p.created ASC", array($parent));
1679 /**
1680  * An array of forum objects that the user is allowed to read/search through.
1681  * @param $userid
1682  * @param $courseid - if 0, we look for forums throughout the whole site.
1683  * @return array of forum objects, or false if no matches
1684  *         Forum objects have the following attributes:
1685  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1686  *         viewhiddentimedposts
1687  */
1688 function forum_get_readable_forums($userid, $courseid=0) {
1690     global $CFG, $DB, $USER;
1691     require_once($CFG->dirroot.'/course/lib.php');
1693     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1694         error('The forum module is not installed');
1695     }
1697     if ($courseid) {
1698         $courses = $DB->get_records('course', array('id' => $courseid));
1699     } else {
1700         // If no course is specified, then the user can see SITE + his courses.
1701         // And admins can see all courses, so pass the $doanything flag enabled
1702         $courses1 = $DB->get_records('course', array('id' => SITEID));
1703         $courses2 = get_my_courses($userid, null, null, true);
1704         $courses = array_merge($courses1, $courses2);
1705     }
1706     if (!$courses) {
1707         return array();
1708     }
1710     $readableforums = array();
1712     foreach ($courses as $course) {
1714         $modinfo =& get_fast_modinfo($course);
1715         if (is_null($modinfo->groups)) {
1716             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1717         }
1719         if (empty($modinfo->instances['forum'])) {
1720             // hmm, no forums?
1721             continue;
1722         }
1724         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1726         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1727             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1728                 continue;
1729             }
1730             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1731             $forum = $courseforums[$forumid];
1733             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1734                 continue;
1735             }
1737          /// group access
1738             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1739                 if (is_null($modinfo->groups)) {
1740                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1741                 }
1742                 if (empty($CFG->enablegroupings)) {
1743                     $forum->onlygroups = $modinfo->groups[0];
1744                     $forum->onlygroups[] = -1;
1745                 } else if (isset($modinfo->groups[$cm->groupingid])) {
1746                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1747                     $forum->onlygroups[] = -1;
1748                 } else {
1749                     $forum->onlygroups = array(-1);
1750                 }
1751             }
1753         /// hidden timed discussions
1754             $forum->viewhiddentimedposts = true;
1755             if (!empty($CFG->forum_enabletimedposts)) {
1756                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1757                     $forum->viewhiddentimedposts = false;
1758                 }
1759             }
1761         /// qanda access
1762             if ($forum->type == 'qanda'
1763                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1765                 // We need to check whether the user has posted in the qanda forum.
1766                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1767                                                     // the user is allowed to see in this forum.
1768                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1769                     foreach ($discussionspostedin as $d) {
1770                         $forum->onlydiscussions[] = $d->id;
1771                     }
1772                 }
1773             }
1775             $readableforums[$forum->id] = $forum;
1776         }
1778         unset($modinfo);
1780     } // End foreach $courses
1782     //print_object($courses);
1783     //print_object($readableforums);
1785     return $readableforums;
1788 /**
1789  * Returns a list of posts found using an array of search terms.
1790  * @param $searchterms - array of search terms, e.g. word +word -word
1791  * @param $courseid - if 0, we search through the whole site
1792  * @param $page
1793  * @param $recordsperpage=50
1794  * @param &$totalcount
1795  * @param $extrasql
1796  * @return array of posts found
1797  */
1798 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1799                             &$totalcount, $extrasql='') {
1800     global $CFG, $DB, $USER;
1801     require_once($CFG->libdir.'/searchlib.php');
1803     $forums = forum_get_readable_forums($USER->id, $courseid);
1805     if (count($forums) == 0) {
1806         $totalcount = 0;
1807         return false;
1808     }
1810     $now = round(time(), -2); // db friendly
1812     $fullaccess = array();
1813     $where = array();
1814     $params = array();
1816     foreach ($forums as $forumid => $forum) {
1817         $select = array();
1819         if (!$forum->viewhiddentimedposts) {
1820             $select[] = "(d.userid = :userid OR (d.timestart < : AND (d.timeend = 0 OR d.timeend > :timeend)))";
1821             $params = array('userid'=>$USER->id, 'timestart'=>$now, 'timeend'=>$now);
1822         }
1824         if ($forum->type == 'qanda') {
1825             if (!empty($forum->onlydiscussions)) {
1826                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda0');
1827                 $params = array_merge($params, $discussionid_params);
1828                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
1829             } else {
1830                 $select[] = "p.parent = 0";
1831             }
1832         }
1834         if (!empty($forum->onlygroups)) {
1835             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps0');
1836             $params = array_merge($params, $groupid_params);
1837             $select[] = "d.groupid $groupid_sql";
1838         }
1840         if ($select) {
1841             $selects = implode(" AND ", $select);
1842             $where[] = "(d.forum = :forum AND $selects)";
1843             $params['forum'] = $forumid;
1844         } else {
1845             $fullaccess[] = $forumid;
1846         }
1847     }
1849     if ($fullaccess) {
1850         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula0');
1851         $params = array_merge($params, $fullid_params);
1852         $where[] = "(d.forum $fullid_sql)";
1853     }
1855     $selectdiscussion = "(".implode(" OR ", $where).")";
1857     $messagesearch = '';
1858     $searchstring = '';
1860     // Need to concat these back together for parser to work.
1861     foreach($searchterms as $searchterm){
1862         if ($searchstring != '') {
1863             $searchstring .= ' ';
1864         }
1865         $searchstring .= $searchterm;
1866     }
1868     // We need to allow quoted strings for the search. The quotes *should* be stripped
1869     // by the parser, but this should be examined carefully for security implications.
1870     $searchstring = str_replace("\\\"","\"",$searchstring);
1871     $parser = new search_parser();
1872     $lexer = new search_lexer($parser);
1874     if ($lexer->parse($searchstring)) {
1875         $parsearray = $parser->get_parsed_array();
1876     // Experimental feature under 1.8! MDL-8830
1877     // Use alternative text searches if defined
1878     // This feature only works under mysql until properly implemented for other DBs
1879     // Requires manual creation of text index for forum_posts before enabling it:
1880     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
1881     // Experimental feature under 1.8! MDL-8830
1882         if (!empty($CFG->forum_usetextsearches)) {
1883             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
1884                                                  'p.userid', 'u.id', 'u.firstname',
1885                                                  'u.lastname', 'p.modified', 'd.forum');
1886         } else {
1887             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
1888                                                  'p.userid', 'u.id', 'u.firstname',
1889                                                  'u.lastname', 'p.modified', 'd.forum');
1890         }
1891         $params = array_merge($params, $msparams);
1892     }
1894     $fromsql = "{forum_posts} p,
1895                   {forum_discussions} d,
1896                   {user} u";
1898     $selectsql = " $messagesearch
1899                AND p.discussion = d.id
1900                AND p.userid = u.id
1901                AND $selectdiscussion
1902                    $extrasql";
1904     $countsql = "SELECT COUNT(*)
1905                    FROM $fromsql
1906                   WHERE $selectsql";
1908     $searchsql = "SELECT p.*,
1909                          d.forum,
1910                          u.firstname,
1911                          u.lastname,
1912                          u.email,
1913                          u.picture,
1914                          u.imagealt
1915                     FROM $fromsql
1916                    WHERE $selectsql
1917                 ORDER BY p.modified DESC";
1919     $totalcount = $DB->count_records_sql($countsql, $params);
1921     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
1924 /**
1925  * Returns a list of ratings for all posts in discussion
1926  * @param object $discussion
1927  * @return array of ratings or false
1928  */
1929 function forum_get_all_discussion_ratings($discussion) {
1930     global $CFG, $DB;
1931     return $DB->get_records_sql("SELECT r.id, r.userid, p.id AS postid, r.rating
1932                               FROM {forum_ratings} r,
1933                                    {forum_posts} p
1934                              WHERE r.post = p.id AND p.discussion = ?
1935                              ORDER BY p.id ASC", array($discussion->id));
1938 /**
1939  * Returns a list of ratings for a particular post - sorted.
1940  * @param int $postid
1941  * @param string $sort
1942  * @return array of ratings or false
1943  */
1944 function forum_get_ratings($postid, $sort="u.firstname ASC") {
1945     global $CFG, $DB;
1946     return $DB->get_records_sql("SELECT u.*, r.rating, r.time
1947                               FROM {forum_ratings} r,
1948                                    {user} u
1949                              WHERE r.post = ?
1950                                AND r.userid = u.id
1951                              ORDER BY $sort", array($postid));
1954 /**
1955  * Returns a list of all new posts that have not been mailed yet
1956  * @param int $starttime - posts created after this time
1957  * @param int $endtime - posts created before this
1958  * @param int $now - used for timed discussions only
1959  */
1960 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
1961     global $CFG, $DB;
1963     $params = array($starttime, $endtime);
1964     if (!empty($CFG->forum_enabletimedposts)) {
1965         if (empty($now)) {
1966             $now = time();
1967         }
1968         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
1969         $params[] = $now;
1970         $params[] = $now;
1971     } else {
1972         $timedsql = "";
1973     }
1975     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
1976                               FROM {forum_posts} p
1977                                    JOIN {forum_discussions} d ON d.id = p.discussion
1978                              WHERE p.mailed = 0
1979                                    AND p.created >= ?
1980                                    AND (p.created < ? OR p.mailnow = 1)
1981                                    $timedsql
1982                           ORDER BY p.modified ASC", $params);
1985 /**
1986  * Marks posts before a certain time as being mailed already
1987  */
1988 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
1989     global $CFG, $DB;
1990     if (empty($now)) {
1991         $now = time();
1992     }
1994     if (empty($CFG->forum_enabletimedposts)) {
1995         return $DB->execute("UPDATE {forum_posts}
1996                                SET mailed = '1'
1997                              WHERE (created < ? OR mailnow = 1)
1998                                    AND mailed = 0", false, array($endtime));
2000     } else {
2001         return $DB->execute("UPDATE {forum_posts}
2002                                SET mailed = '1'
2003                              WHERE discussion NOT IN (SELECT d.id
2004                                                         FROM {forum_discussions} d
2005                                                        WHERE d.timestart > ?)
2006                                    AND (created < ? OR mailnow = 1)
2007                                    AND mailed = 0", false, array($now, $endtime));
2008     }
2011 /**
2012  * Get all the posts for a user in a forum suitable for forum_print_post
2013  */
2014 function forum_get_user_posts($forumid, $userid) {
2015     global $CFG, $DB;
2017     $timedsql = "";
2018     $params = array($forumid, $userid);
2020     if (!empty($CFG->forum_enabletimedposts)) {
2021         $cm = get_coursemodule_from_instance('forum', $forumid);
2022         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2023             $now = time();
2024             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2025             $params[] = $now;
2026             $params[] = $now;
2027         }
2028     }
2030     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2031                               FROM {forum} f
2032                                    JOIN {forum_discussions} d ON d.forum = f.id
2033                                    JOIN {forum_posts} p       ON p.discussion = d.id
2034                                    JOIN {user} u              ON u.id = p.userid
2035                              WHERE f.id = ?
2036                                    AND p.userid = ?
2037                                    $timedsql
2038                           ORDER BY p.modified ASC", $params);
2041 /**
2042  * Get all the discussions user participated in
2043  * @param int $forumid
2044  * @param int $userid
2045  * @return array or false
2046  */
2047 function forum_get_user_involved_discussions($forumid, $userid) {
2048     global $CFG, $DB;
2050     $timedsql = "";
2051     $params = array($forumid, $userid);
2052     if (!empty($CFG->forum_enabletimedposts)) {
2053         $cm = get_coursemodule_from_instance('forum', $forumid);
2054         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2055             $now = time();
2056             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2057             $params[] = $now;
2058             $params[] = $now;
2059         }
2060     }
2062     return $DB->get_records_sql("SELECT DISTINCT d.*
2063                               FROM {forum} f
2064                                    JOIN {forum_discussions} d ON d.forum = f.id
2065                                    JOIN {forum_posts} p       ON p.discussion = d.id
2066                              WHERE f.id = ?
2067                                    AND p.userid = ?
2068                                    $timedsql", $params);
2071 /**
2072  * Get all the posts for a user in a forum suitable for forum_print_post
2073  * @param int $forumid
2074  * @param int $userid
2075  * @return array of counts or false
2076  */
2077 function forum_count_user_posts($forumid, $userid) {
2078     global $CFG, $DB;
2080     $timedsql = "";
2081     $params = array($forumid, $userid);
2082     if (!empty($CFG->forum_enabletimedposts)) {
2083         $cm = get_coursemodule_from_instance('forum', $forumid);
2084         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2085             $now = time();
2086             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2087             $params[] = $now;
2088             $params[] = $now;
2089         }
2090     }
2092     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2093                              FROM {forum} f
2094                                   JOIN {forum_discussions} d ON d.forum = f.id
2095                                   JOIN {forum_posts} p       ON p.discussion = d.id
2096                                   JOIN {user} u              ON u.id = p.userid
2097                             WHERE f.id = ?
2098                                   AND p.userid = ?
2099                                   $timedsql", $params);
2102 /**
2103  * Given a log entry, return the forum post details for it.
2104  */
2105 function forum_get_post_from_log($log) {
2106     global $CFG, $DB;
2108     if ($log->action == "add post") {
2110         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2111                                            u.firstname, u.lastname, u.email, u.picture
2112                                  FROM {forum_discussions} d,
2113                                       {forum_posts} p,
2114                                       {forum} f,
2115                                       {user} u
2116                                 WHERE p.id = ?
2117                                   AND d.id = p.discussion
2118                                   AND p.userid = u.id
2119                                   AND u.deleted <> '1'
2120                                   AND f.id = d.forum", array($log->info));
2123     } else if ($log->action == "add discussion") {
2125         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2126                                            u.firstname, u.lastname, u.email, u.picture
2127                                  FROM {forum_discussions} d,
2128                                       {forum_posts} p,
2129                                       {forum} f,
2130                                       {user} u
2131                                 WHERE d.id = ?
2132                                   AND d.firstpost = p.id
2133                                   AND p.userid = u.id
2134                                   AND u.deleted <> '1'
2135                                   AND f.id = d.forum", array($log->info));
2136     }
2137     return NULL;
2140 /**
2141  * Given a discussion id, return the first post from the discussion
2142  */
2143 function forum_get_firstpost_from_discussion($discussionid) {
2144     global $CFG, $DB;
2146     return $DB->get_record_sql("SELECT p.*
2147                              FROM {forum_discussions} d,
2148                                   {forum_posts} p
2149                             WHERE d.id = ?
2150                               AND d.firstpost = p.id ", array($discussionid));
2153 /**
2154  * Returns an array of counts of replies to each discussion
2155  */
2156 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2157     global $CFG, $DB;
2159     if ($limit > 0) {
2160         $limitfrom = 0;
2161         $limitnum  = $limit;
2162     } else if ($page != -1) {
2163         $limitfrom = $page*$perpage;
2164         $limitnum  = $perpage;
2165     } else {
2166         $limitfrom = 0;
2167         $limitnum  = 0;
2168     }
2170     if ($forumsort == "") {
2171         $orderby = "";
2172         $groupby = "";
2174     } else {
2175         $orderby = "ORDER BY $forumsort";
2176         $groupby = ", ".strtolower($forumsort);
2177         $groupby = str_replace('desc', '', $groupby);
2178         $groupby = str_replace('asc', '', $groupby);
2179     }
2181     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2182         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2183                   FROM {forum_posts} p
2184                        JOIN {forum_discussions} d ON p.discussion = d.id
2185                  WHERE p.parent > 0 AND d.forum = ?
2186               GROUP BY p.discussion";
2187         return $DB->get_records_sql($sql, array($forumid));
2189     } else {
2190         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2191                   FROM {forum_posts} p
2192                        JOIN {forum_discussions} d ON p.discussion = d.id
2193                  WHERE d.forum = ?
2194               GROUP BY p.discussion $groupby
2195               $orderby";
2196         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2197     }
2200 function forum_count_discussions($forum, $cm, $course) {
2201     global $CFG, $DB, $USER;
2203     static $cache = array();
2205     $now = round(time(), -2); // db cache friendliness
2207     $params = array($course->id);
2209     if (!isset($cache[$course->id])) {
2210         if (!empty($CFG->forum_enabletimedposts)) {
2211             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2212             $params[] = $now;
2213             $params[] = $now;
2214         } else {
2215             $timedsql = "";
2216         }
2218         $sql = "SELECT f.id, COUNT(d.id) as dcount
2219                   FROM {forum} f
2220                        JOIN {forum_discussions} d ON d.forum = f.id
2221                  WHERE f.course = ?
2222                        $timedsql
2223               GROUP BY f.id";
2225         if ($counts = $DB->get_records_sql($sql, $params)) {
2226             foreach ($counts as $count) {
2227                 $counts[$count->id] = $count->dcount;
2228             }
2229             $cache[$course->id] = $counts;
2230         } else {
2231             $cache[$course->id] = array();
2232         }
2233     }
2235     if (empty($cache[$course->id][$forum->id])) {
2236         return 0;
2237     }
2239     $groupmode = groups_get_activity_groupmode($cm, $course);
2241     if ($groupmode != SEPARATEGROUPS) {
2242         return $cache[$course->id][$forum->id];
2243     }
2245     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2246         return $cache[$course->id][$forum->id];
2247     }
2249     require_once($CFG->dirroot.'/course/lib.php');
2251     $modinfo =& get_fast_modinfo($course);
2252     if (is_null($modinfo->groups)) {
2253         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2254     }
2256     if (empty($CFG->enablegroupings)) {
2257         $mygroups = $modinfo->groups[0];
2258     } else {
2259         $mygroups = $modinfo->groups[$cm->groupingid];
2260     }
2262     // add all groups posts
2263     if (empty($mygroups)) {
2264         $mygroups = array(-1=>-1);
2265     } else {
2266         $mygroups[-1] = -1;
2267     }
2269     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2270     $params[] = $forum->id;
2272     if (!empty($CFG->forum_enabletimedposts)) {
2273         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2274         $params[] = $now;
2275         $params[] = $now;
2276     } else {
2277         $timedsql = "";
2278     }
2280     $sql = "SELECT COUNT(d.id)
2281               FROM {forum_discussions} d
2282              WHERE d.groupid IN ($mygroups) AND d.forum = ?
2283                    $timedsql";
2285     return $DB->get_field_sql($sql, $params);
2288 /**
2289  * How many unrated posts are in the given discussion for a given user?
2290  */
2291 function forum_count_unrated_posts($discussionid, $userid) {
2292     global $CFG, $DB;
2293     if ($posts = $DB->get_record_sql("SELECT count(*) as num
2294                                    FROM {forum_posts}
2295                                   WHERE parent > 0
2296                                     AND discussion = ?
2297                                     AND userid <> ? ", array($discussionid, $userid))) {
2299         if ($rated = $DB->get_record_sql("SELECT count(*) as num
2300                                        FROM {forum_posts} p,
2301                                             {forum_ratings} r
2302                                       WHERE p.discussion = ?
2303                                         AND p.id = r.post
2304                                         AND r.userid = ?", array($discussionid, $userid))) {
2305             $difference = $posts->num - $rated->num;
2306             if ($difference > 0) {
2307                 return $difference;
2308             } else {
2309                 return 0;    // Just in case there was a counting error
2310             }
2311         } else {
2312             return $posts->num;
2313         }
2314     } else {
2315         return 0;
2316     }
2319 /**
2320  * Get all discussions in a forum
2321  */
2322 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2323     global $CFG, $DB, $USER;
2325     $timelimit = '';
2327     $modcontext = null;
2329     $now = round(time(), -2);
2330     $params = array($cm->instance);
2332     if (!empty($CFG->forum_enabletimedposts)) {
2334         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2336         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2337             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2338             $params[] = $now;
2339             $params[] = $now;
2340             if (isloggedin()) {
2341                 $timelimit .= " OR d.userid = ?";
2342                 $params[] = $USER->id;
2343             }
2344             $timelimit .= ")";
2345         }
2346     }
2348     if ($limit > 0) {
2349         $limitfrom = 0;
2350         $limitnum  = $limit;
2351     } else if ($page != -1) {
2352         $limitfrom = $page*$perpage;
2353         $limitnum  = $perpage;
2354     } else {
2355         $limitfrom = 0;
2356         $limitnum  = 0;
2357     }
2359     $groupmode    = groups_get_activity_groupmode($cm);
2360     $currentgroup = groups_get_activity_group($cm);
2362     if ($groupmode) {
2363         if (empty($modcontext)) {
2364             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2365         }
2367         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2368             if ($currentgroup) {
2369                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2370                 $params[] = $currentgroup;
2371             } else {
2372                 $groupselect = "";
2373             }
2375         } else {
2376             //seprate groups without access all
2377             if ($currentgroup) {
2378                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2379                 $params[] = $currentgroup;
2380             } else {
2381                 $groupselect = "AND d.groupid = -1";
2382             }
2383         }
2384     } else {
2385         $groupselect = "";
2386     }
2389     if (empty($forumsort)) {
2390         $forumsort = "d.timemodified DESC";
2391     }
2392     if (empty($fullpost)) {
2393         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2394     } else {
2395         $postdata = "p.*";
2396     }
2398     if (empty($userlastmodified)) {  // We don't need to know this
2399         $umfields = "";
2400         $umtable  = "";
2401     } else {
2402         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2403         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2404     }
2406     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2407                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2408               FROM {forum_discussions} d
2409                    JOIN {forum_posts} p ON p.discussion = d.id
2410                    JOIN {user} u ON p.userid = u.id
2411                    $umtable
2412              WHERE d.forum = ? AND p.parent = 0
2413                    $timelimit $groupselect
2414           ORDER BY $forumsort";
2415     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2418 function forum_get_discussions_unread($cm) {
2419     global $CFG, $DB, $USER;
2421     $now = round(time(), -2);
2422     $params = array($cutoffdate);
2423     $groupmode    = groups_get_activity_groupmode($cm);
2424     $currentgroup = groups_get_activity_group($cm);
2426     if ($groupmode) {
2427         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2429         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2430             if ($currentgroup) {
2431                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2432                 $params[] = $currentgroup;
2433             } else {
2434                 $groupselect = "";
2435             }
2437         } else {
2438             //seprate groups without access all
2439             if ($currentgroup) {
2440                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2441                 $params[] = $currentgroup;
2442             } else {
2443                 $groupselect = "AND d.groupid = -1";
2444             }
2445         }
2446     } else {
2447         $groupselect = "";
2448     }
2450     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2452     if (!empty($CFG->forum_enabletimedposts)) {
2453         $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2454         $params[] = $now;
2455         $params[] = $now;
2456     } else {
2457         $timedsql = "";
2458     }
2460     $sql = "SELECT d.id, COUNT(p.id) AS unread
2461               FROM {forum_discussions} d
2462                    JOIN {forum_posts} p     ON p.discussion = d.id
2463                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2464              WHERE d.forum = {$cm->instance}
2465                    AND p.modified >= ? AND r.id is NULL
2466                    $groupselect
2467                    $timedsql
2468           GROUP BY d.id";
2469     if ($unreads = $DB->get_records_sql($sql, $params)) {
2470         foreach ($unreads as $unread) {
2471             $unreads[$unread->id] = $unread->unread;
2472         }
2473         return $unreads;
2474     } else {
2475         return array();
2476     }
2479 function forum_get_discussions_count($cm) {
2480     global $CFG, $DB, $USER;
2482     $now = round(time(), -2);
2483     $params = array($cm->instance);
2484     $groupmode    = groups_get_activity_groupmode($cm);
2485     $currentgroup = groups_get_activity_group($cm);
2487     if ($groupmode) {
2488         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2490         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2491             if ($currentgroup) {
2492                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2493                 $params[] = $currentgroup;
2494             } else {
2495                 $groupselect = "";
2496             }
2498         } else {
2499             //seprate groups without access all
2500             if ($currentgroup) {
2501                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2502                 $params[] = $currentgroup;
2503             } else {
2504                 $groupselect = "AND d.groupid = -1";
2505             }
2506         }
2507     } else {
2508         $groupselect = "";
2509     }
2511     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2513     $timelimit = "";
2515     if (!empty($CFG->forum_enabletimedposts)) {
2517         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2519         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2520             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2521             $params[] = $now;
2522             $params[] = $now;
2523             if (isloggedin()) {
2524                 $timelimit .= " OR d.userid = ?";
2525                 $params[] = $USER->id;
2526             }
2527             $timelimit .= ")";
2528         }
2529     }
2531     $sql = "SELECT COUNT(d.id)
2532               FROM {forum_discussions} d
2533                    JOIN {forum_posts} p ON p.discussion = d.id
2534              WHERE d.forum = ? AND p.parent = 0
2535                    $groupselect $timelimit";
2537     return $DB->get_field_sql($sql, $params);
2541 /**
2542  * Get all discussions started by a particular user in a course (or group)
2543  * This function no longer used ...
2544  */
2545 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2546     global $CFG, $DB;
2547     $params = array($courseid, $userid);
2548     if ($groupid) {
2549         $groupselect = " AND d.groupid = ? ";
2550         $params[] = $groupid;
2551     } else  {
2552         $groupselect = "";
2553     }
2555     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2556                                    f.type as forumtype, f.name as forumname, f.id as forumid
2557                               FROM {forum_discussions} d,
2558                                    {forum_posts} p,
2559                                    {user} u,
2560                                    {forum} f
2561                              WHERE d.course = ?
2562                                AND p.discussion = d.id
2563                                AND p.parent = 0
2564                                AND p.userid = u.id
2565                                AND u.id = ?
2566                                AND d.forum = f.id $groupselect
2567                           ORDER BY p.created DESC", $params);
2570 /**
2571  * Returns list of user objects that are subscribed to this forum
2572  */
2573 function forum_subscribed_users($course, $forum, $groupid=0) {
2575     global $CFG, $DB;
2576     $params = array($forum->id);
2578     if ($groupid) {
2579         $grouptables = ", {groups_members} gm ";
2580         $groupselect = "AND gm.groupid = ? AND u.id = gm.userid";
2581         $params[] = $groupid;
2582     } else  {
2583         $grouptables = '';
2584         $groupselect = '';
2585     }
2587     $fields ="u.id,
2588               u.username,
2589               u.firstname,
2590               u.lastname,
2591               u.maildisplay,
2592               u.mailformat,
2593               u.maildigest,
2594               u.emailstop,
2595               u.imagealt,
2596               u.email,
2597               u.city,
2598               u.country,
2599               u.lastaccess,
2600               u.lastlogin,
2601               u.picture,
2602               u.timezone,
2603               u.theme,
2604               u.lang,
2605               u.trackforums,
2606               u.mnethostid";
2608     if (forum_is_forcesubscribed($forum)) {
2609         $context = get_context_instance(CONTEXT_COURSE, $course->id);
2610         $sort = "u.email ASC";
2611         $results = get_users_by_capability($context, 'mod/forum:initialsubscriptions', $fields, $sort, '','','','', false, true);
2612     } else {
2613         $results = $DB->get_records_sql("SELECT $fields
2614                               FROM {user} u,
2615                                    {forum_subscriptions} s $grouptables
2616                              WHERE s.forum = ?
2617                                AND s.userid = u.id
2618                                AND u.deleted = 0  $groupselect
2619                           ORDER BY u.email ASC", $params);
2620     }
2622     static $guestid = null;
2624     if (is_null($guestid)) {
2625         if ($guest = guest_user()) {
2626             $guestid = $guest->id;
2627         } else {
2628             $guestid = 0;
2629         }
2630     }
2632     // Guest user should never be subscribed to a forum.
2633     unset($results[$guestid]);
2635     return $results;
2640 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2643 function forum_get_course_forum($courseid, $type) {
2644 // How to set up special 1-per-course forums
2645     global $CFG, $DB;
2647     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2648         // There should always only be ONE, but with the right combination of
2649         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2650         foreach ($forums as $forum) {
2651             return $forum;   // ie the first one
2652         }
2653     }
2655     // Doesn't exist, so create one now.
2656     $forum->course = $courseid;
2657     $forum->type = "$type";
2658     switch ($forum->type) {
2659         case "news":
2660             $forum->name  = get_string("namenews", "forum");
2661             $forum->intro = get_string("intronews", "forum");
2662             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2663             $forum->assessed = 0;
2664             if ($courseid == SITEID) {
2665                 $forum->name  = get_string("sitenews");
2666                 $forum->forcesubscribe = 0;
2667             }
2668             break;
2669         case "social":
2670             $forum->name  = get_string("namesocial", "forum");
2671             $forum->intro = get_string("introsocial", "forum");
2672             $forum->assessed = 0;
2673             $forum->forcesubscribe = 0;
2674             break;
2675         default:
2676             notify("That forum type doesn't exist!");
2677             return false;
2678             break;
2679     }
2681     $forum->timemodified = time();
2682     $forum->id = $DB->insert_record("forum", $forum);
2684     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2685         notify("Could not find forum module!!");
2686         return false;
2687     }
2688     $mod = new object();
2689     $mod->course = $courseid;
2690     $mod->module = $module->id;
2691     $mod->instance = $forum->id;
2692     $mod->section = 0;
2693     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
2694         notify("Could not add a new course module to the course '" . format_string($course->fullname) . "'");
2695         return false;
2696     }
2697     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
2698         notify("Could not add the new course module to that section");
2699         return false;
2700     }
2701     if (! $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule))) {
2702         notify("Could not update the course module with the correct section");
2703         return false;
2704     }
2705     include_once("$CFG->dirroot/course/lib.php");
2706     rebuild_course_cache($courseid);
2708     return $DB->get_record("forum", array("id" => "$forum->id"));
2712 /**
2713 * Given the data about a posting, builds up the HTML to display it and
2714 * returns the HTML in a string.  This is designed for sending via HTML email.
2715 */
2716 function forum_make_mail_post($course, $forum, $discussion, $post, $userfrom, $userto,
2717                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
2719     global $CFG;
2721     if (!isset($userto->viewfullnames[$forum->id])) {
2722         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
2723             error('Course Module ID was incorrect');
2724         }
2725         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2726         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
2727     } else {
2728         $viewfullnames = $userto->viewfullnames[$forum->id];
2729     }
2731     // format the post body
2732     $options = new object();
2733     $options->para = true;
2734     $formattedtext = format_text(trusttext_strip($post->message), $post->format, $options, $course->id);
2736     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
2738     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
2739     $output .= print_user_picture($userfrom, $course->id, $userfrom->picture, false, true);
2740     $output .= '</td>';
2742     if ($post->parent) {
2743         $output .= '<td class="topic">';
2744     } else {
2745         $output .= '<td class="topic starter">';
2746     }
2747     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
2749     $fullname = fullname($userfrom, $viewfullnames);
2750     $by = new object();
2751     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
2752     $by->date = userdate($post->modified, '', $userto->timezone);
2753     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
2755     $output .= '</td></tr>';
2757     $output .= '<tr><td class="left side" valign="top">';
2759     if (isset($userfrom->groups)) {
2760         $groups = $userfrom->groups[$forum->id];
2761     } else {
2762         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
2763             error('Course Module ID was incorrect');
2764         }
2765         $group = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
2766     }
2768     if ($groups) {
2769         $output .= print_group_picture($groups, $course->id, false, true, true);
2770     } else {
2771         $output .= '&nbsp;';
2772     }
2774     $output .= '</td><td class="content">';
2776     if ($post->attachment) {
2777         $post->course = $course->id;
2778         $output .= '<div class="attachments">';
2779         $output .= forum_print_attachments($post, 'html');
2780         $output .= "</div>";
2781     }
2783     $output .= $formattedtext;
2785 // Commands
2786     $commands = array();
2788     if ($post->parent) {
2789         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
2790                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
2791     }
2793     if ($reply) {
2794         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
2795                       get_string('reply', 'forum').'</a>';
2796     }
2798     $output .= '<div class="commands">';
2799     $output .= implode(' | ', $commands);
2800     $output .= '</div>';
2802 // Context link to post if required
2803     if ($link) {
2804         $output .= '<div class="link">';
2805         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
2806                      get_string('postincontext', 'forum').'</a>';
2807         $output .= '</div>';
2808     }
2810     if ($footer) {
2811         $output .= '<div class="footer">'.$footer.'</div>';
2812     }
2813     $output .= '</td></tr></table>'."\n\n";
2815     return $output;
2818 /**
2819  * Print a forum post
2820  *
2821  * @param object $post The post to print.
2822  * @param integer $courseid The course this post belongs to.
2823  * @param boolean $ownpost Whether this post belongs to the current user.
2824  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
2825  * @param boolean $link Just print a shortened version of the post as a link to the full post.
2826  * @param object $ratings -- I don't really know --
2827  * @param string $footer Extra stuff to print after the message.
2828  * @param string $highlight Space-separated list of terms to highlight.
2829  * @param int $post_read true, false or -99. If we already know whether this user
2830  *          has read this post, pass that in, otherwise, pass in -99, and this
2831  *          function will work it out.
2832  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
2833  *          the current user can't see this post, if this argument is true
2834  *          (the default) then print a dummy 'you can't see this post' post.
2835  *          If false, don't output anything at all.
2836  */
2837 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
2838                           $ratings=NULL, $footer="", $highlight="", $post_read=null, $dummyifcantsee=true, $istracked=null) {
2840     global $USER, $CFG;
2842     static $stredit, $strdelete, $strreply, $strparent, $strprune;
2843     static $strpruneheading, $displaymode;
2844     static $strmarkread, $strmarkunread;
2846     $post->course = $course->id;
2847     $post->forum  = $forum->id;
2849     // caching
2850     if (!isset($cm->cache)) {
2851         $cm->cache = new object();
2852     }
2854     if (!isset($cm->cache->caps)) {
2855         $cm->cache->caps = array();
2856         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2857         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
2858         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
2859         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
2860         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
2861         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
2862         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
2863         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
2864     }
2866     if (!isset($cm->uservisible)) {
2867         $cm->uservisible = coursemodule_visible_for_user($cm);
2868     }
2870     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
2871         if (!$dummyifcantsee) {
2872             return;
2873         }
2874         echo '<a id="p'.$post->id.'"></a>';
2875         echo '<table cellspacing="0" class="forumpost">';
2876         echo '<tr class="header"><td class="picture left">';
2877         //        print_user_picture($post->userid, $courseid, $post->picture);
2878         echo '</td>';
2879         if ($post->parent) {
2880             echo '<td class="topic">';
2881         } else {
2882             echo '<td class="topic starter">';
2883         }
2884         echo '<div class="subject">'.get_string('forumsubjecthidden','forum').'</div>';
2885         echo '<div class="author">';
2886         print_string('forumauthorhidden','forum');
2887         echo '</div></td></tr>';
2889         echo '<tr><td class="left side">';
2890         echo '&nbsp;';
2892         // Actual content
2894         echo '</td><td class="content">'."\n";
2895         echo get_string('forumbodyhidden','forum');
2896         echo '</td></tr></table>';
2897         return;
2898     }
2900     if (empty($stredit)) {
2901         $stredit         = get_string('edit', 'forum');
2902         $strdelete       = get_string('delete', 'forum');
2903         $strreply        = get_string('reply', 'forum');
2904         $strparent       = get_string('parent', 'forum');
2905         $strpruneheading = get_string('pruneheading', 'forum');
2906         $strprune        = get_string('prune', 'forum');
2907         $displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
2908         $strmarkread     = get_string('markread', 'forum');
2909         $strmarkunread   = get_string('markunread', 'forum');
2911     }
2913     $read_style = '';
2914     // ignore trackign status if not tracked or tracked param missing
2915     if ($istracked) {
2916         if (is_null($post_read)) {
2917             debugging('fetching post_read info');
2918             $post_read = forum_tp_is_post_read($USER->id, $post);
2919         }
2921         if ($post_read) {
2922             $read_style = ' read';
2923         } else {
2924             $read_style = ' unread';
2925             echo '<a name="unread"></a>';
2926         }
2927     }
2929     echo '<a id="p'.$post->id.'"></a>';
2930     echo '<table cellspacing="0" class="forumpost'.$read_style.'">';
2932     // Picture
2933     $postuser = new object();
2934     $postuser->id        = $post->userid;
2935     $postuser->firstname = $post->firstname;
2936     $postuser->lastname  = $post->lastname;
2937     $postuser->imagealt  = $post->imagealt;
2938     $postuser->picture   = $post->picture;
2940     echo '<tr class="header"><td class="picture left">';
2941     print_user_picture($postuser, $course->id);
2942     echo '</td>';
2944     if ($post->parent) {
2945         echo '<td class="topic">';
2946     } else {
2947         echo '<td class="topic starter">';
2948     }
2950     if (!empty($post->subjectnoformat)) {
2951         echo '<div class="subject">'.$post->subject.'</div>';
2952     } else {
2953         echo '<div class="subject">'.format_string($post->subject).'</div>';
2954     }
2956     echo '<div class="author">';
2957     $fullname = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
2958     $by = new object();
2959     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.
2960                 $post->userid.'&amp;course='.$course->id.'">'.$fullname.'</a>';
2961     $by->date = userdate($post->modified);
2962     print_string('bynameondate', 'forum', $by);
2963     echo '</div></td></tr>';
2965     echo '<tr><td class="left side">';
2966     if (isset($cm->cache->usersgroups)) {
2967         $groups = array();
2968         if (isset($cm->cache->usersgroups[$post->userid])) {
2969             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
2970                 $groups[$gid] = $cm->cache->groups[$gid];
2971             }
2972         }
2973     } else {
2974         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
2975     }
2977     if ($groups) {
2978         print_group_picture($groups, $course->id, false, false, true);
2979     } else {
2980         echo '&nbsp;';
2981     }
2983 // Actual content
2985     echo '</td><td class="content">'."\n";
2987     if ($post->attachment) {
2988         echo '<div class="attachments">';
2989         $attachedimages = forum_print_attachments($post);
2990         echo '</div>';
2991     } else {
2992         $attachedimages = '';
2993     }
2996     $options = new object();
2997     $options->para      = false;
2998     $options->trusttext = true;
2999     if ($link and (strlen(strip_tags($post->message)) > $CFG->forum_longpost)) {
3000         // Print shortened version
3001         echo format_text(forum_shorten_post($post->message), $post->format, $options, $course->id);
3002         $numwords = count_words(strip_tags($post->message));
3003         echo '<p><a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3004         echo get_string('readtherest', 'forum');
3005         echo '</a> ('.get_string('numwords', '', $numwords).')...</p>';
3006     } else {
3007         // Print whole message
3008         if ($highlight) {
3009             echo highlight($highlight, format_text($post->message, $post->format, $options, $course->id));
3010         } else {
3011             echo format_text($post->message, $post->format, $options, $course->id);
3012         }
3013         echo $attachedimages;
3014     }
3017 // Commands
3019     $commands = array();
3021     if ($istracked) {
3022         // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3023         // Don't display the mark read / unread controls in this case.
3024         if ($CFG->forum_usermarksread and isloggedin()) {
3025             if ($post_read) {
3026                 $mcmd = '&amp;mark=unread&amp;postid='.$post->id;
3027                 $mtxt = $strmarkunread;
3028             } else {
3029                 $mcmd = '&amp;mark=read&amp;postid='.$post->id;
3030                 $mtxt = $strmarkread;
3031             }
3032             if ($displaymode == FORUM_MODE_THREADED) {
3033                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3034                               $post->discussion.'&amp;parent='.$post->id.$mcmd.'">'.$mtxt.'</a>';
3035             } else {
3036                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3037                               $post->discussion.$mcmd.'#p'.$post->id.'">'.$mtxt.'</a>';
3038             }
3039         }
3040     }
3042     if ($post->parent) {  // Zoom in to the parent specifically
3043         if ($displaymode == FORUM_MODE_THREADED) {
3044             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3045                       $post->discussion.'&amp;parent='.$post->parent.'">'.$strparent.'</a>';
3046         } else {
3047             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3048                       $post->discussion.'#p'.$post->parent.'">'.$strparent.'</a>';
3049         }
3050     }
3052     $age = time() - $post->created;
3053     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3054     if (!$post->parent and $forum->type == 'news' and $discussion->timestart > time()) {
3055         $age = 0;
3056     }
3057     $editanypost = $cm->cache->caps['mod/forum:editanypost'];
3059     if ($ownpost or $editanypost) {
3060         if (($age < $CFG->maxeditingtime) or $editanypost) {
3061             $commands[] =  '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?edit='.$post->id.'">'.$stredit.'</a>';
3062         }
3063     }
3065     if ($cm->cache->caps['mod/forum:splitdiscussions']
3066                 && $post->parent && $forum->type != 'single') {
3068         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?prune='.$post->id.
3069                       '" title="'.$strpruneheading.'">'.$strprune.'</a>';
3070     }
3072     if (($ownpost and $age < $CFG->maxeditingtime
3073                 and $cm->cache->caps['mod/forum:deleteownpost'])
3074                 or $cm->cache->caps['mod/forum:deleteanypost']) {
3075         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?delete='.$post->id.'">'.$strdelete.'</a>';
3076     }
3078     if ($reply) {
3079         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.$strreply.'</a>';
3080     }
3082     if (true) { // @todo penny replace this later with a capability check
3083         $p = array(
3084             'postid' => $post->id,
3085         );
3086         //$commands[] = portfolio_add_button('forum_portfolio_caller', $p, '/mod/forum/lib.php', false, true);
3087     }
3089     echo '<div class="commands">';
3090     echo implode(' | ', $commands);
3091     echo '</div>';
3094 // Ratings
3096     $ratingsmenuused = false;
3097     if (!empty($ratings) and isloggedin()) {
3098         echo '<div class="ratings">';
3099         $useratings = true;
3100         if ($ratings->assesstimestart and $ratings->assesstimefinish) {
3101             if ($post->created < $ratings->assesstimestart or $post->created > $ratings->assesstimefinish) {
3102                 $useratings = false;
3103             }
3104         }
3105         if ($useratings) {
3106             $mypost = ($USER->id == $post->userid);
3108             $canviewallratings = $cm->cache->caps['mod/forum:viewanyrating'];
3110             if (isset($cm->cache->ratings)) {
3111                 if (isset($cm->cache->ratings[$post->id])) {
3112                     $allratings = $cm->cache->ratings[$post->id];
3113                 } else {
3114                     $allratings = array(); // no reatings present yet
3115                 }
3116             } else {
3117                 $allratings = NULL; // not preloaded
3118             }
3120             if (isset($cm->cache->myratings)) {
3121                 if (isset($cm->cache->myratings[$post->id])) {
3122                     $myrating = $cm->cache->myratings[$post->id];
3123                 } else {
3124                     $myrating = FORUM_UNSET_POST_RATING; // no reatings present yet
3125                 }
3126             } else {
3127                 $myrating = NULL; // not preloaded
3128             }
3130             if ($canviewallratings and !$mypost) {
3131                 forum_print_ratings($post->id, $ratings->scale, $forum->assessed, $canviewallratings, $allratings);
3132                 if (!empty($ratings->allow)) {
3133                     echo '&nbsp;';
3134                     forum_print_rating_menu($post->id, $USER->id, $ratings->scale, $myrating);
3135                     $ratingsmenuused = true;
3136                 }
3138             } else if ($mypost) {
3139                 forum_print_ratings($post->id, $ratings->scale, $forum->assessed, true, $allratings);
3141             } else if (!empty($ratings->allow) ) {
3142                 forum_print_rating_menu($post->id, $USER->id, $ratings->scale, $myrating);
3143                 $ratingsmenuused = true;
3144             }
3145         }
3146         echo '</div>';
3147     }
3149 // Link to post if required
3151     if ($link) {
3152         echo '<div class="link">';
3153         if ($post->replies == 1) {
3154             $replystring = get_string('repliesone', 'forum', $post->replies);
3155         } else {
3156             $replystring = get_string('repliesmany', 'forum', $post->replies);
3157         }
3158         echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.
3159              get_string('discussthistopic', 'forum').'</a>&nbsp;('.$replystring.')';
3160         echo '</div>';
3161     }
3163     if ($footer) {
3164         echo '<div class="footer">'.$footer.'</div>';
3165     }
3166     echo '</td></tr></table>'."\n\n";
3168     if ($istracked && !$CFG->forum_usermarksread && !$post_read) {
3169         forum_tp_mark_post_read($USER->id, $post, $forum->id);
3170     }
3172     return $ratingsmenuused;
3176 /**
3177  * This function prints the overview of a discussion in the forum listing.
3178  * It needs some discussion information and some post information, these
3179  * happen to be combined for efficiency in the $post parameter by the function
3180  * that calls this one: forum_print_latest_discussions()
3181  *
3182  * @param object $post The post object (passed by reference for speed).
3183  * @param object $forum The forum object.
3184  * @param int $group Current group.
3185  * @param string $datestring Format to use for the dates.
3186  * @param boolean $cantrack Is tracking enabled for this forum.
3187  * @param boolean $forumtracked Is the user tracking this forum.
3188  * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
3189  */
3190 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
3191                                         $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) {
3193     global $USER, $CFG;
3195     static $rowcount;
3196     static $strmarkalldread;
3198     if (empty($modcontext)) {
3199         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3200             error('Course Module ID was incorrect');
3201         }
3202         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3203     }
3205     if (!isset($rowcount)) {
3206         $rowcount = 0;
3207         $strmarkalldread = get_string('markalldread', 'forum');
3208     } else {
3209         $rowcount = ($rowcount + 1) % 2;
3210     }
3212     $post->subject = format_string($post->subject,true);
3214     echo "\n\n";
3215     echo '<tr class="discussion r'.$rowcount.'">';
3217     // Topic
3218     echo '<td class="topic starter">';
3219     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.$post->subject.'</a>';
3220     echo "</td>\n";
3222     // Picture
3223     $postuser = new object;
3224     $postuser->id = $post->userid;
3225     $postuser->firstname = $post->firstname;
3226     $postuser->lastname = $post->lastname;
3227     $postuser->imagealt = $post->imagealt;
3228     $postuser->picture = $post->picture;
3230     echo '<td class="picture">';
3231     print_user_picture($postuser, $forum->course);
3232     echo "</td>\n";
3234     // User name
3235     $fullname = fullname($post, has_capability('moodle/site:viewfullnames', $modcontext));
3236     echo '<td class="author">';
3237     echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->userid.'&amp;course='.$forum->course.'">'.$fullname.'</a>';
3238     echo "</td>\n";
3240     // Group picture
3241     if ($group !== -1) {  // Groups are active - group is a group data object or NULL
3242         echo '<td class="picture group">';
3243         if (!empty($group->picture) and empty($group->hidepicture)) {
3244             print_group_picture($group, $forum->course, false, false, true);
3245         } else if (isset($group->id)) {
3246             if($canviewparticipants) {
3247                 echo '<a href="'.$CFG->wwwroot.'/user/index.php?id='.$forum->course.'&amp;group='.$group->id.'">'.$group->name.'</a>';
3248             } else {
3249                 echo $group->name;
3250             }
3251         }
3252         echo "</td>\n";
3253     }
3255     if (has_capability('mod/forum:viewdiscussion', $modcontext)) {   // Show the column with replies
3256         echo '<td class="replies">';
3257         echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3258         echo $post->replies.'</a>';
3259         echo "</td>\n";
3261         if ($cantrack) {
3262             echo '<td class="replies">';
3263             if ($forumtracked) {
3264                 if ($post->unread > 0) {
3265                     echo '<span class="unread">';
3266                     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#unread">';
3267                     echo $post->unread;
3268                     echo '</a>';
3269                     echo '<a title="'.$strmarkalldread.'" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
3270                          $forum->id.'&amp;d='.$post->discussion.'&amp;mark=read&amp;returnpage=view.php">' .
3271                          '<img src="'.$CFG->pixpath.'/t/clear.gif" class="iconsmall" alt="'.$strmarkalldread.'" /></a>';
3272                     echo '</span>';
3273                 } else {
3274                     echo '<span class="read">';
3275                     echo $post->unread;
3276                     echo '</span>';
3277                 }
3278             } else {
3279                 echo '<span class="read">';
3280                 echo '-';
3281                 echo '</span>';
3282             }
3283             echo "</td>\n";
3284         }
3285     }
3287     echo '<td class="lastpost">';
3288     $usedate = (empty($post->timemodified)) ? $post->modified : $post->timemodified;  // Just in case
3289     $parenturl = (empty($post->lastpostid)) ? '' : '&amp;parent='.$post->lastpostid;
3290     $usermodified = new object();
3291     $usermodified->id        = $post->usermodified;
3292     $usermodified->firstname = $post->umfirstname;
3293     $usermodified->lastname  = $post->umlastname;
3294     echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->usermodified.'&amp;course='.$forum->course.'">'.
3295          fullname($usermodified).'</a><br />';
3296     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.$parenturl.'">'.
3297           userdate($usedate, $datestring).'</a>';
3298     echo "</td>\n";
3300     echo "</tr>\n\n";
3305 /**
3306  * Given a post object that we already know has a long message
3307  * this function truncates the message nicely to the first
3308  * sane place between $CFG->forum_longpost and $CFG->forum_shortpost
3309  */
3310 function forum_shorten_post($message) {
3312    global $CFG;
3314    $i = 0;
3315    $tag = false;
3316    $length = strlen($message);
3317    $count = 0;
3318    $stopzone = false;
3319    $truncate = 0;
3321    for ($i=0; $i<$length; $i++) {
3322        $char = $message[$i];
3324        switch ($char) {
3325            case "<":
3326                $tag = true;
3327                break;
3328            case ">":
3329                $tag = false;
3330                break;
3331            default:
3332                if (!$tag) {
3333                    if ($stopzone) {
3334                        if ($char == ".") {
3335                            $truncate = $i+1;
3336                            break 2;
3337                        }
3338                    }
3339                    $count++;
3340                }
3341                break;
3342        }
3343        if (!$stopzone) {
3344            if ($count > $CFG->forum_shortpost) {
3345                $stopzone = true;
3346            }
3347        }
3348    }
3350    if (!$truncate) {
3351        $truncate = $i;
3352    }
3354    return substr($message, 0, $truncate);
3358 /**
3359  * Print the multiple ratings on a post given to the current user by others.
3360  * Forumid prevents the double lookup of the forumid in discussion to determine the aggregate type
3361  * Scale is an array of ratings
3362  */
3363 function forum_print_ratings($postid, $scale, $aggregatetype, $link=true, $ratings=null) {
3365     $strratings = '';
3367     switch ($aggregatetype) {
3368         case FORUM_AGGREGATE_AVG :
3369             $agg        = forum_get_ratings_mean($postid, $scale, $ratings);
3370             $strratings = get_string("aggregateavg", "forum");
3371             break;
3372         case FORUM_AGGREGATE_COUNT :
3373             $agg        = forum_get_ratings_count($postid, $scale, $ratings);
3374             $strratings = get_string("aggregatecount", "forum");
3375             break;
3376         case FORUM_AGGREGATE_MAX :
3377             $agg        = forum_get_ratings_max($postid, $scale, $ratings);
3378             $strratings = get_string("aggregatemax", "forum");
3379             break;
3380         case FORUM_AGGREGATE_MIN :
3381             $agg        = forum_get_ratings_min($postid, $scale, $ratings);
3382             $strratings = get_string("aggregatemin", "forum");
3383             break;
3384         case FORUM_AGGREGATE_SUM :
3385             $agg        = forum_get_ratings_sum($postid, $scale, $ratings);
3386             $strratings = get_string("aggregatesum", "forum");
3387             break;
3388     }
3390     if ($agg !== "") {
3392         if (empty($strratings)) {
3393             $strratings = get_string("ratings", "forum");
3394         }
3396         echo "$strratings: ";
3397         if ($link) {
3398             link_to_popup_window ("/mod/forum/report.php?id=$postid", "ratings", $agg, 400, 600);
3399         } else {
3400             echo "$agg ";
3401         }
3402     }
3406 /**
3407  * Return the mean rating of a post given to the current user by others.
3408  * Scale is an array of possible ratings in the scale
3409  * Ratings is an optional simple array of actual ratings (just integers)
3410  * Forumid is the forum id field needed - passing it avoids a double query of lookup up the discusion and then the forum id to get the aggregate type
3411  */
3412 function forum_get_ratings_mean($postid, $scale, $ratings=NULL) {
3413     global $DB;
3414     if (is_null($ratings)) {
3415         $ratings = array();
3416         if ($rates = $DB->get_records("forum_ratings", array("post" => $postid))) {
3417             foreach ($rates as $rate) {
3418                 $ratings[] = $rate->rating;
3419             }
3420         }
3421     }
3423     $count = count($ratings);
3425     if ($count == 0 ) {
3426         return "";
3428     } else if ($count == 1) {
3429         $rating = reset($ratings);
3430         return $scale[$rating];
3432     } else {
3433         $total = 0;
3434         foreach ($ratings as $rating) {
3435             $total += $rating;
3436         }
3437         $mean = round( ((float)$total/(float)$count) + 0.001);  // Little fudge factor so that 0.5 goes UP
3439         if (isset($scale[$mean])) {
3440             return $scale[$mean]." ($count)";
3441         } else {
3442             return "$mean ($count)";    // Should never happen, hopefully