MDL-32125 mod_forum: updating subscription mode not reflected
[moodle.git] / mod / forum / lib.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * @package    mod
19  * @subpackage 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 defined('MOODLE_INTERNAL') || die();
26 /** Include required files */
27 require_once($CFG->libdir.'/filelib.php');
28 require_once($CFG->libdir.'/eventslib.php');
29 require_once($CFG->dirroot.'/user/selector/lib.php');
30 require_once($CFG->dirroot.'/mod/forum/post_form.php');
32 /// CONSTANTS ///////////////////////////////////////////////////////////
34 define('FORUM_MODE_FLATOLDEST', 1);
35 define('FORUM_MODE_FLATNEWEST', -1);
36 define('FORUM_MODE_THREADED', 2);
37 define('FORUM_MODE_NESTED', 3);
39 define('FORUM_CHOOSESUBSCRIBE', 0);
40 define('FORUM_FORCESUBSCRIBE', 1);
41 define('FORUM_INITIALSUBSCRIBE', 2);
42 define('FORUM_DISALLOWSUBSCRIBE',3);
44 define('FORUM_TRACKING_OFF', 0);
45 define('FORUM_TRACKING_OPTIONAL', 1);
46 define('FORUM_TRACKING_ON', 2);
48 if (!defined('FORUM_CRON_USER_CACHE')) {
49     /** Defines how many full user records are cached in forum cron. */
50     define('FORUM_CRON_USER_CACHE', 5000);
51 }
53 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
55 /**
56  * Given an object containing all the necessary data,
57  * (defined by the form in mod_form.php) this function
58  * will create a new instance and return the id number
59  * of the new instance.
60  *
61  * @param stdClass $forum add forum instance
62  * @param mod_forum_mod_form $mform
63  * @return int intance id
64  */
65 function forum_add_instance($forum, $mform = null) {
66     global $CFG, $DB;
68     $forum->timemodified = time();
70     if (empty($forum->assessed)) {
71         $forum->assessed = 0;
72     }
74     if (empty($forum->ratingtime) or empty($forum->assessed)) {
75         $forum->assesstimestart  = 0;
76         $forum->assesstimefinish = 0;
77     }
79     $forum->id = $DB->insert_record('forum', $forum);
80     $modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule);
82     if ($forum->type == 'single') {  // Create related discussion.
83         $discussion = new stdClass();
84         $discussion->course        = $forum->course;
85         $discussion->forum         = $forum->id;
86         $discussion->name          = $forum->name;
87         $discussion->assessed      = $forum->assessed;
88         $discussion->message       = $forum->intro;
89         $discussion->messageformat = $forum->introformat;
90         $discussion->messagetrust  = trusttext_trusted(get_context_instance(CONTEXT_COURSE, $forum->course));
91         $discussion->mailnow       = false;
92         $discussion->groupid       = -1;
94         $message = '';
96         $discussion->id = forum_add_discussion($discussion, null, $message);
98         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
99             // ugly hack - we need to copy the files somehow
100             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
101             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
103             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id,
104                     mod_forum_post_form::attachment_options($forum), $post->message);
105             $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
106         }
107     }
109     if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
110     /// all users should be subscribed initially
111     /// Note: forum_get_potential_subscribers should take the forum context,
112     /// but that does not exist yet, becuase the forum is only half build at this
113     /// stage. However, because the forum is brand new, we know that there are
114     /// no role assignments or overrides in the forum context, so using the
115     /// course context gives the same list of users.
116         $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
117         foreach ($users as $user) {
118             forum_subscribe($user->id, $forum->id);
119         }
120     }
122     forum_grade_item_update($forum);
124     return $forum->id;
128 /**
129  * Given an object containing all the necessary data,
130  * (defined by the form in mod_form.php) this function
131  * will update an existing instance with new data.
132  *
133  * @global object
134  * @param object $forum forum instance (with magic quotes)
135  * @return bool success
136  */
137 function forum_update_instance($forum, $mform) {
138     global $DB, $OUTPUT, $USER;
140     $forum->timemodified = time();
141     $forum->id           = $forum->instance;
143     if (empty($forum->assessed)) {
144         $forum->assessed = 0;
145     }
147     if (empty($forum->ratingtime) or empty($forum->assessed)) {
148         $forum->assesstimestart  = 0;
149         $forum->assesstimefinish = 0;
150     }
152     $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
154     // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
155     // if  scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
156     // 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
157     if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
158         forum_update_grades($forum); // recalculate grades for the forum
159     }
161     if ($forum->type == 'single') {  // Update related discussion and post.
162         $discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC');
163         if (!empty($discussions)) {
164             if (count($discussions) > 1) {
165                 echo $OUTPUT->notification(get_string('warnformorepost', 'forum'));
166             }
167             $discussion = array_pop($discussions);
168         } else {
169             // try to recover by creating initial discussion - MDL-16262
170             $discussion = new stdClass();
171             $discussion->course          = $forum->course;
172             $discussion->forum           = $forum->id;
173             $discussion->name            = $forum->name;
174             $discussion->assessed        = $forum->assessed;
175             $discussion->message         = $forum->intro;
176             $discussion->messageformat   = $forum->introformat;
177             $discussion->messagetrust    = true;
178             $discussion->mailnow         = false;
179             $discussion->groupid         = -1;
181             $message = '';
183             forum_add_discussion($discussion, null, $message);
185             if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
186                 print_error('cannotadd', 'forum');
187             }
188         }
189         if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
190             print_error('cannotfindfirstpost', 'forum');
191         }
193         $cm         = get_coursemodule_from_instance('forum', $forum->id);
194         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
196         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
197             // ugly hack - we need to copy the files somehow
198             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
199             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
201             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id,
202                     mod_forum_post_form::editor_options(), $post->message);
203         }
205         $post->subject       = $forum->name;
206         $post->message       = $forum->intro;
207         $post->messageformat = $forum->introformat;
208         $post->messagetrust  = trusttext_trusted($modcontext);
209         $post->modified      = $forum->timemodified;
210         $post->userid        = $USER->id;    // MDL-18599, so that current teacher can take ownership of activities
212         $DB->update_record('forum_posts', $post);
213         $discussion->name = $forum->name;
214         $DB->update_record('forum_discussions', $discussion);
215     }
217     $DB->update_record('forum', $forum);
219     $modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule);
220     if (($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) && ($oldforum->forcesubscribe <> $forum->forcesubscribe)) {
221         $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
222         foreach ($users as $user) {
223             forum_subscribe($user->id, $forum->id);
224         }
225     }
226     forum_grade_item_update($forum);
228     return true;
232 /**
233  * Given an ID of an instance of this module,
234  * this function will permanently delete the instance
235  * and any data that depends on it.
236  *
237  * @global object
238  * @param int $id forum instance id
239  * @return bool success
240  */
241 function forum_delete_instance($id) {
242     global $DB;
244     if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
245         return false;
246     }
247     if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
248         return false;
249     }
250     if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
251         return false;
252     }
254     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
256     // now get rid of all files
257     $fs = get_file_storage();
258     $fs->delete_area_files($context->id);
260     $result = true;
262     if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
263         foreach ($discussions as $discussion) {
264             if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
265                 $result = false;
266             }
267         }
268     }
270     if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
271         $result = false;
272     }
274     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
276     if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
277         $result = false;
278     }
280     forum_grade_item_delete($forum);
282     return $result;
286 /**
287  * Indicates API features that the forum supports.
288  *
289  * @uses FEATURE_GROUPS
290  * @uses FEATURE_GROUPINGS
291  * @uses FEATURE_GROUPMEMBERSONLY
292  * @uses FEATURE_MOD_INTRO
293  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
294  * @uses FEATURE_COMPLETION_HAS_RULES
295  * @uses FEATURE_GRADE_HAS_GRADE
296  * @uses FEATURE_GRADE_OUTCOMES
297  * @param string $feature
298  * @return mixed True if yes (some features may use other values)
299  */
300 function forum_supports($feature) {
301     switch($feature) {
302         case FEATURE_GROUPS:                  return true;
303         case FEATURE_GROUPINGS:               return true;
304         case FEATURE_GROUPMEMBERSONLY:        return true;
305         case FEATURE_MOD_INTRO:               return true;
306         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
307         case FEATURE_COMPLETION_HAS_RULES:    return true;
308         case FEATURE_GRADE_HAS_GRADE:         return true;
309         case FEATURE_GRADE_OUTCOMES:          return true;
310         case FEATURE_RATE:                    return true;
311         case FEATURE_BACKUP_MOODLE2:          return true;
312         case FEATURE_SHOW_DESCRIPTION:        return true;
314         default: return null;
315     }
319 /**
320  * Obtains the automatic completion state for this forum based on any conditions
321  * in forum settings.
322  *
323  * @global object
324  * @global object
325  * @param object $course Course
326  * @param object $cm Course-module
327  * @param int $userid User ID
328  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
329  * @return bool True if completed, false if not. (If no conditions, then return
330  *   value depends on comparison type)
331  */
332 function forum_get_completion_state($course,$cm,$userid,$type) {
333     global $CFG,$DB;
335     // Get forum details
336     if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
337         throw new Exception("Can't find forum {$cm->instance}");
338     }
340     $result=$type; // Default return value
342     $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
343     $postcountsql="
344 SELECT
345     COUNT(1)
346 FROM
347     {forum_posts} fp
348     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
349 WHERE
350     fp.userid=:userid AND fd.forum=:forumid";
352     if ($forum->completiondiscussions) {
353         $value = $forum->completiondiscussions <=
354                  $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
355         if ($type == COMPLETION_AND) {
356             $result = $result && $value;
357         } else {
358             $result = $result || $value;
359         }
360     }
361     if ($forum->completionreplies) {
362         $value = $forum->completionreplies <=
363                  $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
364         if ($type==COMPLETION_AND) {
365             $result = $result && $value;
366         } else {
367             $result = $result || $value;
368         }
369     }
370     if ($forum->completionposts) {
371         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
372         if ($type == COMPLETION_AND) {
373             $result = $result && $value;
374         } else {
375             $result = $result || $value;
376         }
377     }
379     return $result;
382 /**
383  * Create a message-id string to use in the custom headers of forum notification emails
384  *
385  * message-id is used by email clients to identify emails and to nest conversations
386  *
387  * @param int $postid The ID of the forum post we are notifying the user about
388  * @param int $usertoid The ID of the user being notified
389  * @param string $hostname The server's hostname
390  * @return string A unique message-id
391  */
392 function forum_get_email_message_id($postid, $usertoid, $hostname) {
393     return '<'.hash('sha256',$postid.'to'.$usertoid).'@'.$hostname.'>';
396 /**
397  * Removes properties from user record that are not necessary
398  * for sending post notifications.
399  * @param stdClass $user
400  * @return void, $user parameter is modified
401  */
402 function forum_cron_minimise_user_record(stdClass $user) {
404     // We store large amount of users in one huge array,
405     // make sure we do not store info there we do not actually need
406     // in mail generation code or messaging.
408     unset($user->institution);
409     unset($user->department);
410     unset($user->address);
411     unset($user->city);
412     unset($user->url);
413     unset($user->currentlogin);
414     unset($user->description);
415     unset($user->descriptionformat);
418 /**
419  * Function to be run periodically according to the moodle cron
420  * Finds all posts that have yet to be mailed out, and mails them
421  * out to all subscribers
422  *
423  * @global object
424  * @global object
425  * @global object
426  * @uses CONTEXT_MODULE
427  * @uses CONTEXT_COURSE
428  * @uses SITEID
429  * @uses FORMAT_PLAIN
430  * @return void
431  */
432 function forum_cron() {
433     global $CFG, $USER, $DB;
435     $site = get_site();
437     // All users that are subscribed to any post that needs sending,
438     // please increase $CFG->extramemorylimit on large sites that
439     // send notifications to a large number of users.
440     $users = array();
441     $userscount = 0; // Cached user counter - count($users) in PHP is horribly slow!!!
443     // status arrays
444     $mailcount  = array();
445     $errorcount = array();
447     // caches
448     $discussions     = array();
449     $forums          = array();
450     $courses         = array();
451     $coursemodules   = array();
452     $subscribedusers = array();
455     // Posts older than 2 days will not be mailed.  This is to avoid the problem where
456     // cron has not been running for a long time, and then suddenly people are flooded
457     // with mail from the past few weeks or months
458     $timenow   = time();
459     $endtime   = $timenow - $CFG->maxeditingtime;
460     $starttime = $endtime - 48 * 3600;   // Two days earlier
462     if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
463         // Mark them all now as being mailed.  It's unlikely but possible there
464         // might be an error later so that a post is NOT actually mailed out,
465         // but since mail isn't crucial, we can accept this risk.  Doing it now
466         // prevents the risk of duplicated mails, which is a worse problem.
468         if (!forum_mark_old_posts_as_mailed($endtime)) {
469             mtrace('Errors occurred while trying to mark some posts as being mailed.');
470             return false;  // Don't continue trying to mail them, in case we are in a cron loop
471         }
473         // checking post validity, and adding users to loop through later
474         foreach ($posts as $pid => $post) {
476             $discussionid = $post->discussion;
477             if (!isset($discussions[$discussionid])) {
478                 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
479                     $discussions[$discussionid] = $discussion;
480                 } else {
481                     mtrace('Could not find discussion '.$discussionid);
482                     unset($posts[$pid]);
483                     continue;
484                 }
485             }
486             $forumid = $discussions[$discussionid]->forum;
487             if (!isset($forums[$forumid])) {
488                 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
489                     $forums[$forumid] = $forum;
490                 } else {
491                     mtrace('Could not find forum '.$forumid);
492                     unset($posts[$pid]);
493                     continue;
494                 }
495             }
496             $courseid = $forums[$forumid]->course;
497             if (!isset($courses[$courseid])) {
498                 if ($course = $DB->get_record('course', array('id' => $courseid))) {
499                     $courses[$courseid] = $course;
500                 } else {
501                     mtrace('Could not find course '.$courseid);
502                     unset($posts[$pid]);
503                     continue;
504                 }
505             }
506             if (!isset($coursemodules[$forumid])) {
507                 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
508                     $coursemodules[$forumid] = $cm;
509                 } else {
510                     mtrace('Could not find course module for forum '.$forumid);
511                     unset($posts[$pid]);
512                     continue;
513                 }
514             }
517             // caching subscribed users of each forum
518             if (!isset($subscribedusers[$forumid])) {
519                 $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
520                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext, "u.*")) {
521                     foreach ($subusers as $postuser) {
522                         // this user is subscribed to this forum
523                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
524                         $userscount++;
525                         if ($userscount > FORUM_CRON_USER_CACHE) {
526                             // Store minimal user info.
527                             $minuser = new stdClass();
528                             $minuser->id = $postuser->id;
529                             $users[$postuser->id] = $minuser;
530                         } else {
531                             // Cache full user record.
532                             forum_cron_minimise_user_record($postuser);
533                             $users[$postuser->id] = $postuser;
534                         }
535                     }
536                     // Release memory.
537                     unset($subusers);
538                     unset($postuser);
539                 }
540             }
542             $mailcount[$pid] = 0;
543             $errorcount[$pid] = 0;
544         }
545     }
547     if ($users && $posts) {
549         $urlinfo = parse_url($CFG->wwwroot);
550         $hostname = $urlinfo['host'];
552         foreach ($users as $userto) {
554             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
556             mtrace('Processing user '.$userto->id);
558             // Init user caches - we keep the cache for one cycle only,
559             // otherwise it could consume too much memory.
560             if (isset($userto->username)) {
561                 $userto = clone($userto);
562             } else {
563                 $userto = $DB->get_record('user', array('id' => $userto->id));
564                 forum_cron_minimise_user_record($userto);
565             }
566             $userto->viewfullnames = array();
567             $userto->canpost       = array();
568             $userto->markposts     = array();
570             // set this so that the capabilities are cached, and environment matches receiving user
571             cron_setup_user($userto);
573             // reset the caches
574             foreach ($coursemodules as $forumid=>$unused) {
575                 $coursemodules[$forumid]->cache       = new stdClass();
576                 $coursemodules[$forumid]->cache->caps = array();
577                 unset($coursemodules[$forumid]->uservisible);
578             }
580             foreach ($posts as $pid => $post) {
582                 // Set up the environment for the post, discussion, forum, course
583                 $discussion = $discussions[$post->discussion];
584                 $forum      = $forums[$discussion->forum];
585                 $course     = $courses[$forum->course];
586                 $cm         =& $coursemodules[$forum->id];
588                 // Do some checks  to see if we can bail out now
589                 // Only active enrolled users are in the list of subscribers
590                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
591                     continue; // user does not subscribe to this forum
592                 }
594                 // Don't send email if the forum is Q&A and the user has not posted
595                 // Initial topics are still mailed
596                 if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id) && $pid != $discussion->firstpost) {
597                     mtrace('Did not email '.$userto->id.' because user has not posted in discussion');
598                     continue;
599                 }
601                 // Get info about the sending user
602                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
603                     $userfrom = $users[$post->userid];
604                     if (!isset($userfrom->idnumber)) {
605                         // Minimalised user info, fetch full record.
606                         $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
607                         forum_cron_minimise_user_record($userfrom);
608                     }
610                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
611                     forum_cron_minimise_user_record($userfrom);
612                     // Fetch only once if possible, we can add it to user list, it will be skipped anyway.
613                     if ($userscount <= FORUM_CRON_USER_CACHE) {
614                         $userscount++;
615                         $users[$userfrom->id] = $userfrom;
616                     }
618                 } else {
619                     mtrace('Could not find user '.$post->userid);
620                     continue;
621                 }
623                 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
625                 // setup global $COURSE properly - needed for roles and languages
626                 cron_setup_user($userto, $course);
628                 // Fill caches
629                 if (!isset($userto->viewfullnames[$forum->id])) {
630                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
631                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
632                 }
633                 if (!isset($userto->canpost[$discussion->id])) {
634                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
635                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
636                 }
637                 if (!isset($userfrom->groups[$forum->id])) {
638                     if (!isset($userfrom->groups)) {
639                         $userfrom->groups = array();
640                         if (isset($users[$userfrom->id])) {
641                             $users[$userfrom->id]->groups = array();
642                         }
643                     }
644                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
645                     if (isset($users[$userfrom->id])) {
646                         $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
647                     }
648                 }
650                 // Make sure groups allow this user to see this email
651                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
652                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
653                         continue;                           // Be safe and don't send it to anyone
654                     }
656                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
657                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
658                         continue;
659                     }
660                 }
662                 // Make sure we're allowed to see it...
663                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
664                     mtrace('user '.$userto->id. ' can not see '.$post->id);
665                     continue;
666                 }
668                 // OK so we need to send the email.
670                 // Does the user want this post in a digest?  If so postpone it for now.
671                 if ($userto->maildigest > 0) {
672                     // This user wants the mails to be in digest form
673                     $queue = new stdClass();
674                     $queue->userid       = $userto->id;
675                     $queue->discussionid = $discussion->id;
676                     $queue->postid       = $post->id;
677                     $queue->timemodified = $post->created;
678                     $DB->insert_record('forum_queue', $queue);
679                     continue;
680                 }
683                 // Prepare to actually send the post now, and build up the content
685                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
687                 $userfrom->customheaders = array (  // Headers to make emails easier to track
688                            'Precedence: Bulk',
689                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
690                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
691                            'Message-ID: '.forum_get_email_message_id($post->id, $userto->id, $hostname),
692                            'X-Course-Id: '.$course->id,
693                            'X-Course-Name: '.format_string($course->fullname, true)
694                 );
696                 if ($post->parent) {  // This post is a reply, so add headers for threading (see MDL-22551)
697                     $userfrom->customheaders[] = 'In-Reply-To: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
698                     $userfrom->customheaders[] = 'References: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
699                 }
701                 $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
703                 $postsubject = html_to_text("$shortname: ".format_string($post->subject, true));
704                 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
705                 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
707                 // Send the post now!
709                 mtrace('Sending ', '');
711                 $eventdata = new stdClass();
712                 $eventdata->component        = 'mod_forum';
713                 $eventdata->name             = 'posts';
714                 $eventdata->userfrom         = $userfrom;
715                 $eventdata->userto           = $userto;
716                 $eventdata->subject          = $postsubject;
717                 $eventdata->fullmessage      = $posttext;
718                 $eventdata->fullmessageformat = FORMAT_PLAIN;
719                 $eventdata->fullmessagehtml  = $posthtml;
720                 $eventdata->notification = 1;
722                 $smallmessagestrings = new stdClass();
723                 $smallmessagestrings->user = fullname($userfrom);
724                 $smallmessagestrings->forumname = "$shortname: ".format_string($forum->name,true).": ".$discussion->name;
725                 $smallmessagestrings->message = $post->message;
726                 //make sure strings are in message recipients language
727                 $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
729                 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
730                 $eventdata->contexturlname = $discussion->name;
732                 $mailresult = message_send($eventdata);
733                 if (!$mailresult){
734                     mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
735                          " ($userto->email) .. not trying again.");
736                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
737                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
738                     $errorcount[$post->id]++;
739                 } else {
740                     $mailcount[$post->id]++;
742                 // Mark post as read if forum_usermarksread is set off
743                     if (!$CFG->forum_usermarksread) {
744                         $userto->markposts[$post->id] = $post->id;
745                     }
746                 }
748                 mtrace('post '.$post->id. ': '.$post->subject);
749             }
751             // mark processed posts as read
752             forum_tp_mark_posts_read($userto, $userto->markposts);
753             unset($userto);
754         }
755     }
757     if ($posts) {
758         foreach ($posts as $post) {
759             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
760             if ($errorcount[$post->id]) {
761                 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
762             }
763         }
764     }
766     // release some memory
767     unset($subscribedusers);
768     unset($mailcount);
769     unset($errorcount);
771     cron_setup_user();
773     $sitetimezone = $CFG->timezone;
775     // Now see if there are any digest mails waiting to be sent, and if we should send them
777     mtrace('Starting digest processing...');
779     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
781     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
782         set_config('digestmailtimelast', 0);
783     }
785     $timenow = time();
786     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
788     // Delete any really old ones (normally there shouldn't be any)
789     $weekago = $timenow - (7 * 24 * 3600);
790     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
791     mtrace ('Cleaned old digest records');
793     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
795         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
797         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
799         if ($digestposts_rs->valid()) {
801             // We have work to do
802             $usermailcount = 0;
804             //caches - reuse the those filled before too
805             $discussionposts = array();
806             $userdiscussions = array();
808             foreach ($digestposts_rs as $digestpost) {
809                 if (!isset($posts[$digestpost->postid])) {
810                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
811                         $posts[$digestpost->postid] = $post;
812                     } else {
813                         continue;
814                     }
815                 }
816                 $discussionid = $digestpost->discussionid;
817                 if (!isset($discussions[$discussionid])) {
818                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
819                         $discussions[$discussionid] = $discussion;
820                     } else {
821                         continue;
822                     }
823                 }
824                 $forumid = $discussions[$discussionid]->forum;
825                 if (!isset($forums[$forumid])) {
826                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
827                         $forums[$forumid] = $forum;
828                     } else {
829                         continue;
830                     }
831                 }
833                 $courseid = $forums[$forumid]->course;
834                 if (!isset($courses[$courseid])) {
835                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
836                         $courses[$courseid] = $course;
837                     } else {
838                         continue;
839                     }
840                 }
842                 if (!isset($coursemodules[$forumid])) {
843                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
844                         $coursemodules[$forumid] = $cm;
845                     } else {
846                         continue;
847                     }
848                 }
849                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
850                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
851             }
852             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
854             // Data collected, start sending out emails to each user
855             foreach ($userdiscussions as $userid => $thesediscussions) {
857                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
859                 cron_setup_user();
861                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
863                 // First of all delete all the queue entries for this user
864                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
866                 // Init user caches - we keep the cache for one cycle only,
867                 // otherwise it would unnecessarily consume memory.
868                 if (array_key_exists($userid, $users) and isset($users[$userid]->username)) {
869                     $userto = clone($users[$userid]);
870                 } else {
871                     $userto = $DB->get_record('user', array('id' => $userid));
872                     forum_cron_minimise_user_record($userto);
873                 }
874                 $userto->viewfullnames = array();
875                 $userto->canpost       = array();
876                 $userto->markposts     = array();
878                 // Override the language and timezone of the "current" user, so that
879                 // mail is customised for the receiver.
880                 cron_setup_user($userto);
882                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
884                 $headerdata = new stdClass();
885                 $headerdata->sitename = format_string($site->fullname, true);
886                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
888                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
889                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
891                 $posthtml = "<head>";
892 /*                foreach ($CFG->stylesheets as $stylesheet) {
893                     //TODO: MDL-21120
894                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
895                 }*/
896                 $posthtml .= "</head>\n<body id=\"email\">\n";
897                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
899                 foreach ($thesediscussions as $discussionid) {
901                     @set_time_limit(120);   // to be reset for each post
903                     $discussion = $discussions[$discussionid];
904                     $forum      = $forums[$discussion->forum];
905                     $course     = $courses[$forum->course];
906                     $cm         = $coursemodules[$forum->id];
908                     //override language
909                     cron_setup_user($userto, $course);
911                     // Fill caches
912                     if (!isset($userto->viewfullnames[$forum->id])) {
913                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
914                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
915                     }
916                     if (!isset($userto->canpost[$discussion->id])) {
917                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
918                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
919                     }
921                     $strforums      = get_string('forums', 'forum');
922                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
923                     $canreply       = $userto->canpost[$discussion->id];
924                     $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
926                     $posttext .= "\n \n";
927                     $posttext .= '=====================================================================';
928                     $posttext .= "\n \n";
929                     $posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
930                     if ($discussion->name != $forum->name) {
931                         $posttext  .= " -> ".format_string($discussion->name,true);
932                     }
933                     $posttext .= "\n";
935                     $posthtml .= "<p><font face=\"sans-serif\">".
936                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
937                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
938                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
939                     if ($discussion->name == $forum->name) {
940                         $posthtml .= "</font></p>";
941                     } else {
942                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
943                     }
944                     $posthtml .= '<p>';
946                     $postsarray = $discussionposts[$discussionid];
947                     sort($postsarray);
949                     foreach ($postsarray as $postid) {
950                         $post = $posts[$postid];
952                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
953                             $userfrom = $users[$post->userid];
954                             if (!isset($userfrom->idnumber)) {
955                                 $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
956                                 forum_cron_minimise_user_record($userfrom);
957                             }
959                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
960                             forum_cron_minimise_user_record($userfrom);
961                             if ($userscount <= FORUM_CRON_USER_CACHE) {
962                                 $userscount++;
963                                 $users[$userfrom->id] = $userfrom;
964                             }
966                         } else {
967                             mtrace('Could not find user '.$post->userid);
968                             continue;
969                         }
971                         if (!isset($userfrom->groups[$forum->id])) {
972                             if (!isset($userfrom->groups)) {
973                                 $userfrom->groups = array();
974                                 if (isset($users[$userfrom->id])) {
975                                     $users[$userfrom->id]->groups = array();
976                                 }
977                             }
978                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
979                             if (isset($users[$userfrom->id])) {
980                                 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
981                             }
982                         }
984                         $userfrom->customheaders = array ("Precedence: Bulk");
986                         if ($userto->maildigest == 2) {
987                             // Subjects only
988                             $by = new stdClass();
989                             $by->name = fullname($userfrom);
990                             $by->date = userdate($post->modified);
991                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
992                             $posttext .= "\n---------------------------------------------------------------------";
994                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
995                             $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>';
997                         } else {
998                             // The full treatment
999                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
1000                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1002                         // Create an array of postid's for this user to mark as read.
1003                             if (!$CFG->forum_usermarksread) {
1004                                 $userto->markposts[$post->id] = $post->id;
1005                             }
1006                         }
1007                     }
1008                     if ($canunsubscribe) {
1009                         $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>";
1010                     } else {
1011                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
1012                     }
1013                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
1014                 }
1015                 $posthtml .= '</body>';
1017                 if (empty($userto->mailformat) || $userto->mailformat != 1) {
1018                     // This user DOESN'T want to receive HTML
1019                     $posthtml = '';
1020                 }
1022                 $attachment = $attachname='';
1023                 $usetrueaddress = true;
1024                 // Directly email forum digests rather than sending them via messaging, use the
1025                 // site shortname as 'from name', the noreply address will be used by email_to_user.
1026                 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname, $usetrueaddress, $CFG->forum_replytouser);
1028                 if (!$mailresult) {
1029                     mtrace("ERROR!");
1030                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
1031                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
1032                 } else {
1033                     mtrace("success.");
1034                     $usermailcount++;
1036                     // Mark post as read if forum_usermarksread is set off
1037                     forum_tp_mark_posts_read($userto, $userto->markposts);
1038                 }
1039             }
1040         }
1041     /// We have finishied all digest emails, update $CFG->digestmailtimelast
1042         set_config('digestmailtimelast', $timenow);
1043     }
1045     cron_setup_user();
1047     if (!empty($usermailcount)) {
1048         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
1049     }
1051     if (!empty($CFG->forum_lastreadclean)) {
1052         $timenow = time();
1053         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
1054             set_config('forum_lastreadclean', $timenow);
1055             mtrace('Removing old forum read tracking info...');
1056             forum_tp_clean_read_records();
1057         }
1058     } else {
1059         set_config('forum_lastreadclean', time());
1060     }
1063     return true;
1066 /**
1067  * Builds and returns the body of the email notification in plain text.
1068  *
1069  * @global object
1070  * @global object
1071  * @uses CONTEXT_MODULE
1072  * @param object $course
1073  * @param object $cm
1074  * @param object $forum
1075  * @param object $discussion
1076  * @param object $post
1077  * @param object $userfrom
1078  * @param object $userto
1079  * @param boolean $bare
1080  * @return string The email body in plain text format.
1081  */
1082 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
1083     global $CFG, $USER;
1085     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
1087     if (!isset($userto->viewfullnames[$forum->id])) {
1088         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
1089     } else {
1090         $viewfullnames = $userto->viewfullnames[$forum->id];
1091     }
1093     if (!isset($userto->canpost[$discussion->id])) {
1094         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
1095     } else {
1096         $canreply = $userto->canpost[$discussion->id];
1097     }
1099     $by = New stdClass;
1100     $by->name = fullname($userfrom, $viewfullnames);
1101     $by->date = userdate($post->modified, "", $userto->timezone);
1103     $strbynameondate = get_string('bynameondate', 'forum', $by);
1105     $strforums = get_string('forums', 'forum');
1107     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1109     $posttext = '';
1111     if (!$bare) {
1112         $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
1113         $posttext  = "$shortname -> $strforums -> ".format_string($forum->name,true);
1115         if ($discussion->name != $forum->name) {
1116             $posttext  .= " -> ".format_string($discussion->name,true);
1117         }
1118     }
1120     // add absolute file links
1121     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
1123     $posttext .= "\n---------------------------------------------------------------------\n";
1124     $posttext .= format_string($post->subject,true);
1125     if ($bare) {
1126         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1127     }
1128     $posttext .= "\n".$strbynameondate."\n";
1129     $posttext .= "---------------------------------------------------------------------\n";
1130     $posttext .= format_text_email($post->message, $post->messageformat);
1131     $posttext .= "\n\n";
1132     $posttext .= forum_print_attachments($post, $cm, "text");
1134     if (!$bare && $canreply) {
1135         $posttext .= "---------------------------------------------------------------------\n";
1136         $posttext .= get_string("postmailinfo", "forum", $shortname)."\n";
1137         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1138     }
1139     if (!$bare && $canunsubscribe) {
1140         $posttext .= "\n---------------------------------------------------------------------\n";
1141         $posttext .= get_string("unsubscribe", "forum");
1142         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1143     }
1145     return $posttext;
1148 /**
1149  * Builds and returns the body of the email notification in html format.
1150  *
1151  * @global object
1152  * @param object $course
1153  * @param object $cm
1154  * @param object $forum
1155  * @param object $discussion
1156  * @param object $post
1157  * @param object $userfrom
1158  * @param object $userto
1159  * @return string The email text in HTML format
1160  */
1161 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1162     global $CFG;
1164     if ($userto->mailformat != 1) {  // Needs to be HTML
1165         return '';
1166     }
1168     if (!isset($userto->canpost[$discussion->id])) {
1169         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course);
1170     } else {
1171         $canreply = $userto->canpost[$discussion->id];
1172     }
1174     $strforums = get_string('forums', 'forum');
1175     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1176     $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
1178     $posthtml = '<head>';
1179 /*    foreach ($CFG->stylesheets as $stylesheet) {
1180         //TODO: MDL-21120
1181         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1182     }*/
1183     $posthtml .= '</head>';
1184     $posthtml .= "\n<body id=\"email\">\n\n";
1186     $posthtml .= '<div class="navbar">'.
1187     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$shortname.'</a> &raquo; '.
1188     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1189     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1190     if ($discussion->name == $forum->name) {
1191         $posthtml .= '</div>';
1192     } else {
1193         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1194                      format_string($discussion->name,true).'</a></div>';
1195     }
1196     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1198     if ($canunsubscribe) {
1199         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1200                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1201                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1202     }
1204     $posthtml .= '</body>';
1206     return $posthtml;
1210 /**
1211  *
1212  * @param object $course
1213  * @param object $user
1214  * @param object $mod TODO this is not used in this function, refactor
1215  * @param object $forum
1216  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1217  */
1218 function forum_user_outline($course, $user, $mod, $forum) {
1219     global $CFG;
1220     require_once("$CFG->libdir/gradelib.php");
1221     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1222     if (empty($grades->items[0]->grades)) {
1223         $grade = false;
1224     } else {
1225         $grade = reset($grades->items[0]->grades);
1226     }
1228     $count = forum_count_user_posts($forum->id, $user->id);
1230     if ($count && $count->postcount > 0) {
1231         $result = new stdClass();
1232         $result->info = get_string("numposts", "forum", $count->postcount);
1233         $result->time = $count->lastpost;
1234         if ($grade) {
1235             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1236         }
1237         return $result;
1238     } else if ($grade) {
1239         $result = new stdClass();
1240         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1242         //datesubmitted == time created. dategraded == time modified or time overridden
1243         //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
1244         //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
1245         if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1246             $result->time = $grade->dategraded;
1247         } else {
1248             $result->time = $grade->datesubmitted;
1249         }
1251         return $result;
1252     }
1253     return NULL;
1257 /**
1258  * @global object
1259  * @global object
1260  * @param object $coure
1261  * @param object $user
1262  * @param object $mod
1263  * @param object $forum
1264  */
1265 function forum_user_complete($course, $user, $mod, $forum) {
1266     global $CFG,$USER, $OUTPUT;
1267     require_once("$CFG->libdir/gradelib.php");
1269     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1270     if (!empty($grades->items[0]->grades)) {
1271         $grade = reset($grades->items[0]->grades);
1272         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1273         if ($grade->str_feedback) {
1274             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1275         }
1276     }
1278     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1280         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1281             print_error('invalidcoursemodule');
1282         }
1283         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1285         foreach ($posts as $post) {
1286             if (!isset($discussions[$post->discussion])) {
1287                 continue;
1288             }
1289             $discussion = $discussions[$post->discussion];
1291             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1292         }
1293     } else {
1294         echo "<p>".get_string("noposts", "forum")."</p>";
1295     }
1303 /**
1304  * @global object
1305  * @global object
1306  * @global object
1307  * @param array $courses
1308  * @param array $htmlarray
1309  */
1310 function forum_print_overview($courses,&$htmlarray) {
1311     global $USER, $CFG, $DB, $SESSION;
1313     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1314         return array();
1315     }
1317     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1318         return;
1319     }
1321     // Courses to search for new posts
1322     $coursessqls = array();
1323     $params = array();
1324     foreach ($courses as $course) {
1326         // If the user has never entered into the course all posts are pending
1327         if ($course->lastaccess == 0) {
1328             $coursessqls[] = '(f.course = ?)';
1329             $params[] = $course->id;
1331         // Only posts created after the course last access
1332         } else {
1333             $coursessqls[] = '(f.course = ? AND p.created > ?)';
1334             $params[] = $course->id;
1335             $params[] = $course->lastaccess;
1336         }
1337     }
1338     $params[] = $USER->id;
1339     $coursessql = implode(' OR ', $coursessqls);
1341     $sql = "SELECT f.id, COUNT(*) as count "
1342                 .'FROM {forum} f '
1343                 .'JOIN {forum_discussions} d ON d.forum  = f.id '
1344                 .'JOIN {forum_posts} p ON p.discussion = d.id '
1345                 ."WHERE ($coursessql) "
1346                 .'AND p.userid != ? '
1347                 .'GROUP BY f.id';
1349     if (!$new = $DB->get_records_sql($sql, $params)) {
1350         $new = array(); // avoid warnings
1351     }
1353     // also get all forum tracking stuff ONCE.
1354     $trackingforums = array();
1355     foreach ($forums as $forum) {
1356         if (forum_tp_can_track_forums($forum)) {
1357             $trackingforums[$forum->id] = $forum;
1358         }
1359     }
1361     if (count($trackingforums) > 0) {
1362         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1363         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1364             ' FROM {forum_posts} p '.
1365             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1366             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1367         $params = array($USER->id);
1369         foreach ($trackingforums as $track) {
1370             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1371             $params[] = $track->id;
1372             if (isset($SESSION->currentgroup[$track->course])) {
1373                 $groupid =  $SESSION->currentgroup[$track->course];
1374             } else {
1375                 // get first groupid
1376                 $groupids = groups_get_all_groups($track->course, $USER->id);
1377                 if ($groupids) {
1378                     reset($groupids);
1379                     $groupid = key($groupids);
1380                     $SESSION->currentgroup[$track->course] = $groupid;
1381                 } else {
1382                     $groupid = 0;
1383                 }
1384                 unset($groupids);
1385             }
1386             $params[] = $groupid;
1387         }
1388         $sql = substr($sql,0,-3); // take off the last OR
1389         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1390         $params[] = $cutoffdate;
1392         if (!$unread = $DB->get_records_sql($sql, $params)) {
1393             $unread = array();
1394         }
1395     } else {
1396         $unread = array();
1397     }
1399     if (empty($unread) and empty($new)) {
1400         return;
1401     }
1403     $strforum = get_string('modulename','forum');
1405     foreach ($forums as $forum) {
1406         $str = '';
1407         $count = 0;
1408         $thisunread = 0;
1409         $showunread = false;
1410         // either we have something from logs, or trackposts, or nothing.
1411         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1412             $count = $new[$forum->id]->count;
1413         }
1414         if (array_key_exists($forum->id,$unread)) {
1415             $thisunread = $unread[$forum->id]->count;
1416             $showunread = true;
1417         }
1418         if ($count > 0 || $thisunread > 0) {
1419             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1420                 $forum->name.'</a></div>';
1421             $str .= '<div class="info"><span class="postsincelogin">';
1422             $str .= get_string('overviewnumpostssince', 'forum', $count)."</span>";
1423             if (!empty($showunread)) {
1424                 $str .= '<div class="unreadposts">'.get_string('overviewnumunread', 'forum', $thisunread).'</div>';
1425             }
1426             $str .= '</div></div>';
1427         }
1428         if (!empty($str)) {
1429             if (!array_key_exists($forum->course,$htmlarray)) {
1430                 $htmlarray[$forum->course] = array();
1431             }
1432             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1433                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1434             }
1435             $htmlarray[$forum->course]['forum'] .= $str;
1436         }
1437     }
1440 /**
1441  * Given a course and a date, prints a summary of all the new
1442  * messages posted in the course since that date
1443  *
1444  * @global object
1445  * @global object
1446  * @global object
1447  * @uses CONTEXT_MODULE
1448  * @uses VISIBLEGROUPS
1449  * @param object $course
1450  * @param bool $viewfullnames capability
1451  * @param int $timestart
1452  * @return bool success
1453  */
1454 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1455     global $CFG, $USER, $DB, $OUTPUT;
1457     // do not use log table if possible, it may be huge and is expensive to join with other tables
1459     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1460                                               d.timestart, d.timeend, d.userid AS duserid,
1461                                               u.firstname, u.lastname, u.email, u.picture
1462                                          FROM {forum_posts} p
1463                                               JOIN {forum_discussions} d ON d.id = p.discussion
1464                                               JOIN {forum} f             ON f.id = d.forum
1465                                               JOIN {user} u              ON u.id = p.userid
1466                                         WHERE p.created > ? AND f.course = ?
1467                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1468          return false;
1469     }
1471     $modinfo = get_fast_modinfo($course);
1473     $groupmodes = array();
1474     $cms    = array();
1476     $strftimerecent = get_string('strftimerecent');
1478     $printposts = array();
1479     foreach ($posts as $post) {
1480         if (!isset($modinfo->instances['forum'][$post->forum])) {
1481             // not visible
1482             continue;
1483         }
1484         $cm = $modinfo->instances['forum'][$post->forum];
1485         if (!$cm->uservisible) {
1486             continue;
1487         }
1488         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1490         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1491             continue;
1492         }
1494         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1495           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1496             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1497                 continue;
1498             }
1499         }
1501         $groupmode = groups_get_activity_groupmode($cm, $course);
1503         if ($groupmode) {
1504             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1505                 // oki (Open discussions have groupid -1)
1506             } else {
1507                 // separate mode
1508                 if (isguestuser()) {
1509                     // shortcut
1510                     continue;
1511                 }
1513                 if (is_null($modinfo->groups)) {
1514                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1515                 }
1517                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1518                     continue;
1519                 }
1520             }
1521         }
1523         $printposts[] = $post;
1524     }
1525     unset($posts);
1527     if (!$printposts) {
1528         return false;
1529     }
1531     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1532     echo "\n<ul class='unlist'>\n";
1534     foreach ($printposts as $post) {
1535         $subjectclass = empty($post->parent) ? ' bold' : '';
1537         echo '<li><div class="head">'.
1538                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1539                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1540              '</div>';
1541         echo '<div class="info'.$subjectclass.'">';
1542         if (empty($post->parent)) {
1543             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1544         } else {
1545             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1546         }
1547         $post->subject = break_up_long_words(format_string($post->subject, true));
1548         echo $post->subject;
1549         echo "</a>\"</div></li>\n";
1550     }
1552     echo "</ul>\n";
1554     return true;
1557 /**
1558  * Return grade for given user or all users.
1559  *
1560  * @global object
1561  * @global object
1562  * @param object $forum
1563  * @param int $userid optional user id, 0 means all users
1564  * @return array array of grades, false if none
1565  */
1566 function forum_get_user_grades($forum, $userid = 0) {
1567     global $CFG;
1569     require_once($CFG->dirroot.'/rating/lib.php');
1571     $ratingoptions = new stdClass;
1572     $ratingoptions->component = 'mod_forum';
1573     $ratingoptions->ratingarea = 'post';
1575     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1576     $ratingoptions->modulename = 'forum';
1577     $ratingoptions->moduleid   = $forum->id;
1578     $ratingoptions->userid = $userid;
1579     $ratingoptions->aggregationmethod = $forum->assessed;
1580     $ratingoptions->scaleid = $forum->scale;
1581     $ratingoptions->itemtable = 'forum_posts';
1582     $ratingoptions->itemtableusercolumn = 'userid';
1584     $rm = new rating_manager();
1585     return $rm->get_user_grades($ratingoptions);
1588 /**
1589  * Update activity grades
1590  *
1591  * @category grade
1592  * @param object $forum
1593  * @param int $userid specific user only, 0 means all
1594  * @param boolean $nullifnone return null if grade does not exist
1595  * @return void
1596  */
1597 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1598     global $CFG, $DB;
1599     require_once($CFG->libdir.'/gradelib.php');
1601     if (!$forum->assessed) {
1602         forum_grade_item_update($forum);
1604     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1605         forum_grade_item_update($forum, $grades);
1607     } else if ($userid and $nullifnone) {
1608         $grade = new stdClass();
1609         $grade->userid   = $userid;
1610         $grade->rawgrade = NULL;
1611         forum_grade_item_update($forum, $grade);
1613     } else {
1614         forum_grade_item_update($forum);
1615     }
1618 /**
1619  * Update all grades in gradebook.
1620  * @global object
1621  */
1622 function forum_upgrade_grades() {
1623     global $DB;
1625     $sql = "SELECT COUNT('x')
1626               FROM {forum} f, {course_modules} cm, {modules} m
1627              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1628     $count = $DB->count_records_sql($sql);
1630     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1631               FROM {forum} f, {course_modules} cm, {modules} m
1632              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1633     $rs = $DB->get_recordset_sql($sql);
1634     if ($rs->valid()) {
1635         $pbar = new progress_bar('forumupgradegrades', 500, true);
1636         $i=0;
1637         foreach ($rs as $forum) {
1638             $i++;
1639             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1640             forum_update_grades($forum, 0, false);
1641             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1642         }
1643     }
1644     $rs->close();
1647 /**
1648  * Create/update grade item for given forum
1649  *
1650  * @category grade
1651  * @uses GRADE_TYPE_NONE
1652  * @uses GRADE_TYPE_VALUE
1653  * @uses GRADE_TYPE_SCALE
1654  * @param stdClass $forum Forum object with extra cmidnumber
1655  * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
1656  * @return int 0 if ok
1657  */
1658 function forum_grade_item_update($forum, $grades=NULL) {
1659     global $CFG;
1660     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1661         require_once($CFG->libdir.'/gradelib.php');
1662     }
1664     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1666     if (!$forum->assessed or $forum->scale == 0) {
1667         $params['gradetype'] = GRADE_TYPE_NONE;
1669     } else if ($forum->scale > 0) {
1670         $params['gradetype'] = GRADE_TYPE_VALUE;
1671         $params['grademax']  = $forum->scale;
1672         $params['grademin']  = 0;
1674     } else if ($forum->scale < 0) {
1675         $params['gradetype'] = GRADE_TYPE_SCALE;
1676         $params['scaleid']   = -$forum->scale;
1677     }
1679     if ($grades  === 'reset') {
1680         $params['reset'] = true;
1681         $grades = NULL;
1682     }
1684     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1687 /**
1688  * Delete grade item for given forum
1689  *
1690  * @category grade
1691  * @param stdClass $forum Forum object
1692  * @return grade_item
1693  */
1694 function forum_grade_item_delete($forum) {
1695     global $CFG;
1696     require_once($CFG->libdir.'/gradelib.php');
1698     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1702 /**
1703  * This function returns if a scale is being used by one forum
1704  *
1705  * @global object
1706  * @param int $forumid
1707  * @param int $scaleid negative number
1708  * @return bool
1709  */
1710 function forum_scale_used ($forumid,$scaleid) {
1711     global $DB;
1712     $return = false;
1714     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1716     if (!empty($rec) && !empty($scaleid)) {
1717         $return = true;
1718     }
1720     return $return;
1723 /**
1724  * Checks if scale is being used by any instance of forum
1725  *
1726  * This is used to find out if scale used anywhere
1727  *
1728  * @global object
1729  * @param $scaleid int
1730  * @return boolean True if the scale is used by any forum
1731  */
1732 function forum_scale_used_anywhere($scaleid) {
1733     global $DB;
1734     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1735         return true;
1736     } else {
1737         return false;
1738     }
1741 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1743 /**
1744  * Gets a post with all info ready for forum_print_post
1745  * Most of these joins are just to get the forum id
1746  *
1747  * @global object
1748  * @global object
1749  * @param int $postid
1750  * @return mixed array of posts or false
1751  */
1752 function forum_get_post_full($postid) {
1753     global $CFG, $DB;
1755     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1756                              FROM {forum_posts} p
1757                                   JOIN {forum_discussions} d ON p.discussion = d.id
1758                                   LEFT JOIN {user} u ON p.userid = u.id
1759                             WHERE p.id = ?", array($postid));
1762 /**
1763  * Gets posts with all info ready for forum_print_post
1764  * We pass forumid in because we always know it so no need to make a
1765  * complicated join to find it out.
1766  *
1767  * @global object
1768  * @global object
1769  * @return mixed array of posts or false
1770  */
1771 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1772     global $CFG, $DB;
1774     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1775                               FROM {forum_posts} p
1776                          LEFT JOIN {user} u ON p.userid = u.id
1777                              WHERE p.discussion = ?
1778                                AND p.parent > 0 $sort", array($discussion));
1781 /**
1782  * Gets all posts in discussion including top parent.
1783  *
1784  * @global object
1785  * @global object
1786  * @global object
1787  * @param int $discussionid
1788  * @param string $sort
1789  * @param bool $tracking does user track the forum?
1790  * @return array of posts
1791  */
1792 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1793     global $CFG, $DB, $USER;
1795     $tr_sel  = "";
1796     $tr_join = "";
1797     $params = array();
1799     if ($tracking) {
1800         $now = time();
1801         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1802         $tr_sel  = ", fr.id AS postread";
1803         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1804         $params[] = $USER->id;
1805     }
1807     $params[] = $discussionid;
1808     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1809                                      FROM {forum_posts} p
1810                                           LEFT JOIN {user} u ON p.userid = u.id
1811                                           $tr_join
1812                                     WHERE p.discussion = ?
1813                                  ORDER BY $sort", $params)) {
1814         return array();
1815     }
1817     foreach ($posts as $pid=>$p) {
1818         if ($tracking) {
1819             if (forum_tp_is_post_old($p)) {
1820                  $posts[$pid]->postread = true;
1821             }
1822         }
1823         if (!$p->parent) {
1824             continue;
1825         }
1826         if (!isset($posts[$p->parent])) {
1827             continue; // parent does not exist??
1828         }
1829         if (!isset($posts[$p->parent]->children)) {
1830             $posts[$p->parent]->children = array();
1831         }
1832         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1833     }
1835     return $posts;
1838 /**
1839  * Gets posts with all info ready for forum_print_post
1840  * We pass forumid in because we always know it so no need to make a
1841  * complicated join to find it out.
1842  *
1843  * @global object
1844  * @global object
1845  * @param int $parent
1846  * @param int $forumid
1847  * @return array
1848  */
1849 function forum_get_child_posts($parent, $forumid) {
1850     global $CFG, $DB;
1852     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1853                               FROM {forum_posts} p
1854                          LEFT JOIN {user} u ON p.userid = u.id
1855                              WHERE p.parent = ?
1856                           ORDER BY p.created ASC", array($parent));
1859 /**
1860  * An array of forum objects that the user is allowed to read/search through.
1861  *
1862  * @global object
1863  * @global object
1864  * @global object
1865  * @param int $userid
1866  * @param int $courseid if 0, we look for forums throughout the whole site.
1867  * @return array of forum objects, or false if no matches
1868  *         Forum objects have the following attributes:
1869  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1870  *         viewhiddentimedposts
1871  */
1872 function forum_get_readable_forums($userid, $courseid=0) {
1874     global $CFG, $DB, $USER;
1875     require_once($CFG->dirroot.'/course/lib.php');
1877     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1878         print_error('notinstalled', 'forum');
1879     }
1881     if ($courseid) {
1882         $courses = $DB->get_records('course', array('id' => $courseid));
1883     } else {
1884         // If no course is specified, then the user can see SITE + his courses.
1885         $courses1 = $DB->get_records('course', array('id' => SITEID));
1886         $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
1887         $courses = array_merge($courses1, $courses2);
1888     }
1889     if (!$courses) {
1890         return array();
1891     }
1893     $readableforums = array();
1895     foreach ($courses as $course) {
1897         $modinfo = get_fast_modinfo($course);
1898         if (is_null($modinfo->groups)) {
1899             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1900         }
1902         if (empty($modinfo->instances['forum'])) {
1903             // hmm, no forums?
1904             continue;
1905         }
1907         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1909         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1910             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1911                 continue;
1912             }
1913             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1914             $forum = $courseforums[$forumid];
1915             $forum->context = $context;
1916             $forum->cm = $cm;
1918             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1919                 continue;
1920             }
1922          /// group access
1923             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1924                 if (is_null($modinfo->groups)) {
1925                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1926                 }
1927                 if (isset($modinfo->groups[$cm->groupingid])) {
1928                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1929                     $forum->onlygroups[] = -1;
1930                 } else {
1931                     $forum->onlygroups = array(-1);
1932                 }
1933             }
1935         /// hidden timed discussions
1936             $forum->viewhiddentimedposts = true;
1937             if (!empty($CFG->forum_enabletimedposts)) {
1938                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1939                     $forum->viewhiddentimedposts = false;
1940                 }
1941             }
1943         /// qanda access
1944             if ($forum->type == 'qanda'
1945                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1947                 // We need to check whether the user has posted in the qanda forum.
1948                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1949                                                     // the user is allowed to see in this forum.
1950                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1951                     foreach ($discussionspostedin as $d) {
1952                         $forum->onlydiscussions[] = $d->id;
1953                     }
1954                 }
1955             }
1957             $readableforums[$forum->id] = $forum;
1958         }
1960         unset($modinfo);
1962     } // End foreach $courses
1964     return $readableforums;
1967 /**
1968  * Returns a list of posts found using an array of search terms.
1969  *
1970  * @global object
1971  * @global object
1972  * @global object
1973  * @param array $searchterms array of search terms, e.g. word +word -word
1974  * @param int $courseid if 0, we search through the whole site
1975  * @param int $limitfrom
1976  * @param int $limitnum
1977  * @param int &$totalcount
1978  * @param string $extrasql
1979  * @return array|bool Array of posts found or false
1980  */
1981 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1982                             &$totalcount, $extrasql='') {
1983     global $CFG, $DB, $USER;
1984     require_once($CFG->libdir.'/searchlib.php');
1986     $forums = forum_get_readable_forums($USER->id, $courseid);
1988     if (count($forums) == 0) {
1989         $totalcount = 0;
1990         return false;
1991     }
1993     $now = round(time(), -2); // db friendly
1995     $fullaccess = array();
1996     $where = array();
1997     $params = array();
1999     foreach ($forums as $forumid => $forum) {
2000         $select = array();
2002         if (!$forum->viewhiddentimedposts) {
2003             $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
2004             $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
2005         }
2007         $cm = $forum->cm;
2008         $context = $forum->context;
2010         if ($forum->type == 'qanda'
2011             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
2012             if (!empty($forum->onlydiscussions)) {
2013                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
2014                 $params = array_merge($params, $discussionid_params);
2015                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
2016             } else {
2017                 $select[] = "p.parent = 0";
2018             }
2019         }
2021         if (!empty($forum->onlygroups)) {
2022             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
2023             $params = array_merge($params, $groupid_params);
2024             $select[] = "d.groupid $groupid_sql";
2025         }
2027         if ($select) {
2028             $selects = implode(" AND ", $select);
2029             $where[] = "(d.forum = :forum{$forumid} AND $selects)";
2030             $params['forum'.$forumid] = $forumid;
2031         } else {
2032             $fullaccess[] = $forumid;
2033         }
2034     }
2036     if ($fullaccess) {
2037         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
2038         $params = array_merge($params, $fullid_params);
2039         $where[] = "(d.forum $fullid_sql)";
2040     }
2042     $selectdiscussion = "(".implode(" OR ", $where).")";
2044     $messagesearch = '';
2045     $searchstring = '';
2047     // Need to concat these back together for parser to work.
2048     foreach($searchterms as $searchterm){
2049         if ($searchstring != '') {
2050             $searchstring .= ' ';
2051         }
2052         $searchstring .= $searchterm;
2053     }
2055     // We need to allow quoted strings for the search. The quotes *should* be stripped
2056     // by the parser, but this should be examined carefully for security implications.
2057     $searchstring = str_replace("\\\"","\"",$searchstring);
2058     $parser = new search_parser();
2059     $lexer = new search_lexer($parser);
2061     if ($lexer->parse($searchstring)) {
2062         $parsearray = $parser->get_parsed_array();
2063     // Experimental feature under 1.8! MDL-8830
2064     // Use alternative text searches if defined
2065     // This feature only works under mysql until properly implemented for other DBs
2066     // Requires manual creation of text index for forum_posts before enabling it:
2067     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2068     // Experimental feature under 1.8! MDL-8830
2069         if (!empty($CFG->forum_usetextsearches)) {
2070             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2071                                                  'p.userid', 'u.id', 'u.firstname',
2072                                                  'u.lastname', 'p.modified', 'd.forum');
2073         } else {
2074             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2075                                                  'p.userid', 'u.id', 'u.firstname',
2076                                                  'u.lastname', 'p.modified', 'd.forum');
2077         }
2078         $params = array_merge($params, $msparams);
2079     }
2081     $fromsql = "{forum_posts} p,
2082                   {forum_discussions} d,
2083                   {user} u";
2085     $selectsql = " $messagesearch
2086                AND p.discussion = d.id
2087                AND p.userid = u.id
2088                AND $selectdiscussion
2089                    $extrasql";
2091     $countsql = "SELECT COUNT(*)
2092                    FROM $fromsql
2093                   WHERE $selectsql";
2095     $searchsql = "SELECT p.*,
2096                          d.forum,
2097                          u.firstname,
2098                          u.lastname,
2099                          u.email,
2100                          u.picture,
2101                          u.imagealt
2102                     FROM $fromsql
2103                    WHERE $selectsql
2104                 ORDER BY p.modified DESC";
2106     $totalcount = $DB->count_records_sql($countsql, $params);
2108     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2111 /**
2112  * Returns a list of ratings for a particular post - sorted.
2113  *
2114  * TODO: Check if this function is actually used anywhere.
2115  * Up until the fix for MDL-27471 this function wasn't even returning.
2116  *
2117  * @param stdClass $context
2118  * @param int $postid
2119  * @param string $sort
2120  * @return array Array of ratings or false
2121  */
2122 function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
2123     $options = new stdClass;
2124     $options->context = $context;
2125     $options->component = 'mod_forum';
2126     $options->ratingarea = 'post';
2127     $options->itemid = $postid;
2128     $options->sort = "ORDER BY $sort";
2130     $rm = new rating_manager();
2131     return $rm->get_all_ratings_for_item($options);
2134 /**
2135  * Returns a list of all new posts that have not been mailed yet
2136  *
2137  * @param int $starttime posts created after this time
2138  * @param int $endtime posts created before this
2139  * @param int $now used for timed discussions only
2140  * @return array
2141  */
2142 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2143     global $CFG, $DB;
2145     $params = array($starttime, $endtime);
2146     if (!empty($CFG->forum_enabletimedposts)) {
2147         if (empty($now)) {
2148             $now = time();
2149         }
2150         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2151         $params[] = $now;
2152         $params[] = $now;
2153     } else {
2154         $timedsql = "";
2155     }
2157     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2158                               FROM {forum_posts} p
2159                                    JOIN {forum_discussions} d ON d.id = p.discussion
2160                              WHERE p.mailed = 0
2161                                    AND p.created >= ?
2162                                    AND (p.created < ? OR p.mailnow = 1)
2163                                    $timedsql
2164                           ORDER BY p.modified ASC", $params);
2167 /**
2168  * Marks posts before a certain time as being mailed already
2169  *
2170  * @global object
2171  * @global object
2172  * @param int $endtime
2173  * @param int $now Defaults to time()
2174  * @return bool
2175  */
2176 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2177     global $CFG, $DB;
2178     if (empty($now)) {
2179         $now = time();
2180     }
2182     if (empty($CFG->forum_enabletimedposts)) {
2183         return $DB->execute("UPDATE {forum_posts}
2184                                SET mailed = '1'
2185                              WHERE (created < ? OR mailnow = 1)
2186                                    AND mailed = 0", array($endtime));
2188     } else {
2189         return $DB->execute("UPDATE {forum_posts}
2190                                SET mailed = '1'
2191                              WHERE discussion NOT IN (SELECT d.id
2192                                                         FROM {forum_discussions} d
2193                                                        WHERE d.timestart > ?)
2194                                    AND (created < ? OR mailnow = 1)
2195                                    AND mailed = 0", array($now, $endtime));
2196     }
2199 /**
2200  * Get all the posts for a user in a forum suitable for forum_print_post
2201  *
2202  * @global object
2203  * @global object
2204  * @uses CONTEXT_MODULE
2205  * @return array
2206  */
2207 function forum_get_user_posts($forumid, $userid) {
2208     global $CFG, $DB;
2210     $timedsql = "";
2211     $params = array($forumid, $userid);
2213     if (!empty($CFG->forum_enabletimedposts)) {
2214         $cm = get_coursemodule_from_instance('forum', $forumid);
2215         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2216             $now = time();
2217             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2218             $params[] = $now;
2219             $params[] = $now;
2220         }
2221     }
2223     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2224                               FROM {forum} f
2225                                    JOIN {forum_discussions} d ON d.forum = f.id
2226                                    JOIN {forum_posts} p       ON p.discussion = d.id
2227                                    JOIN {user} u              ON u.id = p.userid
2228                              WHERE f.id = ?
2229                                    AND p.userid = ?
2230                                    $timedsql
2231                           ORDER BY p.modified ASC", $params);
2234 /**
2235  * Get all the discussions user participated in
2236  *
2237  * @global object
2238  * @global object
2239  * @uses CONTEXT_MODULE
2240  * @param int $forumid
2241  * @param int $userid
2242  * @return array Array or false
2243  */
2244 function forum_get_user_involved_discussions($forumid, $userid) {
2245     global $CFG, $DB;
2247     $timedsql = "";
2248     $params = array($forumid, $userid);
2249     if (!empty($CFG->forum_enabletimedposts)) {
2250         $cm = get_coursemodule_from_instance('forum', $forumid);
2251         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2252             $now = time();
2253             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2254             $params[] = $now;
2255             $params[] = $now;
2256         }
2257     }
2259     return $DB->get_records_sql("SELECT DISTINCT d.*
2260                               FROM {forum} f
2261                                    JOIN {forum_discussions} d ON d.forum = f.id
2262                                    JOIN {forum_posts} p       ON p.discussion = d.id
2263                              WHERE f.id = ?
2264                                    AND p.userid = ?
2265                                    $timedsql", $params);
2268 /**
2269  * Get all the posts for a user in a forum suitable for forum_print_post
2270  *
2271  * @global object
2272  * @global object
2273  * @param int $forumid
2274  * @param int $userid
2275  * @return array of counts or false
2276  */
2277 function forum_count_user_posts($forumid, $userid) {
2278     global $CFG, $DB;
2280     $timedsql = "";
2281     $params = array($forumid, $userid);
2282     if (!empty($CFG->forum_enabletimedposts)) {
2283         $cm = get_coursemodule_from_instance('forum', $forumid);
2284         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2285             $now = time();
2286             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2287             $params[] = $now;
2288             $params[] = $now;
2289         }
2290     }
2292     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2293                              FROM {forum} f
2294                                   JOIN {forum_discussions} d ON d.forum = f.id
2295                                   JOIN {forum_posts} p       ON p.discussion = d.id
2296                                   JOIN {user} u              ON u.id = p.userid
2297                             WHERE f.id = ?
2298                                   AND p.userid = ?
2299                                   $timedsql", $params);
2302 /**
2303  * Given a log entry, return the forum post details for it.
2304  *
2305  * @global object
2306  * @global object
2307  * @param object $log
2308  * @return array|null
2309  */
2310 function forum_get_post_from_log($log) {
2311     global $CFG, $DB;
2313     if ($log->action == "add post") {
2315         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2316                                            u.firstname, u.lastname, u.email, u.picture
2317                                  FROM {forum_discussions} d,
2318                                       {forum_posts} p,
2319                                       {forum} f,
2320                                       {user} u
2321                                 WHERE p.id = ?
2322                                   AND d.id = p.discussion
2323                                   AND p.userid = u.id
2324                                   AND u.deleted <> '1'
2325                                   AND f.id = d.forum", array($log->info));
2328     } else if ($log->action == "add discussion") {
2330         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2331                                            u.firstname, u.lastname, u.email, u.picture
2332                                  FROM {forum_discussions} d,
2333                                       {forum_posts} p,
2334                                       {forum} f,
2335                                       {user} u
2336                                 WHERE d.id = ?
2337                                   AND d.firstpost = p.id
2338                                   AND p.userid = u.id
2339                                   AND u.deleted <> '1'
2340                                   AND f.id = d.forum", array($log->info));
2341     }
2342     return NULL;
2345 /**
2346  * Given a discussion id, return the first post from the discussion
2347  *
2348  * @global object
2349  * @global object
2350  * @param int $dicsussionid
2351  * @return array
2352  */
2353 function forum_get_firstpost_from_discussion($discussionid) {
2354     global $CFG, $DB;
2356     return $DB->get_record_sql("SELECT p.*
2357                              FROM {forum_discussions} d,
2358                                   {forum_posts} p
2359                             WHERE d.id = ?
2360                               AND d.firstpost = p.id ", array($discussionid));
2363 /**
2364  * Returns an array of counts of replies to each discussion
2365  *
2366  * @global object
2367  * @global object
2368  * @param int $forumid
2369  * @param string $forumsort
2370  * @param int $limit
2371  * @param int $page
2372  * @param int $perpage
2373  * @return array
2374  */
2375 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2376     global $CFG, $DB;
2378     if ($limit > 0) {
2379         $limitfrom = 0;
2380         $limitnum  = $limit;
2381     } else if ($page != -1) {
2382         $limitfrom = $page*$perpage;
2383         $limitnum  = $perpage;
2384     } else {
2385         $limitfrom = 0;
2386         $limitnum  = 0;
2387     }
2389     if ($forumsort == "") {
2390         $orderby = "";
2391         $groupby = "";
2393     } else {
2394         $orderby = "ORDER BY $forumsort";
2395         $groupby = ", ".strtolower($forumsort);
2396         $groupby = str_replace('desc', '', $groupby);
2397         $groupby = str_replace('asc', '', $groupby);
2398     }
2400     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2401         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2402                   FROM {forum_posts} p
2403                        JOIN {forum_discussions} d ON p.discussion = d.id
2404                  WHERE p.parent > 0 AND d.forum = ?
2405               GROUP BY p.discussion";
2406         return $DB->get_records_sql($sql, array($forumid));
2408     } else {
2409         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2410                   FROM {forum_posts} p
2411                        JOIN {forum_discussions} d ON p.discussion = d.id
2412                  WHERE d.forum = ?
2413               GROUP BY p.discussion $groupby
2414               $orderby";
2415         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2416     }
2419 /**
2420  * @global object
2421  * @global object
2422  * @global object
2423  * @staticvar array $cache
2424  * @param object $forum
2425  * @param object $cm
2426  * @param object $course
2427  * @return mixed
2428  */
2429 function forum_count_discussions($forum, $cm, $course) {
2430     global $CFG, $DB, $USER;
2432     static $cache = array();
2434     $now = round(time(), -2); // db cache friendliness
2436     $params = array($course->id);
2438     if (!isset($cache[$course->id])) {
2439         if (!empty($CFG->forum_enabletimedposts)) {
2440             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2441             $params[] = $now;
2442             $params[] = $now;
2443         } else {
2444             $timedsql = "";
2445         }
2447         $sql = "SELECT f.id, COUNT(d.id) as dcount
2448                   FROM {forum} f
2449                        JOIN {forum_discussions} d ON d.forum = f.id
2450                  WHERE f.course = ?
2451                        $timedsql
2452               GROUP BY f.id";
2454         if ($counts = $DB->get_records_sql($sql, $params)) {
2455             foreach ($counts as $count) {
2456                 $counts[$count->id] = $count->dcount;
2457             }
2458             $cache[$course->id] = $counts;
2459         } else {
2460             $cache[$course->id] = array();
2461         }
2462     }
2464     if (empty($cache[$course->id][$forum->id])) {
2465         return 0;
2466     }
2468     $groupmode = groups_get_activity_groupmode($cm, $course);
2470     if ($groupmode != SEPARATEGROUPS) {
2471         return $cache[$course->id][$forum->id];
2472     }
2474     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2475         return $cache[$course->id][$forum->id];
2476     }
2478     require_once($CFG->dirroot.'/course/lib.php');
2480     $modinfo = get_fast_modinfo($course);
2481     if (is_null($modinfo->groups)) {
2482         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2483     }
2485     if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2486         $mygroups = $modinfo->groups[$cm->groupingid];
2487     } else {
2488         $mygroups = false; // Will be set below
2489     }
2491     // add all groups posts
2492     if (empty($mygroups)) {
2493         $mygroups = array(-1=>-1);
2494     } else {
2495         $mygroups[-1] = -1;
2496     }
2498     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2499     $params[] = $forum->id;
2501     if (!empty($CFG->forum_enabletimedposts)) {
2502         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2503         $params[] = $now;
2504         $params[] = $now;
2505     } else {
2506         $timedsql = "";
2507     }
2509     $sql = "SELECT COUNT(d.id)
2510               FROM {forum_discussions} d
2511              WHERE d.groupid $mygroups_sql AND d.forum = ?
2512                    $timedsql";
2514     return $DB->get_field_sql($sql, $params);
2517 /**
2518  * How many posts by other users are unrated by a given user in the given discussion?
2519  *
2520  * TODO: Is this function still used anywhere?
2521  *
2522  * @param int $discussionid
2523  * @param int $userid
2524  * @return mixed
2525  */
2526 function forum_count_unrated_posts($discussionid, $userid) {
2527     global $CFG, $DB;
2529     $sql = "SELECT COUNT(*) as num
2530               FROM {forum_posts}
2531              WHERE parent > 0
2532                AND discussion = :discussionid
2533                AND userid <> :userid";
2534     $params = array('discussionid' => $discussionid, 'userid' => $userid);
2535     $posts = $DB->get_record_sql($sql, $params);
2536     if ($posts) {
2537         $sql = "SELECT count(*) as num
2538                   FROM {forum_posts} p,
2539                        {rating} r
2540                  WHERE p.discussion = :discussionid AND
2541                        p.id = r.itemid AND
2542                        r.userid = userid AND
2543                        r.component = 'mod_forum' AND
2544                        r.ratingarea = 'post'";
2545         $rated = $DB->get_record_sql($sql, $params);
2546         if ($rated) {
2547             if ($posts->num > $rated->num) {
2548                 return $posts->num - $rated->num;
2549             } else {
2550                 return 0;    // Just in case there was a counting error
2551             }
2552         } else {
2553             return $posts->num;
2554         }
2555     } else {
2556         return 0;
2557     }
2560 /**
2561  * Get all discussions in a forum
2562  *
2563  * @global object
2564  * @global object
2565  * @global object
2566  * @uses CONTEXT_MODULE
2567  * @uses VISIBLEGROUPS
2568  * @param object $cm
2569  * @param string $forumsort
2570  * @param bool $fullpost
2571  * @param int $unused
2572  * @param int $limit
2573  * @param bool $userlastmodified
2574  * @param int $page
2575  * @param int $perpage
2576  * @return array
2577  */
2578 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2579     global $CFG, $DB, $USER;
2581     $timelimit = '';
2583     $now = round(time(), -2);
2584     $params = array($cm->instance);
2586     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2588     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2589         return array();
2590     }
2592     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2594         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2595             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2596             $params[] = $now;
2597             $params[] = $now;
2598             if (isloggedin()) {
2599                 $timelimit .= " OR d.userid = ?";
2600                 $params[] = $USER->id;
2601             }
2602             $timelimit .= ")";
2603         }
2604     }
2606     if ($limit > 0) {
2607         $limitfrom = 0;
2608         $limitnum  = $limit;
2609     } else if ($page != -1) {
2610         $limitfrom = $page*$perpage;
2611         $limitnum  = $perpage;
2612     } else {
2613         $limitfrom = 0;
2614         $limitnum  = 0;
2615     }
2617     $groupmode    = groups_get_activity_groupmode($cm);
2618     $currentgroup = groups_get_activity_group($cm);
2620     if ($groupmode) {
2621         if (empty($modcontext)) {
2622             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2623         }
2625         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2626             if ($currentgroup) {
2627                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2628                 $params[] = $currentgroup;
2629             } else {
2630                 $groupselect = "";
2631             }
2633         } else {
2634             //seprate groups without access all
2635             if ($currentgroup) {
2636                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2637                 $params[] = $currentgroup;
2638             } else {
2639                 $groupselect = "AND d.groupid = -1";
2640             }
2641         }
2642     } else {
2643         $groupselect = "";
2644     }
2647     if (empty($forumsort)) {
2648         $forumsort = "d.timemodified DESC";
2649     }
2650     if (empty($fullpost)) {
2651         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2652     } else {
2653         $postdata = "p.*";
2654     }
2656     if (empty($userlastmodified)) {  // We don't need to know this
2657         $umfields = "";
2658         $umtable  = "";
2659     } else {
2660         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2661         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2662     }
2664     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2665                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2666               FROM {forum_discussions} d
2667                    JOIN {forum_posts} p ON p.discussion = d.id
2668                    JOIN {user} u ON p.userid = u.id
2669                    $umtable
2670              WHERE d.forum = ? AND p.parent = 0
2671                    $timelimit $groupselect
2672           ORDER BY $forumsort";
2673     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2676 /**
2677  *
2678  * @global object
2679  * @global object
2680  * @global object
2681  * @uses CONTEXT_MODULE
2682  * @uses VISIBLEGROUPS
2683  * @param object $cm
2684  * @return array
2685  */
2686 function forum_get_discussions_unread($cm) {
2687     global $CFG, $DB, $USER;
2689     $now = round(time(), -2);
2690     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2692     $params = array();
2693     $groupmode    = groups_get_activity_groupmode($cm);
2694     $currentgroup = groups_get_activity_group($cm);
2696     if ($groupmode) {
2697         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2699         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2700             if ($currentgroup) {
2701                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2702                 $params['currentgroup'] = $currentgroup;
2703             } else {
2704                 $groupselect = "";
2705             }
2707         } else {
2708             //separate groups without access all
2709             if ($currentgroup) {
2710                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2711                 $params['currentgroup'] = $currentgroup;
2712             } else {
2713                 $groupselect = "AND d.groupid = -1";
2714             }
2715         }
2716     } else {
2717         $groupselect = "";
2718     }
2720     if (!empty($CFG->forum_enabletimedposts)) {
2721         $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2722         $params['now1'] = $now;
2723         $params['now2'] = $now;
2724     } else {
2725         $timedsql = "";
2726     }
2728     $sql = "SELECT d.id, COUNT(p.id) AS unread
2729               FROM {forum_discussions} d
2730                    JOIN {forum_posts} p     ON p.discussion = d.id
2731                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2732              WHERE d.forum = {$cm->instance}
2733                    AND p.modified >= :cutoffdate AND r.id is NULL
2734                    $groupselect
2735                    $timedsql
2736           GROUP BY d.id";
2737     $params['cutoffdate'] = $cutoffdate;
2739     if ($unreads = $DB->get_records_sql($sql, $params)) {
2740         foreach ($unreads as $unread) {
2741             $unreads[$unread->id] = $unread->unread;
2742         }
2743         return $unreads;
2744     } else {
2745         return array();
2746     }
2749 /**
2750  * @global object
2751  * @global object
2752  * @global object
2753  * @uses CONEXT_MODULE
2754  * @uses VISIBLEGROUPS
2755  * @param object $cm
2756  * @return array
2757  */
2758 function forum_get_discussions_count($cm) {
2759     global $CFG, $DB, $USER;
2761     $now = round(time(), -2);
2762     $params = array($cm->instance);
2763     $groupmode    = groups_get_activity_groupmode($cm);
2764     $currentgroup = groups_get_activity_group($cm);
2766     if ($groupmode) {
2767         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2769         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2770             if ($currentgroup) {
2771                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2772                 $params[] = $currentgroup;
2773             } else {
2774                 $groupselect = "";
2775             }
2777         } else {
2778             //seprate groups without access all
2779             if ($currentgroup) {
2780                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2781                 $params[] = $currentgroup;
2782             } else {
2783                 $groupselect = "AND d.groupid = -1";
2784             }
2785         }
2786     } else {
2787         $groupselect = "";
2788     }
2790     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2792     $timelimit = "";
2794     if (!empty($CFG->forum_enabletimedposts)) {
2796         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2798         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2799             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2800             $params[] = $now;
2801             $params[] = $now;
2802             if (isloggedin()) {
2803                 $timelimit .= " OR d.userid = ?";
2804                 $params[] = $USER->id;
2805             }
2806             $timelimit .= ")";
2807         }
2808     }
2810     $sql = "SELECT COUNT(d.id)
2811               FROM {forum_discussions} d
2812                    JOIN {forum_posts} p ON p.discussion = d.id
2813              WHERE d.forum = ? AND p.parent = 0
2814                    $groupselect $timelimit";
2816     return $DB->get_field_sql($sql, $params);
2820 /**
2821  * Get all discussions started by a particular user in a course (or group)
2822  * This function no longer used ...
2823  *
2824  * @todo Remove this function if no longer used
2825  * @global object
2826  * @global object
2827  * @param int $courseid
2828  * @param int $userid
2829  * @param int $groupid
2830  * @return array
2831  */
2832 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2833     global $CFG, $DB;
2834     $params = array($courseid, $userid);
2835     if ($groupid) {
2836         $groupselect = " AND d.groupid = ? ";
2837         $params[] = $groupid;
2838     } else  {
2839         $groupselect = "";
2840     }
2842     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2843                                    f.type as forumtype, f.name as forumname, f.id as forumid
2844                               FROM {forum_discussions} d,
2845                                    {forum_posts} p,
2846                                    {user} u,
2847                                    {forum} f
2848                              WHERE d.course = ?
2849                                AND p.discussion = d.id
2850                                AND p.parent = 0
2851                                AND p.userid = u.id
2852                                AND u.id = ?
2853                                AND d.forum = f.id $groupselect
2854                           ORDER BY p.created DESC", $params);
2857 /**
2858  * Get the list of potential subscribers to a forum.
2859  *
2860  * @param object $forumcontext the forum context.
2861  * @param integer $groupid the id of a group, or 0 for all groups.
2862  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2863  * @param string $sort sort order. As for get_users_by_capability.
2864  * @return array list of users.
2865  */
2866 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2867     global $DB;
2869     // only active enrolled users or everybody on the frontpage
2870     list($esql, $params) = get_enrolled_sql($forumcontext, '', $groupid, true);
2872     $sql = "SELECT $fields
2873               FROM {user} u
2874               JOIN ($esql) je ON je.id = u.id";
2875     if ($sort) {
2876         $sql = "$sql ORDER BY $sort";
2877     } else {
2878         $sql = "$sql ORDER BY u.lastname ASC, u.firstname ASC";
2879     }
2881     return $DB->get_records_sql($sql, $params);
2884 /**
2885  * Returns list of user objects that are subscribed to this forum
2886  *
2887  * @global object
2888  * @global object
2889  * @param object $course the course
2890  * @param forum $forum the forum
2891  * @param integer $groupid group id, or 0 for all.
2892  * @param object $context the forum context, to save re-fetching it where possible.
2893  * @param string $fields requested user fields (with "u." table prefix)
2894  * @return array list of users.
2895  */
2896 function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
2897     global $CFG, $DB;
2899     if (empty($fields)) {
2900         $fields ="u.id,
2901                   u.username,
2902                   u.firstname,
2903                   u.lastname,
2904                   u.maildisplay,
2905                   u.mailformat,
2906                   u.maildigest,
2907                   u.imagealt,
2908                   u.email,
2909                   u.emailstop,
2910                   u.city,
2911                   u.country,
2912                   u.lastaccess,
2913                   u.lastlogin,
2914                   u.picture,
2915                   u.timezone,
2916                   u.theme,
2917                   u.lang,
2918                   u.trackforums,
2919                   u.mnethostid";
2920     }
2922     if (empty($context)) {
2923         $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2924         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2925     }
2927     if (forum_is_forcesubscribed($forum)) {
2928         $results = forum_get_potential_subscribers($context, $groupid, $fields, "u.email ASC");
2930     } else {
2931         // only active enrolled users or everybody on the frontpage
2932         list($esql, $params) = get_enrolled_sql($context, '', $groupid, true);
2933         $params['forumid'] = $forum->id;
2934         $results = $DB->get_records_sql("SELECT $fields
2935                                            FROM {user} u
2936                                            JOIN ($esql) je ON je.id = u.id
2937                                            JOIN {forum_subscriptions} s ON s.userid = u.id
2938                                           WHERE s.forum = :forumid
2939                                        ORDER BY u.email ASC", $params);
2940     }
2942     // Guest user should never be subscribed to a forum.
2943     unset($results[$CFG->siteguest]);
2945     return $results;
2950 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2953 /**
2954  * @global object
2955  * @global object
2956  * @param int $courseid
2957  * @param string $type
2958  */
2959 function forum_get_course_forum($courseid, $type) {
2960 // How to set up special 1-per-course forums
2961     global $CFG, $DB, $OUTPUT;
2963     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2964         // There should always only be ONE, but with the right combination of
2965         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2966         foreach ($forums as $forum) {
2967             return $forum;   // ie the first one
2968         }
2969     }
2971     // Doesn't exist, so create one now.
2972     $forum = new stdClass();
2973     $forum->course = $courseid;
2974     $forum->type = "$type";
2975     switch ($forum->type) {
2976         case "news":
2977             $forum->name  = get_string("namenews", "forum");
2978             $forum->intro = get_string("intronews", "forum");
2979             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2980             $forum->assessed = 0;
2981             if ($courseid == SITEID) {
2982                 $forum->name  = get_string("sitenews");
2983                 $forum->forcesubscribe = 0;
2984             }
2985             break;
2986         case "social":
2987             $forum->name  = get_string("namesocial", "forum");
2988             $forum->intro = get_string("introsocial", "forum");
2989             $forum->assessed = 0;
2990             $forum->forcesubscribe = 0;
2991             break;
2992         case "blog":
2993             $forum->name = get_string('blogforum', 'forum');
2994             $forum->intro = get_string('introblog', 'forum');
2995             $forum->assessed = 0;
2996             $forum->forcesubscribe = 0;
2997             break;
2998         default:
2999             echo $OUTPUT->notification("That forum type doesn't exist!");
3000             return false;
3001             break;
3002     }
3004     $forum->timemodified = time();
3005     $forum->id = $DB->insert_record("forum", $forum);
3007     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
3008         echo $OUTPUT->notification("Could not find forum module!!");
3009         return false;
3010     }
3011     $mod = new stdClass();
3012     $mod->course = $courseid;
3013     $mod->module = $module->id;
3014     $mod->instance = $forum->id;
3015     $mod->section = 0;
3016     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
3017         echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
3018         return false;
3019     }
3020     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
3021         echo $OUTPUT->notification("Could not add the new course module to that section");
3022         return false;
3023     }
3024     $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule));
3026     include_once("$CFG->dirroot/course/lib.php");
3027     rebuild_course_cache($courseid);
3029     return $DB->get_record("forum", array("id" => "$forum->id"));
3033 /**
3034  * Given the data about a posting, builds up the HTML to display it and
3035  * returns the HTML in a string.  This is designed for sending via HTML email.
3036  *
3037  * @global object
3038  * @param object $course
3039  * @param object $cm
3040  * @param object $forum
3041  * @param object $discussion
3042  * @param object $post
3043  * @param object $userform
3044  * @param object $userto
3045  * @param bool $ownpost
3046  * @param bool $reply
3047  * @param bool $link
3048  * @param bool $rate
3049  * @param string $footer
3050  * @return string
3051  */
3052 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
3053                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
3055     global $CFG, $OUTPUT;
3057     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3059     if (!isset($userto->viewfullnames[$forum->id])) {
3060         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3061     } else {
3062         $viewfullnames = $userto->viewfullnames[$forum->id];
3063     }
3065     // add absolute file links
3066     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3068     // format the post body
3069     $options = new stdClass();
3070     $options->para = true;
3071     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3073     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3075     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3076     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3077     $output .= '</td>';
3079     if ($post->parent) {
3080         $output .= '<td class="topic">';
3081     } else {
3082         $output .= '<td class="topic starter">';
3083     }
3084     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3086     $fullname = fullname($userfrom, $viewfullnames);
3087     $by = new stdClass();
3088     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3089     $by->date = userdate($post->modified, '', $userto->timezone);
3090     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3092     $output .= '</td></tr>';
3094     $output .= '<tr><td class="left side" valign="top">';
3096     if (isset($userfrom->groups)) {
3097         $groups = $userfrom->groups[$forum->id];
3098     } else {
3099         $groups = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3100     }
3102     if ($groups) {
3103         $output .= print_group_picture($groups, $course->id, false, true, true);
3104     } else {
3105         $output .= '&nbsp;';
3106     }
3108     $output .= '</td><td class="content">';
3110     $attachments = forum_print_attachments($post, $cm, 'html');
3111     if ($attachments !== '') {
3112         $output .= '<div class="attachments">';
3113         $output .= $attachments;
3114         $output .= '</div>';
3115     }
3117     $output .= $formattedtext;
3119 // Commands
3120     $commands = array();
3122     if ($post->parent) {
3123         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3124                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3125     }
3127     if ($reply) {
3128         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3129                       get_string('reply', 'forum').'</a>';
3130     }
3132     $output .= '<div class="commands">';
3133     $output .= implode(' | ', $commands);
3134     $output .= '</div>';
3136 // Context link to post if required
3137     if ($link) {
3138         $output .= '<div class="link">';
3139         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3140                      get_string('postincontext', 'forum').'</a>';
3141         $output .= '</div>';
3142     }
3144     if ($footer) {
3145         $output .= '<div class="footer">'.$footer.'</div>';
3146     }
3147     $output .= '</td></tr></table>'."\n\n";
3149     return $output;
3152 /**
3153  * Print a forum post
3154  *
3155  * @global object
3156  * @global object
3157  * @uses FORUM_MODE_THREADED
3158  * @uses PORTFOLIO_FORMAT_PLAINHTML
3159  * @uses PORTFOLIO_FORMAT_FILE
3160  * @uses PORTFOLIO_FORMAT_RICHHTML
3161  * @uses PORTFOLIO_ADD_TEXT_LINK
3162  * @uses CONTEXT_MODULE
3163  * @param object $post The post to print.
3164  * @param object $discussion
3165  * @param object $forum
3166  * @param object $cm
3167  * @param object $course
3168  * @param boolean $ownpost Whether this post belongs to the current user.
3169  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3170  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3171  * @param string $footer Extra stuff to print after the message.
3172  * @param string $highlight Space-separated list of terms to highlight.
3173  * @param int $post_read true, false or -99. If we already know whether this user
3174  *          has read this post, pass that in, otherwise, pass in -99, and this
3175  *          function will work it out.
3176  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3177  *          the current user can't see this post, if this argument is true
3178  *          (the default) then print a dummy 'you can't see this post' post.
3179  *          If false, don't output anything at all.
3180  * @param bool|null $istracked
3181  * @return void
3182  */
3183 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3184                           $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3185     global $USER, $CFG, $OUTPUT;
3187     require_once($CFG->libdir . '/filelib.php');
3189     // String cache
3190     static $str;
3192     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3194     $post->course = $course->id;
3195     $post->forum  = $forum->id;
3196     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3198     // caching
3199     if (!isset($cm->cache)) {
3200         $cm->cache = new stdClass;
3201     }
3203     if (!isset($cm->cache->caps)) {
3204         $cm->cache->caps = array();
3205         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3206         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3207         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3208         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3209         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3210         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3211         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3212         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3213         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3214     }
3216     if (!isset($cm->uservisible)) {
3217         $cm->uservisible = coursemodule_visible_for_user($cm);
3218     }
3220     if ($istracked && is_null($postisread)) {
3221         $postisread = forum_tp_is_post_read($USER->id, $post);
3222     }
3224     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3225         $output = '';
3226         if (!$dummyifcantsee) {
3227             if ($return) {
3228                 return $output;
3229             }
3230             echo $output;
3231             return;
3232         }
3233         $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3234         $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'));
3235         $output .= html_writer::start_tag('div', array('class'=>'row header'));
3236         $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3237         if ($post->parent) {
3238             $output .= html_writer::start_tag('div', array('class'=>'topic'));
3239         } else {
3240             $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3241         }
3242         $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class'=>'subject')); // Subject
3243         $output .= html_writer::tag('div', get_string('forumauthorhidden','forum'), array('class'=>'author')); // author
3244         $output .= html_writer::end_tag('div');
3245         $output .= html_writer::end_tag('div'); // row
3246         $output .= html_writer::start_tag('div', array('class'=>'row'));
3247         $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3248         $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3249         $output .= html_writer::end_tag('div'); // row
3250         $output .= html_writer::end_tag('div'); // forumpost
3252         if ($return) {
3253             return $output;
3254         }
3255         echo $output;
3256         return;
3257     }
3259     if (empty($str)) {
3260         $str = new stdClass;
3261         $str->edit         = get_string('edit', 'forum');
3262         $str->delete       = get_string('delete', 'forum');
3263         $str->reply        = get_string('reply', 'forum');
3264         $str->parent       = get_string('parent', 'forum');
3265         $str->pruneheading = get_string('pruneheading', 'forum');
3266         $str->prune        = get_string('prune', 'forum');
3267         $str->displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3268         $str->markread     = get_string('markread', 'forum');
3269         $str->markunread   = get_string('markunread', 'forum');
3270     }
3272     $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3274     // Build an object that represents the posting user
3275     $postuser = new stdClass;
3276     $postuser->id        = $post->userid;
3277     $postuser->firstname = $post->firstname;
3278     $postuser->lastname  = $post->lastname;
3279     $postuser->imagealt  = $post->imagealt;
3280     $postuser->picture   = $post->picture;
3281     $postuser->email     = $post->email;
3282     // Some handy things for later on
3283     $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3284     $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3286     // Prepare the groups the posting user belongs to
3287     if (isset($cm->cache->usersgroups)) {
3288         $groups = array();
3289         if (isset($cm->cache->usersgroups[$post->userid])) {
3290             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3291                 $groups[$gid] = $cm->cache->groups[$gid];
3292             }
3293         }
3294     } else {
3295         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3296     }
3298     // Prepare the attachements for the post, files then images
3299     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3301     // Determine if we need to shorten this post
3302     $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3305     // Prepare an array of commands
3306     $commands = array();
3308     // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3309     // Don't display the mark read / unread controls in this case.
3310     if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3311         $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3312         $text = $str->markunread;
3313         if (!$postisread) {
3314             $url->param('mark', 'read');
3315             $text = $str->markread;
3316         }
3317         if ($str->displaymode == FORUM_MODE_THREADED) {
3318             $url->param('parent', $post->parent);
3319         } else {
3320             $url->set_anchor('p'.$post->id);
3321         }
3322         $commands[] = array('url'=>$url, 'text'=>$text);
3323     }
3325     // Zoom in to the parent specifically
3326     if ($post->parent) {
3327         $url = new moodle_url($discussionlink);
3328         if ($str->displaymode == FORUM_MODE_THREADED) {
3329             $url->param('parent', $post->parent);
3330         } else {
3331             $url->set_anchor('p'.$post->parent);
3332         }
3333         $commands[] = array('url'=>$url, 'text'=>$str->parent);
3334     }
3336     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3337     $age = time() - $post->created;
3338     if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3339         $age = 0;
3340     }
3341     if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3342         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3343     }
3345     if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3346         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3347     }
3349     if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3350         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3351     }
3353     if ($reply) {
3354         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('reply'=>$post->id)), 'text'=>$str->reply);
3355     }
3357     if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
3358         $p = array('postid' => $post->id);
3359         require_once($CFG->libdir.'/portfoliolib.php');
3360         $button = new portfolio_add_button();
3361         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php');
3362         if (empty($attachments)) {
3363             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3364         } else {
3365             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3366         }
3368         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3369         if (!empty($porfoliohtml)) {
3370             $commands[] = $porfoliohtml;
3371         }
3372     }
3373     // Finished building commands
3376     // Begin output
3378     $output  = '';
3380     if ($istracked) {
3381         if ($postisread) {
3382             $forumpostclass = ' read';
3383         } else {
3384             $forumpostclass = ' unread';
3385             $output .= html_writer::tag('a', '', array('name'=>'unread'));
3386         }
3387     } else {
3388         // ignore trackign status if not tracked or tracked param missing
3389         $forumpostclass = '';
3390     }
3392     $topicclass = '';
3393     if (empty($post->parent)) {
3394         $topicclass = ' firstpost starter';
3395     }
3397     $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3398     $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass));
3399     $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3400     $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3401     $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3402     $output .= html_writer::end_tag('div');
3405     $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3407     $postsubject = $post->subject;
3408     if (empty($post->subjectnoformat)) {
3409         $postsubject = format_string($postsubject);
3410     }
3411     $output .= html_writer::tag('div', $postsubject, array('class'=>'subject'));
3413     $by = new stdClass();
3414     $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3415     $by->date = userdate($post->modified);
3416     $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author'));
3418     $output .= html_writer::end_tag('div'); //topic
3419     $output .= html_writer::end_tag('div'); //row
3421     $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3422     $output .= html_writer::start_tag('div', array('class'=>'left'));
3424     $groupoutput = '';
3425     if ($groups) {
3426         $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3427     }
3428     if (empty($groupoutput)) {
3429         $groupoutput = '&nbsp;';
3430     }
3431     $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3433     $output .= html_writer::end_tag('div'); //left side
3434     $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3435     $output .= html_writer::start_tag('div', array('class'=>'content'));
3436     if (!empty($attachments)) {
3437         $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3438     }
3440     $options = new stdClass;
3441     $options->para    = false;
3442     $options->trusted = $post->messagetrust;
3443     $options->context = $modcontext;
3444     if ($shortenpost) {
3445         // Prepare shortened version
3446         $postclass    = 'shortenedpost';
3447         $postcontent  = format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3448         $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
3449         $postcontent .= html_writer::tag('span', '('.get_string('numwords', 'moodle', count_words(strip_tags($post->message))).')...', array('class'=>'post-word-count'));
3450     } else {
3451         // Prepare whole post
3452         $postclass    = 'fullpost';
3453         $postcontent  = format_text($post->message, $post->messageformat, $options, $course->id);
3454         if (!empty($highlight)) {
3455             $postcontent = highlight($highlight, $postcontent);
3456         }
3457         $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
3458     }
3459     // Output the post content
3460     $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
3461     $output .= html_writer::end_tag('div'); // Content
3462     $output .= html_writer::end_tag('div'); // Content mask
3463     $output .= html_writer::end_tag('div'); // Row
3465     $output .= html_writer::start_tag('div', array('class'=>'row side'));
3466     $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
3467     $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
3469     // Output ratings
3470     if (!empty($post->rating)) {
3471         $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
3472     }
3474     // Output the commands
3475     $commandhtml = array();
3476     foreach ($commands as $command) {
3477         if (is_array($command)) {
3478             $commandhtml[] = html_writer::link($command['url'], $command['text']);
3479         } else {
3480             $commandhtml[] = $command;
3481         }
3482     }
3483     $output .= html_writer::tag('div', implode(' | ', $commandhtml), array('class'=>'commands'));
3485     // Output link to post if required
3486     if ($link) {
3487         if ($post->replies == 1) {
3488             $replystring = get_string('repliesone', 'forum', $post->replies);
3489         } else {
3490             $replystring = get_string('repliesmany', 'forum', $post->replies);
3491         }
<