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