b515244f3b7ea91a309a8ae0784ca2a84877a35c
[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                 $mailresult = message_send($eventdata);
724                 if (!$mailresult){
725                     mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
726                          " ($userto->email) .. not trying again.");
727                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
728                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
729                     $errorcount[$post->id]++;
730                 } else {
731                     $mailcount[$post->id]++;
733                 // Mark post as read if forum_usermarksread is set off
734                     if (!$CFG->forum_usermarksread) {
735                         $userto->markposts[$post->id] = $post->id;
736                     }
737                 }
739                 mtrace('post '.$post->id. ': '.$post->subject);
740             }
742             // mark processed posts as read
743             forum_tp_mark_posts_read($userto, $userto->markposts);
744             unset($userto);
745         }
746     }
748     if ($posts) {
749         foreach ($posts as $post) {
750             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
751             if ($errorcount[$post->id]) {
752                 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
753             }
754         }
755     }
757     // release some memory
758     unset($subscribedusers);
759     unset($mailcount);
760     unset($errorcount);
762     cron_setup_user();
764     $sitetimezone = $CFG->timezone;
766     // Now see if there are any digest mails waiting to be sent, and if we should send them
768     mtrace('Starting digest processing...');
770     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
772     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
773         set_config('digestmailtimelast', 0);
774     }
776     $timenow = time();
777     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
779     // Delete any really old ones (normally there shouldn't be any)
780     $weekago = $timenow - (7 * 24 * 3600);
781     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
782     mtrace ('Cleaned old digest records');
784     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
786         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
788         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
790         if ($digestposts_rs->valid()) {
792             // We have work to do
793             $usermailcount = 0;
795             //caches - reuse the those filled before too
796             $discussionposts = array();
797             $userdiscussions = array();
799             foreach ($digestposts_rs as $digestpost) {
800                 if (!isset($posts[$digestpost->postid])) {
801                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
802                         $posts[$digestpost->postid] = $post;
803                     } else {
804                         continue;
805                     }
806                 }
807                 $discussionid = $digestpost->discussionid;
808                 if (!isset($discussions[$discussionid])) {
809                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
810                         $discussions[$discussionid] = $discussion;
811                     } else {
812                         continue;
813                     }
814                 }
815                 $forumid = $discussions[$discussionid]->forum;
816                 if (!isset($forums[$forumid])) {
817                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
818                         $forums[$forumid] = $forum;
819                     } else {
820                         continue;
821                     }
822                 }
824                 $courseid = $forums[$forumid]->course;
825                 if (!isset($courses[$courseid])) {
826                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
827                         $courses[$courseid] = $course;
828                     } else {
829                         continue;
830                     }
831                 }
833                 if (!isset($coursemodules[$forumid])) {
834                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
835                         $coursemodules[$forumid] = $cm;
836                     } else {
837                         continue;
838                     }
839                 }
840                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
841                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
842             }
843             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
845             // Data collected, start sending out emails to each user
846             foreach ($userdiscussions as $userid => $thesediscussions) {
848                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
850                 cron_setup_user();
852                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
854                 // First of all delete all the queue entries for this user
855                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
857                 // Init user caches - we keep the cache for one cycle only,
858                 // otherwise it would unnecessarily consume memory.
859                 if (array_key_exists($userid, $users) and isset($users[$userid]->username)) {
860                     $userto = clone($users[$userid]);
861                 } else {
862                     $userto = $DB->get_record('user', array('id' => $userid));
863                     forum_cron_minimise_user_record($userto);
864                 }
865                 $userto->viewfullnames = array();
866                 $userto->canpost       = array();
867                 $userto->markposts     = array();
869                 // Override the language and timezone of the "current" user, so that
870                 // mail is customised for the receiver.
871                 cron_setup_user($userto);
873                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
875                 $headerdata = new stdClass();
876                 $headerdata->sitename = format_string($site->fullname, true);
877                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
879                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
880                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
882                 $posthtml = "<head>";
883 /*                foreach ($CFG->stylesheets as $stylesheet) {
884                     //TODO: MDL-21120
885                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
886                 }*/
887                 $posthtml .= "</head>\n<body id=\"email\">\n";
888                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
890                 foreach ($thesediscussions as $discussionid) {
892                     @set_time_limit(120);   // to be reset for each post
894                     $discussion = $discussions[$discussionid];
895                     $forum      = $forums[$discussion->forum];
896                     $course     = $courses[$forum->course];
897                     $cm         = $coursemodules[$forum->id];
899                     //override language
900                     cron_setup_user($userto, $course);
902                     // Fill caches
903                     if (!isset($userto->viewfullnames[$forum->id])) {
904                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
905                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
906                     }
907                     if (!isset($userto->canpost[$discussion->id])) {
908                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
909                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
910                     }
912                     $strforums      = get_string('forums', 'forum');
913                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
914                     $canreply       = $userto->canpost[$discussion->id];
915                     $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
917                     $posttext .= "\n \n";
918                     $posttext .= '=====================================================================';
919                     $posttext .= "\n \n";
920                     $posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
921                     if ($discussion->name != $forum->name) {
922                         $posttext  .= " -> ".format_string($discussion->name,true);
923                     }
924                     $posttext .= "\n";
926                     $posthtml .= "<p><font face=\"sans-serif\">".
927                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
928                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
929                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
930                     if ($discussion->name == $forum->name) {
931                         $posthtml .= "</font></p>";
932                     } else {
933                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
934                     }
935                     $posthtml .= '<p>';
937                     $postsarray = $discussionposts[$discussionid];
938                     sort($postsarray);
940                     foreach ($postsarray as $postid) {
941                         $post = $posts[$postid];
943                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
944                             $userfrom = $users[$post->userid];
945                             if (!isset($userfrom->idnumber)) {
946                                 $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
947                                 forum_cron_minimise_user_record($userfrom);
948                             }
950                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
951                             forum_cron_minimise_user_record($userfrom);
952                             if ($userscount <= FORUM_CRON_USER_CACHE) {
953                                 $userscount++;
954                                 $users[$userfrom->id] = $userfrom;
955                             }
957                         } else {
958                             mtrace('Could not find user '.$post->userid);
959                             continue;
960                         }
962                         if (!isset($userfrom->groups[$forum->id])) {
963                             if (!isset($userfrom->groups)) {
964                                 $userfrom->groups = array();
965                                 if (isset($users[$userfrom->id])) {
966                                     $users[$userfrom->id]->groups = array();
967                                 }
968                             }
969                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
970                             if (isset($users[$userfrom->id])) {
971                                 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
972                             }
973                         }
975                         $userfrom->customheaders = array ("Precedence: Bulk");
977                         if ($userto->maildigest == 2) {
978                             // Subjects only
979                             $by = new stdClass();
980                             $by->name = fullname($userfrom);
981                             $by->date = userdate($post->modified);
982                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
983                             $posttext .= "\n---------------------------------------------------------------------";
985                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
986                             $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>';
988                         } else {
989                             // The full treatment
990                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
991                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
993                         // Create an array of postid's for this user to mark as read.
994                             if (!$CFG->forum_usermarksread) {
995                                 $userto->markposts[$post->id] = $post->id;
996                             }
997                         }
998                     }
999                     if ($canunsubscribe) {
1000                         $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>";
1001                     } else {
1002                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
1003                     }
1004                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
1005                 }
1006                 $posthtml .= '</body>';
1008                 if (empty($userto->mailformat) || $userto->mailformat != 1) {
1009                     // This user DOESN'T want to receive HTML
1010                     $posthtml = '';
1011                 }
1013                 $attachment = $attachname='';
1014                 $usetrueaddress = true;
1015                 // Directly email forum digests rather than sending them via messaging, use the
1016                 // site shortname as 'from name', the noreply address will be used by email_to_user.
1017                 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname, $usetrueaddress, $CFG->forum_replytouser);
1019                 if (!$mailresult) {
1020                     mtrace("ERROR!");
1021                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
1022                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
1023                 } else {
1024                     mtrace("success.");
1025                     $usermailcount++;
1027                     // Mark post as read if forum_usermarksread is set off
1028                     forum_tp_mark_posts_read($userto, $userto->markposts);
1029                 }
1030             }
1031         }
1032     /// We have finishied all digest emails, update $CFG->digestmailtimelast
1033         set_config('digestmailtimelast', $timenow);
1034     }
1036     cron_setup_user();
1038     if (!empty($usermailcount)) {
1039         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
1040     }
1042     if (!empty($CFG->forum_lastreadclean)) {
1043         $timenow = time();
1044         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
1045             set_config('forum_lastreadclean', $timenow);
1046             mtrace('Removing old forum read tracking info...');
1047             forum_tp_clean_read_records();
1048         }
1049     } else {
1050         set_config('forum_lastreadclean', time());
1051     }
1054     return true;
1057 /**
1058  * Builds and returns the body of the email notification in plain text.
1059  *
1060  * @global object
1061  * @global object
1062  * @uses CONTEXT_MODULE
1063  * @param object $course
1064  * @param object $cm
1065  * @param object $forum
1066  * @param object $discussion
1067  * @param object $post
1068  * @param object $userfrom
1069  * @param object $userto
1070  * @param boolean $bare
1071  * @return string The email body in plain text format.
1072  */
1073 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
1074     global $CFG, $USER;
1076     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
1078     if (!isset($userto->viewfullnames[$forum->id])) {
1079         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
1080     } else {
1081         $viewfullnames = $userto->viewfullnames[$forum->id];
1082     }
1084     if (!isset($userto->canpost[$discussion->id])) {
1085         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
1086     } else {
1087         $canreply = $userto->canpost[$discussion->id];
1088     }
1090     $by = New stdClass;
1091     $by->name = fullname($userfrom, $viewfullnames);
1092     $by->date = userdate($post->modified, "", $userto->timezone);
1094     $strbynameondate = get_string('bynameondate', 'forum', $by);
1096     $strforums = get_string('forums', 'forum');
1098     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1100     $posttext = '';
1102     if (!$bare) {
1103         $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
1104         $posttext  = "$shortname -> $strforums -> ".format_string($forum->name,true);
1106         if ($discussion->name != $forum->name) {
1107             $posttext  .= " -> ".format_string($discussion->name,true);
1108         }
1109     }
1111     // add absolute file links
1112     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
1114     $posttext .= "\n---------------------------------------------------------------------\n";
1115     $posttext .= format_string($post->subject,true);
1116     if ($bare) {
1117         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1118     }
1119     $posttext .= "\n".$strbynameondate."\n";
1120     $posttext .= "---------------------------------------------------------------------\n";
1121     $posttext .= format_text_email($post->message, $post->messageformat);
1122     $posttext .= "\n\n";
1123     $posttext .= forum_print_attachments($post, $cm, "text");
1125     if (!$bare && $canreply) {
1126         $posttext .= "---------------------------------------------------------------------\n";
1127         $posttext .= get_string("postmailinfo", "forum", $shortname)."\n";
1128         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1129     }
1130     if (!$bare && $canunsubscribe) {
1131         $posttext .= "\n---------------------------------------------------------------------\n";
1132         $posttext .= get_string("unsubscribe", "forum");
1133         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1134     }
1136     return $posttext;
1139 /**
1140  * Builds and returns the body of the email notification in html format.
1141  *
1142  * @global object
1143  * @param object $course
1144  * @param object $cm
1145  * @param object $forum
1146  * @param object $discussion
1147  * @param object $post
1148  * @param object $userfrom
1149  * @param object $userto
1150  * @return string The email text in HTML format
1151  */
1152 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1153     global $CFG;
1155     if ($userto->mailformat != 1) {  // Needs to be HTML
1156         return '';
1157     }
1159     if (!isset($userto->canpost[$discussion->id])) {
1160         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course);
1161     } else {
1162         $canreply = $userto->canpost[$discussion->id];
1163     }
1165     $strforums = get_string('forums', 'forum');
1166     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1167     $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
1169     $posthtml = '<head>';
1170 /*    foreach ($CFG->stylesheets as $stylesheet) {
1171         //TODO: MDL-21120
1172         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1173     }*/
1174     $posthtml .= '</head>';
1175     $posthtml .= "\n<body id=\"email\">\n\n";
1177     $posthtml .= '<div class="navbar">'.
1178     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$shortname.'</a> &raquo; '.
1179     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1180     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1181     if ($discussion->name == $forum->name) {
1182         $posthtml .= '</div>';
1183     } else {
1184         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1185                      format_string($discussion->name,true).'</a></div>';
1186     }
1187     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1189     if ($canunsubscribe) {
1190         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1191                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1192                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1193     }
1195     $posthtml .= '</body>';
1197     return $posthtml;
1201 /**
1202  *
1203  * @param object $course
1204  * @param object $user
1205  * @param object $mod TODO this is not used in this function, refactor
1206  * @param object $forum
1207  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1208  */
1209 function forum_user_outline($course, $user, $mod, $forum) {
1210     global $CFG;
1211     require_once("$CFG->libdir/gradelib.php");
1212     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1213     if (empty($grades->items[0]->grades)) {
1214         $grade = false;
1215     } else {
1216         $grade = reset($grades->items[0]->grades);
1217     }
1219     $count = forum_count_user_posts($forum->id, $user->id);
1221     if ($count && $count->postcount > 0) {
1222         $result = new stdClass();
1223         $result->info = get_string("numposts", "forum", $count->postcount);
1224         $result->time = $count->lastpost;
1225         if ($grade) {
1226             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1227         }
1228         return $result;
1229     } else if ($grade) {
1230         $result = new stdClass();
1231         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1233         //datesubmitted == time created. dategraded == time modified or time overridden
1234         //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
1235         //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
1236         if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1237             $result->time = $grade->dategraded;
1238         } else {
1239             $result->time = $grade->datesubmitted;
1240         }
1242         return $result;
1243     }
1244     return NULL;
1248 /**
1249  * @global object
1250  * @global object
1251  * @param object $coure
1252  * @param object $user
1253  * @param object $mod
1254  * @param object $forum
1255  */
1256 function forum_user_complete($course, $user, $mod, $forum) {
1257     global $CFG,$USER, $OUTPUT;
1258     require_once("$CFG->libdir/gradelib.php");
1260     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1261     if (!empty($grades->items[0]->grades)) {
1262         $grade = reset($grades->items[0]->grades);
1263         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1264         if ($grade->str_feedback) {
1265             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1266         }
1267     }
1269     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1271         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1272             print_error('invalidcoursemodule');
1273         }
1274         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1276         foreach ($posts as $post) {
1277             if (!isset($discussions[$post->discussion])) {
1278                 continue;
1279             }
1280             $discussion = $discussions[$post->discussion];
1282             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1283         }
1284     } else {
1285         echo "<p>".get_string("noposts", "forum")."</p>";
1286     }
1294 /**
1295  * @global object
1296  * @global object
1297  * @global object
1298  * @param array $courses
1299  * @param array $htmlarray
1300  */
1301 function forum_print_overview($courses,&$htmlarray) {
1302     global $USER, $CFG, $DB, $SESSION;
1304     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1305         return array();
1306     }
1308     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1309         return;
1310     }
1312     // Courses to search for new posts
1313     $coursessqls = array();
1314     $params = array();
1315     foreach ($courses as $course) {
1317         // If the user has never entered into the course all posts are pending
1318         if ($course->lastaccess == 0) {
1319             $coursessqls[] = '(f.course = ?)';
1320             $params[] = $course->id;
1322         // Only posts created after the course last access
1323         } else {
1324             $coursessqls[] = '(f.course = ? AND p.created > ?)';
1325             $params[] = $course->id;
1326             $params[] = $course->lastaccess;
1327         }
1328     }
1329     $params[] = $USER->id;
1330     $coursessql = implode(' OR ', $coursessqls);
1332     $sql = "SELECT f.id, COUNT(*) as count "
1333                 .'FROM {forum} f '
1334                 .'JOIN {forum_discussions} d ON d.forum  = f.id '
1335                 .'JOIN {forum_posts} p ON p.discussion = d.id '
1336                 ."WHERE ($coursessql) "
1337                 .'AND p.userid != ? '
1338                 .'GROUP BY f.id';
1340     if (!$new = $DB->get_records_sql($sql, $params)) {
1341         $new = array(); // avoid warnings
1342     }
1344     // also get all forum tracking stuff ONCE.
1345     $trackingforums = array();
1346     foreach ($forums as $forum) {
1347         if (forum_tp_can_track_forums($forum)) {
1348             $trackingforums[$forum->id] = $forum;
1349         }
1350     }
1352     if (count($trackingforums) > 0) {
1353         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1354         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1355             ' FROM {forum_posts} p '.
1356             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1357             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1358         $params = array($USER->id);
1360         foreach ($trackingforums as $track) {
1361             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1362             $params[] = $track->id;
1363             if (isset($SESSION->currentgroup[$track->course])) {
1364                 $groupid =  $SESSION->currentgroup[$track->course];
1365             } else {
1366                 // get first groupid
1367                 $groupids = groups_get_all_groups($track->course, $USER->id);
1368                 if ($groupids) {
1369                     reset($groupids);
1370                     $groupid = key($groupids);
1371                     $SESSION->currentgroup[$track->course] = $groupid;
1372                 } else {
1373                     $groupid = 0;
1374                 }
1375                 unset($groupids);
1376             }
1377             $params[] = $groupid;
1378         }
1379         $sql = substr($sql,0,-3); // take off the last OR
1380         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1381         $params[] = $cutoffdate;
1383         if (!$unread = $DB->get_records_sql($sql, $params)) {
1384             $unread = array();
1385         }
1386     } else {
1387         $unread = array();
1388     }
1390     if (empty($unread) and empty($new)) {
1391         return;
1392     }
1394     $strforum = get_string('modulename','forum');
1396     foreach ($forums as $forum) {
1397         $str = '';
1398         $count = 0;
1399         $thisunread = 0;
1400         $showunread = false;
1401         // either we have something from logs, or trackposts, or nothing.
1402         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1403             $count = $new[$forum->id]->count;
1404         }
1405         if (array_key_exists($forum->id,$unread)) {
1406             $thisunread = $unread[$forum->id]->count;
1407             $showunread = true;
1408         }
1409         if ($count > 0 || $thisunread > 0) {
1410             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1411                 $forum->name.'</a></div>';
1412             $str .= '<div class="info"><span class="postsincelogin">';
1413             $str .= get_string('overviewnumpostssince', 'forum', $count)."</span>";
1414             if (!empty($showunread)) {
1415                 $str .= '<div class="unreadposts">'.get_string('overviewnumunread', 'forum', $thisunread).'</div>';
1416             }
1417             $str .= '</div></div>';
1418         }
1419         if (!empty($str)) {
1420             if (!array_key_exists($forum->course,$htmlarray)) {
1421                 $htmlarray[$forum->course] = array();
1422             }
1423             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1424                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1425             }
1426             $htmlarray[$forum->course]['forum'] .= $str;
1427         }
1428     }
1431 /**
1432  * Given a course and a date, prints a summary of all the new
1433  * messages posted in the course since that date
1434  *
1435  * @global object
1436  * @global object
1437  * @global object
1438  * @uses CONTEXT_MODULE
1439  * @uses VISIBLEGROUPS
1440  * @param object $course
1441  * @param bool $viewfullnames capability
1442  * @param int $timestart
1443  * @return bool success
1444  */
1445 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1446     global $CFG, $USER, $DB, $OUTPUT;
1448     // do not use log table if possible, it may be huge and is expensive to join with other tables
1450     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1451                                               d.timestart, d.timeend, d.userid AS duserid,
1452                                               u.firstname, u.lastname, u.email, u.picture
1453                                          FROM {forum_posts} p
1454                                               JOIN {forum_discussions} d ON d.id = p.discussion
1455                                               JOIN {forum} f             ON f.id = d.forum
1456                                               JOIN {user} u              ON u.id = p.userid
1457                                         WHERE p.created > ? AND f.course = ?
1458                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1459          return false;
1460     }
1462     $modinfo = get_fast_modinfo($course);
1464     $groupmodes = array();
1465     $cms    = array();
1467     $strftimerecent = get_string('strftimerecent');
1469     $printposts = array();
1470     foreach ($posts as $post) {
1471         if (!isset($modinfo->instances['forum'][$post->forum])) {
1472             // not visible
1473             continue;
1474         }
1475         $cm = $modinfo->instances['forum'][$post->forum];
1476         if (!$cm->uservisible) {
1477             continue;
1478         }
1479         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1481         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1482             continue;
1483         }
1485         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1486           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1487             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1488                 continue;
1489             }
1490         }
1492         $groupmode = groups_get_activity_groupmode($cm, $course);
1494         if ($groupmode) {
1495             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1496                 // oki (Open discussions have groupid -1)
1497             } else {
1498                 // separate mode
1499                 if (isguestuser()) {
1500                     // shortcut
1501                     continue;
1502                 }
1504                 if (is_null($modinfo->groups)) {
1505                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1506                 }
1508                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1509                     continue;
1510                 }
1511             }
1512         }
1514         $printposts[] = $post;
1515     }
1516     unset($posts);
1518     if (!$printposts) {
1519         return false;
1520     }
1522     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1523     echo "\n<ul class='unlist'>\n";
1525     foreach ($printposts as $post) {
1526         $subjectclass = empty($post->parent) ? ' bold' : '';
1528         echo '<li><div class="head">'.
1529                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1530                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1531              '</div>';
1532         echo '<div class="info'.$subjectclass.'">';
1533         if (empty($post->parent)) {
1534             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1535         } else {
1536             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1537         }
1538         $post->subject = break_up_long_words(format_string($post->subject, true));
1539         echo $post->subject;
1540         echo "</a>\"</div></li>\n";
1541     }
1543     echo "</ul>\n";
1545     return true;
1548 /**
1549  * Return grade for given user or all users.
1550  *
1551  * @global object
1552  * @global object
1553  * @param object $forum
1554  * @param int $userid optional user id, 0 means all users
1555  * @return array array of grades, false if none
1556  */
1557 function forum_get_user_grades($forum, $userid = 0) {
1558     global $CFG;
1560     require_once($CFG->dirroot.'/rating/lib.php');
1562     $ratingoptions = new stdClass;
1563     $ratingoptions->component = 'mod_forum';
1564     $ratingoptions->ratingarea = 'post';
1566     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1567     $ratingoptions->modulename = 'forum';
1568     $ratingoptions->moduleid   = $forum->id;
1569     $ratingoptions->userid = $userid;
1570     $ratingoptions->aggregationmethod = $forum->assessed;
1571     $ratingoptions->scaleid = $forum->scale;
1572     $ratingoptions->itemtable = 'forum_posts';
1573     $ratingoptions->itemtableusercolumn = 'userid';
1575     $rm = new rating_manager();
1576     return $rm->get_user_grades($ratingoptions);
1579 /**
1580  * Update activity grades
1581  *
1582  * @category grade
1583  * @param object $forum
1584  * @param int $userid specific user only, 0 means all
1585  * @param boolean $nullifnone return null if grade does not exist
1586  * @return void
1587  */
1588 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1589     global $CFG, $DB;
1590     require_once($CFG->libdir.'/gradelib.php');
1592     if (!$forum->assessed) {
1593         forum_grade_item_update($forum);
1595     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1596         forum_grade_item_update($forum, $grades);
1598     } else if ($userid and $nullifnone) {
1599         $grade = new stdClass();
1600         $grade->userid   = $userid;
1601         $grade->rawgrade = NULL;
1602         forum_grade_item_update($forum, $grade);
1604     } else {
1605         forum_grade_item_update($forum);
1606     }
1609 /**
1610  * Update all grades in gradebook.
1611  * @global object
1612  */
1613 function forum_upgrade_grades() {
1614     global $DB;
1616     $sql = "SELECT COUNT('x')
1617               FROM {forum} f, {course_modules} cm, {modules} m
1618              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1619     $count = $DB->count_records_sql($sql);
1621     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1622               FROM {forum} f, {course_modules} cm, {modules} m
1623              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1624     $rs = $DB->get_recordset_sql($sql);
1625     if ($rs->valid()) {
1626         $pbar = new progress_bar('forumupgradegrades', 500, true);
1627         $i=0;
1628         foreach ($rs as $forum) {
1629             $i++;
1630             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1631             forum_update_grades($forum, 0, false);
1632             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1633         }
1634     }
1635     $rs->close();
1638 /**
1639  * Create/update grade item for given forum
1640  *
1641  * @category grade
1642  * @uses GRADE_TYPE_NONE
1643  * @uses GRADE_TYPE_VALUE
1644  * @uses GRADE_TYPE_SCALE
1645  * @param stdClass $forum Forum object with extra cmidnumber
1646  * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
1647  * @return int 0 if ok
1648  */
1649 function forum_grade_item_update($forum, $grades=NULL) {
1650     global $CFG;
1651     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1652         require_once($CFG->libdir.'/gradelib.php');
1653     }
1655     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1657     if (!$forum->assessed or $forum->scale == 0) {
1658         $params['gradetype'] = GRADE_TYPE_NONE;
1660     } else if ($forum->scale > 0) {
1661         $params['gradetype'] = GRADE_TYPE_VALUE;
1662         $params['grademax']  = $forum->scale;
1663         $params['grademin']  = 0;
1665     } else if ($forum->scale < 0) {
1666         $params['gradetype'] = GRADE_TYPE_SCALE;
1667         $params['scaleid']   = -$forum->scale;
1668     }
1670     if ($grades  === 'reset') {
1671         $params['reset'] = true;
1672         $grades = NULL;
1673     }
1675     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1678 /**
1679  * Delete grade item for given forum
1680  *
1681  * @category grade
1682  * @param stdClass $forum Forum object
1683  * @return grade_item
1684  */
1685 function forum_grade_item_delete($forum) {
1686     global $CFG;
1687     require_once($CFG->libdir.'/gradelib.php');
1689     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1693 /**
1694  * This function returns if a scale is being used by one forum
1695  *
1696  * @global object
1697  * @param int $forumid
1698  * @param int $scaleid negative number
1699  * @return bool
1700  */
1701 function forum_scale_used ($forumid,$scaleid) {
1702     global $DB;
1703     $return = false;
1705     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1707     if (!empty($rec) && !empty($scaleid)) {
1708         $return = true;
1709     }
1711     return $return;
1714 /**
1715  * Checks if scale is being used by any instance of forum
1716  *
1717  * This is used to find out if scale used anywhere
1718  *
1719  * @global object
1720  * @param $scaleid int
1721  * @return boolean True if the scale is used by any forum
1722  */
1723 function forum_scale_used_anywhere($scaleid) {
1724     global $DB;
1725     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1726         return true;
1727     } else {
1728         return false;
1729     }
1732 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1734 /**
1735  * Gets a post with all info ready for forum_print_post
1736  * Most of these joins are just to get the forum id
1737  *
1738  * @global object
1739  * @global object
1740  * @param int $postid
1741  * @return mixed array of posts or false
1742  */
1743 function forum_get_post_full($postid) {
1744     global $CFG, $DB;
1746     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1747                              FROM {forum_posts} p
1748                                   JOIN {forum_discussions} d ON p.discussion = d.id
1749                                   LEFT JOIN {user} u ON p.userid = u.id
1750                             WHERE p.id = ?", array($postid));
1753 /**
1754  * Gets posts with all info ready for forum_print_post
1755  * We pass forumid in because we always know it so no need to make a
1756  * complicated join to find it out.
1757  *
1758  * @global object
1759  * @global object
1760  * @return mixed array of posts or false
1761  */
1762 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1763     global $CFG, $DB;
1765     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1766                               FROM {forum_posts} p
1767                          LEFT JOIN {user} u ON p.userid = u.id
1768                              WHERE p.discussion = ?
1769                                AND p.parent > 0 $sort", array($discussion));
1772 /**
1773  * Gets all posts in discussion including top parent.
1774  *
1775  * @global object
1776  * @global object
1777  * @global object
1778  * @param int $discussionid
1779  * @param string $sort
1780  * @param bool $tracking does user track the forum?
1781  * @return array of posts
1782  */
1783 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1784     global $CFG, $DB, $USER;
1786     $tr_sel  = "";
1787     $tr_join = "";
1788     $params = array();
1790     if ($tracking) {
1791         $now = time();
1792         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1793         $tr_sel  = ", fr.id AS postread";
1794         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1795         $params[] = $USER->id;
1796     }
1798     $params[] = $discussionid;
1799     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1800                                      FROM {forum_posts} p
1801                                           LEFT JOIN {user} u ON p.userid = u.id
1802                                           $tr_join
1803                                     WHERE p.discussion = ?
1804                                  ORDER BY $sort", $params)) {
1805         return array();
1806     }
1808     foreach ($posts as $pid=>$p) {
1809         if ($tracking) {
1810             if (forum_tp_is_post_old($p)) {
1811                  $posts[$pid]->postread = true;
1812             }
1813         }
1814         if (!$p->parent) {
1815             continue;
1816         }
1817         if (!isset($posts[$p->parent])) {
1818             continue; // parent does not exist??
1819         }
1820         if (!isset($posts[$p->parent]->children)) {
1821             $posts[$p->parent]->children = array();
1822         }
1823         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1824     }
1826     return $posts;
1829 /**
1830  * Gets posts with all info ready for forum_print_post
1831  * We pass forumid in because we always know it so no need to make a
1832  * complicated join to find it out.
1833  *
1834  * @global object
1835  * @global object
1836  * @param int $parent
1837  * @param int $forumid
1838  * @return array
1839  */
1840 function forum_get_child_posts($parent, $forumid) {
1841     global $CFG, $DB;
1843     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1844                               FROM {forum_posts} p
1845                          LEFT JOIN {user} u ON p.userid = u.id
1846                              WHERE p.parent = ?
1847                           ORDER BY p.created ASC", array($parent));
1850 /**
1851  * An array of forum objects that the user is allowed to read/search through.
1852  *
1853  * @global object
1854  * @global object
1855  * @global object
1856  * @param int $userid
1857  * @param int $courseid if 0, we look for forums throughout the whole site.
1858  * @return array of forum objects, or false if no matches
1859  *         Forum objects have the following attributes:
1860  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1861  *         viewhiddentimedposts
1862  */
1863 function forum_get_readable_forums($userid, $courseid=0) {
1865     global $CFG, $DB, $USER;
1866     require_once($CFG->dirroot.'/course/lib.php');
1868     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1869         print_error('notinstalled', 'forum');
1870     }
1872     if ($courseid) {
1873         $courses = $DB->get_records('course', array('id' => $courseid));
1874     } else {
1875         // If no course is specified, then the user can see SITE + his courses.
1876         $courses1 = $DB->get_records('course', array('id' => SITEID));
1877         $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
1878         $courses = array_merge($courses1, $courses2);
1879     }
1880     if (!$courses) {
1881         return array();
1882     }
1884     $readableforums = array();
1886     foreach ($courses as $course) {
1888         $modinfo = get_fast_modinfo($course);
1889         if (is_null($modinfo->groups)) {
1890             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1891         }
1893         if (empty($modinfo->instances['forum'])) {
1894             // hmm, no forums?
1895             continue;
1896         }
1898         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1900         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1901             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1902                 continue;
1903             }
1904             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1905             $forum = $courseforums[$forumid];
1906             $forum->context = $context;
1907             $forum->cm = $cm;
1909             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1910                 continue;
1911             }
1913          /// group access
1914             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1915                 if (is_null($modinfo->groups)) {
1916                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1917                 }
1918                 if (isset($modinfo->groups[$cm->groupingid])) {
1919                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1920                     $forum->onlygroups[] = -1;
1921                 } else {
1922                     $forum->onlygroups = array(-1);
1923                 }
1924             }
1926         /// hidden timed discussions
1927             $forum->viewhiddentimedposts = true;
1928             if (!empty($CFG->forum_enabletimedposts)) {
1929                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1930                     $forum->viewhiddentimedposts = false;
1931                 }
1932             }
1934         /// qanda access
1935             if ($forum->type == 'qanda'
1936                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1938                 // We need to check whether the user has posted in the qanda forum.
1939                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1940                                                     // the user is allowed to see in this forum.
1941                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1942                     foreach ($discussionspostedin as $d) {
1943                         $forum->onlydiscussions[] = $d->id;
1944                     }
1945                 }
1946             }
1948             $readableforums[$forum->id] = $forum;
1949         }
1951         unset($modinfo);
1953     } // End foreach $courses
1955     return $readableforums;
1958 /**
1959  * Returns a list of posts found using an array of search terms.
1960  *
1961  * @global object
1962  * @global object
1963  * @global object
1964  * @param array $searchterms array of search terms, e.g. word +word -word
1965  * @param int $courseid if 0, we search through the whole site
1966  * @param int $limitfrom
1967  * @param int $limitnum
1968  * @param int &$totalcount
1969  * @param string $extrasql
1970  * @return array|bool Array of posts found or false
1971  */
1972 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1973                             &$totalcount, $extrasql='') {
1974     global $CFG, $DB, $USER;
1975     require_once($CFG->libdir.'/searchlib.php');
1977     $forums = forum_get_readable_forums($USER->id, $courseid);
1979     if (count($forums) == 0) {
1980         $totalcount = 0;
1981         return false;
1982     }
1984     $now = round(time(), -2); // db friendly
1986     $fullaccess = array();
1987     $where = array();
1988     $params = array();
1990     foreach ($forums as $forumid => $forum) {
1991         $select = array();
1993         if (!$forum->viewhiddentimedposts) {
1994             $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
1995             $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
1996         }
1998         $cm = $forum->cm;
1999         $context = $forum->context;
2001         if ($forum->type == 'qanda'
2002             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
2003             if (!empty($forum->onlydiscussions)) {
2004                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
2005                 $params = array_merge($params, $discussionid_params);
2006                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
2007             } else {
2008                 $select[] = "p.parent = 0";
2009             }
2010         }
2012         if (!empty($forum->onlygroups)) {
2013             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
2014             $params = array_merge($params, $groupid_params);
2015             $select[] = "d.groupid $groupid_sql";
2016         }
2018         if ($select) {
2019             $selects = implode(" AND ", $select);
2020             $where[] = "(d.forum = :forum{$forumid} AND $selects)";
2021             $params['forum'.$forumid] = $forumid;
2022         } else {
2023             $fullaccess[] = $forumid;
2024         }
2025     }
2027     if ($fullaccess) {
2028         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
2029         $params = array_merge($params, $fullid_params);
2030         $where[] = "(d.forum $fullid_sql)";
2031     }
2033     $selectdiscussion = "(".implode(" OR ", $where).")";
2035     $messagesearch = '';
2036     $searchstring = '';
2038     // Need to concat these back together for parser to work.
2039     foreach($searchterms as $searchterm){
2040         if ($searchstring != '') {
2041             $searchstring .= ' ';
2042         }
2043         $searchstring .= $searchterm;
2044     }
2046     // We need to allow quoted strings for the search. The quotes *should* be stripped
2047     // by the parser, but this should be examined carefully for security implications.
2048     $searchstring = str_replace("\\\"","\"",$searchstring);
2049     $parser = new search_parser();
2050     $lexer = new search_lexer($parser);
2052     if ($lexer->parse($searchstring)) {
2053         $parsearray = $parser->get_parsed_array();
2054     // Experimental feature under 1.8! MDL-8830
2055     // Use alternative text searches if defined
2056     // This feature only works under mysql until properly implemented for other DBs
2057     // Requires manual creation of text index for forum_posts before enabling it:
2058     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2059     // Experimental feature under 1.8! MDL-8830
2060         if (!empty($CFG->forum_usetextsearches)) {
2061             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2062                                                  'p.userid', 'u.id', 'u.firstname',
2063                                                  'u.lastname', 'p.modified', 'd.forum');
2064         } else {
2065             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2066                                                  'p.userid', 'u.id', 'u.firstname',
2067                                                  'u.lastname', 'p.modified', 'd.forum');
2068         }
2069         $params = array_merge($params, $msparams);
2070     }
2072     $fromsql = "{forum_posts} p,
2073                   {forum_discussions} d,
2074                   {user} u";
2076     $selectsql = " $messagesearch
2077                AND p.discussion = d.id
2078                AND p.userid = u.id
2079                AND $selectdiscussion
2080                    $extrasql";
2082     $countsql = "SELECT COUNT(*)
2083                    FROM $fromsql
2084                   WHERE $selectsql";
2086     $searchsql = "SELECT p.*,
2087                          d.forum,
2088                          u.firstname,
2089                          u.lastname,
2090                          u.email,
2091                          u.picture,
2092                          u.imagealt
2093                     FROM $fromsql
2094                    WHERE $selectsql
2095                 ORDER BY p.modified DESC";
2097     $totalcount = $DB->count_records_sql($countsql, $params);
2099     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2102 /**
2103  * Returns a list of ratings for a particular post - sorted.
2104  *
2105  * TODO: Check if this function is actually used anywhere.
2106  * Up until the fix for MDL-27471 this function wasn't even returning.
2107  *
2108  * @param stdClass $context
2109  * @param int $postid
2110  * @param string $sort
2111  * @return array Array of ratings or false
2112  */
2113 function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
2114     $options = new stdClass;
2115     $options->context = $context;
2116     $options->component = 'mod_forum';
2117     $options->ratingarea = 'post';
2118     $options->itemid = $postid;
2119     $options->sort = "ORDER BY $sort";
2121     $rm = new rating_manager();
2122     return $rm->get_all_ratings_for_item($options);
2125 /**
2126  * Returns a list of all new posts that have not been mailed yet
2127  *
2128  * @param int $starttime posts created after this time
2129  * @param int $endtime posts created before this
2130  * @param int $now used for timed discussions only
2131  * @return array
2132  */
2133 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2134     global $CFG, $DB;
2136     $params = array($starttime, $endtime);
2137     if (!empty($CFG->forum_enabletimedposts)) {
2138         if (empty($now)) {
2139             $now = time();
2140         }
2141         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2142         $params[] = $now;
2143         $params[] = $now;
2144     } else {
2145         $timedsql = "";
2146     }
2148     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2149                               FROM {forum_posts} p
2150                                    JOIN {forum_discussions} d ON d.id = p.discussion
2151                              WHERE p.mailed = 0
2152                                    AND p.created >= ?
2153                                    AND (p.created < ? OR p.mailnow = 1)
2154                                    $timedsql
2155                           ORDER BY p.modified ASC", $params);
2158 /**
2159  * Marks posts before a certain time as being mailed already
2160  *
2161  * @global object
2162  * @global object
2163  * @param int $endtime
2164  * @param int $now Defaults to time()
2165  * @return bool
2166  */
2167 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2168     global $CFG, $DB;
2169     if (empty($now)) {
2170         $now = time();
2171     }
2173     if (empty($CFG->forum_enabletimedposts)) {
2174         return $DB->execute("UPDATE {forum_posts}
2175                                SET mailed = '1'
2176                              WHERE (created < ? OR mailnow = 1)
2177                                    AND mailed = 0", array($endtime));
2179     } else {
2180         return $DB->execute("UPDATE {forum_posts}
2181                                SET mailed = '1'
2182                              WHERE discussion NOT IN (SELECT d.id
2183                                                         FROM {forum_discussions} d
2184                                                        WHERE d.timestart > ?)
2185                                    AND (created < ? OR mailnow = 1)
2186                                    AND mailed = 0", array($now, $endtime));
2187     }
2190 /**
2191  * Get all the posts for a user in a forum suitable for forum_print_post
2192  *
2193  * @global object
2194  * @global object
2195  * @uses CONTEXT_MODULE
2196  * @return array
2197  */
2198 function forum_get_user_posts($forumid, $userid) {
2199     global $CFG, $DB;
2201     $timedsql = "";
2202     $params = array($forumid, $userid);
2204     if (!empty($CFG->forum_enabletimedposts)) {
2205         $cm = get_coursemodule_from_instance('forum', $forumid);
2206         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2207             $now = time();
2208             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2209             $params[] = $now;
2210             $params[] = $now;
2211         }
2212     }
2214     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2215                               FROM {forum} f
2216                                    JOIN {forum_discussions} d ON d.forum = f.id
2217                                    JOIN {forum_posts} p       ON p.discussion = d.id
2218                                    JOIN {user} u              ON u.id = p.userid
2219                              WHERE f.id = ?
2220                                    AND p.userid = ?
2221                                    $timedsql
2222                           ORDER BY p.modified ASC", $params);
2225 /**
2226  * Get all the discussions user participated in
2227  *
2228  * @global object
2229  * @global object
2230  * @uses CONTEXT_MODULE
2231  * @param int $forumid
2232  * @param int $userid
2233  * @return array Array or false
2234  */
2235 function forum_get_user_involved_discussions($forumid, $userid) {
2236     global $CFG, $DB;
2238     $timedsql = "";
2239     $params = array($forumid, $userid);
2240     if (!empty($CFG->forum_enabletimedposts)) {
2241         $cm = get_coursemodule_from_instance('forum', $forumid);
2242         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2243             $now = time();
2244             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2245             $params[] = $now;
2246             $params[] = $now;
2247         }
2248     }
2250     return $DB->get_records_sql("SELECT DISTINCT d.*
2251                               FROM {forum} f
2252                                    JOIN {forum_discussions} d ON d.forum = f.id
2253                                    JOIN {forum_posts} p       ON p.discussion = d.id
2254                              WHERE f.id = ?
2255                                    AND p.userid = ?
2256                                    $timedsql", $params);
2259 /**
2260  * Get all the posts for a user in a forum suitable for forum_print_post
2261  *
2262  * @global object
2263  * @global object
2264  * @param int $forumid
2265  * @param int $userid
2266  * @return array of counts or false
2267  */
2268 function forum_count_user_posts($forumid, $userid) {
2269     global $CFG, $DB;
2271     $timedsql = "";
2272     $params = array($forumid, $userid);
2273     if (!empty($CFG->forum_enabletimedposts)) {
2274         $cm = get_coursemodule_from_instance('forum', $forumid);
2275         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2276             $now = time();
2277             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2278             $params[] = $now;
2279             $params[] = $now;
2280         }
2281     }
2283     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2284                              FROM {forum} f
2285                                   JOIN {forum_discussions} d ON d.forum = f.id
2286                                   JOIN {forum_posts} p       ON p.discussion = d.id
2287                                   JOIN {user} u              ON u.id = p.userid
2288                             WHERE f.id = ?
2289                                   AND p.userid = ?
2290                                   $timedsql", $params);
2293 /**
2294  * Given a log entry, return the forum post details for it.
2295  *
2296  * @global object
2297  * @global object
2298  * @param object $log
2299  * @return array|null
2300  */
2301 function forum_get_post_from_log($log) {
2302     global $CFG, $DB;
2304     if ($log->action == "add post") {
2306         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2307                                            u.firstname, u.lastname, u.email, u.picture
2308                                  FROM {forum_discussions} d,
2309                                       {forum_posts} p,
2310                                       {forum} f,
2311                                       {user} u
2312                                 WHERE p.id = ?
2313                                   AND d.id = p.discussion
2314                                   AND p.userid = u.id
2315                                   AND u.deleted <> '1'
2316                                   AND f.id = d.forum", array($log->info));
2319     } else if ($log->action == "add discussion") {
2321         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2322                                            u.firstname, u.lastname, u.email, u.picture
2323                                  FROM {forum_discussions} d,
2324                                       {forum_posts} p,
2325                                       {forum} f,
2326                                       {user} u
2327                                 WHERE d.id = ?
2328                                   AND d.firstpost = p.id
2329                                   AND p.userid = u.id
2330                                   AND u.deleted <> '1'
2331                                   AND f.id = d.forum", array($log->info));
2332     }
2333     return NULL;
2336 /**
2337  * Given a discussion id, return the first post from the discussion
2338  *
2339  * @global object
2340  * @global object
2341  * @param int $dicsussionid
2342  * @return array
2343  */
2344 function forum_get_firstpost_from_discussion($discussionid) {
2345     global $CFG, $DB;
2347     return $DB->get_record_sql("SELECT p.*
2348                              FROM {forum_discussions} d,
2349                                   {forum_posts} p
2350                             WHERE d.id = ?
2351                               AND d.firstpost = p.id ", array($discussionid));
2354 /**
2355  * Returns an array of counts of replies to each discussion
2356  *
2357  * @global object
2358  * @global object
2359  * @param int $forumid
2360  * @param string $forumsort
2361  * @param int $limit
2362  * @param int $page
2363  * @param int $perpage
2364  * @return array
2365  */
2366 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2367     global $CFG, $DB;
2369     if ($limit > 0) {
2370         $limitfrom = 0;
2371         $limitnum  = $limit;
2372     } else if ($page != -1) {
2373         $limitfrom = $page*$perpage;
2374         $limitnum  = $perpage;
2375     } else {
2376         $limitfrom = 0;
2377         $limitnum  = 0;
2378     }
2380     if ($forumsort == "") {
2381         $orderby = "";
2382         $groupby = "";
2384     } else {
2385         $orderby = "ORDER BY $forumsort";
2386         $groupby = ", ".strtolower($forumsort);
2387         $groupby = str_replace('desc', '', $groupby);
2388         $groupby = str_replace('asc', '', $groupby);
2389     }
2391     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2392         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2393                   FROM {forum_posts} p
2394                        JOIN {forum_discussions} d ON p.discussion = d.id
2395                  WHERE p.parent > 0 AND d.forum = ?
2396               GROUP BY p.discussion";
2397         return $DB->get_records_sql($sql, array($forumid));
2399     } else {
2400         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2401                   FROM {forum_posts} p
2402                        JOIN {forum_discussions} d ON p.discussion = d.id
2403                  WHERE d.forum = ?
2404               GROUP BY p.discussion $groupby
2405               $orderby";
2406         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2407     }
2410 /**
2411  * @global object
2412  * @global object
2413  * @global object
2414  * @staticvar array $cache
2415  * @param object $forum
2416  * @param object $cm
2417  * @param object $course
2418  * @return mixed
2419  */
2420 function forum_count_discussions($forum, $cm, $course) {
2421     global $CFG, $DB, $USER;
2423     static $cache = array();
2425     $now = round(time(), -2); // db cache friendliness
2427     $params = array($course->id);
2429     if (!isset($cache[$course->id])) {
2430         if (!empty($CFG->forum_enabletimedposts)) {
2431             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2432             $params[] = $now;
2433             $params[] = $now;
2434         } else {
2435             $timedsql = "";
2436         }
2438         $sql = "SELECT f.id, COUNT(d.id) as dcount
2439                   FROM {forum} f
2440                        JOIN {forum_discussions} d ON d.forum = f.id
2441                  WHERE f.course = ?
2442                        $timedsql
2443               GROUP BY f.id";
2445         if ($counts = $DB->get_records_sql($sql, $params)) {
2446             foreach ($counts as $count) {
2447                 $counts[$count->id] = $count->dcount;
2448             }
2449             $cache[$course->id] = $counts;
2450         } else {
2451             $cache[$course->id] = array();
2452         }
2453     }
2455     if (empty($cache[$course->id][$forum->id])) {
2456         return 0;
2457     }
2459     $groupmode = groups_get_activity_groupmode($cm, $course);
2461     if ($groupmode != SEPARATEGROUPS) {
2462         return $cache[$course->id][$forum->id];
2463     }
2465     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2466         return $cache[$course->id][$forum->id];
2467     }
2469     require_once($CFG->dirroot.'/course/lib.php');
2471     $modinfo = get_fast_modinfo($course);
2472     if (is_null($modinfo->groups)) {
2473         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2474     }
2476     if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2477         $mygroups = $modinfo->groups[$cm->groupingid];
2478     } else {
2479         $mygroups = false; // Will be set below
2480     }
2482     // add all groups posts
2483     if (empty($mygroups)) {
2484         $mygroups = array(-1=>-1);
2485     } else {
2486         $mygroups[-1] = -1;
2487     }
2489     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2490     $params[] = $forum->id;
2492     if (!empty($CFG->forum_enabletimedposts)) {
2493         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2494         $params[] = $now;
2495         $params[] = $now;
2496     } else {
2497         $timedsql = "";
2498     }
2500     $sql = "SELECT COUNT(d.id)
2501               FROM {forum_discussions} d
2502              WHERE d.groupid $mygroups_sql AND d.forum = ?
2503                    $timedsql";
2505     return $DB->get_field_sql($sql, $params);
2508 /**
2509  * How many posts by other users are unrated by a given user in the given discussion?
2510  *
2511  * TODO: Is this function still used anywhere?
2512  *
2513  * @param int $discussionid
2514  * @param int $userid
2515  * @return mixed
2516  */
2517 function forum_count_unrated_posts($discussionid, $userid) {
2518     global $CFG, $DB;
2520     $sql = "SELECT COUNT(*) as num
2521               FROM {forum_posts}
2522              WHERE parent > 0
2523                AND discussion = :discussionid
2524                AND userid <> :userid";
2525     $params = array('discussionid' => $discussionid, 'userid' => $userid);
2526     $posts = $DB->get_record_sql($sql, $params);
2527     if ($posts) {
2528         $sql = "SELECT count(*) as num
2529                   FROM {forum_posts} p,
2530                        {rating} r
2531                  WHERE p.discussion = :discussionid AND
2532                        p.id = r.itemid AND
2533                        r.userid = userid AND
2534                        r.component = 'mod_forum' AND
2535                        r.ratingarea = 'post'";
2536         $rated = $DB->get_record_sql($sql, $params);
2537         if ($rated) {
2538             if ($posts->num > $rated->num) {
2539                 return $posts->num - $rated->num;
2540             } else {
2541                 return 0;    // Just in case there was a counting error
2542             }
2543         } else {
2544             return $posts->num;
2545         }
2546     } else {
2547         return 0;
2548     }
2551 /**
2552  * Get all discussions in a forum
2553  *
2554  * @global object
2555  * @global object
2556  * @global object
2557  * @uses CONTEXT_MODULE
2558  * @uses VISIBLEGROUPS
2559  * @param object $cm
2560  * @param string $forumsort
2561  * @param bool $fullpost
2562  * @param int $unused
2563  * @param int $limit
2564  * @param bool $userlastmodified
2565  * @param int $page
2566  * @param int $perpage
2567  * @return array
2568  */
2569 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2570     global $CFG, $DB, $USER;
2572     $timelimit = '';
2574     $now = round(time(), -2);
2575     $params = array($cm->instance);
2577     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2579     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2580         return array();
2581     }
2583     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2585         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2586             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2587             $params[] = $now;
2588             $params[] = $now;
2589             if (isloggedin()) {
2590                 $timelimit .= " OR d.userid = ?";
2591                 $params[] = $USER->id;
2592             }
2593             $timelimit .= ")";
2594         }
2595     }
2597     if ($limit > 0) {
2598         $limitfrom = 0;
2599         $limitnum  = $limit;
2600     } else if ($page != -1) {
2601         $limitfrom = $page*$perpage;
2602         $limitnum  = $perpage;
2603     } else {
2604         $limitfrom = 0;
2605         $limitnum  = 0;
2606     }
2608     $groupmode    = groups_get_activity_groupmode($cm);
2609     $currentgroup = groups_get_activity_group($cm);
2611     if ($groupmode) {
2612         if (empty($modcontext)) {
2613             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2614         }
2616         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2617             if ($currentgroup) {
2618                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2619                 $params[] = $currentgroup;
2620             } else {
2621                 $groupselect = "";
2622             }
2624         } else {
2625             //seprate groups without access all
2626             if ($currentgroup) {
2627                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2628                 $params[] = $currentgroup;
2629             } else {
2630                 $groupselect = "AND d.groupid = -1";
2631             }
2632         }
2633     } else {
2634         $groupselect = "";
2635     }
2638     if (empty($forumsort)) {
2639         $forumsort = "d.timemodified DESC";
2640     }
2641     if (empty($fullpost)) {
2642         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2643     } else {
2644         $postdata = "p.*";
2645     }
2647     if (empty($userlastmodified)) {  // We don't need to know this
2648         $umfields = "";
2649         $umtable  = "";
2650     } else {
2651         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2652         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2653     }
2655     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2656                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2657               FROM {forum_discussions} d
2658                    JOIN {forum_posts} p ON p.discussion = d.id
2659                    JOIN {user} u ON p.userid = u.id
2660                    $umtable
2661              WHERE d.forum = ? AND p.parent = 0
2662                    $timelimit $groupselect
2663           ORDER BY $forumsort";
2664     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2667 /**
2668  *
2669  * @global object
2670  * @global object
2671  * @global object
2672  * @uses CONTEXT_MODULE
2673  * @uses VISIBLEGROUPS
2674  * @param object $cm
2675  * @return array
2676  */
2677 function forum_get_discussions_unread($cm) {
2678     global $CFG, $DB, $USER;
2680     $now = round(time(), -2);
2681     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2683     $params = array();
2684     $groupmode    = groups_get_activity_groupmode($cm);
2685     $currentgroup = groups_get_activity_group($cm);
2687     if ($groupmode) {
2688         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2690         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2691             if ($currentgroup) {
2692                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2693                 $params['currentgroup'] = $currentgroup;
2694             } else {
2695                 $groupselect = "";
2696             }
2698         } else {
2699             //separate groups without access all
2700             if ($currentgroup) {
2701                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2702                 $params['currentgroup'] = $currentgroup;
2703             } else {
2704                 $groupselect = "AND d.groupid = -1";
2705             }
2706         }
2707     } else {
2708         $groupselect = "";
2709     }
2711     if (!empty($CFG->forum_enabletimedposts)) {
2712         $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2713         $params['now1'] = $now;
2714         $params['now2'] = $now;
2715     } else {
2716         $timedsql = "";
2717     }
2719     $sql = "SELECT d.id, COUNT(p.id) AS unread
2720               FROM {forum_discussions} d
2721                    JOIN {forum_posts} p     ON p.discussion = d.id
2722                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2723              WHERE d.forum = {$cm->instance}
2724                    AND p.modified >= :cutoffdate AND r.id is NULL
2725                    $groupselect
2726                    $timedsql
2727           GROUP BY d.id";
2728     $params['cutoffdate'] = $cutoffdate;
2730     if ($unreads = $DB->get_records_sql($sql, $params)) {
2731         foreach ($unreads as $unread) {
2732             $unreads[$unread->id] = $unread->unread;
2733         }
2734         return $unreads;
2735     } else {
2736         return array();
2737     }
2740 /**
2741  * @global object
2742  * @global object
2743  * @global object
2744  * @uses CONEXT_MODULE
2745  * @uses VISIBLEGROUPS
2746  * @param object $cm
2747  * @return array
2748  */
2749 function forum_get_discussions_count($cm) {
2750     global $CFG, $DB, $USER;
2752     $now = round(time(), -2);
2753     $params = array($cm->instance);
2754     $groupmode    = groups_get_activity_groupmode($cm);
2755     $currentgroup = groups_get_activity_group($cm);
2757     if ($groupmode) {
2758         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2760         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2761             if ($currentgroup) {
2762                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2763                 $params[] = $currentgroup;
2764             } else {
2765                 $groupselect = "";
2766             }
2768         } else {
2769             //seprate groups without access all
2770             if ($currentgroup) {
2771                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2772                 $params[] = $currentgroup;
2773             } else {
2774                 $groupselect = "AND d.groupid = -1";
2775             }
2776         }
2777     } else {
2778         $groupselect = "";
2779     }
2781     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2783     $timelimit = "";
2785     if (!empty($CFG->forum_enabletimedposts)) {
2787         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2789         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2790             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2791             $params[] = $now;
2792             $params[] = $now;
2793             if (isloggedin()) {
2794                 $timelimit .= " OR d.userid = ?";
2795                 $params[] = $USER->id;
2796             }
2797             $timelimit .= ")";
2798         }
2799     }
2801     $sql = "SELECT COUNT(d.id)
2802               FROM {forum_discussions} d
2803                    JOIN {forum_posts} p ON p.discussion = d.id
2804              WHERE d.forum = ? AND p.parent = 0
2805                    $groupselect $timelimit";
2807     return $DB->get_field_sql($sql, $params);
2811 /**
2812  * Get all discussions started by a particular user in a course (or group)
2813  * This function no longer used ...
2814  *
2815  * @todo Remove this function if no longer used
2816  * @global object
2817  * @global object
2818  * @param int $courseid
2819  * @param int $userid
2820  * @param int $groupid
2821  * @return array
2822  */
2823 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2824     global $CFG, $DB;
2825     $params = array($courseid, $userid);
2826     if ($groupid) {
2827         $groupselect = " AND d.groupid = ? ";
2828         $params[] = $groupid;
2829     } else  {
2830         $groupselect = "";
2831     }
2833     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2834                                    f.type as forumtype, f.name as forumname, f.id as forumid
2835                               FROM {forum_discussions} d,
2836                                    {forum_posts} p,
2837                                    {user} u,
2838                                    {forum} f
2839                              WHERE d.course = ?
2840                                AND p.discussion = d.id
2841                                AND p.parent = 0
2842                                AND p.userid = u.id
2843                                AND u.id = ?
2844                                AND d.forum = f.id $groupselect
2845                           ORDER BY p.created DESC", $params);
2848 /**
2849  * Get the list of potential subscribers to a forum.
2850  *
2851  * @param object $forumcontext the forum context.
2852  * @param integer $groupid the id of a group, or 0 for all groups.
2853  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2854  * @param string $sort sort order. As for get_users_by_capability.
2855  * @return array list of users.
2856  */
2857 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2858     global $DB;
2860     // only active enrolled users or everybody on the frontpage
2861     list($esql, $params) = get_enrolled_sql($forumcontext, 'mod/forum:allowforcesubscribe', $groupid, true);
2863     $sql = "SELECT $fields
2864               FROM {user} u
2865               JOIN ($esql) je ON je.id = u.id";
2866     if ($sort) {
2867         $sql = "$sql ORDER BY $sort";
2868     } else {
2869         $sql = "$sql ORDER BY u.lastname ASC, u.firstname ASC";
2870     }
2872     return $DB->get_records_sql($sql, $params);
2875 /**
2876  * Returns list of user objects that are subscribed to this forum
2877  *
2878  * @global object
2879  * @global object
2880  * @param object $course the course
2881  * @param forum $forum the forum
2882  * @param integer $groupid group id, or 0 for all.
2883  * @param object $context the forum context, to save re-fetching it where possible.
2884  * @param string $fields requested user fields (with "u." table prefix)
2885  * @return array list of users.
2886  */
2887 function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
2888     global $CFG, $DB;
2890     if (empty($fields)) {
2891         $fields ="u.id,
2892                   u.username,
2893                   u.firstname,
2894                   u.lastname,
2895                   u.maildisplay,
2896                   u.mailformat,
2897                   u.maildigest,
2898                   u.imagealt,
2899                   u.email,
2900                   u.emailstop,
2901                   u.city,
2902                   u.country,
2903                   u.lastaccess,
2904                   u.lastlogin,
2905                   u.picture,
2906                   u.timezone,
2907                   u.theme,
2908                   u.lang,
2909                   u.trackforums,
2910                   u.mnethostid";
2911     }
2913     if (empty($context)) {
2914         $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2915         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2916     }
2918     if (forum_is_forcesubscribed($forum)) {
2919         $results = forum_get_potential_subscribers($context, $groupid, $fields, "u.email ASC");
2921     } else {
2922         // only active enrolled users or everybody on the frontpage
2923         list($esql, $params) = get_enrolled_sql($context, '', $groupid, true);
2924         $params['forumid'] = $forum->id;
2925         $results = $DB->get_records_sql("SELECT $fields
2926                                            FROM {user} u
2927                                            JOIN ($esql) je ON je.id = u.id
2928                                            JOIN {forum_subscriptions} s ON s.userid = u.id
2929                                           WHERE s.forum = :forumid
2930                                        ORDER BY u.email ASC", $params);
2931     }
2933     // Guest user should never be subscribed to a forum.
2934     unset($results[$CFG->siteguest]);
2936     return $results;
2941 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2944 /**
2945  * @global object
2946  * @global object
2947  * @param int $courseid
2948  * @param string $type
2949  */
2950 function forum_get_course_forum($courseid, $type) {
2951 // How to set up special 1-per-course forums
2952     global $CFG, $DB, $OUTPUT;
2954     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2955         // There should always only be ONE, but with the right combination of
2956         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2957         foreach ($forums as $forum) {
2958             return $forum;   // ie the first one
2959         }
2960     }
2962     // Doesn't exist, so create one now.
2963     $forum = new stdClass();
2964     $forum->course = $courseid;
2965     $forum->type = "$type";
2966     switch ($forum->type) {
2967         case "news":
2968             $forum->name  = get_string("namenews", "forum");
2969             $forum->intro = get_string("intronews", "forum");
2970             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2971             $forum->assessed = 0;
2972             if ($courseid == SITEID) {
2973                 $forum->name  = get_string("sitenews");
2974                 $forum->forcesubscribe = 0;
2975             }
2976             break;
2977         case "social":
2978             $forum->name  = get_string("namesocial", "forum");
2979             $forum->intro = get_string("introsocial", "forum");
2980             $forum->assessed = 0;
2981             $forum->forcesubscribe = 0;
2982             break;
2983         case "blog":
2984             $forum->name = get_string('blogforum', 'forum');
2985             $forum->intro = get_string('introblog', 'forum');
2986             $forum->assessed = 0;
2987             $forum->forcesubscribe = 0;
2988             break;
2989         default:
2990             echo $OUTPUT->notification("That forum type doesn't exist!");
2991             return false;
2992             break;
2993     }
2995     $forum->timemodified = time();
2996     $forum->id = $DB->insert_record("forum", $forum);
2998     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2999         echo $OUTPUT->notification("Could not find forum module!!");
3000         return false;
3001     }
3002     $mod = new stdClass();
3003     $mod->course = $courseid;
3004     $mod->module = $module->id;
3005     $mod->instance = $forum->id;
3006     $mod->section = 0;
3007     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
3008         echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
3009         return false;
3010     }
3011     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
3012         echo $OUTPUT->notification("Could not add the new course module to that section");
3013         return false;
3014     }
3015     $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule));
3017     include_once("$CFG->dirroot/course/lib.php");
3018     rebuild_course_cache($courseid);
3020     return $DB->get_record("forum", array("id" => "$forum->id"));
3024 /**
3025  * Given the data about a posting, builds up the HTML to display it and
3026  * returns the HTML in a string.  This is designed for sending via HTML email.
3027  *
3028  * @global object
3029  * @param object $course
3030  * @param object $cm
3031  * @param object $forum
3032  * @param object $discussion
3033  * @param object $post
3034  * @param object $userform
3035  * @param object $userto
3036  * @param bool $ownpost
3037  * @param bool $reply
3038  * @param bool $link
3039  * @param bool $rate
3040  * @param string $footer
3041  * @return string
3042  */
3043 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
3044                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
3046     global $CFG, $OUTPUT;
3048     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3050     if (!isset($userto->viewfullnames[$forum->id])) {
3051         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3052     } else {
3053         $viewfullnames = $userto->viewfullnames[$forum->id];
3054     }
3056     // add absolute file links
3057     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3059     // format the post body
3060     $options = new stdClass();
3061     $options->para = true;
3062     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3064     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3066     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3067     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3068     $output .= '</td>';
3070     if ($post->parent) {
3071         $output .= '<td class="topic">';
3072     } else {
3073         $output .= '<td class="topic starter">';
3074     }
3075     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3077     $fullname = fullname($userfrom, $viewfullnames);
3078     $by = new stdClass();
3079     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3080     $by->date = userdate($post->modified, '', $userto->timezone);
3081     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3083     $output .= '</td></tr>';
3085     $output .= '<tr><td class="left side" valign="top">';
3087     if (isset($userfrom->groups)) {
3088         $groups = $userfrom->groups[$forum->id];
3089     } else {
3090         $groups = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3091     }
3093     if ($groups) {
3094         $output .= print_group_picture($groups, $course->id, false, true, true);
3095     } else {
3096         $output .= '&nbsp;';
3097     }
3099     $output .= '</td><td class="content">';
3101     $attachments = forum_print_attachments($post, $cm, 'html');
3102     if ($attachments !== '') {
3103         $output .= '<div class="attachments">';
3104         $output .= $attachments;
3105         $output .= '</div>';
3106     }
3108     $output .= $formattedtext;
3110 // Commands
3111     $commands = array();
3113     if ($post->parent) {
3114         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3115                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3116     }
3118     if ($reply) {
3119         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3120                       get_string('reply', 'forum').'</a>';
3121     }
3123     $output .= '<div class="commands">';
3124     $output .= implode(' | ', $commands);
3125     $output .= '</div>';
3127 // Context link to post if required
3128     if ($link) {
3129         $output .= '<div class="link">';
3130         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3131                      get_string('postincontext', 'forum').'</a>';
3132         $output .= '</div>';
3133     }
3135     if ($footer) {
3136         $output .= '<div class="footer">'.$footer.'</div>';
3137     }
3138     $output .= '</td></tr></table>'."\n\n";
3140     return $output;
3143 /**
3144  * Print a forum post
3145  *
3146  * @global object
3147  * @global object
3148  * @uses FORUM_MODE_THREADED
3149  * @uses PORTFOLIO_FORMAT_PLAINHTML
3150  * @uses PORTFOLIO_FORMAT_FILE
3151  * @uses PORTFOLIO_FORMAT_RICHHTML
3152  * @uses PORTFOLIO_ADD_TEXT_LINK
3153  * @uses CONTEXT_MODULE
3154  * @param object $post The post to print.
3155  * @param object $discussion
3156  * @param object $forum
3157  * @param object $cm
3158  * @param object $course
3159  * @param boolean $ownpost Whether this post belongs to the current user.
3160  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3161  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3162  * @param string $footer Extra stuff to print after the message.
3163  * @param string $highlight Space-separated list of terms to highlight.
3164  * @param int $post_read true, false or -99. If we already know whether this user
3165  *          has read this post, pass that in, otherwise, pass in -99, and this
3166  *          function will work it out.
3167  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3168  *          the current user can't see this post, if this argument is true
3169  *          (the default) then print a dummy 'you can't see this post' post.
3170  *          If false, don't output anything at all.
3171  * @param bool|null $istracked
3172  * @return void
3173  */
3174 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3175                           $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3176     global $USER, $CFG, $OUTPUT;
3178     require_once($CFG->libdir . '/filelib.php');
3180     // String cache
3181     static $str;
3183     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3185     $post->course = $course->id;
3186     $post->forum  = $forum->id;
3187     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3189     // caching
3190     if (!isset($cm->cache)) {
3191         $cm->cache = new stdClass;
3192     }
3194     if (!isset($cm->cache->caps)) {
3195         $cm->cache->caps = array();
3196         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3197         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3198         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3199         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3200         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3201         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3202         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3203         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3204         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3205     }
3207     if (!isset($cm->uservisible)) {
3208         $cm->uservisible = coursemodule_visible_for_user($cm);
3209     }
3211     if ($istracked && is_null($postisread)) {
3212         $postisread = forum_tp_is_post_read($USER->id, $post);
3213     }
3215     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3216         $output = '';
3217         if (!$dummyifcantsee) {
3218             if ($return) {
3219                 return $output;
3220             }
3221             echo $output;
3222             return;
3223         }
3224         $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3225         $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'));
3226         $output .= html_writer::start_tag('div', array('class'=>'row header'));
3227         $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3228         if ($post->parent) {
3229             $output .= html_writer::start_tag('div', array('class'=>'topic'));
3230         } else {
3231             $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3232         }
3233         $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class'=>'subject')); // Subject
3234         $output .= html_writer::tag('div', get_string('forumauthorhidden','forum'), array('class'=>'author')); // author
3235         $output .= html_writer::end_tag('div');
3236         $output .= html_writer::end_tag('div'); // row
3237         $output .= html_writer::start_tag('div', array('class'=>'row'));
3238         $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3239         $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3240         $output .= html_writer::end_tag('div'); // row
3241         $output .= html_writer::end_tag('div'); // forumpost
3243         if ($return) {
3244             return $output;
3245         }
3246         echo $output;
3247         return;
3248     }
3250     if (empty($str)) {
3251         $str = new stdClass;
3252         $str->edit         = get_string('edit', 'forum');
3253         $str->delete       = get_string('delete', 'forum');
3254         $str->reply        = get_string('reply', 'forum');
3255         $str->parent       = get_string('parent', 'forum');
3256         $str->pruneheading = get_string('pruneheading', 'forum');
3257         $str->prune        = get_string('prune', 'forum');
3258         $str->displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3259         $str->markread     = get_string('markread', 'forum');
3260         $str->markunread   = get_string('markunread', 'forum');
3261     }
3263     $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3265     // Build an object that represents the posting user
3266     $postuser = new stdClass;
3267     $postuser->id        = $post->userid;
3268     $postuser->firstname = $post->firstname;
3269     $postuser->lastname  = $post->lastname;
3270     $postuser->imagealt  = $post->imagealt;
3271     $postuser->picture   = $post->picture;
3272     $postuser->email     = $post->email;
3273     // Some handy things for later on
3274     $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3275     $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3277     // Prepare the groups the posting user belongs to
3278     if (isset($cm->cache->usersgroups)) {
3279         $groups = array();
3280         if (isset($cm->cache->usersgroups[$post->userid])) {
3281             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3282                 $groups[$gid] = $cm->cache->groups[$gid];
3283             }
3284         }
3285     } else {
3286         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3287     }
3289     // Prepare the attachements for the post, files then images
3290     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3292     // Determine if we need to shorten this post
3293     $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3296     // Prepare an array of commands
3297     $commands = array();
3299     // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3300     // Don't display the mark read / unread controls in this case.
3301     if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3302         $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3303         $text = $str->markunread;
3304         if (!$postisread) {
3305             $url->param('mark', 'read');
3306             $text = $str->markread;
3307         }
3308         if ($str->displaymode == FORUM_MODE_THREADED) {
3309             $url->param('parent', $post->parent);
3310         } else {
3311             $url->set_anchor('p'.$post->id);
3312         }
3313         $commands[] = array('url'=>$url, 'text'=>$text);
3314     }
3316     // Zoom in to the parent specifically
3317     if ($post->parent) {
3318         $url = new moodle_url($discussionlink);
3319         if ($str->displaymode == FORUM_MODE_THREADED) {
3320             $url->param('parent', $post->parent);
3321         } else {
3322             $url->set_anchor('p'.$post->parent);
3323         }
3324         $commands[] = array('url'=>$url, 'text'=>$str->parent);
3325     }
3327     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3328     $age = time() - $post->created;
3329     if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3330         $age = 0;
3331     }
3333     if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
3334         if (has_capability('moodle/course:manageactivities', $modcontext)) {
3335             // The first post in single simple is the forum description.
3336             $commands[] = array('url'=>new moodle_url('/course/modedit.php', array('update'=>$cm->id, 'sesskey'=>sesskey(), 'return'=>1)), 'text'=>$str->edit);
3337         }
3338     } else if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3339         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3340     }
3342     if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3343         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3344     }
3346     if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
3347         // Do not allow deleting of first post in single simple type.
3348     } else if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3349         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3350     }
3352     if ($reply) {
3353         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php#mform1', array('reply'=>$post->id)), 'text'=>$str->reply);
3354     }
3356     if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
3357         $p = array('postid' => $post->id);
3358         require_once($CFG->libdir.'/portfoliolib.php');
3359         $button = new portfolio_add_button();
3360         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php');
3361         if (empty($attachments)) {
3362             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3363         } else {
3364             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3365         }
3367         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3368         if (!empty($porfoliohtml)) {
3369             $commands[] = $porfoliohtml;
3370         }
3371     }
3372     // Finished building commands
3375     // Begin output
3377     $output  = '';
3379     if ($istracked) {
3380         if ($postisread) {
3381             $forumpostclass = ' read';
3382         } else {
3383             $forumpostclass = ' unread';
3384             $output .= html_writer::tag('a', '', array('name'=>'unread'));
3385         }
3386     } else {
3387         // ignore trackign status if not tracked or tracked param missing
3388         $forumpostclass = '';
3389     }
3391     $topicclass = '';
3392     if (empty($post->parent)) {
3393         $topicclass = ' firstpost starter';
3394     }
3396     $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3397     $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass));
3398     $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3399     $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3400     $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3401     $output .= html_writer::end_tag('div');
3404     $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3406     $postsubject = $post->subject;
3407     if (empty($post->subjectnoformat)) {
3408         $postsubject = format_string($postsubject);
3409     }
3410     $output .= html_writer::tag('div', $postsubject, array('class'=>'subject'));
3412     $by = new stdClass();
3413     $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3414     $by->date = userdate($post->modified);
3415     $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author'));
3417     $output .= html_writer::end_tag('div'); //topic
3418     $output .= html_writer::end_tag('div'); //row
3420     $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3421     $output .= html_writer::start_tag('div', array('class'=>'left'));
3423     $groupoutput = '';
3424     if ($groups) {
3425         $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3426     }
3427     if (empty($groupoutput)) {
3428         $groupoutput = '&nbsp;';
3429     }
3430     $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3432     $output .= html_writer::end_tag('div'); //left side
3433     $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3434     $output .= html_writer::start_tag('div', array('class'=>'content'));
3435     if (!empty($attachments)) {
3436         $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3437     }
3439     $options = new stdClass;
3440     $options->para    = false;
3441     $options->trusted = $post->messagetrust;
3442     $options->context = $modcontext;
3443     if ($shortenpost) {
3444         // Prepare shortened version
3445         $postclass    = 'shortenedpost';
3446         $postcontent  = format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3447         $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
3448         $postcontent .= html_writer::tag('span', '('.get_string('numwords', 'moodle', count_words(strip_tags($post->message))).')...', array('class'=>'post-word-count'));
3449     } else {
3450         // Prepare whole post
3451         $postclass    = 'fullpost';
3452         $postcontent  = format_text($post->message, $post->messageformat, $options, $course->id);
3453         if (!empty($highlight)) {
3454             $postcontent = highlight($highlight, $postcontent);
3455         }
3456         $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
3457     }
3458     // Output the post content
3459     $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
3460     $output .= html_writer::end_tag('div'); // Content
3461     $output .= html_writer::end_tag('div'); // Content mask
3462     $output .= html_writer::end_tag('div'); // Row
3464     $output .= html_writer::start_tag('div', array('class'=>'row side'));
3465     $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
3466     $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
3468     // Output ratings
3469     if (!empty($post->rating)) {
3470         $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
3471     }
3473     // Output the commands
3474     $commandhtml = array();
3475     foreach ($commands as $command) {
3476         if (is_array($command)) {
3477             $commandhtml[] = html_writer::link($command['url'], $command['text']);
3478         } else {
3479             $commandhtml[] = $command;
3480         }
3481     }
3482     $output .= html_writer::tag('div', implode(' | ', $commandhtml), array('class'=>'commands'));
3484     // Output link to post if required
3485     if ($link) {
3486         if ($post->replies == 1) {
3487             $replystring = get_string('repliesone', 'forum', $post->replies);
3488         } else {
3489             $replystring = get_string('repliesmany', 'forum', $post->replies);
3490         }
3492         $output .= html_writer::start_tag('div', array('class'=>'link'));
3493         $output .= html_writer::link($discussionlink, get_string('discussthistopic', 'forum'));
3494         $output .= '&