MDL-18910 moving modedit features to modname_supports()
[moodle.git] / mod / forum / lib.php
1 <?php  // $Id$
3 require_once($CFG->libdir.'/filelib.php');
4 require_once($CFG->libdir.'/eventslib.php');
5 require_once($CFG->libdir.'/portfoliolib.php');
7 /// CONSTANTS ///////////////////////////////////////////////////////////
9 define('FORUM_MODE_FLATOLDEST', 1);
10 define('FORUM_MODE_FLATNEWEST', -1);
11 define('FORUM_MODE_THREADED', 2);
12 define('FORUM_MODE_NESTED', 3);
14 define('FORUM_FORCESUBSCRIBE', 1);
15 define('FORUM_INITIALSUBSCRIBE', 2);
16 define('FORUM_DISALLOWSUBSCRIBE',3);
18 define('FORUM_TRACKING_OFF', 0);
19 define('FORUM_TRACKING_OPTIONAL', 1);
20 define('FORUM_TRACKING_ON', 2);
22 define('FORUM_UNSET_POST_RATING', -999);
24 define ('FORUM_AGGREGATE_NONE', 0); //no ratings
25 define ('FORUM_AGGREGATE_AVG', 1);
26 define ('FORUM_AGGREGATE_COUNT', 2);
27 define ('FORUM_AGGREGATE_MAX', 3);
28 define ('FORUM_AGGREGATE_MIN', 4);
29 define ('FORUM_AGGREGATE_SUM', 5);
31 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
33 /**
34  * Given an object containing all the necessary data,
35  * (defined by the form in mod_form.php) this function
36  * will create a new instance and return the id number
37  * of the new instance.
38  * @param object $forum add forum instance (with magic quotes)
39  * @return int intance id
40  */
41 function forum_add_instance($forum) {
42     global $CFG, $DB;
44     $forum->timemodified = time();
46     if (empty($forum->assessed)) {
47         $forum->assessed = 0;
48     }
50     if (empty($forum->ratingtime) or empty($forum->assessed)) {
51         $forum->assesstimestart  = 0;
52         $forum->assesstimefinish = 0;
53     }
55     if (!$forum->id = $DB->insert_record('forum', $forum)) {
56         return false;
57     }
59     if ($forum->type == 'single') {  // Create related discussion.
60         $discussion = new object();
61         $discussion->course        = $forum->course;
62         $discussion->forum         = $forum->id;
63         $discussion->name          = $forum->name;
64         $discussion->intro         = $forum->intro;
65         $discussion->assessed      = $forum->assessed;
66         $discussion->messageformat = $forum->messageformat;
67         $discussion->mailnow       = false;
68         $discussion->groupid       = -1;
70         $message = '';
72         if (! forum_add_discussion($discussion, null, $message)) {
73             print_error('cannotadd', 'forum');
74         }
75     }
77     if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
78     /// all users should be subscribed initially
79     /// Note: forum_get_potential_subscribers should take the forum context,
80     /// but that does not exist yet, becuase the forum is only half build at this
81     /// stage. However, because the forum is brand new, we know that there are
82     /// no role assignments or overrides in the forum context, so using the
83     /// course context gives the same list of users.
84         $users = forum_get_potential_subscribers(get_context_instance(CONTEXT_COURSE, $forum->course), 0, 'u.id, u.email', '');
85         foreach ($users as $user) {
86             forum_subscribe($user->id, $forum->id);
87         }
88     }
90     forum_grade_item_update($forum);
92     return $forum->id;
93 }
96 /**
97  * Given an object containing all the necessary data,
98  * (defined by the form in mod_form.php) this function
99  * will update an existing instance with new data.
100  * @param object $forum forum instance (with magic quotes)
101  * @return bool success
102  */
103 function forum_update_instance($forum) {
104     global $DB;
106     $forum->timemodified = time();
107     $forum->id           = $forum->instance;
109     if (empty($forum->assessed)) {
110         $forum->assessed = 0;
111     }
113     if (empty($forum->ratingtime) or empty($forum->assessed)) {
114         $forum->assesstimestart  = 0;
115         $forum->assesstimefinish = 0;
116     }
118     $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
120     // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
121     // if  scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
122     // 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
123     if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
124         forum_update_grades($forum); // recalculate grades for the forum
125     }
127     if ($forum->type == 'single') {  // Update related discussion and post.
128         if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
129             if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC')) {
130                 notify('Warning! There is more than one discussion in this forum - using the most recent');
131                 $discussion = array_pop($discussions);
132             } else {
133                 // try to recover by creating initial discussion - MDL-16262
134                 $discussion = new object();
135                 $discussion->course          = $forum->course;
136                 $discussion->forum           = $forum->id;
137                 $discussion->name            = $forum->name;
138                 $discussion->intro           = $forum->intro;
139                 $discussion->assessed        = $forum->assessed;
140                 $discussion->messageformat   = $forum->messageformat;
141                 $discussion->mailnow         = false;
142                 $discussion->groupid         = -1;
144                 forum_add_discussion($discussion, null, $message);
146                 if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
147                     print_error('cannotadd', 'forum');
148                 }
149             }
150         }
151         if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
152             print_error('cannotfindfirstpost', 'forum');
153         }
155         $post->subject  = $forum->name;
156         $post->message  = $forum->intro;
157         $post->modified = $forum->timemodified;
159         if (! $DB->update_record('forum_posts', ($post))) {
160             print_error('cannotupdatefirstpost', 'forum');
161         }
163         $discussion->name = $forum->name;
165         if (! $DB->update_record('forum_discussions', ($discussion))) {
166             print_error('cannotupdatediscussion', 'forum');
167         }
168     }
170     if (!$DB->update_record('forum', $forum)) {
171         print_error('cannotupdateforum', 'forum');
172     }
174     forum_grade_item_update($forum);
176     return true;
180 /**
181  * Given an ID of an instance of this module,
182  * this function will permanently delete the instance
183  * and any data that depends on it.
184  * @param int forum instance id
185  * @return bool success
186  */
187 function forum_delete_instance($id) {
188     global $DB;
190     if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
191         return false;
192     }
193     if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
194         return false;
195     }
196     if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
197         return false;
198     }
200     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
202     // now get rid of all files
203     $fs = get_file_storage();
204     $fs->delete_area_files($context->id);
206     $result = true;
208     if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
209         foreach ($discussions as $discussion) {
210             if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
211                 $result = false;
212             }
213         }
214     }
216     if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
217         $result = false;
218     }
220     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
222     if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
223         $result = false;
224     }
226     forum_grade_item_delete($forum);
228     return $result;
232 /**
233  * Indicates API features that the forum supports.
234  *
235  * @param string $feature
236  * @return mixed True if yes (some features may use other values)
237  */
238 function forum_supports($feature) {
239     switch($feature) {
240         case FEATURE_GROUPS:                  return true;
241         case FEATURE_GROUPINGS:               return true;
242         case FEATURE_GROUPMEMBERSONLY:        return true;
243         case FEATURE_MODEDIT_INTRO_EDITOR:    return true;
244         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
245         case FEATURE_COMPLETION_HAS_RULES:    return true;
246         case FEATURE_GRADE_HAS_GRADE:         return true;
247         case FEATURE_GRADE_OUTCOMES:          return true;
249         default: return null;
250     }
254 /**
255  * Obtains the automatic completion state for this forum based on any conditions
256  * in forum settings.
257  *
258  * @param object $course Course
259  * @param object $cm Course-module
260  * @param int $userid User ID
261  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
262  * @return bool True if completed, false if not. (If no conditions, then return
263  *   value depends on comparison type)
264  */
265 function forum_get_completion_state($course,$cm,$userid,$type) {
266     global $CFG,$DB;
268     // Get forum details
269     if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
270         throw new Exception("Can't find forum {$cm->instance}");
271     }
273     $result=$type; // Default return value
275     $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
276     $postcountsql="
277 SELECT
278     COUNT(1)
279 FROM
280     {forum_posts} fp
281     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
282 WHERE
283     fp.userid=:userid AND fd.forum=:forumid";
285     if ($forum->completiondiscussions) {
286         $value = $forum->completiondiscussions <=
287                  $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
288         if ($type == COMPLETION_AND) {
289             $result = $result && $value;
290         } else {
291             $result = $result || $value;
292         }
293     }
294     if ($forum->completionreplies) {
295         $value = $forum->completionreplies <=
296                  $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
297         if ($type==COMPLETION_AND) {
298             $result = $result && $value;
299         } else {
300             $result = $result || $value;
301         }
302     }
303     if ($forum->completionposts) {
304         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
305         if ($type == COMPLETION_AND) {
306             $result = $result && $value;
307         } else {
308             $result = $result || $value;
309         }
310     }
312     return $result;
316 /**
317  * Function to be run periodically according to the moodle cron
318  * Finds all posts that have yet to be mailed out, and mails them
319  * out to all subscribers
320  * @return void
321  */
322 function forum_cron() {
323     global $CFG, $USER, $DB;
325     $site = get_site();
327     // all users that are subscribed to any post that needs sending
328     $users = array();
330     // status arrays
331     $mailcount  = array();
332     $errorcount = array();
334     // caches
335     $discussions     = array();
336     $forums          = array();
337     $courses         = array();
338     $coursemodules   = array();
339     $subscribedusers = array();
342     // Posts older than 2 days will not be mailed.  This is to avoid the problem where
343     // cron has not been running for a long time, and then suddenly people are flooded
344     // with mail from the past few weeks or months
345     $timenow   = time();
346     $endtime   = $timenow - $CFG->maxeditingtime;
347     $starttime = $endtime - 48 * 3600;   // Two days earlier
349     if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
350         // Mark them all now as being mailed.  It's unlikely but possible there
351         // might be an error later so that a post is NOT actually mailed out,
352         // but since mail isn't crucial, we can accept this risk.  Doing it now
353         // prevents the risk of duplicated mails, which is a worse problem.
355         if (!forum_mark_old_posts_as_mailed($endtime)) {
356             mtrace('Errors occurred while trying to mark some posts as being mailed.');
357             return false;  // Don't continue trying to mail them, in case we are in a cron loop
358         }
360         // checking post validity, and adding users to loop through later
361         foreach ($posts as $pid => $post) {
363             $discussionid = $post->discussion;
364             if (!isset($discussions[$discussionid])) {
365                 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
366                     $discussions[$discussionid] = $discussion;
367                 } else {
368                     mtrace('Could not find discussion '.$discussionid);
369                     unset($posts[$pid]);
370                     continue;
371                 }
372             }
373             $forumid = $discussions[$discussionid]->forum;
374             if (!isset($forums[$forumid])) {
375                 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
376                     $forums[$forumid] = $forum;
377                 } else {
378                     mtrace('Could not find forum '.$forumid);
379                     unset($posts[$pid]);
380                     continue;
381                 }
382             }
383             $courseid = $forums[$forumid]->course;
384             if (!isset($courses[$courseid])) {
385                 if ($course = $DB->get_record('course', array('id' => $courseid))) {
386                     $courses[$courseid] = $course;
387                 } else {
388                     mtrace('Could not find course '.$courseid);
389                     unset($posts[$pid]);
390                     continue;
391                 }
392             }
393             if (!isset($coursemodules[$forumid])) {
394                 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
395                     $coursemodules[$forumid] = $cm;
396                 } else {
397                     mtrace('Could not course module for forum '.$forumid);
398                     unset($posts[$pid]);
399                     continue;
400                 }
401             }
404             // caching subscribed users of each forum
405             if (!isset($subscribedusers[$forumid])) {
406                 $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
407                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext)) {
408                     foreach ($subusers as $postuser) {
409                         // do not try to mail users with stopped email
410                         if ($postuser->emailstop) {
411                             if (!empty($CFG->forum_logblocked)) {
412                                 add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
413                             }
414                             continue;
415                         }
416                         // this user is subscribed to this forum
417                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
418                         // this user is a user we have to process later
419                         $users[$postuser->id] = $postuser;
420                     }
421                     unset($subusers); // release memory
422                 }
423             }
425             $mailcount[$pid] = 0;
426             $errorcount[$pid] = 0;
427         }
428     }
430     if ($users && $posts) {
432         $urlinfo = parse_url($CFG->wwwroot);
433         $hostname = $urlinfo['host'];
435         foreach ($users as $userto) {
437             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
439             // set this so that the capabilities are cached, and environment matches receiving user
440             cron_setup_user($userto);
442             mtrace('Processing user '.$userto->id);
444             // init caches
445             $userto->viewfullnames = array();
446             $userto->canpost       = array();
447             $userto->markposts     = array();
448             $userto->enrolledin    = array();
450             // reset the caches
451             foreach ($coursemodules as $forumid=>$unused) {
452                 $coursemodules[$forumid]->cache       = new object();
453                 $coursemodules[$forumid]->cache->caps = array();
454                 unset($coursemodules[$forumid]->uservisible);
455             }
457             foreach ($posts as $pid => $post) {
459                 // Set up the environment for the post, discussion, forum, course
460                 $discussion = $discussions[$post->discussion];
461                 $forum      = $forums[$discussion->forum];
462                 $course     = $courses[$forum->course];
463                 $cm         =& $coursemodules[$forum->id];
465                 // Do some checks  to see if we can bail out now
466                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
467                     continue; // user does not subscribe to this forum
468                 }
470                 // Verify user is enrollend in course - if not do not send any email
471                 if (!isset($userto->enrolledin[$course->id])) {
472                     $userto->enrolledin[$course->id] = has_capability('moodle/course:view', get_context_instance(CONTEXT_COURSE, $course->id));
473                 }
474                 if (!$userto->enrolledin[$course->id]) {
475                     // oops - this user should not receive anything from this course
476                     continue;
477                 }
479                 // Get info about the sending user
480                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
481                     $userfrom = $users[$post->userid];
482                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
483                     $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
484                 } else {
485                     mtrace('Could not find user '.$post->userid);
486                     continue;
487                 }
489                 // setup global $COURSE properly - needed for roles and languages
490                 cron_setup_user($userto, $course);
492                 // Fill caches
493                 if (!isset($userto->viewfullnames[$forum->id])) {
494                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
495                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
496                 }
497                 if (!isset($userto->canpost[$discussion->id])) {
498                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
499                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
500                 }
501                 if (!isset($userfrom->groups[$forum->id])) {
502                     if (!isset($userfrom->groups)) {
503                         $userfrom->groups = array();
504                         $users[$userfrom->id]->groups = array();
505                     }
506                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
507                     $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
508                 }
510                 // Make sure groups allow this user to see this email
511                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
512                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
513                         continue;                           // Be safe and don't send it to anyone
514                     }
516                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
517                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
518                         continue;
519                     }
520                 }
522                 // Make sure we're allowed to see it...
523                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
524                     mtrace('user '.$userto->id. ' can not see '.$post->id);
525                     continue;
526                 }
528                 // OK so we need to send the email.
530                 // Does the user want this post in a digest?  If so postpone it for now.
531                 if ($userto->maildigest > 0) {
532                     // This user wants the mails to be in digest form
533                     $queue = new object();
534                     $queue->userid       = $userto->id;
535                     $queue->discussionid = $discussion->id;
536                     $queue->postid       = $post->id;
537                     $queue->timemodified = $post->created;
538                     if (!$DB->insert_record('forum_queue', $queue)) {
539                         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.");
540                     }
541                     continue;
542                 }
545                 // Prepare to actually send the post now, and build up the content
547                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
549                 $userfrom->customheaders = array (  // Headers to make emails easier to track
550                            'Precedence: Bulk',
551                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
552                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
553                            'Message-ID: <moodlepost'.$post->id.'@'.$hostname.'>',
554                            'In-Reply-To: <moodlepost'.$post->parent.'@'.$hostname.'>',
555                            'References: <moodlepost'.$post->parent.'@'.$hostname.'>',
556                            'X-Course-Id: '.$course->id,
557                            'X-Course-Name: '.format_string($course->fullname, true)
558                 );
561                 $postsubject = "$course->shortname: ".format_string($post->subject,true);
562                 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
563                 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
565                 // Send the post now!
567                 mtrace('Sending ', '');
569                 $eventdata = new object();
570                 $eventdata->component        = 'mod/forum';
571                 $eventdata->name             = 'posts';
572                 $eventdata->userfrom         = $userfrom;
573                 $eventdata->userto           = $userto;
574                 $eventdata->subject          = $postsubject;
575                 $eventdata->fullmessage      = $posttext;
576                 $eventdata->fullmessageformat = FORMAT_PLAIN;
577                 $eventdata->fullmessagehtml  = $posthtml;
578                 $eventdata->smallmessage     = '';
579                 if ( events_trigger('message_send', $eventdata) > 0){
581                     mtrace("Error: mod/forum/cron.php: Could not send out mail for id $post->id to user $userto->id".
582                          " ($userto->email) .. not trying again.");
583                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
584                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
585                     $errorcount[$post->id]++;
586                 } else if ($mailresult === 'emailstop') {
587                     // should not be reached anymore - see check above
588                 } else {
589                     $mailcount[$post->id]++;
591                 // Mark post as read if forum_usermarksread is set off
592                     if (!$CFG->forum_usermarksread) {
593                         $userto->markposts[$post->id] = $post->id;
594                     }
595                 }
597                 mtrace('post '.$post->id. ': '.$post->subject);
598             }
600             // mark processed posts as read
601             forum_tp_mark_posts_read($userto, $userto->markposts);
602         }
603     }
605     if ($posts) {
606         foreach ($posts as $post) {
607             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
608             if ($errorcount[$post->id]) {
609                 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
610             }
611         }
612     }
614     // release some memory
615     unset($subscribedusers);
616     unset($mailcount);
617     unset($errorcount);
619     cron_setup_user();
621     $sitetimezone = $CFG->timezone;
623     // Now see if there are any digest mails waiting to be sent, and if we should send them
625     mtrace('Starting digest processing...');
627     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
629     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
630         set_config('digestmailtimelast', 0);
631     }
633     $timenow = time();
634     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
636     // Delete any really old ones (normally there shouldn't be any)
637     $weekago = $timenow - (7 * 24 * 3600);
638     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
639     mtrace ('Cleaned old digest records');
641     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
643         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
645         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
647         if ($digestposts_rs->valid()) {
649             // We have work to do
650             $usermailcount = 0;
652             //caches - reuse the those filled before too
653             $discussionposts = array();
654             $userdiscussions = array();
656             foreach ($digestposts_rs as $digestpost) {
657                 if (!isset($users[$digestpost->userid])) {
658                     if ($user = $DB->get_record('user', array('id' => $digestpost->userid))) {
659                         $users[$digestpost->userid] = $user;
660                     } else {
661                         continue;
662                     }
663                 }
664                 $postuser = $users[$digestpost->userid];
665                 if ($postuser->emailstop) {
666                     if (!empty($CFG->forum_logblocked)) {
667                         add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
668                     }
669                     continue;
670                 }
672                 if (!isset($posts[$digestpost->postid])) {
673                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
674                         $posts[$digestpost->postid] = $post;
675                     } else {
676                         continue;
677                     }
678                 }
679                 $discussionid = $digestpost->discussionid;
680                 if (!isset($discussions[$discussionid])) {
681                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
682                         $discussions[$discussionid] = $discussion;
683                     } else {
684                         continue;
685                     }
686                 }
687                 $forumid = $discussions[$discussionid]->forum;
688                 if (!isset($forums[$forumid])) {
689                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
690                         $forums[$forumid] = $forum;
691                     } else {
692                         continue;
693                     }
694                 }
696                 $courseid = $forums[$forumid]->course;
697                 if (!isset($courses[$courseid])) {
698                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
699                         $courses[$courseid] = $course;
700                     } else {
701                         continue;
702                     }
703                 }
705                 if (!isset($coursemodules[$forumid])) {
706                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
707                         $coursemodules[$forumid] = $cm;
708                     } else {
709                         continue;
710                     }
711                 }
712                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
713                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
714             }
715             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
717             // Data collected, start sending out emails to each user
718             foreach ($userdiscussions as $userid => $thesediscussions) {
720                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
722                 cron_setup_user();
724                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
726                 // First of all delete all the queue entries for this user
727                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
728                 $userto = $users[$userid];
730                 // Override the language and timezone of the "current" user, so that
731                 // mail is customised for the receiver.
732                 cron_setup_user($userto);
734                 // init caches
735                 $userto->viewfullnames = array();
736                 $userto->canpost       = array();
737                 $userto->markposts     = array();
739                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
741                 $headerdata = new object();
742                 $headerdata->sitename = format_string($site->fullname, true);
743                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
745                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
746                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
748                 $posthtml = "<head>";
749                 foreach ($CFG->stylesheets as $stylesheet) {
750                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
751                 }
752                 $posthtml .= "</head>\n<body id=\"email\">\n";
753                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
755                 foreach ($thesediscussions as $discussionid) {
757                     @set_time_limit(120);   // to be reset for each post
759                     $discussion = $discussions[$discussionid];
760                     $forum      = $forums[$discussion->forum];
761                     $course     = $courses[$forum->course];
762                     $cm         = $coursemodules[$forum->id];
764                     //override language
765                     cron_setup_user($userto, $course);
767                     // Fill caches
768                     if (!isset($userto->viewfullnames[$forum->id])) {
769                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
770                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
771                     }
772                     if (!isset($userto->canpost[$discussion->id])) {
773                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
774                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
775                     }
777                     $strforums      = get_string('forums', 'forum');
778                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
779                     $canreply       = $userto->canpost[$discussion->id];
781                     $posttext .= "\n \n";
782                     $posttext .= '=====================================================================';
783                     $posttext .= "\n \n";
784                     $posttext .= "$course->shortname -> $strforums -> ".format_string($forum->name,true);
785                     if ($discussion->name != $forum->name) {
786                         $posttext  .= " -> ".format_string($discussion->name,true);
787                     }
788                     $posttext .= "\n";
790                     $posthtml .= "<p><font face=\"sans-serif\">".
791                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> -> ".
792                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
793                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
794                     if ($discussion->name == $forum->name) {
795                         $posthtml .= "</font></p>";
796                     } else {
797                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
798                     }
799                     $posthtml .= '<p>';
801                     $postsarray = $discussionposts[$discussionid];
802                     sort($postsarray);
804                     foreach ($postsarray as $postid) {
805                         $post = $posts[$postid];
807                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
808                             $userfrom = $users[$post->userid];
809                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
810                             $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
811                         } else {
812                             mtrace('Could not find user '.$post->userid);
813                             continue;
814                         }
816                         if (!isset($userfrom->groups[$forum->id])) {
817                             if (!isset($userfrom->groups)) {
818                                 $userfrom->groups = array();
819                                 $users[$userfrom->id]->groups = array();
820                             }
821                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
822                             $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
823                         }
825                         $userfrom->customheaders = array ("Precedence: Bulk");
827                         if ($userto->maildigest == 2) {
828                             // Subjects only
829                             $by = new object();
830                             $by->name = fullname($userfrom);
831                             $by->date = userdate($post->modified);
832                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
833                             $posttext .= "\n---------------------------------------------------------------------";
835                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
836                             $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>';
838                         } else {
839                             // The full treatment
840                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
841                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
843                         // Create an array of postid's for this user to mark as read.
844                             if (!$CFG->forum_usermarksread) {
845                                 $userto->markposts[$post->id] = $post->id;
846                             }
847                         }
848                     }
849                     if ($canunsubscribe) {
850                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\"><a href=\"$CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\">".get_string("unsubscribe", "forum")."</a></font></div>";
851                     } else {
852                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
853                     }
854                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
855                 }
856                 $posthtml .= '</body>';
858                 if ($userto->mailformat != 1) {
859                     // This user DOESN'T want to receive HTML
860                     $posthtml = '';
861                 }
863                 $eventdata = new object();
864                 $eventdata->component        = 'mod/forum';
865                 $eventdata->name             = 'digests';
866                 $eventdata->userfrom         = $site->shortname;
867                 $eventdata->userto           = $userto;
868                 $eventdata->subject          = $postsubject;
869                 $eventdata->fullmessage      = $posttext;
870                 $eventdata->fullmessageformat = FORMAT_PLAIN;
871                 $eventdata->fullmessagehtml  = $posthtml;
872                 $eventdata->smallmessage     = '';
873                 if ( events_trigger('message_send', $eventdata) > 0){
874                     mtrace("ERROR!");
875                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
876                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
877                 } else if ($mailresult === 'emailstop') {
878                     // should not happen anymore - see check above
879                 } else {
880                     mtrace("success.");
881                     $usermailcount++;
883                     // Mark post as read if forum_usermarksread is set off
884                     forum_tp_mark_posts_read($userto, $userto->markposts);
885                 }
886             }
887         }
888     /// We have finishied all digest emails, update $CFG->digestmailtimelast
889         set_config('digestmailtimelast', $timenow);
890     }
892     cron_setup_user();
894     if (!empty($usermailcount)) {
895         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
896     }
898     if (!empty($CFG->forum_lastreadclean)) {
899         $timenow = time();
900         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
901             set_config('forum_lastreadclean', $timenow);
902             mtrace('Removing old forum read tracking info...');
903             forum_tp_clean_read_records();
904         }
905     } else {
906         set_config('forum_lastreadclean', time());
907     }
910     return true;
913 /**
914  * Builds and returns the body of the email notification in plain text.
915  *
916  * @param object $course
917  * @param object $forum
918  * @param object $discussion
919  * @param object $post
920  * @param object $userfrom
921  * @param object $userto
922  * @param boolean $bare
923  * @return string The email body in plain text format.
924  */
925 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
926     global $CFG, $USER;
928     if (!isset($userto->viewfullnames[$forum->id])) {
929         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
930         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
931     } else {
932         $viewfullnames = $userto->viewfullnames[$forum->id];
933     }
935     if (!isset($userto->canpost[$discussion->id])) {
936         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
937         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
938     } else {
939         $canreply = $userto->canpost[$discussion->id];
940     }
942     $by = New stdClass;
943     $by->name = fullname($userfrom, $viewfullnames);
944     $by->date = userdate($post->modified, "", $userto->timezone);
946     $strbynameondate = get_string('bynameondate', 'forum', $by);
948     $strforums = get_string('forums', 'forum');
950     $canunsubscribe = ! forum_is_forcesubscribed($forum);
952     $posttext = '';
954     if (!$bare) {
955         $posttext  = "$course->shortname -> $strforums -> ".format_string($forum->name,true);
957         if ($discussion->name != $forum->name) {
958             $posttext  .= " -> ".format_string($discussion->name,true);
959         }
960     }
962     $posttext .= "\n---------------------------------------------------------------------\n";
963     $posttext .= format_string($post->subject,true);
964     if ($bare) {
965         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
966     }
967     $posttext .= "\n".$strbynameondate."\n";
968     $posttext .= "---------------------------------------------------------------------\n";
969     $posttext .= format_text_email($post->message, $post->messageformat);
970     $posttext .= "\n\n";
971     $posttext .= forum_print_attachments($post, $cm, "text");
973     if (!$bare && $canreply) {
974         $posttext .= "---------------------------------------------------------------------\n";
975         $posttext .= get_string("postmailinfo", "forum", $course->shortname)."\n";
976         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
977     }
978     if (!$bare && $canunsubscribe) {
979         $posttext .= "\n---------------------------------------------------------------------\n";
980         $posttext .= get_string("unsubscribe", "forum");
981         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
982     }
984     return $posttext;
987 /**
988  * Builds and returns the body of the email notification in html format.
989  *
990  * @param object $course
991  * @param object $forum
992  * @param object $discussion
993  * @param object $post
994  * @param object $userfrom
995  * @param object $userto
996  * @return string The email text in HTML format
997  */
998 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
999     global $CFG;
1001     if ($userto->mailformat != 1) {  // Needs to be HTML
1002         return '';
1003     }
1005     if (!isset($userto->canpost[$discussion->id])) {
1006         $canreply = forum_user_can_post($forum, $discussion, $userto);
1007     } else {
1008         $canreply = $userto->canpost[$discussion->id];
1009     }
1011     $strforums = get_string('forums', 'forum');
1012     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1014     $posthtml = '<head>';
1015     foreach ($CFG->stylesheets as $stylesheet) {
1016         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1017     }
1018     $posthtml .= '</head>';
1019     $posthtml .= "\n<body id=\"email\">\n\n";
1021     $posthtml .= '<div class="navbar">'.
1022     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$course->shortname.'</a> &raquo; '.
1023     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1024     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1025     if ($discussion->name == $forum->name) {
1026         $posthtml .= '</div>';
1027     } else {
1028         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1029                      format_string($discussion->name,true).'</a></div>';
1030     }
1031     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1033     if ($canunsubscribe) {
1034         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1035                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1036                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1037     }
1039     $posthtml .= '</body>';
1041     return $posthtml;
1045 /**
1046  *
1047  * @param object $course
1048  * @param object $user
1049  * @param object $mod TODO this is not used in this function, refactor
1050  * @param object $forum
1051  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1052  */
1053 function forum_user_outline($course, $user, $mod, $forum) {
1054     if ($count = forum_count_user_posts($forum->id, $user->id)) {
1055         if ($count->postcount > 0) {
1056             $result = new object();
1057             $result->info = get_string("numposts", "forum", $count->postcount);
1058             $result->time = $count->lastpost;
1059             return $result;
1060         }
1061     }
1062     return NULL;
1066 /**
1067  *
1068  */
1069 function forum_user_complete($course, $user, $mod, $forum) {
1070     global $CFG,$USER;
1072     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1074         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1075             print_error('invalidcoursemodule');
1076         }
1077         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1079         // preload all user ratings for these discussions - one query only and minimal memory
1080         $cm->cache->ratings = array();
1081         $cm->cache->myratings = array();
1082         if ($postratings = forum_get_all_user_ratings($user->id, $discussions)) {
1083             foreach ($postratings as $pr) {
1084                 if (!isset($cm->cache->ratings[$pr->postid])) {
1085                     $cm->cache->ratings[$pr->postid] = array();
1086                 }
1087                 $cm->cache->ratings[$pr->postid][$pr->id] = $pr->rating;
1089                 if ($pr->userid == $USER->id) {
1090                     $cm->cache->myratings[$pr->postid] = $pr->rating;
1091                 }
1092             }
1093             unset($postratings);
1094         }
1096         foreach ($posts as $post) {
1097             if (!isset($discussions[$post->discussion])) {
1098                 continue;
1099             }
1100             $discussion = $discussions[$post->discussion];
1102             $ratings = null;
1104             if ($forum->assessed) {
1105                 if ($scale = make_grades_menu($forum->scale)) {
1106                     $ratings =new object();
1107                     $ratings->scale = $scale;
1108                     $ratings->assesstimestart = $forum->assesstimestart;
1109                     $ratings->assesstimefinish = $forum->assesstimefinish;
1110                     $ratings->allow = false;
1111                 }
1112             }
1113             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false, $ratings);
1114         }
1115     } else {
1116         echo "<p>".get_string("noposts", "forum")."</p>";
1117     }
1125 /**
1126  *
1127  */
1128 function forum_print_overview($courses,&$htmlarray) {
1129     global $USER, $CFG, $DB;
1131     $LIKE = $DB->sql_ilike();
1133     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1134         return array();
1135     }
1137     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1138         return;
1139     }
1142     // get all forum logs in ONE query (much better!)
1143     $params = array();
1144     $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {log} l "
1145         ." JOIN {course_modules} cm ON cm.id = cmid "
1146         ." WHERE (";
1147     foreach ($courses as $course) {
1148         $sql .= '(l.course = ? AND l.time > ?) OR ';
1149         $params[] = $course->id;
1150         $params[] = $course->lastaccess;
1151     }
1152     $sql = substr($sql,0,-3); // take off the last OR
1154     $sql .= ") AND l.module = 'forum' AND action $LIKE 'add post%' "
1155         ." AND userid != ? GROUP BY cmid,l.course,instance";
1157     $params[] = $USER->id;
1159     if (!$new = $DB->get_records_sql($sql, $params)) {
1160         $new = array(); // avoid warnings
1161     }
1163     // also get all forum tracking stuff ONCE.
1164     $trackingforums = array();
1165     foreach ($forums as $forum) {
1166         if (forum_tp_can_track_forums($forum)) {
1167             $trackingforums[$forum->id] = $forum;
1168         }
1169     }
1171     if (count($trackingforums) > 0) {
1172         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1173         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1174             ' FROM {forum_posts} p '.
1175             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1176             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1177         $params = array($USER->id);
1179         foreach ($trackingforums as $track) {
1180             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1181             $params[] = $track->id;
1182             $params[] = get_current_group($track->course);
1183         }
1184         $sql = substr($sql,0,-3); // take off the last OR
1185         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1186         $params[] = $cutoffdate;
1188         if (!$unread = $DB->get_records_sql($sql, $params)) {
1189             $unread = array();
1190         }
1191     } else {
1192         $unread = array();
1193     }
1195     if (empty($unread) and empty($new)) {
1196         return;
1197     }
1199     $strforum = get_string('modulename','forum');
1200     $strnumunread = get_string('overviewnumunread','forum');
1201     $strnumpostssince = get_string('overviewnumpostssince','forum');
1203     foreach ($forums as $forum) {
1204         $str = '';
1205         $count = 0;
1206         $thisunread = 0;
1207         $showunread = false;
1208         // either we have something from logs, or trackposts, or nothing.
1209         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1210             $count = $new[$forum->id]->count;
1211         }
1212         if (array_key_exists($forum->id,$unread)) {
1213             $thisunread = $unread[$forum->id]->count;
1214             $showunread = true;
1215         }
1216         if ($count > 0 || $thisunread > 0) {
1217             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1218                 $forum->name.'</a></div>';
1219             $str .= '<div class="info">';
1220             $str .= $count.' '.$strnumpostssince;
1221             if (!empty($showunread)) {
1222                 $str .= '<br />'.$thisunread .' '.$strnumunread;
1223             }
1224             $str .= '</div></div>';
1225         }
1226         if (!empty($str)) {
1227             if (!array_key_exists($forum->course,$htmlarray)) {
1228                 $htmlarray[$forum->course] = array();
1229             }
1230             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1231                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1232             }
1233             $htmlarray[$forum->course]['forum'] .= $str;
1234         }
1235     }
1238 /**
1239  * Given a course and a date, prints a summary of all the new
1240  * messages posted in the course since that date
1241  * @param object $course
1242  * @param bool $viewfullnames capability
1243  * @param int $timestart
1244  * @return bool success
1245  */
1246 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1247     global $CFG, $USER, $DB;
1249     // do not use log table if possible, it may be huge and is expensive to join with other tables
1251     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1252                                               d.timestart, d.timeend, d.userid AS duserid,
1253                                               u.firstname, u.lastname, u.email, u.picture
1254                                          FROM {forum_posts} p
1255                                               JOIN {forum_discussions} d ON d.id = p.discussion
1256                                               JOIN {forum} f             ON f.id = d.forum
1257                                               JOIN {user} u              ON u.id = p.userid
1258                                         WHERE p.created > ? AND f.course = ?
1259                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1260          return false;
1261     }
1263     $modinfo =& get_fast_modinfo($course);
1265     $groupmodes = array();
1266     $cms    = array();
1268     $strftimerecent = get_string('strftimerecent');
1270     $printposts = array();
1271     foreach ($posts as $post) {
1272         if (!isset($modinfo->instances['forum'][$post->forum])) {
1273             // not visible
1274             continue;
1275         }
1276         $cm = $modinfo->instances['forum'][$post->forum];
1277         if (!$cm->uservisible) {
1278             continue;
1279         }
1280         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1282         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1283             continue;
1284         }
1286         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1287           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1288             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1289                 continue;
1290             }
1291         }
1293         $groupmode = groups_get_activity_groupmode($cm, $course);
1295         if ($groupmode) {
1296             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1297                 // oki (Open discussions have groupid -1)
1298             } else {
1299                 // separate mode
1300                 if (isguestuser()) {
1301                     // shortcut
1302                     continue;
1303                 }
1305                 if (is_null($modinfo->groups)) {
1306                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1307                 }
1309                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1310                     continue;
1311                 }
1312             }
1313         }
1315         $printposts[] = $post;
1316     }
1317     unset($posts);
1319     if (!$printposts) {
1320         return false;
1321     }
1323     print_headline(get_string('newforumposts', 'forum').':', 3);
1324     echo "\n<ul class='unlist'>\n";
1326     foreach ($printposts as $post) {
1327         $subjectclass = empty($post->parent) ? ' bold' : '';
1329         echo '<li><div class="head">'.
1330                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1331                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1332              '</div>';
1333         echo '<div class="info'.$subjectclass.'">';
1334         if (empty($post->parent)) {
1335             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1336         } else {
1337             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1338         }
1339         $post->subject = break_up_long_words(format_string($post->subject, true));
1340         echo $post->subject;
1341         echo "</a>\"</div></li>\n";
1342     }
1344     echo "</ul>\n";
1346     return true;
1349 /**
1350  * Return grade for given user or all users.
1351  *
1352  * @param int $forumid id of forum
1353  * @param int $userid optional user id, 0 means all users
1354  * @return array array of grades, false if none
1355  */
1356 function forum_get_user_grades($forum, $userid=0) {
1357     global $CFG, $DB;
1359     $params= array();
1360     if ($userid) {
1361         $params[] = $userid;
1362         $user = "AND u.id = ?";
1363     } else {
1364         $user = "";
1365     }
1367     $params[] = $forum->id;
1369     $aggtype = $forum->assessed;
1370     switch ($aggtype) {
1371         case FORUM_AGGREGATE_COUNT :
1372             $sql = "SELECT u.id, u.id AS userid, COUNT(fr.rating) AS rawgrade
1373                       FROM {user} u, {forum_posts} fp,
1374                            {forum_ratings} fr, {forum_discussions} fd
1375                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1376                            AND fr.userid != u.id AND fd.forum = ?
1377                            $user
1378                   GROUP BY u.id";
1379             break;
1380         case FORUM_AGGREGATE_MAX :
1381             $sql = "SELECT u.id, u.id AS userid, MAX(fr.rating) AS rawgrade
1382                       FROM {user} u, {forum_posts} fp,
1383                            {forum_ratings} fr, {forum_discussions} fd
1384                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1385                            AND fr.userid != u.id AND fd.forum = ?
1386                            $user
1387                   GROUP BY u.id";
1388             break;
1389         case FORUM_AGGREGATE_MIN :
1390             $sql = "SELECT u.id, u.id AS userid, MIN(fr.rating) AS rawgrade
1391                       FROM {user} u, {forum_posts} fp,
1392                            {forum_ratings} fr, {forum_discussions} fd
1393                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1394                            AND fr.userid != u.id AND fd.forum = ?
1395                            $user
1396                   GROUP BY u.id";
1397             break;
1398         case FORUM_AGGREGATE_SUM :
1399             $sql = "SELECT u.id, u.id AS userid, SUM(fr.rating) AS rawgrade
1400                      FROM {user} u, {forum_posts} fp,
1401                           {forum_ratings} fr, {forum_discussions} fd
1402                     WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1403                           AND fr.userid != u.id AND fd.forum = ?
1404                           $user
1405                  GROUP BY u.id";
1406             break;
1407         default : //avg
1408             $sql = "SELECT u.id, u.id AS userid, AVG(fr.rating) AS rawgrade
1409                       FROM {user} u, {forum_posts} fp,
1410                            {forum_ratings} fr, {forum_discussions} fd
1411                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1412                            AND fr.userid != u.id AND fd.forum = ?
1413                            $user
1414                   GROUP BY u.id";
1415             break;
1416     }
1418     if ($results = $DB->get_records_sql($sql, $params)) {
1419         // it could throw off the grading if count and sum returned a rawgrade higher than scale
1420         // 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)
1421         foreach ($results as $rid=>$result) {
1422             if ($forum->scale >= 0) {
1423                 //numeric
1424                 if ($result->rawgrade > $forum->scale) {
1425                     $results[$rid]->rawgrade = $forum->scale;
1426                 }
1427             } else {
1428                 //scales
1429                 if ($scale = $DB->get_record('scale', array('id' => -$forum->scale))) {
1430                     $scale = explode(',', $scale->scale);
1431                     $max = count($scale);
1432                     if ($result->rawgrade > $max) {
1433                         $results[$rid]->rawgrade = $max;
1434                     }
1435                 }
1436             }
1437         }
1438     }
1440     return $results;
1443 /**
1444  * Update activity grades
1445  *
1446  * @param object $forum
1447  * @param int $userid specific user only, 0 means all
1448  * @param boolean $nullifnone return null if grade does not exist
1449  * @return void
1450  */
1451 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1452     global $CFG, $DB;
1453     require_once($CFG->libdir.'/gradelib.php');
1455     if (!$forum->assessed) {
1456         forum_grade_item_update($forum);
1458     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1459         forum_grade_item_update($forum, $grades);
1461     } else if ($userid and $nullifnone) {
1462         $grade = new object();
1463         $grade->userid   = $userid;
1464         $grade->rawgrade = NULL;
1465         forum_grade_item_update($forum, $grade);
1467     } else {
1468         forum_grade_item_update($forum);
1469     }
1472 /**
1473  * Update all grades in gradebook.
1474  */
1475 function forum_upgrade_grades() {
1476     global $DB;
1478     $sql = "SELECT COUNT('x')
1479               FROM {forum} f, {course_modules} cm, {modules} m
1480              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1481     $count = $DB->count_records_sql($sql);
1483     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1484               FROM {forum} f, {course_modules} cm, {modules} m
1485              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1486     if ($rs = $DB->get_recordset_sql($sql)) {
1487         $pbar = new progress_bar('forumupgradegrades', 500, true);
1488         $i=0;
1489         foreach ($rs as $forum) {
1490             $i++;
1491             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1492             forum_update_grades($forum, 0, false);
1493             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1494         }
1495         $rs->close();
1496     }
1499 /**
1500  * Create/update grade item for given forum
1501  *
1502  * @param object $forum object with extra cmidnumber
1503  * @param mixed optional array/object of grade(s); 'reset' means reset grades in gradebook
1504  * @return int 0 if ok
1505  */
1506 function forum_grade_item_update($forum, $grades=NULL) {
1507     global $CFG;
1508     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1509         require_once($CFG->libdir.'/gradelib.php');
1510     }
1512     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1514     if (!$forum->assessed or $forum->scale == 0) {
1515         $params['gradetype'] = GRADE_TYPE_NONE;
1517     } else if ($forum->scale > 0) {
1518         $params['gradetype'] = GRADE_TYPE_VALUE;
1519         $params['grademax']  = $forum->scale;
1520         $params['grademin']  = 0;
1522     } else if ($forum->scale < 0) {
1523         $params['gradetype'] = GRADE_TYPE_SCALE;
1524         $params['scaleid']   = -$forum->scale;
1525     }
1527     if ($grades  === 'reset') {
1528         $params['reset'] = true;
1529         $grades = NULL;
1530     }
1532     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1535 /**
1536  * Delete grade item for given forum
1537  *
1538  * @param object $forum object
1539  * @return object grade_item
1540  */
1541 function forum_grade_item_delete($forum) {
1542     global $CFG;
1543     require_once($CFG->libdir.'/gradelib.php');
1545     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1549 /**
1550  * Returns the users with data in one forum
1551  * (users with records in forum_subscriptions, forum_posts and forum_ratings, students)
1552  * @param int $forumid
1553  * @return mixed array or false if none
1554  */
1555 function forum_get_participants($forumid) {
1557     global $CFG, $DB;
1559     //Get students from forum_subscriptions
1560     $st_subscriptions = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1561                                          FROM {user} u,
1562                                               {forum_subscriptions} s
1563                                          WHERE s.forum = ? AND
1564                                                u.id = s.userid", array($forumid));
1565     //Get students from forum_posts
1566     $st_posts = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1567                                  FROM {user} u,
1568                                       {forum_discussions} d,
1569                                       {forum_posts} p
1570                                  WHERE d.forum = ? AND
1571                                        p.discussion = d.id AND
1572                                        u.id = p.userid", array($forumid));
1574     //Get students from forum_ratings
1575     $st_ratings = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1576                                    FROM {user} u,
1577                                         {forum_discussions} d,
1578                                         {forum_posts} p,
1579                                         {forum_ratings} r
1580                                    WHERE d.forum = ? AND
1581                                          p.discussion = d.id AND
1582                                          r.post = p.id AND
1583                                          u.id = r.userid", array($forumid));
1585     //Add st_posts to st_subscriptions
1586     if ($st_posts) {
1587         foreach ($st_posts as $st_post) {
1588             $st_subscriptions[$st_post->id] = $st_post;
1589         }
1590     }
1591     //Add st_ratings to st_subscriptions
1592     if ($st_ratings) {
1593         foreach ($st_ratings as $st_rating) {
1594             $st_subscriptions[$st_rating->id] = $st_rating;
1595         }
1596     }
1597     //Return st_subscriptions array (it contains an array of unique users)
1598     return ($st_subscriptions);
1601 /**
1602  * This function returns if a scale is being used by one forum
1603  * @param int $forumid
1604  * @param int $scaleid negative number
1605  * @return bool
1606  */
1607 function forum_scale_used ($forumid,$scaleid) {
1608     global $DB;
1609     $return = false;
1611     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1613     if (!empty($rec) && !empty($scaleid)) {
1614         $return = true;
1615     }
1617     return $return;
1620 /**
1621  * Checks if scale is being used by any instance of forum
1622  *
1623  * This is used to find out if scale used anywhere
1624  * @param $scaleid int
1625  * @return boolean True if the scale is used by any forum
1626  */
1627 function forum_scale_used_anywhere($scaleid) {
1628     global $DB;
1629     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1630         return true;
1631     } else {
1632         return false;
1633     }
1636 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1638 /**
1639  * Gets a post with all info ready for forum_print_post
1640  * Most of these joins are just to get the forum id
1641  * @param int $postid
1642  * @return mixed array of posts or false
1643  */
1644 function forum_get_post_full($postid) {
1645     global $CFG, $DB;
1647     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1648                              FROM {forum_posts} p
1649                                   JOIN {forum_discussions} d ON p.discussion = d.id
1650                                   LEFT JOIN {user} u ON p.userid = u.id
1651                             WHERE p.id = ?", array($postid));
1654 /**
1655  * Gets posts with all info ready for forum_print_post
1656  * We pass forumid in because we always know it so no need to make a
1657  * complicated join to find it out.
1658  * @return mixed array of posts or false
1659  */
1660 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1661     global $CFG, $DB;
1663     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1664                               FROM {forum_posts} p
1665                          LEFT JOIN {user} u ON p.userid = u.id
1666                              WHERE p.discussion = ?
1667                                AND p.parent > 0 $sort", array($discussion));
1670 /**
1671  * Gets all posts in discussion including top parent.
1672  * @param int $discussionid
1673  * @param string $sort
1674  * @param bool $tracking does user track the forum?
1675  * @return array of posts
1676  */
1677 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1678     global $CFG, $DB, $USER;
1680     $tr_sel  = "";
1681     $tr_join = "";
1682     $params = array();
1684     if ($tracking) {
1685         $now = time();
1686         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1687         $tr_sel  = ", fr.id AS postread";
1688         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1689         $params[] = $USER->id;
1690     }
1692     $params[] = $discussionid;
1693     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1694                                      FROM {forum_posts} p
1695                                           LEFT JOIN {user} u ON p.userid = u.id
1696                                           $tr_join
1697                                     WHERE p.discussion = ?
1698                                  ORDER BY $sort", $params)) {
1699         return array();
1700     }
1702     foreach ($posts as $pid=>$p) {
1703         if ($tracking) {
1704             if (forum_tp_is_post_old($p)) {
1705                  $posts[$pid]->postread = true;
1706             }
1707         }
1708         if (!$p->parent) {
1709             continue;
1710         }
1711         if (!isset($posts[$p->parent])) {
1712             continue; // parent does not exist??
1713         }
1714         if (!isset($posts[$p->parent]->children)) {
1715             $posts[$p->parent]->children = array();
1716         }
1717         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1718     }
1720     return $posts;
1723 /**
1724  * Gets posts with all info ready for forum_print_post
1725  * We pass forumid in because we always know it so no need to make a
1726  * complicated join to find it out.
1727  */
1728 function forum_get_child_posts($parent, $forumid) {
1729     global $CFG, $DB;
1731     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1732                               FROM {forum_posts} p
1733                          LEFT JOIN {user} u ON p.userid = u.id
1734                              WHERE p.parent = ?
1735                           ORDER BY p.created ASC", array($parent));
1738 /**
1739  * An array of forum objects that the user is allowed to read/search through.
1740  * @param $userid
1741  * @param $courseid - if 0, we look for forums throughout the whole site.
1742  * @return array of forum objects, or false if no matches
1743  *         Forum objects have the following attributes:
1744  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1745  *         viewhiddentimedposts
1746  */
1747 function forum_get_readable_forums($userid, $courseid=0) {
1749     global $CFG, $DB, $USER;
1750     require_once($CFG->dirroot.'/course/lib.php');
1752     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1753         print_error('notinstalled', 'forum');
1754     }
1756     if ($courseid) {
1757         $courses = $DB->get_records('course', array('id' => $courseid));
1758     } else {
1759         // If no course is specified, then the user can see SITE + his courses.
1760         // And admins can see all courses, so pass the $doanything flag enabled
1761         $courses1 = $DB->get_records('course', array('id' => SITEID));
1762         $courses2 = get_my_courses($userid, null, null, true);
1763         $courses = array_merge($courses1, $courses2);
1764     }
1765     if (!$courses) {
1766         return array();
1767     }
1769     $readableforums = array();
1771     foreach ($courses as $course) {
1773         $modinfo =& get_fast_modinfo($course);
1774         if (is_null($modinfo->groups)) {
1775             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1776         }
1778         if (empty($modinfo->instances['forum'])) {
1779             // hmm, no forums?
1780             continue;
1781         }
1783         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1785         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1786             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1787                 continue;
1788             }
1789             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1790             $forum = $courseforums[$forumid];
1792             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1793                 continue;
1794             }
1796          /// group access
1797             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1798                 if (is_null($modinfo->groups)) {
1799                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1800                 }
1801                 if (empty($CFG->enablegroupings)) {
1802                     $forum->onlygroups = $modinfo->groups[0];
1803                     $forum->onlygroups[] = -1;
1804                 } else if (isset($modinfo->groups[$cm->groupingid])) {
1805                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1806                     $forum->onlygroups[] = -1;
1807                 } else {
1808                     $forum->onlygroups = array(-1);
1809                 }
1810             }
1812         /// hidden timed discussions
1813             $forum->viewhiddentimedposts = true;
1814             if (!empty($CFG->forum_enabletimedposts)) {
1815                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1816                     $forum->viewhiddentimedposts = false;
1817                 }
1818             }
1820         /// qanda access
1821             if ($forum->type == 'qanda'
1822                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1824                 // We need to check whether the user has posted in the qanda forum.
1825                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1826                                                     // the user is allowed to see in this forum.
1827                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1828                     foreach ($discussionspostedin as $d) {
1829                         $forum->onlydiscussions[] = $d->id;
1830                     }
1831                 }
1832             }
1834             $readableforums[$forum->id] = $forum;
1835         }
1837         unset($modinfo);
1839     } // End foreach $courses
1841     //print_object($courses);
1842     //print_object($readableforums);
1844     return $readableforums;
1847 /**
1848  * Returns a list of posts found using an array of search terms.
1849  * @param $searchterms - array of search terms, e.g. word +word -word
1850  * @param $courseid - if 0, we search through the whole site
1851  * @param $page
1852  * @param $recordsperpage=50
1853  * @param &$totalcount
1854  * @param $extrasql
1855  * @return array of posts found
1856  */
1857 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1858                             &$totalcount, $extrasql='') {
1859     global $CFG, $DB, $USER;
1860     require_once($CFG->libdir.'/searchlib.php');
1862     $forums = forum_get_readable_forums($USER->id, $courseid);
1864     if (count($forums) == 0) {
1865         $totalcount = 0;
1866         return false;
1867     }
1869     $now = round(time(), -2); // db friendly
1871     $fullaccess = array();
1872     $where = array();
1873     $params = array();
1875     foreach ($forums as $forumid => $forum) {
1876         $select = array();
1878         if (!$forum->viewhiddentimedposts) {
1879             $select[] = "(d.userid = :userid OR (d.timestart < : AND (d.timeend = 0 OR d.timeend > :timeend)))";
1880             $params = array('userid'=>$USER->id, 'timestart'=>$now, 'timeend'=>$now);
1881         }
1883         $cm = get_coursemodule_from_instance('forum', $forumid);
1884         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1886         if ($forum->type == 'qanda'
1887             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1888             if (!empty($forum->onlydiscussions)) {
1889                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda0');
1890                 $params = array_merge($params, $discussionid_params);
1891                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
1892             } else {
1893                 $select[] = "p.parent = 0";
1894             }
1895         }
1897         if (!empty($forum->onlygroups)) {
1898             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps0');
1899             $params = array_merge($params, $groupid_params);
1900             $select[] = "d.groupid $groupid_sql";
1901         }
1903         if ($select) {
1904             $selects = implode(" AND ", $select);
1905             $where[] = "(d.forum = :forum AND $selects)";
1906             $params['forum'] = $forumid;
1907         } else {
1908             $fullaccess[] = $forumid;
1909         }
1910     }
1912     if ($fullaccess) {
1913         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula0');
1914         $params = array_merge($params, $fullid_params);
1915         $where[] = "(d.forum $fullid_sql)";
1916     }
1918     $selectdiscussion = "(".implode(" OR ", $where).")";
1920     $messagesearch = '';
1921     $searchstring = '';
1923     // Need to concat these back together for parser to work.
1924     foreach($searchterms as $searchterm){
1925         if ($searchstring != '') {
1926             $searchstring .= ' ';
1927         }
1928         $searchstring .= $searchterm;
1929     }
1931     // We need to allow quoted strings for the search. The quotes *should* be stripped
1932     // by the parser, but this should be examined carefully for security implications.
1933     $searchstring = str_replace("\\\"","\"",$searchstring);
1934     $parser = new search_parser();
1935     $lexer = new search_lexer($parser);
1937     if ($lexer->parse($searchstring)) {
1938         $parsearray = $parser->get_parsed_array();
1939     // Experimental feature under 1.8! MDL-8830
1940     // Use alternative text searches if defined
1941     // This feature only works under mysql until properly implemented for other DBs
1942     // Requires manual creation of text index for forum_posts before enabling it:
1943     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
1944     // Experimental feature under 1.8! MDL-8830
1945         if (!empty($CFG->forum_usetextsearches)) {
1946             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
1947                                                  'p.userid', 'u.id', 'u.firstname',
1948                                                  'u.lastname', 'p.modified', 'd.forum');
1949         } else {
1950             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
1951                                                  'p.userid', 'u.id', 'u.firstname',
1952                                                  'u.lastname', 'p.modified', 'd.forum');
1953         }
1954         $params = array_merge($params, $msparams);
1955     }
1957     $fromsql = "{forum_posts} p,
1958                   {forum_discussions} d,
1959                   {user} u";
1961     $selectsql = " $messagesearch
1962                AND p.discussion = d.id
1963                AND p.userid = u.id
1964                AND $selectdiscussion
1965                    $extrasql";
1967     $countsql = "SELECT COUNT(*)
1968                    FROM $fromsql
1969                   WHERE $selectsql";
1971     $searchsql = "SELECT p.*,
1972                          d.forum,
1973                          u.firstname,
1974                          u.lastname,
1975                          u.email,
1976                          u.picture,
1977                          u.imagealt
1978                     FROM $fromsql
1979                    WHERE $selectsql
1980                 ORDER BY p.modified DESC";
1982     $totalcount = $DB->count_records_sql($countsql, $params);
1984     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
1987 /**
1988  * Returns a list of ratings for all posts in discussion
1989  * @param object $discussion
1990  * @return array of ratings or false
1991  */
1992 function forum_get_all_discussion_ratings($discussion) {
1993     global $CFG, $DB;
1994     return $DB->get_records_sql("SELECT r.id, r.userid, p.id AS postid, r.rating
1995                               FROM {forum_ratings} r,
1996                                    {forum_posts} p
1997                              WHERE r.post = p.id AND p.discussion = ?
1998                              ORDER BY p.id ASC", array($discussion->id));
2001 /**
2002  * Returns a list of ratings for one specific user for all posts in discussion
2003  * @global object $CFG
2004  * @global object $DB
2005  * @param object $discussions the discussions for which we return all ratings
2006  * @param int $userid the user for who we return all ratings
2007  * @return object
2008  */
2009 function forum_get_all_user_ratings($userid, $discussions) {
2010     global $CFG, $DB;
2013     foreach ($discussions as $discussion) {
2014      if (!isset($discussionsid)){
2015          $discussionsid = $discussion->id;
2016      }
2017      else {
2018          $discussionsid .= ",".$discussion->id;
2019      }
2020     }
2022     $sql = "SELECT r.id, r.userid, p.id AS postid, r.rating
2023                               FROM {forum_ratings} r,
2024                                    {forum_posts} p
2025                              WHERE r.post = p.id AND p.userid = :userid";
2028     $params = array();
2029     $params['userid'] = $userid;
2030     //postgres compability
2031     if (!isset($discussionsid)) {
2032        $sql .=" AND p.discussion IN (".$discussionsid.")";
2033     }
2034     $sql .=" ORDER BY p.id ASC";
2036     return $DB->get_records_sql($sql, $params);
2041 /**
2042  * Returns a list of ratings for a particular post - sorted.
2043  * @param int $postid
2044  * @param string $sort
2045  * @return array of ratings or false
2046  */
2047 function forum_get_ratings($postid, $sort="u.firstname ASC") {
2048     global $CFG, $DB;
2049     return $DB->get_records_sql("SELECT u.*, r.rating, r.time
2050                               FROM {forum_ratings} r,
2051                                    {user} u
2052                              WHERE r.post = ?
2053                                AND r.userid = u.id
2054                              ORDER BY $sort", array($postid));
2057 /**
2058  * Returns a list of all new posts that have not been mailed yet
2059  * @param int $starttime - posts created after this time
2060  * @param int $endtime - posts created before this
2061  * @param int $now - used for timed discussions only
2062  */
2063 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2064     global $CFG, $DB;
2066     $params = array($starttime, $endtime);
2067     if (!empty($CFG->forum_enabletimedposts)) {
2068         if (empty($now)) {
2069             $now = time();
2070         }
2071         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2072         $params[] = $now;
2073         $params[] = $now;
2074     } else {
2075         $timedsql = "";
2076     }
2078     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2079                               FROM {forum_posts} p
2080                                    JOIN {forum_discussions} d ON d.id = p.discussion
2081                              WHERE p.mailed = 0
2082                                    AND p.created >= ?
2083                                    AND (p.created < ? OR p.mailnow = 1)
2084                                    $timedsql
2085                           ORDER BY p.modified ASC", $params);
2088 /**
2089  * Marks posts before a certain time as being mailed already
2090  */
2091 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2092     global $CFG, $DB;
2093     if (empty($now)) {
2094         $now = time();
2095     }
2097     if (empty($CFG->forum_enabletimedposts)) {
2098         return $DB->execute("UPDATE {forum_posts}
2099                                SET mailed = '1'
2100                              WHERE (created < ? OR mailnow = 1)
2101                                    AND mailed = 0", array($endtime));
2103     } else {
2104         return $DB->execute("UPDATE {forum_posts}
2105                                SET mailed = '1'
2106                              WHERE discussion NOT IN (SELECT d.id
2107                                                         FROM {forum_discussions} d
2108                                                        WHERE d.timestart > ?)
2109                                    AND (created < ? OR mailnow = 1)
2110                                    AND mailed = 0", array($now, $endtime));
2111     }
2114 /**
2115  * Get all the posts for a user in a forum suitable for forum_print_post
2116  */
2117 function forum_get_user_posts($forumid, $userid) {
2118     global $CFG, $DB;
2120     $timedsql = "";
2121     $params = array($forumid, $userid);
2123     if (!empty($CFG->forum_enabletimedposts)) {
2124         $cm = get_coursemodule_from_instance('forum', $forumid);
2125         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2126             $now = time();
2127             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2128             $params[] = $now;
2129             $params[] = $now;
2130         }
2131     }
2133     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2134                               FROM {forum} f
2135                                    JOIN {forum_discussions} d ON d.forum = f.id
2136                                    JOIN {forum_posts} p       ON p.discussion = d.id
2137                                    JOIN {user} u              ON u.id = p.userid
2138                              WHERE f.id = ?
2139                                    AND p.userid = ?
2140                                    $timedsql
2141                           ORDER BY p.modified ASC", $params);
2144 /**
2145  * Get all the discussions user participated in
2146  * @param int $forumid
2147  * @param int $userid
2148  * @return array or false
2149  */
2150 function forum_get_user_involved_discussions($forumid, $userid) {
2151     global $CFG, $DB;
2153     $timedsql = "";
2154     $params = array($forumid, $userid);
2155     if (!empty($CFG->forum_enabletimedposts)) {
2156         $cm = get_coursemodule_from_instance('forum', $forumid);
2157         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2158             $now = time();
2159             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2160             $params[] = $now;
2161             $params[] = $now;
2162         }
2163     }
2165     return $DB->get_records_sql("SELECT DISTINCT d.*
2166                               FROM {forum} f
2167                                    JOIN {forum_discussions} d ON d.forum = f.id
2168                                    JOIN {forum_posts} p       ON p.discussion = d.id
2169                              WHERE f.id = ?
2170                                    AND p.userid = ?
2171                                    $timedsql", $params);
2174 /**
2175  * Get all the posts for a user in a forum suitable for forum_print_post
2176  * @param int $forumid
2177  * @param int $userid
2178  * @return array of counts or false
2179  */
2180 function forum_count_user_posts($forumid, $userid) {
2181     global $CFG, $DB;
2183     $timedsql = "";
2184     $params = array($forumid, $userid);
2185     if (!empty($CFG->forum_enabletimedposts)) {
2186         $cm = get_coursemodule_from_instance('forum', $forumid);
2187         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2188             $now = time();
2189             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2190             $params[] = $now;
2191             $params[] = $now;
2192         }
2193     }
2195     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2196                              FROM {forum} f
2197                                   JOIN {forum_discussions} d ON d.forum = f.id
2198                                   JOIN {forum_posts} p       ON p.discussion = d.id
2199                                   JOIN {user} u              ON u.id = p.userid
2200                             WHERE f.id = ?
2201                                   AND p.userid = ?
2202                                   $timedsql", $params);
2205 /**
2206  * Given a log entry, return the forum post details for it.
2207  */
2208 function forum_get_post_from_log($log) {
2209     global $CFG, $DB;
2211     if ($log->action == "add post") {
2213         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2214                                            u.firstname, u.lastname, u.email, u.picture
2215                                  FROM {forum_discussions} d,
2216                                       {forum_posts} p,
2217                                       {forum} f,
2218                                       {user} u
2219                                 WHERE p.id = ?
2220                                   AND d.id = p.discussion
2221                                   AND p.userid = u.id
2222                                   AND u.deleted <> '1'
2223                                   AND f.id = d.forum", array($log->info));
2226     } else if ($log->action == "add discussion") {
2228         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2229                                            u.firstname, u.lastname, u.email, u.picture
2230                                  FROM {forum_discussions} d,
2231                                       {forum_posts} p,
2232                                       {forum} f,
2233                                       {user} u
2234                                 WHERE d.id = ?
2235                                   AND d.firstpost = p.id
2236                                   AND p.userid = u.id
2237                                   AND u.deleted <> '1'
2238                                   AND f.id = d.forum", array($log->info));
2239     }
2240     return NULL;
2243 /**
2244  * Given a discussion id, return the first post from the discussion
2245  */
2246 function forum_get_firstpost_from_discussion($discussionid) {
2247     global $CFG, $DB;
2249     return $DB->get_record_sql("SELECT p.*
2250                              FROM {forum_discussions} d,
2251                                   {forum_posts} p
2252                             WHERE d.id = ?
2253                               AND d.firstpost = p.id ", array($discussionid));
2256 /**
2257  * Returns an array of counts of replies to each discussion
2258  */
2259 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2260     global $CFG, $DB;
2262     if ($limit > 0) {
2263         $limitfrom = 0;
2264         $limitnum  = $limit;
2265     } else if ($page != -1) {
2266         $limitfrom = $page*$perpage;
2267         $limitnum  = $perpage;
2268     } else {
2269         $limitfrom = 0;
2270         $limitnum  = 0;
2271     }
2273     if ($forumsort == "") {
2274         $orderby = "";
2275         $groupby = "";
2277     } else {
2278         $orderby = "ORDER BY $forumsort";
2279         $groupby = ", ".strtolower($forumsort);
2280         $groupby = str_replace('desc', '', $groupby);
2281         $groupby = str_replace('asc', '', $groupby);
2282     }
2284     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2285         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2286                   FROM {forum_posts} p
2287                        JOIN {forum_discussions} d ON p.discussion = d.id
2288                  WHERE p.parent > 0 AND d.forum = ?
2289               GROUP BY p.discussion";
2290         return $DB->get_records_sql($sql, array($forumid));
2292     } else {
2293         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2294                   FROM {forum_posts} p
2295                        JOIN {forum_discussions} d ON p.discussion = d.id
2296                  WHERE d.forum = ?
2297               GROUP BY p.discussion $groupby
2298               $orderby";
2299         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2300     }
2303 function forum_count_discussions($forum, $cm, $course) {
2304     global $CFG, $DB, $USER;
2306     static $cache = array();
2308     $now = round(time(), -2); // db cache friendliness
2310     $params = array($course->id);
2312     if (!isset($cache[$course->id])) {
2313         if (!empty($CFG->forum_enabletimedposts)) {
2314             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2315             $params[] = $now;
2316             $params[] = $now;
2317         } else {
2318             $timedsql = "";
2319         }
2321         $sql = "SELECT f.id, COUNT(d.id) as dcount
2322                   FROM {forum} f
2323                        JOIN {forum_discussions} d ON d.forum = f.id
2324                  WHERE f.course = ?
2325                        $timedsql
2326               GROUP BY f.id";
2328         if ($counts = $DB->get_records_sql($sql, $params)) {
2329             foreach ($counts as $count) {
2330                 $counts[$count->id] = $count->dcount;
2331             }
2332             $cache[$course->id] = $counts;
2333         } else {
2334             $cache[$course->id] = array();
2335         }
2336     }
2338     if (empty($cache[$course->id][$forum->id])) {
2339         return 0;
2340     }
2342     $groupmode = groups_get_activity_groupmode($cm, $course);
2344     if ($groupmode != SEPARATEGROUPS) {
2345         return $cache[$course->id][$forum->id];
2346     }
2348     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2349         return $cache[$course->id][$forum->id];
2350     }
2352     require_once($CFG->dirroot.'/course/lib.php');
2354     $modinfo =& get_fast_modinfo($course);
2355     if (is_null($modinfo->groups)) {
2356         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2357     }
2359     if (empty($CFG->enablegroupings)) {
2360         $mygroups = $modinfo->groups[0];
2361     } else {
2362         if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2363             $mygroups = $modinfo->groups[$cm->groupingid];
2364         } else {
2365             $mygroups = false; // Will be set below
2366         }
2367     }
2369     // add all groups posts
2370     if (empty($mygroups)) {
2371         $mygroups = array(-1=>-1);
2372     } else {
2373         $mygroups[-1] = -1;
2374     }
2376     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2377     $params[] = $forum->id;
2379     if (!empty($CFG->forum_enabletimedposts)) {
2380         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2381         $params[] = $now;
2382         $params[] = $now;
2383     } else {
2384         $timedsql = "";
2385     }
2387     $sql = "SELECT COUNT(d.id)
2388               FROM {forum_discussions} d
2389              WHERE d.groupid $mygroups_sql AND d.forum = ?
2390                    $timedsql";
2392     return $DB->get_field_sql($sql, $params);
2395 /**
2396  * How many unrated posts are in the given discussion for a given user?
2397  */
2398 function forum_count_unrated_posts($discussionid, $userid) {
2399     global $CFG, $DB;
2400     if ($posts = $DB->get_record_sql("SELECT count(*) as num
2401                                    FROM {forum_posts}
2402                                   WHERE parent > 0
2403                                     AND discussion = ?
2404                                     AND userid <> ? ", array($discussionid, $userid))) {
2406         if ($rated = $DB->get_record_sql("SELECT count(*) as num
2407                                        FROM {forum_posts} p,
2408                                             {forum_ratings} r
2409                                       WHERE p.discussion = ?
2410                                         AND p.id = r.post
2411                                         AND r.userid = ?", array($discussionid, $userid))) {
2412             $difference = $posts->num - $rated->num;
2413             if ($difference > 0) {
2414                 return $difference;
2415             } else {
2416                 return 0;    // Just in case there was a counting error
2417             }
2418         } else {
2419             return $posts->num;
2420         }
2421     } else {
2422         return 0;
2423     }
2426 /**
2427  * Get all discussions in a forum
2428  */
2429 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2430     global $CFG, $DB, $USER;
2432     $timelimit = '';
2434     $modcontext = null;
2436     $now = round(time(), -2);
2437     $params = array($cm->instance);
2439     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2441     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2442         return array();
2443     }
2445     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2447         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2448             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2449             $params[] = $now;
2450             $params[] = $now;
2451             if (isloggedin()) {
2452                 $timelimit .= " OR d.userid = ?";
2453                 $params[] = $USER->id;
2454             }
2455             $timelimit .= ")";
2456         }
2457     }
2459     if ($limit > 0) {
2460         $limitfrom = 0;
2461         $limitnum  = $limit;
2462     } else if ($page != -1) {
2463         $limitfrom = $page*$perpage;
2464         $limitnum  = $perpage;
2465     } else {
2466         $limitfrom = 0;
2467         $limitnum  = 0;
2468     }
2470     $groupmode    = groups_get_activity_groupmode($cm);
2471     $currentgroup = groups_get_activity_group($cm);
2473     if ($groupmode) {
2474         if (empty($modcontext)) {
2475             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2476         }
2478         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2479             if ($currentgroup) {
2480                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2481                 $params[] = $currentgroup;
2482             } else {
2483                 $groupselect = "";
2484             }
2486         } else {
2487             //seprate groups without access all
2488             if ($currentgroup) {
2489                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2490                 $params[] = $currentgroup;
2491             } else {
2492                 $groupselect = "AND d.groupid = -1";
2493             }
2494         }
2495     } else {
2496         $groupselect = "";
2497     }
2500     if (empty($forumsort)) {
2501         $forumsort = "d.timemodified DESC";
2502     }
2503     if (empty($fullpost)) {
2504         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2505     } else {
2506         $postdata = "p.*";
2507     }
2509     if (empty($userlastmodified)) {  // We don't need to know this
2510         $umfields = "";
2511         $umtable  = "";
2512     } else {
2513         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2514         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2515     }
2517     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2518                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2519               FROM {forum_discussions} d
2520                    JOIN {forum_posts} p ON p.discussion = d.id
2521                    JOIN {user} u ON p.userid = u.id
2522                    $umtable
2523              WHERE d.forum = ? AND p.parent = 0
2524                    $timelimit $groupselect
2525           ORDER BY $forumsort";
2526     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2529 function forum_get_discussions_unread($cm) {
2530     global $CFG, $DB, $USER;
2532     $now = round(time(), -2);
2533     $params = array($cutoffdate);
2534     $groupmode    = groups_get_activity_groupmode($cm);
2535     $currentgroup = groups_get_activity_group($cm);
2537     if ($groupmode) {
2538         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2540         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2541             if ($currentgroup) {
2542                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2543                 $params[] = $currentgroup;
2544             } else {
2545                 $groupselect = "";
2546             }
2548         } else {
2549             //seprate groups without access all
2550             if ($currentgroup) {
2551                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2552                 $params[] = $currentgroup;
2553             } else {
2554                 $groupselect = "AND d.groupid = -1";
2555             }
2556         }
2557     } else {
2558         $groupselect = "";
2559     }
2561     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2563     if (!empty($CFG->forum_enabletimedposts)) {
2564         $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2565         $params[] = $now;
2566         $params[] = $now;
2567     } else {
2568         $timedsql = "";
2569     }
2571     $sql = "SELECT d.id, COUNT(p.id) AS unread
2572               FROM {forum_discussions} d
2573                    JOIN {forum_posts} p     ON p.discussion = d.id
2574                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2575              WHERE d.forum = {$cm->instance}
2576                    AND p.modified >= ? AND r.id is NULL
2577                    $groupselect
2578                    $timedsql
2579           GROUP BY d.id";
2580     if ($unreads = $DB->get_records_sql($sql, $params)) {
2581         foreach ($unreads as $unread) {
2582             $unreads[$unread->id] = $unread->unread;
2583         }
2584         return $unreads;
2585     } else {
2586         return array();
2587     }
2590 function forum_get_discussions_count($cm) {
2591     global $CFG, $DB, $USER;
2593     $now = round(time(), -2);
2594     $params = array($cm->instance);
2595     $groupmode    = groups_get_activity_groupmode($cm);
2596     $currentgroup = groups_get_activity_group($cm);
2598     if ($groupmode) {
2599         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2601         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2602             if ($currentgroup) {
2603                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2604                 $params[] = $currentgroup;
2605             } else {
2606                 $groupselect = "";
2607             }
2609         } else {
2610             //seprate groups without access all
2611             if ($currentgroup) {
2612                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2613                 $params[] = $currentgroup;
2614             } else {
2615                 $groupselect = "AND d.groupid = -1";
2616             }
2617         }
2618     } else {
2619         $groupselect = "";
2620     }
2622     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2624     $timelimit = "";
2626     if (!empty($CFG->forum_enabletimedposts)) {
2628         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2630         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2631             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2632             $params[] = $now;
2633             $params[] = $now;
2634             if (isloggedin()) {
2635                 $timelimit .= " OR d.userid = ?";
2636                 $params[] = $USER->id;
2637             }
2638             $timelimit .= ")";
2639         }
2640     }
2642     $sql = "SELECT COUNT(d.id)
2643               FROM {forum_discussions} d
2644                    JOIN {forum_posts} p ON p.discussion = d.id
2645              WHERE d.forum = ? AND p.parent = 0
2646                    $groupselect $timelimit";
2648     return $DB->get_field_sql($sql, $params);
2652 /**
2653  * Get all discussions started by a particular user in a course (or group)
2654  * This function no longer used ...
2655  */
2656 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2657     global $CFG, $DB;
2658     $params = array($courseid, $userid);
2659     if ($groupid) {
2660         $groupselect = " AND d.groupid = ? ";
2661         $params[] = $groupid;
2662     } else  {
2663         $groupselect = "";
2664     }
2666     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2667                                    f.type as forumtype, f.name as forumname, f.id as forumid
2668                               FROM {forum_discussions} d,
2669                                    {forum_posts} p,
2670                                    {user} u,
2671                                    {forum} f
2672                              WHERE d.course = ?
2673                                AND p.discussion = d.id
2674                                AND p.parent = 0
2675                                AND p.userid = u.id
2676                                AND u.id = ?
2677                                AND d.forum = f.id $groupselect
2678                           ORDER BY p.created DESC", $params);
2681 /**
2682  * Get the list of potential subscribers to a forum.
2683  *
2684  * @param object $forumcontext the forum context.
2685  * @param integer $groupid the id of a group, or 0 for all groups.
2686  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2687  * @param string $sort sort order. As for get_users_by_capability.
2688  * @return array list of users.
2689  */
2690 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2691     return get_users_by_capability($forumcontext, 'mod/forum:initialsubscriptions', $fields, $sort, '', '', $groupid, '', false, true);
2694 /**
2695  * Returns list of user objects that are subscribed to this forum
2696  *
2697  * @param object $course the course
2698  * @param forum $forum the forum
2699  * @param integer $groupid group id, or 0 for all.
2700  * @param object $context the forum context, to save re-fetching it where possible.
2701  * @return array list of users.
2702  */
2703 function forum_subscribed_users($course, $forum, $groupid=0, $context = NULL) {
2704     global $CFG, $DB;
2705     $params = array($forum->id);
2707     if ($groupid) {
2708         $grouptables = ", {groups_members} gm ";
2709         $groupselect = "AND gm.groupid = ? AND u.id = gm.userid";
2710         $params[] = $groupid;
2711     } else  {
2712         $grouptables = '';
2713         $groupselect = '';
2714     }
2716     $fields ="u.id,
2717               u.username,
2718               u.firstname,
2719               u.lastname,
2720               u.maildisplay,
2721               u.mailformat,
2722               u.maildigest,
2723               u.emailstop,
2724               u.imagealt,
2725               u.email,
2726               u.city,
2727               u.country,
2728               u.lastaccess,
2729               u.lastlogin,
2730               u.picture,
2731               u.timezone,
2732               u.theme,
2733               u.lang,
2734               u.trackforums,
2735               u.mnethostid";
2737     if (forum_is_forcesubscribed($forum)) {
2738         if (empty($context)) {
2739             $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2740             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2741         }
2742         $sort = "u.email ASC";
2743         $results = forum_get_potential_subscribers($context, $groupid, $fields, $sort);
2744     } else {
2745         $results = $DB->get_records_sql("SELECT $fields
2746                               FROM {user} u,
2747                                    {forum_subscriptions} s $grouptables
2748                              WHERE s.forum = ?
2749                                AND s.userid = u.id
2750                                AND u.deleted = 0  $groupselect
2751                           ORDER BY u.email ASC", $params);
2752     }
2754     static $guestid = null;
2756     if (is_null($guestid)) {
2757         if ($guest = guest_user()) {
2758             $guestid = $guest->id;
2759         } else {
2760             $guestid = 0;
2761         }
2762     }
2764     // Guest user should never be subscribed to a forum.
2765     unset($results[$guestid]);
2767     return $results;
2772 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2775 function forum_get_course_forum($courseid, $type) {
2776 // How to set up special 1-per-course forums
2777     global $CFG, $DB;
2779     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2780         // There should always only be ONE, but with the right combination of
2781         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2782         foreach ($forums as $forum) {
2783             return $forum;   // ie the first one
2784         }
2785     }
2787     // Doesn't exist, so create one now.
2788     $forum->course = $courseid;
2789     $forum->type = "$type";
2790     switch ($forum->type) {
2791         case "news":
2792             $forum->name  = get_string("namenews", "forum");
2793             $forum->intro = get_string("intronews", "forum");
2794             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2795             $forum->assessed = 0;
2796             if ($courseid == SITEID) {
2797                 $forum->name  = get_string("sitenews");
2798                 $forum->forcesubscribe = 0;
2799             }
2800             break;
2801         case "social":
2802             $forum->name  = get_string("namesocial", "forum");
2803             $forum->intro = get_string("introsocial", "forum");
2804             $forum->assessed = 0;
2805             $forum->forcesubscribe = 0;
2806             break;
2807         default:
2808             notify("That forum type doesn't exist!");
2809             return false;
2810             break;
2811     }
2813     $forum->timemodified = time();
2814     $forum->id = $DB->insert_record("forum", $forum);
2816     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2817         notify("Could not find forum module!!");
2818         return false;
2819     }
2820     $mod = new object();
2821     $mod->course = $courseid;
2822     $mod->module = $module->id;
2823     $mod->instance = $forum->id;
2824     $mod->section = 0;
2825     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
2826         notify("Could not add a new course module to the course '" . format_string($course->fullname) . "'");
2827         return false;
2828     }
2829     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
2830         notify("Could not add the new course module to that section");
2831         return false;
2832     }
2833     if (! $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule))) {
2834         notify("Could not update the course module with the correct section");
2835         return false;
2836     }
2837     include_once("$CFG->dirroot/course/lib.php");
2838     rebuild_course_cache($courseid);
2840     return $DB->get_record("forum", array("id" => "$forum->id"));
2844 /**
2845 * Given the data about a posting, builds up the HTML to display it and
2846 * returns the HTML in a string.  This is designed for sending via HTML email.
2847 */
2848 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
2849                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
2851     global $CFG;
2853     if (!isset($userto->viewfullnames[$forum->id])) {
2854         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
2855             print_error('invalidcoursemodule');
2856         }
2857         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2858         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
2859     } else {
2860         $viewfullnames = $userto->viewfullnames[$forum->id];
2861     }
2863     // format the post body
2864     $options = new object();
2865     $options->para = true;
2866     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
2868     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
2870     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
2871     $output .= print_user_picture($userfrom, $course->id, $userfrom->picture, false, true);
2872     $output .= '</td>';
2874     if ($post->parent) {
2875         $output .= '<td class="topic">';
2876     } else {
2877         $output .= '<td class="topic starter">';
2878     }
2879     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
2881     $fullname = fullname($userfrom, $viewfullnames);
2882     $by = new object();
2883     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
2884     $by->date = userdate($post->modified, '', $userto->timezone);
2885     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
2887     $output .= '</td></tr>';
2889     $output .= '<tr><td class="left side" valign="top">';
2891     if (isset($userfrom->groups)) {
2892         $groups = $userfrom->groups[$forum->id];
2893     } else {
2894         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
2895             print_error('invalidcoursemodule');
2896         }
2897         $group = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
2898     }
2900     if ($groups) {
2901         $output .= print_group_picture($groups, $course->id, false, true, true);
2902     } else {
2903         $output .= '&nbsp;';
2904     }
2906     $output .= '</td><td class="content">';
2908     $attachments = forum_print_attachments($post, $cm, 'html');
2909     if ($attachments !== '') {
2910         $output .= '<div class="attachments">';
2911         $output .= $attachments;
2912         $output .= '</div>';
2913     }
2915     $output .= $formattedtext;
2917 // Commands
2918     $commands = array();
2920     if ($post->parent) {
2921         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
2922                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
2923     }
2925     if ($reply) {
2926         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
2927                       get_string('reply', 'forum').'</a>';
2928     }
2930     $output .= '<div class="commands">';
2931     $output .= implode(' | ', $commands);
2932     $output .= '</div>';
2934 // Context link to post if required
2935     if ($link) {
2936         $output .= '<div class="link">';
2937         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
2938                      get_string('postincontext', 'forum').'</a>';
2939         $output .= '</div>';
2940     }
2942     if ($footer) {
2943         $output .= '<div class="footer">'.$footer.'</div>';
2944     }
2945     $output .= '</td></tr></table>'."\n\n";
2947     return $output;
2950 /**
2951  * Print a forum post
2952  *
2953  * @param object $post The post to print.
2954  * @param integer $courseid The course this post belongs to.
2955  * @param boolean $ownpost Whether this post belongs to the current user.
2956  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
2957  * @param boolean $link Just print a shortened version of the post as a link to the full post.
2958  * @param object $ratings -- I don't really know --
2959  * @param string $footer Extra stuff to print after the message.
2960  * @param string $highlight Space-separated list of terms to highlight.
2961  * @param int $post_read true, false or -99. If we already know whether this user
2962  *          has read this post, pass that in, otherwise, pass in -99, and this
2963  *          function will work it out.
2964  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
2965  *          the current user can't see this post, if this argument is true
2966  *          (the default) then print a dummy 'you can't see this post' post.
2967  *          If false, don't output anything at all.
2968  */
2969 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
2970                           $ratings=NULL, $footer="", $highlight="", $post_read=null, $dummyifcantsee=true, $istracked=null) {
2972     global $USER, $CFG;
2974     static $stredit, $strdelete, $strreply, $strparent, $strprune;
2975     static $strpruneheading, $displaymode;
2976     static $strmarkread, $strmarkunread;
2978     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2980     $post->course = $course->id;
2981     $post->forum  = $forum->id;
2982     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'forum_post', $post->id);
2984     // caching
2985     if (!isset($cm->cache)) {
2986         $cm->cache = new object();
2987     }
2989     if (!isset($cm->cache->caps)) {
2990         $cm->cache->caps = array();
2991         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
2992         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
2993         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
2994         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
2995         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
2996         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
2997         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
2998         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
2999         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3000     }
3002     if (!isset($cm->uservisible)) {
3003         $cm->uservisible = coursemodule_visible_for_user($cm);
3004     }
3006     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3007         if (!$dummyifcantsee) {
3008             return;
3009         }
3010         echo '<a id="p'.$post->id.'"></a>';
3011         echo '<table cellspacing="0" class="forumpost">';
3012         echo '<tr class="header"><td class="picture left">';
3013         //        print_user_picture($post->userid, $courseid, $post->picture);
3014         echo '</td>';
3015         if ($post->parent) {
3016             echo '<td class="topic">';
3017         } else {
3018             echo '<td class="topic starter">';
3019         }
3020         echo '<div class="subject">'.get_string('forumsubjecthidden','forum').'</div>';
3021         echo '<div class="author">';
3022         print_string('forumauthorhidden','forum');
3023         echo '</div></td></tr>';
3025         echo '<tr><td class="left side">';
3026         echo '&nbsp;';
3028         // Actual content
3030         echo '</td><td class="content">'."\n";
3031         echo get_string('forumbodyhidden','forum');
3032         echo '</td></tr></table>';
3033         return;
3034     }
3036     if (empty($stredit)) {
3037         $stredit         = get_string('edit', 'forum');
3038         $strdelete       = get_string('delete', 'forum');
3039         $strreply        = get_string('reply', 'forum');
3040         $strparent       = get_string('parent', 'forum');
3041         $strpruneheading = get_string('pruneheading', 'forum');
3042         $strprune        = get_string('prune', 'forum');
3043         $displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3044         $strmarkread     = get_string('markread', 'forum');
3045         $strmarkunread   = get_string('markunread', 'forum');
3047     }
3049     $read_style = '';
3050     // ignore trackign status if not tracked or tracked param missing
3051     if ($istracked) {
3052         if (is_null($post_read)) {
3053             debugging('fetching post_read info');
3054             $post_read = forum_tp_is_post_read($USER->id, $post);
3055         }
3057         if ($post_read) {
3058             $read_style = ' read';
3059         } else {
3060             $read_style = ' unread';
3061             echo '<a name="unread"></a>';
3062         }
3063     }
3065     echo '<a id="p'.$post->id.'"></a>';
3066     echo '<table cellspacing="0" class="forumpost'.$read_style.'">';
3068     // Picture
3069     $postuser = new object();
3070     $postuser->id        = $post->userid;
3071     $postuser->firstname = $post->firstname;
3072     $postuser->lastname  = $post->lastname;
3073     $postuser->imagealt  = $post->imagealt;
3074     $postuser->picture   = $post->picture;
3076     echo '<tr class="header"><td class="picture left">';
3077     print_user_picture($postuser, $course->id);
3078     echo '</td>';
3080     if ($post->parent) {
3081         echo '<td class="topic">';
3082     } else {
3083         echo '<td class="topic starter">';
3084     }
3086     if (!empty($post->subjectnoformat)) {
3087         echo '<div class="subject">'.$post->subject.'</div>';
3088     } else {
3089         echo '<div class="subject">'.format_string($post->subject).'</div>';
3090     }
3092     echo '<div class="author">';
3093     $fullname = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3094     $by = new object();
3095     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.
3096                 $post->userid.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3097     $by->date = userdate($post->modified);
3098     print_string('bynameondate', 'forum', $by);
3099     echo '</div></td></tr>';
3101     echo '<tr><td class="left side">';
3102     if (isset($cm->cache->usersgroups)) {
3103         $groups = array();
3104         if (isset($cm->cache->usersgroups[$post->userid])) {
3105             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3106                 $groups[$gid] = $cm->cache->groups[$gid];
3107             }
3108         }
3109     } else {
3110         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3111     }
3113     if ($groups) {
3114         print_group_picture($groups, $course->id, false, false, true);
3115     } else {
3116         echo '&nbsp;';
3117     }
3119 // Actual content
3121     echo '</td><td class="content">'."\n";
3123     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3125     if ($attachments !== '') {
3126         echo '<div class="attachments">';
3127         echo $attachments;
3128         echo '</div>';
3129     }
3131     $options = new object();
3132     $options->para    = false;
3133     $options->trusted = $post->messagetrust;
3134     if ($link and (strlen(strip_tags($post->message)) > $CFG->forum_longpost)) {
3135         // Print shortened version
3136         echo format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3137         $numwords = count_words(strip_tags($post->message));
3138         echo '<div class="posting"><a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3139         echo get_string('readtherest', 'forum');
3140         echo '</a> ('.get_string('numwords', '', $numwords).')...</div>';
3141     } else {
3142         // Print whole message
3143         echo '<div class="posting">';
3144         if ($highlight) {
3145             echo highlight($highlight, format_text($post->message, $post->messageformat, $options, $course->id));
3146         } else {
3147             echo format_text($post->message, $post->messageformat, $options, $course->id);
3148         }
3149         echo '</div>';
3150         echo $attachedimages;
3151     }
3154 // Commands
3156     $commands = array();
3158     if ($istracked) {
3159         // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3160         // Don't display the mark read / unread controls in this case.
3161         if ($CFG->forum_usermarksread and isloggedin()) {
3162             if ($post_read) {
3163                 $mcmd = '&amp;mark=unread&amp;postid='.$post->id;
3164                 $mtxt = $strmarkunread;
3165             } else {
3166                 $mcmd = '&amp;mark=read&amp;postid='.$post->id;
3167                 $mtxt = $strmarkread;
3168             }
3169             if ($displaymode == FORUM_MODE_THREADED) {
3170                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3171                               $post->discussion.'&amp;parent='.$post->id.$mcmd.'">'.$mtxt.'</a>';
3172             } else {
3173                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3174                               $post->discussion.$mcmd.'#p'.$post->id.'">'.$mtxt.'</a>';
3175             }
3176         }
3177     }
3179     if ($post->parent) {  // Zoom in to the parent specifically
3180         if ($displaymode == FORUM_MODE_THREADED) {
3181             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3182                       $post->discussion.'&amp;parent='.$post->parent.'">'.$strparent.'</a>';
3183         } else {
3184             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3185                       $post->discussion.'#p'.$post->parent.'">'.$strparent.'</a>';
3186         }
3187     }
3189     $age = time() - $post->created;
3190     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3191     if (!$post->parent and $forum->type == 'news' and $discussion->timestart > time()) {
3192         $age = 0;
3193     }
3194     $editanypost = $cm->cache->caps['mod/forum:editanypost'];
3196     if ($ownpost or $editanypost) {
3197         if (($age < $CFG->maxeditingtime) or $editanypost) {
3198             $commands[] =  '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?edit='.$post->id.'">'.$stredit.'</a>';
3199         }
3200     }
3202     if ($cm->cache->caps['mod/forum:splitdiscussions']
3203                 && $post->parent && $forum->type != 'single') {
3205         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?prune='.$post->id.
3206                       '" title="'.$strpruneheading.'">'.$strprune.'</a>';
3207     }
3209     if (($ownpost and $age < $CFG->maxeditingtime
3210                 and $cm->cache->caps['mod/forum:deleteownpost'])
3211                 or $cm->cache->caps['mod/forum:deleteanypost']) {
3212         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?delete='.$post->id.'">'.$strdelete.'</a>';
3213     }
3215     if ($reply) {
3216         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.$strreply.'</a>';
3217     }
3219     if ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost'])) {
3220         $p = array(
3221             'postid' => $post->id,
3222         );
3223         $button = new portfolio_add_button();
3224         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id));
3225         if (empty($attachments)) {
3226             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3227         } else {
3228             $button->set_formats(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_RICHHTML);
3229         }
3231         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3232         if(!empty($porfoliohtml)){
3233             $commands[] = $porfoliohtml;
3234         }
3235     }
3237     echo '<div class="commands">';
3238     echo implode(' | ', $commands);
3239     echo '</div>';
3242 // Ratings
3244     $ratingsmenuused = false;
3245     if (!empty($ratings) and isloggedin()) {
3246         echo '<div class="ratings">';
3247         $useratings = true;
3248         if ($ratings->assesstimestart and $ratings->assesstimefinish) {
3249             if ($post->created < $ratings->assesstimestart or $post->created > $ratings->assesstimefinish) {
3250                 $useratings = false;
3251             }
3252         }
3253         if ($useratings) {
3254             $mypost = ($USER->id == $post->userid);
3256             $canviewallratings = $cm->cache->caps['mod/forum:viewanyrating'];
3258             if (isset($cm->cache->ratings)) {
3259                 if (isset($cm->cache->ratings[$post->id])) {
3260                     $allratings = $cm->cache->ratings[$post->id];
3261                 } else {
3262                     $allratings = array(); // no reatings present yet
3263                 }
3264             } else {
3265                 $allratings = NULL; // not preloaded
3266             }
3268             if (isset($cm->cache->myratings)) {
3269                 if (isset($cm->cache->myratings[$post->id])) {
3270                     $myrating = $cm->cache->myratings[$post->id];
3271                 } else {
3272                     $myrating = FORUM_UNSET_POST_RATING; // no reatings present yet
3273                 }
3274             } else {
3275                 $myrating = NULL; // not preloaded
3276             }
3278             if ($canviewallratings and !$mypost) {
3279                 echo '<span class="forumpostratingtext">' .
3280                      forum_print_ratings($post->id, $ratings->scale, $forum->assessed, $canviewallratings, $allratings, true) .
3281                      '</span>';
3282                 if (!empty($ratings->allow)) {
3283                     echo '&nbsp;';
3284                     forum_print_rating_menu($post->id, $USER->id, $ratings->scale, $myrating);
3285                     $ratingsmenuused = true;
3286                 }
3288             } else if ($mypost) {
3289                 echo '<span class="forumpostratingtext">' .
3290                      forum_print_ratings($post->id, $ratings->scale, $forum->assessed, true, $allratings, true) .
3291                      '</span>';
3293             } else if (!empty($ratings->allow) ) {
3294                 forum_print_rating_menu($post->id, $USER->id, $ratings->scale, $myrating);
3295                 $ratingsmenuused = true;
3296             }
3297         }
3298         echo '</div>';
3299     }
3301 // Link to post if required
3303     if ($link) {
3304         echo '<div class="link">';
3305         if ($post->replies == 1) {
3306             $replystring = get_string('repliesone', 'forum', $post->replies);
3307         } else {
3308             $replystring = get_string('repliesmany', 'forum', $post->replies);
3309         }
3310         echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.
3311              get_string('discussthistopic', 'forum').'</a>&nbsp;('.$replystring.')';
3312         echo '</div>';
3313     }
3315     if ($footer) {
3316         echo '<div class="footer">'.$footer.'</div>';
3317     }
3318     echo '</td></tr></table>'."\n\n";
3320     if ($istracked && !$CFG->forum_usermarksread && !$post_read) {
3321         forum_tp_mark_post_read($USER->id, $post, $forum->id);
3322     }
3324     return $ratingsmenuused;
3328 /**
3329  * This function prints the overview of a discussion in the forum listing.
3330  * It needs some discussion information and some post information, these
3331  * happen to be combined for efficiency in the $post parameter by the function
3332  * that calls this one: forum_print_latest_discussions()
3333  *
3334  * @param object $post The post object (passed by reference for speed).
3335  * @param object $forum The forum object.
3336  * @param int $group Current group.
3337  * @param string $datestring Format to use for the dates.
3338  * @param boolean $cantrack Is tracking enabled for this forum.
3339  * @param boolean $forumtracked Is the user tracking this forum.
3340  * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
3341  */
3342 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
3343                                         $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) {
3345     global $USER, $CFG;
3347     static $rowcount;
3348     static $strmarkalldread;
3350     if (empty($modcontext)) {
3351         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3352             print_error('invalidcoursemodule');
3353         }
3354         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3355     }
3357     if (!isset($rowcount)) {
3358         $rowcount = 0;
3359         $strmarkalldread = get_string('markalldread', 'forum');
3360     } else {
3361         $rowcount = ($rowcount + 1) % 2;
3362     }
3364     $post->subject = format_string($post->subject,true);
3366     echo "\n\n";
3367     echo '<tr class="discussion r'.$rowcount.'">';
3369     // Topic
3370     echo '<td class="topic starter">';
3371     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.$post->subject.'</a>';
3372     echo "</td>\n";
3374     // Picture
3375     $postuser = new object;
3376     $postuser->id = $post->userid;
3377     $postuser->firstname = $post->firstname;
3378     $postuser->lastname = $post->lastname;
3379     $postuser->imagealt = $post->imagealt;
3380     $postuser->picture = $post->picture;
3382     echo '<td class="picture">';
3383     print_user_picture($postuser, $forum->course);
3384     echo "</td>\n";
3386     // User name
3387     $fullname = fullname($post, has_capability('moodle/site:viewfullnames', $modcontext));
3388     echo '<td class="author">';
3389     echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->userid.'&amp;course='.$forum->course.'">'.$fullname.'</a>';
3390     echo "</td>\n";
3392     // Group picture
3393     if ($group !== -1) {  // Groups are active - group is a group data object or NULL
3394         echo '<td class="picture group">';
3395         if (!empty($group->picture) and empty($group->hidepicture)) {
3396             print_group_picture($group, $forum->course, false, false, true);
3397         } else if (isset($group->id)) {
3398             if($canviewparticipants) {
3399                 echo '<a href="'.$CFG->wwwroot.'/user/index.php?id='.$forum->course.'&amp;group='.$group->id.'">'.$group->name.'</a>';
3400             } else {
3401                 echo $group->name;
3402             }
3403         }
3404         echo "</td>\n";
3405     }
3407     if (has_capability('mod/forum:viewdiscussion', $modcontext)) {   // Show the column with replies
3408         echo '<td class="replies">';
3409         echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3410         echo $post->replies.'</a>';
3411         echo "</td>\n";
3413         if ($cantrack) {
3414             echo '<td class="replies">';
3415             if ($forumtracked) {
3416                 if ($post->unread > 0) {
3417                     echo '<span class="unread">';
3418                     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#unread">';
3419                     echo $post->unread;
3420                     echo '</a>';
3421                     echo '<a title="'.$strmarkalldread.'" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
3422                          $forum->id.'&amp;d='.$post->discussion.'&amp;mark=read&amp;returnpage=view.php">' .
3423                          '<img src="'.$CFG->pixpath.'/t/clear.gif" class="iconsmall" alt="'.$strmarkalldread.'" /></a>';
3424                     echo '</span>';
3425                 } else {
3426                     echo '<span class="read">';
3427                     echo $post->unread;
3428                     echo '</span>';
3429                 }
3430             } else {
3431                 echo '<span class="read">';
3432                 echo '-';
3433                 echo '</span>';
3434             }
3435             echo "</td>\n";
3436         }
3437     }