MDL-15666 - change all the portfolio plugins and callers to use exceptions
[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  */
35 function forum_install() {
36     return true;
37 }
40 /**
41  * Given an object containing all the necessary data,
42  * (defined by the form in mod_form.php) this function
43  * will create a new instance and return the id number
44  * of the new instance.
45  * @param object $forum add forum instance (with magic quotes)
46  * @return int intance id
47  */
48 function forum_add_instance($forum) {
49     global $CFG, $DB;
51     $forum->timemodified = time();
53     if (empty($forum->assessed)) {
54         $forum->assessed = 0;
55     }
57     if (empty($forum->ratingtime) or empty($forum->assessed)) {
58         $forum->assesstimestart  = 0;
59         $forum->assesstimefinish = 0;
60     }
62     if (!$forum->id = $DB->insert_record('forum', $forum)) {
63         return false;
64     }
66     if ($forum->type == 'single') {  // Create related discussion.
67         $discussion = new object();
68         $discussion->course   = $forum->course;
69         $discussion->forum    = $forum->id;
70         $discussion->name     = $forum->name;
71         $discussion->intro    = $forum->intro;
72         $discussion->assessed = $forum->assessed;
73         $discussion->format   = $forum->type;
74         $discussion->mailnow  = false;
75         $discussion->groupid  = -1;
77         $message = '';
79         if (! forum_add_discussion($discussion, null, $message)) {
80             error('Could not add the discussion for this forum');
81         }
82     }
84     if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
85         // all users should be subscribed initially
86         $users = get_users_by_capability(get_context_instance(CONTEXT_COURSE, $forum->course),
87                                          'mod/forum:initialsubscriptions', 'u.id', '', '','','',null, false);
88         foreach ($users as $user) {
89             forum_subscribe($user->id, $forum->id);
90         }
91     }
93     forum_grade_item_update($forum);
95     return $forum->id;
96 }
99 /**
100  * Given an object containing all the necessary data,
101  * (defined by the form in mod_form.php) this function
102  * will update an existing instance with new data.
103  * @param object $forum forum instance (with magic quotes)
104  * @return bool success
105  */
106 function forum_update_instance($forum) {
107     global $DB;
109     $forum->timemodified = time();
110     $forum->id           = $forum->instance;
112     if (empty($forum->assessed)) {
113         $forum->assessed = 0;
114     }
116     if (empty($forum->ratingtime) or empty($forum->assessed)) {
117         $forum->assesstimestart  = 0;
118         $forum->assesstimefinish = 0;
119     }
121     $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
123     // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
124     // if  scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
125     // 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
126     if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
127         forum_update_grades($forum); // recalculate grades for the forum
128     }
130     if ($forum->type == 'single') {  // Update related discussion and post.
131         if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
132             if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC')) {
133                 notify('Warning! There is more than one discussion in this forum - using the most recent');
134                 $discussion = array_pop($discussions);
135             } else {
136                 error('Could not find the discussion in this forum');
137             }
138         }
139         if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
140             error('Could not find the first post in this forum discussion');
141         }
143         $post->subject  = $forum->name;
144         $post->message  = $forum->intro;
145         $post->modified = $forum->timemodified;
147         if (! $DB->update_record('forum_posts', ($post))) {
148             error('Could not update the first post');
149         }
151         $discussion->name = $forum->name;
153         if (! $DB->update_record('forum_discussions', ($discussion))) {
154             error('Could not update the discussion');
155         }
156     }
158     if (!$DB->update_record('forum', $forum)) {
159         error('Can not update forum');
160     }
162     forum_grade_item_update($forum);
164     return true;
168 /**
169  * Given an ID of an instance of this module,
170  * this function will permanently delete the instance
171  * and any data that depends on it.
172  * @param int forum instance id
173  * @return bool success
174  */
175 function forum_delete_instance($id) {
176     global $DB;
178     if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
179         return false;
180     }
181     if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
182         return false;
183     }
184     if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
185         return false;
186     }
188     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
190     // now get rid of all files
191     $fs = get_file_storage();
192     $fs->delete_area_files($context->id);
194     $result = true;
196     if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
197         foreach ($discussions as $discussion) {
198             if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
199                 $result = false;
200             }
201         }
202     }
204     if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
205         $result = false;
206     }
208     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
210     if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
211         $result = false;
212     }
214     forum_grade_item_delete($forum);
216     return $result;
220 /**
221  * Indicates API features that the forum supports.
222  *
223  * @param string $feature
224  * @return mixed True if yes (some features may use other values)
225  */
226 function forum_supports($feature) {
227     switch($feature) {
228         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
229         case FEATURE_COMPLETION_HAS_RULES: return true;
230         default: return null;
231     }
235 /**
236  * Obtains the automatic completion state for this forum based on any conditions
237  * in forum settings.
238  *
239  * @param object $course Course
240  * @param object $cm Course-module
241  * @param int $userid User ID
242  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
243  * @return bool True if completed, false if not. (If no conditions, then return
244  *   value depends on comparison type)
245  */
246 function forum_get_completion_state($course,$cm,$userid,$type) {
247     global $CFG,$DB;
249     // Get forum details
250     if(!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
251         throw new Exception("Can't find forum {$cm->instance}");
252     }
254     $result=$type; // Default return value
256     $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
257     $postcountsql="
258 SELECT
259     COUNT(1)
260 FROM
261     {forum_posts} fp
262     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
263 WHERE
264     fp.userid=:userid AND fd.forum=:forumid";
266     if($forum->completiondiscussions) {
267         $value = $forum->completiondiscussions <=
268           $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
269           if($type==COMPLETION_AND) {
270             $result=$result && $value;
271         } else {
272             $result=$result || $value;
273         }
274     }
275     if($forum->completionreplies) {
276         $value = $forum->completionreplies <=
277             $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
278         if($type==COMPLETION_AND) {
279             $result=$result && $value;
280         } else {
281             $result=$result || $value;
282         }
283     }
284     if($forum->completionposts) {
285         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
286         if($type==COMPLETION_AND) {
287             $result=$result && $value;
288         } else {
289             $result=$result || $value;
290         }
291     }
293     return $result;
297 /**
298  * Function to be run periodically according to the moodle cron
299  * Finds all posts that have yet to be mailed out, and mails them
300  * out to all subscribers
301  * @return void
302  */
303 function forum_cron() {
304     global $CFG, $USER, $DB;
306     $cronuser = clone($USER);
307     $site = get_site();
309     // all users that are subscribed to any post that needs sending
310     $users = array();
312     // status arrays
313     $mailcount  = array();
314     $errorcount = array();
316     // caches
317     $discussions     = array();
318     $forums          = array();
319     $courses         = array();
320     $coursemodules   = array();
321     $subscribedusers = array();
324     // Posts older than 2 days will not be mailed.  This is to avoid the problem where
325     // cron has not been running for a long time, and then suddenly people are flooded
326     // with mail from the past few weeks or months
327     $timenow   = time();
328     $endtime   = $timenow - $CFG->maxeditingtime;
329     $starttime = $endtime - 48 * 3600;   // Two days earlier
331     if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
332         // Mark them all now as being mailed.  It's unlikely but possible there
333         // might be an error later so that a post is NOT actually mailed out,
334         // but since mail isn't crucial, we can accept this risk.  Doing it now
335         // prevents the risk of duplicated mails, which is a worse problem.
337         if (!forum_mark_old_posts_as_mailed($endtime)) {
338             mtrace('Errors occurred while trying to mark some posts as being mailed.');
339             return false;  // Don't continue trying to mail them, in case we are in a cron loop
340         }
342         // checking post validity, and adding users to loop through later
343         foreach ($posts as $pid => $post) {
345             $discussionid = $post->discussion;
346             if (!isset($discussions[$discussionid])) {
347                 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
348                     $discussions[$discussionid] = $discussion;
349                 } else {
350                     mtrace('Could not find discussion '.$discussionid);
351                     unset($posts[$pid]);
352                     continue;
353                 }
354             }
355             $forumid = $discussions[$discussionid]->forum;
356             if (!isset($forums[$forumid])) {
357                 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
358                     $forums[$forumid] = $forum;
359                 } else {
360                     mtrace('Could not find forum '.$forumid);
361                     unset($posts[$pid]);
362                     continue;
363                 }
364             }
365             $courseid = $forums[$forumid]->course;
366             if (!isset($courses[$courseid])) {
367                 if ($course = $DB->get_record('course', array('id' => $courseid))) {
368                     $courses[$courseid] = $course;
369                 } else {
370                     mtrace('Could not find course '.$courseid);
371                     unset($posts[$pid]);
372                     continue;
373                 }
374             }
375             if (!isset($coursemodules[$forumid])) {
376                 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
377                     $coursemodules[$forumid] = $cm;
378                 } else {
379                     mtrace('Could not course module for forum '.$forumid);
380                     unset($posts[$pid]);
381                     continue;
382                 }
383             }
386             // caching subscribed users of each forum
387             if (!isset($subscribedusers[$forumid])) {
388                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, false)) {
389                     foreach ($subusers as $postuser) {
390                         // do not try to mail users with stopped email
391                         if ($postuser->emailstop) {
392                             if (!empty($CFG->forum_logblocked)) {
393                                 add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
394                             }
395                             continue;
396                         }
397                         // this user is subscribed to this forum
398                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
399                         // this user is a user we have to process later
400                         $users[$postuser->id] = $postuser;
401                     }
402                     unset($subusers); // release memory
403                 }
404             }
406             $mailcount[$pid] = 0;
407             $errorcount[$pid] = 0;
408         }
409     }
411     if ($users && $posts) {
413         $urlinfo = parse_url($CFG->wwwroot);
414         $hostname = $urlinfo['host'];
416         foreach ($users as $userto) {
418             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
420             // set this so that the capabilities are cached, and environment matches receiving user
421             $USER = $userto;
423             mtrace('Processing user '.$userto->id);
425             // init caches
426             $userto->viewfullnames = array();
427             $userto->canpost       = array();
428             $userto->markposts     = array();
429             $userto->enrolledin    = array();
431             // reset the caches
432             foreach ($coursemodules as $forumid=>$unused) {
433                 $coursemodules[$forumid]->cache       = new object();
434                 $coursemodules[$forumid]->cache->caps = array();
435                 unset($coursemodules[$forumid]->uservisible);
436             }
438             foreach ($posts as $pid => $post) {
440                 // Set up the environment for the post, discussion, forum, course
441                 $discussion = $discussions[$post->discussion];
442                 $forum      = $forums[$discussion->forum];
443                 $course     = $courses[$forum->course];
444                 $cm         =& $coursemodules[$forum->id];
446                 // Do some checks  to see if we can bail out now
447                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
448                     continue; // user does not subscribe to this forum
449                 }
451                 // Verify user is enrollend in course - if not do not send any email
452                 if (!isset($userto->enrolledin[$course->id])) {
453                     $userto->enrolledin[$course->id] = has_capability('moodle/course:view', get_context_instance(CONTEXT_COURSE, $course->id));
454                 }
455                 if (!$userto->enrolledin[$course->id]) {
456                     // oops - this user should not receive anything from this course
457                     continue;
458                 }
460                 // Get info about the sending user
461                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
462                     $userfrom = $users[$post->userid];
463                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
464                     $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
465                 } else {
466                     mtrace('Could not find user '.$post->userid);
467                     continue;
468                 }
470                 // setup global $COURSE properly - needed for roles and languages
471                 course_setup($course);   // More environment
473                 // Fill caches
474                 if (!isset($userto->viewfullnames[$forum->id])) {
475                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
476                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
477                 }
478                 if (!isset($userto->canpost[$discussion->id])) {
479                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
480                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
481                 }
482                 if (!isset($userfrom->groups[$forum->id])) {
483                     if (!isset($userfrom->groups)) {
484                         $userfrom->groups = array();
485                         $users[$userfrom->id]->groups = array();
486                     }
487                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
488                     $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
489                 }
491                 // Make sure groups allow this user to see this email
492                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
493                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
494                         continue;                           // Be safe and don't send it to anyone
495                     }
497                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
498                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
499                         continue;
500                     }
501                 }
503                 // Make sure we're allowed to see it...
504                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
505                     mtrace('user '.$userto->id. ' can not see '.$post->id);
506                     continue;
507                 }
509                 // OK so we need to send the email.
511                 // Does the user want this post in a digest?  If so postpone it for now.
512                 if ($userto->maildigest > 0) {
513                     // This user wants the mails to be in digest form
514                     $queue = new object();
515                     $queue->userid       = $userto->id;
516                     $queue->discussionid = $discussion->id;
517                     $queue->postid       = $post->id;
518                     $queue->timemodified = $post->created;
519                     if (!$DB->insert_record('forum_queue', $queue)) {
520                         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.");
521                     }
522                     continue;
523                 }
526                 // Prepare to actually send the post now, and build up the content
528                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
530                 $userfrom->customheaders = array (  // Headers to make emails easier to track
531                            'Precedence: Bulk',
532                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
533                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
534                            'Message-ID: <moodlepost'.$post->id.'@'.$hostname.'>',
535                            'In-Reply-To: <moodlepost'.$post->parent.'@'.$hostname.'>',
536                            'References: <moodlepost'.$post->parent.'@'.$hostname.'>',
537                            'X-Course-Id: '.$course->id,
538                            'X-Course-Name: '.format_string($course->fullname, true)
539                 );
542                 $postsubject = "$course->shortname: ".format_string($post->subject,true);
543                 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
544                 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
546                 // Send the post now!
548                 mtrace('Sending ', '');
550                 $eventdata = new object();
551                 $eventdata->component        = 'mod/forum';
552                 $eventdata->name             = 'posts';
553                 $eventdata->userfrom         = $userfrom;
554                 $eventdata->userto           = $userto;
555                 $eventdata->subject          = $postsubject;
556                 $eventdata->fullmessage      = $posttext;
557                 $eventdata->fullmessageformat = FORMAT_PLAIN;
558                 $eventdata->fullmessagehtml  = $posthtml;
559                 $eventdata->smallmessage     = '';
560                 if ( events_trigger('message_send', $eventdata) > 0){
562                     mtrace("Error: mod/forum/cron.php: Could not send out mail for id $post->id to user $userto->id".
563                          " ($userto->email) .. not trying again.");
564                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
565                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
566                     $errorcount[$post->id]++;
567                 } else if ($mailresult === 'emailstop') {
568                     // should not be reached anymore - see check above
569                 } else {
570                     $mailcount[$post->id]++;
572                 // Mark post as read if forum_usermarksread is set off
573                     if (!$CFG->forum_usermarksread) {
574                         $userto->markposts[$post->id] = $post->id;
575                     }
576                 }
578                 mtrace('post '.$post->id. ': '.$post->subject);
579             }
581             // mark processed posts as read
582             forum_tp_mark_posts_read($userto, $userto->markposts);
583         }
584     }
586     if ($posts) {
587         foreach ($posts as $post) {
588             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
589             if ($errorcount[$post->id]) {
590                 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
591             }
592         }
593     }
595     // release some memory
596     unset($subscribedusers);
597     unset($mailcount);
598     unset($errorcount);
600     $USER = clone($cronuser);
601     course_setup(SITEID);
603     $sitetimezone = $CFG->timezone;
605     // Now see if there are any digest mails waiting to be sent, and if we should send them
607     mtrace('Starting digest processing...');
609     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
611     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
612         set_config('digestmailtimelast', 0);
613     }
615     $timenow = time();
616     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
618     // Delete any really old ones (normally there shouldn't be any)
619     $weekago = $timenow - (7 * 24 * 3600);
620     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
621     mtrace ('Cleaned old digest records');
623     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
625         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
627         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
629         if ($digestposts_rs->valid()) {
631             // We have work to do
632             $usermailcount = 0;
634             //caches - reuse the those filled before too
635             $discussionposts = array();
636             $userdiscussions = array();
638             foreach ($digestposts_rs as $digestpost) {
639                 if (!isset($users[$digestpost->userid])) {
640                     if ($user = $DB->get_record('user', array('id' => $digestpost->userid))) {
641                         $users[$digestpost->userid] = $user;
642                     } else {
643                         continue;
644                     }
645                 }
646                 $postuser = $users[$digestpost->userid];
647                 if ($postuser->emailstop) {
648                     if (!empty($CFG->forum_logblocked)) {
649                         add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
650                     }
651                     continue;
652                 }
654                 if (!isset($posts[$digestpost->postid])) {
655                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
656                         $posts[$digestpost->postid] = $post;
657                     } else {
658                         continue;
659                     }
660                 }
661                 $discussionid = $digestpost->discussionid;
662                 if (!isset($discussions[$discussionid])) {
663                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
664                         $discussions[$discussionid] = $discussion;
665                     } else {
666                         continue;
667                     }
668                 }
669                 $forumid = $discussions[$discussionid]->forum;
670                 if (!isset($forums[$forumid])) {
671                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
672                         $forums[$forumid] = $forum;
673                     } else {
674                         continue;
675                     }
676                 }
678                 $courseid = $forums[$forumid]->course;
679                 if (!isset($courses[$courseid])) {
680                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
681                         $courses[$courseid] = $course;
682                     } else {
683                         continue;
684                     }
685                 }
687                 if (!isset($coursemodules[$forumid])) {
688                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
689                         $coursemodules[$forumid] = $cm;
690                     } else {
691                         continue;
692                     }
693                 }
694                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
695                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
696             }
697             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
699             // Data collected, start sending out emails to each user
700             foreach ($userdiscussions as $userid => $thesediscussions) {
702                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
704                 $USER = $cronuser;
705                 course_setup(SITEID); // reset cron user language, theme and timezone settings
707                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
709                 // First of all delete all the queue entries for this user
710                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
711                 $userto = $users[$userid];
713                 // Override the language and timezone of the "current" user, so that
714                 // mail is customised for the receiver.
715                 $USER = $userto;
716                 course_setup(SITEID);
718                 // init caches
719                 $userto->viewfullnames = array();
720                 $userto->canpost       = array();
721                 $userto->markposts     = array();
723                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
725                 $headerdata = new object();
726                 $headerdata->sitename = format_string($site->fullname, true);
727                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
729                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
730                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
732                 $posthtml = "<head>";
733                 foreach ($CFG->stylesheets as $stylesheet) {
734                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
735                 }
736                 $posthtml .= "</head>\n<body id=\"email\">\n";
737                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
739                 foreach ($thesediscussions as $discussionid) {
741                     @set_time_limit(120);   // to be reset for each post
743                     $discussion = $discussions[$discussionid];
744                     $forum      = $forums[$discussion->forum];
745                     $course     = $courses[$forum->course];
746                     $cm         = $coursemodules[$forum->id];
748                     //override language
749                     course_setup($course);
751                     // Fill caches
752                     if (!isset($userto->viewfullnames[$forum->id])) {
753                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
754                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
755                     }
756                     if (!isset($userto->canpost[$discussion->id])) {
757                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
758                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
759                     }
761                     $strforums      = get_string('forums', 'forum');
762                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
763                     $canreply       = $userto->canpost[$discussion->id];
765                     $posttext .= "\n \n";
766                     $posttext .= '=====================================================================';
767                     $posttext .= "\n \n";
768                     $posttext .= "$course->shortname -> $strforums -> ".format_string($forum->name,true);
769                     if ($discussion->name != $forum->name) {
770                         $posttext  .= " -> ".format_string($discussion->name,true);
771                     }
772                     $posttext .= "\n";
774                     $posthtml .= "<p><font face=\"sans-serif\">".
775                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> -> ".
776                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
777                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
778                     if ($discussion->name == $forum->name) {
779                         $posthtml .= "</font></p>";
780                     } else {
781                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
782                     }
783                     $posthtml .= '<p>';
785                     $postsarray = $discussionposts[$discussionid];
786                     sort($postsarray);
788                     foreach ($postsarray as $postid) {
789                         $post = $posts[$postid];
791                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
792                             $userfrom = $users[$post->userid];
793                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
794                             $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
795                         } else {
796                             mtrace('Could not find user '.$post->userid);
797                             continue;
798                         }
800                         if (!isset($userfrom->groups[$forum->id])) {
801                             if (!isset($userfrom->groups)) {
802                                 $userfrom->groups = array();
803                                 $users[$userfrom->id]->groups = array();
804                             }
805                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
806                             $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
807                         }
809                         $userfrom->customheaders = array ("Precedence: Bulk");
811                         if ($userto->maildigest == 2) {
812                             // Subjects only
813                             $by = new object();
814                             $by->name = fullname($userfrom);
815                             $by->date = userdate($post->modified);
816                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
817                             $posttext .= "\n---------------------------------------------------------------------";
819                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
820                             $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>';
822                         } else {
823                             // The full treatment
824                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
825                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
827                         // Create an array of postid's for this user to mark as read.
828                             if (!$CFG->forum_usermarksread) {
829                                 $userto->markposts[$post->id] = $post->id;
830                             }
831                         }
832                     }
833                     if ($canunsubscribe) {
834                         $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>";
835                     } else {
836                         $posthtml .= "\n<div align=\"right\"><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
837                     }
838                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
839                 }
840                 $posthtml .= '</body>';
842                 if ($userto->mailformat != 1) {
843                     // This user DOESN'T want to receive HTML
844                     $posthtml = '';
845                 }
847                 $eventdata = new object();
848                 $eventdata->component        = 'mod/forum';
849                 $eventdata->name             = 'digests';
850                 $eventdata->userfrom         = $site->shortname;
851                 $eventdata->userto           = $userto;
852                 $eventdata->subject          = $postsubject;
853                 $eventdata->fullmessage      = $posttext;
854                 $eventdata->fullmessageformat = FORMAT_PLAIN;
855                 $eventdata->fullmessagehtml  = $posthtml;
856                 $eventdata->smallmessage     = '';
857                 if ( events_trigger('message_send', $eventdata) > 0){
858                     mtrace("ERROR!");
859                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
860                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
861                 } else if ($mailresult === 'emailstop') {
862                     // should not happen anymore - see check above
863                 } else {
864                     mtrace("success.");
865                     $usermailcount++;
867                     // Mark post as read if forum_usermarksread is set off
868                     forum_tp_mark_posts_read($userto, $userto->markposts);
869                 }
870             }
871         }
872     /// We have finishied all digest emails, update $CFG->digestmailtimelast
873         set_config('digestmailtimelast', $timenow);
874     }
876     $USER = $cronuser;
877     course_setup(SITEID); // reset cron user language, theme and timezone settings
879     if (!empty($usermailcount)) {
880         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
881     }
883     if (!empty($CFG->forum_lastreadclean)) {
884         $timenow = time();
885         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
886             set_config('forum_lastreadclean', $timenow);
887             mtrace('Removing old forum read tracking info...');
888             forum_tp_clean_read_records();
889         }
890     } else {
891         set_config('forum_lastreadclean', time());
892     }
895     return true;
898 /**
899  * Builds and returns the body of the email notification in plain text.
900  *
901  * @param object $course
902  * @param object $forum
903  * @param object $discussion
904  * @param object $post
905  * @param object $userfrom
906  * @param object $userto
907  * @param boolean $bare
908  * @return string The email body in plain text format.
909  */
910 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
911     global $CFG, $USER;
913     if (!isset($userto->viewfullnames[$forum->id])) {
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     $posttext .= forum_print_attachments($post, $cm, "text");
958     if (!$bare && $canreply) {
959         $posttext .= "---------------------------------------------------------------------\n";
960         $posttext .= get_string("postmailinfo", "forum", $course->shortname)."\n";
961         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
962     }
963     if (!$bare && $canunsubscribe) {
964         $posttext .= "\n---------------------------------------------------------------------\n";
965         $posttext .= get_string("unsubscribe", "forum");
966         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
967     }
969     return $posttext;
972 /**
973  * Builds and returns the body of the email notification in html format.
974  *
975  * @param object $course
976  * @param object $forum
977  * @param object $discussion
978  * @param object $post
979  * @param object $userfrom
980  * @param object $userto
981  * @return string The email text in HTML format
982  */
983 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
984     global $CFG;
986     if ($userto->mailformat != 1) {  // Needs to be HTML
987         return '';
988     }
990     if (!isset($userto->canpost[$discussion->id])) {
991         $canreply = forum_user_can_post($forum, $discussion, $userto);
992     } else {
993         $canreply = $userto->canpost[$discussion->id];
994     }
996     $strforums = get_string('forums', 'forum');
997     $canunsubscribe = ! forum_is_forcesubscribed($forum);
999     $posthtml = '<head>';
1000     foreach ($CFG->stylesheets as $stylesheet) {
1001         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1002     }
1003     $posthtml .= '</head>';
1004     $posthtml .= "\n<body id=\"email\">\n\n";
1006     $posthtml .= '<div class="navbar">'.
1007     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$course->shortname.'</a> &raquo; '.
1008     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1009     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1010     if ($discussion->name == $forum->name) {
1011         $posthtml .= '</div>';
1012     } else {
1013         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1014                      format_string($discussion->name,true).'</a></div>';
1015     }
1016     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1018     if ($canunsubscribe) {
1019         $posthtml .= '<hr /><div align="center" class="unsubscribelink">
1020                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1021                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1022     }
1024     $posthtml .= '</body>';
1026     return $posthtml;
1030 /**
1031  *
1032  * @param object $course
1033  * @param object $user
1034  * @param object $mod TODO this is not used in this function, refactor
1035  * @param object $forum
1036  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1037  */
1038 function forum_user_outline($course, $user, $mod, $forum) {
1039     if ($count = forum_count_user_posts($forum->id, $user->id)) {
1040         if ($count->postcount > 0) {
1041             $result = new object();
1042             $result->info = get_string("numposts", "forum", $count->postcount);
1043             $result->time = $count->lastpost;
1044             return $result;
1045         }
1046     }
1047     return NULL;
1051 /**
1052  *
1053  */
1054 function forum_user_complete($course, $user, $mod, $forum) {
1055     global $CFG,$USER;
1057     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1059         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1060             error('Course Module ID was incorrect');
1061         }
1062         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1064         // preload all user ratings for these discussions - one query only and minimal memory
1065         $cm->cache->ratings = array();
1066         $cm->cache->myratings = array();
1067         if ($postratings = forum_get_all_user_ratings($user->id, $discussions)) {
1068             foreach ($postratings as $pr) {
1069                 if (!isset($cm->cache->ratings[$pr->postid])) {
1070                     $cm->cache->ratings[$pr->postid] = array();
1071                 }
1072                 $cm->cache->ratings[$pr->postid][$pr->id] = $pr->rating;
1074                 if ($pr->userid == $USER->id) {
1075                     $cm->cache->myratings[$pr->postid] = $pr->rating;
1076                 }
1077             }
1078             unset($postratings);
1079         }
1081         foreach ($posts as $post) {
1082             if (!isset($discussions[$post->discussion])) {
1083                 continue;
1084             }
1085            $discussion = $discussions[$post->discussion];
1087             $ratings = null;
1089             if ($forum->assessed) {
1090                 if ($scale = make_grades_menu($forum->scale)) {
1091                     $ratings =new object();
1092                     $ratings->scale = $scale;
1093                     $ratings->assesstimestart = $forum->assesstimestart;
1094                     $ratings->assesstimefinish = $forum->assesstimefinish;
1095                     $ratings->allow = false;
1096         }
1097  }
1100             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false, $ratings);
1102         }
1103     } else {
1104         echo "<p>".get_string("noposts", "forum")."</p>";
1105     }
1113 /**
1114  *
1115  */
1116 function forum_print_overview($courses,&$htmlarray) {
1117     global $USER, $CFG, $DB;
1119     $LIKE = $DB->sql_ilike();
1121     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1122         return array();
1123     }
1125     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1126         return;
1127     }
1130     // get all forum logs in ONE query (much better!)
1131     $params = array();
1132     $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {log} l "
1133         ." JOIN {course_modules} cm ON cm.id = cmid "
1134         ." WHERE (";
1135     foreach ($courses as $course) {
1136         $sql .= '(l.course = ? AND l.time > ?) OR ';
1137         $params[] = $course->id;
1138         $params[] = $course->lastaccess;
1139     }
1140     $sql = substr($sql,0,-3); // take off the last OR
1142     $sql .= ") AND l.module = 'forum' AND action $LIKE 'add post%' "
1143         ." AND userid != ? GROUP BY cmid,l.course,instance";
1145     $params[] = $USER->id;
1147     if (!$new = $DB->get_records_sql($sql, $params)) {
1148         $new = array(); // avoid warnings
1149     }
1151     // also get all forum tracking stuff ONCE.
1152     $trackingforums = array();
1153     foreach ($forums as $forum) {
1154         if (forum_tp_can_track_forums($forum)) {
1155             $trackingforums[$forum->id] = $forum;
1156         }
1157     }
1159     if (count($trackingforums) > 0) {
1160         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1161         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1162             ' FROM {forum_posts} p '.
1163             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1164             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1165         $params = array($USER->id);
1167         foreach ($trackingforums as $track) {
1168             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1169             $params[] = $track->id;
1170             $params[] = get_current_group($track->course);
1171         }
1172         $sql = substr($sql,0,-3); // take off the last OR
1173         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1174         $params[] = $cutoffdate;
1176         if (!$unread = $DB->get_records_sql($sql, $params)) {
1177             $unread = array();
1178         }
1179     } else {
1180         $unread = array();
1181     }
1183     if (empty($unread) and empty($new)) {
1184         return;
1185     }
1187     $strforum = get_string('modulename','forum');
1188     $strnumunread = get_string('overviewnumunread','forum');
1189     $strnumpostssince = get_string('overviewnumpostssince','forum');
1191     foreach ($forums as $forum) {
1192         $str = '';
1193         $count = 0;
1194         $thisunread = 0;
1195         $showunread = false;
1196         // either we have something from logs, or trackposts, or nothing.
1197         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1198             $count = $new[$forum->id]->count;
1199         }
1200         if (array_key_exists($forum->id,$unread)) {
1201             $thisunread = $unread[$forum->id]->count;
1202             $showunread = true;
1203         }
1204         if ($count > 0 || $thisunread > 0) {
1205             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1206                 $forum->name.'</a></div>';
1207             $str .= '<div class="info">';
1208             $str .= $count.' '.$strnumpostssince;
1209             if (!empty($showunread)) {
1210                 $str .= '<br />'.$thisunread .' '.$strnumunread;
1211             }
1212             $str .= '</div></div>';
1213         }
1214         if (!empty($str)) {
1215             if (!array_key_exists($forum->course,$htmlarray)) {
1216                 $htmlarray[$forum->course] = array();
1217             }
1218             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1219                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1220             }
1221             $htmlarray[$forum->course]['forum'] .= $str;
1222         }
1223     }
1226 /**
1227  * Given a course and a date, prints a summary of all the new
1228  * messages posted in the course since that date
1229  * @param object $course
1230  * @param bool $viewfullnames capability
1231  * @param int $timestart
1232  * @return bool success
1233  */
1234 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1235     global $CFG, $USER, $DB;
1237     // do not use log table if possible, it may be huge and is expensive to join with other tables
1239     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1240                                               d.timestart, d.timeend, d.userid AS duserid,
1241                                               u.firstname, u.lastname, u.email, u.picture
1242                                          FROM {forum_posts} p
1243                                               JOIN {forum_discussions} d ON d.id = p.discussion
1244                                               JOIN {forum} f             ON f.id = d.forum
1245                                               JOIN {user} u              ON u.id = p.userid
1246                                         WHERE p.created > ? AND f.course = ?
1247                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1248          return false;
1249     }
1251     $modinfo =& get_fast_modinfo($course);
1253     $groupmodes = array();
1254     $cms    = array();
1256     $strftimerecent = get_string('strftimerecent');
1258     $printposts = array();
1259     foreach ($posts as $post) {
1260         if (!isset($modinfo->instances['forum'][$post->forum])) {
1261             // not visible
1262             continue;
1263         }
1264         $cm = $modinfo->instances['forum'][$post->forum];
1265         if (!$cm->uservisible) {
1266             continue;
1267         }
1268         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1270         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1271             continue;
1272         }
1274         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1275           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1276             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1277                 continue;
1278             }
1279         }
1281         $groupmode = groups_get_activity_groupmode($cm, $course);
1283         if ($groupmode) {
1284             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1285                 // oki (Open discussions have groupid -1)
1286             } else {
1287                 // separate mode
1288                 if (isguestuser()) {
1289                     // shortcut
1290                     continue;
1291                 }
1293                 if (is_null($modinfo->groups)) {
1294                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1295                 }
1297                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1298                     continue;
1299                 }
1300             }
1301         }
1303         $printposts[] = $post;
1304     }
1305     unset($posts);
1307     if (!$printposts) {
1308         return false;
1309     }
1311     print_headline(get_string('newforumposts', 'forum').':', 3);
1312     echo "\n<ul class='unlist'>\n";
1314     foreach ($printposts as $post) {
1315         $subjectclass = empty($post->parent) ? ' bold' : '';
1317         echo '<li><div class="head">'.
1318                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1319                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1320              '</div>';
1321         echo '<div class="info'.$subjectclass.'">';
1322         if (empty($post->parent)) {
1323             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1324         } else {
1325             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1326         }
1327         $post->subject = break_up_long_words(format_string($post->subject, true));
1328         echo $post->subject;
1329         echo "</a>\"</div></li>\n";
1330     }
1332     echo "</ul>\n";
1334     return true;
1337 /**
1338  * Return grade for given user or all users.
1339  *
1340  * @param int $forumid id of forum
1341  * @param int $userid optional user id, 0 means all users
1342  * @return array array of grades, false if none
1343  */
1344 function forum_get_user_grades($forum, $userid=0) {
1345     global $CFG, $DB;
1347     $params= array();
1348     if ($userid) {
1349         $params[] = $userid;
1350         $user = "AND u.id = ?";
1351     } else {
1352         $user = "";
1353     }
1355     $params[] = $forum->id;
1357     $aggtype = $forum->assessed;
1358     switch ($aggtype) {
1359         case FORUM_AGGREGATE_COUNT :
1360             $sql = "SELECT u.id, u.id AS userid, COUNT(fr.rating) AS rawgrade
1361                       FROM {user} u, {forum_posts} fp,
1362                            {forum_ratings} fr, {forum_discussions} fd
1363                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1364                            AND fr.userid != u.id AND fd.forum = ?
1365                            $user
1366                   GROUP BY u.id";
1367             break;
1368         case FORUM_AGGREGATE_MAX :
1369             $sql = "SELECT u.id, u.id AS userid, MAX(fr.rating) AS rawgrade
1370                       FROM {user} u, {forum_posts} fp,
1371                            {forum_ratings} fr, {forum_discussions} fd
1372                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1373                            AND fr.userid != u.id AND fd.forum = ?
1374                            $user
1375                   GROUP BY u.id";
1376             break;
1377         case FORUM_AGGREGATE_MIN :
1378             $sql = "SELECT u.id, u.id AS userid, MIN(fr.rating) AS rawgrade
1379                       FROM {user} u, {forum_posts} fp,
1380                            {forum_ratings} fr, {forum_discussions} fd
1381                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1382                            AND fr.userid != u.id AND fd.forum = ?
1383                            $user
1384                   GROUP BY u.id";
1385             break;
1386         case FORUM_AGGREGATE_SUM :
1387             $sql = "SELECT u.id, u.id AS userid, SUM(fr.rating) AS rawgrade
1388                      FROM {user} u, {forum_posts} fp,
1389                           {forum_ratings} fr, {forum_discussions} fd
1390                     WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1391                           AND fr.userid != u.id AND fd.forum = ?
1392                           $user
1393                  GROUP BY u.id";
1394             break;
1395         default : //avg
1396             $sql = "SELECT u.id, u.id AS userid, AVG(fr.rating) AS rawgrade
1397                       FROM {user} u, {forum_posts} fp,
1398                            {forum_ratings} fr, {forum_discussions} fd
1399                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1400                            AND fr.userid != u.id AND fd.forum = ?
1401                            $user
1402                   GROUP BY u.id";
1403             break;
1404     }
1406     if ($results = $DB->get_records_sql($sql, $params)) {
1407         // it could throw off the grading if count and sum returned a rawgrade higher than scale
1408         // 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)
1409         foreach ($results as $rid=>$result) {
1410             if ($forum->scale >= 0) {
1411                 //numeric
1412                 if ($result->rawgrade > $forum->scale) {
1413                     $results[$rid]->rawgrade = $forum->scale;
1414                 }
1415             } else {
1416                 //scales
1417                 if ($scale = $DB->get_record('scale', array('id' => -$forum->scale))) {
1418                     $scale = explode(',', $scale->scale);
1419                     $max = count($scale);
1420                     if ($result->rawgrade > $max) {
1421                         $results[$rid]->rawgrade = $max;
1422                     }
1423                 }
1424             }
1425         }
1426     }
1428     return $results;
1431 /**
1432  * Update activity grades
1433  *
1434  * @param object $forum
1435  * @param int $userid specific user only, 0 means all
1436  * @param boolean $nullifnone return null if grade does not exist
1437  * @return void
1438  */
1439 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1440     global $CFG, $DB;
1441     require_once($CFG->libdir.'/gradelib.php');
1443     if (!$forum->assessed) {
1444         forum_grade_item_update($forum);
1446     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1447         forum_grade_item_update($forum, $grades);
1449     } else if ($userid and $nullifnone) {
1450         $grade = new object();
1451         $grade->userid   = $userid;
1452         $grade->rawgrade = NULL;
1453         forum_grade_item_update($forum, $grade);
1455     } else {
1456         forum_grade_item_update($forum);
1457     }
1460 /**
1461  * Update all grades in gradebook.
1462  */
1463 function forum_upgrade_grades() {
1464     global $DB;
1466     $sql = "SELECT COUNT('x')
1467               FROM {forum} f, {course_modules} cm, {modules} m
1468              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1469     $count = $DB->count_records_sql($sql);
1471     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1472               FROM {forum} f, {course_modules} cm, {modules} m
1473              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1474     if ($rs = $DB->get_recordset_sql($sql)) {
1475         // too much debug output
1476         $prevdebug = $DB->get_debug();
1477         $DB->set_debug(false);
1478         $pbar = new progress_bar('forumupgradegrades', 500, true);
1479         $i=0;
1480         foreach ($rs as $forum) {
1481             $i++;
1482             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1483             forum_update_grades($forum, 0, false);
1484             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1485         }
1486         $DB->set_debug($prevdebug);
1487         $rs->close();
1488     }
1491 /**
1492  * Create/update grade item for given forum
1493  *
1494  * @param object $forum object with extra cmidnumber
1495  * @param mixed optional array/object of grade(s); 'reset' means reset grades in gradebook
1496  * @return int 0 if ok
1497  */
1498 function forum_grade_item_update($forum, $grades=NULL) {
1499     global $CFG;
1500     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1501         require_once($CFG->libdir.'/gradelib.php');
1502     }
1504     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1506     if (!$forum->assessed or $forum->scale == 0) {
1507         $params['gradetype'] = GRADE_TYPE_NONE;
1509     } else if ($forum->scale > 0) {
1510         $params['gradetype'] = GRADE_TYPE_VALUE;
1511         $params['grademax']  = $forum->scale;
1512         $params['grademin']  = 0;
1514     } else if ($forum->scale < 0) {
1515         $params['gradetype'] = GRADE_TYPE_SCALE;
1516         $params['scaleid']   = -$forum->scale;
1517     }
1519     if ($grades  === 'reset') {
1520         $params['reset'] = true;
1521         $grades = NULL;
1522     }
1524     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1527 /**
1528  * Delete grade item for given forum
1529  *
1530  * @param object $forum object
1531  * @return object grade_item
1532  */
1533 function forum_grade_item_delete($forum) {
1534     global $CFG;
1535     require_once($CFG->libdir.'/gradelib.php');
1537     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1541 /**
1542  * Returns the users with data in one forum
1543  * (users with records in forum_subscriptions, forum_posts and forum_ratings, students)
1544  * @param int $forumid
1545  * @return mixed array or false if none
1546  */
1547 function forum_get_participants($forumid) {
1549     global $CFG, $DB;
1551     //Get students from forum_subscriptions
1552     $st_subscriptions = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1553                                          FROM {user} u,
1554                                               {forum_subscriptions} s
1555                                          WHERE s.forum = ? AND
1556                                                u.id = s.userid", array($forumid));
1557     //Get students from forum_posts
1558     $st_posts = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1559                                  FROM {user} u,
1560                                       {forum_discussions} d,
1561                                       {forum_posts} p
1562                                  WHERE d.forum = ? AND
1563                                        p.discussion = d.id AND
1564                                        u.id = p.userid", array($forumid));
1566     //Get students from forum_ratings
1567     $st_ratings = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1568                                    FROM {user} u,
1569                                         {forum_discussions} d,
1570                                         {forum_posts} p,
1571                                         {forum_ratings} r
1572                                    WHERE d.forum = ? AND
1573                                          p.discussion = d.id AND
1574                                          r.post = p.id AND
1575                                          u.id = r.userid", array($forumid));
1577     //Add st_posts to st_subscriptions
1578     if ($st_posts) {
1579         foreach ($st_posts as $st_post) {
1580             $st_subscriptions[$st_post->id] = $st_post;
1581         }
1582     }
1583     //Add st_ratings to st_subscriptions
1584     if ($st_ratings) {
1585         foreach ($st_ratings as $st_rating) {
1586             $st_subscriptions[$st_rating->id] = $st_rating;
1587         }
1588     }
1589     //Return st_subscriptions array (it contains an array of unique users)
1590     return ($st_subscriptions);
1593 /**
1594  * This function returns if a scale is being used by one forum
1595  * @param int $forumid
1596  * @param int $scaleid negative number
1597  * @return bool
1598  */
1599 function forum_scale_used ($forumid,$scaleid) {
1600     global $DB;
1601     $return = false;
1603     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1605     if (!empty($rec) && !empty($scaleid)) {
1606         $return = true;
1607     }
1609     return $return;
1612 /**
1613  * Checks if scale is being used by any instance of forum
1614  *
1615  * This is used to find out if scale used anywhere
1616  * @param $scaleid int
1617  * @return boolean True if the scale is used by any forum
1618  */
1619 function forum_scale_used_anywhere($scaleid) {
1620     global $DB;
1621     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1622         return true;
1623     } else {
1624         return false;
1625     }
1628 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1630 /**
1631  * Gets a post with all info ready for forum_print_post
1632  * Most of these joins are just to get the forum id
1633  * @param int $postid
1634  * @return mixed array of posts or false
1635  */
1636 function forum_get_post_full($postid) {
1637     global $CFG, $DB;
1639     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1640                              FROM {forum_posts} p
1641                                   JOIN {forum_discussions} d ON p.discussion = d.id
1642                                   LEFT JOIN {user} u ON p.userid = u.id
1643                             WHERE p.id = ?", array($postid));
1646 /**
1647  * Gets posts with all info ready for forum_print_post
1648  * We pass forumid in because we always know it so no need to make a
1649  * complicated join to find it out.
1650  * @return mixed array of posts or false
1651  */
1652 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1653     global $CFG, $DB;
1655     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1656                               FROM {forum_posts} p
1657                          LEFT JOIN {user} u ON p.userid = u.id
1658                              WHERE p.discussion = ?
1659                                AND p.parent > 0 $sort", array($discussion));
1662 /**
1663  * Gets all posts in discussion including top parent.
1664  * @param int $discussionid
1665  * @param string $sort
1666  * @param bool $tracking does user track the forum?
1667  * @return array of posts
1668  */
1669 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1670     global $CFG, $DB, $USER;
1672     $tr_sel  = "";
1673     $tr_join = "";
1674     $params = array();
1676     if ($tracking) {
1677         $now = time();
1678         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1679         $tr_sel  = ", fr.id AS postread";
1680         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1681         $params[] = $USER->id;
1682     }
1684     $params[] = $discussionid;
1685     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1686                                      FROM {forum_posts} p
1687                                           LEFT JOIN {user} u ON p.userid = u.id
1688                                           $tr_join
1689                                     WHERE p.discussion = ?
1690                                  ORDER BY $sort", $params)) {
1691         return array();
1692     }
1694     foreach ($posts as $pid=>$p) {
1695         if ($tracking) {
1696             if (forum_tp_is_post_old($p)) {
1697                  $posts[$pid]->postread = true;
1698             }
1699         }
1700         if (!$p->parent) {
1701             continue;
1702         }
1703         if (!isset($posts[$p->parent])) {
1704             continue; // parent does not exist??
1705         }
1706         if (!isset($posts[$p->parent]->children)) {
1707             $posts[$p->parent]->children = array();
1708         }
1709         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1710     }
1712     return $posts;
1715 /**
1716  * Gets posts with all info ready for forum_print_post
1717  * We pass forumid in because we always know it so no need to make a
1718  * complicated join to find it out.
1719  */
1720 function forum_get_child_posts($parent, $forumid) {
1721     global $CFG, $DB;
1723     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1724                               FROM {forum_posts} p
1725                          LEFT JOIN {user} u ON p.userid = u.id
1726                              WHERE p.parent = ?
1727                           ORDER BY p.created ASC", array($parent));
1730 /**
1731  * An array of forum objects that the user is allowed to read/search through.
1732  * @param $userid
1733  * @param $courseid - if 0, we look for forums throughout the whole site.
1734  * @return array of forum objects, or false if no matches
1735  *         Forum objects have the following attributes:
1736  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1737  *         viewhiddentimedposts
1738  */
1739 function forum_get_readable_forums($userid, $courseid=0) {
1741     global $CFG, $DB, $USER;
1742     require_once($CFG->dirroot.'/course/lib.php');
1744     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1745         error('The forum module is not installed');
1746     }
1748     if ($courseid) {
1749         $courses = $DB->get_records('course', array('id' => $courseid));
1750     } else {
1751         // If no course is specified, then the user can see SITE + his courses.
1752         // And admins can see all courses, so pass the $doanything flag enabled
1753         $courses1 = $DB->get_records('course', array('id' => SITEID));
1754         $courses2 = get_my_courses($userid, null, null, true);
1755         $courses = array_merge($courses1, $courses2);
1756     }
1757     if (!$courses) {
1758         return array();
1759     }
1761     $readableforums = array();
1763     foreach ($courses as $course) {
1765         $modinfo =& get_fast_modinfo($course);
1766         if (is_null($modinfo->groups)) {
1767             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1768         }
1770         if (empty($modinfo->instances['forum'])) {
1771             // hmm, no forums?
1772             continue;
1773         }
1775         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1777         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1778             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1779                 continue;
1780             }
1781             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1782             $forum = $courseforums[$forumid];
1784             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1785                 continue;
1786             }
1788          /// group access
1789             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1790                 if (is_null($modinfo->groups)) {
1791                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1792                 }
1793                 if (empty($CFG->enablegroupings)) {
1794                     $forum->onlygroups = $modinfo->groups[0];
1795                     $forum->onlygroups[] = -1;
1796                 } else if (isset($modinfo->groups[$cm->groupingid])) {
1797                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1798                     $forum->onlygroups[] = -1;
1799                 } else {
1800                     $forum->onlygroups = array(-1);
1801                 }
1802             }
1804         /// hidden timed discussions
1805             $forum->viewhiddentimedposts = true;
1806             if (!empty($CFG->forum_enabletimedposts)) {
1807                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1808                     $forum->viewhiddentimedposts = false;
1809                 }
1810             }
1812         /// qanda access
1813             if ($forum->type == 'qanda'
1814                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1816                 // We need to check whether the user has posted in the qanda forum.
1817                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1818                                                     // the user is allowed to see in this forum.
1819                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1820                     foreach ($discussionspostedin as $d) {
1821                         $forum->onlydiscussions[] = $d->id;
1822                     }
1823                 }
1824             }
1826             $readableforums[$forum->id] = $forum;
1827         }
1829         unset($modinfo);
1831     } // End foreach $courses
1833     //print_object($courses);
1834     //print_object($readableforums);
1836     return $readableforums;
1839 /**
1840  * Returns a list of posts found using an array of search terms.
1841  * @param $searchterms - array of search terms, e.g. word +word -word
1842  * @param $courseid - if 0, we search through the whole site
1843  * @param $page
1844  * @param $recordsperpage=50
1845  * @param &$totalcount
1846  * @param $extrasql
1847  * @return array of posts found
1848  */
1849 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1850                             &$totalcount, $extrasql='') {
1851     global $CFG, $DB, $USER;
1852     require_once($CFG->libdir.'/searchlib.php');
1854     $forums = forum_get_readable_forums($USER->id, $courseid);
1856     if (count($forums) == 0) {
1857         $totalcount = 0;
1858         return false;
1859     }
1861     $now = round(time(), -2); // db friendly
1863     $fullaccess = array();
1864     $where = array();
1865     $params = array();
1867     foreach ($forums as $forumid => $forum) {
1868         $select = array();
1870         if (!$forum->viewhiddentimedposts) {
1871             $select[] = "(d.userid = :userid OR (d.timestart < : AND (d.timeend = 0 OR d.timeend > :timeend)))";
1872             $params = array('userid'=>$USER->id, 'timestart'=>$now, 'timeend'=>$now);
1873         }
1875         if ($forum->type == 'qanda') {
1876             if (!empty($forum->onlydiscussions)) {
1877                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda0');
1878                 $params = array_merge($params, $discussionid_params);
1879                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
1880             } else {
1881                 $select[] = "p.parent = 0";
1882             }
1883         }
1885         if (!empty($forum->onlygroups)) {
1886             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps0');
1887             $params = array_merge($params, $groupid_params);
1888             $select[] = "d.groupid $groupid_sql";
1889         }
1891         if ($select) {
1892             $selects = implode(" AND ", $select);
1893             $where[] = "(d.forum = :forum AND $selects)";
1894             $params['forum'] = $forumid;
1895         } else {
1896             $fullaccess[] = $forumid;
1897         }
1898     }
1900     if ($fullaccess) {
1901         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula0');
1902         $params = array_merge($params, $fullid_params);
1903         $where[] = "(d.forum $fullid_sql)";
1904     }
1906     $selectdiscussion = "(".implode(" OR ", $where).")";
1908     $messagesearch = '';
1909     $searchstring = '';
1911     // Need to concat these back together for parser to work.
1912     foreach($searchterms as $searchterm){
1913         if ($searchstring != '') {
1914             $searchstring .= ' ';
1915         }
1916         $searchstring .= $searchterm;
1917     }
1919     // We need to allow quoted strings for the search. The quotes *should* be stripped
1920     // by the parser, but this should be examined carefully for security implications.
1921     $searchstring = str_replace("\\\"","\"",$searchstring);
1922     $parser = new search_parser();
1923     $lexer = new search_lexer($parser);
1925     if ($lexer->parse($searchstring)) {
1926         $parsearray = $parser->get_parsed_array();
1927     // Experimental feature under 1.8! MDL-8830
1928     // Use alternative text searches if defined
1929     // This feature only works under mysql until properly implemented for other DBs
1930     // Requires manual creation of text index for forum_posts before enabling it:
1931     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
1932     // Experimental feature under 1.8! MDL-8830
1933         if (!empty($CFG->forum_usetextsearches)) {
1934             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
1935                                                  'p.userid', 'u.id', 'u.firstname',
1936                                                  'u.lastname', 'p.modified', 'd.forum');
1937         } else {
1938             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
1939                                                  'p.userid', 'u.id', 'u.firstname',
1940                                                  'u.lastname', 'p.modified', 'd.forum');
1941         }
1942         $params = array_merge($params, $msparams);
1943     }
1945     $fromsql = "{forum_posts} p,
1946                   {forum_discussions} d,
1947                   {user} u";
1949     $selectsql = " $messagesearch
1950                AND p.discussion = d.id
1951                AND p.userid = u.id
1952                AND $selectdiscussion
1953                    $extrasql";
1955     $countsql = "SELECT COUNT(*)
1956                    FROM $fromsql
1957                   WHERE $selectsql";
1959     $searchsql = "SELECT p.*,
1960                          d.forum,
1961                          u.firstname,
1962                          u.lastname,
1963                          u.email,
1964                          u.picture,
1965                          u.imagealt
1966                     FROM $fromsql
1967                    WHERE $selectsql
1968                 ORDER BY p.modified DESC";
1970     $totalcount = $DB->count_records_sql($countsql, $params);
1972     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
1975 /**
1976  * Returns a list of ratings for all posts in discussion
1977  * @param object $discussion
1978  * @return array of ratings or false
1979  */
1980 function forum_get_all_discussion_ratings($discussion) {
1981     global $CFG, $DB;
1982     return $DB->get_records_sql("SELECT r.id, r.userid, p.id AS postid, r.rating
1983                               FROM {forum_ratings} r,
1984                                    {forum_posts} p
1985                              WHERE r.post = p.id AND p.discussion = ?
1986                              ORDER BY p.id ASC", array($discussion->id));
1989 /**
1990  * Returns a list of ratings for one specific user for all posts in discussion
1991  * @global object $CFG
1992  * @global object $DB
1993  * @param object $discussions the discussions for which we return all ratings
1994  * @param int $userid the user for who we return all ratings
1995  * @return object
1996  */
1997 function forum_get_all_user_ratings($userid, $discussions) {
1998     global $CFG, $DB;
2001     foreach ($discussions as $discussion) {
2002      if (!isset($discussionsid)){
2003          $discussionsid = $discussion->id;
2004      }
2005      else {
2006          $discussionsid .= ",".$discussion->id;
2007      }
2008     }
2010     $sql = "SELECT r.id, r.userid, p.id AS postid, r.rating
2011                               FROM {forum_ratings} r,
2012                                    {forum_posts} p
2013                              WHERE r.post = p.id AND p.userid = :userid";
2016     $params = array();
2017     $params['userid'] = $userid;
2018     //postgres compability
2019     if (!isset($discussionsid)) {
2020        $sql .=" AND p.discussion IN (".$discussionsid.")";
2021     }
2022     $sql .=" ORDER BY p.id ASC";
2024     return $DB->get_records_sql($sql, $params);
2029 /**
2030  * Returns a list of ratings for a particular post - sorted.
2031  * @param int $postid
2032  * @param string $sort
2033  * @return array of ratings or false
2034  */
2035 function forum_get_ratings($postid, $sort="u.firstname ASC") {
2036     global $CFG, $DB;
2037     return $DB->get_records_sql("SELECT u.*, r.rating, r.time
2038                               FROM {forum_ratings} r,
2039                                    {user} u
2040                              WHERE r.post = ?
2041                                AND r.userid = u.id
2042                              ORDER BY $sort", array($postid));
2045 /**
2046  * Returns a list of all new posts that have not been mailed yet
2047  * @param int $starttime - posts created after this time
2048  * @param int $endtime - posts created before this
2049  * @param int $now - used for timed discussions only
2050  */
2051 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2052     global $CFG, $DB;
2054     $params = array($starttime, $endtime);
2055     if (!empty($CFG->forum_enabletimedposts)) {
2056         if (empty($now)) {
2057             $now = time();
2058         }
2059         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2060         $params[] = $now;
2061         $params[] = $now;
2062     } else {
2063         $timedsql = "";
2064     }
2066     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2067                               FROM {forum_posts} p
2068                                    JOIN {forum_discussions} d ON d.id = p.discussion
2069                              WHERE p.mailed = 0
2070                                    AND p.created >= ?
2071                                    AND (p.created < ? OR p.mailnow = 1)
2072                                    $timedsql
2073                           ORDER BY p.modified ASC", $params);
2076 /**
2077  * Marks posts before a certain time as being mailed already
2078  */
2079 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2080     global $CFG, $DB;
2081     if (empty($now)) {
2082         $now = time();
2083     }
2085     if (empty($CFG->forum_enabletimedposts)) {
2086         return $DB->execute("UPDATE {forum_posts}
2087                                SET mailed = '1'
2088                              WHERE (created < ? OR mailnow = 1)
2089                                    AND mailed = 0", array($endtime));
2091     } else {
2092         return $DB->execute("UPDATE {forum_posts}
2093                                SET mailed = '1'
2094                              WHERE discussion NOT IN (SELECT d.id
2095                                                         FROM {forum_discussions} d
2096                                                        WHERE d.timestart > ?)
2097                                    AND (created < ? OR mailnow = 1)
2098                                    AND mailed = 0", array($now, $endtime));
2099     }
2102 /**
2103  * Get all the posts for a user in a forum suitable for forum_print_post
2104  */
2105 function forum_get_user_posts($forumid, $userid) {
2106     global $CFG, $DB;
2108     $timedsql = "";
2109     $params = array($forumid, $userid);
2111     if (!empty($CFG->forum_enabletimedposts)) {
2112         $cm = get_coursemodule_from_instance('forum', $forumid);
2113         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2114             $now = time();
2115             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2116             $params[] = $now;
2117             $params[] = $now;
2118         }
2119     }
2121     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2122                               FROM {forum} f
2123                                    JOIN {forum_discussions} d ON d.forum = f.id
2124                                    JOIN {forum_posts} p       ON p.discussion = d.id
2125                                    JOIN {user} u              ON u.id = p.userid
2126                              WHERE f.id = ?
2127                                    AND p.userid = ?
2128                                    $timedsql
2129                           ORDER BY p.modified ASC", $params);
2132 /**
2133  * Get all the discussions user participated in
2134  * @param int $forumid
2135  * @param int $userid
2136  * @return array or false
2137  */
2138 function forum_get_user_involved_discussions($forumid, $userid) {
2139     global $CFG, $DB;
2141     $timedsql = "";
2142     $params = array($forumid, $userid);
2143     if (!empty($CFG->forum_enabletimedposts)) {
2144         $cm = get_coursemodule_from_instance('forum', $forumid);
2145         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2146             $now = time();
2147             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2148             $params[] = $now;
2149             $params[] = $now;
2150         }
2151     }
2153     return $DB->get_records_sql("SELECT DISTINCT d.*
2154                               FROM {forum} f
2155                                    JOIN {forum_discussions} d ON d.forum = f.id
2156                                    JOIN {forum_posts} p       ON p.discussion = d.id
2157                              WHERE f.id = ?
2158                                    AND p.userid = ?
2159                                    $timedsql", $params);
2162 /**
2163  * Get all the posts for a user in a forum suitable for forum_print_post
2164  * @param int $forumid
2165  * @param int $userid
2166  * @return array of counts or false
2167  */
2168 function forum_count_user_posts($forumid, $userid) {
2169     global $CFG, $DB;
2171     $timedsql = "";
2172     $params = array($forumid, $userid);
2173     if (!empty($CFG->forum_enabletimedposts)) {
2174         $cm = get_coursemodule_from_instance('forum', $forumid);
2175         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2176             $now = time();
2177             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2178             $params[] = $now;
2179             $params[] = $now;
2180         }
2181     }
2183     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2184                              FROM {forum} f
2185                                   JOIN {forum_discussions} d ON d.forum = f.id
2186                                   JOIN {forum_posts} p       ON p.discussion = d.id
2187                                   JOIN {user} u              ON u.id = p.userid
2188                             WHERE f.id = ?
2189                                   AND p.userid = ?
2190                                   $timedsql", $params);
2193 /**
2194  * Given a log entry, return the forum post details for it.
2195  */
2196 function forum_get_post_from_log($log) {
2197     global $CFG, $DB;
2199     if ($log->action == "add post") {
2201         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2202                                            u.firstname, u.lastname, u.email, u.picture
2203                                  FROM {forum_discussions} d,
2204                                       {forum_posts} p,
2205                                       {forum} f,
2206                                       {user} u
2207                                 WHERE p.id = ?
2208                                   AND d.id = p.discussion
2209                                   AND p.userid = u.id
2210                                   AND u.deleted <> '1'
2211                                   AND f.id = d.forum", array($log->info));
2214     } else if ($log->action == "add discussion") {
2216         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2217                                            u.firstname, u.lastname, u.email, u.picture
2218                                  FROM {forum_discussions} d,
2219                                       {forum_posts} p,
2220                                       {forum} f,
2221                                       {user} u
2222                                 WHERE d.id = ?
2223                                   AND d.firstpost = p.id
2224                                   AND p.userid = u.id
2225                                   AND u.deleted <> '1'
2226                                   AND f.id = d.forum", array($log->info));
2227     }
2228     return NULL;
2231 /**
2232  * Given a discussion id, return the first post from the discussion
2233  */
2234 function forum_get_firstpost_from_discussion($discussionid) {
2235     global $CFG, $DB;
2237     return $DB->get_record_sql("SELECT p.*
2238                              FROM {forum_discussions} d,
2239                                   {forum_posts} p
2240                             WHERE d.id = ?
2241                               AND d.firstpost = p.id ", array($discussionid));
2244 /**
2245  * Returns an array of counts of replies to each discussion
2246  */
2247 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2248     global $CFG, $DB;
2250     if ($limit > 0) {
2251         $limitfrom = 0;
2252         $limitnum  = $limit;
2253     } else if ($page != -1) {
2254         $limitfrom = $page*$perpage;
2255         $limitnum  = $perpage;
2256     } else {
2257         $limitfrom = 0;
2258         $limitnum  = 0;
2259     }
2261     if ($forumsort == "") {
2262         $orderby = "";
2263         $groupby = "";
2265     } else {
2266         $orderby = "ORDER BY $forumsort";
2267         $groupby = ", ".strtolower($forumsort);
2268         $groupby = str_replace('desc', '', $groupby);
2269         $groupby = str_replace('asc', '', $groupby);
2270     }
2272     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2273         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2274                   FROM {forum_posts} p
2275                        JOIN {forum_discussions} d ON p.discussion = d.id
2276                  WHERE p.parent > 0 AND d.forum = ?
2277               GROUP BY p.discussion";
2278         return $DB->get_records_sql($sql, array($forumid));
2280     } else {
2281         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2282                   FROM {forum_posts} p
2283                        JOIN {forum_discussions} d ON p.discussion = d.id
2284                  WHERE d.forum = ?
2285               GROUP BY p.discussion $groupby
2286               $orderby";
2287         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2288     }
2291 function forum_count_discussions($forum, $cm, $course) {
2292     global $CFG, $DB, $USER;
2294     static $cache = array();
2296     $now = round(time(), -2); // db cache friendliness
2298     $params = array($course->id);
2300     if (!isset($cache[$course->id])) {
2301         if (!empty($CFG->forum_enabletimedposts)) {
2302             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2303             $params[] = $now;
2304             $params[] = $now;
2305         } else {
2306             $timedsql = "";
2307         }
2309         $sql = "SELECT f.id, COUNT(d.id) as dcount
2310                   FROM {forum} f
2311                        JOIN {forum_discussions} d ON d.forum = f.id
2312                  WHERE f.course = ?
2313                        $timedsql
2314               GROUP BY f.id";
2316         if ($counts = $DB->get_records_sql($sql, $params)) {
2317             foreach ($counts as $count) {
2318                 $counts[$count->id] = $count->dcount;
2319             }
2320             $cache[$course->id] = $counts;
2321         } else {
2322             $cache[$course->id] = array();
2323         }
2324     }
2326     if (empty($cache[$course->id][$forum->id])) {
2327         return 0;
2328     }
2330     $groupmode = groups_get_activity_groupmode($cm, $course);
2332     if ($groupmode != SEPARATEGROUPS) {
2333         return $cache[$course->id][$forum->id];
2334     }
2336     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2337         return $cache[$course->id][$forum->id];
2338     }
2340     require_once($CFG->dirroot.'/course/lib.php');
2342     $modinfo =& get_fast_modinfo($course);
2343     if (is_null($modinfo->groups)) {
2344         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2345     }
2347     if (empty($CFG->enablegroupings)) {
2348         $mygroups = $modinfo->groups[0];
2349     } else {
2350         $mygroups = $modinfo->groups[$cm->groupingid];
2351     }
2353     // add all groups posts
2354     if (empty($mygroups)) {
2355         $mygroups = array(-1=>-1);
2356     } else {
2357         $mygroups[-1] = -1;
2358     }
2360     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2361     $params[] = $forum->id;
2363     if (!empty($CFG->forum_enabletimedposts)) {
2364         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2365         $params[] = $now;
2366         $params[] = $now;
2367     } else {
2368         $timedsql = "";
2369     }
2371     $sql = "SELECT COUNT(d.id)
2372               FROM {forum_discussions} d
2373              WHERE d.groupid IN ($mygroups) AND d.forum = ?
2374                    $timedsql";
2376     return $DB->get_field_sql($sql, $params);
2379 /**
2380  * How many unrated posts are in the given discussion for a given user?
2381  */
2382 function forum_count_unrated_posts($discussionid, $userid) {
2383     global $CFG, $DB;
2384     if ($posts = $DB->get_record_sql("SELECT count(*) as num
2385                                    FROM {forum_posts}
2386                                   WHERE parent > 0
2387                                     AND discussion = ?
2388                                     AND userid <> ? ", array($discussionid, $userid))) {
2390         if ($rated = $DB->get_record_sql("SELECT count(*) as num
2391                                        FROM {forum_posts} p,
2392                                             {forum_ratings} r
2393                                       WHERE p.discussion = ?
2394                                         AND p.id = r.post
2395                                         AND r.userid = ?", array($discussionid, $userid))) {
2396             $difference = $posts->num - $rated->num;
2397             if ($difference > 0) {
2398                 return $difference;
2399             } else {
2400                 return 0;    // Just in case there was a counting error
2401             }
2402         } else {
2403             return $posts->num;
2404         }
2405     } else {
2406         return 0;
2407     }
2410 /**
2411  * Get all discussions in a forum
2412  */
2413 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2414     global $CFG, $DB, $USER;
2416     $timelimit = '';
2418     $modcontext = null;
2420     $now = round(time(), -2);
2421     $params = array($cm->instance);
2423     if (!empty($CFG->forum_enabletimedposts)) {
2425         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2427         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2428             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2429             $params[] = $now;
2430             $params[] = $now;
2431             if (isloggedin()) {
2432                 $timelimit .= " OR d.userid = ?";
2433                 $params[] = $USER->id;
2434             }
2435             $timelimit .= ")";
2436         }
2437     }
2439     if ($limit > 0) {
2440         $limitfrom = 0;
2441         $limitnum  = $limit;
2442     } else if ($page != -1) {
2443         $limitfrom = $page*$perpage;
2444         $limitnum  = $perpage;
2445     } else {
2446         $limitfrom = 0;
2447         $limitnum  = 0;
2448     }
2450     $groupmode    = groups_get_activity_groupmode($cm);
2451     $currentgroup = groups_get_activity_group($cm);
2453     if ($groupmode) {
2454         if (empty($modcontext)) {
2455             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2456         }
2458         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2459             if ($currentgroup) {
2460                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2461                 $params[] = $currentgroup;
2462             } else {
2463                 $groupselect = "";
2464             }
2466         } else {
2467             //seprate groups without access all
2468             if ($currentgroup) {
2469                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2470                 $params[] = $currentgroup;
2471             } else {
2472                 $groupselect = "AND d.groupid = -1";
2473             }
2474         }
2475     } else {
2476         $groupselect = "";
2477     }
2480     if (empty($forumsort)) {
2481         $forumsort = "d.timemodified DESC";
2482     }
2483     if (empty($fullpost)) {
2484         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2485     } else {
2486         $postdata = "p.*";
2487     }
2489     if (empty($userlastmodified)) {  // We don't need to know this
2490         $umfields = "";
2491         $umtable  = "";
2492     } else {
2493         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2494         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2495     }
2497     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2498                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2499               FROM {forum_discussions} d
2500                    JOIN {forum_posts} p ON p.discussion = d.id
2501                    JOIN {user} u ON p.userid = u.id
2502                    $umtable
2503              WHERE d.forum = ? AND p.parent = 0
2504                    $timelimit $groupselect
2505           ORDER BY $forumsort";
2506     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2509 function forum_get_discussions_unread($cm) {
2510     global $CFG, $DB, $USER;
2512     $now = round(time(), -2);
2513     $params = array($cutoffdate);
2514     $groupmode    = groups_get_activity_groupmode($cm);
2515     $currentgroup = groups_get_activity_group($cm);
2517     if ($groupmode) {
2518         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2520         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2521             if ($currentgroup) {
2522                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2523                 $params[] = $currentgroup;
2524             } else {
2525                 $groupselect = "";
2526             }
2528         } else {
2529             //seprate groups without access all
2530             if ($currentgroup) {
2531                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2532                 $params[] = $currentgroup;
2533             } else {
2534                 $groupselect = "AND d.groupid = -1";
2535             }
2536         }
2537     } else {
2538         $groupselect = "";
2539     }
2541     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2543     if (!empty($CFG->forum_enabletimedposts)) {
2544         $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2545         $params[] = $now;
2546         $params[] = $now;
2547     } else {
2548         $timedsql = "";
2549     }
2551     $sql = "SELECT d.id, COUNT(p.id) AS unread
2552               FROM {forum_discussions} d
2553                    JOIN {forum_posts} p     ON p.discussion = d.id
2554                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2555              WHERE d.forum = {$cm->instance}
2556                    AND p.modified >= ? AND r.id is NULL
2557                    $groupselect
2558                    $timedsql
2559           GROUP BY d.id";
2560     if ($unreads = $DB->get_records_sql($sql, $params)) {
2561         foreach ($unreads as $unread) {
2562             $unreads[$unread->id] = $unread->unread;
2563         }
2564         return $unreads;
2565     } else {
2566         return array();
2567     }
2570 function forum_get_discussions_count($cm) {
2571     global $CFG, $DB, $USER;
2573     $now = round(time(), -2);
2574     $params = array($cm->instance);
2575     $groupmode    = groups_get_activity_groupmode($cm);
2576     $currentgroup = groups_get_activity_group($cm);
2578     if ($groupmode) {
2579         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2581         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2582             if ($currentgroup) {
2583                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2584                 $params[] = $currentgroup;
2585             } else {
2586                 $groupselect = "";
2587             }
2589         } else {
2590             //seprate groups without access all
2591             if ($currentgroup) {
2592                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2593                 $params[] = $currentgroup;
2594             } else {
2595                 $groupselect = "AND d.groupid = -1";
2596             }
2597         }
2598     } else {
2599         $groupselect = "";
2600     }
2602     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2604     $timelimit = "";
2606     if (!empty($CFG->forum_enabletimedposts)) {
2608         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2610         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2611             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2612             $params[] = $now;
2613             $params[] = $now;
2614             if (isloggedin()) {
2615                 $timelimit .= " OR d.userid = ?";
2616                 $params[] = $USER->id;
2617             }
2618             $timelimit .= ")";
2619         }
2620     }
2622     $sql = "SELECT COUNT(d.id)
2623               FROM {forum_discussions} d
2624                    JOIN {forum_posts} p ON p.discussion = d.id
2625              WHERE d.forum = ? AND p.parent = 0
2626                    $groupselect $timelimit";
2628     return $DB->get_field_sql($sql, $params);
2632 /**
2633  * Get all discussions started by a particular user in a course (or group)
2634  * This function no longer used ...
2635  */
2636 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2637     global $CFG, $DB;
2638     $params = array($courseid, $userid);
2639     if ($groupid) {
2640         $groupselect = " AND d.groupid = ? ";
2641         $params[] = $groupid;
2642     } else  {
2643         $groupselect = "";
2644     }
2646     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2647                                    f.type as forumtype, f.name as forumname, f.id as forumid
2648                               FROM {forum_discussions} d,
2649                                    {forum_posts} p,
2650                                    {user} u,
2651                                    {forum} f
2652                              WHERE d.course = ?
2653                                AND p.discussion = d.id
2654                                AND p.parent = 0
2655                                AND p.userid = u.id
2656                                AND u.id = ?
2657                                AND d.forum = f.id $groupselect
2658                           ORDER BY p.created DESC", $params);
2661 /**
2662  * Returns list of user objects that are subscribed to this forum
2663  */
2664 function forum_subscribed_users($course, $forum, $groupid=0) {
2666     global $CFG, $DB;
2667     $params = array($forum->id);
2669     if ($groupid) {
2670         $grouptables = ", {groups_members} gm ";
2671         $groupselect = "AND gm.groupid = ? AND u.id = gm.userid";
2672         $params[] = $groupid;
2673     } else  {
2674         $grouptables = '';
2675         $groupselect = '';
2676     }
2678     $fields ="u.id,
2679               u.username,
2680               u.firstname,
2681               u.lastname,
2682               u.maildisplay,
2683               u.mailformat,
2684               u.maildigest,
2685               u.emailstop,
2686               u.imagealt,
2687               u.email,
2688               u.city,
2689               u.country,
2690               u.lastaccess,
2691               u.lastlogin,
2692               u.picture,
2693               u.timezone,
2694               u.theme,
2695               u.lang,
2696               u.trackforums,
2697               u.mnethostid";
2699     if (forum_is_forcesubscribed($forum)) {
2700         $context = get_context_instance(CONTEXT_COURSE, $course->id);
2701         $sort = "u.email ASC";
2702         $results = get_users_by_capability($context, 'mod/forum:initialsubscriptions', $fields, $sort, '','','','', false, true);
2703     } else {
2704         $results = $DB->get_records_sql("SELECT $fields
2705                               FROM {user} u,
2706                                    {forum_subscriptions} s $grouptables
2707                              WHERE s.forum = ?
2708                                AND s.userid = u.id
2709                                AND u.deleted = 0  $groupselect
2710                           ORDER BY u.email ASC", $params);
2711     }
2713     static $guestid = null;
2715     if (is_null($guestid)) {
2716         if ($guest = guest_user()) {
2717             $guestid = $guest->id;
2718         } else {
2719             $guestid = 0;
2720         }
2721     }
2723     // Guest user should never be subscribed to a forum.
2724     unset($results[$guestid]);
2726     return $results;
2731 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2734 function forum_get_course_forum($courseid, $type) {
2735 // How to set up special 1-per-course forums
2736     global $CFG, $DB;
2738     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2739         // There should always only be ONE, but with the right combination of
2740         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2741         foreach ($forums as $forum) {
2742             return $forum;   // ie the first one
2743         }
2744     }
2746     // Doesn't exist, so create one now.
2747     $forum->course = $courseid;
2748     $forum->type = "$type";
2749     switch ($forum->type) {
2750         case "news":
2751             $forum->name  = get_string("namenews", "forum");
2752             $forum->intro = get_string("intronews", "forum");
2753             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2754             $forum->assessed = 0;
2755             if ($courseid == SITEID) {
2756                 $forum->name  = get_string("sitenews");
2757                 $forum->forcesubscribe = 0;
2758             }
2759             break;
2760         case "social":
2761             $forum->name  = get_string("namesocial", "forum");
2762             $forum->intro = get_string("introsocial", "forum");
2763             $forum->assessed = 0;
2764             $forum->forcesubscribe = 0;
2765             break;
2766         default:
2767             notify("That forum type doesn't exist!");
2768             return false;
2769             break;
2770     }
2772     $forum->timemodified = time();
2773     $forum->id = $DB->insert_record("forum", $forum);
2775     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2776         notify("Could not find forum module!!");
2777         return false;
2778     }
2779     $mod = new object();
2780     $mod->course = $courseid;
2781     $mod->module = $module->id;
2782     $mod->instance = $forum->id;
2783     $mod->section = 0;
2784     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
2785         notify("Could not add a new course module to the course '" . format_string($course->fullname) . "'");
2786         return false;
2787     }
2788     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
2789         notify("Could not add the new course module to that section");
2790         return false;
2791     }
2792     if (! $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule))) {
2793         notify("Could not update the course module with the correct section");
2794         return false;
2795     }
2796     include_once("$CFG->dirroot/course/lib.php");
2797     rebuild_course_cache($courseid);
2799     return $DB->get_record("forum", array("id" => "$forum->id"));
2803 /**
2804 * Given the data about a posting, builds up the HTML to display it and
2805 * returns the HTML in a string.  This is designed for sending via HTML email.
2806 */
2807 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
2808                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
2810     global $CFG;
2812     if (!isset($userto->viewfullnames[$forum->id])) {
2813         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
2814             error('Course Module ID was incorrect');
2815         }
2816         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2817         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
2818     } else {
2819         $viewfullnames = $userto->viewfullnames[$forum->id];
2820     }
2822     // format the post body
2823     $options = new object();
2824     $options->para = true;
2825     $formattedtext = format_text(trusttext_strip($post->message), $post->format, $options, $course->id);
2827     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
2829     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
2830     $output .= print_user_picture($userfrom, $course->id, $userfrom->picture, false, true);
2831     $output .= '</td>';
2833     if ($post->parent) {
2834         $output .= '<td class="topic">';
2835     } else {
2836         $output .= '<td class="topic starter">';
2837     }
2838     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
2840     $fullname = fullname($userfrom, $viewfullnames);
2841     $by = new object();
2842     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
2843     $by->date = userdate($post->modified, '', $userto->timezone);
2844     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
2846     $output .= '</td></tr>';
2848     $output .= '<tr><td class="left side" valign="top">';
2850     if (isset($userfrom->groups)) {
2851         $groups = $userfrom->groups[$forum->id];
2852     } else {
2853         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
2854             error('Course Module ID was incorrect');
2855         }
2856         $group = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
2857     }
2859     if ($groups) {
2860         $output .= print_group_picture($groups, $course->id, false, true, true);
2861     } else {
2862         $output .= '&nbsp;';
2863     }
2865     $output .= '</td><td class="content">';
2867     $attachments = forum_print_attachments($post, $cm, 'html');
2868     if ($attachments !== '') {
2869         $output .= '<div class="attachments">';
2870         $output .= $attachments;
2871         $output .= '</div>';
2872     }
2874     $output .= $formattedtext;
2876 // Commands
2877     $commands = array();
2879     if ($post->parent) {
2880         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
2881                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
2882     }
2884     if ($reply) {
2885         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
2886                       get_string('reply', 'forum').'</a>';
2887     }
2889     $output .= '<div class="commands">';
2890     $output .= implode(' | ', $commands);
2891     $output .= '</div>';
2893 // Context link to post if required
2894     if ($link) {
2895         $output .= '<div class="link">';
2896         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
2897                      get_string('postincontext', 'forum').'</a>';
2898         $output .= '</div>';
2899     }
2901     if ($footer) {
2902         $output .= '<div class="footer">'.$footer.'</div>';
2903     }
2904     $output .= '</td></tr></table>'."\n\n";
2906     return $output;
2909 /**
2910  * Print a forum post
2911  *
2912  * @param object $post The post to print.
2913  * @param integer $courseid The course this post belongs to.
2914  * @param boolean $ownpost Whether this post belongs to the current user.
2915  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
2916  * @param boolean $link Just print a shortened version of the post as a link to the full post.
2917  * @param object $ratings -- I don't really know --
2918  * @param string $footer Extra stuff to print after the message.
2919  * @param string $highlight Space-separated list of terms to highlight.
2920  * @param int $post_read true, false or -99. If we already know whether this user
2921  *          has read this post, pass that in, otherwise, pass in -99, and this
2922  *          function will work it out.
2923  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
2924  *          the current user can't see this post, if this argument is true
2925  *          (the default) then print a dummy 'you can't see this post' post.
2926  *          If false, don't output anything at all.
2927  */
2928 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
2929                           $ratings=NULL, $footer="", $highlight="", $post_read=null, $dummyifcantsee=true, $istracked=null) {
2931     global $USER, $CFG;
2933     static $stredit, $strdelete, $strreply, $strparent, $strprune;
2934     static $strpruneheading, $displaymode;
2935     static $strmarkread, $strmarkunread;
2937     $post->course = $course->id;
2938     $post->forum  = $forum->id;
2940     // caching
2941     if (!isset($cm->cache)) {
2942         $cm->cache = new object();
2943     }
2945     if (!isset($cm->cache->caps)) {
2946         $cm->cache->caps = array();
2947         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2948         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
2949         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
2950         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
2951         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
2952         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
2953         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
2954         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
2955         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
2956         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
2957     }
2959     if (!isset($cm->uservisible)) {
2960         $cm->uservisible = coursemodule_visible_for_user($cm);
2961     }
2963     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
2964         if (!$dummyifcantsee) {
2965             return;
2966         }
2967         echo '<a id="p'.$post->id.'"></a>';
2968         echo '<table cellspacing="0" class="forumpost">';
2969         echo '<tr class="header"><td class="picture left">';
2970         //        print_user_picture($post->userid, $courseid, $post->picture);
2971         echo '</td>';
2972         if ($post->parent) {
2973             echo '<td class="topic">';
2974         } else {
2975             echo '<td class="topic starter">';
2976         }
2977         echo '<div class="subject">'.get_string('forumsubjecthidden','forum').'</div>';
2978         echo '<div class="author">';
2979         print_string('forumauthorhidden','forum');
2980         echo '</div></td></tr>';
2982         echo '<tr><td class="left side">';
2983         echo '&nbsp;';
2985         // Actual content
2987         echo '</td><td class="content">'."\n";
2988         echo get_string('forumbodyhidden','forum');
2989         echo '</td></tr></table>';
2990         return;
2991     }
2993     if (empty($stredit)) {
2994         $stredit         = get_string('edit', 'forum');
2995         $strdelete       = get_string('delete', 'forum');
2996         $strreply        = get_string('reply', 'forum');
2997         $strparent       = get_string('parent', 'forum');
2998         $strpruneheading = get_string('pruneheading', 'forum');
2999         $strprune        = get_string('prune', 'forum');
3000         $displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3001         $strmarkread     = get_string('markread', 'forum');
3002         $strmarkunread   = get_string('markunread', 'forum');
3004     }
3006     $read_style = '';
3007     // ignore trackign status if not tracked or tracked param missing
3008     if ($istracked) {
3009         if (is_null($post_read)) {
3010             debugging('fetching post_read info');
3011             $post_read = forum_tp_is_post_read($USER->id, $post);
3012         }
3014         if ($post_read) {
3015             $read_style = ' read';
3016         } else {
3017             $read_style = ' unread';
3018             echo '<a name="unread"></a>';
3019         }
3020     }
3022     echo '<a id="p'.$post->id.'"></a>';
3023     echo '<table cellspacing="0" class="forumpost'.$read_style.'">';
3025     // Picture
3026     $postuser = new object();
3027     $postuser->id        = $post->userid;
3028     $postuser->firstname = $post->firstname;
3029     $postuser->lastname  = $post->lastname;
3030     $postuser->imagealt  = $post->imagealt;
3031     $postuser->picture   = $post->picture;
3033     echo '<tr class="header"><td class="picture left">';
3034     print_user_picture($postuser, $course->id);
3035     echo '</td>';
3037     if ($post->parent) {
3038         echo '<td class="topic">';
3039     } else {
3040         echo '<td class="topic starter">';
3041     }
3043     if (!empty($post->subjectnoformat)) {
3044         echo '<div class="subject">'.$post->subject.'</div>';
3045     } else {
3046         echo '<div class="subject">'.format_string($post->subject).'</div>';
3047     }
3049     echo '<div class="author">';
3050     $fullname = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3051     $by = new object();
3052     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.
3053                 $post->userid.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3054     $by->date = userdate($post->modified);
3055     print_string('bynameondate', 'forum', $by);
3056     echo '</div></td></tr>';
3058     echo '<tr><td class="left side">';
3059     if (isset($cm->cache->usersgroups)) {
3060         $groups = array();
3061         if (isset($cm->cache->usersgroups[$post->userid])) {
3062             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3063                 $groups[$gid] = $cm->cache->groups[$gid];
3064             }
3065         }
3066     } else {
3067         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3068     }
3070     if ($groups) {
3071         print_group_picture($groups, $course->id, false, false, true);
3072     } else {
3073         echo '&nbsp;';
3074     }
3076 // Actual content
3078     echo '</td><td class="content">'."\n";
3080     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3082     if ($attachments !== '') {
3083         echo '<div class="attachments">';
3084         echo $attachments;
3085         echo '</div>';
3086     }
3088     $options = new object();
3089     $options->para      = false;
3090     $options->trusttext = true;
3091     if ($link and (strlen(strip_tags($post->message)) > $CFG->forum_longpost)) {
3092         // Print shortened version
3093         echo format_text(forum_shorten_post($post->message), $post->format, $options, $course->id);
3094         $numwords = count_words(strip_tags($post->message));
3095         echo '<p><a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3096         echo get_string('readtherest', 'forum');
3097         echo '</a> ('.get_string('numwords', '', $numwords).')...</p>';
3098     } else {
3099         // Print whole message
3100         if ($highlight) {
3101             echo highlight($highlight, format_text($post->message, $post->format, $options, $course->id));
3102         } else {
3103             echo format_text($post->message, $post->format, $options, $course->id);
3104         }
3105         echo $attachedimages;
3106     }
3109 // Commands
3111     $commands = array();
3113     if ($istracked) {
3114         // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3115         // Don't display the mark read / unread controls in this case.
3116         if ($CFG->forum_usermarksread and isloggedin()) {
3117             if ($post_read) {
3118                 $mcmd = '&amp;mark=unread&amp;postid='.$post->id;
3119                 $mtxt = $strmarkunread;
3120             } else {
3121                 $mcmd = '&amp;mark=read&amp;postid='.$post->id;
3122                 $mtxt = $strmarkread;
3123             }
3124             if ($displaymode == FORUM_MODE_THREADED) {
3125                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3126                               $post->discussion.'&amp;parent='.$post->id.$mcmd.'">'.$mtxt.'</a>';
3127             } else {
3128                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3129                               $post->discussion.$mcmd.'#p'.$post->id.'">'.$mtxt.'</a>';
3130             }
3131         }
3132     }
3134     if ($post->parent) {  // Zoom in to the parent specifically
3135         if ($displaymode == FORUM_MODE_THREADED) {
3136             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3137                       $post->discussion.'&amp;parent='.$post->parent.'">'.$strparent.'</a>';
3138         } else {
3139             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3140                       $post->discussion.'#p'.$post->parent.'">'.$strparent.'</a>';
3141         }
3142     }
3144     $age = time() - $post->created;
3145     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3146     if (!$post->parent and $forum->type == 'news' and $discussion->timestart > time()) {
3147         $age = 0;
3148     }
3149     $editanypost = $cm->cache->caps['mod/forum:editanypost'];
3151     if ($ownpost or $editanypost) {
3152         if (($age < $CFG->maxeditingtime) or $editanypost) {
3153             $commands[] =  '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?edit='.$post->id.'">'.$stredit.'</a>';
3154         }
3155     }
3157     if ($cm->cache->caps['mod/forum:splitdiscussions']
3158                 && $post->parent && $forum->type != 'single') {
3160         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?prune='.$post->id.
3161                       '" title="'.$strpruneheading.'">'.$strprune.'</a>';
3162     }
3164     if (($ownpost and $age < $CFG->maxeditingtime
3165                 and $cm->cache->caps['mod/forum:deleteownpost'])
3166                 or $cm->cache->caps['mod/forum:deleteanypost']) {
3167         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?delete='.$post->id.'">'.$strdelete.'</a>';
3168     }
3170     if ($reply) {
3171         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.$strreply.'</a>';
3172     }
3174     if ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost'])) {
3175         $p = array(
3176             'postid' => $post->id,
3177         );
3178         $button = new portfolio_add_button();
3179         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id));
3180         if (empty($attachments)) {
3181             $button->set_formats(PORTFOLIO_FORMAT_HTML);
3182         }
3183         $commands[] = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3184     }
3186     echo '<div class="commands">';
3187     echo implode(' | ', $commands);
3188     echo '</div>';
3191 // Ratings
3193     $ratingsmenuused = false;
3194     if (!empty($ratings) and isloggedin()) {
3195         echo '<div class="ratings">';
3196         $useratings = true;
3197         if ($ratings->assesstimestart and $ratings->assesstimefinish) {
3198             if ($post->created < $ratings->assesstimestart or $post->created > $ratings->assesstimefinish) {
3199                 $useratings = false;
3200             }
3201         }
3202         if ($useratings) {
3203             $mypost = ($USER->id == $post->userid);
3205             $canviewallratings = $cm->cache->caps['mod/forum:viewanyrating'];
3207             if (isset($cm->cache->ratings)) {
3208                 if (isset($cm->cache->ratings[$post->id])) {
3209                     $allratings = $cm->cache->ratings[$post->id];
3210                 } else {
3211                     $allratings = array(); // no reatings present yet
3212                 }
3213             } else {
3214                 $allratings = NULL; // not preloaded
3215             }
3217             if (isset($cm->cache->myratings)) {
3218                 if (isset($cm->cache->myratings[$post->id])) {
3219                     $myrating = $cm->cache->myratings[$post->id];
3220                 } else {
3221                     $myrating = FORUM_UNSET_POST_RATING; // no reatings present yet
3222                 }
3223             } else {
3224                 $myrating = NULL; // not preloaded
3225             }
3227             if ($canviewallratings and !$mypost) {
3228                 forum_print_ratings($post->id, $ratings->scale, $forum->assessed, $canviewallratings, $allratings);
3229                 if (!empty($ratings->allow)) {
3230                     echo '&nbsp;';
3231                     forum_print_rating_menu($post->id, $USER->id, $ratings->scale, $myrating);
3232                     $ratingsmenuused = true;
3233                 }
3235             } else if ($mypost) {
3236                 forum_print_ratings($post->id, $ratings->scale, $forum->assessed, true, $allratings);
3238             } else if (!empty($ratings->allow) ) {
3239                 forum_print_rating_menu($post->id, $USER->id, $ratings->scale, $myrating);
3240                 $ratingsmenuused = true;
3241             }
3242         }
3243         echo '</div>';
3244     }
3246 // Link to post if required
3248     if ($link) {
3249         echo '<div class="link">';
3250         if ($post->replies == 1) {
3251             $replystring = get_string('repliesone', 'forum', $post->replies);
3252         } else {
3253             $replystring = get_string('repliesmany', 'forum', $post->replies);
3254         }
3255         echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.
3256              get_string('discussthistopic', 'forum').'</a>&nbsp;('.$replystring.')';
3257         echo '</div>';
3258     }
3260     if ($footer) {
3261         echo '<div class="footer">'.$footer.'</div>';
3262     }
3263     echo '</td></tr></table>'."\n\n";
3265     if ($istracked && !$CFG->forum_usermarksread && !$post_read) {
3266         forum_tp_mark_post_read($USER->id, $post, $forum->id);
3267     }
3269     return $ratingsmenuused;
3273 /**
3274  * This function prints the overview of a discussion in the forum listing.
3275  * It needs some discussion information and some post information, these
3276  * happen to be combined for efficiency in the $post parameter by the function
3277  * that calls this one: forum_print_latest_discussions()
3278  *
3279  * @param object $post The post object (passed by reference for speed).
3280  * @param object $forum The forum object.
3281  * @param int $group Current group.
3282  * @param string $datestring Format to use for the dates.
3283  * @param boolean $cantrack Is tracking enabled for this forum.
3284  * @param boolean $forumtracked Is the user tracking this forum.
3285  * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
3286  */
3287 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
3288                                         $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) {
3290     global $USER, $CFG;
3292     static $rowcount;
3293     static $strmarkalldread;
3295     if (empty($modcontext)) {
3296         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3297             error('Course Module ID was incorrect');
3298         }
3299         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3300     }
3302     if (!isset($rowcount)) {
3303         $rowcount = 0;
3304         $strmarkalldread = get_string('markalldread', 'forum');
3305     } else {
3306         $rowcount = ($rowcount + 1) % 2;
3307     }
3309     $post->subject = format_string($post->subject,true);
3311     echo "\n\n";
3312     echo '<tr class="discussion r'.$rowcount.'">';
3314     // Topic
3315     echo '<td class="topic starter">';
3316     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.$post->subject.'</a>';
3317     echo "</td>\n";
3319     // Picture
3320     $postuser = new object;
3321     $postuser->id = $post->userid;
3322     $postuser->firstname = $post->firstname;
3323     $postuser->lastname = $post->lastname;
3324     $postuser->imagealt = $post->imagealt;
3325     $postuser->picture = $post->picture;
3327     echo '<td class="picture">';
3328     print_user_picture($postuser, $forum->course);
3329     echo "</td>\n";
3331     // User name
3332     $fullname = fullname($post, has_capability('moodle/site:viewfullnames', $modcontext));
3333     echo '<td class="author">';
3334     echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->userid.'&amp;course='.$forum->course.'">'.$fullname.'</a>';
3335     echo "</td>\n";
3337     // Group picture
3338     if ($group !== -1) {  // Groups are active - group is a group data object or NULL
3339         echo '<td class="picture group">';
3340         if (!empty($group->picture) and empty($group->hidepicture)) {
3341             print_group_picture($group, $forum->course, false, false, true);
3342         } else if (isset($group->id)) {
3343             if($canviewparticipants) {
3344                 echo '<a href="'.$CFG->wwwroot.'/user/index.php?id='.$forum->course.'&amp;group='.$group->id.'">'.$group->name.'</a>';
3345             } else {
3346                 echo $group->name;
3347             }
3348         }
3349         echo "</td>\n";
3350     }
3352     if (has_capability('mod/forum:viewdiscussion', $modcontext)) {   // Show the column with replies
3353         echo '<td class="replies">';
3354         echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3355         echo $post->replies.'</a>';
3356         echo "</td>\n";
3358         if ($cantrack) {
3359             echo '<td class="replies">';
3360             if ($forumtracked) {
3361                 if ($post->unread > 0) {
3362                     echo '<span class="unread">';
3363                     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#unread">';
3364                     echo $post->unread;
3365                     echo '</a>';
3366                     echo '<a title="'.$strmarkalldread.'" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
3367                          $forum->id.'&amp;d='.$post->discussion.'&amp;mark=read&amp;returnpage=view.php">' .
3368                          '<img src="'.$CFG->pixpath.'/t/clear.gif" class="iconsmall" alt="'.$strmarkalldread.'" /></a>';
3369                     echo '</span>';
3370                 } else {
3371                     echo '<span class="read">';
3372                     echo $post->unread;
3373                     echo '</span>';
3374                 }
3375             } else {
3376                 echo '<span class="read">';
3377                 echo '-';
3378                 echo '</span>';
3379             }
3380             echo "</td>\n";
3381         }
3382     }
3384     echo '<td class="lastpost">';
3385     $usedate = (empty($post->timemodified)) ? $post->modified : $post->timemodified;  // Just in case
3386     $parenturl = (empty($post->lastpostid)) ? '' : '&amp;parent='.$post->lastpostid;
3387     $usermodified = new object();
3388     $usermodified->id        = $post->usermodified;
3389     $usermodified->firstname = $post->umfirstname;
3390     $usermodified->lastname  = $post->umlastname;
3391     echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->usermodified.'&amp;course='.$forum->course.'">'.
3392          fullname($usermodified).'</a><br />';
3393     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.$parenturl.'">'.
3394           userdate($usedate, $datestring).'</a>';
3395     echo "</td>\n";
3397     echo "</tr>\n\n";
3402 /**
3403  * Given a post object that we already know has a long message
3404  * this function truncates the message nicely to the first
3405  * sane place between $CFG->forum_longpost and $CFG->forum_shortpost
3406  */
3407 function forum_shorten_post($message) {
3409    global $CFG;
3411    $i = 0;
3412    $tag = false;
3413    $length = strlen($message);
3414    $count = 0;
3415    $stopzone = false;
3416    $truncate = 0;
3418    for ($i=0; $i<$length; $i++) {
3419        $char = $message[$i];
3421        switch ($char) {
3422            case "<":
3423                $tag = true;
3424                break;
3425            case ">":
3426                $tag = false;
3427                break;
3428            default:
3429                if (!$tag) {
3430                    if ($stopzone) {
3431                        if ($char == ".") {
3432                            $truncate = $i+1;
3433                            break 2;
3434                        }
3435                    }
3436                    $count++;
3437                }
3438                break;
3439        }
3440        if (!$stopzone) {
3441            if ($count > $CFG->forum_shortpost) {
3442                $stopzone = true;
3443            }
3444        }
3445    }
3447    if (!$truncate) {
3448        $truncate = $i;
3449    }