MDL-24996 - make forum_print_post() to specify context on format_text() calls
[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 find 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;
638                 $eventdata->notification = 1;
640                 $smallmessagestrings = new stdClass();
641                 $smallmessagestrings->user = fullname($userfrom);
642                 $smallmessagestrings->forumname = "{$course->shortname}: ".format_string($forum->name,true).": ".$discussion->name;
643                 $smallmessagestrings->message = $post->message;
644                 $eventdata->smallmessage = get_string('smallmessage', 'forum', $smallmessagestrings);
646                 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
647                 $eventdata->contexturlname = $discussion->name;
649                 $mailresult = message_send($eventdata);
650                 if (!$mailresult){
651                     mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
652                          " ($userto->email) .. not trying again.");
653                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
654                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
655                     $errorcount[$post->id]++;
656                 } else if ($mailresult === 'emailstop') {
657                     // should not be reached anymore - see check above
658                     mtrace("Error: mod/forum/lib.php forum_cron(): received 'emailstop' while sending out mail for id $post->id to user $userto->id ($userto->email)");
659                 } else {
660                     $mailcount[$post->id]++;
662                 // Mark post as read if forum_usermarksread is set off
663                     if (!$CFG->forum_usermarksread) {
664                         $userto->markposts[$post->id] = $post->id;
665                     }
666                 }
668                 mtrace('post '.$post->id. ': '.$post->subject);
669             }
671             // mark processed posts as read
672             forum_tp_mark_posts_read($userto, $userto->markposts);
673         }
674     }
676     if ($posts) {
677         foreach ($posts as $post) {
678             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
679             if ($errorcount[$post->id]) {
680                 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
681             }
682         }
683     }
685     // release some memory
686     unset($subscribedusers);
687     unset($mailcount);
688     unset($errorcount);
690     cron_setup_user();
692     $sitetimezone = $CFG->timezone;
694     // Now see if there are any digest mails waiting to be sent, and if we should send them
696     mtrace('Starting digest processing...');
698     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
700     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
701         set_config('digestmailtimelast', 0);
702     }
704     $timenow = time();
705     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
707     // Delete any really old ones (normally there shouldn't be any)
708     $weekago = $timenow - (7 * 24 * 3600);
709     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
710     mtrace ('Cleaned old digest records');
712     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
714         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
716         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
718         if ($digestposts_rs->valid()) {
720             // We have work to do
721             $usermailcount = 0;
723             //caches - reuse the those filled before too
724             $discussionposts = array();
725             $userdiscussions = array();
727             foreach ($digestposts_rs as $digestpost) {
728                 if (!isset($users[$digestpost->userid])) {
729                     if ($user = $DB->get_record('user', array('id' => $digestpost->userid))) {
730                         $users[$digestpost->userid] = $user;
731                     } else {
732                         continue;
733                     }
734                 }
735                 $postuser = $users[$digestpost->userid];
736                 if ($postuser->emailstop) {
737                     if (!empty($CFG->forum_logblocked)) {
738                         add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
739                     }
740                     continue;
741                 }
743                 if (!isset($posts[$digestpost->postid])) {
744                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
745                         $posts[$digestpost->postid] = $post;
746                     } else {
747                         continue;
748                     }
749                 }
750                 $discussionid = $digestpost->discussionid;
751                 if (!isset($discussions[$discussionid])) {
752                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
753                         $discussions[$discussionid] = $discussion;
754                     } else {
755                         continue;
756                     }
757                 }
758                 $forumid = $discussions[$discussionid]->forum;
759                 if (!isset($forums[$forumid])) {
760                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
761                         $forums[$forumid] = $forum;
762                     } else {
763                         continue;
764                     }
765                 }
767                 $courseid = $forums[$forumid]->course;
768                 if (!isset($courses[$courseid])) {
769                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
770                         $courses[$courseid] = $course;
771                     } else {
772                         continue;
773                     }
774                 }
776                 if (!isset($coursemodules[$forumid])) {
777                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
778                         $coursemodules[$forumid] = $cm;
779                     } else {
780                         continue;
781                     }
782                 }
783                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
784                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
785             }
786             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
788             // Data collected, start sending out emails to each user
789             foreach ($userdiscussions as $userid => $thesediscussions) {
791                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
793                 cron_setup_user();
795                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
797                 // First of all delete all the queue entries for this user
798                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
799                 $userto = $users[$userid];
801                 // Override the language and timezone of the "current" user, so that
802                 // mail is customised for the receiver.
803                 cron_setup_user($userto);
805                 // init caches
806                 $userto->viewfullnames = array();
807                 $userto->canpost       = array();
808                 $userto->markposts     = array();
810                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
812                 $headerdata = new stdClass();
813                 $headerdata->sitename = format_string($site->fullname, true);
814                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
816                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
817                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
819                 $posthtml = "<head>";
820 /*                foreach ($CFG->stylesheets as $stylesheet) {
821                     //TODO: MDL-21120
822                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
823                 }*/
824                 $posthtml .= "</head>\n<body id=\"email\">\n";
825                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
827                 foreach ($thesediscussions as $discussionid) {
829                     @set_time_limit(120);   // to be reset for each post
831                     $discussion = $discussions[$discussionid];
832                     $forum      = $forums[$discussion->forum];
833                     $course     = $courses[$forum->course];
834                     $cm         = $coursemodules[$forum->id];
836                     //override language
837                     cron_setup_user($userto, $course);
839                     // Fill caches
840                     if (!isset($userto->viewfullnames[$forum->id])) {
841                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
842                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
843                     }
844                     if (!isset($userto->canpost[$discussion->id])) {
845                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
846                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
847                     }
849                     $strforums      = get_string('forums', 'forum');
850                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
851                     $canreply       = $userto->canpost[$discussion->id];
853                     $posttext .= "\n \n";
854                     $posttext .= '=====================================================================';
855                     $posttext .= "\n \n";
856                     $posttext .= "$course->shortname -> $strforums -> ".format_string($forum->name,true);
857                     if ($discussion->name != $forum->name) {
858                         $posttext  .= " -> ".format_string($discussion->name,true);
859                     }
860                     $posttext .= "\n";
862                     $posthtml .= "<p><font face=\"sans-serif\">".
863                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> -> ".
864                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
865                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
866                     if ($discussion->name == $forum->name) {
867                         $posthtml .= "</font></p>";
868                     } else {
869                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
870                     }
871                     $posthtml .= '<p>';
873                     $postsarray = $discussionposts[$discussionid];
874                     sort($postsarray);
876                     foreach ($postsarray as $postid) {
877                         $post = $posts[$postid];
879                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
880                             $userfrom = $users[$post->userid];
881                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
882                             $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
883                         } else {
884                             mtrace('Could not find user '.$post->userid);
885                             continue;
886                         }
888                         if (!isset($userfrom->groups[$forum->id])) {
889                             if (!isset($userfrom->groups)) {
890                                 $userfrom->groups = array();
891                                 $users[$userfrom->id]->groups = array();
892                             }
893                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
894                             $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
895                         }
897                         $userfrom->customheaders = array ("Precedence: Bulk");
899                         if ($userto->maildigest == 2) {
900                             // Subjects only
901                             $by = new stdClass();
902                             $by->name = fullname($userfrom);
903                             $by->date = userdate($post->modified);
904                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
905                             $posttext .= "\n---------------------------------------------------------------------";
907                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
908                             $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>';
910                         } else {
911                             // The full treatment
912                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
913                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
915                         // Create an array of postid's for this user to mark as read.
916                             if (!$CFG->forum_usermarksread) {
917                                 $userto->markposts[$post->id] = $post->id;
918                             }
919                         }
920                     }
921                     if ($canunsubscribe) {
922                         $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>";
923                     } else {
924                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
925                     }
926                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
927                 }
928                 $posthtml .= '</body>';
930                 if (empty($userto->mailformat) || $userto->mailformat != 1) {
931                     // This user DOESN'T want to receive HTML
932                     $posthtml = '';
933                 }
935                 $attachment = $attachname='';
936                 $usetrueaddress = true;
937                 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname, $usetrueaddress, $CFG->forum_replytouser);
939                 if (!$mailresult) {
940                     mtrace("ERROR!");
941                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
942                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
943                 } else if ($mailresult === 'emailstop') {
944                     // should not happen anymore - see check above
945                 } else {
946                     mtrace("success.");
947                     $usermailcount++;
949                     // Mark post as read if forum_usermarksread is set off
950                     forum_tp_mark_posts_read($userto, $userto->markposts);
951                 }
952             }
953         }
954     /// We have finishied all digest emails, update $CFG->digestmailtimelast
955         set_config('digestmailtimelast', $timenow);
956     }
958     cron_setup_user();
960     if (!empty($usermailcount)) {
961         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
962     }
964     if (!empty($CFG->forum_lastreadclean)) {
965         $timenow = time();
966         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
967             set_config('forum_lastreadclean', $timenow);
968             mtrace('Removing old forum read tracking info...');
969             forum_tp_clean_read_records();
970         }
971     } else {
972         set_config('forum_lastreadclean', time());
973     }
976     return true;
979 /**
980  * Builds and returns the body of the email notification in plain text.
981  *
982  * @global object
983  * @global object
984  * @uses CONTEXT_MODULE
985  * @param object $course
986  * @param object $cm
987  * @param object $forum
988  * @param object $discussion
989  * @param object $post
990  * @param object $userfrom
991  * @param object $userto
992  * @param boolean $bare
993  * @return string The email body in plain text format.
994  */
995 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
996     global $CFG, $USER;
998     if (!isset($userto->viewfullnames[$forum->id])) {
999         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
1000         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
1001     } else {
1002         $viewfullnames = $userto->viewfullnames[$forum->id];
1003     }
1005     if (!isset($userto->canpost[$discussion->id])) {
1006         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
1007         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
1008     } else {
1009         $canreply = $userto->canpost[$discussion->id];
1010     }
1012     $by = New stdClass;
1013     $by->name = fullname($userfrom, $viewfullnames);
1014     $by->date = userdate($post->modified, "", $userto->timezone);
1016     $strbynameondate = get_string('bynameondate', 'forum', $by);
1018     $strforums = get_string('forums', 'forum');
1020     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1022     $posttext = '';
1024     if (!$bare) {
1025         $posttext  = "$course->shortname -> $strforums -> ".format_string($forum->name,true);
1027         if ($discussion->name != $forum->name) {
1028             $posttext  .= " -> ".format_string($discussion->name,true);
1029         }
1030     }
1032     $posttext .= "\n---------------------------------------------------------------------\n";
1033     $posttext .= format_string($post->subject,true);
1034     if ($bare) {
1035         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1036     }
1037     $posttext .= "\n".$strbynameondate."\n";
1038     $posttext .= "---------------------------------------------------------------------\n";
1039     $posttext .= format_text_email($post->message, $post->messageformat);
1040     $posttext .= "\n\n";
1041     $posttext .= forum_print_attachments($post, $cm, "text");
1043     if (!$bare && $canreply) {
1044         $posttext .= "---------------------------------------------------------------------\n";
1045         $posttext .= get_string("postmailinfo", "forum", $course->shortname)."\n";
1046         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1047     }
1048     if (!$bare && $canunsubscribe) {
1049         $posttext .= "\n---------------------------------------------------------------------\n";
1050         $posttext .= get_string("unsubscribe", "forum");
1051         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1052     }
1054     return $posttext;
1057 /**
1058  * Builds and returns the body of the email notification in html format.
1059  *
1060  * @global object
1061  * @param object $course
1062  * @param object $cm
1063  * @param object $forum
1064  * @param object $discussion
1065  * @param object $post
1066  * @param object $userfrom
1067  * @param object $userto
1068  * @return string The email text in HTML format
1069  */
1070 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1071     global $CFG;
1073     if ($userto->mailformat != 1) {  // Needs to be HTML
1074         return '';
1075     }
1077     if (!isset($userto->canpost[$discussion->id])) {
1078         $canreply = forum_user_can_post($forum, $discussion, $userto);
1079     } else {
1080         $canreply = $userto->canpost[$discussion->id];
1081     }
1083     $strforums = get_string('forums', 'forum');
1084     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1086     $posthtml = '<head>';
1087 /*    foreach ($CFG->stylesheets as $stylesheet) {
1088         //TODO: MDL-21120
1089         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1090     }*/
1091     $posthtml .= '</head>';
1092     $posthtml .= "\n<body id=\"email\">\n\n";
1094     $posthtml .= '<div class="navbar">'.
1095     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$course->shortname.'</a> &raquo; '.
1096     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1097     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1098     if ($discussion->name == $forum->name) {
1099         $posthtml .= '</div>';
1100     } else {
1101         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1102                      format_string($discussion->name,true).'</a></div>';
1103     }
1104     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1106     if ($canunsubscribe) {
1107         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1108                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1109                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1110     }
1112     $posthtml .= '</body>';
1114     return $posthtml;
1118 /**
1119  *
1120  * @param object $course
1121  * @param object $user
1122  * @param object $mod TODO this is not used in this function, refactor
1123  * @param object $forum
1124  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1125  */
1126 function forum_user_outline($course, $user, $mod, $forum) {
1127     global $CFG;
1128     require_once("$CFG->libdir/gradelib.php");
1129     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1130     if (empty($grades->items[0]->grades)) {
1131         $grade = false;
1132     } else {
1133         $grade = reset($grades->items[0]->grades);
1134     }
1136     $count = forum_count_user_posts($forum->id, $user->id);
1138     if ($count && $count->postcount > 0) {
1139         $result = new stdClass();
1140         $result->info = get_string("numposts", "forum", $count->postcount);
1141         $result->time = $count->lastpost;
1142         if ($grade) {
1143             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1144         }
1145         return $result;
1146     } else if ($grade) {
1147         $result = new stdClass();
1148         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1149         $result->time = $grade->dategraded;
1150         return $result;
1151     }
1152     return NULL;
1156 /**
1157  * @global object
1158  * @global object
1159  * @param object $coure
1160  * @param object $user
1161  * @param object $mod
1162  * @param object $forum
1163  */
1164 function forum_user_complete($course, $user, $mod, $forum) {
1165     global $CFG,$USER, $OUTPUT;
1166     require_once("$CFG->libdir/gradelib.php");
1168     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1169     if (!empty($grades->items[0]->grades)) {
1170         $grade = reset($grades->items[0]->grades);
1171         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1172         if ($grade->str_feedback) {
1173             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1174         }
1175     }
1177     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1179         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1180             print_error('invalidcoursemodule');
1181         }
1182         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1184         foreach ($posts as $post) {
1185             if (!isset($discussions[$post->discussion])) {
1186                 continue;
1187             }
1188             $discussion = $discussions[$post->discussion];
1190             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1191         }
1192     } else {
1193         echo "<p>".get_string("noposts", "forum")."</p>";
1194     }
1202 /**
1203  * @global object
1204  * @global object
1205  * @global object
1206  * @param array $courses
1207  * @param array $htmlarray
1208  */
1209 function forum_print_overview($courses,&$htmlarray) {
1210     global $USER, $CFG, $DB, $SESSION;
1212     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1213         return array();
1214     }
1216     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1217         return;
1218     }
1221     // get all forum logs in ONE query (much better!)
1222     $params = array();
1223     $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {log} l "
1224         ." JOIN {course_modules} cm ON cm.id = cmid "
1225         ." WHERE (";
1226     foreach ($courses as $course) {
1227         $sql .= '(l.course = ? AND l.time > ?) OR ';
1228         $params[] = $course->id;
1229         $params[] = $course->lastaccess;
1230     }
1231     $sql = substr($sql,0,-3); // take off the last OR
1233     $sql .= ") AND l.module = 'forum' AND action = 'add post' "
1234         ." AND userid != ? GROUP BY cmid,l.course,instance";
1236     $params[] = $USER->id;
1238     if (!$new = $DB->get_records_sql($sql, $params)) {
1239         $new = array(); // avoid warnings
1240     }
1242     // also get all forum tracking stuff ONCE.
1243     $trackingforums = array();
1244     foreach ($forums as $forum) {
1245         if (forum_tp_can_track_forums($forum)) {
1246             $trackingforums[$forum->id] = $forum;
1247         }
1248     }
1250     if (count($trackingforums) > 0) {
1251         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1252         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1253             ' FROM {forum_posts} p '.
1254             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1255             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1256         $params = array($USER->id);
1258         foreach ($trackingforums as $track) {
1259             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1260             $params[] = $track->id;
1261             if (isset($SESSION->currentgroup[$track->course])) {
1262                 $groupid =  $SESSION->currentgroup[$track->course];
1263             } else {
1264                 $groupid = groups_get_all_groups($track->course, $USER->id);
1265                 if (is_array($groupid)) {
1266                     $groupid = array_shift(array_keys($groupid));
1267                     $SESSION->currentgroup[$track->course] = $groupid;
1268                 } else {
1269                     $groupid = 0;
1270                 }
1271             }
1272             $params[] = $groupid;
1273         }
1274         $sql = substr($sql,0,-3); // take off the last OR
1275         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1276         $params[] = $cutoffdate;
1278         if (!$unread = $DB->get_records_sql($sql, $params)) {
1279             $unread = array();
1280         }
1281     } else {
1282         $unread = array();
1283     }
1285     if (empty($unread) and empty($new)) {
1286         return;
1287     }
1289     $strforum = get_string('modulename','forum');
1290     $strnumunread = get_string('overviewnumunread','forum');
1291     $strnumpostssince = get_string('overviewnumpostssince','forum');
1293     foreach ($forums as $forum) {
1294         $str = '';
1295         $count = 0;
1296         $thisunread = 0;
1297         $showunread = false;
1298         // either we have something from logs, or trackposts, or nothing.
1299         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1300             $count = $new[$forum->id]->count;
1301         }
1302         if (array_key_exists($forum->id,$unread)) {
1303             $thisunread = $unread[$forum->id]->count;
1304             $showunread = true;
1305         }
1306         if ($count > 0 || $thisunread > 0) {
1307             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1308                 $forum->name.'</a></div>';
1309             $str .= '<div class="info">';
1310             $str .= $count.' '.$strnumpostssince;
1311             if (!empty($showunread)) {
1312                 $str .= '<br />'.$thisunread .' '.$strnumunread;
1313             }
1314             $str .= '</div></div>';
1315         }
1316         if (!empty($str)) {
1317             if (!array_key_exists($forum->course,$htmlarray)) {
1318                 $htmlarray[$forum->course] = array();
1319             }
1320             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1321                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1322             }
1323             $htmlarray[$forum->course]['forum'] .= $str;
1324         }
1325     }
1328 /**
1329  * Given a course and a date, prints a summary of all the new
1330  * messages posted in the course since that date
1331  *
1332  * @global object
1333  * @global object
1334  * @global object
1335  * @uses CONTEXT_MODULE
1336  * @uses VISIBLEGROUPS
1337  * @param object $course
1338  * @param bool $viewfullnames capability
1339  * @param int $timestart
1340  * @return bool success
1341  */
1342 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1343     global $CFG, $USER, $DB, $OUTPUT;
1345     // do not use log table if possible, it may be huge and is expensive to join with other tables
1347     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1348                                               d.timestart, d.timeend, d.userid AS duserid,
1349                                               u.firstname, u.lastname, u.email, u.picture
1350                                          FROM {forum_posts} p
1351                                               JOIN {forum_discussions} d ON d.id = p.discussion
1352                                               JOIN {forum} f             ON f.id = d.forum
1353                                               JOIN {user} u              ON u.id = p.userid
1354                                         WHERE p.created > ? AND f.course = ?
1355                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1356          return false;
1357     }
1359     $modinfo =& get_fast_modinfo($course);
1361     $groupmodes = array();
1362     $cms    = array();
1364     $strftimerecent = get_string('strftimerecent');
1366     $printposts = array();
1367     foreach ($posts as $post) {
1368         if (!isset($modinfo->instances['forum'][$post->forum])) {
1369             // not visible
1370             continue;
1371         }
1372         $cm = $modinfo->instances['forum'][$post->forum];
1373         if (!$cm->uservisible) {
1374             continue;
1375         }
1376         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1378         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1379             continue;
1380         }
1382         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1383           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1384             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1385                 continue;
1386             }
1387         }
1389         $groupmode = groups_get_activity_groupmode($cm, $course);
1391         if ($groupmode) {
1392             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1393                 // oki (Open discussions have groupid -1)
1394             } else {
1395                 // separate mode
1396                 if (isguestuser()) {
1397                     // shortcut
1398                     continue;
1399                 }
1401                 if (is_null($modinfo->groups)) {
1402                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1403                 }
1405                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1406                     continue;
1407                 }
1408             }
1409         }
1411         $printposts[] = $post;
1412     }
1413     unset($posts);
1415     if (!$printposts) {
1416         return false;
1417     }
1419     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1420     echo "\n<ul class='unlist'>\n";
1422     foreach ($printposts as $post) {
1423         $subjectclass = empty($post->parent) ? ' bold' : '';
1425         echo '<li><div class="head">'.
1426                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1427                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1428              '</div>';
1429         echo '<div class="info'.$subjectclass.'">';
1430         if (empty($post->parent)) {
1431             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1432         } else {
1433             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1434         }
1435         $post->subject = break_up_long_words(format_string($post->subject, true));
1436         echo $post->subject;
1437         echo "</a>\"</div></li>\n";
1438     }
1440     echo "</ul>\n";
1442     return true;
1445 /**
1446  * Return grade for given user or all users.
1447  *
1448  * @global object
1449  * @global object
1450  * @param object $forum
1451  * @param int $userid optional user id, 0 means all users
1452  * @return array array of grades, false if none
1453  */
1454 function forum_get_user_grades($forum, $userid=0) {
1455     global $CFG;
1457     require_once($CFG->dirroot.'/rating/lib.php');
1458     $rm = new rating_manager();
1460     $ratingoptions = new stdclass();
1462     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1463     $ratingoptions->modulename = 'forum';
1464     $ratingoptions->moduleid   = $forum->id;
1465     //$ratingoptions->cmidnumber = $forum->cmidnumber;
1467     $ratingoptions->userid = $userid;
1468     $ratingoptions->aggregationmethod = $forum->assessed;
1469     $ratingoptions->scaleid = $forum->scale;
1470     $ratingoptions->itemtable = 'forum_posts';
1471     $ratingoptions->itemtableusercolumn = 'userid';
1473     return $rm->get_user_grades($ratingoptions);
1476 /**
1477  * Update activity grades
1478  *
1479  * @global object
1480  * @global object
1481  * @param object $forum
1482  * @param int $userid specific user only, 0 means all
1483  * @param boolean $nullifnone return null if grade does not exist
1484  * @return void
1485  */
1486 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1487     global $CFG, $DB;
1488     require_once($CFG->libdir.'/gradelib.php');
1490     if (!$forum->assessed) {
1491         forum_grade_item_update($forum);
1493     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1494         forum_grade_item_update($forum, $grades);
1496     } else if ($userid and $nullifnone) {
1497         $grade = new stdClass();
1498         $grade->userid   = $userid;
1499         $grade->rawgrade = NULL;
1500         forum_grade_item_update($forum, $grade);
1502     } else {
1503         forum_grade_item_update($forum);
1504     }
1507 /**
1508  * Update all grades in gradebook.
1509  * @global object
1510  */
1511 function forum_upgrade_grades() {
1512     global $DB;
1514     $sql = "SELECT COUNT('x')
1515               FROM {forum} f, {course_modules} cm, {modules} m
1516              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1517     $count = $DB->count_records_sql($sql);
1519     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1520               FROM {forum} f, {course_modules} cm, {modules} m
1521              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1522     if ($rs = $DB->get_recordset_sql($sql)) {
1523         $pbar = new progress_bar('forumupgradegrades', 500, true);
1524         $i=0;
1525         foreach ($rs as $forum) {
1526             $i++;
1527             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1528             forum_update_grades($forum, 0, false);
1529             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1530         }
1531         $rs->close();
1532     }
1535 /**
1536  * Create/update grade item for given forum
1537  *
1538  * @global object
1539  * @uses GRADE_TYPE_NONE
1540  * @uses GRADE_TYPE_VALUE
1541  * @uses GRADE_TYPE_SCALE
1542  * @param object $forum object with extra cmidnumber
1543  * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
1544  * @return int 0 if ok
1545  */
1546 function forum_grade_item_update($forum, $grades=NULL) {
1547     global $CFG;
1548     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1549         require_once($CFG->libdir.'/gradelib.php');
1550     }
1552     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1554     if (!$forum->assessed or $forum->scale == 0) {
1555         $params['gradetype'] = GRADE_TYPE_NONE;
1557     } else if ($forum->scale > 0) {
1558         $params['gradetype'] = GRADE_TYPE_VALUE;
1559         $params['grademax']  = $forum->scale;
1560         $params['grademin']  = 0;
1562     } else if ($forum->scale < 0) {
1563         $params['gradetype'] = GRADE_TYPE_SCALE;
1564         $params['scaleid']   = -$forum->scale;
1565     }
1567     if ($grades  === 'reset') {
1568         $params['reset'] = true;
1569         $grades = NULL;
1570     }
1572     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1575 /**
1576  * Delete grade item for given forum
1577  *
1578  * @global object
1579  * @param object $forum object
1580  * @return object grade_item
1581  */
1582 function forum_grade_item_delete($forum) {
1583     global $CFG;
1584     require_once($CFG->libdir.'/gradelib.php');
1586     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1590 /**
1591  * Returns the users with data in one forum
1592  * (users with records in forum_subscriptions, forum_posts, students)
1593  *
1594  * @global object
1595  * @global object
1596  * @param int $forumid
1597  * @return mixed array or false if none
1598  */
1599 function forum_get_participants($forumid) {
1601     global $CFG, $DB;
1603     //Get students from forum_subscriptions
1604     $st_subscriptions = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1605                                          FROM {user} u,
1606                                               {forum_subscriptions} s
1607                                          WHERE s.forum = ? AND
1608                                                u.id = s.userid", array($forumid));
1609     //Get students from forum_posts
1610     $st_posts = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1611                                  FROM {user} u,
1612                                       {forum_discussions} d,
1613                                       {forum_posts} p
1614                                  WHERE d.forum = ? AND
1615                                        p.discussion = d.id AND
1616                                        u.id = p.userid", array($forumid));
1618     //Get students from the ratings table
1619     $st_ratings = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1620                                    FROM {user} u,
1621                                         {forum_discussions} d,
1622                                         {forum_posts} p,
1623                                         {ratings} r
1624                                    WHERE d.forum = ? AND
1625                                          p.discussion = d.id AND
1626                                          r.post = p.id AND
1627                                          u.id = r.userid", array($forumid));
1629     //Add st_posts to st_subscriptions
1630     if ($st_posts) {
1631         foreach ($st_posts as $st_post) {
1632             $st_subscriptions[$st_post->id] = $st_post;
1633         }
1634     }
1635     //Add st_ratings to st_subscriptions
1636     if ($st_ratings) {
1637         foreach ($st_ratings as $st_rating) {
1638             $st_subscriptions[$st_rating->id] = $st_rating;
1639         }
1640     }
1641     //Return st_subscriptions array (it contains an array of unique users)
1642     return ($st_subscriptions);
1645 /**
1646  * This function returns if a scale is being used by one forum
1647  *
1648  * @global object
1649  * @param int $forumid
1650  * @param int $scaleid negative number
1651  * @return bool
1652  */
1653 function forum_scale_used ($forumid,$scaleid) {
1654     global $DB;
1655     $return = false;
1657     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1659     if (!empty($rec) && !empty($scaleid)) {
1660         $return = true;
1661     }
1663     return $return;
1666 /**
1667  * Checks if scale is being used by any instance of forum
1668  *
1669  * This is used to find out if scale used anywhere
1670  *
1671  * @global object
1672  * @param $scaleid int
1673  * @return boolean True if the scale is used by any forum
1674  */
1675 function forum_scale_used_anywhere($scaleid) {
1676     global $DB;
1677     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1678         return true;
1679     } else {
1680         return false;
1681     }
1684 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1686 /**
1687  * Gets a post with all info ready for forum_print_post
1688  * Most of these joins are just to get the forum id
1689  *
1690  * @global object
1691  * @global object
1692  * @param int $postid
1693  * @return mixed array of posts or false
1694  */
1695 function forum_get_post_full($postid) {
1696     global $CFG, $DB;
1698     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1699                              FROM {forum_posts} p
1700                                   JOIN {forum_discussions} d ON p.discussion = d.id
1701                                   LEFT JOIN {user} u ON p.userid = u.id
1702                             WHERE p.id = ?", array($postid));
1705 /**
1706  * Gets posts with all info ready for forum_print_post
1707  * We pass forumid in because we always know it so no need to make a
1708  * complicated join to find it out.
1709  *
1710  * @global object
1711  * @global object
1712  * @return mixed array of posts or false
1713  */
1714 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1715     global $CFG, $DB;
1717     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1718                               FROM {forum_posts} p
1719                          LEFT JOIN {user} u ON p.userid = u.id
1720                              WHERE p.discussion = ?
1721                                AND p.parent > 0 $sort", array($discussion));
1724 /**
1725  * Gets all posts in discussion including top parent.
1726  *
1727  * @global object
1728  * @global object
1729  * @global object
1730  * @param int $discussionid
1731  * @param string $sort
1732  * @param bool $tracking does user track the forum?
1733  * @return array of posts
1734  */
1735 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1736     global $CFG, $DB, $USER;
1738     $tr_sel  = "";
1739     $tr_join = "";
1740     $params = array();
1742     if ($tracking) {
1743         $now = time();
1744         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1745         $tr_sel  = ", fr.id AS postread";
1746         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1747         $params[] = $USER->id;
1748     }
1750     $params[] = $discussionid;
1751     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1752                                      FROM {forum_posts} p
1753                                           LEFT JOIN {user} u ON p.userid = u.id
1754                                           $tr_join
1755                                     WHERE p.discussion = ?
1756                                  ORDER BY $sort", $params)) {
1757         return array();
1758     }
1760     foreach ($posts as $pid=>$p) {
1761         if ($tracking) {
1762             if (forum_tp_is_post_old($p)) {
1763                  $posts[$pid]->postread = true;
1764             }
1765         }
1766         if (!$p->parent) {
1767             continue;
1768         }
1769         if (!isset($posts[$p->parent])) {
1770             continue; // parent does not exist??
1771         }
1772         if (!isset($posts[$p->parent]->children)) {
1773             $posts[$p->parent]->children = array();
1774         }
1775         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1776     }
1778     return $posts;
1781 /**
1782  * Gets posts with all info ready for forum_print_post
1783  * We pass forumid in because we always know it so no need to make a
1784  * complicated join to find it out.
1785  *
1786  * @global object
1787  * @global object
1788  * @param int $parent
1789  * @param int $forumid
1790  * @return array
1791  */
1792 function forum_get_child_posts($parent, $forumid) {
1793     global $CFG, $DB;
1795     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1796                               FROM {forum_posts} p
1797                          LEFT JOIN {user} u ON p.userid = u.id
1798                              WHERE p.parent = ?
1799                           ORDER BY p.created ASC", array($parent));
1802 /**
1803  * An array of forum objects that the user is allowed to read/search through.
1804  *
1805  * @global object
1806  * @global object
1807  * @global object
1808  * @param int $userid
1809  * @param int $courseid if 0, we look for forums throughout the whole site.
1810  * @return array of forum objects, or false if no matches
1811  *         Forum objects have the following attributes:
1812  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1813  *         viewhiddentimedposts
1814  */
1815 function forum_get_readable_forums($userid, $courseid=0) {
1817     global $CFG, $DB, $USER;
1818     require_once($CFG->dirroot.'/course/lib.php');
1820     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1821         print_error('notinstalled', 'forum');
1822     }
1824     if ($courseid) {
1825         $courses = $DB->get_records('course', array('id' => $courseid));
1826     } else {
1827         // If no course is specified, then the user can see SITE + his courses.
1828         $courses1 = $DB->get_records('course', array('id' => SITEID));
1829         $courses2 = enrol_get_users_courses($userid, true);
1830         $courses = array_merge($courses1, $courses2);
1831     }
1832     if (!$courses) {
1833         return array();
1834     }
1836     $readableforums = array();
1838     foreach ($courses as $course) {
1840         $modinfo =& get_fast_modinfo($course);
1841         if (is_null($modinfo->groups)) {
1842             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1843         }
1845         if (empty($modinfo->instances['forum'])) {
1846             // hmm, no forums?
1847             continue;
1848         }
1850         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1852         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1853             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1854                 continue;
1855             }
1856             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1857             $forum = $courseforums[$forumid];
1858             $forum->context = $context;
1859             $forum->cm = $cm;
1861             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1862                 continue;
1863             }
1865          /// group access
1866             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1867                 if (is_null($modinfo->groups)) {
1868                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1869                 }
1870                 if (isset($modinfo->groups[$cm->groupingid])) {
1871                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1872                     $forum->onlygroups[] = -1;
1873                 } else {
1874                     $forum->onlygroups = array(-1);
1875                 }
1876             }
1878         /// hidden timed discussions
1879             $forum->viewhiddentimedposts = true;
1880             if (!empty($CFG->forum_enabletimedposts)) {
1881                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1882                     $forum->viewhiddentimedposts = false;
1883                 }
1884             }
1886         /// qanda access
1887             if ($forum->type == 'qanda'
1888                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1890                 // We need to check whether the user has posted in the qanda forum.
1891                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1892                                                     // the user is allowed to see in this forum.
1893                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1894                     foreach ($discussionspostedin as $d) {
1895                         $forum->onlydiscussions[] = $d->id;
1896                     }
1897                 }
1898             }
1900             $readableforums[$forum->id] = $forum;
1901         }
1903         unset($modinfo);
1905     } // End foreach $courses
1907     return $readableforums;
1910 /**
1911  * Returns a list of posts found using an array of search terms.
1912  *
1913  * @global object
1914  * @global object
1915  * @global object
1916  * @param array $searchterms array of search terms, e.g. word +word -word
1917  * @param int $courseid if 0, we search through the whole site
1918  * @param int $limitfrom
1919  * @param int $limitnum
1920  * @param int &$totalcount
1921  * @param string $extrasql
1922  * @return array|bool Array of posts found or false
1923  */
1924 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1925                             &$totalcount, $extrasql='') {
1926     global $CFG, $DB, $USER;
1927     require_once($CFG->libdir.'/searchlib.php');
1929     $forums = forum_get_readable_forums($USER->id, $courseid);
1931     if (count($forums) == 0) {
1932         $totalcount = 0;
1933         return false;
1934     }
1936     $now = round(time(), -2); // db friendly
1938     $fullaccess = array();
1939     $where = array();
1940     $params = array();
1942     foreach ($forums as $forumid => $forum) {
1943         $select = array();
1945         if (!$forum->viewhiddentimedposts) {
1946             $select[] = "(d.userid = :userid OR (d.timestart < : AND (d.timeend = 0 OR d.timeend > :timeend)))";
1947             $params = array('userid'=>$USER->id, 'timestart'=>$now, 'timeend'=>$now);
1948         }
1950         $cm = $forum->cm;
1951         $context = $forum->context;
1953         if ($forum->type == 'qanda'
1954             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1955             if (!empty($forum->onlydiscussions)) {
1956                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda0');
1957                 $params = array_merge($params, $discussionid_params);
1958                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
1959             } else {
1960                 $select[] = "p.parent = 0";
1961             }
1962         }
1964         if (!empty($forum->onlygroups)) {
1965             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps0');
1966             $params = array_merge($params, $groupid_params);
1967             $select[] = "d.groupid $groupid_sql";
1968         }
1970         if ($select) {
1971             $selects = implode(" AND ", $select);
1972             $where[] = "(d.forum = :forum AND $selects)";
1973             $params['forum'] = $forumid;
1974         } else {
1975             $fullaccess[] = $forumid;
1976         }
1977     }
1979     if ($fullaccess) {
1980         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula0');
1981         $params = array_merge($params, $fullid_params);
1982         $where[] = "(d.forum $fullid_sql)";
1983     }
1985     $selectdiscussion = "(".implode(" OR ", $where).")";
1987     $messagesearch = '';
1988     $searchstring = '';
1990     // Need to concat these back together for parser to work.
1991     foreach($searchterms as $searchterm){
1992         if ($searchstring != '') {
1993             $searchstring .= ' ';
1994         }
1995         $searchstring .= $searchterm;
1996     }
1998     // We need to allow quoted strings for the search. The quotes *should* be stripped
1999     // by the parser, but this should be examined carefully for security implications.
2000     $searchstring = str_replace("\\\"","\"",$searchstring);
2001     $parser = new search_parser();
2002     $lexer = new search_lexer($parser);
2004     if ($lexer->parse($searchstring)) {
2005         $parsearray = $parser->get_parsed_array();
2006     // Experimental feature under 1.8! MDL-8830
2007     // Use alternative text searches if defined
2008     // This feature only works under mysql until properly implemented for other DBs
2009     // Requires manual creation of text index for forum_posts before enabling it:
2010     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2011     // Experimental feature under 1.8! MDL-8830
2012         if (!empty($CFG->forum_usetextsearches)) {
2013             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2014                                                  'p.userid', 'u.id', 'u.firstname',
2015                                                  'u.lastname', 'p.modified', 'd.forum');
2016         } else {
2017             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2018                                                  'p.userid', 'u.id', 'u.firstname',
2019                                                  'u.lastname', 'p.modified', 'd.forum');
2020         }
2021         $params = array_merge($params, $msparams);
2022     }
2024     $fromsql = "{forum_posts} p,
2025                   {forum_discussions} d,
2026                   {user} u";
2028     $selectsql = " $messagesearch
2029                AND p.discussion = d.id
2030                AND p.userid = u.id
2031                AND $selectdiscussion
2032                    $extrasql";
2034     $countsql = "SELECT COUNT(*)
2035                    FROM $fromsql
2036                   WHERE $selectsql";
2038     $searchsql = "SELECT p.*,
2039                          d.forum,
2040                          u.firstname,
2041                          u.lastname,
2042                          u.email,
2043                          u.picture,
2044                          u.imagealt,
2045                          u.email
2046                     FROM $fromsql
2047                    WHERE $selectsql
2048                 ORDER BY p.modified DESC";
2050     $totalcount = $DB->count_records_sql($countsql, $params);
2052     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2055 /**
2056  * Returns a list of ratings for a particular post - sorted.
2057  *
2058  * @global object
2059  * @global object
2060  * @param int $postid
2061  * @param string $sort
2062  * @return array Array of ratings or false
2063  */
2064 function forum_get_ratings($context, $postid, $sort="u.firstname ASC") {
2065     global $PAGE;
2067     $options = new stdclass();
2068     $options->context = $PAGE->context;
2069     $options->itemid = $postid;
2070     $options->sort = "ORDER BY $sort";
2072     $rm = new rating_manager();
2073     $rm->get_all_ratings_for_item($options);
2076 /**
2077  * Returns a list of all new posts that have not been mailed yet
2078  *
2079  * @global object
2080  * @global object
2081  * @param int $starttime posts created after this time
2082  * @param int $endtime posts created before this
2083  * @param int $now used for timed discussions only
2084  * @return array
2085  */
2086 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2087     global $CFG, $DB;
2089     $params = array($starttime, $endtime);
2090     if (!empty($CFG->forum_enabletimedposts)) {
2091         if (empty($now)) {
2092             $now = time();
2093         }
2094         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2095         $params[] = $now;
2096         $params[] = $now;
2097     } else {
2098         $timedsql = "";
2099     }
2101     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2102                               FROM {forum_posts} p
2103                                    JOIN {forum_discussions} d ON d.id = p.discussion
2104                              WHERE p.mailed = 0
2105                                    AND p.created >= ?
2106                                    AND (p.created < ? OR p.mailnow = 1)
2107                                    $timedsql
2108                           ORDER BY p.modified ASC", $params);
2111 /**
2112  * Marks posts before a certain time as being mailed already
2113  *
2114  * @global object
2115  * @global object
2116  * @param int $endtime
2117  * @param int $now Defaults to time()
2118  * @return bool
2119  */
2120 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2121     global $CFG, $DB;
2122     if (empty($now)) {
2123         $now = time();
2124     }
2126     if (empty($CFG->forum_enabletimedposts)) {
2127         return $DB->execute("UPDATE {forum_posts}
2128                                SET mailed = '1'
2129                              WHERE (created < ? OR mailnow = 1)
2130                                    AND mailed = 0", array($endtime));
2132     } else {
2133         return $DB->execute("UPDATE {forum_posts}
2134                                SET mailed = '1'
2135                              WHERE discussion NOT IN (SELECT d.id
2136                                                         FROM {forum_discussions} d
2137                                                        WHERE d.timestart > ?)
2138                                    AND (created < ? OR mailnow = 1)
2139                                    AND mailed = 0", array($now, $endtime));
2140     }
2143 /**
2144  * Get all the posts for a user in a forum suitable for forum_print_post
2145  *
2146  * @global object
2147  * @global object
2148  * @uses CONTEXT_MODULE
2149  * @return array
2150  */
2151 function forum_get_user_posts($forumid, $userid) {
2152     global $CFG, $DB;
2154     $timedsql = "";
2155     $params = array($forumid, $userid);
2157     if (!empty($CFG->forum_enabletimedposts)) {
2158         $cm = get_coursemodule_from_instance('forum', $forumid);
2159         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2160             $now = time();
2161             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2162             $params[] = $now;
2163             $params[] = $now;
2164         }
2165     }
2167     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2168                               FROM {forum} f
2169                                    JOIN {forum_discussions} d ON d.forum = f.id
2170                                    JOIN {forum_posts} p       ON p.discussion = d.id
2171                                    JOIN {user} u              ON u.id = p.userid
2172                              WHERE f.id = ?
2173                                    AND p.userid = ?
2174                                    $timedsql
2175                           ORDER BY p.modified ASC", $params);
2178 /**
2179  * Get all the discussions user participated in
2180  *
2181  * @global object
2182  * @global object
2183  * @uses CONTEXT_MODULE
2184  * @param int $forumid
2185  * @param int $userid
2186  * @return array Array or false
2187  */
2188 function forum_get_user_involved_discussions($forumid, $userid) {
2189     global $CFG, $DB;
2191     $timedsql = "";
2192     $params = array($forumid, $userid);
2193     if (!empty($CFG->forum_enabletimedposts)) {
2194         $cm = get_coursemodule_from_instance('forum', $forumid);
2195         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2196             $now = time();
2197             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2198             $params[] = $now;
2199             $params[] = $now;
2200         }
2201     }
2203     return $DB->get_records_sql("SELECT DISTINCT d.*
2204                               FROM {forum} f
2205                                    JOIN {forum_discussions} d ON d.forum = f.id
2206                                    JOIN {forum_posts} p       ON p.discussion = d.id
2207                              WHERE f.id = ?
2208                                    AND p.userid = ?
2209                                    $timedsql", $params);
2212 /**
2213  * Get all the posts for a user in a forum suitable for forum_print_post
2214  *
2215  * @global object
2216  * @global object
2217  * @param int $forumid
2218  * @param int $userid
2219  * @return array of counts or false
2220  */
2221 function forum_count_user_posts($forumid, $userid) {
2222     global $CFG, $DB;
2224     $timedsql = "";
2225     $params = array($forumid, $userid);
2226     if (!empty($CFG->forum_enabletimedposts)) {
2227         $cm = get_coursemodule_from_instance('forum', $forumid);
2228         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2229             $now = time();
2230             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2231             $params[] = $now;
2232             $params[] = $now;
2233         }
2234     }
2236     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2237                              FROM {forum} f
2238                                   JOIN {forum_discussions} d ON d.forum = f.id
2239                                   JOIN {forum_posts} p       ON p.discussion = d.id
2240                                   JOIN {user} u              ON u.id = p.userid
2241                             WHERE f.id = ?
2242                                   AND p.userid = ?
2243                                   $timedsql", $params);
2246 /**
2247  * Given a log entry, return the forum post details for it.
2248  *
2249  * @global object
2250  * @global object
2251  * @param object $log
2252  * @return array|null
2253  */
2254 function forum_get_post_from_log($log) {
2255     global $CFG, $DB;
2257     if ($log->action == "add post") {
2259         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2260                                            u.firstname, u.lastname, u.email, u.picture
2261                                  FROM {forum_discussions} d,
2262                                       {forum_posts} p,
2263                                       {forum} f,
2264                                       {user} u
2265                                 WHERE p.id = ?
2266                                   AND d.id = p.discussion
2267                                   AND p.userid = u.id
2268                                   AND u.deleted <> '1'
2269                                   AND f.id = d.forum", array($log->info));
2272     } else if ($log->action == "add discussion") {
2274         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2275                                            u.firstname, u.lastname, u.email, u.picture
2276                                  FROM {forum_discussions} d,
2277                                       {forum_posts} p,
2278                                       {forum} f,
2279                                       {user} u
2280                                 WHERE d.id = ?
2281                                   AND d.firstpost = p.id
2282                                   AND p.userid = u.id
2283                                   AND u.deleted <> '1'
2284                                   AND f.id = d.forum", array($log->info));
2285     }
2286     return NULL;
2289 /**
2290  * Given a discussion id, return the first post from the discussion
2291  *
2292  * @global object
2293  * @global object
2294  * @param int $dicsussionid
2295  * @return array
2296  */
2297 function forum_get_firstpost_from_discussion($discussionid) {
2298     global $CFG, $DB;
2300     return $DB->get_record_sql("SELECT p.*
2301                              FROM {forum_discussions} d,
2302                                   {forum_posts} p
2303                             WHERE d.id = ?
2304                               AND d.firstpost = p.id ", array($discussionid));
2307 /**
2308  * Returns an array of counts of replies to each discussion
2309  *
2310  * @global object
2311  * @global object
2312  * @param int $forumid
2313  * @param string $forumsort
2314  * @param int $limit
2315  * @param int $page
2316  * @param int $perpage
2317  * @return array
2318  */
2319 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2320     global $CFG, $DB;
2322     if ($limit > 0) {
2323         $limitfrom = 0;
2324         $limitnum  = $limit;
2325     } else if ($page != -1) {
2326         $limitfrom = $page*$perpage;
2327         $limitnum  = $perpage;
2328     } else {
2329         $limitfrom = 0;
2330         $limitnum  = 0;
2331     }
2333     if ($forumsort == "") {
2334         $orderby = "";
2335         $groupby = "";
2337     } else {
2338         $orderby = "ORDER BY $forumsort";
2339         $groupby = ", ".strtolower($forumsort);
2340         $groupby = str_replace('desc', '', $groupby);
2341         $groupby = str_replace('asc', '', $groupby);
2342     }
2344     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2345         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2346                   FROM {forum_posts} p
2347                        JOIN {forum_discussions} d ON p.discussion = d.id
2348                  WHERE p.parent > 0 AND d.forum = ?
2349               GROUP BY p.discussion";
2350         return $DB->get_records_sql($sql, array($forumid));
2352     } else {
2353         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2354                   FROM {forum_posts} p
2355                        JOIN {forum_discussions} d ON p.discussion = d.id
2356                  WHERE d.forum = ?
2357               GROUP BY p.discussion $groupby
2358               $orderby";
2359         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2360     }
2363 /**
2364  * @global object
2365  * @global object
2366  * @global object
2367  * @staticvar array $cache
2368  * @param object $forum
2369  * @param object $cm
2370  * @param object $course
2371  * @return mixed
2372  */
2373 function forum_count_discussions($forum, $cm, $course) {
2374     global $CFG, $DB, $USER;
2376     static $cache = array();
2378     $now = round(time(), -2); // db cache friendliness
2380     $params = array($course->id);
2382     if (!isset($cache[$course->id])) {
2383         if (!empty($CFG->forum_enabletimedposts)) {
2384             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2385             $params[] = $now;
2386             $params[] = $now;
2387         } else {
2388             $timedsql = "";
2389         }
2391         $sql = "SELECT f.id, COUNT(d.id) as dcount
2392                   FROM {forum} f
2393                        JOIN {forum_discussions} d ON d.forum = f.id
2394                  WHERE f.course = ?
2395                        $timedsql
2396               GROUP BY f.id";
2398         if ($counts = $DB->get_records_sql($sql, $params)) {
2399             foreach ($counts as $count) {
2400                 $counts[$count->id] = $count->dcount;
2401             }
2402             $cache[$course->id] = $counts;
2403         } else {
2404             $cache[$course->id] = array();
2405         }
2406     }
2408     if (empty($cache[$course->id][$forum->id])) {
2409         return 0;
2410     }
2412     $groupmode = groups_get_activity_groupmode($cm, $course);
2414     if ($groupmode != SEPARATEGROUPS) {
2415         return $cache[$course->id][$forum->id];
2416     }
2418     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2419         return $cache[$course->id][$forum->id];
2420     }
2422     require_once($CFG->dirroot.'/course/lib.php');
2424     $modinfo =& get_fast_modinfo($course);
2425     if (is_null($modinfo->groups)) {
2426         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2427     }
2429     if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2430         $mygroups = $modinfo->groups[$cm->groupingid];
2431     } else {
2432         $mygroups = false; // Will be set below
2433     }
2435     // add all groups posts
2436     if (empty($mygroups)) {
2437         $mygroups = array(-1=>-1);
2438     } else {
2439         $mygroups[-1] = -1;
2440     }
2442     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2443     $params[] = $forum->id;
2445     if (!empty($CFG->forum_enabletimedposts)) {
2446         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2447         $params[] = $now;
2448         $params[] = $now;
2449     } else {
2450         $timedsql = "";
2451     }
2453     $sql = "SELECT COUNT(d.id)
2454               FROM {forum_discussions} d
2455              WHERE d.groupid $mygroups_sql AND d.forum = ?
2456                    $timedsql";
2458     return $DB->get_field_sql($sql, $params);
2461 /**
2462  * How many posts by other users are unrated by a given user in the given discussion?
2463  *
2464  * @global object
2465  * @global object
2466  * @param int $discussionid
2467  * @param int $userid
2468  * @return mixed
2469  */
2470 function forum_count_unrated_posts($discussionid, $userid) {
2471     global $CFG, $DB;
2472     if ($posts = $DB->get_record_sql("SELECT count(*) as num
2473                                    FROM {forum_posts}
2474                                   WHERE parent > 0
2475                                     AND discussion = ?
2476                                     AND userid <> ? ", array($discussionid, $userid))) {
2478         if ($rated = $DB->get_record_sql("SELECT count(*) as num
2479                                        FROM {forum_posts} p,
2480                                             {rating} r
2481                                       WHERE p.discussion = ?
2482                                         AND p.id = r.itemid
2483                                         AND r.userid = ?", array($discussionid, $userid))) {
2484             $difference = $posts->num - $rated->num;
2485             if ($difference > 0) {
2486                 return $difference;
2487             } else {
2488                 return 0;    // Just in case there was a counting error
2489             }
2490         } else {
2491             return $posts->num;
2492         }
2493     } else {
2494         return 0;
2495     }
2498 /**
2499  * Get all discussions in a forum
2500  *
2501  * @global object
2502  * @global object
2503  * @global object
2504  * @uses CONTEXT_MODULE
2505  * @uses VISIBLEGROUPS
2506  * @param object $cm
2507  * @param string $forumsort
2508  * @param bool $fullpost
2509  * @param int $unused
2510  * @param int $limit
2511  * @param bool $userlastmodified
2512  * @param int $page
2513  * @param int $perpage
2514  * @return array
2515  */
2516 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2517     global $CFG, $DB, $USER;
2519     $timelimit = '';
2521     $now = round(time(), -2);
2522     $params = array($cm->instance);
2524     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2526     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2527         return array();
2528     }
2530     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2532         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2533             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2534             $params[] = $now;
2535             $params[] = $now;
2536             if (isloggedin()) {
2537                 $timelimit .= " OR d.userid = ?";
2538                 $params[] = $USER->id;
2539             }
2540             $timelimit .= ")";
2541         }
2542     }
2544     if ($limit > 0) {
2545         $limitfrom = 0;
2546         $limitnum  = $limit;
2547     } else if ($page != -1) {
2548         $limitfrom = $page*$perpage;
2549         $limitnum  = $perpage;
2550     } else {
2551         $limitfrom = 0;
2552         $limitnum  = 0;
2553     }
2555     $groupmode    = groups_get_activity_groupmode($cm);
2556     $currentgroup = groups_get_activity_group($cm);
2558     if ($groupmode) {
2559         if (empty($modcontext)) {
2560             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2561         }
2563         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2564             if ($currentgroup) {
2565                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2566                 $params[] = $currentgroup;
2567             } else {
2568                 $groupselect = "";
2569             }
2571         } else {
2572             //seprate groups without access all
2573             if ($currentgroup) {
2574                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2575                 $params[] = $currentgroup;
2576             } else {
2577                 $groupselect = "AND d.groupid = -1";
2578             }
2579         }
2580     } else {
2581         $groupselect = "";
2582     }
2585     if (empty($forumsort)) {
2586         $forumsort = "d.timemodified DESC";
2587     }
2588     if (empty($fullpost)) {
2589         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2590     } else {
2591         $postdata = "p.*";
2592     }
2594     if (empty($userlastmodified)) {  // We don't need to know this
2595         $umfields = "";
2596         $umtable  = "";
2597     } else {
2598         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2599         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2600     }
2602     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2603                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2604               FROM {forum_discussions} d
2605                    JOIN {forum_posts} p ON p.discussion = d.id
2606                    JOIN {user} u ON p.userid = u.id
2607                    $umtable
2608              WHERE d.forum = ? AND p.parent = 0
2609                    $timelimit $groupselect
2610           ORDER BY $forumsort";
2611     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2614 /**
2615  *
2616  * @global object
2617  * @global object
2618  * @global object
2619  * @uses CONTEXT_MODULE
2620  * @uses VISIBLEGROUPS
2621  * @param object $cm
2622  * @return array
2623  */
2624 function forum_get_discussions_unread($cm) {
2625     global $CFG, $DB, $USER;
2627     $now = round(time(), -2);
2628     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2630     $params = array();
2631     $groupmode    = groups_get_activity_groupmode($cm);
2632     $currentgroup = groups_get_activity_group($cm);
2634     if ($groupmode) {
2635         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2637         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2638             if ($currentgroup) {
2639                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2640                 $params['currentgroup'] = $currentgroup;
2641             } else {
2642                 $groupselect = "";
2643             }
2645         } else {
2646             //separate groups without access all
2647             if ($currentgroup) {
2648                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2649                 $params['currentgroup'] = $currentgroup;
2650             } else {
2651                 $groupselect = "AND d.groupid = -1";
2652             }
2653         }
2654     } else {
2655         $groupselect = "";
2656     }
2658     if (!empty($CFG->forum_enabletimedposts)) {
2659         $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2660         $params['now1'] = $now;
2661         $params['now2'] = $now;
2662     } else {
2663         $timedsql = "";
2664     }
2666     $sql = "SELECT d.id, COUNT(p.id) AS unread
2667               FROM {forum_discussions} d
2668                    JOIN {forum_posts} p     ON p.discussion = d.id
2669                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2670              WHERE d.forum = {$cm->instance}
2671                    AND p.modified >= :cutoffdate AND r.id is NULL
2672                    $groupselect
2673                    $timedsql
2674           GROUP BY d.id";
2675     $params['cutoffdate'] = $cutoffdate;
2677     if ($unreads = $DB->get_records_sql($sql, $params)) {
2678         foreach ($unreads as $unread) {
2679             $unreads[$unread->id] = $unread->unread;
2680         }
2681         return $unreads;
2682     } else {
2683         return array();
2684     }
2687 /**
2688  * @global object
2689  * @global object
2690  * @global object
2691  * @uses CONEXT_MODULE
2692  * @uses VISIBLEGROUPS
2693  * @param object $cm
2694  * @return array
2695  */
2696 function forum_get_discussions_count($cm) {
2697     global $CFG, $DB, $USER;
2699     $now = round(time(), -2);
2700     $params = array($cm->instance);
2701     $groupmode    = groups_get_activity_groupmode($cm);
2702     $currentgroup = groups_get_activity_group($cm);
2704     if ($groupmode) {
2705         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2707         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2708             if ($currentgroup) {
2709                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2710                 $params[] = $currentgroup;
2711             } else {
2712                 $groupselect = "";
2713             }
2715         } else {
2716             //seprate groups without access all
2717             if ($currentgroup) {
2718                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2719                 $params[] = $currentgroup;
2720             } else {
2721                 $groupselect = "AND d.groupid = -1";
2722             }
2723         }
2724     } else {
2725         $groupselect = "";
2726     }
2728     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2730     $timelimit = "";
2732     if (!empty($CFG->forum_enabletimedposts)) {
2734         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2736         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2737             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2738             $params[] = $now;
2739             $params[] = $now;
2740             if (isloggedin()) {
2741                 $timelimit .= " OR d.userid = ?";
2742                 $params[] = $USER->id;
2743             }
2744             $timelimit .= ")";
2745         }
2746     }
2748     $sql = "SELECT COUNT(d.id)
2749               FROM {forum_discussions} d
2750                    JOIN {forum_posts} p ON p.discussion = d.id
2751              WHERE d.forum = ? AND p.parent = 0
2752                    $groupselect $timelimit";
2754     return $DB->get_field_sql($sql, $params);
2758 /**
2759  * Get all discussions started by a particular user in a course (or group)
2760  * This function no longer used ...
2761  *
2762  * @todo Remove this function if no longer used
2763  * @global object
2764  * @global object
2765  * @param int $courseid
2766  * @param int $userid
2767  * @param int $groupid
2768  * @return array
2769  */
2770 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2771     global $CFG, $DB;
2772     $params = array($courseid, $userid);
2773     if ($groupid) {
2774         $groupselect = " AND d.groupid = ? ";
2775         $params[] = $groupid;
2776     } else  {
2777         $groupselect = "";
2778     }
2780     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2781                                    f.type as forumtype, f.name as forumname, f.id as forumid
2782                               FROM {forum_discussions} d,
2783                                    {forum_posts} p,
2784                                    {user} u,
2785                                    {forum} f
2786                              WHERE d.course = ?
2787                                AND p.discussion = d.id
2788                                AND p.parent = 0
2789                                AND p.userid = u.id
2790                                AND u.id = ?
2791                                AND d.forum = f.id $groupselect
2792                           ORDER BY p.created DESC", $params);
2795 /**
2796  * Get the list of potential subscribers to a forum.
2797  *
2798  * @param object $forumcontext the forum context.
2799  * @param integer $groupid the id of a group, or 0 for all groups.
2800  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2801  * @param string $sort sort order. As for get_users_by_capability.
2802  * @return array list of users.
2803  */
2804 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2805     return get_users_by_capability($forumcontext, 'mod/forum:initialsubscriptions', $fields, $sort, '', '', $groupid, '', false, true);
2808 /**
2809  * Returns list of user objects that are subscribed to this forum
2810  *
2811  * @global object
2812  * @global object
2813  * @param object $course the course
2814  * @param forum $forum the forum
2815  * @param integer $groupid group id, or 0 for all.
2816  * @param object $context the forum context, to save re-fetching it where possible.
2817  * @return array list of users.
2818  */
2819 function forum_subscribed_users($course, $forum, $groupid=0, $context = NULL) {
2820     global $CFG, $DB;
2821     $params = array($forum->id);
2823     if ($groupid) {
2824         $grouptables = ", {groups_members} gm ";
2825         $groupselect = "AND gm.groupid = ? AND u.id = gm.userid";
2826         $params[] = $groupid;
2827     } else  {
2828         $grouptables = '';
2829         $groupselect = '';
2830     }
2832     $fields ="u.id,
2833               u.username,
2834               u.firstname,
2835               u.lastname,
2836               u.maildisplay,
2837               u.mailformat,
2838               u.maildigest,
2839               u.emailstop,
2840               u.imagealt,
2841               u.email,
2842               u.city,
2843               u.country,
2844               u.lastaccess,
2845               u.lastlogin,
2846               u.picture,
2847               u.timezone,
2848               u.theme,
2849               u.lang,
2850               u.trackforums,
2851               u.mnethostid";
2853     if (forum_is_forcesubscribed($forum)) {
2854         if (empty($context)) {
2855             $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2856             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2857         }
2858         $sort = "u.email ASC";
2859         $results = forum_get_potential_subscribers($context, $groupid, $fields, $sort);
2860     } else {
2861         $results = $DB->get_records_sql("SELECT $fields
2862                               FROM {user} u,
2863                                    {forum_subscriptions} s $grouptables
2864                              WHERE s.forum = ?
2865                                AND s.userid = u.id
2866                                AND u.deleted = 0  $groupselect
2867                           ORDER BY u.email ASC", $params);
2868     }
2870     static $guestid = null;
2872     if (is_null($guestid)) {
2873         if ($guest = guest_user()) {
2874             $guestid = $guest->id;
2875         } else {
2876             $guestid = 0;
2877         }
2878     }
2880     // Guest user should never be subscribed to a forum.
2881     unset($results[$guestid]);
2883     return $results;
2888 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2891 /**
2892  * @global object
2893  * @global object
2894  * @param int $courseid
2895  * @param string $type
2896  */
2897 function forum_get_course_forum($courseid, $type) {
2898 // How to set up special 1-per-course forums
2899     global $CFG, $DB, $OUTPUT;
2901     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2902         // There should always only be ONE, but with the right combination of
2903         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2904         foreach ($forums as $forum) {
2905             return $forum;   // ie the first one
2906         }
2907     }
2909     // Doesn't exist, so create one now.
2910     $forum->course = $courseid;
2911     $forum->type = "$type";
2912     switch ($forum->type) {
2913         case "news":
2914             $forum->name  = get_string("namenews", "forum");
2915             $forum->intro = get_string("intronews", "forum");
2916             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2917             $forum->assessed = 0;
2918             if ($courseid == SITEID) {
2919                 $forum->name  = get_string("sitenews");
2920                 $forum->forcesubscribe = 0;
2921             }
2922             break;
2923         case "social":
2924             $forum->name  = get_string("namesocial", "forum");
2925             $forum->intro = get_string("introsocial", "forum");
2926             $forum->assessed = 0;
2927             $forum->forcesubscribe = 0;
2928             break;
2929         case "blog":
2930             $forum->name = get_string('blogforum', 'forum');
2931             $forum->intro = get_string('introblog', 'forum');
2932             $forum->assessed = 0;
2933             $forum->forcesubscribe = 0;
2934             break;
2935         default:
2936             echo $OUTPUT->notification("That forum type doesn't exist!");
2937             return false;
2938             break;
2939     }
2941     $forum->timemodified = time();
2942     $forum->id = $DB->insert_record("forum", $forum);
2944     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2945         echo $OUTPUT->notification("Could not find forum module!!");
2946         return false;
2947     }
2948     $mod = new stdClass();
2949     $mod->course = $courseid;
2950     $mod->module = $module->id;
2951     $mod->instance = $forum->id;
2952     $mod->section = 0;
2953     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
2954         echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
2955         return false;
2956     }
2957     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
2958         echo $OUTPUT->notification("Could not add the new course module to that section");
2959         return false;
2960     }
2961     $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule));
2963     include_once("$CFG->dirroot/course/lib.php");
2964     rebuild_course_cache($courseid);
2966     return $DB->get_record("forum", array("id" => "$forum->id"));
2970 /**
2971  * Given the data about a posting, builds up the HTML to display it and
2972  * returns the HTML in a string.  This is designed for sending via HTML email.
2973  *
2974  * @global object
2975  * @param object $course
2976  * @param object $cm
2977  * @param object $forum
2978  * @param object $discussion
2979  * @param object $post
2980  * @param object $userform
2981  * @param object $userto
2982  * @param bool $ownpost
2983  * @param bool $reply
2984  * @param bool $link
2985  * @param bool $rate
2986  * @param string $footer
2987  * @return string
2988  */
2989 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
2990                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
2992     global $CFG, $OUTPUT;
2994     if (!isset($userto->viewfullnames[$forum->id])) {
2995         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
2996             print_error('invalidcoursemodule');
2997         }
2998         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2999         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3000     } else {
3001         $viewfullnames = $userto->viewfullnames[$forum->id];
3002     }
3004     // format the post body
3005     $options = new stdClass();
3006     $options->para = true;
3007     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3009     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3011     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3012     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3013     $output .= '</td>';
3015     if ($post->parent) {
3016         $output .= '<td class="topic">';
3017     } else {
3018         $output .= '<td class="topic starter">';
3019     }
3020     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3022     $fullname = fullname($userfrom, $viewfullnames);
3023     $by = new stdClass();
3024     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3025     $by->date = userdate($post->modified, '', $userto->timezone);
3026     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3028     $output .= '</td></tr>';
3030     $output .= '<tr><td class="left side" valign="top">';
3032     if (isset($userfrom->groups)) {
3033         $groups = $userfrom->groups[$forum->id];
3034     } else {
3035         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
3036             print_error('invalidcoursemodule');
3037         }
3038         $group = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3039     }
3041     if ($groups) {
3042         $output .= print_group_picture($groups, $course->id, false, true, true);
3043     } else {
3044         $output .= '&nbsp;';
3045     }
3047     $output .= '</td><td class="content">';
3049     $attachments = forum_print_attachments($post, $cm, 'html');
3050     if ($attachments !== '') {
3051         $output .= '<div class="attachments">';
3052         $output .= $attachments;
3053         $output .= '</div>';
3054     }
3056     $output .= $formattedtext;
3058 // Commands
3059     $commands = array();
3061     if ($post->parent) {
3062         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3063                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3064     }
3066     if ($reply) {
3067         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3068                       get_string('reply', 'forum').'</a>';
3069     }
3071     $output .= '<div class="commands">';
3072     $output .= implode(' | ', $commands);
3073     $output .= '</div>';
3075 // Context link to post if required
3076     if ($link) {
3077         $output .= '<div class="link">';
3078         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3079                      get_string('postincontext', 'forum').'</a>';
3080         $output .= '</div>';
3081     }
3083     if ($footer) {
3084         $output .= '<div class="footer">'.$footer.'</div>';
3085     }
3086     $output .= '</td></tr></table>'."\n\n";
3088     return $output;
3091 /**
3092  * Print a forum post
3093  *
3094  * @global object
3095  * @global object
3096  * @uses FORUM_MODE_THREADED
3097  * @uses PORTFOLIO_FORMAT_PLAINHTML
3098  * @uses PORTFOLIO_FORMAT_FILE
3099  * @uses PORTFOLIO_FORMAT_RICHHTML
3100  * @uses PORTFOLIO_ADD_TEXT_LINK
3101  * @uses CONTEXT_MODULE
3102  * @param object $post The post to print.
3103  * @param object $discussion
3104  * @param object $forum
3105  * @param object $cm
3106  * @param object $course
3107  * @param boolean $ownpost Whether this post belongs to the current user.
3108  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3109  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3110  * @param string $footer Extra stuff to print after the message.
3111  * @param string $highlight Space-separated list of terms to highlight.
3112  * @param int $post_read true, false or -99. If we already know whether this user
3113  *          has read this post, pass that in, otherwise, pass in -99, and this
3114  *          function will work it out.
3115  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3116  *          the current user can't see this post, if this argument is true
3117  *          (the default) then print a dummy 'you can't see this post' post.
3118  *          If false, don't output anything at all.
3119  * @param bool|null $istracked
3120  * @return void
3121  */
3122 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3123                           $footer="", $highlight="", $post_read=null, $dummyifcantsee=true, $istracked=null) {
3125     global $USER, $CFG, $OUTPUT;
3127     static $stredit, $strdelete, $strreply, $strparent, $strprune;
3128     static $strpruneheading, $displaymode;
3129     static $strmarkread, $strmarkunread;
3131     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3133     $post->course = $course->id;
3134     $post->forum  = $forum->id;
3135     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3137     // caching
3138     if (!isset($cm->cache)) {
3139         $cm->cache = new stdClass();
3140     }
3142     if (!isset($cm->cache->caps)) {
3143         $cm->cache->caps = array();
3144         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3145         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3146         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3147         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3148         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3149         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3150         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3151         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3152         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3153     }
3155     if (!isset($cm->uservisible)) {
3156         $cm->uservisible = coursemodule_visible_for_user($cm);
3157     }
3159     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3160         if (!$dummyifcantsee) {
3161             return;
3162         }
3163         echo '<a id="p'.$post->id.'"></a>';
3164         echo '<table cellspacing="0" class="forumpost">';
3165         echo '<tr class="header"><td class="picture left">';
3166         //        print_user_picture($post->userid, $courseid, $post->picture);
3167         echo '</td>';
3168         if ($post->parent) {
3169             echo '<td class="topic">';
3170         } else {
3171             echo '<td class="topic starter">';
3172         }
3173         echo '<div class="subject">'.get_string('forumsubjecthidden','forum').'</div>';
3174         echo '<div class="author">';
3175         print_string('forumauthorhidden','forum');
3176         echo '</div></td></tr>';
3178         echo '<tr><td class="left side">';
3179         echo '&nbsp;';
3181         // Actual content
3183         echo '</td><td class="content">'."\n";
3184         echo get_string('forumbodyhidden','forum');
3185         echo '</td></tr></table>';
3186         return;
3187     }
3189     if (empty($stredit)) {
3190         $stredit         = get_string('edit', 'forum');
3191         $strdelete       = get_string('delete', 'forum');
3192         $strreply        = get_string('reply', 'forum');
3193         $strparent       = get_string('parent', 'forum');
3194         $strpruneheading = get_string('pruneheading', 'forum');
3195         $strprune        = get_string('prune', 'forum');
3196         $displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3197         $strmarkread     = get_string('markread', 'forum');
3198         $strmarkunread   = get_string('markunread', 'forum');
3200     }
3202     $read_style = '';
3203     // ignore trackign status if not tracked or tracked param missing
3204     if ($istracked) {
3205         if (is_null($post_read)) {
3206             debugging('fetching post_read info');
3207             $post_read = forum_tp_is_post_read($USER->id, $post);
3208         }
3210         if ($post_read) {
3211             $read_style = ' read';
3212         } else {
3213             $read_style = ' unread';
3214             echo '<a name="unread"></a>';
3215         }
3216     }
3218     echo '<a id="p'.$post->id.'"></a>';
3219     echo '<table cellspacing="0" class="forumpost'.$read_style.'">';
3221     // Picture
3222     $postuser = new stdClass();
3223     $postuser->id        = $post->userid;
3224     $postuser->firstname = $post->firstname;
3225     $postuser->lastname  = $post->lastname;
3226     $postuser->imagealt  = $post->imagealt;
3227     $postuser->picture   = $post->picture;
3228     $postuser->email     = $post->email;
3230     echo '<tr class="header"><td class="picture left">';
3231     echo $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3232     echo '</td>';
3234     if ($post->parent) {
3235         echo '<td class="topic">';
3236     } else {
3237         echo '<td class="topic starter">';
3238     }
3240     if (!empty($post->subjectnoformat)) {
3241         echo '<div class="subject">'.$post->subject.'</div>';
3242     } else {
3243         echo '<div class="subject">'.format_string($post->subject).'</div>';
3244     }
3246     echo '<div class="author">';
3247     $fullname = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3248     $by = new stdClass();
3249     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.
3250                 $post->userid.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3251     $by->date = userdate($post->modified);
3252     print_string('bynameondate', 'forum', $by);
3253     echo '</div></td></tr>';
3255     echo '<tr><td class="left side">';
3256     if (isset($cm->cache->usersgroups)) {
3257         $groups = array();
3258         if (isset($cm->cache->usersgroups[$post->userid])) {
3259             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3260                 $groups[$gid] = $cm->cache->groups[$gid];
3261             }
3262         }
3263     } else {
3264         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3265     }
3267     if ($groups) {
3268         print_group_picture($groups, $course->id, false, false, true);
3269     } else {
3270         echo '&nbsp;';
3271     }
3273 // Actual content
3275     echo '</td><td class="content">'."\n";
3277     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3279     if ($attachments !== '') {
3280         echo '<div class="attachments">';
3281         echo $attachments;
3282         echo '</div>';
3283     }
3285     $options = new stdClass();
3286     $options->para    = false;
3287     $options->trusted = $post->messagetrust;
3288     $options->context = $modcontext;
3289     if ($link and (strlen(strip_tags($post->message)) > $CFG->forum_longpost)) {
3290         // Print shortened version
3291         echo format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3292         $numwords = count_words(strip_tags($post->message));
3293         echo '<div class="posting shortenedpost"><a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3294         echo get_string('readtherest', 'forum');
3295         echo '</a> ('.get_string('numwords', '', $numwords).')...</div>';
3296     } else {
3297         // Print whole message
3298         echo '<div class="posting fullpost">';
3299         if ($highlight) {
3300             echo highlight($highlight, format_text($post->message, $post->messageformat, $options, $course->id));
3301         } else {
3302             echo format_text($post->message, $post->messageformat, $options, $course->id);
3303         }
3304         echo '</div>';
3305         echo $attachedimages;
3306     }
3309 // Commands
3311     $commands = array();
3313     if ($istracked) {
3314         // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3315         // Don't display the mark read / unread controls in this case.
3316         if ($CFG->forum_usermarksread and isloggedin()) {
3317             if ($post_read) {
3318                 $mcmd = '&amp;mark=unread&amp;postid='.$post->id;
3319                 $mtxt = $strmarkunread;
3320             } else {
3321                 $mcmd = '&amp;mark=read&amp;postid='.$post->id;
3322                 $mtxt = $strmarkread;
3323             }
3324             if ($displaymode == FORUM_MODE_THREADED) {
3325                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3326                               $post->discussion.'&amp;parent='.$post->id.$mcmd.'">'.$mtxt.'</a>';
3327             } else {
3328                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3329                               $post->discussion.$mcmd.'#p'.$post->id.'">'.$mtxt.'</a>';
3330             }
3331         }
3332     }
3334     if ($post->parent) {  // Zoom in to the parent specifically
3335         if ($displaymode == FORUM_MODE_THREADED) {
3336             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3337                       $post->discussion.'&amp;parent='.$post->parent.'">'.$strparent.'</a>';
3338         } else {
3339             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3340                       $post->discussion.'#p'.$post->parent.'">'.$strparent.'</a>';
3341         }
3342     }
3344     $age = time() - $post->created;
3345     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3346     if (!$post->parent and $forum->type == 'news' and $discussion->timestart > time()) {
3347         $age = 0;
3348     }
3349     $editanypost = $cm->cache->caps['mod/forum:editanypost'];
3351     if ($ownpost or $editanypost) {
3352         if (($age < $CFG->maxeditingtime) or $editanypost) {
3353             $commands[] =  '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?edit='.$post->id.'">'.$stredit.'</a>';
3354         }
3355     }
3357     if ($cm->cache->caps['mod/forum:splitdiscussions']
3358                 && $post->parent && $forum->type != 'single') {
3360         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?prune='.$post->id.
3361                       '" title="'.$strpruneheading.'">'.$strprune.'</a>';
3362     }
3364     if (($ownpost and $age < $CFG->maxeditingtime
3365                 and $cm->cache->caps['mod/forum:deleteownpost'])
3366                 or $cm->cache->caps['mod/forum:deleteanypost']) {
3367         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?delete='.$post->id.'">'.$strdelete.'</a>';
3368     }
3370     if ($reply) {
3371         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.$strreply.'</a>';
3372     }
3374     if ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost'])) {
3375         $p = array(
3376             'postid' => $post->id,
3377         );
3378         require_once($CFG->libdir.'/portfoliolib.php');
3379         $button = new portfolio_add_button();
3380         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php');
3381         if (empty($attachments)) {
3382             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3383         } else {
3384             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3385         }
3387         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3388         if (!empty($porfoliohtml)) {
3389             $commands[] = $porfoliohtml;
3390         }
3391     }
3393     echo '<div class="commands">';
3394     echo implode(' | ', $commands);
3395     echo '</div>';
3398 // Ratings
3399     if (!empty($post->rating)) {
3400         echo html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
3401     }
3403 // Link to post if required
3405     if ($link) {
3406         echo '<div class="link">';
3407         if ($post->replies == 1) {
3408             $replystring = get_string('repliesone', 'forum', $post->replies);
3409         } else {
3410             $replystring = get_string('repliesmany', 'forum', $post->replies);
3411         }
3412         echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.
3413              get_string('discussthistopic', 'forum').'</a>&nbsp;('.$replystring.')';
3414         echo '</div>';
3415     }
3417     if ($footer) {
3418         echo '<div class="footer">'.$footer.'</div>';
3419     }
3420     echo '</td></tr></table>'."\n\n";
3422     if ($istracked && !$CFG->forum_usermarksread && !$post_read) {
3423         forum_tp_mark_post_read($USER->id, $post, $forum->id);
3424     }
3427 /**
3428  * Return rating related permissions
3429  * @param string $options the context id
3430  * @return array an associative array of the user's rating permissions
3431  */
3432 function forum_rating_permissions($contextid) {
3433     $context = get_context_instance_by_id($contextid);
3435     if (!$context) {
3436         print_error('invalidcontext');
3437         return null;
3438     } else {
3439         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));
3440     }
3443 /**
3444  * Returns the names of the table and columns necessary to check items for ratings
3445  * @return array an array containing the item table, item id and user id columns
3446  */
3447 function forum_rating_item_check_info() {
3448     return array('forum_posts','id','userid');
3452 /**
3453  * This function prints the overview of a discussion in the forum listing.
3454  * It needs some discussion information and some post information, these
3455  * happen to be combined for efficiency in the $post parameter by the function
3456  * that calls this one: forum_print_latest_discussions()
3457  *
3458  * @global object
3459  * @global object
3460  * @param object $post The post object (passed by reference for speed).
3461  * @param object $forum The forum object.
3462  * @param int $group Current group.
3463  * @param string $datestring Format to use for the dates.
3464  * @param boolean $cantrack Is tracking enabled for this forum.
3465  * @param boolean $forumtracked Is the user tracking this forum.
3466  * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
3467  */
3468 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
3469                                         $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) {
3471     global $USER, $CFG, $OUTPUT;
3473     static $rowcount;
3474     static $strmarkalldread;
3476     if (empty($modcontext)) {
3477         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3478             print_error('invalidcoursemodule');
3479         }
3480         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3481     }
3483     if (!isset($rowcount)) {
3484         $rowcount = 0;
3485         $strmarkalldread = get_string('markalldread', 'forum');
3486     } else {
3487         $rowcount = ($rowcount + 1) % 2;
3488     }
3490     $post->subject = format_string($post->subject,true);
3492     echo "\n\n";
3493     echo '<tr class="discussion r'.$rowcount.'">';
3495     // Topic
3496     echo '<td class="topic starter">';
3497     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.$post->subject.'</a>';
3498     echo "</td>\n";
3500     // Picture
3501     $postuser = new stdClass();