0cf9d4e5b7078bcb5cb9832a2b81415fe2039bad
[moodle.git] / mod / forum / lib.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * @package mod-forum
20  * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
21  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22  */
24 /** Include required files */
25 require_once($CFG->libdir.'/filelib.php');
26 require_once($CFG->libdir.'/eventslib.php');
27 require_once($CFG->libdir . '/completionlib.php');
28 require_once($CFG->dirroot.'/user/selector/lib.php');
30 /// CONSTANTS ///////////////////////////////////////////////////////////
32 define('FORUM_MODE_FLATOLDEST', 1);
33 define('FORUM_MODE_FLATNEWEST', -1);
34 define('FORUM_MODE_THREADED', 2);
35 define('FORUM_MODE_NESTED', 3);
37 define('FORUM_CHOOSESUBSCRIBE', 0);
38 define('FORUM_FORCESUBSCRIBE', 1);
39 define('FORUM_INITIALSUBSCRIBE', 2);
40 define('FORUM_DISALLOWSUBSCRIBE',3);
42 define('FORUM_TRACKING_OFF', 0);
43 define('FORUM_TRACKING_OPTIONAL', 1);
44 define('FORUM_TRACKING_ON', 2);
46 //todo andrew remove this
47 define('FORUM_UNSET_POST_RATING', -999);
49 //todo andrew and remove these
50 define ('FORUM_AGGREGATE_NONE', 0); //no ratings
51 define ('FORUM_AGGREGATE_AVG', 1);
52 define ('FORUM_AGGREGATE_COUNT', 2);
53 define ('FORUM_AGGREGATE_MAX', 3);
54 define ('FORUM_AGGREGATE_MIN', 4);
55 define ('FORUM_AGGREGATE_SUM', 5);
57 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
59 /**
60  * Given an object containing all the necessary data,
61  * (defined by the form in mod_form.php) this function
62  * will create a new instance and return the id number
63  * of the new instance.
64  *
65  * @global object
66  * @global object
67  * @param object $forum add forum instance (with magic quotes)
68  * @return int intance id
69  */
70 function forum_add_instance($forum) {
71     global $CFG, $DB;
73     $forum->timemodified = time();
75     if (empty($forum->assessed)) {
76         $forum->assessed = 0;
77     }
79     if (empty($forum->ratingtime) or empty($forum->assessed)) {
80         $forum->assesstimestart  = 0;
81         $forum->assesstimefinish = 0;
82     }
84     $forum->id = $DB->insert_record('forum', $forum);
86     if ($forum->type == 'single') {  // Create related discussion.
87         $discussion = new object();
88         $discussion->course        = $forum->course;
89         $discussion->forum         = $forum->id;
90         $discussion->name          = $forum->name;
91         $discussion->intro         = $forum->intro;
92         $discussion->assessed      = $forum->assessed;
93         $discussion->messageformat = $forum->messageformat;
94         $discussion->mailnow       = false;
95         $discussion->groupid       = -1;
97         $message = '';
99         if (! forum_add_discussion($discussion, null, $message)) {
100             print_error('cannotadd', 'forum');
101         }
102     }
104     if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
105     /// all users should be subscribed initially
106     /// Note: forum_get_potential_subscribers should take the forum context,
107     /// but that does not exist yet, becuase the forum is only half build at this
108     /// stage. However, because the forum is brand new, we know that there are
109     /// no role assignments or overrides in the forum context, so using the
110     /// course context gives the same list of users.
111         $users = forum_get_potential_subscribers(get_context_instance(CONTEXT_COURSE, $forum->course), 0, 'u.id, u.email', '');
112         foreach ($users as $user) {
113             forum_subscribe($user->id, $forum->id);
114         }
115     }
117     forum_grade_item_update($forum);
119     return $forum->id;
123 /**
124  * Given an object containing all the necessary data,
125  * (defined by the form in mod_form.php) this function
126  * will update an existing instance with new data.
127  *
128  * @global object
129  * @param object $forum forum instance (with magic quotes)
130  * @return bool success
131  */
132 function forum_update_instance($forum) {
133     global $DB, $OUTPUT, $USER;
135     $forum->timemodified = time();
136     $forum->id           = $forum->instance;
138     if (empty($forum->assessed)) {
139         $forum->assessed = 0;
140     }
142     if (empty($forum->ratingtime) or empty($forum->assessed)) {
143         $forum->assesstimestart  = 0;
144         $forum->assesstimefinish = 0;
145     }
147     $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
149     // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
150     // if  scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
151     // 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
152     if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
153         forum_update_grades($forum); // recalculate grades for the forum
154     }
156     if ($forum->type == 'single') {  // Update related discussion and post.
157         if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
158             if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC')) {
159                 echo $OUTPUT->notification('Warning! There is more than one discussion in this forum - using the most recent');
160                 $discussion = array_pop($discussions);
161             } else {
162                 // try to recover by creating initial discussion - MDL-16262
163                 $discussion = new object();
164                 $discussion->course          = $forum->course;
165                 $discussion->forum           = $forum->id;
166                 $discussion->name            = $forum->name;
167                 $discussion->intro           = $forum->intro;
168                 $discussion->assessed        = $forum->assessed;
169                 $discussion->messageformat   = $forum->messageformat;
170                 $discussion->mailnow         = false;
171                 $discussion->groupid         = -1;
173                 forum_add_discussion($discussion, null, $message);
175                 if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
176                     print_error('cannotadd', 'forum');
177                 }
178             }
179         }
180         if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
181             print_error('cannotfindfirstpost', 'forum');
182         }
184         $post->subject  = $forum->name;
185         $post->message  = $forum->intro;
186         $post->modified = $forum->timemodified;
187         $post->userid   = $USER->id;    // MDL-18599, so that current teacher can take ownership of activities
189         $DB->update_record('forum_posts', $post);
190         $discussion->name = $forum->name;
191         $DB->update_record('forum_discussions', $discussion);
192     }
194     $DB->update_record('forum', $forum);
196     forum_grade_item_update($forum);
198     return true;
202 /**
203  * Given an ID of an instance of this module,
204  * this function will permanently delete the instance
205  * and any data that depends on it.
206  *
207  * @global object
208  * @param int $id forum instance id
209  * @return bool success
210  */
211 function forum_delete_instance($id) {
212     global $DB;
214     if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
215         return false;
216     }
217     if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
218         return false;
219     }
220     if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
221         return false;
222     }
224     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
226     // now get rid of all files
227     $fs = get_file_storage();
228     $fs->delete_area_files($context->id);
230     $result = true;
232     if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
233         foreach ($discussions as $discussion) {
234             if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
235                 $result = false;
236             }
237         }
238     }
240     if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
241         $result = false;
242     }
244     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
246     if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
247         $result = false;
248     }
250     forum_grade_item_delete($forum);
252     return $result;
256 /**
257  * Indicates API features that the forum supports.
258  *
259  * @uses FEATURE_GROUPS
260  * @uses FEATURE_GROUPINGS
261  * @uses FEATURE_GROUPMEMBERSONLY
262  * @uses FEATURE_MOD_INTRO
263  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
264  * @uses FEATURE_COMPLETION_HAS_RULES
265  * @uses FEATURE_GRADE_HAS_GRADE
266  * @uses FEATURE_GRADE_OUTCOMES
267  * @param string $feature
268  * @return mixed True if yes (some features may use other values)
269  */
270 function forum_supports($feature) {
271     switch($feature) {
272         case FEATURE_GROUPS:                  return true;
273         case FEATURE_GROUPINGS:               return true;
274         case FEATURE_GROUPMEMBERSONLY:        return true;
275         case FEATURE_MOD_INTRO:               return true;
276         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
277         case FEATURE_COMPLETION_HAS_RULES:    return true;
278         case FEATURE_GRADE_HAS_GRADE:         return true;
279         case FEATURE_GRADE_OUTCOMES:          return true;
280         case FEATURE_RATE:                 return true;
282         default: return null;
283     }
287 /**
288  * Obtains the automatic completion state for this forum based on any conditions
289  * in forum settings.
290  *
291  * @global object
292  * @global object
293  * @param object $course Course
294  * @param object $cm Course-module
295  * @param int $userid User ID
296  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
297  * @return bool True if completed, false if not. (If no conditions, then return
298  *   value depends on comparison type)
299  */
300 function forum_get_completion_state($course,$cm,$userid,$type) {
301     global $CFG,$DB;
303     // Get forum details
304     if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
305         throw new Exception("Can't find forum {$cm->instance}");
306     }
308     $result=$type; // Default return value
310     $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
311     $postcountsql="
312 SELECT
313     COUNT(1)
314 FROM
315     {forum_posts} fp
316     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
317 WHERE
318     fp.userid=:userid AND fd.forum=:forumid";
320     if ($forum->completiondiscussions) {
321         $value = $forum->completiondiscussions <=
322                  $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
323         if ($type == COMPLETION_AND) {
324             $result = $result && $value;
325         } else {
326             $result = $result || $value;
327         }
328     }
329     if ($forum->completionreplies) {
330         $value = $forum->completionreplies <=
331                  $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
332         if ($type==COMPLETION_AND) {
333             $result = $result && $value;
334         } else {
335             $result = $result || $value;
336         }
337     }
338     if ($forum->completionposts) {
339         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
340         if ($type == COMPLETION_AND) {
341             $result = $result && $value;
342         } else {
343             $result = $result || $value;
344         }
345     }
347     return $result;
351 /**
352  * Function to be run periodically according to the moodle cron
353  * Finds all posts that have yet to be mailed out, and mails them
354  * out to all subscribers
355  *
356  * @global object
357  * @global object
358  * @global object
359  * @uses CONTEXT_MODULE
360  * @uses CONTEXT_COURSE
361  * @uses SITEID
362  * @uses FORMAT_PLAIN
363  * @return void
364  */
365 function forum_cron() {
366     global $CFG, $USER, $DB;
368     $site = get_site();
370     // all users that are subscribed to any post that needs sending
371     $users = array();
373     // status arrays
374     $mailcount  = array();
375     $errorcount = array();
377     // caches
378     $discussions     = array();
379     $forums          = array();
380     $courses         = array();
381     $coursemodules   = array();
382     $subscribedusers = array();
385     // Posts older than 2 days will not be mailed.  This is to avoid the problem where
386     // cron has not been running for a long time, and then suddenly people are flooded
387     // with mail from the past few weeks or months
388     $timenow   = time();
389     $endtime   = $timenow - $CFG->maxeditingtime;
390     $starttime = $endtime - 48 * 3600;   // Two days earlier
392     if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
393         // Mark them all now as being mailed.  It's unlikely but possible there
394         // might be an error later so that a post is NOT actually mailed out,
395         // but since mail isn't crucial, we can accept this risk.  Doing it now
396         // prevents the risk of duplicated mails, which is a worse problem.
398         if (!forum_mark_old_posts_as_mailed($endtime)) {
399             mtrace('Errors occurred while trying to mark some posts as being mailed.');
400             return false;  // Don't continue trying to mail them, in case we are in a cron loop
401         }
403         // checking post validity, and adding users to loop through later
404         foreach ($posts as $pid => $post) {
406             $discussionid = $post->discussion;
407             if (!isset($discussions[$discussionid])) {
408                 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
409                     $discussions[$discussionid] = $discussion;
410                 } else {
411                     mtrace('Could not find discussion '.$discussionid);
412                     unset($posts[$pid]);
413                     continue;
414                 }
415             }
416             $forumid = $discussions[$discussionid]->forum;
417             if (!isset($forums[$forumid])) {
418                 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
419                     $forums[$forumid] = $forum;
420                 } else {
421                     mtrace('Could not find forum '.$forumid);
422                     unset($posts[$pid]);
423                     continue;
424                 }
425             }
426             $courseid = $forums[$forumid]->course;
427             if (!isset($courses[$courseid])) {
428                 if ($course = $DB->get_record('course', array('id' => $courseid))) {
429                     $courses[$courseid] = $course;
430                 } else {
431                     mtrace('Could not find course '.$courseid);
432                     unset($posts[$pid]);
433                     continue;
434                 }
435             }
436             if (!isset($coursemodules[$forumid])) {
437                 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
438                     $coursemodules[$forumid] = $cm;
439                 } else {
440                     mtrace('Could not course module for forum '.$forumid);
441                     unset($posts[$pid]);
442                     continue;
443                 }
444             }
447             // caching subscribed users of each forum
448             if (!isset($subscribedusers[$forumid])) {
449                 $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
450                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext)) {
451                     foreach ($subusers as $postuser) {
452                         // do not try to mail users with stopped email
453                         if ($postuser->emailstop) {
454                             if (!empty($CFG->forum_logblocked)) {
455                                 add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
456                             }
457                             continue;
458                         }
459                         // this user is subscribed to this forum
460                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
461                         // this user is a user we have to process later
462                         $users[$postuser->id] = $postuser;
463                     }
464                     unset($subusers); // release memory
465                 }
466             }
468             $mailcount[$pid] = 0;
469             $errorcount[$pid] = 0;
470         }
471     }
473     if ($users && $posts) {
475         $urlinfo = parse_url($CFG->wwwroot);
476         $hostname = $urlinfo['host'];
478         foreach ($users as $userto) {
480             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
482             // set this so that the capabilities are cached, and environment matches receiving user
483             cron_setup_user($userto);
485             mtrace('Processing user '.$userto->id);
487             // init caches
488             $userto->viewfullnames = array();
489             $userto->canpost       = array();
490             $userto->markposts     = array();
491             $userto->enrolledin    = array();
493             // reset the caches
494             foreach ($coursemodules as $forumid=>$unused) {
495                 $coursemodules[$forumid]->cache       = new object();
496                 $coursemodules[$forumid]->cache->caps = array();
497                 unset($coursemodules[$forumid]->uservisible);
498             }
500             foreach ($posts as $pid => $post) {
502                 // Set up the environment for the post, discussion, forum, course
503                 $discussion = $discussions[$post->discussion];
504                 $forum      = $forums[$discussion->forum];
505                 $course     = $courses[$forum->course];
506                 $cm         =& $coursemodules[$forum->id];
508                 // Do some checks  to see if we can bail out now
509                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
510                     continue; // user does not subscribe to this forum
511                 }
513                 // Verify user is enrollend in course - if not do not send any email
514                 if (!isset($userto->enrolledin[$course->id])) {
515                     $userto->enrolledin[$course->id] = is_enrolled(get_context_instance(CONTEXT_COURSE, $course->id));
516                 }
517                 if (!$userto->enrolledin[$course->id]) {
518                     // oops - this user should not receive anything from this course
519                     continue;
520                 }
522                 // Get info about the sending user
523                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
524                     $userfrom = $users[$post->userid];
525                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
526                     $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
527                 } else {
528                     mtrace('Could not find user '.$post->userid);
529                     continue;
530                 }
532                 // setup global $COURSE properly - needed for roles and languages
533                 cron_setup_user($userto, $course);
535                 // Fill caches
536                 if (!isset($userto->viewfullnames[$forum->id])) {
537                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
538                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
539                 }
540                 if (!isset($userto->canpost[$discussion->id])) {
541                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
542                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
543                 }
544                 if (!isset($userfrom->groups[$forum->id])) {
545                     if (!isset($userfrom->groups)) {
546                         $userfrom->groups = array();
547                         $users[$userfrom->id]->groups = array();
548                     }
549                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
550                     $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
551                 }
553                 // Make sure groups allow this user to see this email
554                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
555                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
556                         continue;                           // Be safe and don't send it to anyone
557                     }
559                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
560                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
561                         continue;
562                     }
563                 }
565                 // Make sure we're allowed to see it...
566                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
567                     mtrace('user '.$userto->id. ' can not see '.$post->id);
568                     continue;
569                 }
571                 // OK so we need to send the email.
573                 // Does the user want this post in a digest?  If so postpone it for now.
574                 if ($userto->maildigest > 0) {
575                     // This user wants the mails to be in digest form
576                     $queue = new object();
577                     $queue->userid       = $userto->id;
578                     $queue->discussionid = $discussion->id;
579                     $queue->postid       = $post->id;
580                     $queue->timemodified = $post->created;
581                     $DB->insert_record('forum_queue', $queue);
582                     continue;
583                 }
586                 // Prepare to actually send the post now, and build up the content
588                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
590                 $userfrom->customheaders = array (  // Headers to make emails easier to track
591                            'Precedence: Bulk',
592                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
593                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
594                            'Message-ID: <moodlepost'.$post->id.'@'.$hostname.'>',
595                            'In-Reply-To: <moodlepost'.$post->parent.'@'.$hostname.'>',
596                            'References: <moodlepost'.$post->parent.'@'.$hostname.'>',
597                            'X-Course-Id: '.$course->id,
598                            'X-Course-Name: '.format_string($course->fullname, true)
599                 );
602                 $postsubject = "$course->shortname: ".format_string($post->subject,true);
603                 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
604                 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
606                 // Send the post now!
608                 mtrace('Sending ', '');
610                 $eventdata = new object();
611                 $eventdata->component        = 'mod/forum';
612                 $eventdata->name             = 'posts';
613                 $eventdata->userfrom         = $userfrom;
614                 $eventdata->userto           = $userto;
615                 $eventdata->subject          = $postsubject;
616                 $eventdata->fullmessage      = $posttext;
617                 $eventdata->fullmessageformat = FORMAT_PLAIN;
618                 $eventdata->fullmessagehtml  = $posthtml;
619                 $eventdata->smallmessage     = '';
620                 if (!message_send($eventdata)){
622                     mtrace("Error: mod/forum/cron.php: Could not send out mail for id $post->id to user $userto->id".
623                          " ($userto->email) .. not trying again.");
624                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
625                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
626                     $errorcount[$post->id]++;
627                 } else if ($mailresult === 'emailstop') {
628                     // should not be reached anymore - see check above
629                 } else {
630                     $mailcount[$post->id]++;
632                 // Mark post as read if forum_usermarksread is set off
633                     if (!$CFG->forum_usermarksread) {
634                         $userto->markposts[$post->id] = $post->id;
635                     }
636                 }
638                 mtrace('post '.$post->id. ': '.$post->subject);
639             }
641             // mark processed posts as read
642             forum_tp_mark_posts_read($userto, $userto->markposts);
643         }
644     }
646     if ($posts) {
647         foreach ($posts as $post) {
648             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
649             if ($errorcount[$post->id]) {
650                 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
651             }
652         }
653     }
655     // release some memory
656     unset($subscribedusers);
657     unset($mailcount);
658     unset($errorcount);
660     cron_setup_user();
662     $sitetimezone = $CFG->timezone;
664     // Now see if there are any digest mails waiting to be sent, and if we should send them
666     mtrace('Starting digest processing...');
668     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
670     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
671         set_config('digestmailtimelast', 0);
672     }
674     $timenow = time();
675     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
677     // Delete any really old ones (normally there shouldn't be any)
678     $weekago = $timenow - (7 * 24 * 3600);
679     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
680     mtrace ('Cleaned old digest records');
682     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
684         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
686         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
688         if ($digestposts_rs->valid()) {
690             // We have work to do
691             $usermailcount = 0;
693             //caches - reuse the those filled before too
694             $discussionposts = array();
695             $userdiscussions = array();
697             foreach ($digestposts_rs as $digestpost) {
698                 if (!isset($users[$digestpost->userid])) {
699                     if ($user = $DB->get_record('user', array('id' => $digestpost->userid))) {
700                         $users[$digestpost->userid] = $user;
701                     } else {
702                         continue;
703                     }
704                 }
705                 $postuser = $users[$digestpost->userid];
706                 if ($postuser->emailstop) {
707                     if (!empty($CFG->forum_logblocked)) {
708                         add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
709                     }
710                     continue;
711                 }
713                 if (!isset($posts[$digestpost->postid])) {
714                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
715                         $posts[$digestpost->postid] = $post;
716                     } else {
717                         continue;
718                     }
719                 }
720                 $discussionid = $digestpost->discussionid;
721                 if (!isset($discussions[$discussionid])) {
722                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
723                         $discussions[$discussionid] = $discussion;
724                     } else {
725                         continue;
726                     }
727                 }
728                 $forumid = $discussions[$discussionid]->forum;
729                 if (!isset($forums[$forumid])) {
730                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
731                         $forums[$forumid] = $forum;
732                     } else {
733                         continue;
734                     }
735                 }
737                 $courseid = $forums[$forumid]->course;
738                 if (!isset($courses[$courseid])) {
739                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
740                         $courses[$courseid] = $course;
741                     } else {
742                         continue;
743                     }
744                 }
746                 if (!isset($coursemodules[$forumid])) {
747                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
748                         $coursemodules[$forumid] = $cm;
749                     } else {
750                         continue;
751                     }
752                 }
753                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
754                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
755             }
756             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
758             // Data collected, start sending out emails to each user
759             foreach ($userdiscussions as $userid => $thesediscussions) {
761                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
763                 cron_setup_user();
765                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
767                 // First of all delete all the queue entries for this user
768                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
769                 $userto = $users[$userid];
771                 // Override the language and timezone of the "current" user, so that
772                 // mail is customised for the receiver.
773                 cron_setup_user($userto);
775                 // init caches
776                 $userto->viewfullnames = array();
777                 $userto->canpost       = array();
778                 $userto->markposts     = array();
780                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
782                 $headerdata = new object();
783                 $headerdata->sitename = format_string($site->fullname, true);
784                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
786                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
787                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
789                 $posthtml = "<head>";
790 /*                foreach ($CFG->stylesheets as $stylesheet) {
791                     //TODO: MDL-21120
792                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
793                 }*/
794                 $posthtml .= "</head>\n<body id=\"email\">\n";
795                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
797                 foreach ($thesediscussions as $discussionid) {
799                     @set_time_limit(120);   // to be reset for each post
801                     $discussion = $discussions[$discussionid];
802                     $forum      = $forums[$discussion->forum];
803                     $course     = $courses[$forum->course];
804                     $cm         = $coursemodules[$forum->id];
806                     //override language
807                     cron_setup_user($userto, $course);
809                     // Fill caches
810                     if (!isset($userto->viewfullnames[$forum->id])) {
811                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
812                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
813                     }
814                     if (!isset($userto->canpost[$discussion->id])) {
815                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
816                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
817                     }
819                     $strforums      = get_string('forums', 'forum');
820                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
821                     $canreply       = $userto->canpost[$discussion->id];
823                     $posttext .= "\n \n";
824                     $posttext .= '=====================================================================';
825                     $posttext .= "\n \n";
826                     $posttext .= "$course->shortname -> $strforums -> ".format_string($forum->name,true);
827                     if ($discussion->name != $forum->name) {
828                         $posttext  .= " -> ".format_string($discussion->name,true);
829                     }
830                     $posttext .= "\n";
832                     $posthtml .= "<p><font face=\"sans-serif\">".
833                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> -> ".
834                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
835                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
836                     if ($discussion->name == $forum->name) {
837                         $posthtml .= "</font></p>";
838                     } else {
839                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
840                     }
841                     $posthtml .= '<p>';
843                     $postsarray = $discussionposts[$discussionid];
844                     sort($postsarray);
846                     foreach ($postsarray as $postid) {
847                         $post = $posts[$postid];
849                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
850                             $userfrom = $users[$post->userid];
851                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
852                             $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
853                         } else {
854                             mtrace('Could not find user '.$post->userid);
855                             continue;
856                         }
858                         if (!isset($userfrom->groups[$forum->id])) {
859                             if (!isset($userfrom->groups)) {
860                                 $userfrom->groups = array();
861                                 $users[$userfrom->id]->groups = array();
862                             }
863                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
864                             $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
865                         }
867                         $userfrom->customheaders = array ("Precedence: Bulk");
869                         if ($userto->maildigest == 2) {
870                             // Subjects only
871                             $by = new object();
872                             $by->name = fullname($userfrom);
873                             $by->date = userdate($post->modified);
874                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
875                             $posttext .= "\n---------------------------------------------------------------------";
877                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
878                             $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>';
880                         } else {
881                             // The full treatment
882                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
883                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
885                         // Create an array of postid's for this user to mark as read.
886                             if (!$CFG->forum_usermarksread) {
887                                 $userto->markposts[$post->id] = $post->id;
888                             }
889                         }
890                     }
891                     if ($canunsubscribe) {
892                         $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>";
893                     } else {
894                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
895                     }
896                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
897                 }
898                 $posthtml .= '</body>';
900                 if ($userto->mailformat != 1) {
901                     // This user DOESN'T want to receive HTML
902                     $posthtml = '';
903                 }
905                 $eventdata = new object();
906                 $eventdata->component        = 'mod/forum';
907                 $eventdata->name             = 'digests';
908                 $eventdata->userfrom         = $site->shortname;
909                 $eventdata->userto           = $userto;
910                 $eventdata->subject          = $postsubject;
911                 $eventdata->fullmessage      = $posttext;
912                 $eventdata->fullmessageformat = FORMAT_PLAIN;
913                 $eventdata->fullmessagehtml  = $posthtml;
914                 $eventdata->smallmessage     = '';
915                 if (!message_send($eventdata)){
916                     mtrace("ERROR!");
917                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
918                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
919                 } else if ($mailresult === 'emailstop') {
920                     // should not happen anymore - see check above
921                 } else {
922                     mtrace("success.");
923                     $usermailcount++;
925                     // Mark post as read if forum_usermarksread is set off
926                     forum_tp_mark_posts_read($userto, $userto->markposts);
927                 }
928             }
929         }
930     /// We have finishied all digest emails, update $CFG->digestmailtimelast
931         set_config('digestmailtimelast', $timenow);
932     }
934     cron_setup_user();
936     if (!empty($usermailcount)) {
937         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
938     }
940     if (!empty($CFG->forum_lastreadclean)) {
941         $timenow = time();
942         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
943             set_config('forum_lastreadclean', $timenow);
944             mtrace('Removing old forum read tracking info...');
945             forum_tp_clean_read_records();
946         }
947     } else {
948         set_config('forum_lastreadclean', time());
949     }
952     return true;
955 /**
956  * Builds and returns the body of the email notification in plain text.
957  *
958  * @global object
959  * @global object
960  * @uses CONTEXT_MODULE
961  * @param object $course
962  * @param object $cm
963  * @param object $forum
964  * @param object $discussion
965  * @param object $post
966  * @param object $userfrom
967  * @param object $userto
968  * @param boolean $bare
969  * @return string The email body in plain text format.
970  */
971 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
972     global $CFG, $USER;
974     if (!isset($userto->viewfullnames[$forum->id])) {
975         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
976         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
977     } else {
978         $viewfullnames = $userto->viewfullnames[$forum->id];
979     }
981     if (!isset($userto->canpost[$discussion->id])) {
982         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
983         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
984     } else {
985         $canreply = $userto->canpost[$discussion->id];
986     }
988     $by = New stdClass;
989     $by->name = fullname($userfrom, $viewfullnames);
990     $by->date = userdate($post->modified, "", $userto->timezone);
992     $strbynameondate = get_string('bynameondate', 'forum', $by);
994     $strforums = get_string('forums', 'forum');
996     $canunsubscribe = ! forum_is_forcesubscribed($forum);
998     $posttext = '';
1000     if (!$bare) {
1001         $posttext  = "$course->shortname -> $strforums -> ".format_string($forum->name,true);
1003         if ($discussion->name != $forum->name) {
1004             $posttext  .= " -> ".format_string($discussion->name,true);
1005         }
1006     }
1008     $posttext .= "\n---------------------------------------------------------------------\n";
1009     $posttext .= format_string($post->subject,true);
1010     if ($bare) {
1011         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1012     }
1013     $posttext .= "\n".$strbynameondate."\n";
1014     $posttext .= "---------------------------------------------------------------------\n";
1015     $posttext .= format_text_email($post->message, $post->messageformat);
1016     $posttext .= "\n\n";
1017     $posttext .= forum_print_attachments($post, $cm, "text");
1019     if (!$bare && $canreply) {
1020         $posttext .= "---------------------------------------------------------------------\n";
1021         $posttext .= get_string("postmailinfo", "forum", $course->shortname)."\n";
1022         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1023     }
1024     if (!$bare && $canunsubscribe) {
1025         $posttext .= "\n---------------------------------------------------------------------\n";
1026         $posttext .= get_string("unsubscribe", "forum");
1027         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1028     }
1030     return $posttext;
1033 /**
1034  * Builds and returns the body of the email notification in html format.
1035  *
1036  * @global object
1037  * @param object $course
1038  * @param object $cm
1039  * @param object $forum
1040  * @param object $discussion
1041  * @param object $post
1042  * @param object $userfrom
1043  * @param object $userto
1044  * @return string The email text in HTML format
1045  */
1046 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1047     global $CFG;
1049     if ($userto->mailformat != 1) {  // Needs to be HTML
1050         return '';
1051     }
1053     if (!isset($userto->canpost[$discussion->id])) {
1054         $canreply = forum_user_can_post($forum, $discussion, $userto);
1055     } else {
1056         $canreply = $userto->canpost[$discussion->id];
1057     }
1059     $strforums = get_string('forums', 'forum');
1060     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1062     $posthtml = '<head>';
1063 /*    foreach ($CFG->stylesheets as $stylesheet) {
1064         //TODO: MDL-21120
1065         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1066     }*/
1067     $posthtml .= '</head>';
1068     $posthtml .= "\n<body id=\"email\">\n\n";
1070     $posthtml .= '<div class="navbar">'.
1071     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$course->shortname.'</a> &raquo; '.
1072     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1073     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1074     if ($discussion->name == $forum->name) {
1075         $posthtml .= '</div>';
1076     } else {
1077         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1078                      format_string($discussion->name,true).'</a></div>';
1079     }
1080     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1082     if ($canunsubscribe) {
1083         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1084                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1085                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1086     }
1088     $posthtml .= '</body>';
1090     return $posthtml;
1094 /**
1095  *
1096  * @param object $course
1097  * @param object $user
1098  * @param object $mod TODO this is not used in this function, refactor
1099  * @param object $forum
1100  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1101  */
1102 function forum_user_outline($course, $user, $mod, $forum) {
1103     global $CFG;
1104     require_once("$CFG->libdir/gradelib.php");
1105     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1106     if (empty($grades->items[0]->grades)) {
1107         $grade = false;
1108     } else {
1109         $grade = reset($grades->items[0]->grades);
1110     }
1112     $count = forum_count_user_posts($forum->id, $user->id);
1114     if ($count && $count->postcount > 0) {
1115         $result = new object();
1116         $result->info = get_string("numposts", "forum", $count->postcount);
1117         $result->time = $count->lastpost;
1118         if ($grade) {
1119             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1120         }
1121         return $result;
1122     } else if ($grade) {
1123         $result = new object();
1124         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1125         $result->time = $grade->dategraded;
1126         return $result;
1127     }
1128     return NULL;
1132 /**
1133  * @global object
1134  * @global object
1135  * @param object $coure
1136  * @param object $user
1137  * @param object $mod
1138  * @param object $forum
1139  */
1140 function forum_user_complete($course, $user, $mod, $forum) {
1141     global $CFG,$USER, $OUTPUT;
1142     require_once("$CFG->libdir/gradelib.php");
1144     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1145     if (!empty($grades->items[0]->grades)) {
1146         $grade = reset($grades->items[0]->grades);
1147         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1148         if ($grade->str_feedback) {
1149             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1150         }
1151     }
1153     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1155         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1156             print_error('invalidcoursemodule');
1157         }
1158         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1160         // preload all user ratings for these discussions - one query only and minimal memory
1161         $cm->cache->ratings = array();
1162         $cm->cache->myratings = array();
1163         if ($postratings = forum_get_all_user_ratings($user->id, $discussions)) {
1164             foreach ($postratings as $pr) {
1165                 if (!isset($cm->cache->ratings[$pr->postid])) {
1166                     $cm->cache->ratings[$pr->postid] = array();
1167                 }
1168                 $cm->cache->ratings[$pr->postid][$pr->id] = $pr->rating;
1170                 if ($pr->userid == $USER->id) {
1171                     $cm->cache->myratings[$pr->postid] = $pr->rating;
1172                 }
1173             }
1174             unset($postratings);
1175         }
1177         foreach ($posts as $post) {
1178             if (!isset($discussions[$post->discussion])) {
1179                 continue;
1180             }
1181             $discussion = $discussions[$post->discussion];
1183             $ratings = null;
1185             if ($forum->assessed) {
1186                 if ($scale = make_grades_menu($forum->scale)) {
1187                     $ratings =new object();
1188                     $ratings->scale = $scale;
1189                     $ratings->assesstimestart = $forum->assesstimestart;
1190                     $ratings->assesstimefinish = $forum->assesstimefinish;
1191                     $ratings->allow = false;
1192                 }
1193             }
1194             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false, $ratings);
1195         }
1196     } else {
1197         echo "<p>".get_string("noposts", "forum")."</p>";
1198     }
1206 /**
1207  * @global object
1208  * @global object
1209  * @global object
1210  * @param array $courses
1211  * @param array $htmlarray
1212  */
1213 function forum_print_overview($courses,&$htmlarray) {
1214     global $USER, $CFG, $DB, $SESSION;
1216     //$LIKE = $DB->sql_ilike();//no longer using like in queries. MDL-20578
1218     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1219         return array();
1220     }
1222     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1223         return;
1224     }
1227     // get all forum logs in ONE query (much better!)
1228     $params = array();
1229     $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {log} l "
1230         ." JOIN {course_modules} cm ON cm.id = cmid "
1231         ." WHERE (";
1232     foreach ($courses as $course) {
1233         $sql .= '(l.course = ? AND l.time > ?) OR ';
1234         $params[] = $course->id;
1235         $params[] = $course->lastaccess;
1236     }
1237     $sql = substr($sql,0,-3); // take off the last OR
1239     $sql .= ") AND l.module = 'forum' AND action = 'add post' "
1240         ." AND userid != ? GROUP BY cmid,l.course,instance";
1242     $params[] = $USER->id;
1244     if (!$new = $DB->get_records_sql($sql, $params)) {
1245         $new = array(); // avoid warnings
1246     }
1248     // also get all forum tracking stuff ONCE.
1249     $trackingforums = array();
1250     foreach ($forums as $forum) {
1251         if (forum_tp_can_track_forums($forum)) {
1252             $trackingforums[$forum->id] = $forum;
1253         }
1254     }
1256     if (count($trackingforums) > 0) {
1257         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1258         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1259             ' FROM {forum_posts} p '.
1260             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1261             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1262         $params = array($USER->id);
1264         foreach ($trackingforums as $track) {
1265             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1266             $params[] = $track->id;
1267             if (isset($SESSION->currentgroup[$track->course])) {
1268                 $groupid =  $SESSION->currentgroup[$track->course];
1269             } else {
1270                 $groupid = groups_get_all_groups($track->course, $USER->id);
1271                 if (is_array($groupid)) {
1272                     $groupid = array_shift(array_keys($groupid));
1273                     $SESSION->currentgroup[$track->course] = $groupid;
1274                 } else {
1275                     $groupid = 0;
1276                 }
1277             }
1278             $params[] = $groupid;
1279         }
1280         $sql = substr($sql,0,-3); // take off the last OR
1281         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1282         $params[] = $cutoffdate;
1284         if (!$unread = $DB->get_records_sql($sql, $params)) {
1285             $unread = array();
1286         }
1287     } else {
1288         $unread = array();
1289     }
1291     if (empty($unread) and empty($new)) {
1292         return;
1293     }
1295     $strforum = get_string('modulename','forum');
1296     $strnumunread = get_string('overviewnumunread','forum');
1297     $strnumpostssince = get_string('overviewnumpostssince','forum');
1299     foreach ($forums as $forum) {
1300         $str = '';
1301         $count = 0;
1302         $thisunread = 0;
1303         $showunread = false;
1304         // either we have something from logs, or trackposts, or nothing.
1305         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1306             $count = $new[$forum->id]->count;
1307         }
1308         if (array_key_exists($forum->id,$unread)) {
1309             $thisunread = $unread[$forum->id]->count;
1310             $showunread = true;
1311         }
1312         if ($count > 0 || $thisunread > 0) {
1313             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1314                 $forum->name.'</a></div>';
1315             $str .= '<div class="info">';
1316             $str .= $count.' '.$strnumpostssince;
1317             if (!empty($showunread)) {
1318                 $str .= '<br />'.$thisunread .' '.$strnumunread;
1319             }
1320             $str .= '</div></div>';
1321         }
1322         if (!empty($str)) {
1323             if (!array_key_exists($forum->course,$htmlarray)) {
1324                 $htmlarray[$forum->course] = array();
1325             }
1326             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1327                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1328             }
1329             $htmlarray[$forum->course]['forum'] .= $str;
1330         }
1331     }
1334 /**
1335  * Given a course and a date, prints a summary of all the new
1336  * messages posted in the course since that date
1337  *
1338  * @global object
1339  * @global object
1340  * @global object
1341  * @uses CONTEXT_MODULE
1342  * @uses VISIBLEGROUPS
1343  * @param object $course
1344  * @param bool $viewfullnames capability
1345  * @param int $timestart
1346  * @return bool success
1347  */
1348 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1349     global $CFG, $USER, $DB, $OUTPUT;
1351     // do not use log table if possible, it may be huge and is expensive to join with other tables
1353     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1354                                               d.timestart, d.timeend, d.userid AS duserid,
1355                                               u.firstname, u.lastname, u.email, u.picture
1356                                          FROM {forum_posts} p
1357                                               JOIN {forum_discussions} d ON d.id = p.discussion
1358                                               JOIN {forum} f             ON f.id = d.forum
1359                                               JOIN {user} u              ON u.id = p.userid
1360                                         WHERE p.created > ? AND f.course = ?
1361                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1362          return false;
1363     }
1365     $modinfo =& get_fast_modinfo($course);
1367     $groupmodes = array();
1368     $cms    = array();
1370     $strftimerecent = get_string('strftimerecent');
1372     $printposts = array();
1373     foreach ($posts as $post) {
1374         if (!isset($modinfo->instances['forum'][$post->forum])) {
1375             // not visible
1376             continue;
1377         }
1378         $cm = $modinfo->instances['forum'][$post->forum];
1379         if (!$cm->uservisible) {
1380             continue;
1381         }
1382         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1384         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1385             continue;
1386         }
1388         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1389           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1390             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1391                 continue;
1392             }
1393         }
1395         $groupmode = groups_get_activity_groupmode($cm, $course);
1397         if ($groupmode) {
1398             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1399                 // oki (Open discussions have groupid -1)
1400             } else {
1401                 // separate mode
1402                 if (isguestuser()) {
1403                     // shortcut
1404                     continue;
1405                 }
1407                 if (is_null($modinfo->groups)) {
1408                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1409                 }
1411                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1412                     continue;
1413                 }
1414             }
1415         }
1417         $printposts[] = $post;
1418     }
1419     unset($posts);
1421     if (!$printposts) {
1422         return false;
1423     }
1425     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1426     echo "\n<ul class='unlist'>\n";
1428     foreach ($printposts as $post) {
1429         $subjectclass = empty($post->parent) ? ' bold' : '';
1431         echo '<li><div class="head">'.
1432                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1433                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1434              '</div>';
1435         echo '<div class="info'.$subjectclass.'">';
1436         if (empty($post->parent)) {
1437             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1438         } else {
1439             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1440         }
1441         $post->subject = break_up_long_words(format_string($post->subject, true));
1442         echo $post->subject;
1443         echo "</a>\"</div></li>\n";
1444     }
1446     echo "</ul>\n";
1448     return true;
1451 /**
1452  * Return grade for given user or all users.
1453  *
1454  * @global object
1455  * @global object
1456  * @param object $forum
1457  * @param int $userid optional user id, 0 means all users
1458  * @return array array of grades, false if none
1459  */
1460  //todo andrew pretty sure I can remove this
1461 function forum_get_user_grades($forum, $userid=0) {
1462     global $CFG, $DB;
1464     $params= array();
1465     if ($userid) {
1466         $params[] = $userid;
1467         $user = "AND u.id = ?";
1468     } else {
1469         $user = "";
1470     }
1472     $params[] = $forum->id;
1474     $aggtype = $forum->assessed;
1475     switch ($aggtype) {
1476         case FORUM_AGGREGATE_COUNT :
1477             $sql = "SELECT u.id, u.id AS userid, COUNT(fr.rating) AS rawgrade
1478                       FROM {user} u, {forum_posts} fp,
1479                            {forum_ratings} fr, {forum_discussions} fd
1480                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1481                            AND fr.userid != u.id AND fd.forum = ?
1482                            $user
1483                   GROUP BY u.id";
1484             break;
1485         case FORUM_AGGREGATE_MAX :
1486             $sql = "SELECT u.id, u.id AS userid, MAX(fr.rating) AS rawgrade
1487                       FROM {user} u, {forum_posts} fp,
1488                            {forum_ratings} fr, {forum_discussions} fd
1489                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1490                            AND fr.userid != u.id AND fd.forum = ?
1491                            $user
1492                   GROUP BY u.id";
1493             break;
1494         case FORUM_AGGREGATE_MIN :
1495             $sql = "SELECT u.id, u.id AS userid, MIN(fr.rating) AS rawgrade
1496                       FROM {user} u, {forum_posts} fp,
1497                            {forum_ratings} fr, {forum_discussions} fd
1498                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1499                            AND fr.userid != u.id AND fd.forum = ?
1500                            $user
1501                   GROUP BY u.id";
1502             break;
1503         case FORUM_AGGREGATE_SUM :
1504             $sql = "SELECT u.id, u.id AS userid, SUM(fr.rating) AS rawgrade
1505                      FROM {user} u, {forum_posts} fp,
1506                           {forum_ratings} fr, {forum_discussions} fd
1507                     WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1508                           AND fr.userid != u.id AND fd.forum = ?
1509                           $user
1510                  GROUP BY u.id";
1511             break;
1512         default : //avg
1513             $sql = "SELECT u.id, u.id AS userid, AVG(fr.rating) AS rawgrade
1514                       FROM {user} u, {forum_posts} fp,
1515                            {forum_ratings} fr, {forum_discussions} fd
1516                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1517                            AND fr.userid != u.id AND fd.forum = ?
1518                            $user
1519                   GROUP BY u.id";
1520             break;
1521     }
1523     if ($results = $DB->get_records_sql($sql, $params)) {
1524         // it could throw off the grading if count and sum returned a rawgrade higher than scale
1525         // 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)
1526         foreach ($results as $rid=>$result) {
1527             if ($forum->scale >= 0) {
1528                 //numeric
1529                 if ($result->rawgrade > $forum->scale) {
1530                     $results[$rid]->rawgrade = $forum->scale;
1531                 }
1532             } else {
1533                 //scales
1534                 if ($scale = $DB->get_record('scale', array('id' => -$forum->scale))) {
1535                     $scale = explode(',', $scale->scale);
1536                     $max = count($scale);
1537                     if ($result->rawgrade > $max) {
1538                         $results[$rid]->rawgrade = $max;
1539                     }
1540                 }
1541             }
1542         }
1543     }
1545     return $results;
1548 /**
1549  * Update activity grades
1550  *
1551  * @global object
1552  * @global object
1553  * @param object $forum
1554  * @param int $userid specific user only, 0 means all
1555  * @param boolean $nullifnone return null if grade does not exist
1556  * @return void
1557  */
1558 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1559     global $CFG, $DB;
1560     require_once($CFG->libdir.'/gradelib.php');
1562     if (!$forum->assessed) {
1563         forum_grade_item_update($forum);
1565     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1566         forum_grade_item_update($forum, $grades);
1568     } else if ($userid and $nullifnone) {
1569         $grade = new object();
1570         $grade->userid   = $userid;
1571         $grade->rawgrade = NULL;
1572         forum_grade_item_update($forum, $grade);
1574     } else {
1575         forum_grade_item_update($forum);
1576     }
1579 /**
1580  * Update all grades in gradebook.
1581  * @global object
1582  */
1583 function forum_upgrade_grades() {
1584     global $DB;
1586     $sql = "SELECT COUNT('x')
1587               FROM {forum} f, {course_modules} cm, {modules} m
1588              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1589     $count = $DB->count_records_sql($sql);
1591     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1592               FROM {forum} f, {course_modules} cm, {modules} m
1593              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1594     if ($rs = $DB->get_recordset_sql($sql)) {
1595         $pbar = new progress_bar('forumupgradegrades', 500, true);
1596         $i=0;
1597         foreach ($rs as $forum) {
1598             $i++;
1599             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1600             forum_update_grades($forum, 0, false);
1601             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1602         }
1603         $rs->close();
1604     }
1607 /**
1608  * Create/update grade item for given forum
1609  *
1610  * @global object
1611  * @uses GRADE_TYPE_NONE
1612  * @uses GRADE_TYPE_VALUE
1613  * @uses GRADE_TYPE_SCALE
1614  * @param object $forum object with extra cmidnumber
1615  * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
1616  * @return int 0 if ok
1617  */
1618 function forum_grade_item_update($forum, $grades=NULL) {
1619     global $CFG;
1620     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1621         require_once($CFG->libdir.'/gradelib.php');
1622     }
1624     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1626     if (!$forum->assessed or $forum->scale == 0) {
1627         $params['gradetype'] = GRADE_TYPE_NONE;
1629     } else if ($forum->scale > 0) {
1630         $params['gradetype'] = GRADE_TYPE_VALUE;
1631         $params['grademax']  = $forum->scale;
1632         $params['grademin']  = 0;
1634     } else if ($forum->scale < 0) {
1635         $params['gradetype'] = GRADE_TYPE_SCALE;
1636         $params['scaleid']   = -$forum->scale;
1637     }
1639     if ($grades  === 'reset') {
1640         $params['reset'] = true;
1641         $grades = NULL;
1642     }
1644     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1647 /**
1648  * Delete grade item for given forum
1649  *
1650  * @global object
1651  * @param object $forum object
1652  * @return object grade_item
1653  */
1654 function forum_grade_item_delete($forum) {
1655     global $CFG;
1656     require_once($CFG->libdir.'/gradelib.php');
1658     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1662 /**
1663  * Returns the users with data in one forum
1664  * (users with records in forum_subscriptions, forum_posts and forum_ratings, students)
1665  *
1666  * @global object
1667  * @global object
1668  * @param int $forumid
1669  * @return mixed array or false if none
1670  */
1671 function forum_get_participants($forumid) {
1673     global $CFG, $DB;
1675     //Get students from forum_subscriptions
1676     $st_subscriptions = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1677                                          FROM {user} u,
1678                                               {forum_subscriptions} s
1679                                          WHERE s.forum = ? AND
1680                                                u.id = s.userid", array($forumid));
1681     //Get students from forum_posts
1682     $st_posts = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1683                                  FROM {user} u,
1684                                       {forum_discussions} d,
1685                                       {forum_posts} p
1686                                  WHERE d.forum = ? AND
1687                                        p.discussion = d.id AND
1688                                        u.id = p.userid", array($forumid));
1690     //Get students from forum_ratings
1691     $st_ratings = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1692                                    FROM {user} u,
1693                                         {forum_discussions} d,
1694                                         {forum_posts} p,
1695                                         {forum_ratings} r
1696                                    WHERE d.forum = ? AND
1697                                          p.discussion = d.id AND
1698                                          r.post = p.id AND
1699                                          u.id = r.userid", array($forumid));
1701     //Add st_posts to st_subscriptions
1702     if ($st_posts) {
1703         foreach ($st_posts as $st_post) {
1704             $st_subscriptions[$st_post->id] = $st_post;
1705         }
1706     }
1707     //Add st_ratings to st_subscriptions
1708     if ($st_ratings) {
1709         foreach ($st_ratings as $st_rating) {
1710             $st_subscriptions[$st_rating->id] = $st_rating;
1711         }
1712     }
1713     //Return st_subscriptions array (it contains an array of unique users)
1714     return ($st_subscriptions);
1717 /**
1718  * This function returns if a scale is being used by one forum
1719  *
1720  * @global object
1721  * @param int $forumid
1722  * @param int $scaleid negative number
1723  * @return bool
1724  */
1725 function forum_scale_used ($forumid,$scaleid) {
1726     global $DB;
1727     $return = false;
1729     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1731     if (!empty($rec) && !empty($scaleid)) {
1732         $return = true;
1733     }
1735     return $return;
1738 /**
1739  * Checks if scale is being used by any instance of forum
1740  *
1741  * This is used to find out if scale used anywhere
1742  *
1743  * @global object
1744  * @param $scaleid int
1745  * @return boolean True if the scale is used by any forum
1746  */
1747 function forum_scale_used_anywhere($scaleid) {
1748     global $DB;
1749     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1750         return true;
1751     } else {
1752         return false;
1753     }
1756 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1758 /**
1759  * Gets a post with all info ready for forum_print_post
1760  * Most of these joins are just to get the forum id
1761  *
1762  * @global object
1763  * @global object
1764  * @param int $postid
1765  * @return mixed array of posts or false
1766  */
1767 function forum_get_post_full($postid) {
1768     global $CFG, $DB;
1770     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1771                              FROM {forum_posts} p
1772                                   JOIN {forum_discussions} d ON p.discussion = d.id
1773                                   LEFT JOIN {user} u ON p.userid = u.id
1774                             WHERE p.id = ?", array($postid));
1777 /**
1778  * Gets posts with all info ready for forum_print_post
1779  * We pass forumid in because we always know it so no need to make a
1780  * complicated join to find it out.
1781  *
1782  * @global object
1783  * @global object
1784  * @return mixed array of posts or false
1785  */
1786 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1787     global $CFG, $DB;
1789     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1790                               FROM {forum_posts} p
1791                          LEFT JOIN {user} u ON p.userid = u.id
1792                              WHERE p.discussion = ?
1793                                AND p.parent > 0 $sort", array($discussion));
1796 /**
1797  * Gets all posts in discussion including top parent.
1798  *
1799  * @global object
1800  * @global object
1801  * @global object
1802  * @param int $discussionid
1803  * @param string $sort
1804  * @param bool $tracking does user track the forum?
1805  * @return array of posts
1806  */
1807 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1808     global $CFG, $DB, $USER;
1810     $tr_sel  = "";
1811     $tr_join = "";
1812     $params = array();
1814     if ($tracking) {
1815         $now = time();
1816         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1817         $tr_sel  = ", fr.id AS postread";
1818         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1819         $params[] = $USER->id;
1820     }
1822     $params[] = $discussionid;
1823     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1824                                      FROM {forum_posts} p
1825                                           LEFT JOIN {user} u ON p.userid = u.id
1826                                           $tr_join
1827                                     WHERE p.discussion = ?
1828                                  ORDER BY $sort", $params)) {
1829         return array();
1830     }
1832     foreach ($posts as $pid=>$p) {
1833         if ($tracking) {
1834             if (forum_tp_is_post_old($p)) {
1835                  $posts[$pid]->postread = true;
1836             }
1837         }
1838         if (!$p->parent) {
1839             continue;
1840         }
1841         if (!isset($posts[$p->parent])) {
1842             continue; // parent does not exist??
1843         }
1844         if (!isset($posts[$p->parent]->children)) {
1845             $posts[$p->parent]->children = array();
1846         }
1847         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1848     }
1850     return $posts;
1853 /**
1854  * Gets posts with all info ready for forum_print_post
1855  * We pass forumid in because we always know it so no need to make a
1856  * complicated join to find it out.
1857  *
1858  * @global object
1859  * @global object
1860  * @param int $parent
1861  * @param int $forumid
1862  * @return array
1863  */
1864 function forum_get_child_posts($parent, $forumid) {
1865     global $CFG, $DB;
1867     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1868                               FROM {forum_posts} p
1869                          LEFT JOIN {user} u ON p.userid = u.id
1870                              WHERE p.parent = ?
1871                           ORDER BY p.created ASC", array($parent));
1874 /**
1875  * An array of forum objects that the user is allowed to read/search through.
1876  *
1877  * @global object
1878  * @global object
1879  * @global object
1880  * @param int $userid
1881  * @param int $courseid if 0, we look for forums throughout the whole site.
1882  * @return array of forum objects, or false if no matches
1883  *         Forum objects have the following attributes:
1884  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1885  *         viewhiddentimedposts
1886  */
1887 function forum_get_readable_forums($userid, $courseid=0) {
1889     global $CFG, $DB, $USER;
1890     require_once($CFG->dirroot.'/course/lib.php');
1892     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1893         print_error('notinstalled', 'forum');
1894     }
1896     if ($courseid) {
1897         $courses = $DB->get_records('course', array('id' => $courseid));
1898     } else {
1899         // If no course is specified, then the user can see SITE + his courses.
1900         // And admins can see all courses, so pass the $doanything flag enabled
1901         $courses1 = $DB->get_records('course', array('id' => SITEID));
1902         $courses2 = get_my_courses($userid, null, null, true);
1903         $courses = array_merge($courses1, $courses2);
1904     }
1905     if (!$courses) {
1906         return array();
1907     }
1909     $readableforums = array();
1911     foreach ($courses as $course) {
1913         $modinfo =& get_fast_modinfo($course);
1914         if (is_null($modinfo->groups)) {
1915             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1916         }
1918         if (empty($modinfo->instances['forum'])) {
1919             // hmm, no forums?
1920             continue;
1921         }
1923         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1925         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1926             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1927                 continue;
1928             }
1929             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1930             $forum = $courseforums[$forumid];
1932             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1933                 continue;
1934             }
1936          /// group access
1937             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1938                 if (is_null($modinfo->groups)) {
1939                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1940                 }
1941                 if (isset($modinfo->groups[$cm->groupingid])) {
1942                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1943                     $forum->onlygroups[] = -1;
1944                 } else {
1945                     $forum->onlygroups = array(-1);
1946                 }
1947             }
1949         /// hidden timed discussions
1950             $forum->viewhiddentimedposts = true;
1951             if (!empty($CFG->forum_enabletimedposts)) {
1952                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1953                     $forum->viewhiddentimedposts = false;
1954                 }
1955             }
1957         /// qanda access
1958             if ($forum->type == 'qanda'
1959                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1961                 // We need to check whether the user has posted in the qanda forum.
1962                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1963                                                     // the user is allowed to see in this forum.
1964                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1965                     foreach ($discussionspostedin as $d) {
1966                         $forum->onlydiscussions[] = $d->id;
1967                     }
1968                 }
1969             }
1971             $readableforums[$forum->id] = $forum;
1972         }
1974         unset($modinfo);
1976     } // End foreach $courses
1978     //print_object($courses);
1979     //print_object($readableforums);
1981     return $readableforums;
1984 /**
1985  * Returns a list of posts found using an array of search terms.
1986  *
1987  * @global object
1988  * @global object
1989  * @global object
1990  * @param array $searchterms array of search terms, e.g. word +word -word
1991  * @param int $courseid if 0, we search through the whole site
1992  * @param int $limitfrom
1993  * @param int $limitnum
1994  * @param int &$totalcount
1995  * @param string $extrasql
1996  * @return array|bool Array of posts found or false
1997  */
1998 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1999                             &$totalcount, $extrasql='') {
2000     global $CFG, $DB, $USER;
2001     require_once($CFG->libdir.'/searchlib.php');
2003     $forums = forum_get_readable_forums($USER->id, $courseid);
2005     if (count($forums) == 0) {
2006         $totalcount = 0;
2007         return false;
2008     }
2010     $now = round(time(), -2); // db friendly
2012     $fullaccess = array();
2013     $where = array();
2014     $params = array();
2016     foreach ($forums as $forumid => $forum) {
2017         $select = array();
2019         if (!$forum->viewhiddentimedposts) {
2020             $select[] = "(d.userid = :userid OR (d.timestart < : AND (d.timeend = 0 OR d.timeend > :timeend)))";
2021             $params = array('userid'=>$USER->id, 'timestart'=>$now, 'timeend'=>$now);
2022         }
2024         $cm = get_coursemodule_from_instance('forum', $forumid);
2025         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2027         if ($forum->type == 'qanda'
2028             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
2029             if (!empty($forum->onlydiscussions)) {
2030                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda0');
2031                 $params = array_merge($params, $discussionid_params);
2032                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
2033             } else {
2034                 $select[] = "p.parent = 0";
2035             }
2036         }
2038         if (!empty($forum->onlygroups)) {
2039             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps0');
2040             $params = array_merge($params, $groupid_params);
2041             $select[] = "d.groupid $groupid_sql";
2042         }
2044         if ($select) {
2045             $selects = implode(" AND ", $select);
2046             $where[] = "(d.forum = :forum AND $selects)";
2047             $params['forum'] = $forumid;
2048         } else {
2049             $fullaccess[] = $forumid;
2050         }
2051     }
2053     if ($fullaccess) {
2054         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula0');
2055         $params = array_merge($params, $fullid_params);
2056         $where[] = "(d.forum $fullid_sql)";
2057     }
2059     $selectdiscussion = "(".implode(" OR ", $where).")";
2061     $messagesearch = '';
2062     $searchstring = '';
2064     // Need to concat these back together for parser to work.
2065     foreach($searchterms as $searchterm){
2066         if ($searchstring != '') {
2067             $searchstring .= ' ';
2068         }
2069         $searchstring .= $searchterm;
2070     }
2072     // We need to allow quoted strings for the search. The quotes *should* be stripped
2073     // by the parser, but this should be examined carefully for security implications.
2074     $searchstring = str_replace("\\\"","\"",$searchstring);
2075     $parser = new search_parser();
2076     $lexer = new search_lexer($parser);
2078     if ($lexer->parse($searchstring)) {
2079         $parsearray = $parser->get_parsed_array();
2080     // Experimental feature under 1.8! MDL-8830
2081     // Use alternative text searches if defined
2082     // This feature only works under mysql until properly implemented for other DBs
2083     // Requires manual creation of text index for forum_posts before enabling it:
2084     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2085     // Experimental feature under 1.8! MDL-8830
2086         if (!empty($CFG->forum_usetextsearches)) {
2087             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2088                                                  'p.userid', 'u.id', 'u.firstname',
2089                                                  'u.lastname', 'p.modified', 'd.forum');
2090         } else {
2091             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2092                                                  'p.userid', 'u.id', 'u.firstname',
2093                                                  'u.lastname', 'p.modified', 'd.forum');
2094         }
2095         $params = array_merge($params, $msparams);
2096     }
2098     $fromsql = "{forum_posts} p,
2099                   {forum_discussions} d,
2100                   {user} u";
2102     $selectsql = " $messagesearch
2103                AND p.discussion = d.id
2104                AND p.userid = u.id
2105                AND $selectdiscussion
2106                    $extrasql";
2108     $countsql = "SELECT COUNT(*)
2109                    FROM $fromsql
2110                   WHERE $selectsql";
2112     $searchsql = "SELECT p.*,
2113                          d.forum,
2114                          u.firstname,
2115                          u.lastname,
2116                          u.email,
2117                          u.picture,
2118                          u.imagealt
2119                     FROM $fromsql
2120                    WHERE $selectsql
2121                 ORDER BY p.modified DESC";
2123     $totalcount = $DB->count_records_sql($countsql, $params);
2125     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2128 /**
2129  * Returns a list of ratings for all posts in discussion
2130  *
2131  * @global object
2132  * @global object
2133  * @param object $discussion
2134  * @return array of ratings or false
2135  */
2136 function forum_get_all_discussion_ratings($discussion) {
2137     global $CFG, $DB;
2138     return $DB->get_records_sql("SELECT r.id, r.userid, p.id AS postid, r.rating
2139                               FROM {forum_ratings} r,
2140                                    {forum_posts} p
2141                              WHERE r.post = p.id AND p.discussion = ?
2142                              ORDER BY p.id ASC", array($discussion->id));
2145 /**
2146  * Returns a list of ratings for one specific user for all posts in discussion
2147  * @global object
2148  * @global object
2149  * @param object $discussions the discussions for which we return all ratings
2150  * @param int $userid the user for who we return all ratings
2151  * @return array
2152  */
2153 function forum_get_all_user_ratings($userid, $discussions) {
2154     global $CFG, $DB;
2157     foreach ($discussions as $discussion) {
2158      if (!isset($discussionsid)){
2159          $discussionsid = $discussion->id;
2160      }
2161      else {
2162          $discussionsid .= ",".$discussion->id;
2163      }
2164     }
2166     $sql = "SELECT r.id, r.userid, p.id AS postid, r.rating
2167                               FROM {forum_ratings} r,
2168                                    {forum_posts} p
2169                              WHERE r.post = p.id AND p.userid = :userid";
2172     $params = array();
2173     $params['userid'] = $userid;
2174     //postgres compability
2175     if (!isset($discussionsid)) {
2176        $sql .=" AND p.discussion IN (".$discussionsid.")";
2177     }
2178     $sql .=" ORDER BY p.id ASC";
2180     return $DB->get_records_sql($sql, $params);
2185 /**
2186  * Returns a list of ratings for a particular post - sorted.
2187  *
2188  * @global object
2189  * @global object
2190  * @param int $postid
2191  * @param string $sort
2192  * @return array Array of ratings or false
2193  */
2194 function forum_get_ratings($postid, $sort="u.firstname ASC") {
2195     global $CFG, $DB;
2196     return $DB->get_records_sql("SELECT u.*, r.rating, r.time
2197                               FROM {forum_ratings} r,
2198                                    {user} u
2199                              WHERE r.post = ?
2200                                AND r.userid = u.id
2201                              ORDER BY $sort", array($postid));
2204 /**
2205  * Returns a list of all new posts that have not been mailed yet
2206  *
2207  * @global object
2208  * @global object
2209  * @param int $starttime posts created after this time
2210  * @param int $endtime posts created before this
2211  * @param int $now used for timed discussions only
2212  * @return array
2213  */
2214 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2215     global $CFG, $DB;
2217     $params = array($starttime, $endtime);
2218     if (!empty($CFG->forum_enabletimedposts)) {
2219         if (empty($now)) {
2220             $now = time();
2221         }
2222         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2223         $params[] = $now;
2224         $params[] = $now;
2225     } else {
2226         $timedsql = "";
2227     }
2229     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2230                               FROM {forum_posts} p
2231                                    JOIN {forum_discussions} d ON d.id = p.discussion
2232                              WHERE p.mailed = 0
2233                                    AND p.created >= ?
2234                                    AND (p.created < ? OR p.mailnow = 1)
2235                                    $timedsql
2236                           ORDER BY p.modified ASC", $params);
2239 /**
2240  * Marks posts before a certain time as being mailed already
2241  *
2242  * @global object
2243  * @global object
2244  * @param int $endtime
2245  * @param int $now Defaults to time()
2246  * @return bool
2247  */
2248 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2249     global $CFG, $DB;
2250     if (empty($now)) {
2251         $now = time();
2252     }
2254     if (empty($CFG->forum_enabletimedposts)) {
2255         return $DB->execute("UPDATE {forum_posts}
2256                                SET mailed = '1'
2257                              WHERE (created < ? OR mailnow = 1)
2258                                    AND mailed = 0", array($endtime));
2260     } else {
2261         return $DB->execute("UPDATE {forum_posts}
2262                                SET mailed = '1'
2263                              WHERE discussion NOT IN (SELECT d.id
2264                                                         FROM {forum_discussions} d
2265                                                        WHERE d.timestart > ?)
2266                                    AND (created < ? OR mailnow = 1)
2267                                    AND mailed = 0", array($now, $endtime));
2268     }
2271 /**
2272  * Get all the posts for a user in a forum suitable for forum_print_post
2273  *
2274  * @global object
2275  * @global object
2276  * @uses CONTEXT_MODULE
2277  * @return array
2278  */
2279 function forum_get_user_posts($forumid, $userid) {
2280     global $CFG, $DB;
2282     $timedsql = "";
2283     $params = array($forumid, $userid);
2285     if (!empty($CFG->forum_enabletimedposts)) {
2286         $cm = get_coursemodule_from_instance('forum', $forumid);
2287         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2288             $now = time();
2289             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2290             $params[] = $now;
2291             $params[] = $now;
2292         }
2293     }
2295     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2296                               FROM {forum} f
2297                                    JOIN {forum_discussions} d ON d.forum = f.id
2298                                    JOIN {forum_posts} p       ON p.discussion = d.id
2299                                    JOIN {user} u              ON u.id = p.userid
2300                              WHERE f.id = ?
2301                                    AND p.userid = ?
2302                                    $timedsql
2303                           ORDER BY p.modified ASC", $params);
2306 /**
2307  * Get all the discussions user participated in
2308  *
2309  * @global object
2310  * @global object
2311  * @uses CONTEXT_MODULE
2312  * @param int $forumid
2313  * @param int $userid
2314  * @return array Array or false
2315  */
2316 function forum_get_user_involved_discussions($forumid, $userid) {
2317     global $CFG, $DB;
2319     $timedsql = "";
2320     $params = array($forumid, $userid);
2321     if (!empty($CFG->forum_enabletimedposts)) {
2322         $cm = get_coursemodule_from_instance('forum', $forumid);
2323         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2324             $now = time();
2325             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2326             $params[] = $now;
2327             $params[] = $now;
2328         }
2329     }
2331     return $DB->get_records_sql("SELECT DISTINCT d.*
2332                               FROM {forum} f
2333                                    JOIN {forum_discussions} d ON d.forum = f.id
2334                                    JOIN {forum_posts} p       ON p.discussion = d.id
2335                              WHERE f.id = ?
2336                                    AND p.userid = ?
2337                                    $timedsql", $params);
2340 /**
2341  * Get all the posts for a user in a forum suitable for forum_print_post
2342  *
2343  * @global object
2344  * @global object
2345  * @param int $forumid
2346  * @param int $userid
2347  * @return array of counts or false
2348  */
2349 function forum_count_user_posts($forumid, $userid) {
2350     global $CFG, $DB;
2352     $timedsql = "";
2353     $params = array($forumid, $userid);
2354     if (!empty($CFG->forum_enabletimedposts)) {
2355         $cm = get_coursemodule_from_instance('forum', $forumid);
2356         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2357             $now = time();
2358             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2359             $params[] = $now;
2360             $params[] = $now;
2361         }
2362     }
2364     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2365                              FROM {forum} f
2366                                   JOIN {forum_discussions} d ON d.forum = f.id
2367                                   JOIN {forum_posts} p       ON p.discussion = d.id
2368                                   JOIN {user} u              ON u.id = p.userid
2369                             WHERE f.id = ?
2370                                   AND p.userid = ?
2371                                   $timedsql", $params);
2374 /**
2375  * Given a log entry, return the forum post details for it.
2376  *
2377  * @global object
2378  * @global object
2379  * @param object $log
2380  * @return array|null
2381  */
2382 function forum_get_post_from_log($log) {
2383     global $CFG, $DB;
2385     if ($log->action == "add post") {
2387         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2388                                            u.firstname, u.lastname, u.email, u.picture
2389                                  FROM {forum_discussions} d,
2390                                       {forum_posts} p,
2391                                       {forum} f,
2392                                       {user} u
2393                                 WHERE p.id = ?
2394                                   AND d.id = p.discussion
2395                                   AND p.userid = u.id
2396                                   AND u.deleted <> '1'
2397                                   AND f.id = d.forum", array($log->info));
2400     } else if ($log->action == "add discussion") {
2402         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2403                                            u.firstname, u.lastname, u.email, u.picture
2404                                  FROM {forum_discussions} d,
2405                                       {forum_posts} p,
2406                                       {forum} f,
2407                                       {user} u
2408                                 WHERE d.id = ?
2409                                   AND d.firstpost = p.id
2410                                   AND p.userid = u.id
2411                                   AND u.deleted <> '1'
2412                                   AND f.id = d.forum", array($log->info));
2413     }
2414     return NULL;
2417 /**
2418  * Given a discussion id, return the first post from the discussion
2419  *
2420  * @global object
2421  * @global object
2422  * @param int $dicsussionid
2423  * @return array
2424  */
2425 function forum_get_firstpost_from_discussion($discussionid) {
2426     global $CFG, $DB;
2428     return $DB->get_record_sql("SELECT p.*
2429                              FROM {forum_discussions} d,
2430                                   {forum_posts} p
2431                             WHERE d.id = ?
2432                               AND d.firstpost = p.id ", array($discussionid));
2435 /**
2436  * Returns an array of counts of replies to each discussion
2437  *
2438  * @global object
2439  * @global object
2440  * @param int $forumid
2441  * @param string $forumsort
2442  * @param int $limit
2443  * @param int $page
2444  * @param int $perpage
2445  * @return array
2446  */
2447 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2448     global $CFG, $DB;
2450     if ($limit > 0) {
2451         $limitfrom = 0;
2452         $limitnum  = $limit;
2453     } else if ($page != -1) {
2454         $limitfrom = $page*$perpage;
2455         $limitnum  = $perpage;
2456     } else {
2457         $limitfrom = 0;
2458         $limitnum  = 0;
2459     }
2461     if ($forumsort == "") {
2462         $orderby = "";
2463         $groupby = "";
2465     } else {
2466         $orderby = "ORDER BY $forumsort";
2467         $groupby = ", ".strtolower($forumsort);
2468         $groupby = str_replace('desc', '', $groupby);
2469         $groupby = str_replace('asc', '', $groupby);
2470     }
2472     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2473         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2474                   FROM {forum_posts} p
2475                        JOIN {forum_discussions} d ON p.discussion = d.id
2476                  WHERE p.parent > 0 AND d.forum = ?
2477               GROUP BY p.discussion";
2478         return $DB->get_records_sql($sql, array($forumid));
2480     } else {
2481         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2482                   FROM {forum_posts} p
2483                        JOIN {forum_discussions} d ON p.discussion = d.id
2484                  WHERE d.forum = ?
2485               GROUP BY p.discussion $groupby
2486               $orderby";
2487         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2488     }
2491 /**
2492  * @global object
2493  * @global object
2494  * @global object
2495  * @staticvar array $cache
2496  * @param object $forum
2497  * @param object $cm
2498  * @param object $course
2499  * @return mixed
2500  */
2501 function forum_count_discussions($forum, $cm, $course) {
2502     global $CFG, $DB, $USER;
2504     static $cache = array();
2506     $now = round(time(), -2); // db cache friendliness
2508     $params = array($course->id);
2510     if (!isset($cache[$course->id])) {
2511         if (!empty($CFG->forum_enabletimedposts)) {
2512             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2513             $params[] = $now;
2514             $params[] = $now;
2515         } else {
2516             $timedsql = "";
2517         }
2519         $sql = "SELECT f.id, COUNT(d.id) as dcount
2520                   FROM {forum} f
2521                        JOIN {forum_discussions} d ON d.forum = f.id
2522                  WHERE f.course = ?
2523                        $timedsql
2524               GROUP BY f.id";
2526         if ($counts = $DB->get_records_sql($sql, $params)) {
2527             foreach ($counts as $count) {
2528                 $counts[$count->id] = $count->dcount;
2529             }
2530             $cache[$course->id] = $counts;
2531         } else {
2532             $cache[$course->id] = array();
2533         }
2534     }
2536     if (empty($cache[$course->id][$forum->id])) {
2537         return 0;
2538     }
2540     $groupmode = groups_get_activity_groupmode($cm, $course);
2542     if ($groupmode != SEPARATEGROUPS) {
2543         return $cache[$course->id][$forum->id];
2544     }
2546     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2547         return $cache[$course->id][$forum->id];
2548     }
2550     require_once($CFG->dirroot.'/course/lib.php');
2552     $modinfo =& get_fast_modinfo($course);
2553     if (is_null($modinfo->groups)) {
2554         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2555     }
2557     if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2558         $mygroups = $modinfo->groups[$cm->groupingid];
2559     } else {
2560         $mygroups = false; // Will be set below
2561     }
2563     // add all groups posts
2564     if (empty($mygroups)) {
2565         $mygroups = array(-1=>-1);
2566     } else {
2567         $mygroups[-1] = -1;
2568     }
2570     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2571     $params[] = $forum->id;
2573     if (!empty($CFG->forum_enabletimedposts)) {
2574         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2575         $params[] = $now;
2576         $params[] = $now;
2577     } else {
2578         $timedsql = "";
2579     }
2581     $sql = "SELECT COUNT(d.id)
2582               FROM {forum_discussions} d
2583              WHERE d.groupid $mygroups_sql AND d.forum = ?
2584                    $timedsql";
2586     return $DB->get_field_sql($sql, $params);
2589 /**
2590  * How many unrated posts are in the given discussion for a given user?
2591  *
2592  * @global object
2593  * @global object
2594  * @param int $discussionid
2595  * @param int $userid
2596  * @return mixed
2597  */
2598 function forum_count_unrated_posts($discussionid, $userid) {
2599     global $CFG, $DB;
2600     if ($posts = $DB->get_record_sql("SELECT count(*) as num
2601                                    FROM {forum_posts}
2602                                   WHERE parent > 0
2603                                     AND discussion = ?
2604                                     AND userid <> ? ", array($discussionid, $userid))) {
2606         if ($rated = $DB->get_record_sql("SELECT count(*) as num
2607                                        FROM {forum_posts} p,
2608                                             {forum_ratings} r
2609                                       WHERE p.discussion = ?
2610                                         AND p.id = r.post
2611                                         AND r.userid = ?", array($discussionid, $userid))) {
2612             $difference = $posts->num - $rated->num;
2613             if ($difference > 0) {
2614                 return $difference;
2615             } else {
2616                 return 0;    // Just in case there was a counting error
2617             }
2618         } else {
2619             return $posts->num;
2620         }
2621     } else {
2622         return 0;
2623     }
2626 /**
2627  * Get all discussions in a forum
2628  *
2629  * @global object
2630  * @global object
2631  * @global object
2632  * @uses CONTEXT_MODULE
2633  * @uses VISIBLEGROUPS
2634  * @param object $cm
2635  * @param string $forumsort
2636  * @param bool $fullpost
2637  * @param int $unused
2638  * @param int $limit
2639  * @param bool $userlastmodified
2640  * @param int $page
2641  * @param int $perpage
2642  * @return array
2643  */
2644 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2645     global $CFG, $DB, $USER;
2647     $timelimit = '';
2649     $modcontext = null;
2651     $now = round(time(), -2);
2652     $params = array($cm->instance);
2654     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2656     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2657         return array();
2658     }
2660     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2662         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2663             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2664             $params[] = $now;
2665             $params[] = $now;
2666             if (isloggedin()) {
2667                 $timelimit .= " OR d.userid = ?";
2668                 $params[] = $USER->id;
2669             }
2670             $timelimit .= ")";
2671         }
2672     }
2674     if ($limit > 0) {
2675         $limitfrom = 0;
2676         $limitnum  = $limit;
2677     } else if ($page != -1) {
2678         $limitfrom = $page*$perpage;
2679         $limitnum  = $perpage;
2680     } else {
2681         $limitfrom = 0;
2682         $limitnum  = 0;
2683     }
2685     $groupmode    = groups_get_activity_groupmode($cm);
2686     $currentgroup = groups_get_activity_group($cm);
2688     if ($groupmode) {
2689         if (empty($modcontext)) {
2690             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2691         }
2693         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2694             if ($currentgroup) {
2695                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2696                 $params[] = $currentgroup;
2697             } else {
2698                 $groupselect = "";
2699             }
2701         } else {
2702             //seprate groups without access all
2703             if ($currentgroup) {
2704                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2705                 $params[] = $currentgroup;
2706             } else {
2707                 $groupselect = "AND d.groupid = -1";
2708             }
2709         }
2710     } else {
2711         $groupselect = "";
2712     }
2715     if (empty($forumsort)) {
2716         $forumsort = "d.timemodified DESC";
2717     }
2718     if (empty($fullpost)) {
2719         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2720     } else {
2721         $postdata = "p.*";
2722     }
2724     if (empty($userlastmodified)) {  // We don't need to know this
2725         $umfields = "";
2726         $umtable  = "";
2727     } else {
2728         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2729         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2730     }
2732     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2733                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2734               FROM {forum_discussions} d
2735                    JOIN {forum_posts} p ON p.discussion = d.id
2736                    JOIN {user} u ON p.userid = u.id
2737                    $umtable
2738              WHERE d.forum = ? AND p.parent = 0
2739                    $timelimit $groupselect
2740           ORDER BY $forumsort";
2741     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2744 /**
2745  *
2746  * @global object
2747  * @global object
2748  * @global object
2749  * @uses CONTEXT_MODULE
2750  * @uses VISIBLEGROUPS
2751  * @param object $cm
2752  * @return array
2753  */
2754 function forum_get_discussions_unread($cm) {
2755     global $CFG, $DB, $USER;
2757     $now = round(time(), -2);
2758     $params = array($cutoffdate);
2759     $groupmode    = groups_get_activity_groupmode($cm);
2760     $currentgroup = groups_get_activity_group($cm);
2762     if ($groupmode) {
2763         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2765         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2766             if ($currentgroup) {
2767                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2768                 $params[] = $currentgroup;
2769             } else {
2770                 $groupselect = "";
2771             }
2773         } else {
2774             //seprate groups without access all
2775             if ($currentgroup) {
2776                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2777                 $params[] = $currentgroup;
2778             } else {
2779                 $groupselect = "AND d.groupid = -1";
2780             }
2781         }
2782     } else {
2783         $groupselect = "";
2784     }
2786     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2788     if (!empty($CFG->forum_enabletimedposts)) {
2789         $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2790         $params[] = $now;
2791         $params[] = $now;
2792     } else {
2793         $timedsql = "";
2794     }
2796     $sql = "SELECT d.id, COUNT(p.id) AS unread
2797               FROM {forum_discussions} d
2798                    JOIN {forum_posts} p     ON p.discussion = d.id
2799                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2800              WHERE d.forum = {$cm->instance}
2801                    AND p.modified >= ? AND r.id is NULL
2802                    $groupselect
2803                    $timedsql
2804           GROUP BY d.id";
2805     if ($unreads = $DB->get_records_sql($sql, $params)) {
2806         foreach ($unreads as $unread) {
2807             $unreads[$unread->id] = $unread->unread;
2808         }
2809         return $unreads;
2810     } else {
2811         return array();
2812     }
2815 /**
2816  * @global object
2817  * @global object
2818  * @global object
2819  * @uses CONEXT_MODULE
2820  * @uses VISIBLEGROUPS
2821  * @param object $cm
2822  * @return array
2823  */
2824 function forum_get_discussions_count($cm) {
2825     global $CFG, $DB, $USER;
2827     $now = round(time(), -2);
2828     $params = array($cm->instance);
2829     $groupmode    = groups_get_activity_groupmode($cm);
2830     $currentgroup = groups_get_activity_group($cm);
2832     if ($groupmode) {
2833         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2835         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2836             if ($currentgroup) {
2837                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2838                 $params[] = $currentgroup;
2839             } else {
2840                 $groupselect = "";
2841             }
2843         } else {
2844             //seprate groups without access all
2845             if ($currentgroup) {
2846                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2847                 $params[] = $currentgroup;
2848             } else {
2849                 $groupselect = "AND d.groupid = -1";
2850             }
2851         }
2852     } else {
2853         $groupselect = "";
2854     }
2856     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2858     $timelimit = "";
2860     if (!empty($CFG->forum_enabletimedposts)) {
2862         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2864         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2865             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2866             $params[] = $now;
2867             $params[] = $now;
2868             if (isloggedin()) {
2869                 $timelimit .= " OR d.userid = ?";
2870                 $params[] = $USER->id;
2871             }
2872             $timelimit .= ")";
2873         }
2874     }
2876     $sql = "SELECT COUNT(d.id)
2877               FROM {forum_discussions} d
2878                    JOIN {forum_posts} p ON p.discussion = d.id
2879              WHERE d.forum = ? AND p.parent = 0
2880                    $groupselect $timelimit";
2882     return $DB->get_field_sql($sql, $params);
2886 /**
2887  * Get all discussions started by a particular user in a course (or group)
2888  * This function no longer used ...
2889  *
2890  * @todo Remove this function if no longer used
2891  * @global object
2892  * @global object
2893  * @param int $courseid
2894  * @param int $userid
2895  * @param int $groupid
2896  * @return array
2897  */
2898 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2899     global $CFG, $DB;
2900     $params = array($courseid, $userid);
2901     if ($groupid) {
2902         $groupselect = " AND d.groupid = ? ";
2903         $params[] = $groupid;
2904     } else  {
2905         $groupselect = "";
2906     }
2908     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2909                                    f.type as forumtype, f.name as forumname, f.id as forumid
2910                               FROM {forum_discussions} d,
2911                                    {forum_posts} p,
2912                                    {user} u,
2913                                    {forum} f
2914                              WHERE d.course = ?
2915                                AND p.discussion = d.id
2916                                AND p.parent = 0
2917                                AND p.userid = u.id
2918                                AND u.id = ?
2919                                AND d.forum = f.id $groupselect
2920                           ORDER BY p.created DESC", $params);
2923 /**
2924  * Get the list of potential subscribers to a forum.
2925  *
2926  * @param object $forumcontext the forum context.
2927  * @param integer $groupid the id of a group, or 0 for all groups.
2928  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2929  * @param string $sort sort order. As for get_users_by_capability.
2930  * @return array list of users.
2931  */
2932 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2933     return get_users_by_capability($forumcontext, 'mod/forum:initialsubscriptions', $fields, $sort, '', '', $groupid, '', false, true);
2936 /**
2937  * Returns list of user objects that are subscribed to this forum
2938  *
2939  * @global object
2940  * @global object
2941  * @param object $course the course
2942  * @param forum $forum the forum
2943  * @param integer $groupid group id, or 0 for all.
2944  * @param object $context the forum context, to save re-fetching it where possible.
2945  * @return array list of users.
2946  */
2947 function forum_subscribed_users($course, $forum, $groupid=0, $context = NULL) {
2948     global $CFG, $DB;
2949     $params = array($forum->id);
2951     if ($groupid) {
2952         $grouptables = ", {groups_members} gm ";
2953         $groupselect = "AND gm.groupid = ? AND u.id = gm.userid";
2954         $params[] = $groupid;
2955     } else  {
2956         $grouptables = '';
2957         $groupselect = '';
2958     }
2960     $fields ="u.id,
2961               u.username,
2962               u.firstname,
2963               u.lastname,
2964               u.maildisplay,
2965               u.mailformat,
2966               u.maildigest,
2967               u.emailstop,
2968               u.imagealt,
2969               u.email,
2970               u.city,
2971               u.country,
2972               u.lastaccess,
2973               u.lastlogin,
2974               u.picture,
2975               u.timezone,
2976               u.theme,
2977               u.lang,
2978               u.trackforums,
2979               u.mnethostid";
2981     if (forum_is_forcesubscribed($forum)) {
2982         if (empty($context)) {
2983             $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2984             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2985         }
2986         $sort = "u.email ASC";
2987         $results = forum_get_potential_subscribers($context, $groupid, $fields, $sort);
2988     } else {
2989         $results = $DB->get_records_sql("SELECT $fields
2990                               FROM {user} u,
2991                                    {forum_subscriptions} s $grouptables
2992                              WHERE s.forum = ?
2993                                AND s.userid = u.id
2994                                AND u.deleted = 0  $groupselect
2995                           ORDER BY u.email ASC", $params);
2996     }
2998     static $guestid = null;
3000     if (is_null($guestid)) {
3001         if ($guest = guest_user()) {
3002             $guestid = $guest->id;
3003         } else {
3004             $guestid = 0;
3005         }
3006     }
3008     // Guest user should never be subscribed to a forum.
3009     unset($results[$guestid]);
3011     return $results;
3016 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
3019 /**
3020  * @global object
3021  * @global object
3022  * @param int $courseid
3023  * @param string $type
3024  */
3025 function forum_get_course_forum($courseid, $type) {
3026 // How to set up special 1-per-course forums
3027     global $CFG, $DB, $OUTPUT;
3029     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
3030         // There should always only be ONE, but with the right combination of
3031         // errors there might be more.  In this case, just return the oldest one (lowest ID).
3032         foreach ($forums as $forum) {
3033             return $forum;   // ie the first one
3034         }
3035     }
3037     // Doesn't exist, so create one now.
3038     $forum->course = $courseid;
3039     $forum->type = "$type";
3040     switch ($forum->type) {
3041         case "news":
3042             $forum->name  = get_string("namenews", "forum");
3043             $forum->intro = get_string("intronews", "forum");
3044             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
3045             $forum->assessed = 0;
3046             if ($courseid == SITEID) {
3047                 $forum->name  = get_string("sitenews");
3048                 $forum->forcesubscribe = 0;
3049             }
3050             break;
3051         case "social":
3052             $forum->name  = get_string("namesocial", "forum");
3053             $forum->intro = get_string("introsocial", "forum");
3054             $forum->assessed = 0;
3055             $forum->forcesubscribe = 0;
3056             break;
3057         case "blog":
3058             $forum->name = get_string('blogforum', 'forum');
3059             $forum->intro = get_string('introblog', 'forum');
3060             $forum->assessed = 0;
3061             $forum->forcesubscribe = 0;
3062             break;
3063         default:
3064             echo $OUTPUT->notification("That forum type doesn't exist!");
3065             return false;
3066             break;
3067     }
3069     $forum->timemodified = time();
3070     $forum->id = $DB->insert_record("forum", $forum);
3072     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
3073         echo $OUTPUT->notification("Could not find forum module!!");
3074         return false;
3075     }
3076     $mod = new object();
3077     $mod->course = $courseid;
3078     $mod->module = $module->id;
3079     $mod->instance = $forum->id;
3080     $mod->section = 0;
3081     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
3082         echo $OUTPUT->notification("Could not add a new course module to the course '" . format_string($course->fullname) . "'");
3083         return false;
3084     }
3085     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
3086         echo $OUTPUT->notification("Could not add the new course module to that section");
3087         return false;
3088     }
3089     if (! $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule))) {
3090         echo $OUTPUT->notification("Could not update the course module with the correct section");
3091         return false;
3092     }
3093     include_once("$CFG->dirroot/course/lib.php");
3094     rebuild_course_cache($courseid);
3096     return $DB->get_record("forum", array("id" => "$forum->id"));
3100 /**
3101  * Given the data about a posting, builds up the HTML to display it and
3102  * returns the HTML in a string.  This is designed for sending via HTML email.
3103  *
3104  * @global object
3105  * @param object $course
3106  * @param object $cm
3107  * @param object $forum
3108  * @param object $discussion
3109  * @param object $post
3110  * @param object $userform
3111  * @param object $userto
3112  * @param bool $ownpost
3113  * @param bool $reply
3114  * @param bool $link
3115  * @param bool $rate
3116  * @param string $footer
3117  * @return string
3118  */
3119 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
3120                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
3122     global $CFG, $OUTPUT;
3124     if (!isset($userto->viewfullnames[$forum->id])) {
3125         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
3126             print_error('invalidcoursemodule');
3127         }
3128         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3129         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3130     } else {
3131         $viewfullnames = $userto->viewfullnames[$forum->id];
3132     }
3134     // format the post body
3135     $options = new object();
3136     $options->para = true;
3137     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3139     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3141     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3142     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3143     $output .= '</td>';
3145     if ($post->parent) {
3146         $output .= '<td class="topic">';
3147     } else {
3148         $output .= '<td class="topic starter">';
3149     }
3150     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3152     $fullname = fullname($userfrom, $viewfullnames);
3153     $by = new object();
3154     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3155     $by->date = userdate($post->modified, '', $userto->timezone);
3156     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3158     $output .= '</td></tr>';
3160     $output .= '<tr><td class="left side" valign="top">';
3162     if (isset($userfrom->groups)) {
3163         $groups = $userfrom->groups[$forum->id];
3164     } else {
3165         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
3166             print_error('invalidcoursemodule');
3167         }
3168         $group = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3169     }
3171     if ($groups) {
3172         $output .= print_group_picture($groups, $course->id, false, true, true);
3173     } else {
3174         $output .= '&nbsp;';
3175     }
3177     $output .= '</td><td class="content">';
3179     $attachments = forum_print_attachments($post, $cm, 'html');
3180     if ($attachments !== '') {
3181         $output .= '<div class="attachments">';
3182         $output .= $attachments;
3183         $output .= '</div>';
3184     }
3186     $output .= $formattedtext;
3188 // Commands
3189     $commands = array();
3191     if ($post->parent) {
3192         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3193                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3194     }
3196     if ($reply) {
3197         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3198                       get_string('reply', 'forum').'</a>';
3199     }
3201     $output .= '<div class="commands">';
3202     $output .= implode(' | ', $commands);
3203     $output .= '</div>';
3205 // Context link to post if required
3206     if ($link) {
3207         $output .= '<div class="link">';
3208         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3209                      get_string('postincontext', 'forum').'</a>';
3210         $output .= '</div>';
3211     }
3213     if ($footer) {
3214         $output .= '<div class="footer">'.$footer.'</div>';
3215     }
3216     $output .= '</td></tr></table>'."\n\n";
3218     return $output;
3221 /**
3222  * Print a forum post
3223  *
3224  * @global object
3225  * @global object
3226  * @uses FORUM_MODE_THREADED
3227  * @uses PORTFOLIO_FORMAT_PLAINHTML
3228  * @uses PORTFOLIO_FORMAT_FILE
3229  * @uses PORTFOLIO_FORMAT_RICHHTML
3230  * @uses PORTFOLIO_ADD_TEXT_LINK
3231  * @uses CONTEXT_MODULE
3232  * @param object $post The post to print.
3233  * @param object $discussion
3234  * @param object $forum
3235  * @param object $cm
3236  * @param object $course
3237  * @param boolean $ownpost Whether this post belongs to the current user.
3238  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3239  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3240  * @param object $ratings -- I don't really know --
3241  * @param string $footer Extra stuff to print after the message.
3242  * @param string $highlight Space-separated list of terms to highlight.
3243  * @param int $post_read true, false or -99. If we already know whether this user
3244  *          has read this post, pass that in, otherwise, pass in -99, and this
3245  *          function will work it out.
3246  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3247  *          the current user can't see this post, if this argument is true
3248  *          (the default) then print a dummy 'you can't see this post' post.
3249  *          If false, don't output anything at all.
3250  * @param bool|null $istracked
3251  */
3252 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3253                           $ratings=NULL, $footer="", $highlight="", $post_read=null, $dummyifcantsee=true, $istracked=null) {
3255     global $USER, $CFG, $OUTPUT;
3257     static $stredit, $strdelete, $strreply, $strparent, $strprune;
3258     static $strpruneheading, $displaymode;
3259     static $strmarkread, $strmarkunread;
3261     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3263     $post->course = $course->id;
3264     $post->forum  = $forum->id;
3265     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'forum_post', $post->id);
3267     // caching
3268     if (!isset($cm->cache)) {
3269         $cm->cache = new object();
3270     }
3272     if (!isset($cm->cache->caps)) {
3273         $cm->cache->caps = array();
3274         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3275         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3276         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3277         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3278         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3279         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3280         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3281         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3282         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3283     }
3285     if (!isset($cm->uservisible)) {
3286         $cm->uservisible = coursemodule_visible_for_user($cm);
3287     }
3289     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3290         if (!$dummyifcantsee) {
3291             return;
3292         }
3293         echo '<a id="p'.$post->id.'"></a>';
3294         echo '<table cellspacing="0" class="forumpost">';
3295         echo '<tr class="header"><td class="picture left">';
3296         //        print_user_picture($post->userid, $courseid, $post->picture);
3297         echo '</td>';
3298         if ($post->parent) {
3299             echo '<td class="topic">';
3300         } else {
3301             echo '<td class="topic starter">';
3302         }
3303         echo '<div class="subject">'.get_string('forumsubjecthidden','forum').'</div>';
3304         echo '<div class="author">';
3305         print_string('forumauthorhidden','forum');
3306         echo '</div></td></tr>';
3308         echo '<tr><td class="left side">';
3309         echo '&nbsp;';
3311         // Actual content
3313         echo '</td><td class="content">'."\n";
3314         echo get_string('forumbodyhidden','forum');
3315         echo '</td></tr></table>';
3316         return;
3317     }
3319     if (empty($stredit)) {
3320         $stredit         = get_string('edit', 'forum');
3321         $strdelete       = get_string('delete', 'forum');
3322         $strreply        = get_string('reply', 'forum');
3323         $strparent       = get_string('parent', 'forum');
3324         $strpruneheading = get_string('pruneheading', 'forum');
3325         $strprune        = get_string('prune', 'forum');
3326         $displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3327         $strmarkread     = get_string('markread', 'forum');
3328         $strmarkunread   = get_string('markunread', 'forum');
3330     }
3332     $read_style = '';
3333     // ignore trackign status if not tracked or tracked param missing
3334     if ($istracked) {
3335         if (is_null($post_read)) {
3336             debugging('fetching post_read info');
3337             $post_read = forum_tp_is_post_read($USER->id, $post);
3338         }
3340         if ($post_read) {
3341             $read_style = ' read';
3342         } else {
3343             $read_style = ' unread';
3344             echo '<a name="unread"></a>';
3345         }
3346     }
3348     echo '<a id="p'.$post->id.'"></a>';
3349     echo '<table cellspacing="0" class="forumpost'.$read_style.'">';
3351     // Picture
3352     $postuser = new object();
3353     $postuser->id        = $post->userid;
3354     $postuser->firstname = $post->firstname;
3355     $postuser->lastname  = $post->lastname;
3356     $postuser->imagealt  = $post->imagealt;
3357     $postuser->picture   = $post->picture;
3359     echo '<tr class="header"><td class="picture left">';
3360     echo $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3361     echo '</td>';
3363     if ($post->parent) {
3364         echo '<td class="topic">';
3365     } else {
3366         echo '<td class="topic starter">';
3367     }
3369     if (!empty($post->subjectnoformat)) {
3370         echo '<div class="subject">'.$post->subject.'</div>';
3371     } else {
3372         echo '<div class="subject">'.format_string($post->subject).'</div>';
3373     }
3375     echo '<div class="author">';
3376     $fullname = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3377     $by = new object();
3378     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.
3379                 $post->userid.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3380     $by->date = userdate($post->modified);
3381     print_string('bynameondate', 'forum', $by);
3382     echo '</div></td></tr>';
3384     echo '<tr><td class="left side">';
3385     if (isset($cm->cache->usersgroups)) {
3386         $groups = array();
3387         if (isset($cm->cache->usersgroups[$post->userid])) {
3388             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3389                 $groups[$gid] = $cm->cache->groups[$gid];
3390             }
3391         }
3392     } else {
3393         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3394     }
3396     if ($groups) {
3397         print_group_picture($groups, $course->id, false, false, true);
3398     } else {
3399         echo '&nbsp;';
3400     }
3402 // Actual content
3404     echo '</td><td class="content">'."\n";
3406     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3408     if ($attachments !== '') {
3409         echo '<div class="attachments">';
3410         echo $attachments;
3411         echo '</div>';
3412     }
3414     $options = new object();
3415     $options->para    = false;
3416     $options->trusted = $post->messagetrust;
3417     if ($link and (strlen(strip_tags($post->message)) > $CFG->forum_longpost)) {
3418         // Print shortened version
3419         echo format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3420         $numwords = count_words(strip_tags($post->message));
3421         echo '<div class="posting"><a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3422         echo get_string('readtherest', 'forum');
3423         echo '</a> ('.get_string('numwords', '', $numwords).')...</div>';
3424     } else {
3425         // Print whole message
3426         echo '<div class="posting">';
3427         if ($highlight) {
3428             echo highlight($highlight, format_text($post->message, $post->messageformat, $options, $course->id));
3429         } else {
3430             echo format_text($post->message, $post->messageformat, $options, $course->id);
3431         }
3432         echo '</div>';
3433         echo $attachedimages;
3434     }
3437 // Commands
3439     $commands = array();
3441     if ($istracked) {
3442         // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3443         // Don't display the mark read / unread controls in this case.
3444         if ($CFG->forum_usermarksread and isloggedin()) {
3445             if ($post_read) {
3446                 $mcmd = '&amp;mark=unread&amp;postid='.$post->id;
3447                 $mtxt = $strmarkunread;
3448             } else {
3449                 $mcmd = '&amp;mark=read&amp;postid='.$post->id;
3450                 $mtxt = $strmarkread;
3451             }
3452             if ($displaymode == FORUM_MODE_THREADED) {
3453                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3454                               $post->discussion.'&amp;parent='.$post->id.$mcmd.'">'.$mtxt.'</a>';
3455             } else {
3456                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3457                               $post->discussion.$mcmd.'#p'.$post->id.'">'.$mtxt.'</a>';
3458             }
3459         }
3460     }
3462     if ($post->parent) {  // Zoom in to the parent specifically
3463         if ($displaymode == FORUM_MODE_THREADED) {
3464             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3465                       $post->discussion.'&amp;parent='.$post->parent.'">'.$strparent.'</a>';
3466         } else {
3467             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3468                       $post->discussion.'#p'.$post->parent.'">'.$strparent.'</a>';
3469         }
3470     }
3472     $age = time() - $post->created;
3473     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3474     if (!$post->parent and $forum->type == 'news' and $discussion->timestart > time()) {
3475         $age = 0;
3476     }
3477     $editanypost = $cm->cache->caps['mod/forum:editanypost'];
3479     if ($ownpost or $editanypost) {
3480         if (($age < $CFG->maxeditingtime) or $editanypost) {
3481             $commands[] =  '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?edit='.$post->id.'">'.$stredit.'</a>';
3482         }
3483     }
3485     if ($cm->cache->caps['mod/forum:splitdiscussions']
3486                 && $post->parent && $forum->type != 'single') {
3488         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?prune='.$post->id.
3489                       '" title="'.$strpruneheading.'">'.$strprune.'</a>';
3490     }
3492     if (($ownpost and $age < $CFG->maxeditingtime
3493                 and $cm->cache->caps['mod/forum:deleteownpost'])
3494                 or $cm->cache->caps['mod/forum:deleteanypost']) {
3495         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?delete='.$post->id.'">'.$strdelete.'</a>';
3496     }
3498     if ($reply) {
3499         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.$strreply.'</a>';
3500     }
3502     if ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost'])) {
3503         $p = array(
3504             'postid' => $post->id,
3505         );
3506         require_once($CFG->libdir.'/portfoliolib.php');
3507         $button = new portfolio_add_button();
3508         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php');
3509         if (empty($attachments)) {
3510             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3511         } else {
3512             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3513         }
3515         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3516         if(!empty($porfoliohtml)){
3517             $commands[] = $porfoliohtml;
3518         }
3519     }
3521     echo '<div class="commands">';
3522     echo implode(' | ', $commands);
3523     echo '</div>';
3526 // Ratings
3528     $ratingsmenuused = false;
3529     if (!empty($ratings) and isloggedin()) {
3530         echo '<div class="ratings">';
3531         $useratings = true;
3532         if ($ratings->assesstimestart and $ratings->assesstimefinish) {
3533             if ($post->created < $ratings->assesstimestart or $post->created > $ratings->assesstimefinish) {
3534                 $useratings = false;
3535             }
3536         }
3537         if ($useratings) {
3538             $mypost = ($USER->id == $post->userid);
3540             $canviewallratings = $cm->cache->caps['mod/forum:viewanyrating'];
3542             if (isset($cm->cache->ratings)) {
3543                 if (isset($cm->cache->ratings[$post->id])) {
3544                     $allratings = $cm->cache->ratings[$post->id];
3545                 } else {
3546                     $allratings = array(); // no reatings present yet
3547                 }
3548             } else {
3549                 $allratings = NULL; // not preloaded
3550             }
3552             if (isset($cm->cache->myratings)) {
3553                 if (isset($cm->cache->myratings[$post->id])) {
3554                     $myrating = $cm->cache->myratings[$post->id];
3555                 } else {
3556                     $myrating = FORUM_UNSET_POST_RATING; // no reatings present yet
3557                 }
3558             } else {
3559                 $myrating = NULL; // not preloaded
3560             }
3562             if ($canviewallratings and !$mypost) {
3563                 echo '<span class="forumpostratingtext">' .
3564                      forum_print_ratings($post->id, $ratings->scale, $forum->assessed, $canviewallratings, $allratings, true) .
3565                      '</span>';
3566                 if (!empty($ratings->allow)) {
3567                     echo '&nbsp;';
3568                     forum_print_rating_menu($post->id, $USER->id, $ratings->scale, $myrating);
3569                     $ratingsmenuused = true;
3570                 }
3572             } else if ($mypost) {
3573                 echo '<span class="forumpostratingtext">' .
3574                      forum_print_ratings($post->id, $ratings->scale, $forum->assessed, true, $allratings, true) .
3575                      '</span>';
3577             } else if (!empty($ratings->allow) ) {
3578                 forum_print_rating_menu($post->id, $USER->id, $ratings->scale, $myrating);
3579                 $ratingsmenuused = true;
3580             }
3581         }
3582         echo '</div>';
3583     }
3585 // Link to post if required
3587     if ($link) {
3588         echo '<div class="link">';
3589         if ($post->replies == 1) {
3590             $replystring = get_string('repliesone', 'forum', $post->replies);
3591         } else {
3592             $replystring = get_string('repliesmany', 'forum', $post->replies);
3593         }
3594         echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.
3595              get_string('discussthistopic', 'forum').'</a>&nbsp;('.$replystring.')';
3596         echo '</div>';
3597     }
3599     if ($footer) {
3600         echo '<div class="footer">'.$footer.'</div>';
3601     }
3602     echo '</td></tr></table>'."\n\n";
3604     if ($istracked && !$CFG->forum_usermarksread && !$post_read) {
3605         forum_tp_mark_post_read($USER->id, $post, $forum->id);
3606     }
3608     return $ratingsmenuused;
3612 /**
3613  * This function prints the overview of a discussion in the forum listing.
3614  * It needs some discussion information and some post information, these
3615  * happen to be combined for efficiency in the $post parameter by the function
3616  * that calls this one: forum_print_latest_discussions()
3617  *
3618  * @global object
3619  * @global object
3620  * @param object $post The post object (passed by reference for speed).
3621  * @param object $forum The forum object.
3622  * @param int $group Current group.
3623  * @param string $datestring Format to use for the dates.
3624  * @param boolean $cantrack Is tracking enabled for this forum.
3625  * @param boolean $forumtracked Is the user tracking this forum.
3626  * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
3627  */
3628 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
3629                                         $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) {
3631     global $USER, $CFG, $OUTPUT;
3633     static $rowcount;
3634     static $strmarkalldread;
3636     if (empty($modcontext)) {
3637         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3638             print_error('invalidcoursemodule');
3639         }
3640         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3641     }
3643     if (!isset($rowcount)) {
3644         $rowcount = 0;
3645         $strmarkalldread = get_string('markalldread', 'forum');
3646     } else {
3647         $rowcount = ($rowcount + 1) % 2;
3648     }
3650     $post->subject = format_string($post->subject,true);
3652     echo "\n\n";
3653     echo '<tr class="discussion r'.$rowcount.'">';
3655     // Topic
3656     echo '<td class="topic starter">';
3657     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.$post->subject.'</a>';
3658     echo "</td>\n";
3660     // Picture
3661     $postuser = new object;
3662     $postuser->id = $post->userid;
3663     $postuser->firstname = $post->firstname;
3664     $postuser->lastname = $post->lastname;
3665     $postuser->imagealt = $post->imagealt;
3666     $postuser->picture = $post->picture;
3668     echo '<td class="picture">';
3669     echo $OUTPUT->user_picture($postuser, array('courseid'=>$forum->course));
3670     echo "</td>\n";
3672     // User name
3673     $fullname = fullname($post, has_capability('moodle/site:viewfullnames', $modcontext));
3674     echo '<td class="author">';
3675     echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->userid.'&amp;course='.$forum->course.'">'.$fullname.'</a>';
3676     echo "</td>\n";
3678     // Group picture
3679     if ($group !== -1) {  // Groups are active - group is a group data object or NULL
3680         echo '<td class="picture group">';
3681         if (!empty($group->picture) and empty($group->hidepicture)) {
3682             print_group_picture($group, $forum->course, false, false, true);
3683         } else if (isset($group->id)) {
3684             if($canviewparticipants) {
3685                 echo '<a href="'.$CFG->wwwroot.'/user/index.php?id='.$forum->course.'&amp;group='.$group->id.'">'.$group->name.'</a>';
3686             } else {
3687                 echo $group->name;
3688             }
3689         }
3690         echo "</td>\n";
3691     }
3693     if (has_capability('mod/forum:viewdiscussion', $modcontext)) {   // Show the column with replies
3694         echo '<td class="replies">';
3695         echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3696         echo $post->replies.'</a>';
3697         echo "</td>\n";
3699         if ($cantrack) {
3700             echo '<td class="replies">';
3701             if ($forumtracked) {
3702                 if ($post->unread > 0) {
3703                     echo '<span class="unread">';
3704                     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#unread">';
3705                     echo $post->unread;
3706                     echo '</a>';
3707                     echo '<a title="'.$strmarkalldread.'" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
3708                          $forum->id.'&amp;d='.$post->discussion.'&amp;mark=read&amp;returnpage=view.php">' .
3709                          '<img src="'.$OUTPUT->pix_url('t/clear') . '" class="iconsmall" alt="'.$strmarkalldread.'" /></a>';
3710                     echo '</span>';
3711                 } else {
3712                     echo '<span class="read">';
3713                     echo $post->unread;
3714                     echo '</span>';
3715                 }
3716             } else {
3717                 echo '<span class="read">';
3718                 echo '-';
3719                 echo '</span>';
3720             }
3721             echo "</td>\n";
3722         }
3723     }
3725     echo '<td class="lastpost">';
3726     $usedate = (empty($post->timemodified)) ? $post->modified : $post->timemodified;  // Just in case
3727     $parenturl = (empty($post->lastpostid)) ? '' : '&amp;parent='.$post->lastpostid;
3728     $usermodified = new object();
3729     $usermodified->id        = $post->usermodified;
3730     $usermodified->firstname = $post->umfirstname;
3731     $usermodified->lastname  = $post->umlastname;
3732     echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->usermodified.'&amp;course='.$forum->course.'">'.
3733          fullname($usermodified).'</a><br />';
3734     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.$parenturl.'">'.
3735           userdate($usedate, $datestring).'</a>';
3736     echo "</td>\n";
3738     echo "</tr>\n\n";
3743 /**
3744  * Given a post object that we already know has a long message
3745  * this function truncates the message nicely to the first
3746  * sane place between $CFG->forum_longpost and $CFG->forum_shortpost
3747  *
3748  * @global object
3749  * @param string $message
3750  * @return string
3751  */
3752 function forum_shorten_post($message) {
3754    global $CFG;
3756    $i = 0;
3757    $tag = false;
3758    $length = strlen($message);
3759    $count = 0;
3760    $stopzone = false;
3761    $truncate = 0;
3763    for ($i=0; $i<$length; $i++) {
3764        $char = $message[$i];
3766        switch ($char) {
3767            case "<":
3768                $tag = true;
3769                break;
3770            case ">":
3771                $tag = false;
3772                break;
3773            default:
3774                if (!$tag) {
3775                    if ($stopzone) {
3776                        if ($char == ".") {
3777                            $truncate = $i+1;
3778                            break 2;
3779                        }
3780                    }
3781                    $count++;
3782                }
3783                break;
3784        }
3785        if (!$stopzone) {
3786            if ($count > $CFG->forum_shortpost) {
3787                $stopzone = true;
3788            }
3789        }
3790    }
3792    if (!$truncate) {
3793        $truncate = $i;
3794    }
3796    return substr($message, 0, $truncate);
3800 /**
3801  * Print the multiple ratings on a post given to the current user by others.
3802  * Forumid prevents the double lookup of the forumid in discussion to determine the aggregate type
3803  * Scale is an array of ratings
3804  *
3805  * @uses FORUM_AGGREGATE_AVG
3806  * @uses FORUM_AGGREGATE_COUNT
3807  * @uses FORUM_AGGREGATE_MAX
3808  * @uses FORUM_AGGREGATE_MIN
3809  * @uses FORUM_AGGREGATE_SUM
3810  * @param int $postid
3811  * @param array $scale
3812  * @param int $aggregatetype
3813  * @param bool $link
3814  * @param array $ratings
3815  * @param bool $return
3816  * @return string|void
3817  */
3818 function forum_print_ratings($postid, $scale, $aggregatetype, $link=true, $ratings=null, $return=false) {
3819     global $OUTPUT;
3820     $strratings = '';
3822     switch ($aggregatetype) {
3823         case FORUM_AGGREGATE_AVG :
3824             $agg        = forum_get_ratings_mean($postid, $scale, $ratings);
3825             $strratings = get_string("aggregateavg", "forum");
3826             break;
3827         case FORUM_AGGREGATE_COUNT :
3828             $agg        = forum_get_ratings_count($postid, $scale, $ratings);
3829             $strratings = get_string("aggregatecount", "forum");
3830             break;
3831         case FORUM_AGGREGATE_MAX :
3832             $agg        = forum_get_ratings_max($postid, $scale, $ratings);
3833             $strratings = get_string("aggregatemax", "forum");
3834             break;
3835         case FORUM_AGGREGATE_MIN :
3836             $agg        = forum_get_ratings_min($postid, $scale, $ratings);
3837             $strratings = get_string("aggregatemin", "forum");
3838             break;
3839         case FORUM_AGGREGATE_SUM :
3840             $agg        = forum_get_ratings_sum($postid, $scale, $ratings);
3841             $strratings = get_string("aggregatesum", "forum");
3842             break;
3843     }
3845     if ($agg !== "") {
3847         if (empty($strratings)) {
3848             $strratings = get_string("ratings", "forum");
3849         }
3851         $strratings .= ': ';
3853         if ($link) {
3855             $link = new moodle_url("/mod/forum/report.php?id=$postid");
3856             $action = new popup_action('click', $link, 'ratings', array('height' => 400, 'width' => 600));
3857             $strratings .= $OUTPUT->action_link($link, $agg, $action);
3858         } else {
3859             $strratings .= "$agg ";
3860         }
3862         if ($return) {
3863             return $strratings;
3864         } else {
3865             echo $strratings;
3866         }
3867     }
3871 /**
3872  * Return the mean rating of a post given to the current user by others.
3873  *
3874  * @global object
3875  * @param int $postid
3876  * @param array $scal Scale is an array of possible ratings in the scale
3877  * @param array $ratings Ratings is an optional simple array of actual ratings (just integers)
3878  * @return string
3879  */
3880 function forum_get_ratings_mean($postid, $scale, $ratings=NULL) {
3881     global $DB;
3882     if (is_null($ratings)) {
3883         $ratings = array();
3884         if ($rates = $DB->get_records("forum_ratings", array("post" => $postid))) {
3885             foreach ($rates as $rate) {
3886                 $ratings[] = $rate->rating;
3887             }
3888         }
3889     }
3891     $count = count($ratings);
3893     if ($count == 0 ) {
3894         return "";
3896     } else if ($count == 1) {
3897         $rating = reset($ratings);
3898         return $scale[$rating];
3900     } else {
3901         $total = 0;
3902         foreach ($ratings as $rating) {
3903             $total += $rating;
3904         }
3905         $mean = round( ((float)$total/(float)$count) + 0.001);  // Little fudge factor so that 0.5 goes UP
3907         if (isset($scale[$mean])) {
3908             return $scale[$mean]." ($count)";
3909         } else {
3910             return "$mean ($count)";    // Should never happen, hopefully
3911         }
3912     }
3915 /**
3916  * Return the count of the ratings of a post given to the current user by others.
3917  *
3918  * For numerical grades, the scale index is the same as the real grade value from interval {0..n}
3919  * and $scale looks like Array( 0 => '0/n', 1 => '1/n', ..., n => 'n/n' )
3920  *
3921  * For scales, the index is the order of the scale item {1..n}
3922  * and $scale looks like Array( 1 => 'poor', 2 => 'weak', 3 => 'good' )
3923  * In case of no ratings done yet, we have nothing to display.
3924  *
3925  * @global object
3926  * @param int $postid
3927  * @param array $scale Possible ratings in the scale - the end of the scale is the highest or max grade
3928  * @param array $ratings An optional simple array of actual ratings (just integers)
3929  * @return string
3930  */
3931 function forum_get_ratings_count($postid, $scale, $ratings=NULL) {
3932     global $DB;
3933     if (is_null($ratings)) {
3934         $ratings = array();
3935         if ($rates = $DB->get_records("forum_ratings", array("post" => $postid))) {
3936             foreach ($rates as $rate) {
3937                 $ratings[] = $rate->rating;
3938             }
3939         }
3940     }
3942     $count = count($ratings);
3943     if (! array_key_exists(0, $scale)) {
3944         $scaleused = true;
3945     } else {
3946         $scaleused = false;
3947     }
3949     if ($count == 0) {
3950         if ($scaleused) {    // If no rating given yet and we use a scale
3951             return get_string('noratinggiven', 'forum');
3952         } else {
3953             return '';
3954         }
3955     }
3957     $maxgradeidx = max(array_keys($scale)); // For numerical grades, the index is the same as the real grade value {0..n}
3958                                             // and $scale looks like Array( 0 => '0/n', 1 => '1/n', ..., n => 'n/n' )
3959                                             // For scales, the index is the order of the scale item {1..n}
3960                                             // and $scale looks like Array( 1 => 'poor', 2 => 'weak', 3 => 'good' )
3962     if ($count > $maxgradeidx) {      // The count exceeds the max grade
3963         $a = new stdClass();
3964         $a->count = $count;
3965         $a->grade = $scale[$maxgradeidx];
3966         return get_string('aggregatecountformat', 'forum', $a);
3967     } else {                                // Display the count and the aggregated grade for this post
3968         $a = new stdClass();
3969         $a->count = $count;
3970         $a->grade = $scale[$count];
3971         return get_string('aggregatecountformat', 'forum', $a);
3972     }
3975 /**
3976  * Return the max rating of a post given to the current user by others.
3977  *
3978  * @global object
3979  * @param int $postid
3980  * @param array $scale Scale is an array of possible ratings in the scale
3981  * @param array $ratings Ratings is an optional simple array of actual ratings (just integers)
3982  */
3983 function forum_get_ratings_max($postid, $scale, $ratings=NULL) {
3985     global $DB;
3986     if (is_null($ratings)) {
3987         $ratings = array();
3988         if ($rates = $DB->get_records("forum_ratings", array("post" => $postid))) {
3989             foreach ($rates as $rate) {
3990                 $ratings[] = $rate->rating;
3991             }
3992         }
3993     }
3995     $count = count($ratings);
3997     if ($count == 0 ) {
3998         return "";
4000     } else if ($count == 1) { //this works for max
4001         $rating = reset($ratings);
4002         return $scale[$rating];
4004     } else {
4005         $max = max($ratings);
4007         if (isset($scale[$max])) {
4008             return $scale[$max]." ($count)";
4009         } else {
4010             return "$max ($count)";    // Should never happen, hopefully
4011         }
4012     }
4015 /**
4016  * Return the min rating of a post given to the current user by others.
4017  *
4018  * @global object
4019  * @param int postid
4020  * @param array $scale Scale is an array of possible ratings in the scale
4021  * @param array $ratings  Ratings is an optional simple array of actual ratings (just integers)
4022  * @return string
4023  */
4024 function forum_get_ratings_min($postid, $scale,  $ratings=NULL) {
4025     global $DB;
4026     if (is_null($ratings)) {
4027         $ratings = array();
4028         if ($rates = $DB->get_records("forum_ratings", array("post" => $postid))) {
4029             foreach ($rates as $rate) {
4030                 $ratings[] = $rate->rating;
4031             }
4032         }
4033     }
4035     $count = count($ratings);
4037     if ($count == 0 ) {
4038         return "";
4040     } else if ($count == 1) {
4041         $rating = reset($ratings);
4042         return $scale[$rating]; //this works for min
4044     } else {
4045         $min = min($ratings);
4047         if (isset($scale[$min])) {
4048             return $scale[$min]." ($count)";
4049         } else {
4050             return "$min ($count)";    // Should never happen, hopefully
4051         }
4052     }
4056 /**
4057  * Return the sum or total of ratings of a post given to the current user by others.
4058  *
4059  * @global object
4060  * @param int $postid
4061  * @param array $scale Scale is an array of possible ratings in the scale
4062  * @param array $ratings Ratings is an optional simple array of actual ratings (just integers)
4063  * @return string
4064  */
4065 function forum_get_ratings_sum($postid, $scale, $ratings=NULL) {
4066     global $DB;
4067     if (is_null($ratings)) {
4068         $ratings = array();
4069         if ($rates = $DB->get_records("forum_ratings", array("post" => $postid))) {
4070             foreach ($rates as $rate) {
4071                 $ratings[] = $rate->rating;
4072             }
4073         }
4074     }
4076     $count = count($ratings);
4077     $scalecount = count($scale)-1; //this should give us the last element of the scale aka the max grade with  $scale[$scalecount]
4079     if ($count == 0 ) {
4080         return "";
4082     } else if ($count == 1) { //this works for max.
4083         $rating = reset($ratings);
4084         return $scale[$rating];
4086     } else {
4087         $total = 0;
4088         foreach ($ratings as $rating) {
4089             $total += $rating;
4090         }
4091         if ($total > $scale[$scalecount]) { //if the total exceeds the max grade then set it to the max grade
4092             $total = $scale[$scalecount];
4093         }
4094         if (isset($scale[$total])) {
4095             return $scale[$total]." ($count)";
4096         } else {
4097             return "$total ($count)";    // Should never happen, hopefully
4098         }
4099     }
4102 /**
4103  * Return a summary of post ratings given to the current user by others.
4104  *
4105  * @global object
4106  * @param int $postid
4107  * @param array $scale Scale is an array of possible ratings in the scale
4108  * @param array $ratings Ratings is an optional simple array of actual ratings (just integers)
4109  * @return string
4110  */
4111 function forum_get_ratings_summary($postid, $scale, $ratings=NULL) {
4112     global $DB;
4113     if (is_null($ratings)) {
4114         $ratings = array();
4115         if ($rates = $DB->get_records("forum_ratings", array("post" => $postid))) {
4116             foreach ($rates as $rate) {
4117