message MDL-24563 altered messaging so that forum post notifications aren't so overwh...
[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 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
48 /**
49  * Given an object containing all the necessary data,
50  * (defined by the form in mod_form.php) this function
51  * will create a new instance and return the id number
52  * of the new instance.
53  *
54  * @global object
55  * @global object
56  * @param object $forum add forum instance (with magic quotes)
57  * @return int intance id
58  */
59 function forum_add_instance($forum, $mform) {
60     global $CFG, $DB;
62     $forum->timemodified = time();
64     if (empty($forum->assessed)) {
65         $forum->assessed = 0;
66     }
68     if (empty($forum->ratingtime) or empty($forum->assessed)) {
69         $forum->assesstimestart  = 0;
70         $forum->assesstimefinish = 0;
71     }
73     $forum->id = $DB->insert_record('forum', $forum);
74     $modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule);
76     if ($forum->type == 'single') {  // Create related discussion.
77         $discussion = new stdClass();
78         $discussion->course        = $forum->course;
79         $discussion->forum         = $forum->id;
80         $discussion->name          = $forum->name;
81         $discussion->assessed      = $forum->assessed;
82         $discussion->message       = $forum->intro;
83         $discussion->messageformat = $forum->introformat;
84         $discussion->messagetrust  = trusttext_trusted(get_context_instance(CONTEXT_COURSE, $forum->course));
85         $discussion->mailnow       = false;
86         $discussion->groupid       = -1;
88         $message = '';
90         $discussion->id = forum_add_discussion($discussion, null, $message);
92         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
93             // ugly hack - we need to copy the files somehow
94             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
95             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
97             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
98             $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
99         }
100     }
102     if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
103     /// all users should be subscribed initially
104     /// Note: forum_get_potential_subscribers should take the forum context,
105     /// but that does not exist yet, becuase the forum is only half build at this
106     /// stage. However, because the forum is brand new, we know that there are
107     /// no role assignments or overrides in the forum context, so using the
108     /// course context gives the same list of users.
109         $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
110         foreach ($users as $user) {
111             forum_subscribe($user->id, $forum->id);
112         }
113     }
115     forum_grade_item_update($forum);
117     return $forum->id;
121 /**
122  * Given an object containing all the necessary data,
123  * (defined by the form in mod_form.php) this function
124  * will update an existing instance with new data.
125  *
126  * @global object
127  * @param object $forum forum instance (with magic quotes)
128  * @return bool success
129  */
130 function forum_update_instance($forum, $mform) {
131     global $DB, $OUTPUT, $USER;
133     $forum->timemodified = time();
134     $forum->id           = $forum->instance;
136     if (empty($forum->assessed)) {
137         $forum->assessed = 0;
138     }
140     if (empty($forum->ratingtime) or empty($forum->assessed)) {
141         $forum->assesstimestart  = 0;
142         $forum->assesstimefinish = 0;
143     }
145     $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
147     // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
148     // if  scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
149     // 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
150     if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
151         forum_update_grades($forum); // recalculate grades for the forum
152     }
154     if ($forum->type == 'single') {  // Update related discussion and post.
155         if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
156             if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC')) {
157                 echo $OUTPUT->notification('Warning! There is more than one discussion in this forum - using the most recent');
158                 $discussion = array_pop($discussions);
159             } else {
160                 // try to recover by creating initial discussion - MDL-16262
161                 $discussion = new stdClass();
162                 $discussion->course          = $forum->course;
163                 $discussion->forum           = $forum->id;
164                 $discussion->name            = $forum->name;
165                 $discussion->assessed        = $forum->assessed;
166                 $discussion->message         = $forum->intro;
167                 $discussion->messageformat   = $forum->introformat;
168                 $discussion->messagetrust    = true;
169                 $discussion->mailnow         = false;
170                 $discussion->groupid         = -1;
172                 $message = '';
174                 forum_add_discussion($discussion, null, $message);
176                 if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
177                     print_error('cannotadd', 'forum');
178                 }
179             }
180         }
181         if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
182             print_error('cannotfindfirstpost', 'forum');
183         }
185         $cm         = get_coursemodule_from_instance('forum', $forum->id);
186         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
188         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
189             // ugly hack - we need to copy the files somehow
190             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
191             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
193             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
194         }
196         $post->subject       = $forum->name;
197         $post->message       = $forum->intro;
198         $post->messageformat = $forum->introformat;
199         $post->messagetrust  = trusttext_trusted($modcontext);
200         $post->modified      = $forum->timemodified;
201         $post->userid        = $USER->id;    // MDL-18599, so that current teacher can take ownership of activities
203         $DB->update_record('forum_posts', $post);
204         $discussion->name = $forum->name;
205         $DB->update_record('forum_discussions', $discussion);
206     }
208     $DB->update_record('forum', $forum);
210     forum_grade_item_update($forum);
212     return true;
216 /**
217  * Given an ID of an instance of this module,
218  * this function will permanently delete the instance
219  * and any data that depends on it.
220  *
221  * @global object
222  * @param int $id forum instance id
223  * @return bool success
224  */
225 function forum_delete_instance($id) {
226     global $DB;
228     if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
229         return false;
230     }
231     if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
232         return false;
233     }
234     if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
235         return false;
236     }
238     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
240     // now get rid of all files
241     $fs = get_file_storage();
242     $fs->delete_area_files($context->id);
244     $result = true;
246     if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
247         foreach ($discussions as $discussion) {
248             if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
249                 $result = false;
250             }
251         }
252     }
254     if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
255         $result = false;
256     }
258     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
260     if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
261         $result = false;
262     }
264     forum_grade_item_delete($forum);
266     return $result;
270 /**
271  * Indicates API features that the forum supports.
272  *
273  * @uses FEATURE_GROUPS
274  * @uses FEATURE_GROUPINGS
275  * @uses FEATURE_GROUPMEMBERSONLY
276  * @uses FEATURE_MOD_INTRO
277  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
278  * @uses FEATURE_COMPLETION_HAS_RULES
279  * @uses FEATURE_GRADE_HAS_GRADE
280  * @uses FEATURE_GRADE_OUTCOMES
281  * @param string $feature
282  * @return mixed True if yes (some features may use other values)
283  */
284 function forum_supports($feature) {
285     switch($feature) {
286         case FEATURE_GROUPS:                  return true;
287         case FEATURE_GROUPINGS:               return true;
288         case FEATURE_GROUPMEMBERSONLY:        return true;
289         case FEATURE_MOD_INTRO:               return true;
290         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
291         case FEATURE_COMPLETION_HAS_RULES:    return true;
292         case FEATURE_GRADE_HAS_GRADE:         return true;
293         case FEATURE_GRADE_OUTCOMES:          return true;
294         case FEATURE_RATE:                    return true;
295         case FEATURE_BACKUP_MOODLE2:          return true;
297         default: return null;
298     }
302 /**
303  * Obtains the automatic completion state for this forum based on any conditions
304  * in forum settings.
305  *
306  * @global object
307  * @global object
308  * @param object $course Course
309  * @param object $cm Course-module
310  * @param int $userid User ID
311  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
312  * @return bool True if completed, false if not. (If no conditions, then return
313  *   value depends on comparison type)
314  */
315 function forum_get_completion_state($course,$cm,$userid,$type) {
316     global $CFG,$DB;
318     // Get forum details
319     if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
320         throw new Exception("Can't find forum {$cm->instance}");
321     }
323     $result=$type; // Default return value
325     $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
326     $postcountsql="
327 SELECT
328     COUNT(1)
329 FROM
330     {forum_posts} fp
331     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
332 WHERE
333     fp.userid=:userid AND fd.forum=:forumid";
335     if ($forum->completiondiscussions) {
336         $value = $forum->completiondiscussions <=
337                  $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
338         if ($type == COMPLETION_AND) {
339             $result = $result && $value;
340         } else {
341             $result = $result || $value;
342         }
343     }
344     if ($forum->completionreplies) {
345         $value = $forum->completionreplies <=
346                  $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
347         if ($type==COMPLETION_AND) {
348             $result = $result && $value;
349         } else {
350             $result = $result || $value;
351         }
352     }
353     if ($forum->completionposts) {
354         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
355         if ($type == COMPLETION_AND) {
356             $result = $result && $value;
357         } else {
358             $result = $result || $value;
359         }
360     }
362     return $result;
366 /**
367  * Function to be run periodically according to the moodle cron
368  * Finds all posts that have yet to be mailed out, and mails them
369  * out to all subscribers
370  *
371  * @global object
372  * @global object
373  * @global object
374  * @uses CONTEXT_MODULE
375  * @uses CONTEXT_COURSE
376  * @uses SITEID
377  * @uses FORMAT_PLAIN
378  * @return void
379  */
380 function forum_cron() {
381     global $CFG, $USER, $DB;
383     $site = get_site();
385     // all users that are subscribed to any post that needs sending
386     $users = array();
388     // status arrays
389     $mailcount  = array();
390     $errorcount = array();
392     // caches
393     $discussions     = array();
394     $forums          = array();
395     $courses         = array();
396     $coursemodules   = array();
397     $subscribedusers = array();
400     // Posts older than 2 days will not be mailed.  This is to avoid the problem where
401     // cron has not been running for a long time, and then suddenly people are flooded
402     // with mail from the past few weeks or months
403     $timenow   = time();
404     $endtime   = $timenow - $CFG->maxeditingtime;
405     $starttime = $endtime - 48 * 3600;   // Two days earlier
407     if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
408         // Mark them all now as being mailed.  It's unlikely but possible there
409         // might be an error later so that a post is NOT actually mailed out,
410         // but since mail isn't crucial, we can accept this risk.  Doing it now
411         // prevents the risk of duplicated mails, which is a worse problem.
413         if (!forum_mark_old_posts_as_mailed($endtime)) {
414             mtrace('Errors occurred while trying to mark some posts as being mailed.');
415             return false;  // Don't continue trying to mail them, in case we are in a cron loop
416         }
418         // checking post validity, and adding users to loop through later
419         foreach ($posts as $pid => $post) {
421             $discussionid = $post->discussion;
422             if (!isset($discussions[$discussionid])) {
423                 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
424                     $discussions[$discussionid] = $discussion;
425                 } else {
426                     mtrace('Could not find discussion '.$discussionid);
427                     unset($posts[$pid]);
428                     continue;
429                 }
430             }
431             $forumid = $discussions[$discussionid]->forum;
432             if (!isset($forums[$forumid])) {
433                 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
434                     $forums[$forumid] = $forum;
435                 } else {
436                     mtrace('Could not find forum '.$forumid);
437                     unset($posts[$pid]);
438                     continue;
439                 }
440             }
441             $courseid = $forums[$forumid]->course;
442             if (!isset($courses[$courseid])) {
443                 if ($course = $DB->get_record('course', array('id' => $courseid))) {
444                     $courses[$courseid] = $course;
445                 } else {
446                     mtrace('Could not find course '.$courseid);
447                     unset($posts[$pid]);
448                     continue;
449                 }
450             }
451             if (!isset($coursemodules[$forumid])) {
452                 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
453                     $coursemodules[$forumid] = $cm;
454                 } else {
455                     mtrace('Could not course module for forum '.$forumid);
456                     unset($posts[$pid]);
457                     continue;
458                 }
459             }
462             // caching subscribed users of each forum
463             if (!isset($subscribedusers[$forumid])) {
464                 $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
465                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext)) {
466                     foreach ($subusers as $postuser) {
467                         // do not try to mail users with stopped email
468                         if ($postuser->emailstop) {
469                             if (!empty($CFG->forum_logblocked)) {
470                                 add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
471                             }
472                             continue;
473                         }
474                         // this user is subscribed to this forum
475                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
476                         // this user is a user we have to process later
477                         $users[$postuser->id] = $postuser;
478                     }
479                     unset($subusers); // release memory
480                 }
481             }
483             $mailcount[$pid] = 0;
484             $errorcount[$pid] = 0;
485         }
486     }
488     if ($users && $posts) {
490         $urlinfo = parse_url($CFG->wwwroot);
491         $hostname = $urlinfo['host'];
493         foreach ($users as $userto) {
495             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
497             // set this so that the capabilities are cached, and environment matches receiving user
498             cron_setup_user($userto);
500             mtrace('Processing user '.$userto->id);
502             // init caches
503             $userto->viewfullnames = array();
504             $userto->canpost       = array();
505             $userto->markposts     = array();
506             $userto->enrolledin    = array();
508             // reset the caches
509             foreach ($coursemodules as $forumid=>$unused) {
510                 $coursemodules[$forumid]->cache       = new stdClass();
511                 $coursemodules[$forumid]->cache->caps = array();
512                 unset($coursemodules[$forumid]->uservisible);
513             }
515             foreach ($posts as $pid => $post) {
517                 // Set up the environment for the post, discussion, forum, course
518                 $discussion = $discussions[$post->discussion];
519                 $forum      = $forums[$discussion->forum];
520                 $course     = $courses[$forum->course];
521                 $cm         =& $coursemodules[$forum->id];
523                 // Do some checks  to see if we can bail out now
524                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
525                     continue; // user does not subscribe to this forum
526                 }
528                 // Verify user is enrollend in course - if not do not send any email
529                 if (!isset($userto->enrolledin[$course->id])) {
530                     $userto->enrolledin[$course->id] = is_enrolled(get_context_instance(CONTEXT_COURSE, $course->id));
531                 }
532                 if (!$userto->enrolledin[$course->id]) {
533                     // oops - this user should not receive anything from this course
534                     continue;
535                 }
537                 // Get info about the sending user
538                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
539                     $userfrom = $users[$post->userid];
540                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
541                     $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
542                 } else {
543                     mtrace('Could not find user '.$post->userid);
544                     continue;
545                 }
547                 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
549                 // setup global $COURSE properly - needed for roles and languages
550                 cron_setup_user($userto, $course);
552                 // Fill caches
553                 if (!isset($userto->viewfullnames[$forum->id])) {
554                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
555                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
556                 }
557                 if (!isset($userto->canpost[$discussion->id])) {
558                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
559                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
560                 }
561                 if (!isset($userfrom->groups[$forum->id])) {
562                     if (!isset($userfrom->groups)) {
563                         $userfrom->groups = array();
564                         $users[$userfrom->id]->groups = array();
565                     }
566                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
567                     $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
568                 }
570                 // Make sure groups allow this user to see this email
571                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
572                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
573                         continue;                           // Be safe and don't send it to anyone
574                     }
576                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
577                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
578                         continue;
579                     }
580                 }
582                 // Make sure we're allowed to see it...
583                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
584                     mtrace('user '.$userto->id. ' can not see '.$post->id);
585                     continue;
586                 }
588                 // OK so we need to send the email.
590                 // Does the user want this post in a digest?  If so postpone it for now.
591                 if ($userto->maildigest > 0) {
592                     // This user wants the mails to be in digest form
593                     $queue = new stdClass();
594                     $queue->userid       = $userto->id;
595                     $queue->discussionid = $discussion->id;
596                     $queue->postid       = $post->id;
597                     $queue->timemodified = $post->created;
598                     $DB->insert_record('forum_queue', $queue);
599                     continue;
600                 }
603                 // Prepare to actually send the post now, and build up the content
605                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
607                 $userfrom->customheaders = array (  // Headers to make emails easier to track
608                            'Precedence: Bulk',
609                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
610                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
611                            'Message-ID: <moodlepost'.$post->id.'@'.$hostname.'>',
612                            'X-Course-Id: '.$course->id,
613                            'X-Course-Name: '.format_string($course->fullname, true)
614                 );
616                 if ($post->parent) {  // This post is a reply, so add headers for threading (see MDL-22551)
617                     $userfrom->customheaders[] = 'In-Reply-To: <moodlepost'.$post->parent.'@'.$hostname.'>';
618                     $userfrom->customheaders[] = 'References: <moodlepost'.$post->parent.'@'.$hostname.'>';
619                 }
621                 $postsubject = "$course->shortname: ".format_string($post->subject,true);
622                 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
623                 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
625                 // Send the post now!
627                 mtrace('Sending ', '');
629                 $eventdata = new stdClass();
630                 $eventdata->component        = 'mod_forum';
631                 $eventdata->name             = 'posts';
632                 $eventdata->userfrom         = $userfrom;
633                 $eventdata->userto           = $userto;
634                 $eventdata->subject          = $postsubject;
635                 $eventdata->fullmessage      = $posttext;
636                 $eventdata->fullmessageformat = FORMAT_PLAIN;
637                 $eventdata->fullmessagehtml  = $posthtml;
639                 $smallmessagestrings = new stdClass();
640                 $smallmessagestrings->user = fullname($userfrom);
641                 $smallmessagestrings->forumname = "{$course->shortname}->".format_string($forum->name,true);
642                 $smallmessagestrings->replylink = "$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id";
643                 $smallmessagestrings->message = $post->message;
644                 $eventdata->smallmessage = get_string('smallmessage', 'forum', $smallmessagestrings);
646                 $mailresult = message_send($eventdata);
647                 if (!$mailresult){
648                     mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
649                          " ($userto->email) .. not trying again.");
650                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
651                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
652                     $errorcount[$post->id]++;
653                 } else if ($mailresult === 'emailstop') {
654                     // should not be reached anymore - see check above
655                     mtrace("Error: mod/forum/lib.php forum_cron(): received 'emailstop' while sending out mail for id $post->id to user $userto->id ($userto->email)");
656                 } else {
657                     $mailcount[$post->id]++;
659                 // Mark post as read if forum_usermarksread is set off
660                     if (!$CFG->forum_usermarksread) {
661                         $userto->markposts[$post->id] = $post->id;
662                     }
663                 }
665                 mtrace('post '.$post->id. ': '.$post->subject);
666             }
668             // mark processed posts as read
669             forum_tp_mark_posts_read($userto, $userto->markposts);
670         }
671     }
673     if ($posts) {
674         foreach ($posts as $post) {
675             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
676             if ($errorcount[$post->id]) {
677                 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
678             }
679         }
680     }
682     // release some memory
683     unset($subscribedusers);
684     unset($mailcount);
685     unset($errorcount);
687     cron_setup_user();
689     $sitetimezone = $CFG->timezone;
691     // Now see if there are any digest mails waiting to be sent, and if we should send them
693     mtrace('Starting digest processing...');
695     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
697     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
698         set_config('digestmailtimelast', 0);
699     }
701     $timenow = time();
702     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
704     // Delete any really old ones (normally there shouldn't be any)
705     $weekago = $timenow - (7 * 24 * 3600);
706     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
707     mtrace ('Cleaned old digest records');
709     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
711         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
713         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
715         if ($digestposts_rs->valid()) {
717             // We have work to do
718             $usermailcount = 0;
720             //caches - reuse the those filled before too
721             $discussionposts = array();
722             $userdiscussions = array();
724             foreach ($digestposts_rs as $digestpost) {
725                 if (!isset($users[$digestpost->userid])) {
726                     if ($user = $DB->get_record('user', array('id' => $digestpost->userid))) {
727                         $users[$digestpost->userid] = $user;
728                     } else {
729                         continue;
730                     }
731                 }
732                 $postuser = $users[$digestpost->userid];
733                 if ($postuser->emailstop) {
734                     if (!empty($CFG->forum_logblocked)) {
735                         add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
736                     }
737                     continue;
738                 }
740                 if (!isset($posts[$digestpost->postid])) {
741                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
742                         $posts[$digestpost->postid] = $post;
743                     } else {
744                         continue;
745                     }
746                 }
747                 $discussionid = $digestpost->discussionid;
748                 if (!isset($discussions[$discussionid])) {
749                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
750                         $discussions[$discussionid] = $discussion;
751                     } else {
752                         continue;
753                     }
754                 }
755                 $forumid = $discussions[$discussionid]->forum;
756                 if (!isset($forums[$forumid])) {
757                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
758                         $forums[$forumid] = $forum;
759                     } else {
760                         continue;
761                     }
762                 }
764                 $courseid = $forums[$forumid]->course;
765                 if (!isset($courses[$courseid])) {
766                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
767                         $courses[$courseid] = $course;
768                     } else {
769                         continue;
770                     }
771                 }
773                 if (!isset($coursemodules[$forumid])) {
774                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
775                         $coursemodules[$forumid] = $cm;
776                     } else {
777                         continue;
778                     }
779                 }
780                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
781                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
782             }
783             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
785             // Data collected, start sending out emails to each user
786             foreach ($userdiscussions as $userid => $thesediscussions) {
788                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
790                 cron_setup_user();
792                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
794                 // First of all delete all the queue entries for this user
795                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
796                 $userto = $users[$userid];
798                 // Override the language and timezone of the "current" user, so that
799                 // mail is customised for the receiver.
800                 cron_setup_user($userto);
802                 // init caches
803                 $userto->viewfullnames = array();
804                 $userto->canpost       = array();
805                 $userto->markposts     = array();
807                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
809                 $headerdata = new stdClass();
810                 $headerdata->sitename = format_string($site->fullname, true);
811                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
813                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
814                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
816                 $posthtml = "<head>";
817 /*                foreach ($CFG->stylesheets as $stylesheet) {
818                     //TODO: MDL-21120
819                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
820                 }*/
821                 $posthtml .= "</head>\n<body id=\"email\">\n";
822                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
824                 foreach ($thesediscussions as $discussionid) {
826                     @set_time_limit(120);   // to be reset for each post
828                     $discussion = $discussions[$discussionid];
829                     $forum      = $forums[$discussion->forum];
830                     $course     = $courses[$forum->course];
831                     $cm         = $coursemodules[$forum->id];
833                     //override language
834                     cron_setup_user($userto, $course);
836                     // Fill caches
837                     if (!isset($userto->viewfullnames[$forum->id])) {
838                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
839                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
840                     }
841                     if (!isset($userto->canpost[$discussion->id])) {
842                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
843                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
844                     }
846                     $strforums      = get_string('forums', 'forum');
847                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
848                     $canreply       = $userto->canpost[$discussion->id];
850                     $posttext .= "\n \n";
851                     $posttext .= '=====================================================================';
852                     $posttext .= "\n \n";
853                     $posttext .= "$course->shortname -> $strforums -> ".format_string($forum->name,true);
854                     if ($discussion->name != $forum->name) {
855                         $posttext  .= " -> ".format_string($discussion->name,true);
856                     }
857                     $posttext .= "\n";
859                     $posthtml .= "<p><font face=\"sans-serif\">".
860                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> -> ".
861                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
862                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
863                     if ($discussion->name == $forum->name) {
864                         $posthtml .= "</font></p>";
865                     } else {
866                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
867                     }
868                     $posthtml .= '<p>';
870                     $postsarray = $discussionposts[$discussionid];
871                     sort($postsarray);
873                     foreach ($postsarray as $postid) {
874                         $post = $posts[$postid];
876                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
877                             $userfrom = $users[$post->userid];
878                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
879                             $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
880                         } else {
881                             mtrace('Could not find user '.$post->userid);
882                             continue;
883                         }
885                         if (!isset($userfrom->groups[$forum->id])) {
886                             if (!isset($userfrom->groups)) {
887                                 $userfrom->groups = array();
888                                 $users[$userfrom->id]->groups = array();
889                             }
890                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
891                             $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
892                         }
894                         $userfrom->customheaders = array ("Precedence: Bulk");
896                         if ($userto->maildigest == 2) {
897                             // Subjects only
898                             $by = new stdClass();
899                             $by->name = fullname($userfrom);
900                             $by->date = userdate($post->modified);
901                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
902                             $posttext .= "\n---------------------------------------------------------------------";
904                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
905                             $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>';
907                         } else {
908                             // The full treatment
909                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
910                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
912                         // Create an array of postid's for this user to mark as read.
913                             if (!$CFG->forum_usermarksread) {
914                                 $userto->markposts[$post->id] = $post->id;
915                             }
916                         }
917                     }
918                     if ($canunsubscribe) {
919                         $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>";
920                     } else {
921                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
922                     }
923                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
924                 }
925                 $posthtml .= '</body>';
927                 if (empty($userto->mailformat) || $userto->mailformat != 1) {
928                     // This user DOESN'T want to receive HTML
929                     $posthtml = '';
930                 }
932                 $attachment = $attachname='';
933                 $usetrueaddress = true;
934                 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname, $usetrueaddress, $CFG->forum_replytouser);
936                 if (!$mailresult) {
937                     mtrace("ERROR!");
938                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
939                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
940                 } else if ($mailresult === 'emailstop') {
941                     // should not happen anymore - see check above
942                 } else {
943                     mtrace("success.");
944                     $usermailcount++;
946                     // Mark post as read if forum_usermarksread is set off
947                     forum_tp_mark_posts_read($userto, $userto->markposts);
948                 }
949             }
950         }
951     /// We have finishied all digest emails, update $CFG->digestmailtimelast
952         set_config('digestmailtimelast', $timenow);
953     }
955     cron_setup_user();
957     if (!empty($usermailcount)) {
958         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
959     }
961     if (!empty($CFG->forum_lastreadclean)) {
962         $timenow = time();
963         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
964             set_config('forum_lastreadclean', $timenow);
965             mtrace('Removing old forum read tracking info...');
966             forum_tp_clean_read_records();
967         }
968     } else {
969         set_config('forum_lastreadclean', time());
970     }
973     return true;
976 /**
977  * Builds and returns the body of the email notification in plain text.
978  *
979  * @global object
980  * @global object
981  * @uses CONTEXT_MODULE
982  * @param object $course
983  * @param object $cm
984  * @param object $forum
985  * @param object $discussion
986  * @param object $post
987  * @param object $userfrom
988  * @param object $userto
989  * @param boolean $bare
990  * @return string The email body in plain text format.
991  */
992 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
993     global $CFG, $USER;
995     if (!isset($userto->viewfullnames[$forum->id])) {
996         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
997         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
998     } else {
999         $viewfullnames = $userto->viewfullnames[$forum->id];
1000     }
1002     if (!isset($userto->canpost[$discussion->id])) {
1003         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
1004         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
1005     } else {
1006         $canreply = $userto->canpost[$discussion->id];
1007     }
1009     $by = New stdClass;
1010     $by->name = fullname($userfrom, $viewfullnames);
1011     $by->date = userdate($post->modified, "", $userto->timezone);
1013     $strbynameondate = get_string('bynameondate', 'forum', $by);
1015     $strforums = get_string('forums', 'forum');
1017     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1019     $posttext = '';
1021     if (!$bare) {
1022         $posttext  = "$course->shortname -> $strforums -> ".format_string($forum->name,true);
1024         if ($discussion->name != $forum->name) {
1025             $posttext  .= " -> ".format_string($discussion->name,true);
1026         }
1027     }
1029     $posttext .= "\n---------------------------------------------------------------------\n";
1030     $posttext .= format_string($post->subject,true);
1031     if ($bare) {
1032         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1033     }
1034     $posttext .= "\n".$strbynameondate."\n";
1035     $posttext .= "---------------------------------------------------------------------\n";
1036     $posttext .= format_text_email($post->message, $post->messageformat);
1037     $posttext .= "\n\n";
1038     $posttext .= forum_print_attachments($post, $cm, "text");
1040     if (!$bare && $canreply) {
1041         $posttext .= "---------------------------------------------------------------------\n";
1042         $posttext .= get_string("postmailinfo", "forum", $course->shortname)."\n";
1043         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1044     }
1045     if (!$bare && $canunsubscribe) {
1046         $posttext .= "\n---------------------------------------------------------------------\n";
1047         $posttext .= get_string("unsubscribe", "forum");
1048         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1049     }
1051     return $posttext;
1054 /**
1055  * Builds and returns the body of the email notification in html format.
1056  *
1057  * @global object
1058  * @param object $course
1059  * @param object $cm
1060  * @param object $forum
1061  * @param object $discussion
1062  * @param object $post
1063  * @param object $userfrom
1064  * @param object $userto
1065  * @return string The email text in HTML format
1066  */
1067 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1068     global $CFG;
1070     if ($userto->mailformat != 1) {  // Needs to be HTML
1071         return '';
1072     }
1074     if (!isset($userto->canpost[$discussion->id])) {
1075         $canreply = forum_user_can_post($forum, $discussion, $userto);
1076     } else {
1077         $canreply = $userto->canpost[$discussion->id];
1078     }
1080     $strforums = get_string('forums', 'forum');
1081     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1083     $posthtml = '<head>';
1084 /*    foreach ($CFG->stylesheets as $stylesheet) {
1085         //TODO: MDL-21120
1086         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1087     }*/
1088     $posthtml .= '</head>';
1089     $posthtml .= "\n<body id=\"email\">\n\n";
1091     $posthtml .= '<div class="navbar">'.
1092     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$course->shortname.'</a> &raquo; '.
1093     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1094     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1095     if ($discussion->name == $forum->name) {
1096         $posthtml .= '</div>';
1097     } else {
1098         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1099                      format_string($discussion->name,true).'</a></div>';
1100     }
1101     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1103     if ($canunsubscribe) {
1104         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1105                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1106                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1107     }
1109     $posthtml .= '</body>';
1111     return $posthtml;
1115 /**
1116  *
1117  * @param object $course
1118  * @param object $user
1119  * @param object $mod TODO this is not used in this function, refactor
1120  * @param object $forum
1121  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1122  */
1123 function forum_user_outline($course, $user, $mod, $forum) {
1124     global $CFG;
1125     require_once("$CFG->libdir/gradelib.php");
1126     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1127     if (empty($grades->items[0]->grades)) {
1128         $grade = false;
1129     } else {
1130         $grade = reset($grades->items[0]->grades);
1131     }
1133     $count = forum_count_user_posts($forum->id, $user->id);
1135     if ($count && $count->postcount > 0) {
1136         $result = new stdClass();
1137         $result->info = get_string("numposts", "forum", $count->postcount);
1138         $result->time = $count->lastpost;
1139         if ($grade) {
1140             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1141         }
1142         return $result;
1143     } else if ($grade) {
1144         $result = new stdClass();
1145         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1146         $result->time = $grade->dategraded;
1147         return $result;
1148     }
1149     return NULL;
1153 /**
1154  * @global object
1155  * @global object
1156  * @param object $coure
1157  * @param object $user
1158  * @param object $mod
1159  * @param object $forum
1160  */
1161 function forum_user_complete($course, $user, $mod, $forum) {
1162     global $CFG,$USER, $OUTPUT;
1163     require_once("$CFG->libdir/gradelib.php");
1165     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1166     if (!empty($grades->items[0]->grades)) {
1167         $grade = reset($grades->items[0]->grades);
1168         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1169         if ($grade->str_feedback) {
1170             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1171         }
1172     }
1174     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1176         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1177             print_error('invalidcoursemodule');
1178         }
1179         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1181         foreach ($posts as $post) {
1182             if (!isset($discussions[$post->discussion])) {
1183                 continue;
1184             }
1185             $discussion = $discussions[$post->discussion];
1187             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1188         }
1189     } else {
1190         echo "<p>".get_string("noposts", "forum")."</p>";
1191     }
1199 /**
1200  * @global object
1201  * @global object
1202  * @global object
1203  * @param array $courses
1204  * @param array $htmlarray
1205  */
1206 function forum_print_overview($courses,&$htmlarray) {
1207     global $USER, $CFG, $DB, $SESSION;
1209     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1210         return array();
1211     }
1213     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1214         return;
1215     }
1218     // get all forum logs in ONE query (much better!)
1219     $params = array();
1220     $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {log} l "
1221         ." JOIN {course_modules} cm ON cm.id = cmid "
1222         ." WHERE (";
1223     foreach ($courses as $course) {
1224         $sql .= '(l.course = ? AND l.time > ?) OR ';
1225         $params[] = $course->id;
1226         $params[] = $course->lastaccess;
1227     }
1228     $sql = substr($sql,0,-3); // take off the last OR
1230     $sql .= ") AND l.module = 'forum' AND action = 'add post' "
1231         ." AND userid != ? GROUP BY cmid,l.course,instance";
1233     $params[] = $USER->id;
1235     if (!$new = $DB->get_records_sql($sql, $params)) {
1236         $new = array(); // avoid warnings
1237     }
1239     // also get all forum tracking stuff ONCE.
1240     $trackingforums = array();
1241     foreach ($forums as $forum) {
1242         if (forum_tp_can_track_forums($forum)) {
1243             $trackingforums[$forum->id] = $forum;
1244         }
1245     }
1247     if (count($trackingforums) > 0) {
1248         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1249         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1250             ' FROM {forum_posts} p '.
1251             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1252             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1253         $params = array($USER->id);
1255         foreach ($trackingforums as $track) {
1256             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1257             $params[] = $track->id;
1258             if (isset($SESSION->currentgroup[$track->course])) {
1259                 $groupid =  $SESSION->currentgroup[$track->course];
1260             } else {
1261                 $groupid = groups_get_all_groups($track->course, $USER->id);
1262                 if (is_array($groupid)) {
1263                     $groupid = array_shift(array_keys($groupid));
1264                     $SESSION->currentgroup[$track->course] = $groupid;
1265                 } else {
1266                     $groupid = 0;
1267                 }
1268             }
1269             $params[] = $groupid;
1270         }
1271         $sql = substr($sql,0,-3); // take off the last OR
1272         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1273         $params[] = $cutoffdate;
1275         if (!$unread = $DB->get_records_sql($sql, $params)) {
1276             $unread = array();
1277         }
1278     } else {
1279         $unread = array();
1280     }
1282     if (empty($unread) and empty($new)) {
1283         return;
1284     }
1286     $strforum = get_string('modulename','forum');
1287     $strnumunread = get_string('overviewnumunread','forum');
1288     $strnumpostssince = get_string('overviewnumpostssince','forum');
1290     foreach ($forums as $forum) {
1291         $str = '';
1292         $count = 0;
1293         $thisunread = 0;
1294         $showunread = false;
1295         // either we have something from logs, or trackposts, or nothing.
1296         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1297             $count = $new[$forum->id]->count;
1298         }
1299         if (array_key_exists($forum->id,$unread)) {
1300             $thisunread = $unread[$forum->id]->count;
1301             $showunread = true;
1302         }
1303         if ($count > 0 || $thisunread > 0) {
1304             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1305                 $forum->name.'</a></div>';
1306             $str .= '<div class="info">';
1307             $str .= $count.' '.$strnumpostssince;
1308             if (!empty($showunread)) {
1309                 $str .= '<br />'.$thisunread .' '.$strnumunread;
1310             }
1311             $str .= '</div></div>';
1312         }
1313         if (!empty($str)) {
1314             if (!array_key_exists($forum->course,$htmlarray)) {
1315                 $htmlarray[$forum->course] = array();
1316             }
1317             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1318                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1319             }
1320             $htmlarray[$forum->course]['forum'] .= $str;
1321         }
1322     }
1325 /**
1326  * Given a course and a date, prints a summary of all the new
1327  * messages posted in the course since that date
1328  *
1329  * @global object
1330  * @global object
1331  * @global object
1332  * @uses CONTEXT_MODULE
1333  * @uses VISIBLEGROUPS
1334  * @param object $course
1335  * @param bool $viewfullnames capability
1336  * @param int $timestart
1337  * @return bool success
1338  */
1339 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1340     global $CFG, $USER, $DB, $OUTPUT;
1342     // do not use log table if possible, it may be huge and is expensive to join with other tables
1344     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1345                                               d.timestart, d.timeend, d.userid AS duserid,
1346                                               u.firstname, u.lastname, u.email, u.picture
1347                                          FROM {forum_posts} p
1348                                               JOIN {forum_discussions} d ON d.id = p.discussion
1349                                               JOIN {forum} f             ON f.id = d.forum
1350                                               JOIN {user} u              ON u.id = p.userid
1351                                         WHERE p.created > ? AND f.course = ?
1352                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1353          return false;
1354     }
1356     $modinfo =& get_fast_modinfo($course);
1358     $groupmodes = array();
1359     $cms    = array();
1361     $strftimerecent = get_string('strftimerecent');
1363     $printposts = array();
1364     foreach ($posts as $post) {
1365         if (!isset($modinfo->instances['forum'][$post->forum])) {
1366             // not visible
1367             continue;
1368         }
1369         $cm = $modinfo->instances['forum'][$post->forum];
1370         if (!$cm->uservisible) {
1371             continue;
1372         }
1373         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1375         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1376             continue;
1377         }
1379         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1380           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1381             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1382                 continue;
1383             }
1384         }
1386         $groupmode = groups_get_activity_groupmode($cm, $course);
1388         if ($groupmode) {
1389             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1390                 // oki (Open discussions have groupid -1)
1391             } else {
1392                 // separate mode
1393                 if (isguestuser()) {
1394                     // shortcut
1395                     continue;
1396                 }
1398                 if (is_null($modinfo->groups)) {
1399                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1400                 }
1402                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1403                     continue;
1404                 }
1405             }
1406         }
1408         $printposts[] = $post;
1409     }
1410     unset($posts);
1412     if (!$printposts) {
1413         return false;
1414     }
1416     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1417     echo "\n<ul class='unlist'>\n";
1419     foreach ($printposts as $post) {
1420         $subjectclass = empty($post->parent) ? ' bold' : '';
1422         echo '<li><div class="head">'.
1423                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1424                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1425              '</div>';
1426         echo '<div class="info'.$subjectclass.'">';
1427         if (empty($post->parent)) {
1428             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1429         } else {
1430             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1431         }
1432         $post->subject = break_up_long_words(format_string($post->subject, true));
1433         echo $post->subject;
1434         echo "</a>\"</div></li>\n";
1435     }
1437     echo "</ul>\n";
1439     return true;
1442 /**
1443  * Return grade for given user or all users.
1444  *
1445  * @global object
1446  * @global object
1447  * @param object $forum
1448  * @param int $userid optional user id, 0 means all users
1449  * @return array array of grades, false if none
1450  */
1451 function forum_get_user_grades($forum, $userid=0) {
1452     global $CFG;
1454     require_once($CFG->dirroot.'/rating/lib.php');
1455     $rm = new rating_manager();
1457     $ratingoptions = new stdclass();
1459     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1460     $ratingoptions->modulename = 'forum';
1461     $ratingoptions->moduleid   = $forum->id;
1462     //$ratingoptions->cmidnumber = $forum->cmidnumber;
1464     $ratingoptions->userid = $userid;
1465     $ratingoptions->aggregationmethod = $forum->assessed;
1466     $ratingoptions->scaleid = $forum->scale;
1467     $ratingoptions->itemtable = 'forum_posts';
1468     $ratingoptions->itemtableusercolumn = 'userid';
1470     return $rm->get_user_grades($ratingoptions);
1473 /**
1474  * Update activity grades
1475  *
1476  * @global object
1477  * @global object
1478  * @param object $forum
1479  * @param int $userid specific user only, 0 means all
1480  * @param boolean $nullifnone return null if grade does not exist
1481  * @return void
1482  */
1483 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1484     global $CFG, $DB;
1485     require_once($CFG->libdir.'/gradelib.php');
1487     if (!$forum->assessed) {
1488         forum_grade_item_update($forum);
1490     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1491         forum_grade_item_update($forum, $grades);
1493     } else if ($userid and $nullifnone) {
1494         $grade = new stdClass();
1495         $grade->userid   = $userid;
1496         $grade->rawgrade = NULL;
1497         forum_grade_item_update($forum, $grade);
1499     } else {
1500         forum_grade_item_update($forum);
1501     }
1504 /**
1505  * Update all grades in gradebook.
1506  * @global object
1507  */
1508 function forum_upgrade_grades() {
1509     global $DB;
1511     $sql = "SELECT COUNT('x')
1512               FROM {forum} f, {course_modules} cm, {modules} m
1513              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1514     $count = $DB->count_records_sql($sql);
1516     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1517               FROM {forum} f, {course_modules} cm, {modules} m
1518              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1519     if ($rs = $DB->get_recordset_sql($sql)) {
1520         $pbar = new progress_bar('forumupgradegrades', 500, true);
1521         $i=0;
1522         foreach ($rs as $forum) {
1523             $i++;
1524             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1525             forum_update_grades($forum, 0, false);
1526             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1527         }
1528         $rs->close();
1529     }
1532 /**
1533  * Create/update grade item for given forum
1534  *
1535  * @global object
1536  * @uses GRADE_TYPE_NONE
1537  * @uses GRADE_TYPE_VALUE
1538  * @uses GRADE_TYPE_SCALE
1539  * @param object $forum object with extra cmidnumber
1540  * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
1541  * @return int 0 if ok
1542  */
1543 function forum_grade_item_update($forum, $grades=NULL) {
1544     global $CFG;
1545     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1546         require_once($CFG->libdir.'/gradelib.php');
1547     }
1549     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1551     if (!$forum->assessed or $forum->scale == 0) {
1552         $params['gradetype'] = GRADE_TYPE_NONE;
1554     } else if ($forum->scale > 0) {
1555         $params['gradetype'] = GRADE_TYPE_VALUE;
1556         $params['grademax']  = $forum->scale;
1557         $params['grademin']  = 0;
1559     } else if ($forum->scale < 0) {
1560         $params['gradetype'] = GRADE_TYPE_SCALE;
1561         $params['scaleid']   = -$forum->scale;
1562     }
1564     if ($grades  === 'reset') {
1565         $params['reset'] = true;
1566         $grades = NULL;
1567     }
1569     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1572 /**
1573  * Delete grade item for given forum
1574  *
1575  * @global object
1576  * @param object $forum object
1577  * @return object grade_item
1578  */
1579 function forum_grade_item_delete($forum) {
1580     global $CFG;
1581     require_once($CFG->libdir.'/gradelib.php');
1583     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1587 /**
1588  * Returns the users with data in one forum
1589  * (users with records in forum_subscriptions, forum_posts, students)
1590  *
1591  * @global object
1592  * @global object
1593  * @param int $forumid
1594  * @return mixed array or false if none
1595  */
1596 function forum_get_participants($forumid) {
1598     global $CFG, $DB;
1600     //Get students from forum_subscriptions
1601     $st_subscriptions = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1602                                          FROM {user} u,
1603                                               {forum_subscriptions} s
1604                                          WHERE s.forum = ? AND
1605                                                u.id = s.userid", array($forumid));
1606     //Get students from forum_posts
1607     $st_posts = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1608                                  FROM {user} u,
1609                                       {forum_discussions} d,
1610                                       {forum_posts} p
1611                                  WHERE d.forum = ? AND
1612                                        p.discussion = d.id AND
1613                                        u.id = p.userid", array($forumid));
1615     //Get students from the ratings table
1616     $st_ratings = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1617                                    FROM {user} u,
1618                                         {forum_discussions} d,
1619                                         {forum_posts} p,
1620                                         {ratings} r
1621                                    WHERE d.forum = ? AND
1622                                          p.discussion = d.id AND
1623                                          r.post = p.id AND
1624                                          u.id = r.userid", array($forumid));
1626     //Add st_posts to st_subscriptions
1627     if ($st_posts) {
1628         foreach ($st_posts as $st_post) {
1629             $st_subscriptions[$st_post->id] = $st_post;
1630         }
1631     }
1632     //Add st_ratings to st_subscriptions
1633     if ($st_ratings) {
1634         foreach ($st_ratings as $st_rating) {
1635             $st_subscriptions[$st_rating->id] = $st_rating;
1636         }
1637     }
1638     //Return st_subscriptions array (it contains an array of unique users)
1639     return ($st_subscriptions);
1642 /**
1643  * This function returns if a scale is being used by one forum
1644  *
1645  * @global object
1646  * @param int $forumid
1647  * @param int $scaleid negative number
1648  * @return bool
1649  */
1650 function forum_scale_used ($forumid,$scaleid) {
1651     global $DB;
1652     $return = false;
1654     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1656     if (!empty($rec) && !empty($scaleid)) {
1657         $return = true;
1658     }
1660     return $return;
1663 /**
1664  * Checks if scale is being used by any instance of forum
1665  *
1666  * This is used to find out if scale used anywhere
1667  *
1668  * @global object
1669  * @param $scaleid int
1670  * @return boolean True if the scale is used by any forum
1671  */
1672 function forum_scale_used_anywhere($scaleid) {
1673     global $DB;
1674     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1675         return true;
1676     } else {
1677         return false;
1678     }
1681 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1683 /**
1684  * Gets a post with all info ready for forum_print_post
1685  * Most of these joins are just to get the forum id
1686  *
1687  * @global object
1688  * @global object
1689  * @param int $postid
1690  * @return mixed array of posts or false
1691  */
1692 function forum_get_post_full($postid) {
1693     global $CFG, $DB;
1695     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1696                              FROM {forum_posts} p
1697                                   JOIN {forum_discussions} d ON p.discussion = d.id
1698                                   LEFT JOIN {user} u ON p.userid = u.id
1699                             WHERE p.id = ?", array($postid));
1702 /**
1703  * Gets posts with all info ready for forum_print_post
1704  * We pass forumid in because we always know it so no need to make a
1705  * complicated join to find it out.
1706  *
1707  * @global object
1708  * @global object
1709  * @return mixed array of posts or false
1710  */
1711 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1712     global $CFG, $DB;
1714     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1715                               FROM {forum_posts} p
1716                          LEFT JOIN {user} u ON p.userid = u.id
1717                              WHERE p.discussion = ?
1718                                AND p.parent > 0 $sort", array($discussion));
1721 /**
1722  * Gets all posts in discussion including top parent.
1723  *
1724  * @global object
1725  * @global object
1726  * @global object
1727  * @param int $discussionid
1728  * @param string $sort
1729  * @param bool $tracking does user track the forum?
1730  * @return array of posts
1731  */
1732 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1733     global $CFG, $DB, $USER;
1735     $tr_sel  = "";
1736     $tr_join = "";
1737     $params = array();
1739     if ($tracking) {
1740         $now = time();
1741         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1742         $tr_sel  = ", fr.id AS postread";
1743         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1744         $params[] = $USER->id;
1745     }
1747     $params[] = $discussionid;
1748     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1749                                      FROM {forum_posts} p
1750                                           LEFT JOIN {user} u ON p.userid = u.id
1751                                           $tr_join
1752                                     WHERE p.discussion = ?
1753                                  ORDER BY $sort", $params)) {
1754         return array();
1755     }
1757     foreach ($posts as $pid=>$p) {
1758         if ($tracking) {
1759             if (forum_tp_is_post_old($p)) {
1760                  $posts[$pid]->postread = true;
1761             }
1762         }
1763         if (!$p->parent) {
1764             continue;
1765         }
1766         if (!isset($posts[$p->parent])) {
1767             continue; // parent does not exist??
1768         }
1769         if (!isset($posts[$p->parent]->children)) {
1770             $posts[$p->parent]->children = array();
1771         }
1772         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1773     }
1775     return $posts;
1778 /**
1779  * Gets posts with all info ready for forum_print_post
1780  * We pass forumid in because we always know it so no need to make a
1781  * complicated join to find it out.
1782  *
1783  * @global object
1784  * @global object
1785  * @param int $parent
1786  * @param int $forumid
1787  * @return array
1788  */
1789 function forum_get_child_posts($parent, $forumid) {
1790     global $CFG, $DB;
1792     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1793                               FROM {forum_posts} p
1794                          LEFT JOIN {user} u ON p.userid = u.id
1795                              WHERE p.parent = ?
1796                           ORDER BY p.created ASC", array($parent));
1799 /**
1800  * An array of forum objects that the user is allowed to read/search through.
1801  *
1802  * @global object
1803  * @global object
1804  * @global object
1805  * @param int $userid
1806  * @param int $courseid if 0, we look for forums throughout the whole site.
1807  * @return array of forum objects, or false if no matches
1808  *         Forum objects have the following attributes:
1809  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1810  *         viewhiddentimedposts
1811  */
1812 function forum_get_readable_forums($userid, $courseid=0) {
1814     global $CFG, $DB, $USER;
1815     require_once($CFG->dirroot.'/course/lib.php');
1817     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1818         print_error('notinstalled', 'forum');
1819     }
1821     if ($courseid) {
1822         $courses = $DB->get_records('course', array('id' => $courseid));
1823     } else {
1824         // If no course is specified, then the user can see SITE + his courses.
1825         $courses1 = $DB->get_records('course', array('id' => SITEID));
1826         $courses2 = enrol_get_users_courses($userid, true);
1827         $courses = array_merge($courses1, $courses2);
1828     }
1829     if (!$courses) {
1830         return array();
1831     }
1833     $readableforums = array();
1835     foreach ($courses as $course) {
1837         $modinfo =& get_fast_modinfo($course);
1838         if (is_null($modinfo->groups)) {
1839             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1840         }
1842         if (empty($modinfo->instances['forum'])) {
1843             // hmm, no forums?
1844             continue;
1845         }
1847         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1849         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1850             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1851                 continue;
1852             }
1853             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1854             $forum = $courseforums[$forumid];
1856             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1857                 continue;
1858             }
1860          /// group access
1861             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1862                 if (is_null($modinfo->groups)) {
1863                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1864                 }
1865                 if (isset($modinfo->groups[$cm->groupingid])) {
1866                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1867                     $forum->onlygroups[] = -1;
1868                 } else {
1869                     $forum->onlygroups = array(-1);
1870                 }
1871             }
1873         /// hidden timed discussions
1874             $forum->viewhiddentimedposts = true;
1875             if (!empty($CFG->forum_enabletimedposts)) {
1876                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1877                     $forum->viewhiddentimedposts = false;
1878                 }
1879             }
1881         /// qanda access
1882             if ($forum->type == 'qanda'
1883                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1885                 // We need to check whether the user has posted in the qanda forum.
1886                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1887                                                     // the user is allowed to see in this forum.
1888                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1889                     foreach ($discussionspostedin as $d) {
1890                         $forum->onlydiscussions[] = $d->id;
1891                     }
1892                 }
1893             }
1895             $readableforums[$forum->id] = $forum;
1896         }
1898         unset($modinfo);
1900     } // End foreach $courses
1902     return $readableforums;
1905 /**
1906  * Returns a list of posts found using an array of search terms.
1907  *
1908  * @global object
1909  * @global object
1910  * @global object
1911  * @param array $searchterms array of search terms, e.g. word +word -word
1912  * @param int $courseid if 0, we search through the whole site
1913  * @param int $limitfrom
1914  * @param int $limitnum
1915  * @param int &$totalcount
1916  * @param string $extrasql
1917  * @return array|bool Array of posts found or false
1918  */
1919 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1920                             &$totalcount, $extrasql='') {
1921     global $CFG, $DB, $USER;
1922     require_once($CFG->libdir.'/searchlib.php');
1924     $forums = forum_get_readable_forums($USER->id, $courseid);
1926     if (count($forums) == 0) {
1927         $totalcount = 0;
1928         return false;
1929     }
1931     $now = round(time(), -2); // db friendly
1933     $fullaccess = array();
1934     $where = array();
1935     $params = array();
1937     foreach ($forums as $forumid => $forum) {
1938         $select = array();
1940         if (!$forum->viewhiddentimedposts) {
1941             $select[] = "(d.userid = :userid OR (d.timestart < : AND (d.timeend = 0 OR d.timeend > :timeend)))";
1942             $params = array('userid'=>$USER->id, 'timestart'=>$now, 'timeend'=>$now);
1943         }
1945         $cm = get_coursemodule_from_instance('forum', $forumid);
1946         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1948         if ($forum->type == 'qanda'
1949             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1950             if (!empty($forum->onlydiscussions)) {
1951                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda0');
1952                 $params = array_merge($params, $discussionid_params);
1953                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
1954             } else {
1955                 $select[] = "p.parent = 0";
1956             }
1957         }
1959         if (!empty($forum->onlygroups)) {
1960             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps0');
1961             $params = array_merge($params, $groupid_params);
1962             $select[] = "d.groupid $groupid_sql";
1963         }
1965         if ($select) {
1966             $selects = implode(" AND ", $select);
1967             $where[] = "(d.forum = :forum AND $selects)";
1968             $params['forum'] = $forumid;
1969         } else {
1970             $fullaccess[] = $forumid;
1971         }
1972     }
1974     if ($fullaccess) {
1975         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula0');
1976         $params = array_merge($params, $fullid_params);
1977         $where[] = "(d.forum $fullid_sql)";
1978     }
1980     $selectdiscussion = "(".implode(" OR ", $where).")";
1982     $messagesearch = '';
1983     $searchstring = '';
1985     // Need to concat these back together for parser to work.
1986     foreach($searchterms as $searchterm){
1987         if ($searchstring != '') {
1988             $searchstring .= ' ';
1989         }
1990         $searchstring .= $searchterm;
1991     }
1993     // We need to allow quoted strings for the search. The quotes *should* be stripped
1994     // by the parser, but this should be examined carefully for security implications.
1995     $searchstring = str_replace("\\\"","\"",$searchstring);
1996     $parser = new search_parser();
1997     $lexer = new search_lexer($parser);
1999     if ($lexer->parse($searchstring)) {
2000         $parsearray = $parser->get_parsed_array();
2001     // Experimental feature under 1.8! MDL-8830
2002     // Use alternative text searches if defined
2003     // This feature only works under mysql until properly implemented for other DBs
2004     // Requires manual creation of text index for forum_posts before enabling it:
2005     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2006     // Experimental feature under 1.8! MDL-8830
2007         if (!empty($CFG->forum_usetextsearches)) {
2008             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2009                                                  'p.userid', 'u.id', 'u.firstname',
2010                                                  'u.lastname', 'p.modified', 'd.forum');
2011         } else {
2012             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2013                                                  'p.userid', 'u.id', 'u.firstname',
2014                                                  'u.lastname', 'p.modified', 'd.forum');
2015         }
2016         $params = array_merge($params, $msparams);
2017     }
2019     $fromsql = "{forum_posts} p,
2020                   {forum_discussions} d,
2021                   {user} u";
2023     $selectsql = " $messagesearch
2024                AND p.discussion = d.id
2025                AND p.userid = u.id
2026                AND $selectdiscussion
2027                    $extrasql";
2029     $countsql = "SELECT COUNT(*)
2030                    FROM $fromsql
2031                   WHERE $selectsql";
2033     $searchsql = "SELECT p.*,
2034                          d.forum,
2035                          u.firstname,
2036                          u.lastname,
2037                          u.email,
2038                          u.picture,
2039                          u.imagealt,
2040                          u.email
2041                     FROM $fromsql
2042                    WHERE $selectsql
2043                 ORDER BY p.modified DESC";
2045     $totalcount = $DB->count_records_sql($countsql, $params);
2047     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2050 /**
2051  * Returns a list of ratings for a particular post - sorted.
2052  *
2053  * @global object
2054  * @global object
2055  * @param int $postid
2056  * @param string $sort
2057  * @return array Array of ratings or false
2058  */
2059 function forum_get_ratings($context, $postid, $sort="u.firstname ASC") {
2060     global $PAGE;
2062     $options = new stdclass();
2063     $options->context = $PAGE->context;
2064     $options->itemid = $postid;
2065     $options->sort = "ORDER BY $sort";
2067     $rm = new rating_manager();
2068     $rm->get_all_ratings_for_item($options);
2071 /**
2072  * Returns a list of all new posts that have not been mailed yet
2073  *
2074  * @global object
2075  * @global object
2076  * @param int $starttime posts created after this time
2077  * @param int $endtime posts created before this
2078  * @param int $now used for timed discussions only
2079  * @return array
2080  */
2081 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2082     global $CFG, $DB;
2084     $params = array($starttime, $endtime);
2085     if (!empty($CFG->forum_enabletimedposts)) {
2086         if (empty($now)) {
2087             $now = time();
2088         }
2089         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2090         $params[] = $now;
2091         $params[] = $now;
2092     } else {
2093         $timedsql = "";
2094     }
2096     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2097                               FROM {forum_posts} p
2098                                    JOIN {forum_discussions} d ON d.id = p.discussion
2099                              WHERE p.mailed = 0
2100                                    AND p.created >= ?
2101                                    AND (p.created < ? OR p.mailnow = 1)
2102                                    $timedsql
2103                           ORDER BY p.modified ASC", $params);
2106 /**
2107  * Marks posts before a certain time as being mailed already
2108  *
2109  * @global object
2110  * @global object
2111  * @param int $endtime
2112  * @param int $now Defaults to time()
2113  * @return bool
2114  */
2115 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2116     global $CFG, $DB;
2117     if (empty($now)) {
2118         $now = time();
2119     }
2121     if (empty($CFG->forum_enabletimedposts)) {
2122         return $DB->execute("UPDATE {forum_posts}
2123                                SET mailed = '1'
2124                              WHERE (created < ? OR mailnow = 1)
2125                                    AND mailed = 0", array($endtime));
2127     } else {
2128         return $DB->execute("UPDATE {forum_posts}
2129                                SET mailed = '1'
2130                              WHERE discussion NOT IN (SELECT d.id
2131                                                         FROM {forum_discussions} d
2132                                                        WHERE d.timestart > ?)
2133                                    AND (created < ? OR mailnow = 1)
2134                                    AND mailed = 0", array($now, $endtime));
2135     }
2138 /**
2139  * Get all the posts for a user in a forum suitable for forum_print_post
2140  *
2141  * @global object
2142  * @global object
2143  * @uses CONTEXT_MODULE
2144  * @return array
2145  */
2146 function forum_get_user_posts($forumid, $userid) {
2147     global $CFG, $DB;
2149     $timedsql = "";
2150     $params = array($forumid, $userid);
2152     if (!empty($CFG->forum_enabletimedposts)) {
2153         $cm = get_coursemodule_from_instance('forum', $forumid);
2154         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2155             $now = time();
2156             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2157             $params[] = $now;
2158             $params[] = $now;
2159         }
2160     }
2162     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2163                               FROM {forum} f
2164                                    JOIN {forum_discussions} d ON d.forum = f.id
2165                                    JOIN {forum_posts} p       ON p.discussion = d.id
2166                                    JOIN {user} u              ON u.id = p.userid
2167                              WHERE f.id = ?
2168                                    AND p.userid = ?
2169                                    $timedsql
2170                           ORDER BY p.modified ASC", $params);
2173 /**
2174  * Get all the discussions user participated in
2175  *
2176  * @global object
2177  * @global object
2178  * @uses CONTEXT_MODULE
2179  * @param int $forumid
2180  * @param int $userid
2181  * @return array Array or false
2182  */
2183 function forum_get_user_involved_discussions($forumid, $userid) {
2184     global $CFG, $DB;
2186     $timedsql = "";
2187     $params = array($forumid, $userid);
2188     if (!empty($CFG->forum_enabletimedposts)) {
2189         $cm = get_coursemodule_from_instance('forum', $forumid);
2190         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2191             $now = time();
2192             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2193             $params[] = $now;
2194             $params[] = $now;
2195         }
2196     }
2198     return $DB->get_records_sql("SELECT DISTINCT d.*
2199                               FROM {forum} f
2200                                    JOIN {forum_discussions} d ON d.forum = f.id
2201                                    JOIN {forum_posts} p       ON p.discussion = d.id
2202                              WHERE f.id = ?
2203                                    AND p.userid = ?
2204                                    $timedsql", $params);
2207 /**
2208  * Get all the posts for a user in a forum suitable for forum_print_post
2209  *
2210  * @global object
2211  * @global object
2212  * @param int $forumid
2213  * @param int $userid
2214  * @return array of counts or false
2215  */
2216 function forum_count_user_posts($forumid, $userid) {
2217     global $CFG, $DB;
2219     $timedsql = "";
2220     $params = array($forumid, $userid);
2221     if (!empty($CFG->forum_enabletimedposts)) {
2222         $cm = get_coursemodule_from_instance('forum', $forumid);
2223         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2224             $now = time();
2225             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2226             $params[] = $now;
2227             $params[] = $now;
2228         }
2229     }
2231     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2232                              FROM {forum} f
2233                                   JOIN {forum_discussions} d ON d.forum = f.id
2234                                   JOIN {forum_posts} p       ON p.discussion = d.id
2235                                   JOIN {user} u              ON u.id = p.userid
2236                             WHERE f.id = ?
2237                                   AND p.userid = ?
2238                                   $timedsql", $params);
2241 /**
2242  * Given a log entry, return the forum post details for it.
2243  *
2244  * @global object
2245  * @global object
2246  * @param object $log
2247  * @return array|null
2248  */
2249 function forum_get_post_from_log($log) {
2250     global $CFG, $DB;
2252     if ($log->action == "add post") {
2254         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2255                                            u.firstname, u.lastname, u.email, u.picture
2256                                  FROM {forum_discussions} d,
2257                                       {forum_posts} p,
2258                                       {forum} f,
2259                                       {user} u
2260                                 WHERE p.id = ?
2261                                   AND d.id = p.discussion
2262                                   AND p.userid = u.id
2263                                   AND u.deleted <> '1'
2264                                   AND f.id = d.forum", array($log->info));
2267     } else if ($log->action == "add discussion") {
2269         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2270                                            u.firstname, u.lastname, u.email, u.picture
2271                                  FROM {forum_discussions} d,
2272                                       {forum_posts} p,
2273                                       {forum} f,
2274                                       {user} u
2275                                 WHERE d.id = ?
2276                                   AND d.firstpost = p.id
2277                                   AND p.userid = u.id
2278                                   AND u.deleted <> '1'
2279                                   AND f.id = d.forum", array($log->info));
2280     }
2281     return NULL;
2284 /**
2285  * Given a discussion id, return the first post from the discussion
2286  *
2287  * @global object
2288  * @global object
2289  * @param int $dicsussionid
2290  * @return array
2291  */
2292 function forum_get_firstpost_from_discussion($discussionid) {
2293     global $CFG, $DB;
2295     return $DB->get_record_sql("SELECT p.*
2296                              FROM {forum_discussions} d,
2297                                   {forum_posts} p
2298                             WHERE d.id = ?
2299                               AND d.firstpost = p.id ", array($discussionid));
2302 /**
2303  * Returns an array of counts of replies to each discussion
2304  *
2305  * @global object
2306  * @global object
2307  * @param int $forumid
2308  * @param string $forumsort
2309  * @param int $limit
2310  * @param int $page
2311  * @param int $perpage
2312  * @return array
2313  */
2314 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2315     global $CFG, $DB;
2317     if ($limit > 0) {
2318         $limitfrom = 0;
2319         $limitnum  = $limit;
2320     } else if ($page != -1) {
2321         $limitfrom = $page*$perpage;
2322         $limitnum  = $perpage;
2323     } else {
2324         $limitfrom = 0;
2325         $limitnum  = 0;
2326     }
2328     if ($forumsort == "") {
2329         $orderby = "";
2330         $groupby = "";
2332     } else {
2333         $orderby = "ORDER BY $forumsort";
2334         $groupby = ", ".strtolower($forumsort);
2335         $groupby = str_replace('desc', '', $groupby);
2336         $groupby = str_replace('asc', '', $groupby);
2337     }
2339     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2340         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2341                   FROM {forum_posts} p
2342                        JOIN {forum_discussions} d ON p.discussion = d.id
2343                  WHERE p.parent > 0 AND d.forum = ?
2344               GROUP BY p.discussion";
2345         return $DB->get_records_sql($sql, array($forumid));
2347     } else {
2348         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2349                   FROM {forum_posts} p
2350                        JOIN {forum_discussions} d ON p.discussion = d.id
2351                  WHERE d.forum = ?
2352               GROUP BY p.discussion $groupby
2353               $orderby";
2354         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2355     }
2358 /**
2359  * @global object
2360  * @global object
2361  * @global object
2362  * @staticvar array $cache
2363  * @param object $forum
2364  * @param object $cm
2365  * @param object $course
2366  * @return mixed
2367  */
2368 function forum_count_discussions($forum, $cm, $course) {
2369     global $CFG, $DB, $USER;
2371     static $cache = array();
2373     $now = round(time(), -2); // db cache friendliness
2375     $params = array($course->id);
2377     if (!isset($cache[$course->id])) {
2378         if (!empty($CFG->forum_enabletimedposts)) {
2379             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2380             $params[] = $now;
2381             $params[] = $now;
2382         } else {
2383             $timedsql = "";
2384         }
2386         $sql = "SELECT f.id, COUNT(d.id) as dcount
2387                   FROM {forum} f
2388                        JOIN {forum_discussions} d ON d.forum = f.id
2389                  WHERE f.course = ?
2390                        $timedsql
2391               GROUP BY f.id";
2393         if ($counts = $DB->get_records_sql($sql, $params)) {
2394             foreach ($counts as $count) {
2395                 $counts[$count->id] = $count->dcount;
2396             }
2397             $cache[$course->id] = $counts;
2398         } else {
2399             $cache[$course->id] = array();
2400         }
2401     }
2403     if (empty($cache[$course->id][$forum->id])) {
2404         return 0;
2405     }
2407     $groupmode = groups_get_activity_groupmode($cm, $course);
2409     if ($groupmode != SEPARATEGROUPS) {
2410         return $cache[$course->id][$forum->id];
2411     }
2413     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2414         return $cache[$course->id][$forum->id];
2415     }
2417     require_once($CFG->dirroot.'/course/lib.php');
2419     $modinfo =& get_fast_modinfo($course);
2420     if (is_null($modinfo->groups)) {
2421         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2422     }
2424     if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2425         $mygroups = $modinfo->groups[$cm->groupingid];
2426     } else {
2427         $mygroups = false; // Will be set below
2428     }
2430     // add all groups posts
2431     if (empty($mygroups)) {
2432         $mygroups = array(-1=>-1);
2433     } else {
2434         $mygroups[-1] = -1;
2435     }
2437     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2438     $params[] = $forum->id;
2440     if (!empty($CFG->forum_enabletimedposts)) {
2441         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2442         $params[] = $now;
2443         $params[] = $now;
2444     } else {
2445         $timedsql = "";
2446     }
2448     $sql = "SELECT COUNT(d.id)
2449               FROM {forum_discussions} d
2450              WHERE d.groupid $mygroups_sql AND d.forum = ?
2451                    $timedsql";
2453     return $DB->get_field_sql($sql, $params);
2456 /**
2457  * How many posts by other users are unrated by a given user in the given discussion?
2458  *
2459  * @global object
2460  * @global object
2461  * @param int $discussionid
2462  * @param int $userid
2463  * @return mixed
2464  */
2465 function forum_count_unrated_posts($discussionid, $userid) {
2466     global $CFG, $DB;
2467     if ($posts = $DB->get_record_sql("SELECT count(*) as num
2468                                    FROM {forum_posts}
2469                                   WHERE parent > 0
2470                                     AND discussion = ?
2471                                     AND userid <> ? ", array($discussionid, $userid))) {
2473         if ($rated = $DB->get_record_sql("SELECT count(*) as num
2474                                        FROM {forum_posts} p,
2475                                             {rating} r
2476                                       WHERE p.discussion = ?
2477                                         AND p.id = r.itemid
2478                                         AND r.userid = ?", array($discussionid, $userid))) {
2479             $difference = $posts->num - $rated->num;
2480             if ($difference > 0) {
2481                 return $difference;
2482             } else {
2483                 return 0;    // Just in case there was a counting error
2484             }
2485         } else {
2486             return $posts->num;
2487         }
2488     } else {
2489         return 0;
2490     }
2493 /**
2494  * Get all discussions in a forum
2495  *
2496  * @global object
2497  * @global object
2498  * @global object
2499  * @uses CONTEXT_MODULE
2500  * @uses VISIBLEGROUPS
2501  * @param object $cm
2502  * @param string $forumsort
2503  * @param bool $fullpost
2504  * @param int $unused
2505  * @param int $limit
2506  * @param bool $userlastmodified
2507  * @param int $page
2508  * @param int $perpage
2509  * @return array
2510  */
2511 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2512     global $CFG, $DB, $USER;
2514     $timelimit = '';
2516     $now = round(time(), -2);
2517     $params = array($cm->instance);
2519     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2521     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2522         return array();
2523     }
2525     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2527         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2528             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2529             $params[] = $now;
2530             $params[] = $now;
2531             if (isloggedin()) {
2532                 $timelimit .= " OR d.userid = ?";
2533                 $params[] = $USER->id;
2534             }
2535             $timelimit .= ")";
2536         }
2537     }
2539     if ($limit > 0) {
2540         $limitfrom = 0;
2541         $limitnum  = $limit;
2542     } else if ($page != -1) {
2543         $limitfrom = $page*$perpage;
2544         $limitnum  = $perpage;
2545     } else {
2546         $limitfrom = 0;
2547         $limitnum  = 0;
2548     }
2550     $groupmode    = groups_get_activity_groupmode($cm);
2551     $currentgroup = groups_get_activity_group($cm);
2553     if ($groupmode) {
2554         if (empty($modcontext)) {
2555             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2556         }
2558         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2559             if ($currentgroup) {
2560                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2561                 $params[] = $currentgroup;
2562             } else {
2563                 $groupselect = "";
2564             }
2566         } else {
2567             //seprate groups without access all
2568             if ($currentgroup) {
2569                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2570                 $params[] = $currentgroup;
2571             } else {
2572                 $groupselect = "AND d.groupid = -1";
2573             }
2574         }
2575     } else {
2576         $groupselect = "";
2577     }
2580     if (empty($forumsort)) {
2581         $forumsort = "d.timemodified DESC";
2582     }
2583     if (empty($fullpost)) {
2584         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2585     } else {
2586         $postdata = "p.*";
2587     }
2589     if (empty($userlastmodified)) {  // We don't need to know this
2590         $umfields = "";
2591         $umtable  = "";
2592     } else {
2593         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2594         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2595     }
2597     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2598                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2599               FROM {forum_discussions} d
2600                    JOIN {forum_posts} p ON p.discussion = d.id
2601                    JOIN {user} u ON p.userid = u.id
2602                    $umtable
2603              WHERE d.forum = ? AND p.parent = 0
2604                    $timelimit $groupselect
2605           ORDER BY $forumsort";
2606     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2609 /**
2610  *
2611  * @global object
2612  * @global object
2613  * @global object
2614  * @uses CONTEXT_MODULE
2615  * @uses VISIBLEGROUPS
2616  * @param object $cm
2617  * @return array
2618  */
2619 function forum_get_discussions_unread($cm) {
2620     global $CFG, $DB, $USER;
2622     $now = round(time(), -2);
2623     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2625     $params = array();
2626     $groupmode    = groups_get_activity_groupmode($cm);
2627     $currentgroup = groups_get_activity_group($cm);
2629     if ($groupmode) {
2630         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2632         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2633             if ($currentgroup) {
2634                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2635                 $params['currentgroup'] = $currentgroup;
2636             } else {
2637                 $groupselect = "";
2638             }
2640         } else {
2641             //separate groups without access all
2642             if ($currentgroup) {
2643                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2644                 $params['currentgroup'] = $currentgroup;
2645             } else {
2646                 $groupselect = "AND d.groupid = -1";
2647             }
2648         }
2649     } else {
2650         $groupselect = "";
2651     }
2653     if (!empty($CFG->forum_enabletimedposts)) {
2654         $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2655         $params['now1'] = $now;
2656         $params['now2'] = $now;
2657     } else {
2658         $timedsql = "";
2659     }
2661     $sql = "SELECT d.id, COUNT(p.id) AS unread
2662               FROM {forum_discussions} d
2663                    JOIN {forum_posts} p     ON p.discussion = d.id
2664                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2665              WHERE d.forum = {$cm->instance}
2666                    AND p.modified >= :cutoffdate AND r.id is NULL
2667                    $groupselect
2668                    $timedsql
2669           GROUP BY d.id";
2670     $params['cutoffdate'] = $cutoffdate;
2672     if ($unreads = $DB->get_records_sql($sql, $params)) {
2673         foreach ($unreads as $unread) {
2674             $unreads[$unread->id] = $unread->unread;
2675         }
2676         return $unreads;
2677     } else {
2678         return array();
2679     }
2682 /**
2683  * @global object
2684  * @global object
2685  * @global object
2686  * @uses CONEXT_MODULE
2687  * @uses VISIBLEGROUPS
2688  * @param object $cm
2689  * @return array
2690  */
2691 function forum_get_discussions_count($cm) {
2692     global $CFG, $DB, $USER;
2694     $now = round(time(), -2);
2695     $params = array($cm->instance);
2696     $groupmode    = groups_get_activity_groupmode($cm);
2697     $currentgroup = groups_get_activity_group($cm);
2699     if ($groupmode) {
2700         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2702         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2703             if ($currentgroup) {
2704                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2705                 $params[] = $currentgroup;
2706             } else {
2707                 $groupselect = "";
2708             }
2710         } else {
2711             //seprate groups without access all
2712             if ($currentgroup) {
2713                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2714                 $params[] = $currentgroup;
2715             } else {
2716                 $groupselect = "AND d.groupid = -1";
2717             }
2718         }
2719     } else {
2720         $groupselect = "";
2721     }
2723     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2725     $timelimit = "";
2727     if (!empty($CFG->forum_enabletimedposts)) {
2729         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2731         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2732             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2733             $params[] = $now;
2734             $params[] = $now;
2735             if (isloggedin()) {
2736                 $timelimit .= " OR d.userid = ?";
2737                 $params[] = $USER->id;
2738             }
2739             $timelimit .= ")";
2740         }
2741     }
2743     $sql = "SELECT COUNT(d.id)
2744               FROM {forum_discussions} d
2745                    JOIN {forum_posts} p ON p.discussion = d.id
2746              WHERE d.forum = ? AND p.parent = 0
2747                    $groupselect $timelimit";
2749     return $DB->get_field_sql($sql, $params);
2753 /**
2754  * Get all discussions started by a particular user in a course (or group)
2755  * This function no longer used ...
2756  *
2757  * @todo Remove this function if no longer used
2758  * @global object
2759  * @global object
2760  * @param int $courseid
2761  * @param int $userid
2762  * @param int $groupid
2763  * @return array
2764  */
2765 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2766     global $CFG, $DB;
2767     $params = array($courseid, $userid);
2768     if ($groupid) {
2769         $groupselect = " AND d.groupid = ? ";
2770         $params[] = $groupid;
2771     } else  {
2772         $groupselect = "";
2773     }
2775     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2776                                    f.type as forumtype, f.name as forumname, f.id as forumid
2777                               FROM {forum_discussions} d,
2778                                    {forum_posts} p,
2779                                    {user} u,
2780                                    {forum} f
2781                              WHERE d.course = ?
2782                                AND p.discussion = d.id
2783                                AND p.parent = 0
2784                                AND p.userid = u.id
2785                                AND u.id = ?
2786                                AND d.forum = f.id $groupselect
2787                           ORDER BY p.created DESC", $params);
2790 /**
2791  * Get the list of potential subscribers to a forum.
2792  *
2793  * @param object $forumcontext the forum context.
2794  * @param integer $groupid the id of a group, or 0 for all groups.
2795  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2796  * @param string $sort sort order. As for get_users_by_capability.
2797  * @return array list of users.
2798  */
2799 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2800     return get_users_by_capability($forumcontext, 'mod/forum:initialsubscriptions', $fields, $sort, '', '', $groupid, '', false, true);
2803 /**
2804  * Returns list of user objects that are subscribed to this forum
2805  *
2806  * @global object
2807  * @global object
2808  * @param object $course the course
2809  * @param forum $forum the forum
2810  * @param integer $groupid group id, or 0 for all.
2811  * @param object $context the forum context, to save re-fetching it where possible.
2812  * @return array list of users.
2813  */
2814 function forum_subscribed_users($course, $forum, $groupid=0, $context = NULL) {
2815     global $CFG, $DB;
2816     $params = array($forum->id);
2818     if ($groupid) {
2819         $grouptables = ", {groups_members} gm ";
2820         $groupselect = "AND gm.groupid = ? AND u.id = gm.userid";
2821         $params[] = $groupid;
2822     } else  {
2823         $grouptables = '';
2824         $groupselect = '';
2825     }
2827     $fields ="u.id,
2828               u.username,
2829               u.firstname,
2830               u.lastname,
2831               u.maildisplay,
2832               u.mailformat,
2833               u.maildigest,
2834               u.emailstop,
2835               u.imagealt,
2836               u.email,
2837               u.city,
2838               u.country,
2839               u.lastaccess,
2840               u.lastlogin,
2841               u.picture,
2842               u.timezone,
2843               u.theme,
2844               u.lang,
2845               u.trackforums,
2846               u.mnethostid";
2848     if (forum_is_forcesubscribed($forum)) {
2849         if (empty($context)) {
2850             $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2851             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2852         }
2853         $sort = "u.email ASC";
2854         $results = forum_get_potential_subscribers($context, $groupid, $fields, $sort);
2855     } else {
2856         $results = $DB->get_records_sql("SELECT $fields
2857                               FROM {user} u,
2858                                    {forum_subscriptions} s $grouptables
2859                              WHERE s.forum = ?
2860                                AND s.userid = u.id
2861                                AND u.deleted = 0  $groupselect
2862                           ORDER BY u.email ASC", $params);
2863     }
2865     static $guestid = null;
2867     if (is_null($guestid)) {
2868         if ($guest = guest_user()) {
2869             $guestid = $guest->id;
2870         } else {
2871             $guestid = 0;
2872         }
2873     }
2875     // Guest user should never be subscribed to a forum.
2876     unset($results[$guestid]);
2878     return $results;
2883 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2886 /**
2887  * @global object
2888  * @global object
2889  * @param int $courseid
2890  * @param string $type
2891  */
2892 function forum_get_course_forum($courseid, $type) {
2893 // How to set up special 1-per-course forums
2894     global $CFG, $DB, $OUTPUT;
2896     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2897         // There should always only be ONE, but with the right combination of
2898         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2899         foreach ($forums as $forum) {
2900             return $forum;   // ie the first one
2901         }
2902     }
2904     // Doesn't exist, so create one now.
2905     $forum->course = $courseid;
2906     $forum->type = "$type";
2907     switch ($forum->type) {
2908         case "news":
2909             $forum->name  = get_string("namenews", "forum");
2910             $forum->intro = get_string("intronews", "forum");
2911             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2912             $forum->assessed = 0;
2913             if ($courseid == SITEID) {
2914                 $forum->name  = get_string("sitenews");
2915                 $forum->forcesubscribe = 0;
2916             }
2917             break;
2918         case "social":
2919             $forum->name  = get_string("namesocial", "forum");
2920             $forum->intro = get_string("introsocial", "forum");
2921             $forum->assessed = 0;
2922             $forum->forcesubscribe = 0;
2923             break;
2924         case "blog":
2925             $forum->name = get_string('blogforum', 'forum');
2926             $forum->intro = get_string('introblog', 'forum');
2927             $forum->assessed = 0;
2928             $forum->forcesubscribe = 0;
2929             break;
2930         default:
2931             echo $OUTPUT->notification("That forum type doesn't exist!");
2932             return false;
2933             break;
2934     }
2936     $forum->timemodified = time();
2937     $forum->id = $DB->insert_record("forum", $forum);
2939     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2940         echo $OUTPUT->notification("Could not find forum module!!");
2941         return false;
2942     }
2943     $mod = new stdClass();
2944     $mod->course = $courseid;
2945     $mod->module = $module->id;
2946     $mod->instance = $forum->id;
2947     $mod->section = 0;
2948     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
2949         echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
2950         return false;
2951     }
2952     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
2953         echo $OUTPUT->notification("Could not add the new course module to that section");
2954         return false;
2955     }
2956     $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule));
2958     include_once("$CFG->dirroot/course/lib.php");
2959     rebuild_course_cache($courseid);
2961     return $DB->get_record("forum", array("id" => "$forum->id"));
2965 /**
2966  * Given the data about a posting, builds up the HTML to display it and
2967  * returns the HTML in a string.  This is designed for sending via HTML email.
2968  *
2969  * @global object
2970  * @param object $course
2971  * @param object $cm
2972  * @param object $forum
2973  * @param object $discussion
2974  * @param object $post
2975  * @param object $userform
2976  * @param object $userto
2977  * @param bool $ownpost
2978  * @param bool $reply
2979  * @param bool $link
2980  * @param bool $rate
2981  * @param string $footer
2982  * @return string
2983  */
2984 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
2985                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
2987     global $CFG, $OUTPUT;
2989     if (!isset($userto->viewfullnames[$forum->id])) {
2990         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
2991             print_error('invalidcoursemodule');
2992         }
2993         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2994         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
2995     } else {
2996         $viewfullnames = $userto->viewfullnames[$forum->id];
2997     }
2999     // format the post body
3000     $options = new stdClass();
3001     $options->para = true;
3002     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3004     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3006     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3007     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3008     $output .= '</td>';
3010     if ($post->parent) {
3011         $output .= '<td class="topic">';
3012     } else {
3013         $output .= '<td class="topic starter">';
3014     }
3015     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3017     $fullname = fullname($userfrom, $viewfullnames);
3018     $by = new stdClass();
3019     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3020     $by->date = userdate($post->modified, '', $userto->timezone);
3021     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3023     $output .= '</td></tr>';
3025     $output .= '<tr><td class="left side" valign="top">';
3027     if (isset($userfrom->groups)) {
3028         $groups = $userfrom->groups[$forum->id];
3029     } else {
3030         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
3031             print_error('invalidcoursemodule');
3032         }
3033         $group = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3034     }
3036     if ($groups) {
3037         $output .= print_group_picture($groups, $course->id, false, true, true);
3038     } else {
3039         $output .= '&nbsp;';
3040     }
3042     $output .= '</td><td class="content">';
3044     $attachments = forum_print_attachments($post, $cm, 'html');
3045     if ($attachments !== '') {
3046         $output .= '<div class="attachments">';
3047         $output .= $attachments;
3048         $output .= '</div>';
3049     }
3051     $output .= $formattedtext;
3053 // Commands
3054     $commands = array();
3056     if ($post->parent) {
3057         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3058                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3059     }
3061     if ($reply) {
3062         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3063                       get_string('reply', 'forum').'</a>';
3064     }
3066     $output .= '<div class="commands">';
3067     $output .= implode(' | ', $commands);
3068     $output .= '</div>';
3070 // Context link to post if required
3071     if ($link) {
3072         $output .= '<div class="link">';
3073         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3074                      get_string('postincontext', 'forum').'</a>';
3075         $output .= '</div>';
3076     }
3078     if ($footer) {
3079         $output .= '<div class="footer">'.$footer.'</div>';
3080     }
3081     $output .= '</td></tr></table>'."\n\n";
3083     return $output;
3086 /**
3087  * Print a forum post
3088  *
3089  * @global object
3090  * @global object
3091  * @uses FORUM_MODE_THREADED
3092  * @uses PORTFOLIO_FORMAT_PLAINHTML
3093  * @uses PORTFOLIO_FORMAT_FILE
3094  * @uses PORTFOLIO_FORMAT_RICHHTML
3095  * @uses PORTFOLIO_ADD_TEXT_LINK
3096  * @uses CONTEXT_MODULE
3097  * @param object $post The post to print.
3098  * @param object $discussion
3099  * @param object $forum
3100  * @param object $cm
3101  * @param object $course
3102  * @param boolean $ownpost Whether this post belongs to the current user.
3103  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3104  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3105  * @param string $footer Extra stuff to print after the message.
3106  * @param string $highlight Space-separated list of terms to highlight.
3107  * @param int $post_read true, false or -99. If we already know whether this user
3108  *          has read this post, pass that in, otherwise, pass in -99, and this
3109  *          function will work it out.
3110  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3111  *          the current user can't see this post, if this argument is true
3112  *          (the default) then print a dummy 'you can't see this post' post.
3113  *          If false, don't output anything at all.
3114  * @param bool|null $istracked
3115  * @return void
3116  */
3117 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3118                           $footer="", $highlight="", $post_read=null, $dummyifcantsee=true, $istracked=null) {
3120     global $USER, $CFG, $OUTPUT;
3122     static $stredit, $strdelete, $strreply, $strparent, $strprune;
3123     static $strpruneheading, $displaymode;
3124     static $strmarkread, $strmarkunread;
3126     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3128     $post->course = $course->id;
3129     $post->forum  = $forum->id;
3130     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3132     // caching
3133     if (!isset($cm->cache)) {
3134         $cm->cache = new stdClass();
3135     }
3137     if (!isset($cm->cache->caps)) {
3138         $cm->cache->caps = array();
3139         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3140         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3141         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3142         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3143         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3144         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3145         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3146         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3147         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3148     }
3150     if (!isset($cm->uservisible)) {
3151         $cm->uservisible = coursemodule_visible_for_user($cm);
3152     }
3154     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3155         if (!$dummyifcantsee) {
3156             return;
3157         }
3158         echo '<a id="p'.$post->id.'"></a>';
3159         echo '<table cellspacing="0" class="forumpost">';
3160         echo '<tr class="header"><td class="picture left">';
3161         //        print_user_picture($post->userid, $courseid, $post->picture);
3162         echo '</td>';
3163         if ($post->parent) {
3164             echo '<td class="topic">';
3165         } else {
3166             echo '<td class="topic starter">';
3167         }
3168         echo '<div class="subject">'.get_string('forumsubjecthidden','forum').'</div>';
3169         echo '<div class="author">';
3170         print_string('forumauthorhidden','forum');
3171         echo '</div></td></tr>';
3173         echo '<tr><td class="left side">';
3174         echo '&nbsp;';
3176         // Actual content
3178         echo '</td><td class="content">'."\n";
3179         echo get_string('forumbodyhidden','forum');
3180         echo '</td></tr></table>';
3181         return;
3182     }
3184     if (empty($stredit)) {
3185         $stredit         = get_string('edit', 'forum');
3186         $strdelete       = get_string('delete', 'forum');
3187         $strreply        = get_string('reply', 'forum');
3188         $strparent       = get_string('parent', 'forum');
3189         $strpruneheading = get_string('pruneheading', 'forum');
3190         $strprune        = get_string('prune', 'forum');
3191         $displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3192         $strmarkread     = get_string('markread', 'forum');
3193         $strmarkunread   = get_string('markunread', 'forum');
3195     }
3197     $read_style = '';
3198     // ignore trackign status if not tracked or tracked param missing
3199     if ($istracked) {
3200         if (is_null($post_read)) {
3201             debugging('fetching post_read info');
3202             $post_read = forum_tp_is_post_read($USER->id, $post);
3203         }
3205         if ($post_read) {
3206             $read_style = ' read';
3207         } else {
3208             $read_style = ' unread';
3209             echo '<a name="unread"></a>';
3210         }
3211     }
3213     echo '<a id="p'.$post->id.'"></a>';
3214     echo '<table cellspacing="0" class="forumpost'.$read_style.'">';
3216     // Picture
3217     $postuser = new stdClass();
3218     $postuser->id        = $post->userid;
3219     $postuser->firstname = $post->firstname;
3220     $postuser->lastname  = $post->lastname;
3221     $postuser->imagealt  = $post->imagealt;
3222     $postuser->picture   = $post->picture;
3223     $postuser->email     = $post->email;
3225     echo '<tr class="header"><td class="picture left">';
3226     echo $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3227     echo '</td>';
3229     if ($post->parent) {
3230         echo '<td class="topic">';
3231     } else {
3232         echo '<td class="topic starter">';
3233     }
3235     if (!empty($post->subjectnoformat)) {
3236         echo '<div class="subject">'.$post->subject.'</div>';
3237     } else {
3238         echo '<div class="subject">'.format_string($post->subject).'</div>';
3239     }
3241     echo '<div class="author">';
3242     $fullname = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3243     $by = new stdClass();
3244     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.
3245                 $post->userid.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3246     $by->date = userdate($post->modified);
3247     print_string('bynameondate', 'forum', $by);
3248     echo '</div></td></tr>';
3250     echo '<tr><td class="left side">';
3251     if (isset($cm->cache->usersgroups)) {
3252         $groups = array();
3253         if (isset($cm->cache->usersgroups[$post->userid])) {
3254             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3255                 $groups[$gid] = $cm->cache->groups[$gid];
3256             }
3257         }
3258     } else {
3259         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3260     }
3262     if ($groups) {
3263         print_group_picture($groups, $course->id, false, false, true);
3264     } else {
3265         echo '&nbsp;';
3266     }
3268 // Actual content
3270     echo '</td><td class="content">'."\n";
3272     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3274     if ($attachments !== '') {
3275         echo '<div class="attachments">';
3276         echo $attachments;
3277         echo '</div>';
3278     }
3280     $options = new stdClass();
3281     $options->para    = false;
3282     $options->trusted = $post->messagetrust;
3283     if ($link and (strlen(strip_tags($post->message)) > $CFG->forum_longpost)) {
3284         // Print shortened version
3285         echo format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3286         $numwords = count_words(strip_tags($post->message));
3287         echo '<div class="posting shortenedpost"><a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3288         echo get_string('readtherest', 'forum');
3289         echo '</a> ('.get_string('numwords', '', $numwords).')...</div>';
3290     } else {
3291         // Print whole message
3292         echo '<div class="posting fullpost">';
3293         if ($highlight) {
3294             echo highlight($highlight, format_text($post->message, $post->messageformat, $options, $course->id));
3295         } else {
3296             echo format_text($post->message, $post->messageformat, $options, $course->id);
3297         }
3298         echo '</div>';
3299         echo $attachedimages;
3300     }
3303 // Commands
3305     $commands = array();
3307     if ($istracked) {
3308         // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3309         // Don't display the mark read / unread controls in this case.
3310         if ($CFG->forum_usermarksread and isloggedin()) {
3311             if ($post_read) {
3312                 $mcmd = '&amp;mark=unread&amp;postid='.$post->id;
3313                 $mtxt = $strmarkunread;
3314             } else {
3315                 $mcmd = '&amp;mark=read&amp;postid='.$post->id;
3316                 $mtxt = $strmarkread;
3317             }
3318             if ($displaymode == FORUM_MODE_THREADED) {
3319                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3320                               $post->discussion.'&amp;parent='.$post->id.$mcmd.'">'.$mtxt.'</a>';
3321             } else {
3322                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3323                               $post->discussion.$mcmd.'#p'.$post->id.'">'.$mtxt.'</a>';
3324             }
3325         }
3326     }
3328     if ($post->parent) {  // Zoom in to the parent specifically
3329         if ($displaymode == FORUM_MODE_THREADED) {
3330             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3331                       $post->discussion.'&amp;parent='.$post->parent.'">'.$strparent.'</a>';
3332         } else {
3333             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3334                       $post->discussion.'#p'.$post->parent.'">'.$strparent.'</a>';
3335         }
3336     }
3338     $age = time() - $post->created;
3339     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3340     if (!$post->parent and $forum->type == 'news' and $discussion->timestart > time()) {
3341         $age = 0;
3342     }
3343     $editanypost = $cm->cache->caps['mod/forum:editanypost'];
3345     if ($ownpost or $editanypost) {
3346         if (($age < $CFG->maxeditingtime) or $editanypost) {
3347             $commands[] =  '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?edit='.$post->id.'">'.$stredit.'</a>';
3348         }
3349     }
3351     if ($cm->cache->caps['mod/forum:splitdiscussions']
3352                 && $post->parent && $forum->type != 'single') {
3354         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?prune='.$post->id.
3355                       '" title="'.$strpruneheading.'">'.$strprune.'</a>';
3356     }
3358     if (($ownpost and $age < $CFG->maxeditingtime
3359                 and $cm->cache->caps['mod/forum:deleteownpost'])
3360                 or $cm->cache->caps['mod/forum:deleteanypost']) {
3361         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?delete='.$post->id.'">'.$strdelete.'</a>';
3362     }
3364     if ($reply) {
3365         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.$strreply.'</a>';
3366     }
3368     if ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost'])) {
3369         $p = array(
3370             'postid' => $post->id,
3371         );
3372         require_once($CFG->libdir.'/portfoliolib.php');
3373         $button = new portfolio_add_button();
3374         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php');
3375         if (empty($attachments)) {
3376             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3377         } else {
3378             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3379         }
3381         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3382         if (!empty($porfoliohtml)) {
3383             $commands[] = $porfoliohtml;
3384         }
3385     }
3387     echo '<div class="commands">';
3388     echo implode(' | ', $commands);
3389     echo '</div>';
3392 // Ratings
3393     if (!empty($post->rating)) {
3394         echo html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
3395     }
3397 // Link to post if required
3399     if ($link) {
3400         echo '<div class="link">';
3401         if ($post->replies == 1) {
3402             $replystring = get_string('repliesone', 'forum', $post->replies);
3403         } else {
3404             $replystring = get_string('repliesmany', 'forum', $post->replies);
3405         }
3406         echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.
3407              get_string('discussthistopic', 'forum').'</a>&nbsp;('.$replystring.')';
3408         echo '</div>';
3409     }
3411     if ($footer) {
3412         echo '<div class="footer">'.$footer.'</div>';
3413     }
3414     echo '</td></tr></table>'."\n\n";
3416     if ($istracked && !$CFG->forum_usermarksread && !$post_read) {
3417         forum_tp_mark_post_read($USER->id, $post, $forum->id);
3418     }
3421 /**
3422  * Return rating related permissions
3423  * @param string $options the context id
3424  * @return array an associative array of the user's rating permissions
3425  */
3426 function forum_rating_permissions($contextid) {
3427     $context = get_context_instance_by_id($contextid);
3429     if (!$context) {
3430         print_error('invalidcontext');
3431         return null;
3432     } else {
3433         return array('view'=>has_capability('mod/forum:viewrating',$context), 'viewany'=>has_capability('mod/forum:viewanyrating',$context), 'viewall'=>has_capability('mod/forum:viewallratings',$context), 'rate'=>has_capability('mod/forum:rate',$context));
3434     }
3437 /**
3438  * Returns the names of the table and columns necessary to check items for ratings
3439  * @return array an array containing the item table, item id and user id columns
3440  */
3441 function forum_rating_item_check_info() {
3442     return array('forum_posts','id','userid');
3446 /**
3447  * This function prints the overview of a discussion in the forum listing.
3448  * It needs some discussion information and some post information, these
3449  * happen to be combined for efficiency in the $post parameter by the function
3450  * that calls this one: forum_print_latest_discussions()
3451  *
3452  * @global object
3453  * @global object
3454  * @param object $post The post object (passed by reference for speed).
3455  * @param object $forum The forum object.
3456  * @param int $group Current group.
3457  * @param string $datestring Format to use for the dates.
3458  * @param boolean $cantrack Is tracking enabled for this forum.
3459  * @param boolean $forumtracked Is the user tracking this forum.
3460  * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
3461  */
3462 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
3463                                         $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) {
3465     global $USER, $CFG, $OUTPUT;
3467     static $rowcount;
3468     static $strmarkalldread;
3470     if (empty($modcontext)) {
3471         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3472             print_error('invalidcoursemodule');
3473         }
3474         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3475     }
3477     if (!isset($rowcount)) {
3478         $rowcount = 0;
3479         $strmarkalldread = get_string('markalldread', 'forum');
3480     } else {
3481         $rowcount = ($rowcount + 1) % 2;
3482     }
3484     $post->subject = format_string($post->subject,true);
3486     echo "\n\n";
3487     echo '<tr class="discussion r'.$rowcount.'">';
3489     // Topic
3490     echo '<td class="topic starter">';
3491     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.$post->subject.'</a>';
3492     echo "</td>\n";
3494     // Picture
3495     $postuser = new stdClass();
3496     $postuser->id = $post->userid;
3497     $postuser->firstname = $post->firstname;
3498     $postuser->lastname = $post->lastname;
3499     $postuser->imagealt = $post->imagealt;
3500     $postuser->picture = $post->picture;
3501     $postuser->email = $post->email;