MDL-29534 Forum: if forum_replytouser is not set then send forum email using noreplya...
[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             $options = array('subdirs'=>true); // Use the same options as intro field!
104             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $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         $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
197         $post->subject       = $forum->name;
198         $post->message       = $forum->intro;
199         $post->messageformat = $forum->introformat;
200         $post->messagetrust  = trusttext_trusted($modcontext);
201         $post->modified      = $forum->timemodified;
202         $post->userid        = $USER->id;    // MDL-18599, so that current teacher can take ownership of activities.
204         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
205             // Ugly hack - we need to copy the files somehow.
206             $options = array('subdirs'=>true); // Use the same options as intro field!
207             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
208         }
210         $DB->update_record('forum_posts', $post);
211         $discussion->name = $forum->name;
212         $DB->update_record('forum_discussions', $discussion);
213     }
215     $DB->update_record('forum', $forum);
217     forum_grade_item_update($forum);
219     return true;
223 /**
224  * Given an ID of an instance of this module,
225  * this function will permanently delete the instance
226  * and any data that depends on it.
227  *
228  * @global object
229  * @param int $id forum instance id
230  * @return bool success
231  */
232 function forum_delete_instance($id) {
233     global $DB;
235     if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
236         return false;
237     }
238     if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
239         return false;
240     }
241     if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
242         return false;
243     }
245     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
247     // now get rid of all files
248     $fs = get_file_storage();
249     $fs->delete_area_files($context->id);
251     $result = true;
253     if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
254         foreach ($discussions as $discussion) {
255             if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
256                 $result = false;
257             }
258         }
259     }
261     if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
262         $result = false;
263     }
265     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
267     if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
268         $result = false;
269     }
271     forum_grade_item_delete($forum);
273     return $result;
277 /**
278  * Indicates API features that the forum supports.
279  *
280  * @uses FEATURE_GROUPS
281  * @uses FEATURE_GROUPINGS
282  * @uses FEATURE_GROUPMEMBERSONLY
283  * @uses FEATURE_MOD_INTRO
284  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
285  * @uses FEATURE_COMPLETION_HAS_RULES
286  * @uses FEATURE_GRADE_HAS_GRADE
287  * @uses FEATURE_GRADE_OUTCOMES
288  * @param string $feature
289  * @return mixed True if yes (some features may use other values)
290  */
291 function forum_supports($feature) {
292     switch($feature) {
293         case FEATURE_GROUPS:                  return true;
294         case FEATURE_GROUPINGS:               return true;
295         case FEATURE_GROUPMEMBERSONLY:        return true;
296         case FEATURE_MOD_INTRO:               return true;
297         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
298         case FEATURE_COMPLETION_HAS_RULES:    return true;
299         case FEATURE_GRADE_HAS_GRADE:         return true;
300         case FEATURE_GRADE_OUTCOMES:          return true;
301         case FEATURE_RATE:                    return true;
302         case FEATURE_BACKUP_MOODLE2:          return true;
303         case FEATURE_SHOW_DESCRIPTION:        return true;
305         default: return null;
306     }
310 /**
311  * Obtains the automatic completion state for this forum based on any conditions
312  * in forum settings.
313  *
314  * @global object
315  * @global object
316  * @param object $course Course
317  * @param object $cm Course-module
318  * @param int $userid User ID
319  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
320  * @return bool True if completed, false if not. (If no conditions, then return
321  *   value depends on comparison type)
322  */
323 function forum_get_completion_state($course,$cm,$userid,$type) {
324     global $CFG,$DB;
326     // Get forum details
327     if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
328         throw new Exception("Can't find forum {$cm->instance}");
329     }
331     $result=$type; // Default return value
333     $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
334     $postcountsql="
335 SELECT
336     COUNT(1)
337 FROM
338     {forum_posts} fp
339     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
340 WHERE
341     fp.userid=:userid AND fd.forum=:forumid";
343     if ($forum->completiondiscussions) {
344         $value = $forum->completiondiscussions <=
345                  $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
346         if ($type == COMPLETION_AND) {
347             $result = $result && $value;
348         } else {
349             $result = $result || $value;
350         }
351     }
352     if ($forum->completionreplies) {
353         $value = $forum->completionreplies <=
354                  $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
355         if ($type==COMPLETION_AND) {
356             $result = $result && $value;
357         } else {
358             $result = $result || $value;
359         }
360     }
361     if ($forum->completionposts) {
362         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
363         if ($type == COMPLETION_AND) {
364             $result = $result && $value;
365         } else {
366             $result = $result || $value;
367         }
368     }
370     return $result;
373 /**
374  * Create a message-id string to use in the custom headers of forum notification emails
375  *
376  * message-id is used by email clients to identify emails and to nest conversations
377  *
378  * @param int $postid The ID of the forum post we are notifying the user about
379  * @param int $usertoid The ID of the user being notified
380  * @param string $hostname The server's hostname
381  * @return string A unique message-id
382  */
383 function forum_get_email_message_id($postid, $usertoid, $hostname) {
384     return '<'.hash('sha256',$postid.'to'.$usertoid).'@'.$hostname.'>';
387 /**
388  * Removes properties from user record that are not necessary
389  * for sending post notifications.
390  * @param stdClass $user
391  * @return void, $user parameter is modified
392  */
393 function forum_cron_minimise_user_record(stdClass $user) {
395     // We store large amount of users in one huge array,
396     // make sure we do not store info there we do not actually need
397     // in mail generation code or messaging.
399     unset($user->institution);
400     unset($user->department);
401     unset($user->address);
402     unset($user->city);
403     unset($user->url);
404     unset($user->currentlogin);
405     unset($user->description);
406     unset($user->descriptionformat);
409 /**
410  * Function to be run periodically according to the moodle cron
411  * Finds all posts that have yet to be mailed out, and mails them
412  * out to all subscribers
413  *
414  * @global object
415  * @global object
416  * @global object
417  * @uses CONTEXT_MODULE
418  * @uses CONTEXT_COURSE
419  * @uses SITEID
420  * @uses FORMAT_PLAIN
421  * @return void
422  */
423 function forum_cron() {
424     global $CFG, $USER, $DB;
426     $site = get_site();
428     // All users that are subscribed to any post that needs sending,
429     // please increase $CFG->extramemorylimit on large sites that
430     // send notifications to a large number of users.
431     $users = array();
432     $userscount = 0; // Cached user counter - count($users) in PHP is horribly slow!!!
434     // status arrays
435     $mailcount  = array();
436     $errorcount = array();
438     // caches
439     $discussions     = array();
440     $forums          = array();
441     $courses         = array();
442     $coursemodules   = array();
443     $subscribedusers = array();
446     // Posts older than 2 days will not be mailed.  This is to avoid the problem where
447     // cron has not been running for a long time, and then suddenly people are flooded
448     // with mail from the past few weeks or months
449     $timenow   = time();
450     $endtime   = $timenow - $CFG->maxeditingtime;
451     $starttime = $endtime - 48 * 3600;   // Two days earlier
453     if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
454         // Mark them all now as being mailed.  It's unlikely but possible there
455         // might be an error later so that a post is NOT actually mailed out,
456         // but since mail isn't crucial, we can accept this risk.  Doing it now
457         // prevents the risk of duplicated mails, which is a worse problem.
459         if (!forum_mark_old_posts_as_mailed($endtime)) {
460             mtrace('Errors occurred while trying to mark some posts as being mailed.');
461             return false;  // Don't continue trying to mail them, in case we are in a cron loop
462         }
464         // checking post validity, and adding users to loop through later
465         foreach ($posts as $pid => $post) {
467             $discussionid = $post->discussion;
468             if (!isset($discussions[$discussionid])) {
469                 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
470                     $discussions[$discussionid] = $discussion;
471                 } else {
472                     mtrace('Could not find discussion '.$discussionid);
473                     unset($posts[$pid]);
474                     continue;
475                 }
476             }
477             $forumid = $discussions[$discussionid]->forum;
478             if (!isset($forums[$forumid])) {
479                 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
480                     $forums[$forumid] = $forum;
481                 } else {
482                     mtrace('Could not find forum '.$forumid);
483                     unset($posts[$pid]);
484                     continue;
485                 }
486             }
487             $courseid = $forums[$forumid]->course;
488             if (!isset($courses[$courseid])) {
489                 if ($course = $DB->get_record('course', array('id' => $courseid))) {
490                     $courses[$courseid] = $course;
491                 } else {
492                     mtrace('Could not find course '.$courseid);
493                     unset($posts[$pid]);
494                     continue;
495                 }
496             }
497             if (!isset($coursemodules[$forumid])) {
498                 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
499                     $coursemodules[$forumid] = $cm;
500                 } else {
501                     mtrace('Could not find course module for forum '.$forumid);
502                     unset($posts[$pid]);
503                     continue;
504                 }
505             }
508             // caching subscribed users of each forum
509             if (!isset($subscribedusers[$forumid])) {
510                 $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
511                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext, "u.*")) {
512                     foreach ($subusers as $postuser) {
513                         // this user is subscribed to this forum
514                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
515                         $userscount++;
516                         if ($userscount > FORUM_CRON_USER_CACHE) {
517                             // Store minimal user info.
518                             $minuser = new stdClass();
519                             $minuser->id = $postuser->id;
520                             $users[$postuser->id] = $minuser;
521                         } else {
522                             // Cache full user record.
523                             forum_cron_minimise_user_record($postuser);
524                             $users[$postuser->id] = $postuser;
525                         }
526                     }
527                     // Release memory.
528                     unset($subusers);
529                     unset($postuser);
530                 }
531             }
533             $mailcount[$pid] = 0;
534             $errorcount[$pid] = 0;
535         }
536     }
538     if ($users && $posts) {
540         $urlinfo = parse_url($CFG->wwwroot);
541         $hostname = $urlinfo['host'];
543         foreach ($users as $userto) {
545             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
547             mtrace('Processing user '.$userto->id);
549             // Init user caches - we keep the cache for one cycle only,
550             // otherwise it could consume too much memory.
551             if (isset($userto->username)) {
552                 $userto = clone($userto);
553             } else {
554                 $userto = $DB->get_record('user', array('id' => $userto->id));
555                 forum_cron_minimise_user_record($userto);
556             }
557             $userto->viewfullnames = array();
558             $userto->canpost       = array();
559             $userto->markposts     = array();
561             // set this so that the capabilities are cached, and environment matches receiving user
562             cron_setup_user($userto);
564             // reset the caches
565             foreach ($coursemodules as $forumid=>$unused) {
566                 $coursemodules[$forumid]->cache       = new stdClass();
567                 $coursemodules[$forumid]->cache->caps = array();
568                 unset($coursemodules[$forumid]->uservisible);
569             }
571             foreach ($posts as $pid => $post) {
573                 // Set up the environment for the post, discussion, forum, course
574                 $discussion = $discussions[$post->discussion];
575                 $forum      = $forums[$discussion->forum];
576                 $course     = $courses[$forum->course];
577                 $cm         =& $coursemodules[$forum->id];
579                 // Do some checks  to see if we can bail out now
580                 // Only active enrolled users are in the list of subscribers
581                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
582                     continue; // user does not subscribe to this forum
583                 }
585                 // Don't send email if the forum is Q&A and the user has not posted
586                 // Initial topics are still mailed
587                 if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id) && $pid != $discussion->firstpost) {
588                     mtrace('Did not email '.$userto->id.' because user has not posted in discussion');
589                     continue;
590                 }
592                 // Get info about the sending user
593                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
594                     $userfrom = $users[$post->userid];
595                     if (!isset($userfrom->idnumber)) {
596                         // Minimalised user info, fetch full record.
597                         $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
598                         forum_cron_minimise_user_record($userfrom);
599                     }
601                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
602                     forum_cron_minimise_user_record($userfrom);
603                     // Fetch only once if possible, we can add it to user list, it will be skipped anyway.
604                     if ($userscount <= FORUM_CRON_USER_CACHE) {
605                         $userscount++;
606                         $users[$userfrom->id] = $userfrom;
607                     }
609                 } else {
610                     mtrace('Could not find user '.$post->userid);
611                     continue;
612                 }
614                 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
616                 // setup global $COURSE properly - needed for roles and languages
617                 cron_setup_user($userto, $course);
619                 // Fill caches
620                 if (!isset($userto->viewfullnames[$forum->id])) {
621                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
622                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
623                 }
624                 if (!isset($userto->canpost[$discussion->id])) {
625                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
626                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
627                 }
628                 if (!isset($userfrom->groups[$forum->id])) {
629                     if (!isset($userfrom->groups)) {
630                         $userfrom->groups = array();
631                         if (isset($users[$userfrom->id])) {
632                             $users[$userfrom->id]->groups = array();
633                         }
634                     }
635                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
636                     if (isset($users[$userfrom->id])) {
637                         $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
638                     }
639                 }
641                 // Make sure groups allow this user to see this email
642                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
643                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
644                         continue;                           // Be safe and don't send it to anyone
645                     }
647                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
648                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
649                         continue;
650                     }
651                 }
653                 // Make sure we're allowed to see it...
654                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
655                     mtrace('user '.$userto->id. ' can not see '.$post->id);
656                     continue;
657                 }
659                 // OK so we need to send the email.
661                 // Does the user want this post in a digest?  If so postpone it for now.
662                 if ($userto->maildigest > 0) {
663                     // This user wants the mails to be in digest form
664                     $queue = new stdClass();
665                     $queue->userid       = $userto->id;
666                     $queue->discussionid = $discussion->id;
667                     $queue->postid       = $post->id;
668                     $queue->timemodified = $post->created;
669                     $DB->insert_record('forum_queue', $queue);
670                     continue;
671                 }
674                 // Prepare to actually send the post now, and build up the content
676                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
678                 $userfrom->customheaders = array (  // Headers to make emails easier to track
679                            'Precedence: Bulk',
680                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
681                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
682                            'Message-ID: '.forum_get_email_message_id($post->id, $userto->id, $hostname),
683                            'X-Course-Id: '.$course->id,
684                            'X-Course-Name: '.format_string($course->fullname, true)
685                 );
687                 if ($post->parent) {  // This post is a reply, so add headers for threading (see MDL-22551)
688                     $userfrom->customheaders[] = 'In-Reply-To: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
689                     $userfrom->customheaders[] = 'References: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
690                 }
692                 $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
694                 $postsubject = html_to_text("$shortname: ".format_string($post->subject, true));
695                 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
696                 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
698                 // Send the post now!
700                 mtrace('Sending ', '');
702                 $eventdata = new stdClass();
703                 $eventdata->component        = 'mod_forum';
704                 $eventdata->name             = 'posts';
705                 $eventdata->userfrom         = $userfrom;
706                 $eventdata->userto           = $userto;
707                 $eventdata->subject          = $postsubject;
708                 $eventdata->fullmessage      = $posttext;
709                 $eventdata->fullmessageformat = FORMAT_PLAIN;
710                 $eventdata->fullmessagehtml  = $posthtml;
711                 $eventdata->notification = 1;
713                 $smallmessagestrings = new stdClass();
714                 $smallmessagestrings->user = fullname($userfrom);
715                 $smallmessagestrings->forumname = "$shortname: ".format_string($forum->name,true).": ".$discussion->name;
716                 $smallmessagestrings->message = $post->message;
717                 //make sure strings are in message recipients language
718                 $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
720                 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
721                 $eventdata->contexturlname = $discussion->name;
723                 // If forum_replytouser is not set then send mail using the noreplyaddress.
724                 if (empty($CFG->forum_replytouser)) {
725                     $eventdata->userfrom->email = $CFG->noreplyaddress;
726                 }
728                 $mailresult = message_send($eventdata);
729                 if (!$mailresult){
730                     mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
731                          " ($userto->email) .. not trying again.");
732                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
733                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
734                     $errorcount[$post->id]++;
735                 } else {
736                     $mailcount[$post->id]++;
738                 // Mark post as read if forum_usermarksread is set off
739                     if (!$CFG->forum_usermarksread) {
740                         $userto->markposts[$post->id] = $post->id;
741                     }
742                 }
744                 mtrace('post '.$post->id. ': '.$post->subject);
745             }
747             // mark processed posts as read
748             forum_tp_mark_posts_read($userto, $userto->markposts);
749             unset($userto);
750         }
751     }
753     if ($posts) {
754         foreach ($posts as $post) {
755             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
756             if ($errorcount[$post->id]) {
757                 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
758             }
759         }
760     }
762     // release some memory
763     unset($subscribedusers);
764     unset($mailcount);
765     unset($errorcount);
767     cron_setup_user();
769     $sitetimezone = $CFG->timezone;
771     // Now see if there are any digest mails waiting to be sent, and if we should send them
773     mtrace('Starting digest processing...');
775     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
777     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
778         set_config('digestmailtimelast', 0);
779     }
781     $timenow = time();
782     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
784     // Delete any really old ones (normally there shouldn't be any)
785     $weekago = $timenow - (7 * 24 * 3600);
786     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
787     mtrace ('Cleaned old digest records');
789     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
791         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
793         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
795         if ($digestposts_rs->valid()) {
797             // We have work to do
798             $usermailcount = 0;
800             //caches - reuse the those filled before too
801             $discussionposts = array();
802             $userdiscussions = array();
804             foreach ($digestposts_rs as $digestpost) {
805                 if (!isset($posts[$digestpost->postid])) {
806                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
807                         $posts[$digestpost->postid] = $post;
808                     } else {
809                         continue;
810                     }
811                 }
812                 $discussionid = $digestpost->discussionid;
813                 if (!isset($discussions[$discussionid])) {
814                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
815                         $discussions[$discussionid] = $discussion;
816                     } else {
817                         continue;
818                     }
819                 }
820                 $forumid = $discussions[$discussionid]->forum;
821                 if (!isset($forums[$forumid])) {
822                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
823                         $forums[$forumid] = $forum;
824                     } else {
825                         continue;
826                     }
827                 }
829                 $courseid = $forums[$forumid]->course;
830                 if (!isset($courses[$courseid])) {
831                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
832                         $courses[$courseid] = $course;
833                     } else {
834                         continue;
835                     }
836                 }
838                 if (!isset($coursemodules[$forumid])) {
839                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
840                         $coursemodules[$forumid] = $cm;
841                     } else {
842                         continue;
843                     }
844                 }
845                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
846                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
847             }
848             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
850             // Data collected, start sending out emails to each user
851             foreach ($userdiscussions as $userid => $thesediscussions) {
853                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
855                 cron_setup_user();
857                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
859                 // First of all delete all the queue entries for this user
860                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
862                 // Init user caches - we keep the cache for one cycle only,
863                 // otherwise it would unnecessarily consume memory.
864                 if (array_key_exists($userid, $users) and isset($users[$userid]->username)) {
865                     $userto = clone($users[$userid]);
866                 } else {
867                     $userto = $DB->get_record('user', array('id' => $userid));
868                     forum_cron_minimise_user_record($userto);
869                 }
870                 $userto->viewfullnames = array();
871                 $userto->canpost       = array();
872                 $userto->markposts     = array();
874                 // Override the language and timezone of the "current" user, so that
875                 // mail is customised for the receiver.
876                 cron_setup_user($userto);
878                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
880                 $headerdata = new stdClass();
881                 $headerdata->sitename = format_string($site->fullname, true);
882                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
884                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
885                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
887                 $posthtml = "<head>";
888 /*                foreach ($CFG->stylesheets as $stylesheet) {
889                     //TODO: MDL-21120
890                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
891                 }*/
892                 $posthtml .= "</head>\n<body id=\"email\">\n";
893                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
895                 foreach ($thesediscussions as $discussionid) {
897                     @set_time_limit(120);   // to be reset for each post
899                     $discussion = $discussions[$discussionid];
900                     $forum      = $forums[$discussion->forum];
901                     $course     = $courses[$forum->course];
902                     $cm         = $coursemodules[$forum->id];
904                     //override language
905                     cron_setup_user($userto, $course);
907                     // Fill caches
908                     if (!isset($userto->viewfullnames[$forum->id])) {
909                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
910                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
911                     }
912                     if (!isset($userto->canpost[$discussion->id])) {
913                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
914                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
915                     }
917                     $strforums      = get_string('forums', 'forum');
918                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
919                     $canreply       = $userto->canpost[$discussion->id];
920                     $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
922                     $posttext .= "\n \n";
923                     $posttext .= '=====================================================================';
924                     $posttext .= "\n \n";
925                     $posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
926                     if ($discussion->name != $forum->name) {
927                         $posttext  .= " -> ".format_string($discussion->name,true);
928                     }
929                     $posttext .= "\n";
931                     $posthtml .= "<p><font face=\"sans-serif\">".
932                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
933                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
934                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
935                     if ($discussion->name == $forum->name) {
936                         $posthtml .= "</font></p>";
937                     } else {
938                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
939                     }
940                     $posthtml .= '<p>';
942                     $postsarray = $discussionposts[$discussionid];
943                     sort($postsarray);
945                     foreach ($postsarray as $postid) {
946                         $post = $posts[$postid];
948                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
949                             $userfrom = $users[$post->userid];
950                             if (!isset($userfrom->idnumber)) {
951                                 $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
952                                 forum_cron_minimise_user_record($userfrom);
953                             }
955                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
956                             forum_cron_minimise_user_record($userfrom);
957                             if ($userscount <= FORUM_CRON_USER_CACHE) {
958                                 $userscount++;
959                                 $users[$userfrom->id] = $userfrom;
960                             }
962                         } else {
963                             mtrace('Could not find user '.$post->userid);
964                             continue;
965                         }
967                         if (!isset($userfrom->groups[$forum->id])) {
968                             if (!isset($userfrom->groups)) {
969                                 $userfrom->groups = array();
970                                 if (isset($users[$userfrom->id])) {
971                                     $users[$userfrom->id]->groups = array();
972                                 }
973                             }
974                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
975                             if (isset($users[$userfrom->id])) {
976                                 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
977                             }
978                         }
980                         $userfrom->customheaders = array ("Precedence: Bulk");
982                         if ($userto->maildigest == 2) {
983                             // Subjects only
984                             $by = new stdClass();
985                             $by->name = fullname($userfrom);
986                             $by->date = userdate($post->modified);
987                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
988                             $posttext .= "\n---------------------------------------------------------------------";
990                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
991                             $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>';
993                         } else {
994                             // The full treatment
995                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
996                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
998                         // Create an array of postid's for this user to mark as read.
999                             if (!$CFG->forum_usermarksread) {
1000                                 $userto->markposts[$post->id] = $post->id;
1001                             }
1002                         }
1003                     }
1004                     if ($canunsubscribe) {
1005                         $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>";
1006                     } else {
1007                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
1008                     }
1009                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
1010                 }
1011                 $posthtml .= '</body>';
1013                 if (empty($userto->mailformat) || $userto->mailformat != 1) {
1014                     // This user DOESN'T want to receive HTML
1015                     $posthtml = '';
1016                 }
1018                 $attachment = $attachname='';
1019                 // Directly email forum digests rather than sending them via messaging, use the
1020                 // site shortname as 'from name', the noreply address will be used by email_to_user.
1021                 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname);
1023                 if (!$mailresult) {
1024                     mtrace("ERROR!");
1025                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
1026                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
1027                 } else {
1028                     mtrace("success.");
1029                     $usermailcount++;
1031                     // Mark post as read if forum_usermarksread is set off
1032                     forum_tp_mark_posts_read($userto, $userto->markposts);
1033                 }
1034             }
1035         }
1036     /// We have finishied all digest emails, update $CFG->digestmailtimelast
1037         set_config('digestmailtimelast', $timenow);
1038     }
1040     cron_setup_user();
1042     if (!empty($usermailcount)) {
1043         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
1044     }
1046     if (!empty($CFG->forum_lastreadclean)) {
1047         $timenow = time();
1048         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
1049             set_config('forum_lastreadclean', $timenow);
1050             mtrace('Removing old forum read tracking info...');
1051             forum_tp_clean_read_records();
1052         }
1053     } else {
1054         set_config('forum_lastreadclean', time());
1055     }
1058     return true;
1061 /**
1062  * Builds and returns the body of the email notification in plain text.
1063  *
1064  * @global object
1065  * @global object
1066  * @uses CONTEXT_MODULE
1067  * @param object $course
1068  * @param object $cm
1069  * @param object $forum
1070  * @param object $discussion
1071  * @param object $post
1072  * @param object $userfrom
1073  * @param object $userto
1074  * @param boolean $bare
1075  * @return string The email body in plain text format.
1076  */
1077 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
1078     global $CFG, $USER;
1080     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
1082     if (!isset($userto->viewfullnames[$forum->id])) {
1083         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
1084     } else {
1085         $viewfullnames = $userto->viewfullnames[$forum->id];
1086     }
1088     if (!isset($userto->canpost[$discussion->id])) {
1089         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
1090     } else {
1091         $canreply = $userto->canpost[$discussion->id];
1092     }
1094     $by = New stdClass;
1095     $by->name = fullname($userfrom, $viewfullnames);
1096     $by->date = userdate($post->modified, "", $userto->timezone);
1098     $strbynameondate = get_string('bynameondate', 'forum', $by);
1100     $strforums = get_string('forums', 'forum');
1102     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1104     $posttext = '';
1106     if (!$bare) {
1107         $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
1108         $posttext  = "$shortname -> $strforums -> ".format_string($forum->name,true);
1110         if ($discussion->name != $forum->name) {
1111             $posttext  .= " -> ".format_string($discussion->name,true);
1112         }
1113     }
1115     // add absolute file links
1116     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
1118     $posttext .= "\n---------------------------------------------------------------------\n";
1119     $posttext .= format_string($post->subject,true);
1120     if ($bare) {
1121         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1122     }
1123     $posttext .= "\n".$strbynameondate."\n";
1124     $posttext .= "---------------------------------------------------------------------\n";
1125     $posttext .= format_text_email($post->message, $post->messageformat);
1126     $posttext .= "\n\n";
1127     $posttext .= forum_print_attachments($post, $cm, "text");
1129     if (!$bare && $canreply) {
1130         $posttext .= "---------------------------------------------------------------------\n";
1131         $posttext .= get_string("postmailinfo", "forum", $shortname)."\n";
1132         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1133     }
1134     if (!$bare && $canunsubscribe) {
1135         $posttext .= "\n---------------------------------------------------------------------\n";
1136         $posttext .= get_string("unsubscribe", "forum");
1137         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1138     }
1140     return $posttext;
1143 /**
1144  * Builds and returns the body of the email notification in html format.
1145  *
1146  * @global object
1147  * @param object $course
1148  * @param object $cm
1149  * @param object $forum
1150  * @param object $discussion
1151  * @param object $post
1152  * @param object $userfrom
1153  * @param object $userto
1154  * @return string The email text in HTML format
1155  */
1156 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1157     global $CFG;
1159     if ($userto->mailformat != 1) {  // Needs to be HTML
1160         return '';
1161     }
1163     if (!isset($userto->canpost[$discussion->id])) {
1164         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course);
1165     } else {
1166         $canreply = $userto->canpost[$discussion->id];
1167     }
1169     $strforums = get_string('forums', 'forum');
1170     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1171     $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
1173     $posthtml = '<head>';
1174 /*    foreach ($CFG->stylesheets as $stylesheet) {
1175         //TODO: MDL-21120
1176         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1177     }*/
1178     $posthtml .= '</head>';
1179     $posthtml .= "\n<body id=\"email\">\n\n";
1181     $posthtml .= '<div class="navbar">'.
1182     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$shortname.'</a> &raquo; '.
1183     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1184     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1185     if ($discussion->name == $forum->name) {
1186         $posthtml .= '</div>';
1187     } else {
1188         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1189                      format_string($discussion->name,true).'</a></div>';
1190     }
1191     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1193     if ($canunsubscribe) {
1194         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1195                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1196                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1197     }
1199     $posthtml .= '</body>';
1201     return $posthtml;
1205 /**
1206  *
1207  * @param object $course
1208  * @param object $user
1209  * @param object $mod TODO this is not used in this function, refactor
1210  * @param object $forum
1211  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1212  */
1213 function forum_user_outline($course, $user, $mod, $forum) {
1214     global $CFG;
1215     require_once("$CFG->libdir/gradelib.php");
1216     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1217     if (empty($grades->items[0]->grades)) {
1218         $grade = false;
1219     } else {
1220         $grade = reset($grades->items[0]->grades);
1221     }
1223     $count = forum_count_user_posts($forum->id, $user->id);
1225     if ($count && $count->postcount > 0) {
1226         $result = new stdClass();
1227         $result->info = get_string("numposts", "forum", $count->postcount);
1228         $result->time = $count->lastpost;
1229         if ($grade) {
1230             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1231         }
1232         return $result;
1233     } else if ($grade) {
1234         $result = new stdClass();
1235         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1237         //datesubmitted == time created. dategraded == time modified or time overridden
1238         //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
1239         //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
1240         if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1241             $result->time = $grade->dategraded;
1242         } else {
1243             $result->time = $grade->datesubmitted;
1244         }
1246         return $result;
1247     }
1248     return NULL;
1252 /**
1253  * @global object
1254  * @global object
1255  * @param object $coure
1256  * @param object $user
1257  * @param object $mod
1258  * @param object $forum
1259  */
1260 function forum_user_complete($course, $user, $mod, $forum) {
1261     global $CFG,$USER, $OUTPUT;
1262     require_once("$CFG->libdir/gradelib.php");
1264     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1265     if (!empty($grades->items[0]->grades)) {
1266         $grade = reset($grades->items[0]->grades);
1267         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1268         if ($grade->str_feedback) {
1269             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1270         }
1271     }
1273     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1275         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1276             print_error('invalidcoursemodule');
1277         }
1278         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1280         foreach ($posts as $post) {
1281             if (!isset($discussions[$post->discussion])) {
1282                 continue;
1283             }
1284             $discussion = $discussions[$post->discussion];
1286             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1287         }
1288     } else {
1289         echo "<p>".get_string("noposts", "forum")."</p>";
1290     }
1298 /**
1299  * @global object
1300  * @global object
1301  * @global object
1302  * @param array $courses
1303  * @param array $htmlarray
1304  */
1305 function forum_print_overview($courses,&$htmlarray) {
1306     global $USER, $CFG, $DB, $SESSION;
1308     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1309         return array();
1310     }
1312     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1313         return;
1314     }
1316     // Courses to search for new posts
1317     $coursessqls = array();
1318     $params = array();
1319     foreach ($courses as $course) {
1321         // If the user has never entered into the course all posts are pending
1322         if ($course->lastaccess == 0) {
1323             $coursessqls[] = '(f.course = ?)';
1324             $params[] = $course->id;
1326         // Only posts created after the course last access
1327         } else {
1328             $coursessqls[] = '(f.course = ? AND p.created > ?)';
1329             $params[] = $course->id;
1330             $params[] = $course->lastaccess;
1331         }
1332     }
1333     $params[] = $USER->id;
1334     $coursessql = implode(' OR ', $coursessqls);
1336     $sql = "SELECT f.id, COUNT(*) as count "
1337                 .'FROM {forum} f '
1338                 .'JOIN {forum_discussions} d ON d.forum  = f.id '
1339                 .'JOIN {forum_posts} p ON p.discussion = d.id '
1340                 ."WHERE ($coursessql) "
1341                 .'AND p.userid != ? '
1342                 .'GROUP BY f.id';
1344     if (!$new = $DB->get_records_sql($sql, $params)) {
1345         $new = array(); // avoid warnings
1346     }
1348     // also get all forum tracking stuff ONCE.
1349     $trackingforums = array();
1350     foreach ($forums as $forum) {
1351         if (forum_tp_can_track_forums($forum)) {
1352             $trackingforums[$forum->id] = $forum;
1353         }
1354     }
1356     if (count($trackingforums) > 0) {
1357         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1358         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1359             ' FROM {forum_posts} p '.
1360             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1361             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1362         $params = array($USER->id);
1364         foreach ($trackingforums as $track) {
1365             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1366             $params[] = $track->id;
1367             if (isset($SESSION->currentgroup[$track->course])) {
1368                 $groupid =  $SESSION->currentgroup[$track->course];
1369             } else {
1370                 // get first groupid
1371                 $groupids = groups_get_all_groups($track->course, $USER->id);
1372                 if ($groupids) {
1373                     reset($groupids);
1374                     $groupid = key($groupids);
1375                     $SESSION->currentgroup[$track->course] = $groupid;
1376                 } else {
1377                     $groupid = 0;
1378                 }
1379                 unset($groupids);
1380             }
1381             $params[] = $groupid;
1382         }
1383         $sql = substr($sql,0,-3); // take off the last OR
1384         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1385         $params[] = $cutoffdate;
1387         if (!$unread = $DB->get_records_sql($sql, $params)) {
1388             $unread = array();
1389         }
1390     } else {
1391         $unread = array();
1392     }
1394     if (empty($unread) and empty($new)) {
1395         return;
1396     }
1398     $strforum = get_string('modulename','forum');
1400     foreach ($forums as $forum) {
1401         $str = '';
1402         $count = 0;
1403         $thisunread = 0;
1404         $showunread = false;
1405         // either we have something from logs, or trackposts, or nothing.
1406         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1407             $count = $new[$forum->id]->count;
1408         }
1409         if (array_key_exists($forum->id,$unread)) {
1410             $thisunread = $unread[$forum->id]->count;
1411             $showunread = true;
1412         }
1413         if ($count > 0 || $thisunread > 0) {
1414             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1415                 $forum->name.'</a></div>';
1416             $str .= '<div class="info"><span class="postsincelogin">';
1417             $str .= get_string('overviewnumpostssince', 'forum', $count)."</span>";
1418             if (!empty($showunread)) {
1419                 $str .= '<div class="unreadposts">'.get_string('overviewnumunread', 'forum', $thisunread).'</div>';
1420             }
1421             $str .= '</div></div>';
1422         }
1423         if (!empty($str)) {
1424             if (!array_key_exists($forum->course,$htmlarray)) {
1425                 $htmlarray[$forum->course] = array();
1426             }
1427             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1428                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1429             }
1430             $htmlarray[$forum->course]['forum'] .= $str;
1431         }
1432     }
1435 /**
1436  * Given a course and a date, prints a summary of all the new
1437  * messages posted in the course since that date
1438  *
1439  * @global object
1440  * @global object
1441  * @global object
1442  * @uses CONTEXT_MODULE
1443  * @uses VISIBLEGROUPS
1444  * @param object $course
1445  * @param bool $viewfullnames capability
1446  * @param int $timestart
1447  * @return bool success
1448  */
1449 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1450     global $CFG, $USER, $DB, $OUTPUT;
1452     // do not use log table if possible, it may be huge and is expensive to join with other tables
1454     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1455                                               d.timestart, d.timeend, d.userid AS duserid,
1456                                               u.firstname, u.lastname, u.email, u.picture
1457                                          FROM {forum_posts} p
1458                                               JOIN {forum_discussions} d ON d.id = p.discussion
1459                                               JOIN {forum} f             ON f.id = d.forum
1460                                               JOIN {user} u              ON u.id = p.userid
1461                                         WHERE p.created > ? AND f.course = ?
1462                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1463          return false;
1464     }
1466     $modinfo = get_fast_modinfo($course);
1468     $groupmodes = array();
1469     $cms    = array();
1471     $strftimerecent = get_string('strftimerecent');
1473     $printposts = array();
1474     foreach ($posts as $post) {
1475         if (!isset($modinfo->instances['forum'][$post->forum])) {
1476             // not visible
1477             continue;
1478         }
1479         $cm = $modinfo->instances['forum'][$post->forum];
1480         if (!$cm->uservisible) {
1481             continue;
1482         }
1483         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1485         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1486             continue;
1487         }
1489         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1490           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1491             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1492                 continue;
1493             }
1494         }
1496         $groupmode = groups_get_activity_groupmode($cm, $course);
1498         if ($groupmode) {
1499             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1500                 // oki (Open discussions have groupid -1)
1501             } else {
1502                 // separate mode
1503                 if (isguestuser()) {
1504                     // shortcut
1505                     continue;
1506                 }
1508                 if (is_null($modinfo->groups)) {
1509                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1510                 }
1512                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1513                     continue;
1514                 }
1515             }
1516         }
1518         $printposts[] = $post;
1519     }
1520     unset($posts);
1522     if (!$printposts) {
1523         return false;
1524     }
1526     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1527     echo "\n<ul class='unlist'>\n";
1529     foreach ($printposts as $post) {
1530         $subjectclass = empty($post->parent) ? ' bold' : '';
1532         echo '<li><div class="head">'.
1533                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1534                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1535              '</div>';
1536         echo '<div class="info'.$subjectclass.'">';
1537         if (empty($post->parent)) {
1538             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1539         } else {
1540             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1541         }
1542         $post->subject = break_up_long_words(format_string($post->subject, true));
1543         echo $post->subject;
1544         echo "</a>\"</div></li>\n";
1545     }
1547     echo "</ul>\n";
1549     return true;
1552 /**
1553  * Return grade for given user or all users.
1554  *
1555  * @global object
1556  * @global object
1557  * @param object $forum
1558  * @param int $userid optional user id, 0 means all users
1559  * @return array array of grades, false if none
1560  */
1561 function forum_get_user_grades($forum, $userid = 0) {
1562     global $CFG;
1564     require_once($CFG->dirroot.'/rating/lib.php');
1566     $ratingoptions = new stdClass;
1567     $ratingoptions->component = 'mod_forum';
1568     $ratingoptions->ratingarea = 'post';
1570     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1571     $ratingoptions->modulename = 'forum';
1572     $ratingoptions->moduleid   = $forum->id;
1573     $ratingoptions->userid = $userid;
1574     $ratingoptions->aggregationmethod = $forum->assessed;
1575     $ratingoptions->scaleid = $forum->scale;
1576     $ratingoptions->itemtable = 'forum_posts';
1577     $ratingoptions->itemtableusercolumn = 'userid';
1579     $rm = new rating_manager();
1580     return $rm->get_user_grades($ratingoptions);
1583 /**
1584  * Update activity grades
1585  *
1586  * @category grade
1587  * @param object $forum
1588  * @param int $userid specific user only, 0 means all
1589  * @param boolean $nullifnone return null if grade does not exist
1590  * @return void
1591  */
1592 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1593     global $CFG, $DB;
1594     require_once($CFG->libdir.'/gradelib.php');
1596     if (!$forum->assessed) {
1597         forum_grade_item_update($forum);
1599     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1600         forum_grade_item_update($forum, $grades);
1602     } else if ($userid and $nullifnone) {
1603         $grade = new stdClass();
1604         $grade->userid   = $userid;
1605         $grade->rawgrade = NULL;
1606         forum_grade_item_update($forum, $grade);
1608     } else {
1609         forum_grade_item_update($forum);
1610     }
1613 /**
1614  * Update all grades in gradebook.
1615  * @global object
1616  */
1617 function forum_upgrade_grades() {
1618     global $DB;
1620     $sql = "SELECT COUNT('x')
1621               FROM {forum} f, {course_modules} cm, {modules} m
1622              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1623     $count = $DB->count_records_sql($sql);
1625     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
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     $rs = $DB->get_recordset_sql($sql);
1629     if ($rs->valid()) {
1630         $pbar = new progress_bar('forumupgradegrades', 500, true);
1631         $i=0;
1632         foreach ($rs as $forum) {
1633             $i++;
1634             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1635             forum_update_grades($forum, 0, false);
1636             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1637         }
1638     }
1639     $rs->close();
1642 /**
1643  * Create/update grade item for given forum
1644  *
1645  * @category grade
1646  * @uses GRADE_TYPE_NONE
1647  * @uses GRADE_TYPE_VALUE
1648  * @uses GRADE_TYPE_SCALE
1649  * @param stdClass $forum Forum object with extra cmidnumber
1650  * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
1651  * @return int 0 if ok
1652  */
1653 function forum_grade_item_update($forum, $grades=NULL) {
1654     global $CFG;
1655     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1656         require_once($CFG->libdir.'/gradelib.php');
1657     }
1659     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1661     if (!$forum->assessed or $forum->scale == 0) {
1662         $params['gradetype'] = GRADE_TYPE_NONE;
1664     } else if ($forum->scale > 0) {
1665         $params['gradetype'] = GRADE_TYPE_VALUE;
1666         $params['grademax']  = $forum->scale;
1667         $params['grademin']  = 0;
1669     } else if ($forum->scale < 0) {
1670         $params['gradetype'] = GRADE_TYPE_SCALE;
1671         $params['scaleid']   = -$forum->scale;
1672     }
1674     if ($grades  === 'reset') {
1675         $params['reset'] = true;
1676         $grades = NULL;
1677     }
1679     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1682 /**
1683  * Delete grade item for given forum
1684  *
1685  * @category grade
1686  * @param stdClass $forum Forum object
1687  * @return grade_item
1688  */
1689 function forum_grade_item_delete($forum) {
1690     global $CFG;
1691     require_once($CFG->libdir.'/gradelib.php');
1693     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1697 /**
1698  * This function returns if a scale is being used by one forum
1699  *
1700  * @global object
1701  * @param int $forumid
1702  * @param int $scaleid negative number
1703  * @return bool
1704  */
1705 function forum_scale_used ($forumid,$scaleid) {
1706     global $DB;
1707     $return = false;
1709     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1711     if (!empty($rec) && !empty($scaleid)) {
1712         $return = true;
1713     }
1715     return $return;
1718 /**
1719  * Checks if scale is being used by any instance of forum
1720  *
1721  * This is used to find out if scale used anywhere
1722  *
1723  * @global object
1724  * @param $scaleid int
1725  * @return boolean True if the scale is used by any forum
1726  */
1727 function forum_scale_used_anywhere($scaleid) {
1728     global $DB;
1729     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1730         return true;
1731     } else {
1732         return false;
1733     }
1736 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1738 /**
1739  * Gets a post with all info ready for forum_print_post
1740  * Most of these joins are just to get the forum id
1741  *
1742  * @global object
1743  * @global object
1744  * @param int $postid
1745  * @return mixed array of posts or false
1746  */
1747 function forum_get_post_full($postid) {
1748     global $CFG, $DB;
1750     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1751                              FROM {forum_posts} p
1752                                   JOIN {forum_discussions} d ON p.discussion = d.id
1753                                   LEFT JOIN {user} u ON p.userid = u.id
1754                             WHERE p.id = ?", array($postid));
1757 /**
1758  * Gets posts with all info ready for forum_print_post
1759  * We pass forumid in because we always know it so no need to make a
1760  * complicated join to find it out.
1761  *
1762  * @global object
1763  * @global object
1764  * @return mixed array of posts or false
1765  */
1766 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1767     global $CFG, $DB;
1769     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1770                               FROM {forum_posts} p
1771                          LEFT JOIN {user} u ON p.userid = u.id
1772                              WHERE p.discussion = ?
1773                                AND p.parent > 0 $sort", array($discussion));
1776 /**
1777  * Gets all posts in discussion including top parent.
1778  *
1779  * @global object
1780  * @global object
1781  * @global object
1782  * @param int $discussionid
1783  * @param string $sort
1784  * @param bool $tracking does user track the forum?
1785  * @return array of posts
1786  */
1787 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1788     global $CFG, $DB, $USER;
1790     $tr_sel  = "";
1791     $tr_join = "";
1792     $params = array();
1794     if ($tracking) {
1795         $now = time();
1796         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1797         $tr_sel  = ", fr.id AS postread";
1798         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1799         $params[] = $USER->id;
1800     }
1802     $params[] = $discussionid;
1803     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1804                                      FROM {forum_posts} p
1805                                           LEFT JOIN {user} u ON p.userid = u.id
1806                                           $tr_join
1807                                     WHERE p.discussion = ?
1808                                  ORDER BY $sort", $params)) {
1809         return array();
1810     }
1812     foreach ($posts as $pid=>$p) {
1813         if ($tracking) {
1814             if (forum_tp_is_post_old($p)) {
1815                  $posts[$pid]->postread = true;
1816             }
1817         }
1818         if (!$p->parent) {
1819             continue;
1820         }
1821         if (!isset($posts[$p->parent])) {
1822             continue; // parent does not exist??
1823         }
1824         if (!isset($posts[$p->parent]->children)) {
1825             $posts[$p->parent]->children = array();
1826         }
1827         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1828     }
1830     return $posts;
1833 /**
1834  * Gets posts with all info ready for forum_print_post
1835  * We pass forumid in because we always know it so no need to make a
1836  * complicated join to find it out.
1837  *
1838  * @global object
1839  * @global object
1840  * @param int $parent
1841  * @param int $forumid
1842  * @return array
1843  */
1844 function forum_get_child_posts($parent, $forumid) {
1845     global $CFG, $DB;
1847     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1848                               FROM {forum_posts} p
1849                          LEFT JOIN {user} u ON p.userid = u.id
1850                              WHERE p.parent = ?
1851                           ORDER BY p.created ASC", array($parent));
1854 /**
1855  * An array of forum objects that the user is allowed to read/search through.
1856  *
1857  * @global object
1858  * @global object
1859  * @global object
1860  * @param int $userid
1861  * @param int $courseid if 0, we look for forums throughout the whole site.
1862  * @return array of forum objects, or false if no matches
1863  *         Forum objects have the following attributes:
1864  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1865  *         viewhiddentimedposts
1866  */
1867 function forum_get_readable_forums($userid, $courseid=0) {
1869     global $CFG, $DB, $USER;
1870     require_once($CFG->dirroot.'/course/lib.php');
1872     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1873         print_error('notinstalled', 'forum');
1874     }
1876     if ($courseid) {
1877         $courses = $DB->get_records('course', array('id' => $courseid));
1878     } else {
1879         // If no course is specified, then the user can see SITE + his courses.
1880         $courses1 = $DB->get_records('course', array('id' => SITEID));
1881         $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
1882         $courses = array_merge($courses1, $courses2);
1883     }
1884     if (!$courses) {
1885         return array();
1886     }
1888     $readableforums = array();
1890     foreach ($courses as $course) {
1892         $modinfo = get_fast_modinfo($course);
1893         if (is_null($modinfo->groups)) {
1894             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1895         }
1897         if (empty($modinfo->instances['forum'])) {
1898             // hmm, no forums?
1899             continue;
1900         }
1902         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1904         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1905             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1906                 continue;
1907             }
1908             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1909             $forum = $courseforums[$forumid];
1910             $forum->context = $context;
1911             $forum->cm = $cm;
1913             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1914                 continue;
1915             }
1917          /// group access
1918             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1919                 if (is_null($modinfo->groups)) {
1920                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1921                 }
1922                 if (isset($modinfo->groups[$cm->groupingid])) {
1923                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1924                     $forum->onlygroups[] = -1;
1925                 } else {
1926                     $forum->onlygroups = array(-1);
1927                 }
1928             }
1930         /// hidden timed discussions
1931             $forum->viewhiddentimedposts = true;
1932             if (!empty($CFG->forum_enabletimedposts)) {
1933                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1934                     $forum->viewhiddentimedposts = false;
1935                 }
1936             }
1938         /// qanda access
1939             if ($forum->type == 'qanda'
1940                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1942                 // We need to check whether the user has posted in the qanda forum.
1943                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1944                                                     // the user is allowed to see in this forum.
1945                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1946                     foreach ($discussionspostedin as $d) {
1947                         $forum->onlydiscussions[] = $d->id;
1948                     }
1949                 }
1950             }
1952             $readableforums[$forum->id] = $forum;
1953         }
1955         unset($modinfo);
1957     } // End foreach $courses
1959     return $readableforums;
1962 /**
1963  * Returns a list of posts found using an array of search terms.
1964  *
1965  * @global object
1966  * @global object
1967  * @global object
1968  * @param array $searchterms array of search terms, e.g. word +word -word
1969  * @param int $courseid if 0, we search through the whole site
1970  * @param int $limitfrom
1971  * @param int $limitnum
1972  * @param int &$totalcount
1973  * @param string $extrasql
1974  * @return array|bool Array of posts found or false
1975  */
1976 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1977                             &$totalcount, $extrasql='') {
1978     global $CFG, $DB, $USER;
1979     require_once($CFG->libdir.'/searchlib.php');
1981     $forums = forum_get_readable_forums($USER->id, $courseid);
1983     if (count($forums) == 0) {
1984         $totalcount = 0;
1985         return false;
1986     }
1988     $now = round(time(), -2); // db friendly
1990     $fullaccess = array();
1991     $where = array();
1992     $params = array();
1994     foreach ($forums as $forumid => $forum) {
1995         $select = array();
1997         if (!$forum->viewhiddentimedposts) {
1998             $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
1999             $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
2000         }
2002         $cm = $forum->cm;
2003         $context = $forum->context;
2005         if ($forum->type == 'qanda'
2006             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
2007             if (!empty($forum->onlydiscussions)) {
2008                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
2009                 $params = array_merge($params, $discussionid_params);
2010                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
2011             } else {
2012                 $select[] = "p.parent = 0";
2013             }
2014         }
2016         if (!empty($forum->onlygroups)) {
2017             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
2018             $params = array_merge($params, $groupid_params);
2019             $select[] = "d.groupid $groupid_sql";
2020         }
2022         if ($select) {
2023             $selects = implode(" AND ", $select);
2024             $where[] = "(d.forum = :forum{$forumid} AND $selects)";
2025             $params['forum'.$forumid] = $forumid;
2026         } else {
2027             $fullaccess[] = $forumid;
2028         }
2029     }
2031     if ($fullaccess) {
2032         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
2033         $params = array_merge($params, $fullid_params);
2034         $where[] = "(d.forum $fullid_sql)";
2035     }
2037     $selectdiscussion = "(".implode(" OR ", $where).")";
2039     $messagesearch = '';
2040     $searchstring = '';
2042     // Need to concat these back together for parser to work.
2043     foreach($searchterms as $searchterm){
2044         if ($searchstring != '') {
2045             $searchstring .= ' ';
2046         }
2047         $searchstring .= $searchterm;
2048     }
2050     // We need to allow quoted strings for the search. The quotes *should* be stripped
2051     // by the parser, but this should be examined carefully for security implications.
2052     $searchstring = str_replace("\\\"","\"",$searchstring);
2053     $parser = new search_parser();
2054     $lexer = new search_lexer($parser);
2056     if ($lexer->parse($searchstring)) {
2057         $parsearray = $parser->get_parsed_array();
2058     // Experimental feature under 1.8! MDL-8830
2059     // Use alternative text searches if defined
2060     // This feature only works under mysql until properly implemented for other DBs
2061     // Requires manual creation of text index for forum_posts before enabling it:
2062     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2063     // Experimental feature under 1.8! MDL-8830
2064         if (!empty($CFG->forum_usetextsearches)) {
2065             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2066                                                  'p.userid', 'u.id', 'u.firstname',
2067                                                  'u.lastname', 'p.modified', 'd.forum');
2068         } else {
2069             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2070                                                  'p.userid', 'u.id', 'u.firstname',
2071                                                  'u.lastname', 'p.modified', 'd.forum');
2072         }
2073         $params = array_merge($params, $msparams);
2074     }
2076     $fromsql = "{forum_posts} p,
2077                   {forum_discussions} d,
2078                   {user} u";
2080     $selectsql = " $messagesearch
2081                AND p.discussion = d.id
2082                AND p.userid = u.id
2083                AND $selectdiscussion
2084                    $extrasql";
2086     $countsql = "SELECT COUNT(*)
2087                    FROM $fromsql
2088                   WHERE $selectsql";
2090     $searchsql = "SELECT p.*,
2091                          d.forum,
2092                          u.firstname,
2093                          u.lastname,
2094                          u.email,
2095                          u.picture,
2096                          u.imagealt
2097                     FROM $fromsql
2098                    WHERE $selectsql
2099                 ORDER BY p.modified DESC";
2101     $totalcount = $DB->count_records_sql($countsql, $params);
2103     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2106 /**
2107  * Returns a list of ratings for a particular post - sorted.
2108  *
2109  * TODO: Check if this function is actually used anywhere.
2110  * Up until the fix for MDL-27471 this function wasn't even returning.
2111  *
2112  * @param stdClass $context
2113  * @param int $postid
2114  * @param string $sort
2115  * @return array Array of ratings or false
2116  */
2117 function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
2118     $options = new stdClass;
2119     $options->context = $context;
2120     $options->component = 'mod_forum';
2121     $options->ratingarea = 'post';
2122     $options->itemid = $postid;
2123     $options->sort = "ORDER BY $sort";
2125     $rm = new rating_manager();
2126     return $rm->get_all_ratings_for_item($options);
2129 /**
2130  * Returns a list of all new posts that have not been mailed yet
2131  *
2132  * @param int $starttime posts created after this time
2133  * @param int $endtime posts created before this
2134  * @param int $now used for timed discussions only
2135  * @return array
2136  */
2137 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2138     global $CFG, $DB;
2140     $params = array($starttime, $endtime);
2141     if (!empty($CFG->forum_enabletimedposts)) {
2142         if (empty($now)) {
2143             $now = time();
2144         }
2145         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2146         $params[] = $now;
2147         $params[] = $now;
2148     } else {
2149         $timedsql = "";
2150     }
2152     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2153                               FROM {forum_posts} p
2154                                    JOIN {forum_discussions} d ON d.id = p.discussion
2155                              WHERE p.mailed = 0
2156                                    AND p.created >= ?
2157                                    AND (p.created < ? OR p.mailnow = 1)
2158                                    $timedsql
2159                           ORDER BY p.modified ASC", $params);
2162 /**
2163  * Marks posts before a certain time as being mailed already
2164  *
2165  * @global object
2166  * @global object
2167  * @param int $endtime
2168  * @param int $now Defaults to time()
2169  * @return bool
2170  */
2171 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2172     global $CFG, $DB;
2173     if (empty($now)) {
2174         $now = time();
2175     }
2177     if (empty($CFG->forum_enabletimedposts)) {
2178         return $DB->execute("UPDATE {forum_posts}
2179                                SET mailed = '1'
2180                              WHERE (created < ? OR mailnow = 1)
2181                                    AND mailed = 0", array($endtime));
2183     } else {
2184         return $DB->execute("UPDATE {forum_posts}
2185                                SET mailed = '1'
2186                              WHERE discussion NOT IN (SELECT d.id
2187                                                         FROM {forum_discussions} d
2188                                                        WHERE d.timestart > ?)
2189                                    AND (created < ? OR mailnow = 1)
2190                                    AND mailed = 0", array($now, $endtime));
2191     }
2194 /**
2195  * Get all the posts for a user in a forum suitable for forum_print_post
2196  *
2197  * @global object
2198  * @global object
2199  * @uses CONTEXT_MODULE
2200  * @return array
2201  */
2202 function forum_get_user_posts($forumid, $userid) {
2203     global $CFG, $DB;
2205     $timedsql = "";
2206     $params = array($forumid, $userid);
2208     if (!empty($CFG->forum_enabletimedposts)) {
2209         $cm = get_coursemodule_from_instance('forum', $forumid);
2210         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2211             $now = time();
2212             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2213             $params[] = $now;
2214             $params[] = $now;
2215         }
2216     }
2218     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2219                               FROM {forum} f
2220                                    JOIN {forum_discussions} d ON d.forum = f.id
2221                                    JOIN {forum_posts} p       ON p.discussion = d.id
2222                                    JOIN {user} u              ON u.id = p.userid
2223                              WHERE f.id = ?
2224                                    AND p.userid = ?
2225                                    $timedsql
2226                           ORDER BY p.modified ASC", $params);
2229 /**
2230  * Get all the discussions user participated in
2231  *
2232  * @global object
2233  * @global object
2234  * @uses CONTEXT_MODULE
2235  * @param int $forumid
2236  * @param int $userid
2237  * @return array Array or false
2238  */
2239 function forum_get_user_involved_discussions($forumid, $userid) {
2240     global $CFG, $DB;
2242     $timedsql = "";
2243     $params = array($forumid, $userid);
2244     if (!empty($CFG->forum_enabletimedposts)) {
2245         $cm = get_coursemodule_from_instance('forum', $forumid);
2246         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2247             $now = time();
2248             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2249             $params[] = $now;
2250             $params[] = $now;
2251         }
2252     }
2254     return $DB->get_records_sql("SELECT DISTINCT d.*
2255                               FROM {forum} f
2256                                    JOIN {forum_discussions} d ON d.forum = f.id
2257                                    JOIN {forum_posts} p       ON p.discussion = d.id
2258                              WHERE f.id = ?
2259                                    AND p.userid = ?
2260                                    $timedsql", $params);
2263 /**
2264  * Get all the posts for a user in a forum suitable for forum_print_post
2265  *
2266  * @global object
2267  * @global object
2268  * @param int $forumid
2269  * @param int $userid
2270  * @return array of counts or false
2271  */
2272 function forum_count_user_posts($forumid, $userid) {
2273     global $CFG, $DB;
2275     $timedsql = "";
2276     $params = array($forumid, $userid);
2277     if (!empty($CFG->forum_enabletimedposts)) {
2278         $cm = get_coursemodule_from_instance('forum', $forumid);
2279         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2280             $now = time();
2281             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2282             $params[] = $now;
2283             $params[] = $now;
2284         }
2285     }
2287     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2288                              FROM {forum} f
2289                                   JOIN {forum_discussions} d ON d.forum = f.id
2290                                   JOIN {forum_posts} p       ON p.discussion = d.id
2291                                   JOIN {user} u              ON u.id = p.userid
2292                             WHERE f.id = ?
2293                                   AND p.userid = ?
2294                                   $timedsql", $params);
2297 /**
2298  * Given a log entry, return the forum post details for it.
2299  *
2300  * @global object
2301  * @global object
2302  * @param object $log
2303  * @return array|null
2304  */
2305 function forum_get_post_from_log($log) {
2306     global $CFG, $DB;
2308     if ($log->action == "add post") {
2310         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2311                                            u.firstname, u.lastname, u.email, u.picture
2312                                  FROM {forum_discussions} d,
2313                                       {forum_posts} p,
2314                                       {forum} f,
2315                                       {user} u
2316                                 WHERE p.id = ?
2317                                   AND d.id = p.discussion
2318                                   AND p.userid = u.id
2319                                   AND u.deleted <> '1'
2320                                   AND f.id = d.forum", array($log->info));
2323     } else if ($log->action == "add discussion") {
2325         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2326                                            u.firstname, u.lastname, u.email, u.picture
2327                                  FROM {forum_discussions} d,
2328                                       {forum_posts} p,
2329                                       {forum} f,
2330                                       {user} u
2331                                 WHERE d.id = ?
2332                                   AND d.firstpost = p.id
2333                                   AND p.userid = u.id
2334                                   AND u.deleted <> '1'
2335                                   AND f.id = d.forum", array($log->info));
2336     }
2337     return NULL;
2340 /**
2341  * Given a discussion id, return the first post from the discussion
2342  *
2343  * @global object
2344  * @global object
2345  * @param int $dicsussionid
2346  * @return array
2347  */
2348 function forum_get_firstpost_from_discussion($discussionid) {
2349     global $CFG, $DB;
2351     return $DB->get_record_sql("SELECT p.*
2352                              FROM {forum_discussions} d,
2353                                   {forum_posts} p
2354                             WHERE d.id = ?
2355                               AND d.firstpost = p.id ", array($discussionid));
2358 /**
2359  * Returns an array of counts of replies to each discussion
2360  *
2361  * @global object
2362  * @global object
2363  * @param int $forumid
2364  * @param string $forumsort
2365  * @param int $limit
2366  * @param int $page
2367  * @param int $perpage
2368  * @return array
2369  */
2370 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2371     global $CFG, $DB;
2373     if ($limit > 0) {
2374         $limitfrom = 0;
2375         $limitnum  = $limit;
2376     } else if ($page != -1) {
2377         $limitfrom = $page*$perpage;
2378         $limitnum  = $perpage;
2379     } else {
2380         $limitfrom = 0;
2381         $limitnum  = 0;
2382     }
2384     if ($forumsort == "") {
2385         $orderby = "";
2386         $groupby = "";
2388     } else {
2389         $orderby = "ORDER BY $forumsort";
2390         $groupby = ", ".strtolower($forumsort);
2391         $groupby = str_replace('desc', '', $groupby);
2392         $groupby = str_replace('asc', '', $groupby);
2393     }
2395     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2396         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2397                   FROM {forum_posts} p
2398                        JOIN {forum_discussions} d ON p.discussion = d.id
2399                  WHERE p.parent > 0 AND d.forum = ?
2400               GROUP BY p.discussion";
2401         return $DB->get_records_sql($sql, array($forumid));
2403     } else {
2404         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2405                   FROM {forum_posts} p
2406                        JOIN {forum_discussions} d ON p.discussion = d.id
2407                  WHERE d.forum = ?
2408               GROUP BY p.discussion $groupby
2409               $orderby";
2410         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2411     }
2414 /**
2415  * @global object
2416  * @global object
2417  * @global object
2418  * @staticvar array $cache
2419  * @param object $forum
2420  * @param object $cm
2421  * @param object $course
2422  * @return mixed
2423  */
2424 function forum_count_discussions($forum, $cm, $course) {
2425     global $CFG, $DB, $USER;
2427     static $cache = array();
2429     $now = round(time(), -2); // db cache friendliness
2431     $params = array($course->id);
2433     if (!isset($cache[$course->id])) {
2434         if (!empty($CFG->forum_enabletimedposts)) {
2435             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2436             $params[] = $now;
2437             $params[] = $now;
2438         } else {
2439             $timedsql = "";
2440         }
2442         $sql = "SELECT f.id, COUNT(d.id) as dcount
2443                   FROM {forum} f
2444                        JOIN {forum_discussions} d ON d.forum = f.id
2445                  WHERE f.course = ?
2446                        $timedsql
2447               GROUP BY f.id";
2449         if ($counts = $DB->get_records_sql($sql, $params)) {
2450             foreach ($counts as $count) {
2451                 $counts[$count->id] = $count->dcount;
2452             }
2453             $cache[$course->id] = $counts;
2454         } else {
2455             $cache[$course->id] = array();
2456         }
2457     }
2459     if (empty($cache[$course->id][$forum->id])) {
2460         return 0;
2461     }
2463     $groupmode = groups_get_activity_groupmode($cm, $course);
2465     if ($groupmode != SEPARATEGROUPS) {
2466         return $cache[$course->id][$forum->id];
2467     }
2469     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2470         return $cache[$course->id][$forum->id];
2471     }
2473     require_once($CFG->dirroot.'/course/lib.php');
2475     $modinfo = get_fast_modinfo($course);
2476     if (is_null($modinfo->groups)) {
2477         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2478     }
2480     if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2481         $mygroups = $modinfo->groups[$cm->groupingid];
2482     } else {
2483         $mygroups = false; // Will be set below
2484     }
2486     // add all groups posts
2487     if (empty($mygroups)) {
2488         $mygroups = array(-1=>-1);
2489     } else {
2490         $mygroups[-1] = -1;
2491     }
2493     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2494     $params[] = $forum->id;
2496     if (!empty($CFG->forum_enabletimedposts)) {
2497         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2498         $params[] = $now;
2499         $params[] = $now;
2500     } else {
2501         $timedsql = "";
2502     }
2504     $sql = "SELECT COUNT(d.id)
2505               FROM {forum_discussions} d
2506              WHERE d.groupid $mygroups_sql AND d.forum = ?
2507                    $timedsql";
2509     return $DB->get_field_sql($sql, $params);
2512 /**
2513  * How many posts by other users are unrated by a given user in the given discussion?
2514  *
2515  * TODO: Is this function still used anywhere?
2516  *
2517  * @param int $discussionid
2518  * @param int $userid
2519  * @return mixed
2520  */
2521 function forum_count_unrated_posts($discussionid, $userid) {
2522     global $CFG, $DB;
2524     $sql = "SELECT COUNT(*) as num
2525               FROM {forum_posts}
2526              WHERE parent > 0
2527                AND discussion = :discussionid
2528                AND userid <> :userid";
2529     $params = array('discussionid' => $discussionid, 'userid' => $userid);
2530     $posts = $DB->get_record_sql($sql, $params);
2531     if ($posts) {
2532         $sql = "SELECT count(*) as num
2533                   FROM {forum_posts} p,
2534                        {rating} r
2535                  WHERE p.discussion = :discussionid AND
2536                        p.id = r.itemid AND
2537                        r.userid = userid AND
2538                        r.component = 'mod_forum' AND
2539                        r.ratingarea = 'post'";
2540         $rated = $DB->get_record_sql($sql, $params);
2541         if ($rated) {
2542             if ($posts->num > $rated->num) {
2543                 return $posts->num - $rated->num;
2544             } else {
2545                 return 0;    // Just in case there was a counting error
2546             }
2547         } else {
2548             return $posts->num;
2549         }
2550     } else {
2551         return 0;
2552     }
2555 /**
2556  * Get all discussions in a forum
2557  *
2558  * @global object
2559  * @global object
2560  * @global object
2561  * @uses CONTEXT_MODULE
2562  * @uses VISIBLEGROUPS
2563  * @param object $cm
2564  * @param string $forumsort
2565  * @param bool $fullpost
2566  * @param int $unused
2567  * @param int $limit
2568  * @param bool $userlastmodified
2569  * @param int $page
2570  * @param int $perpage
2571  * @return array
2572  */
2573 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2574     global $CFG, $DB, $USER;
2576     $timelimit = '';
2578     $now = round(time(), -2);
2579     $params = array($cm->instance);
2581     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2583     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2584         return array();
2585     }
2587     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2589         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2590             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2591             $params[] = $now;
2592             $params[] = $now;
2593             if (isloggedin()) {
2594                 $timelimit .= " OR d.userid = ?";
2595                 $params[] = $USER->id;
2596             }
2597             $timelimit .= ")";
2598         }
2599     }
2601     if ($limit > 0) {
2602         $limitfrom = 0;
2603         $limitnum  = $limit;
2604     } else if ($page != -1) {
2605         $limitfrom = $page*$perpage;
2606         $limitnum  = $perpage;
2607     } else {
2608         $limitfrom = 0;
2609         $limitnum  = 0;
2610     }
2612     $groupmode    = groups_get_activity_groupmode($cm);
2613     $currentgroup = groups_get_activity_group($cm);
2615     if ($groupmode) {
2616         if (empty($modcontext)) {
2617             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2618         }
2620         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2621             if ($currentgroup) {
2622                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2623                 $params[] = $currentgroup;
2624             } else {
2625                 $groupselect = "";
2626             }
2628         } else {
2629             //seprate groups without access all
2630             if ($currentgroup) {
2631                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2632                 $params[] = $currentgroup;
2633             } else {
2634                 $groupselect = "AND d.groupid = -1";
2635             }
2636         }
2637     } else {
2638         $groupselect = "";
2639     }
2642     if (empty($forumsort)) {
2643         $forumsort = "d.timemodified DESC";
2644     }
2645     if (empty($fullpost)) {
2646         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2647     } else {
2648         $postdata = "p.*";
2649     }
2651     if (empty($userlastmodified)) {  // We don't need to know this
2652         $umfields = "";
2653         $umtable  = "";
2654     } else {
2655         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2656         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2657     }
2659     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2660                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2661               FROM {forum_discussions} d
2662                    JOIN {forum_posts} p ON p.discussion = d.id
2663                    JOIN {user} u ON p.userid = u.id
2664                    $umtable
2665              WHERE d.forum = ? AND p.parent = 0
2666                    $timelimit $groupselect
2667           ORDER BY $forumsort";
2668     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2671 /**
2672  *
2673  * @global object
2674  * @global object
2675  * @global object
2676  * @uses CONTEXT_MODULE
2677  * @uses VISIBLEGROUPS
2678  * @param object $cm
2679  * @return array
2680  */
2681 function forum_get_discussions_unread($cm) {
2682     global $CFG, $DB, $USER;
2684     $now = round(time(), -2);
2685     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2687     $params = array();
2688     $groupmode    = groups_get_activity_groupmode($cm);
2689     $currentgroup = groups_get_activity_group($cm);
2691     if ($groupmode) {
2692         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2694         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2695             if ($currentgroup) {
2696                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2697                 $params['currentgroup'] = $currentgroup;
2698             } else {
2699                 $groupselect = "";
2700             }
2702         } else {
2703             //separate groups without access all
2704             if ($currentgroup) {
2705                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2706                 $params['currentgroup'] = $currentgroup;
2707             } else {
2708                 $groupselect = "AND d.groupid = -1";
2709             }
2710         }
2711     } else {
2712         $groupselect = "";
2713     }
2715     if (!empty($CFG->forum_enabletimedposts)) {
2716         $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2717         $params['now1'] = $now;
2718         $params['now2'] = $now;
2719     } else {
2720         $timedsql = "";
2721     }
2723     $sql = "SELECT d.id, COUNT(p.id) AS unread
2724               FROM {forum_discussions} d
2725                    JOIN {forum_posts} p     ON p.discussion = d.id
2726                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2727              WHERE d.forum = {$cm->instance}
2728                    AND p.modified >= :cutoffdate AND r.id is NULL
2729                    $groupselect
2730                    $timedsql
2731           GROUP BY d.id";
2732     $params['cutoffdate'] = $cutoffdate;
2734     if ($unreads = $DB->get_records_sql($sql, $params)) {
2735         foreach ($unreads as $unread) {
2736             $unreads[$unread->id] = $unread->unread;
2737         }
2738         return $unreads;
2739     } else {
2740         return array();
2741     }
2744 /**
2745  * @global object
2746  * @global object
2747  * @global object
2748  * @uses CONEXT_MODULE
2749  * @uses VISIBLEGROUPS
2750  * @param object $cm
2751  * @return array
2752  */
2753 function forum_get_discussions_count($cm) {
2754     global $CFG, $DB, $USER;
2756     $now = round(time(), -2);
2757     $params = array($cm->instance);
2758     $groupmode    = groups_get_activity_groupmode($cm);
2759     $currentgroup = groups_get_activity_group($cm);
2761     if ($groupmode) {
2762         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2764         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2765             if ($currentgroup) {
2766                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2767                 $params[] = $currentgroup;
2768             } else {
2769                 $groupselect = "";
2770             }
2772         } else {
2773             //seprate groups without access all
2774             if ($currentgroup) {
2775                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2776                 $params[] = $currentgroup;
2777             } else {
2778                 $groupselect = "AND d.groupid = -1";
2779             }
2780         }
2781     } else {
2782         $groupselect = "";
2783     }
2785     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2787     $timelimit = "";
2789     if (!empty($CFG->forum_enabletimedposts)) {
2791         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2793         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2794             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2795             $params[] = $now;
2796             $params[] = $now;
2797             if (isloggedin()) {
2798                 $timelimit .= " OR d.userid = ?";
2799                 $params[] = $USER->id;
2800             }
2801             $timelimit .= ")";
2802         }
2803     }
2805     $sql = "SELECT COUNT(d.id)
2806               FROM {forum_discussions} d
2807                    JOIN {forum_posts} p ON p.discussion = d.id
2808              WHERE d.forum = ? AND p.parent = 0
2809                    $groupselect $timelimit";
2811     return $DB->get_field_sql($sql, $params);
2815 /**
2816  * Get all discussions started by a particular user in a course (or group)
2817  * This function no longer used ...
2818  *
2819  * @todo Remove this function if no longer used
2820  * @global object
2821  * @global object
2822  * @param int $courseid
2823  * @param int $userid
2824  * @param int $groupid
2825  * @return array
2826  */
2827 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2828     global $CFG, $DB;
2829     $params = array($courseid, $userid);
2830     if ($groupid) {
2831         $groupselect = " AND d.groupid = ? ";
2832         $params[] = $groupid;
2833     } else  {
2834         $groupselect = "";
2835     }
2837     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2838                                    f.type as forumtype, f.name as forumname, f.id as forumid
2839                               FROM {forum_discussions} d,
2840                                    {forum_posts} p,
2841                                    {user} u,
2842                                    {forum} f
2843                              WHERE d.course = ?
2844                                AND p.discussion = d.id
2845                                AND p.parent = 0
2846                                AND p.userid = u.id
2847                                AND u.id = ?
2848                                AND d.forum = f.id $groupselect
2849                           ORDER BY p.created DESC", $params);
2852 /**
2853  * Get the list of potential subscribers to a forum.
2854  *
2855  * @param object $forumcontext the forum context.
2856  * @param integer $groupid the id of a group, or 0 for all groups.
2857  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2858  * @param string $sort sort order. As for get_users_by_capability.
2859  * @return array list of users.
2860  */
2861 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2862     global $DB;
2864     // only active enrolled users or everybody on the frontpage
2865     list($esql, $params) = get_enrolled_sql($forumcontext, 'mod/forum:allowforcesubscribe', $groupid, true);
2867     $sql = "SELECT $fields
2868               FROM {user} u
2869               JOIN ($esql) je ON je.id = u.id";
2870     if ($sort) {
2871         $sql = "$sql ORDER BY $sort";
2872     } else {
2873         $sql = "$sql ORDER BY u.lastname ASC, u.firstname ASC";
2874     }
2876     return $DB->get_records_sql($sql, $params);
2879 /**
2880  * Returns list of user objects that are subscribed to this forum
2881  *
2882  * @global object
2883  * @global object
2884  * @param object $course the course
2885  * @param forum $forum the forum
2886  * @param integer $groupid group id, or 0 for all.
2887  * @param object $context the forum context, to save re-fetching it where possible.
2888  * @param string $fields requested user fields (with "u." table prefix)
2889  * @return array list of users.
2890  */
2891 function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
2892     global $CFG, $DB;
2894     if (empty($fields)) {
2895         $fields ="u.id,
2896                   u.username,
2897                   u.firstname,
2898                   u.lastname,
2899                   u.maildisplay,
2900                   u.mailformat,
2901                   u.maildigest,
2902                   u.imagealt,
2903                   u.email,
2904                   u.emailstop,
2905                   u.city,
2906                   u.country,
2907                   u.lastaccess,
2908                   u.lastlogin,
2909                   u.picture,
2910                   u.timezone,
2911                   u.theme,
2912                   u.lang,
2913                   u.trackforums,
2914                   u.mnethostid";
2915     }
2917     if (empty($context)) {
2918         $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2919         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2920     }
2922     if (forum_is_forcesubscribed($forum)) {
2923         $results = forum_get_potential_subscribers($context, $groupid, $fields, "u.email ASC");
2925     } else {
2926         // only active enrolled users or everybody on the frontpage
2927         list($esql, $params) = get_enrolled_sql($context, '', $groupid, true);
2928         $params['forumid'] = $forum->id;
2929         $results = $DB->get_records_sql("SELECT $fields
2930                                            FROM {user} u
2931                                            JOIN ($esql) je ON je.id = u.id
2932                                            JOIN {forum_subscriptions} s ON s.userid = u.id
2933                                           WHERE s.forum = :forumid
2934                                        ORDER BY u.email ASC", $params);
2935     }
2937     // Guest user should never be subscribed to a forum.
2938     unset($results[$CFG->siteguest]);
2940     return $results;
2945 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2948 /**
2949  * @global object
2950  * @global object
2951  * @param int $courseid
2952  * @param string $type
2953  */
2954 function forum_get_course_forum($courseid, $type) {
2955 // How to set up special 1-per-course forums
2956     global $CFG, $DB, $OUTPUT;
2958     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2959         // There should always only be ONE, but with the right combination of
2960         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2961         foreach ($forums as $forum) {
2962             return $forum;   // ie the first one
2963         }
2964     }
2966     // Doesn't exist, so create one now.
2967     $forum = new stdClass();
2968     $forum->course = $courseid;
2969     $forum->type = "$type";
2970     switch ($forum->type) {
2971         case "news":
2972             $forum->name  = get_string("namenews", "forum");
2973             $forum->intro = get_string("intronews", "forum");
2974             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2975             $forum->assessed = 0;
2976             if ($courseid == SITEID) {
2977                 $forum->name  = get_string("sitenews");
2978                 $forum->forcesubscribe = 0;
2979             }
2980             break;
2981         case "social":
2982             $forum->name  = get_string("namesocial", "forum");
2983             $forum->intro = get_string("introsocial", "forum");
2984             $forum->assessed = 0;
2985             $forum->forcesubscribe = 0;
2986             break;
2987         case "blog":
2988             $forum->name = get_string('blogforum', 'forum');
2989             $forum->intro = get_string('introblog', 'forum');
2990             $forum->assessed = 0;
2991             $forum->forcesubscribe = 0;
2992             break;
2993         default:
2994             echo $OUTPUT->notification("That forum type doesn't exist!");
2995             return false;
2996             break;
2997     }
2999     $forum->timemodified = time();
3000     $forum->id = $DB->insert_record("forum", $forum);
3002     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
3003         echo $OUTPUT->notification("Could not find forum module!!");
3004         return false;
3005     }
3006     $mod = new stdClass();
3007     $mod->course = $courseid;
3008     $mod->module = $module->id;
3009     $mod->instance = $forum->id;
3010     $mod->section = 0;
3011     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
3012         echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
3013         return false;
3014     }
3015     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
3016         echo $OUTPUT->notification("Could not add the new course module to that section");
3017         return false;
3018     }
3019     $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule));
3021     include_once("$CFG->dirroot/course/lib.php");
3022     rebuild_course_cache($courseid);
3024     return $DB->get_record("forum", array("id" => "$forum->id"));
3028 /**
3029  * Given the data about a posting, builds up the HTML to display it and
3030  * returns the HTML in a string.  This is designed for sending via HTML email.
3031  *
3032  * @global object
3033  * @param object $course
3034  * @param object $cm
3035  * @param object $forum
3036  * @param object $discussion
3037  * @param object $post
3038  * @param object $userform
3039  * @param object $userto
3040  * @param bool $ownpost
3041  * @param bool $reply
3042  * @param bool $link
3043  * @param bool $rate
3044  * @param string $footer
3045  * @return string
3046  */
3047 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
3048                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
3050     global $CFG, $OUTPUT;
3052     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3054     if (!isset($userto->viewfullnames[$forum->id])) {
3055         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3056     } else {
3057         $viewfullnames = $userto->viewfullnames[$forum->id];
3058     }
3060     // add absolute file links
3061     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3063     // format the post body
3064     $options = new stdClass();
3065     $options->para = true;
3066     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3068     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3070     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3071     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3072     $output .= '</td>';
3074     if ($post->parent) {
3075         $output .= '<td class="topic">';
3076     } else {
3077         $output .= '<td class="topic starter">';
3078     }
3079     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3081     $fullname = fullname($userfrom, $viewfullnames);
3082     $by = new stdClass();
3083     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3084     $by->date = userdate($post->modified, '', $userto->timezone);
3085     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3087     $output .= '</td></tr>';
3089     $output .= '<tr><td class="left side" valign="top">';
3091     if (isset($userfrom->groups)) {
3092         $groups = $userfrom->groups[$forum->id];
3093     } else {
3094         $groups = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3095     }
3097     if ($groups) {
3098         $output .= print_group_picture($groups, $course->id, false, true, true);
3099     } else {
3100         $output .= '&nbsp;';
3101     }
3103     $output .= '</td><td class="content">';
3105     $attachments = forum_print_attachments($post, $cm, 'html');
3106     if ($attachments !== '') {
3107         $output .= '<div class="attachments">';
3108         $output .= $attachments;
3109         $output .= '</div>';
3110     }
3112     $output .= $formattedtext;
3114 // Commands
3115     $commands = array();
3117     if ($post->parent) {
3118         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3119                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3120     }
3122     if ($reply) {
3123         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3124                       get_string('reply', 'forum').'</a>';
3125     }
3127     $output .= '<div class="commands">';
3128     $output .= implode(' | ', $commands);
3129     $output .= '</div>';
3131 // Context link to post if required
3132     if ($link) {
3133         $output .= '<div class="link">';
3134         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3135                      get_string('postincontext', 'forum').'</a>';
3136         $output .= '</div>';
3137     }
3139     if ($footer) {
3140         $output .= '<div class="footer">'.$footer.'</div>';
3141     }
3142     $output .= '</td></tr></table>'."\n\n";
3144     return $output;
3147 /**
3148  * Print a forum post
3149  *
3150  * @global object
3151  * @global object
3152  * @uses FORUM_MODE_THREADED
3153  * @uses PORTFOLIO_FORMAT_PLAINHTML
3154  * @uses PORTFOLIO_FORMAT_FILE
3155  * @uses PORTFOLIO_FORMAT_RICHHTML
3156  * @uses PORTFOLIO_ADD_TEXT_LINK
3157  * @uses CONTEXT_MODULE
3158  * @param object $post The post to print.
3159  * @param object $discussion
3160  * @param object $forum
3161  * @param object $cm
3162  * @param object $course
3163  * @param boolean $ownpost Whether this post belongs to the current user.
3164  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3165  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3166  * @param string $footer Extra stuff to print after the message.
3167  * @param string $highlight Space-separated list of terms to highlight.
3168  * @param int $post_read true, false or -99. If we already know whether this user
3169  *          has read this post, pass that in, otherwise, pass in -99, and this
3170  *          function will work it out.
3171  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3172  *          the current user can't see this post, if this argument is true
3173  *          (the default) then print a dummy 'you can't see this post' post.
3174  *          If false, don't output anything at all.
3175  * @param bool|null $istracked
3176  * @return void
3177  */
3178 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3179                           $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3180     global $USER, $CFG, $OUTPUT;
3182     require_once($CFG->libdir . '/filelib.php');
3184     // String cache
3185     static $str;
3187     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3189     $post->course = $course->id;
3190     $post->forum  = $forum->id;
3191     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3193     // caching
3194     if (!isset($cm->cache)) {
3195         $cm->cache = new stdClass;
3196     }
3198     if (!isset($cm->cache->caps)) {
3199         $cm->cache->caps = array();
3200         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3201         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3202         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3203         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3204         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3205         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3206         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3207         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3208         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3209     }
3211     if (!isset($cm->uservisible)) {
3212         $cm->uservisible = coursemodule_visible_for_user($cm);
3213     }
3215     if ($istracked && is_null($postisread)) {
3216         $postisread = forum_tp_is_post_read($USER->id, $post);
3217     }
3219     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3220         $output = '';
3221         if (!$dummyifcantsee) {
3222             if ($return) {
3223                 return $output;
3224             }
3225             echo $output;
3226             return;
3227         }
3228         $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3229         $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'));
3230         $output .= html_writer::start_tag('div', array('class'=>'row header'));
3231         $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3232         if ($post->parent) {
3233             $output .= html_writer::start_tag('div', array('class'=>'topic'));
3234         } else {
3235             $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3236         }
3237         $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class'=>'subject')); // Subject
3238         $output .= html_writer::tag('div', get_string('forumauthorhidden','forum'), array('class'=>'author')); // author
3239         $output .= html_writer::end_tag('div');
3240         $output .= html_writer::end_tag('div'); // row
3241         $output .= html_writer::start_tag('div', array('class'=>'row'));
3242         $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3243         $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3244         $output .= html_writer::end_tag('div'); // row
3245         $output .= html_writer::end_tag('div'); // forumpost
3247         if ($return) {
3248             return $output;
3249         }
3250         echo $output;
3251         return;
3252     }
3254     if (empty($str)) {
3255         $str = new stdClass;
3256         $str->edit         = get_string('edit', 'forum');
3257         $str->delete       = get_string('delete', 'forum');
3258         $str->reply        = get_string('reply', 'forum');
3259         $str->parent       = get_string('parent', 'forum');
3260         $str->pruneheading = get_string('pruneheading', 'forum');
3261         $str->prune        = get_string('prune', 'forum');
3262         $str->displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3263         $str->markread     = get_string('markread', 'forum');
3264         $str->markunread   = get_string('markunread', 'forum');
3265     }
3267     $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3269     // Build an object that represents the posting user
3270     $postuser = new stdClass;
3271     $postuser->id        = $post->userid;
3272     $postuser->firstname = $post->firstname;
3273     $postuser->lastname  = $post->lastname;
3274     $postuser->imagealt  = $post->imagealt;
3275     $postuser->picture   = $post->picture;
3276     $postuser->email     = $post->email;
3277     // Some handy things for later on
3278     $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3279     $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3281     // Prepare the groups the posting user belongs to
3282     if (isset($cm->cache->usersgroups)) {
3283         $groups = array();
3284         if (isset($cm->cache->usersgroups[$post->userid])) {
3285             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3286                 $groups[$gid] = $cm->cache->groups[$gid];
3287             }
3288         }
3289     } else {
3290         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3291     }
3293     // Prepare the attachements for the post, files then images
3294     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3296     // Determine if we need to shorten this post
3297     $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3300     // Prepare an array of commands
3301     $commands = array();
3303     // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3304     // Don't display the mark read / unread controls in this case.
3305     if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3306         $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3307         $text = $str->markunread;
3308         if (!$postisread) {
3309             $url->param('mark', 'read');
3310             $text = $str->markread;
3311         }
3312         if ($str->displaymode == FORUM_MODE_THREADED) {
3313             $url->param('parent', $post->parent);
3314         } else {
3315             $url->set_anchor('p'.$post->id);
3316         }
3317         $commands[] = array('url'=>$url, 'text'=>$text);
3318     }
3320     // Zoom in to the parent specifically
3321     if ($post->parent) {
3322         $url = new moodle_url($discussionlink);
3323         if ($str->displaymode == FORUM_MODE_THREADED) {
3324             $url->param('parent', $post->parent);
3325         } else {
3326             $url->set_anchor('p'.$post->parent);
3327         }
3328         $commands[] = array('url'=>$url, 'text'=>$str->parent);
3329     }
3331     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3332     $age = time() - $post->created;
3333     if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3334         $age = 0;
3335     }
3337     if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
3338         if (has_capability('moodle/course:manageactivities', $modcontext)) {
3339             // The first post in single simple is the forum description.
3340             $commands[] = array('url'=>new moodle_url('/course/modedit.php', array('update'=>$cm->id, 'sesskey'=>sesskey(), 'return'=>1)), 'text'=>$str->edit);
3341         }
3342     } else if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3343         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3344     }
3346     if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3347         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3348     }
3350     if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
3351         // Do not allow deleting of first post in single simple type.
3352     } else if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3353         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3354     }
3356     if ($reply) {
3357         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php#mform1', array('reply'=>$post->id)), 'text'=>$str->reply);
3358     }
3360     if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
3361         $p = array('postid' => $post->id);
3362         require_once($CFG->libdir.'/portfoliolib.php');
3363         $button = new portfolio_add_button();
3364         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php');
3365         if (empty($attachments)) {
3366             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3367         } else {
3368             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3369         }
3371         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3372         if (!empty($porfoliohtml)) {
3373             $commands[] = $porfoliohtml;
3374         }
3375     }
3376     // Finished building commands
3379     // Begin output
3381     $output  = '';
3383     if ($istracked) {
3384         if ($postisread) {
3385             $forumpostclass = ' read';
3386         } else {
3387             $forumpostclass = ' unread';
3388             $output .= html_writer::tag('a', '', array('name'=>'unread'));
3389         }
3390     } else {
3391         // ignore trackign status if not tracked or tracked param missing
3392         $forumpostclass = '';
3393     }
3395     $topicclass = '';
3396     if (empty($post->parent)) {
3397         $topicclass = ' firstpost starter';
3398     }
3400     $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3401     $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass));
3402     $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3403     $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3404     $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3405     $output .= html_writer::end_tag('div');
3408     $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3410     $postsubject = $post->subject;
3411     if (empty($post->subjectnoformat)) {
3412         $postsubject = format_string($postsubject);
3413     }
3414     $output .= html_writer::tag('div', $postsubject, array('class'=>'subject'));
3416     $by = new stdClass();
3417     $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3418     $by->date = userdate($post->modified);
3419     $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author'));
3421     $output .= html_writer::end_tag('div'); //topic
3422     $output .= html_writer::end_tag('div'); //row
3424     $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3425     $output .= html_writer::start_tag('div', array('class'=>'left'));
3427     $groupoutput = '';
3428     if ($groups) {
3429         $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3430     }
3431     if (empty($groupoutput)) {
3432         $groupoutput = '&nbsp;';
3433     }
3434     $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3436     $output .= html_writer::end_tag('div'); //left side
3437     $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3438     $output .= html_writer::start_tag('div', array('class'=>'content'));
3439     if (!empty($attachments)) {
3440         $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3441     }
3443     $options = new stdClass;
3444     $options->para    = false;
3445     $options->trusted = $post->messagetrust;
3446     $options->context = $modcontext;
3447     if ($shortenpost) {
3448         // Prepare shortened version
3449         $postclass    = 'shortenedpost';
3450         $postcontent  = format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3451         $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
3452         $postcontent .= html_writer::tag('span', '('.get_string('numwords', 'moodle', count_words(strip_tags($post->message))).')...', array('class'=>'post-word-count'));
3453     } else {
3454         // Prepare whole post
3455         $postclass    = 'fullpost';
3456         $postcontent  = format_text($post->message, $post->messageformat, $options, $course->id);
3457         if (!empty($highlight)) {
3458             $postcontent = highlight($highlight, $postcontent);
3459         }
3460         $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
3461     }
3462     // Output the post content
3463     $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
3464     $output .= html_writer::end_tag('div'); // Content
3465     $output .= html_writer::end_tag('div'); // Content mask
3466     $output .= html_writer::end_tag('div'); // Row
3468     $output .= html_writer::start_tag('div', array('class'=>'row side'));
3469     $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
3470     $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
3472     // Output ratings
3473     if (!empty($post->rating)) {
3474         $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
3475     }
3477     // Output the commands
3478     $commandhtml = array();
3479     foreach ($commands as $command) {
3480         if (is_array($command)) {
3481             $commandhtml[] = html_writer::link($command['url'], $command['text']);
3482         } else {
3483             $commandhtml[] = $command;
3484         }
3485     }
3486     $output .= html_writer::tag('div', implode(' | ', $commandhtml), array('class'=>'commands'));
3488     // Output link to post if required
3489     if ($link) {
3490         if ($post->replies == 1) {