Merge branch 'MDL-37953_master' of https://github.com/markn86/moodle
[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 define('FORUM_MAILED_PENDING', 0);
49 define('FORUM_MAILED_SUCCESS', 1);
50 define('FORUM_MAILED_ERROR', 2);
52 if (!defined('FORUM_CRON_USER_CACHE')) {
53     /** Defines how many full user records are cached in forum cron. */
54     define('FORUM_CRON_USER_CACHE', 5000);
55 }
57 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
59 /**
60  * Given an object containing all the necessary data,
61  * (defined by the form in mod_form.php) this function
62  * will create a new instance and return the id number
63  * of the new instance.
64  *
65  * @param stdClass $forum add forum instance
66  * @param mod_forum_mod_form $mform
67  * @return int intance id
68  */
69 function forum_add_instance($forum, $mform = null) {
70     global $CFG, $DB;
72     $forum->timemodified = time();
74     if (empty($forum->assessed)) {
75         $forum->assessed = 0;
76     }
78     if (empty($forum->ratingtime) or empty($forum->assessed)) {
79         $forum->assesstimestart  = 0;
80         $forum->assesstimefinish = 0;
81     }
83     $forum->id = $DB->insert_record('forum', $forum);
84     $modcontext = context_module::instance($forum->coursemodule);
86     if ($forum->type == 'single') {  // Create related discussion.
87         $discussion = new stdClass();
88         $discussion->course        = $forum->course;
89         $discussion->forum         = $forum->id;
90         $discussion->name          = $forum->name;
91         $discussion->assessed      = $forum->assessed;
92         $discussion->message       = $forum->intro;
93         $discussion->messageformat = $forum->introformat;
94         $discussion->messagetrust  = trusttext_trusted(context_course::instance($forum->course));
95         $discussion->mailnow       = false;
96         $discussion->groupid       = -1;
98         $message = '';
100         $discussion->id = forum_add_discussion($discussion, null, $message);
102         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
103             // Ugly hack - we need to copy the files somehow.
104             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
105             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
107             $options = array('subdirs'=>true); // Use the same options as intro field!
108             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
109             $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
110         }
111     }
113     if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
114         $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email');
115         foreach ($users as $user) {
116             forum_subscribe($user->id, $forum->id);
117         }
118     }
120     forum_grade_item_update($forum);
122     return $forum->id;
126 /**
127  * Given an object containing all the necessary data,
128  * (defined by the form in mod_form.php) this function
129  * will update an existing instance with new data.
130  *
131  * @global object
132  * @param object $forum forum instance (with magic quotes)
133  * @return bool success
134  */
135 function forum_update_instance($forum, $mform) {
136     global $DB, $OUTPUT, $USER;
138     $forum->timemodified = time();
139     $forum->id           = $forum->instance;
141     if (empty($forum->assessed)) {
142         $forum->assessed = 0;
143     }
145     if (empty($forum->ratingtime) or empty($forum->assessed)) {
146         $forum->assesstimestart  = 0;
147         $forum->assesstimefinish = 0;
148     }
150     $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
152     // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
153     // if  scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
154     // 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
155     if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
156         forum_update_grades($forum); // recalculate grades for the forum
157     }
159     if ($forum->type == 'single') {  // Update related discussion and post.
160         $discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC');
161         if (!empty($discussions)) {
162             if (count($discussions) > 1) {
163                 echo $OUTPUT->notification(get_string('warnformorepost', 'forum'));
164             }
165             $discussion = array_pop($discussions);
166         } else {
167             // try to recover by creating initial discussion - MDL-16262
168             $discussion = new stdClass();
169             $discussion->course          = $forum->course;
170             $discussion->forum           = $forum->id;
171             $discussion->name            = $forum->name;
172             $discussion->assessed        = $forum->assessed;
173             $discussion->message         = $forum->intro;
174             $discussion->messageformat   = $forum->introformat;
175             $discussion->messagetrust    = true;
176             $discussion->mailnow         = false;
177             $discussion->groupid         = -1;
179             $message = '';
181             forum_add_discussion($discussion, null, $message);
183             if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
184                 print_error('cannotadd', 'forum');
185             }
186         }
187         if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
188             print_error('cannotfindfirstpost', 'forum');
189         }
191         $cm         = get_coursemodule_from_instance('forum', $forum->id);
192         $modcontext = context_module::instance($cm->id, MUST_EXIST);
194         $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
195         $post->subject       = $forum->name;
196         $post->message       = $forum->intro;
197         $post->messageformat = $forum->introformat;
198         $post->messagetrust  = trusttext_trusted($modcontext);
199         $post->modified      = $forum->timemodified;
200         $post->userid        = $USER->id;    // MDL-18599, so that current teacher can take ownership of activities.
202         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
203             // Ugly hack - we need to copy the files somehow.
204             $options = array('subdirs'=>true); // Use the same options as intro field!
205             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
206         }
208         $DB->update_record('forum_posts', $post);
209         $discussion->name = $forum->name;
210         $DB->update_record('forum_discussions', $discussion);
211     }
213     $DB->update_record('forum', $forum);
215     forum_grade_item_update($forum);
217     return true;
221 /**
222  * Given an ID of an instance of this module,
223  * this function will permanently delete the instance
224  * and any data that depends on it.
225  *
226  * @global object
227  * @param int $id forum instance id
228  * @return bool success
229  */
230 function forum_delete_instance($id) {
231     global $DB;
233     if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
234         return false;
235     }
236     if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
237         return false;
238     }
239     if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
240         return false;
241     }
243     $context = context_module::instance($cm->id);
245     // now get rid of all files
246     $fs = get_file_storage();
247     $fs->delete_area_files($context->id);
249     $result = true;
251     if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
252         foreach ($discussions as $discussion) {
253             if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
254                 $result = false;
255             }
256         }
257     }
259     if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
260         $result = false;
261     }
263     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
265     if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
266         $result = false;
267     }
269     forum_grade_item_delete($forum);
271     return $result;
275 /**
276  * Indicates API features that the forum supports.
277  *
278  * @uses FEATURE_GROUPS
279  * @uses FEATURE_GROUPINGS
280  * @uses FEATURE_GROUPMEMBERSONLY
281  * @uses FEATURE_MOD_INTRO
282  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
283  * @uses FEATURE_COMPLETION_HAS_RULES
284  * @uses FEATURE_GRADE_HAS_GRADE
285  * @uses FEATURE_GRADE_OUTCOMES
286  * @param string $feature
287  * @return mixed True if yes (some features may use other values)
288  */
289 function forum_supports($feature) {
290     switch($feature) {
291         case FEATURE_GROUPS:                  return true;
292         case FEATURE_GROUPINGS:               return true;
293         case FEATURE_GROUPMEMBERSONLY:        return true;
294         case FEATURE_MOD_INTRO:               return true;
295         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
296         case FEATURE_COMPLETION_HAS_RULES:    return true;
297         case FEATURE_GRADE_HAS_GRADE:         return true;
298         case FEATURE_GRADE_OUTCOMES:          return true;
299         case FEATURE_RATE:                    return true;
300         case FEATURE_BACKUP_MOODLE2:          return true;
301         case FEATURE_SHOW_DESCRIPTION:        return true;
302         case FEATURE_PLAGIARISM:              return true;
304         default: return null;
305     }
309 /**
310  * Obtains the automatic completion state for this forum based on any conditions
311  * in forum settings.
312  *
313  * @global object
314  * @global object
315  * @param object $course Course
316  * @param object $cm Course-module
317  * @param int $userid User ID
318  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
319  * @return bool True if completed, false if not. (If no conditions, then return
320  *   value depends on comparison type)
321  */
322 function forum_get_completion_state($course,$cm,$userid,$type) {
323     global $CFG,$DB;
325     // Get forum details
326     if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
327         throw new Exception("Can't find forum {$cm->instance}");
328     }
330     $result=$type; // Default return value
332     $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
333     $postcountsql="
334 SELECT
335     COUNT(1)
336 FROM
337     {forum_posts} fp
338     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
339 WHERE
340     fp.userid=:userid AND fd.forum=:forumid";
342     if ($forum->completiondiscussions) {
343         $value = $forum->completiondiscussions <=
344                  $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
345         if ($type == COMPLETION_AND) {
346             $result = $result && $value;
347         } else {
348             $result = $result || $value;
349         }
350     }
351     if ($forum->completionreplies) {
352         $value = $forum->completionreplies <=
353                  $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
354         if ($type==COMPLETION_AND) {
355             $result = $result && $value;
356         } else {
357             $result = $result || $value;
358         }
359     }
360     if ($forum->completionposts) {
361         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
362         if ($type == COMPLETION_AND) {
363             $result = $result && $value;
364         } else {
365             $result = $result || $value;
366         }
367     }
369     return $result;
372 /**
373  * Create a message-id string to use in the custom headers of forum notification emails
374  *
375  * message-id is used by email clients to identify emails and to nest conversations
376  *
377  * @param int $postid The ID of the forum post we are notifying the user about
378  * @param int $usertoid The ID of the user being notified
379  * @param string $hostname The server's hostname
380  * @return string A unique message-id
381  */
382 function forum_get_email_message_id($postid, $usertoid, $hostname) {
383     return '<'.hash('sha256',$postid.'to'.$usertoid).'@'.$hostname.'>';
386 /**
387  * Removes properties from user record that are not necessary
388  * for sending post notifications.
389  * @param stdClass $user
390  * @return void, $user parameter is modified
391  */
392 function forum_cron_minimise_user_record(stdClass $user) {
394     // We store large amount of users in one huge array,
395     // make sure we do not store info there we do not actually need
396     // in mail generation code or messaging.
398     unset($user->institution);
399     unset($user->department);
400     unset($user->address);
401     unset($user->city);
402     unset($user->url);
403     unset($user->currentlogin);
404     unset($user->description);
405     unset($user->descriptionformat);
408 /**
409  * Function to be run periodically according to the moodle cron
410  * Finds all posts that have yet to be mailed out, and mails them
411  * out to all subscribers
412  *
413  * @global object
414  * @global object
415  * @global object
416  * @uses CONTEXT_MODULE
417  * @uses CONTEXT_COURSE
418  * @uses SITEID
419  * @uses FORMAT_PLAIN
420  * @return void
421  */
422 function forum_cron() {
423     global $CFG, $USER, $DB;
425     $site = get_site();
427     // All users that are subscribed to any post that needs sending,
428     // please increase $CFG->extramemorylimit on large sites that
429     // send notifications to a large number of users.
430     $users = array();
431     $userscount = 0; // Cached user counter - count($users) in PHP is horribly slow!!!
433     // status arrays
434     $mailcount  = array();
435     $errorcount = array();
437     // caches
438     $discussions     = array();
439     $forums          = array();
440     $courses         = array();
441     $coursemodules   = array();
442     $subscribedusers = array();
445     // Posts older than 2 days will not be mailed.  This is to avoid the problem where
446     // cron has not been running for a long time, and then suddenly people are flooded
447     // with mail from the past few weeks or months
448     $timenow   = time();
449     $endtime   = $timenow - $CFG->maxeditingtime;
450     $starttime = $endtime - 48 * 3600;   // Two days earlier
452     if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
453         // Mark them all now as being mailed.  It's unlikely but possible there
454         // might be an error later so that a post is NOT actually mailed out,
455         // but since mail isn't crucial, we can accept this risk.  Doing it now
456         // prevents the risk of duplicated mails, which is a worse problem.
458         if (!forum_mark_old_posts_as_mailed($endtime)) {
459             mtrace('Errors occurred while trying to mark some posts as being mailed.');
460             return false;  // Don't continue trying to mail them, in case we are in a cron loop
461         }
463         // checking post validity, and adding users to loop through later
464         foreach ($posts as $pid => $post) {
466             $discussionid = $post->discussion;
467             if (!isset($discussions[$discussionid])) {
468                 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
469                     $discussions[$discussionid] = $discussion;
470                 } else {
471                     mtrace('Could not find discussion '.$discussionid);
472                     unset($posts[$pid]);
473                     continue;
474                 }
475             }
476             $forumid = $discussions[$discussionid]->forum;
477             if (!isset($forums[$forumid])) {
478                 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
479                     $forums[$forumid] = $forum;
480                 } else {
481                     mtrace('Could not find forum '.$forumid);
482                     unset($posts[$pid]);
483                     continue;
484                 }
485             }
486             $courseid = $forums[$forumid]->course;
487             if (!isset($courses[$courseid])) {
488                 if ($course = $DB->get_record('course', array('id' => $courseid))) {
489                     $courses[$courseid] = $course;
490                 } else {
491                     mtrace('Could not find course '.$courseid);
492                     unset($posts[$pid]);
493                     continue;
494                 }
495             }
496             if (!isset($coursemodules[$forumid])) {
497                 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
498                     $coursemodules[$forumid] = $cm;
499                 } else {
500                     mtrace('Could not find course module for forum '.$forumid);
501                     unset($posts[$pid]);
502                     continue;
503                 }
504             }
507             // caching subscribed users of each forum
508             if (!isset($subscribedusers[$forumid])) {
509                 $modcontext = context_module::instance($coursemodules[$forumid]->id);
510                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext, "u.*")) {
511                     foreach ($subusers as $postuser) {
512                         // this user is subscribed to this forum
513                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
514                         $userscount++;
515                         if ($userscount > FORUM_CRON_USER_CACHE) {
516                             // Store minimal user info.
517                             $minuser = new stdClass();
518                             $minuser->id = $postuser->id;
519                             $users[$postuser->id] = $minuser;
520                         } else {
521                             // Cache full user record.
522                             forum_cron_minimise_user_record($postuser);
523                             $users[$postuser->id] = $postuser;
524                         }
525                     }
526                     // Release memory.
527                     unset($subusers);
528                     unset($postuser);
529                 }
530             }
532             $mailcount[$pid] = 0;
533             $errorcount[$pid] = 0;
534         }
535     }
537     if ($users && $posts) {
539         $urlinfo = parse_url($CFG->wwwroot);
540         $hostname = $urlinfo['host'];
542         foreach ($users as $userto) {
544             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
546             mtrace('Processing user '.$userto->id);
548             // Init user caches - we keep the cache for one cycle only,
549             // otherwise it could consume too much memory.
550             if (isset($userto->username)) {
551                 $userto = clone($userto);
552             } else {
553                 $userto = $DB->get_record('user', array('id' => $userto->id));
554                 forum_cron_minimise_user_record($userto);
555             }
556             $userto->viewfullnames = array();
557             $userto->canpost       = array();
558             $userto->markposts     = array();
560             // set this so that the capabilities are cached, and environment matches receiving user
561             cron_setup_user($userto);
563             // reset the caches
564             foreach ($coursemodules as $forumid=>$unused) {
565                 $coursemodules[$forumid]->cache       = new stdClass();
566                 $coursemodules[$forumid]->cache->caps = array();
567                 unset($coursemodules[$forumid]->uservisible);
568             }
570             foreach ($posts as $pid => $post) {
572                 // Set up the environment for the post, discussion, forum, course
573                 $discussion = $discussions[$post->discussion];
574                 $forum      = $forums[$discussion->forum];
575                 $course     = $courses[$forum->course];
576                 $cm         =& $coursemodules[$forum->id];
578                 // Do some checks  to see if we can bail out now
579                 // Only active enrolled users are in the list of subscribers
580                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
581                     continue; // user does not subscribe to this forum
582                 }
584                 // Don't send email if the forum is Q&A and the user has not posted
585                 // Initial topics are still mailed
586                 if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id) && $pid != $discussion->firstpost) {
587                     mtrace('Did not email '.$userto->id.' because user has not posted in discussion');
588                     continue;
589                 }
591                 // Get info about the sending user
592                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
593                     $userfrom = $users[$post->userid];
594                     if (!isset($userfrom->idnumber)) {
595                         // Minimalised user info, fetch full record.
596                         $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
597                         forum_cron_minimise_user_record($userfrom);
598                     }
600                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
601                     forum_cron_minimise_user_record($userfrom);
602                     // Fetch only once if possible, we can add it to user list, it will be skipped anyway.
603                     if ($userscount <= FORUM_CRON_USER_CACHE) {
604                         $userscount++;
605                         $users[$userfrom->id] = $userfrom;
606                     }
608                 } else {
609                     mtrace('Could not find user '.$post->userid);
610                     continue;
611                 }
613                 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
615                 // setup global $COURSE properly - needed for roles and languages
616                 cron_setup_user($userto, $course);
618                 // Fill caches
619                 if (!isset($userto->viewfullnames[$forum->id])) {
620                     $modcontext = context_module::instance($cm->id);
621                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
622                 }
623                 if (!isset($userto->canpost[$discussion->id])) {
624                     $modcontext = context_module::instance($cm->id);
625                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
626                 }
627                 if (!isset($userfrom->groups[$forum->id])) {
628                     if (!isset($userfrom->groups)) {
629                         $userfrom->groups = array();
630                         if (isset($users[$userfrom->id])) {
631                             $users[$userfrom->id]->groups = array();
632                         }
633                     }
634                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
635                     if (isset($users[$userfrom->id])) {
636                         $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
637                     }
638                 }
640                 // Make sure groups allow this user to see this email
641                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
642                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
643                         continue;                           // Be safe and don't send it to anyone
644                     }
646                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
647                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
648                         continue;
649                     }
650                 }
652                 // Make sure we're allowed to see it...
653                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
654                     mtrace('user '.$userto->id. ' can not see '.$post->id);
655                     continue;
656                 }
658                 // OK so we need to send the email.
660                 // Does the user want this post in a digest?  If so postpone it for now.
661                 if ($userto->maildigest > 0) {
662                     // This user wants the mails to be in digest form
663                     $queue = new stdClass();
664                     $queue->userid       = $userto->id;
665                     $queue->discussionid = $discussion->id;
666                     $queue->postid       = $post->id;
667                     $queue->timemodified = $post->created;
668                     $DB->insert_record('forum_queue', $queue);
669                     continue;
670                 }
673                 // Prepare to actually send the post now, and build up the content
675                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
677                 $userfrom->customheaders = array (  // Headers to make emails easier to track
678                            'Precedence: Bulk',
679                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
680                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
681                            'Message-ID: '.forum_get_email_message_id($post->id, $userto->id, $hostname),
682                            'X-Course-Id: '.$course->id,
683                            'X-Course-Name: '.format_string($course->fullname, true)
684                 );
686                 if ($post->parent) {  // This post is a reply, so add headers for threading (see MDL-22551)
687                     $userfrom->customheaders[] = 'In-Reply-To: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
688                     $userfrom->customheaders[] = 'References: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
689                 }
691                 $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
693                 $postsubject = html_to_text("$shortname: ".format_string($post->subject, true));
694                 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
695                 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
697                 // Send the post now!
699                 mtrace('Sending ', '');
701                 $eventdata = new stdClass();
702                 $eventdata->component        = 'mod_forum';
703                 $eventdata->name             = 'posts';
704                 $eventdata->userfrom         = $userfrom;
705                 $eventdata->userto           = $userto;
706                 $eventdata->subject          = $postsubject;
707                 $eventdata->fullmessage      = $posttext;
708                 $eventdata->fullmessageformat = FORMAT_PLAIN;
709                 $eventdata->fullmessagehtml  = $posthtml;
710                 $eventdata->notification = 1;
712                 // If forum_replytouser is not set then send mail using the noreplyaddress.
713                 if (empty($CFG->forum_replytouser)) {
714                     // Clone userfrom as it is referenced by $users.
715                     $cloneduserfrom = clone($userfrom);
716                     $cloneduserfrom->email = $CFG->noreplyaddress;
717                     $eventdata->userfrom = $cloneduserfrom;
718                 }
720                 $smallmessagestrings = new stdClass();
721                 $smallmessagestrings->user = fullname($userfrom);
722                 $smallmessagestrings->forumname = "$shortname: ".format_string($forum->name,true).": ".$discussion->name;
723                 $smallmessagestrings->message = $post->message;
724                 //make sure strings are in message recipients language
725                 $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
727                 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
728                 $eventdata->contexturlname = $discussion->name;
730                 $mailresult = message_send($eventdata);
731                 if (!$mailresult){
732                     mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
733                          " ($userto->email) .. not trying again.");
734                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
735                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
736                     $errorcount[$post->id]++;
737                 } else {
738                     $mailcount[$post->id]++;
740                 // Mark post as read if forum_usermarksread is set off
741                     if (!$CFG->forum_usermarksread) {
742                         $userto->markposts[$post->id] = $post->id;
743                     }
744                 }
746                 mtrace('post '.$post->id. ': '.$post->subject);
747             }
749             // mark processed posts as read
750             forum_tp_mark_posts_read($userto, $userto->markposts);
751             unset($userto);
752         }
753     }
755     if ($posts) {
756         foreach ($posts as $post) {
757             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
758             if ($errorcount[$post->id]) {
759                 $DB->set_field('forum_posts', 'mailed', FORUM_MAILED_ERROR, array('id' => $post->id));
760             }
761         }
762     }
764     // release some memory
765     unset($subscribedusers);
766     unset($mailcount);
767     unset($errorcount);
769     cron_setup_user();
771     $sitetimezone = $CFG->timezone;
773     // Now see if there are any digest mails waiting to be sent, and if we should send them
775     mtrace('Starting digest processing...');
777     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
779     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
780         set_config('digestmailtimelast', 0);
781     }
783     $timenow = time();
784     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
786     // Delete any really old ones (normally there shouldn't be any)
787     $weekago = $timenow - (7 * 24 * 3600);
788     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
789     mtrace ('Cleaned old digest records');
791     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
793         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
795         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
797         if ($digestposts_rs->valid()) {
799             // We have work to do
800             $usermailcount = 0;
802             //caches - reuse the those filled before too
803             $discussionposts = array();
804             $userdiscussions = array();
806             foreach ($digestposts_rs as $digestpost) {
807                 if (!isset($posts[$digestpost->postid])) {
808                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
809                         $posts[$digestpost->postid] = $post;
810                     } else {
811                         continue;
812                     }
813                 }
814                 $discussionid = $digestpost->discussionid;
815                 if (!isset($discussions[$discussionid])) {
816                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
817                         $discussions[$discussionid] = $discussion;
818                     } else {
819                         continue;
820                     }
821                 }
822                 $forumid = $discussions[$discussionid]->forum;
823                 if (!isset($forums[$forumid])) {
824                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
825                         $forums[$forumid] = $forum;
826                     } else {
827                         continue;
828                     }
829                 }
831                 $courseid = $forums[$forumid]->course;
832                 if (!isset($courses[$courseid])) {
833                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
834                         $courses[$courseid] = $course;
835                     } else {
836                         continue;
837                     }
838                 }
840                 if (!isset($coursemodules[$forumid])) {
841                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
842                         $coursemodules[$forumid] = $cm;
843                     } else {
844                         continue;
845                     }
846                 }
847                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
848                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
849             }
850             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
852             // Data collected, start sending out emails to each user
853             foreach ($userdiscussions as $userid => $thesediscussions) {
855                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
857                 cron_setup_user();
859                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
861                 // First of all delete all the queue entries for this user
862                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
864                 // Init user caches - we keep the cache for one cycle only,
865                 // otherwise it would unnecessarily consume memory.
866                 if (array_key_exists($userid, $users) and isset($users[$userid]->username)) {
867                     $userto = clone($users[$userid]);
868                 } else {
869                     $userto = $DB->get_record('user', array('id' => $userid));
870                     forum_cron_minimise_user_record($userto);
871                 }
872                 $userto->viewfullnames = array();
873                 $userto->canpost       = array();
874                 $userto->markposts     = array();
876                 // Override the language and timezone of the "current" user, so that
877                 // mail is customised for the receiver.
878                 cron_setup_user($userto);
880                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
882                 $headerdata = new stdClass();
883                 $headerdata->sitename = format_string($site->fullname, true);
884                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
886                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
887                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
889                 $posthtml = "<head>";
890 /*                foreach ($CFG->stylesheets as $stylesheet) {
891                     //TODO: MDL-21120
892                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
893                 }*/
894                 $posthtml .= "</head>\n<body id=\"email\">\n";
895                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
897                 foreach ($thesediscussions as $discussionid) {
899                     @set_time_limit(120);   // to be reset for each post
901                     $discussion = $discussions[$discussionid];
902                     $forum      = $forums[$discussion->forum];
903                     $course     = $courses[$forum->course];
904                     $cm         = $coursemodules[$forum->id];
906                     //override language
907                     cron_setup_user($userto, $course);
909                     // Fill caches
910                     if (!isset($userto->viewfullnames[$forum->id])) {
911                         $modcontext = context_module::instance($cm->id);
912                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
913                     }
914                     if (!isset($userto->canpost[$discussion->id])) {
915                         $modcontext = context_module::instance($cm->id);
916                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
917                     }
919                     $strforums      = get_string('forums', 'forum');
920                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
921                     $canreply       = $userto->canpost[$discussion->id];
922                     $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
924                     $posttext .= "\n \n";
925                     $posttext .= '=====================================================================';
926                     $posttext .= "\n \n";
927                     $posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
928                     if ($discussion->name != $forum->name) {
929                         $posttext  .= " -> ".format_string($discussion->name,true);
930                     }
931                     $posttext .= "\n";
933                     $posthtml .= "<p><font face=\"sans-serif\">".
934                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
935                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
936                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
937                     if ($discussion->name == $forum->name) {
938                         $posthtml .= "</font></p>";
939                     } else {
940                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
941                     }
942                     $posthtml .= '<p>';
944                     $postsarray = $discussionposts[$discussionid];
945                     sort($postsarray);
947                     foreach ($postsarray as $postid) {
948                         $post = $posts[$postid];
950                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
951                             $userfrom = $users[$post->userid];
952                             if (!isset($userfrom->idnumber)) {
953                                 $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
954                                 forum_cron_minimise_user_record($userfrom);
955                             }
957                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
958                             forum_cron_minimise_user_record($userfrom);
959                             if ($userscount <= FORUM_CRON_USER_CACHE) {
960                                 $userscount++;
961                                 $users[$userfrom->id] = $userfrom;
962                             }
964                         } else {
965                             mtrace('Could not find user '.$post->userid);
966                             continue;
967                         }
969                         if (!isset($userfrom->groups[$forum->id])) {
970                             if (!isset($userfrom->groups)) {
971                                 $userfrom->groups = array();
972                                 if (isset($users[$userfrom->id])) {
973                                     $users[$userfrom->id]->groups = array();
974                                 }
975                             }
976                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
977                             if (isset($users[$userfrom->id])) {
978                                 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
979                             }
980                         }
982                         $userfrom->customheaders = array ("Precedence: Bulk");
984                         if ($userto->maildigest == 2) {
985                             // Subjects only
986                             $by = new stdClass();
987                             $by->name = fullname($userfrom);
988                             $by->date = userdate($post->modified);
989                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
990                             $posttext .= "\n---------------------------------------------------------------------";
992                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
993                             $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>';
995                         } else {
996                             // The full treatment
997                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
998                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1000                         // Create an array of postid's for this user to mark as read.
1001                             if (!$CFG->forum_usermarksread) {
1002                                 $userto->markposts[$post->id] = $post->id;
1003                             }
1004                         }
1005                     }
1006                     if ($canunsubscribe) {
1007                         $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>";
1008                     } else {
1009                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
1010                     }
1011                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
1012                 }
1013                 $posthtml .= '</body>';
1015                 if (empty($userto->mailformat) || $userto->mailformat != 1) {
1016                     // This user DOESN'T want to receive HTML
1017                     $posthtml = '';
1018                 }
1020                 $attachment = $attachname='';
1021                 // Directly email forum digests rather than sending them via messaging, use the
1022                 // site shortname as 'from name', the noreply address will be used by email_to_user.
1023                 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname);
1025                 if (!$mailresult) {
1026                     mtrace("ERROR!");
1027                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
1028                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
1029                 } else {
1030                     mtrace("success.");
1031                     $usermailcount++;
1033                     // Mark post as read if forum_usermarksread is set off
1034                     forum_tp_mark_posts_read($userto, $userto->markposts);
1035                 }
1036             }
1037         }
1038     /// We have finishied all digest emails, update $CFG->digestmailtimelast
1039         set_config('digestmailtimelast', $timenow);
1040     }
1042     cron_setup_user();
1044     if (!empty($usermailcount)) {
1045         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
1046     }
1048     if (!empty($CFG->forum_lastreadclean)) {
1049         $timenow = time();
1050         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
1051             set_config('forum_lastreadclean', $timenow);
1052             mtrace('Removing old forum read tracking info...');
1053             forum_tp_clean_read_records();
1054         }
1055     } else {
1056         set_config('forum_lastreadclean', time());
1057     }
1060     return true;
1063 /**
1064  * Builds and returns the body of the email notification in plain text.
1065  *
1066  * @global object
1067  * @global object
1068  * @uses CONTEXT_MODULE
1069  * @param object $course
1070  * @param object $cm
1071  * @param object $forum
1072  * @param object $discussion
1073  * @param object $post
1074  * @param object $userfrom
1075  * @param object $userto
1076  * @param boolean $bare
1077  * @return string The email body in plain text format.
1078  */
1079 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
1080     global $CFG, $USER;
1082     $modcontext = context_module::instance($cm->id);
1084     if (!isset($userto->viewfullnames[$forum->id])) {
1085         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
1086     } else {
1087         $viewfullnames = $userto->viewfullnames[$forum->id];
1088     }
1090     if (!isset($userto->canpost[$discussion->id])) {
1091         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
1092     } else {
1093         $canreply = $userto->canpost[$discussion->id];
1094     }
1096     $by = New stdClass;
1097     $by->name = fullname($userfrom, $viewfullnames);
1098     $by->date = userdate($post->modified, "", $userto->timezone);
1100     $strbynameondate = get_string('bynameondate', 'forum', $by);
1102     $strforums = get_string('forums', 'forum');
1104     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1106     $posttext = '';
1108     if (!$bare) {
1109         $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
1110         $posttext  = "$shortname -> $strforums -> ".format_string($forum->name,true);
1112         if ($discussion->name != $forum->name) {
1113             $posttext  .= " -> ".format_string($discussion->name,true);
1114         }
1115     }
1117     // add absolute file links
1118     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
1120     $posttext .= "\n---------------------------------------------------------------------\n";
1121     $posttext .= format_string($post->subject,true);
1122     if ($bare) {
1123         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1124     }
1125     $posttext .= "\n".$strbynameondate."\n";
1126     $posttext .= "---------------------------------------------------------------------\n";
1127     $posttext .= format_text_email($post->message, $post->messageformat);
1128     $posttext .= "\n\n";
1129     $posttext .= forum_print_attachments($post, $cm, "text");
1131     if (!$bare && $canreply) {
1132         $posttext .= "---------------------------------------------------------------------\n";
1133         $posttext .= get_string("postmailinfo", "forum", $shortname)."\n";
1134         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1135     }
1136     if (!$bare && $canunsubscribe) {
1137         $posttext .= "\n---------------------------------------------------------------------\n";
1138         $posttext .= get_string("unsubscribe", "forum");
1139         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1140     }
1142     return $posttext;
1145 /**
1146  * Builds and returns the body of the email notification in html format.
1147  *
1148  * @global object
1149  * @param object $course
1150  * @param object $cm
1151  * @param object $forum
1152  * @param object $discussion
1153  * @param object $post
1154  * @param object $userfrom
1155  * @param object $userto
1156  * @return string The email text in HTML format
1157  */
1158 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1159     global $CFG;
1161     if ($userto->mailformat != 1) {  // Needs to be HTML
1162         return '';
1163     }
1165     if (!isset($userto->canpost[$discussion->id])) {
1166         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course);
1167     } else {
1168         $canreply = $userto->canpost[$discussion->id];
1169     }
1171     $strforums = get_string('forums', 'forum');
1172     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1173     $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
1175     $posthtml = '<head>';
1176 /*    foreach ($CFG->stylesheets as $stylesheet) {
1177         //TODO: MDL-21120
1178         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1179     }*/
1180     $posthtml .= '</head>';
1181     $posthtml .= "\n<body id=\"email\">\n\n";
1183     $posthtml .= '<div class="navbar">'.
1184     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$shortname.'</a> &raquo; '.
1185     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1186     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1187     if ($discussion->name == $forum->name) {
1188         $posthtml .= '</div>';
1189     } else {
1190         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1191                      format_string($discussion->name,true).'</a></div>';
1192     }
1193     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1195     if ($canunsubscribe) {
1196         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1197                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1198                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1199     }
1201     $posthtml .= '</body>';
1203     return $posthtml;
1207 /**
1208  *
1209  * @param object $course
1210  * @param object $user
1211  * @param object $mod TODO this is not used in this function, refactor
1212  * @param object $forum
1213  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1214  */
1215 function forum_user_outline($course, $user, $mod, $forum) {
1216     global $CFG;
1217     require_once("$CFG->libdir/gradelib.php");
1218     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1219     if (empty($grades->items[0]->grades)) {
1220         $grade = false;
1221     } else {
1222         $grade = reset($grades->items[0]->grades);
1223     }
1225     $count = forum_count_user_posts($forum->id, $user->id);
1227     if ($count && $count->postcount > 0) {
1228         $result = new stdClass();
1229         $result->info = get_string("numposts", "forum", $count->postcount);
1230         $result->time = $count->lastpost;
1231         if ($grade) {
1232             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1233         }
1234         return $result;
1235     } else if ($grade) {
1236         $result = new stdClass();
1237         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1239         //datesubmitted == time created. dategraded == time modified or time overridden
1240         //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
1241         //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
1242         if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1243             $result->time = $grade->dategraded;
1244         } else {
1245             $result->time = $grade->datesubmitted;
1246         }
1248         return $result;
1249     }
1250     return NULL;
1254 /**
1255  * @global object
1256  * @global object
1257  * @param object $coure
1258  * @param object $user
1259  * @param object $mod
1260  * @param object $forum
1261  */
1262 function forum_user_complete($course, $user, $mod, $forum) {
1263     global $CFG,$USER, $OUTPUT;
1264     require_once("$CFG->libdir/gradelib.php");
1266     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1267     if (!empty($grades->items[0]->grades)) {
1268         $grade = reset($grades->items[0]->grades);
1269         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1270         if ($grade->str_feedback) {
1271             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1272         }
1273     }
1275     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1277         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1278             print_error('invalidcoursemodule');
1279         }
1280         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1282         foreach ($posts as $post) {
1283             if (!isset($discussions[$post->discussion])) {
1284                 continue;
1285             }
1286             $discussion = $discussions[$post->discussion];
1288             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1289         }
1290     } else {
1291         echo "<p>".get_string("noposts", "forum")."</p>";
1292     }
1300 /**
1301  * @global object
1302  * @global object
1303  * @global object
1304  * @param array $courses
1305  * @param array $htmlarray
1306  */
1307 function forum_print_overview($courses,&$htmlarray) {
1308     global $USER, $CFG, $DB, $SESSION;
1310     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1311         return array();
1312     }
1314     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1315         return;
1316     }
1318     // Courses to search for new posts
1319     $coursessqls = array();
1320     $params = array();
1321     foreach ($courses as $course) {
1323         // If the user has never entered into the course all posts are pending
1324         if ($course->lastaccess == 0) {
1325             $coursessqls[] = '(f.course = ?)';
1326             $params[] = $course->id;
1328         // Only posts created after the course last access
1329         } else {
1330             $coursessqls[] = '(f.course = ? AND p.created > ?)';
1331             $params[] = $course->id;
1332             $params[] = $course->lastaccess;
1333         }
1334     }
1335     $params[] = $USER->id;
1336     $coursessql = implode(' OR ', $coursessqls);
1338     $sql = "SELECT f.id, COUNT(*) as count "
1339                 .'FROM {forum} f '
1340                 .'JOIN {forum_discussions} d ON d.forum  = f.id '
1341                 .'JOIN {forum_posts} p ON p.discussion = d.id '
1342                 ."WHERE ($coursessql) "
1343                 .'AND p.userid != ? '
1344                 .'GROUP BY f.id';
1346     if (!$new = $DB->get_records_sql($sql, $params)) {
1347         $new = array(); // avoid warnings
1348     }
1350     // also get all forum tracking stuff ONCE.
1351     $trackingforums = array();
1352     foreach ($forums as $forum) {
1353         if (forum_tp_can_track_forums($forum)) {
1354             $trackingforums[$forum->id] = $forum;
1355         }
1356     }
1358     if (count($trackingforums) > 0) {
1359         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1360         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1361             ' FROM {forum_posts} p '.
1362             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1363             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1364         $params = array($USER->id);
1366         foreach ($trackingforums as $track) {
1367             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1368             $params[] = $track->id;
1369             if (isset($SESSION->currentgroup[$track->course])) {
1370                 $groupid =  $SESSION->currentgroup[$track->course];
1371             } else {
1372                 // get first groupid
1373                 $groupids = groups_get_all_groups($track->course, $USER->id);
1374                 if ($groupids) {
1375                     reset($groupids);
1376                     $groupid = key($groupids);
1377                     $SESSION->currentgroup[$track->course] = $groupid;
1378                 } else {
1379                     $groupid = 0;
1380                 }
1381                 unset($groupids);
1382             }
1383             $params[] = $groupid;
1384         }
1385         $sql = substr($sql,0,-3); // take off the last OR
1386         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1387         $params[] = $cutoffdate;
1389         if (!$unread = $DB->get_records_sql($sql, $params)) {
1390             $unread = array();
1391         }
1392     } else {
1393         $unread = array();
1394     }
1396     if (empty($unread) and empty($new)) {
1397         return;
1398     }
1400     $strforum = get_string('modulename','forum');
1402     foreach ($forums as $forum) {
1403         $str = '';
1404         $count = 0;
1405         $thisunread = 0;
1406         $showunread = false;
1407         // either we have something from logs, or trackposts, or nothing.
1408         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1409             $count = $new[$forum->id]->count;
1410         }
1411         if (array_key_exists($forum->id,$unread)) {
1412             $thisunread = $unread[$forum->id]->count;
1413             $showunread = true;
1414         }
1415         if ($count > 0 || $thisunread > 0) {
1416             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1417                 $forum->name.'</a></div>';
1418             $str .= '<div class="info"><span class="postsincelogin">';
1419             $str .= get_string('overviewnumpostssince', 'forum', $count)."</span>";
1420             if (!empty($showunread)) {
1421                 $str .= '<div class="unreadposts">'.get_string('overviewnumunread', 'forum', $thisunread).'</div>';
1422             }
1423             $str .= '</div></div>';
1424         }
1425         if (!empty($str)) {
1426             if (!array_key_exists($forum->course,$htmlarray)) {
1427                 $htmlarray[$forum->course] = array();
1428             }
1429             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1430                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1431             }
1432             $htmlarray[$forum->course]['forum'] .= $str;
1433         }
1434     }
1437 /**
1438  * Given a course and a date, prints a summary of all the new
1439  * messages posted in the course since that date
1440  *
1441  * @global object
1442  * @global object
1443  * @global object
1444  * @uses CONTEXT_MODULE
1445  * @uses VISIBLEGROUPS
1446  * @param object $course
1447  * @param bool $viewfullnames capability
1448  * @param int $timestart
1449  * @return bool success
1450  */
1451 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1452     global $CFG, $USER, $DB, $OUTPUT;
1454     // do not use log table if possible, it may be huge and is expensive to join with other tables
1456     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1457                                               d.timestart, d.timeend, d.userid AS duserid,
1458                                               u.firstname, u.lastname, u.email, u.picture
1459                                          FROM {forum_posts} p
1460                                               JOIN {forum_discussions} d ON d.id = p.discussion
1461                                               JOIN {forum} f             ON f.id = d.forum
1462                                               JOIN {user} u              ON u.id = p.userid
1463                                         WHERE p.created > ? AND f.course = ?
1464                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1465          return false;
1466     }
1468     $modinfo = get_fast_modinfo($course);
1470     $groupmodes = array();
1471     $cms    = array();
1473     $strftimerecent = get_string('strftimerecent');
1475     $printposts = array();
1476     foreach ($posts as $post) {
1477         if (!isset($modinfo->instances['forum'][$post->forum])) {
1478             // not visible
1479             continue;
1480         }
1481         $cm = $modinfo->instances['forum'][$post->forum];
1482         if (!$cm->uservisible) {
1483             continue;
1484         }
1485         $context = context_module::instance($cm->id);
1487         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1488             continue;
1489         }
1491         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1492           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1493             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1494                 continue;
1495             }
1496         }
1498         $groupmode = groups_get_activity_groupmode($cm, $course);
1500         if ($groupmode) {
1501             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1502                 // oki (Open discussions have groupid -1)
1503             } else {
1504                 // separate mode
1505                 if (isguestuser()) {
1506                     // shortcut
1507                     continue;
1508                 }
1510                 if (is_null($modinfo->groups)) {
1511                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1512                 }
1514                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1515                     continue;
1516                 }
1517             }
1518         }
1520         $printposts[] = $post;
1521     }
1522     unset($posts);
1524     if (!$printposts) {
1525         return false;
1526     }
1528     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1529     echo "\n<ul class='unlist'>\n";
1531     foreach ($printposts as $post) {
1532         $subjectclass = empty($post->parent) ? ' bold' : '';
1534         echo '<li><div class="head">'.
1535                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1536                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1537              '</div>';
1538         echo '<div class="info'.$subjectclass.'">';
1539         if (empty($post->parent)) {
1540             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1541         } else {
1542             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1543         }
1544         $post->subject = break_up_long_words(format_string($post->subject, true));
1545         echo $post->subject;
1546         echo "</a>\"</div></li>\n";
1547     }
1549     echo "</ul>\n";
1551     return true;
1554 /**
1555  * Return grade for given user or all users.
1556  *
1557  * @global object
1558  * @global object
1559  * @param object $forum
1560  * @param int $userid optional user id, 0 means all users
1561  * @return array array of grades, false if none
1562  */
1563 function forum_get_user_grades($forum, $userid = 0) {
1564     global $CFG;
1566     require_once($CFG->dirroot.'/rating/lib.php');
1568     $ratingoptions = new stdClass;
1569     $ratingoptions->component = 'mod_forum';
1570     $ratingoptions->ratingarea = 'post';
1572     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1573     $ratingoptions->modulename = 'forum';
1574     $ratingoptions->moduleid   = $forum->id;
1575     $ratingoptions->userid = $userid;
1576     $ratingoptions->aggregationmethod = $forum->assessed;
1577     $ratingoptions->scaleid = $forum->scale;
1578     $ratingoptions->itemtable = 'forum_posts';
1579     $ratingoptions->itemtableusercolumn = 'userid';
1581     $rm = new rating_manager();
1582     return $rm->get_user_grades($ratingoptions);
1585 /**
1586  * Update activity grades
1587  *
1588  * @category grade
1589  * @param object $forum
1590  * @param int $userid specific user only, 0 means all
1591  * @param boolean $nullifnone return null if grade does not exist
1592  * @return void
1593  */
1594 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1595     global $CFG, $DB;
1596     require_once($CFG->libdir.'/gradelib.php');
1598     if (!$forum->assessed) {
1599         forum_grade_item_update($forum);
1601     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1602         forum_grade_item_update($forum, $grades);
1604     } else if ($userid and $nullifnone) {
1605         $grade = new stdClass();
1606         $grade->userid   = $userid;
1607         $grade->rawgrade = NULL;
1608         forum_grade_item_update($forum, $grade);
1610     } else {
1611         forum_grade_item_update($forum);
1612     }
1615 /**
1616  * Update all grades in gradebook.
1617  * @global object
1618  */
1619 function forum_upgrade_grades() {
1620     global $DB;
1622     $sql = "SELECT COUNT('x')
1623               FROM {forum} f, {course_modules} cm, {modules} m
1624              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1625     $count = $DB->count_records_sql($sql);
1627     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1628               FROM {forum} f, {course_modules} cm, {modules} m
1629              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1630     $rs = $DB->get_recordset_sql($sql);
1631     if ($rs->valid()) {
1632         $pbar = new progress_bar('forumupgradegrades', 500, true);
1633         $i=0;
1634         foreach ($rs as $forum) {
1635             $i++;
1636             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1637             forum_update_grades($forum, 0, false);
1638             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1639         }
1640     }
1641     $rs->close();
1644 /**
1645  * Create/update grade item for given forum
1646  *
1647  * @category grade
1648  * @uses GRADE_TYPE_NONE
1649  * @uses GRADE_TYPE_VALUE
1650  * @uses GRADE_TYPE_SCALE
1651  * @param stdClass $forum Forum object with extra cmidnumber
1652  * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
1653  * @return int 0 if ok
1654  */
1655 function forum_grade_item_update($forum, $grades=NULL) {
1656     global $CFG;
1657     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1658         require_once($CFG->libdir.'/gradelib.php');
1659     }
1661     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1663     if (!$forum->assessed or $forum->scale == 0) {
1664         $params['gradetype'] = GRADE_TYPE_NONE;
1666     } else if ($forum->scale > 0) {
1667         $params['gradetype'] = GRADE_TYPE_VALUE;
1668         $params['grademax']  = $forum->scale;
1669         $params['grademin']  = 0;
1671     } else if ($forum->scale < 0) {
1672         $params['gradetype'] = GRADE_TYPE_SCALE;
1673         $params['scaleid']   = -$forum->scale;
1674     }
1676     if ($grades  === 'reset') {
1677         $params['reset'] = true;
1678         $grades = NULL;
1679     }
1681     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1684 /**
1685  * Delete grade item for given forum
1686  *
1687  * @category grade
1688  * @param stdClass $forum Forum object
1689  * @return grade_item
1690  */
1691 function forum_grade_item_delete($forum) {
1692     global $CFG;
1693     require_once($CFG->libdir.'/gradelib.php');
1695     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1699 /**
1700  * This function returns if a scale is being used by one forum
1701  *
1702  * @global object
1703  * @param int $forumid
1704  * @param int $scaleid negative number
1705  * @return bool
1706  */
1707 function forum_scale_used ($forumid,$scaleid) {
1708     global $DB;
1709     $return = false;
1711     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1713     if (!empty($rec) && !empty($scaleid)) {
1714         $return = true;
1715     }
1717     return $return;
1720 /**
1721  * Checks if scale is being used by any instance of forum
1722  *
1723  * This is used to find out if scale used anywhere
1724  *
1725  * @global object
1726  * @param $scaleid int
1727  * @return boolean True if the scale is used by any forum
1728  */
1729 function forum_scale_used_anywhere($scaleid) {
1730     global $DB;
1731     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1732         return true;
1733     } else {
1734         return false;
1735     }
1738 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1740 /**
1741  * Gets a post with all info ready for forum_print_post
1742  * Most of these joins are just to get the forum id
1743  *
1744  * @global object
1745  * @global object
1746  * @param int $postid
1747  * @return mixed array of posts or false
1748  */
1749 function forum_get_post_full($postid) {
1750     global $CFG, $DB;
1752     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1753                              FROM {forum_posts} p
1754                                   JOIN {forum_discussions} d ON p.discussion = d.id
1755                                   LEFT JOIN {user} u ON p.userid = u.id
1756                             WHERE p.id = ?", array($postid));
1759 /**
1760  * Gets posts with all info ready for forum_print_post
1761  * We pass forumid in because we always know it so no need to make a
1762  * complicated join to find it out.
1763  *
1764  * @global object
1765  * @global object
1766  * @return mixed array of posts or false
1767  */
1768 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1769     global $CFG, $DB;
1771     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1772                               FROM {forum_posts} p
1773                          LEFT JOIN {user} u ON p.userid = u.id
1774                              WHERE p.discussion = ?
1775                                AND p.parent > 0 $sort", array($discussion));
1778 /**
1779  * Gets all posts in discussion including top parent.
1780  *
1781  * @global object
1782  * @global object
1783  * @global object
1784  * @param int $discussionid
1785  * @param string $sort
1786  * @param bool $tracking does user track the forum?
1787  * @return array of posts
1788  */
1789 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1790     global $CFG, $DB, $USER;
1792     $tr_sel  = "";
1793     $tr_join = "";
1794     $params = array();
1796     if ($tracking) {
1797         $now = time();
1798         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1799         $tr_sel  = ", fr.id AS postread";
1800         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1801         $params[] = $USER->id;
1802     }
1804     $params[] = $discussionid;
1805     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1806                                      FROM {forum_posts} p
1807                                           LEFT JOIN {user} u ON p.userid = u.id
1808                                           $tr_join
1809                                     WHERE p.discussion = ?
1810                                  ORDER BY $sort", $params)) {
1811         return array();
1812     }
1814     foreach ($posts as $pid=>$p) {
1815         if ($tracking) {
1816             if (forum_tp_is_post_old($p)) {
1817                  $posts[$pid]->postread = true;
1818             }
1819         }
1820         if (!$p->parent) {
1821             continue;
1822         }
1823         if (!isset($posts[$p->parent])) {
1824             continue; // parent does not exist??
1825         }
1826         if (!isset($posts[$p->parent]->children)) {
1827             $posts[$p->parent]->children = array();
1828         }
1829         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1830     }
1832     return $posts;
1835 /**
1836  * Gets posts with all info ready for forum_print_post
1837  * We pass forumid in because we always know it so no need to make a
1838  * complicated join to find it out.
1839  *
1840  * @global object
1841  * @global object
1842  * @param int $parent
1843  * @param int $forumid
1844  * @return array
1845  */
1846 function forum_get_child_posts($parent, $forumid) {
1847     global $CFG, $DB;
1849     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1850                               FROM {forum_posts} p
1851                          LEFT JOIN {user} u ON p.userid = u.id
1852                              WHERE p.parent = ?
1853                           ORDER BY p.created ASC", array($parent));
1856 /**
1857  * An array of forum objects that the user is allowed to read/search through.
1858  *
1859  * @global object
1860  * @global object
1861  * @global object
1862  * @param int $userid
1863  * @param int $courseid if 0, we look for forums throughout the whole site.
1864  * @return array of forum objects, or false if no matches
1865  *         Forum objects have the following attributes:
1866  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1867  *         viewhiddentimedposts
1868  */
1869 function forum_get_readable_forums($userid, $courseid=0) {
1871     global $CFG, $DB, $USER;
1872     require_once($CFG->dirroot.'/course/lib.php');
1874     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1875         print_error('notinstalled', 'forum');
1876     }
1878     if ($courseid) {
1879         $courses = $DB->get_records('course', array('id' => $courseid));
1880     } else {
1881         // If no course is specified, then the user can see SITE + his courses.
1882         $courses1 = $DB->get_records('course', array('id' => SITEID));
1883         $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
1884         $courses = array_merge($courses1, $courses2);
1885     }
1886     if (!$courses) {
1887         return array();
1888     }
1890     $readableforums = array();
1892     foreach ($courses as $course) {
1894         $modinfo = get_fast_modinfo($course);
1895         if (is_null($modinfo->groups)) {
1896             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1897         }
1899         if (empty($modinfo->instances['forum'])) {
1900             // hmm, no forums?
1901             continue;
1902         }
1904         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1906         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1907             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1908                 continue;
1909             }
1910             $context = context_module::instance($cm->id);
1911             $forum = $courseforums[$forumid];
1912             $forum->context = $context;
1913             $forum->cm = $cm;
1915             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1916                 continue;
1917             }
1919          /// group access
1920             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1921                 if (is_null($modinfo->groups)) {
1922                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1923                 }
1924                 if (isset($modinfo->groups[$cm->groupingid])) {
1925                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1926                     $forum->onlygroups[] = -1;
1927                 } else {
1928                     $forum->onlygroups = array(-1);
1929                 }
1930             }
1932         /// hidden timed discussions
1933             $forum->viewhiddentimedposts = true;
1934             if (!empty($CFG->forum_enabletimedposts)) {
1935                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1936                     $forum->viewhiddentimedposts = false;
1937                 }
1938             }
1940         /// qanda access
1941             if ($forum->type == 'qanda'
1942                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1944                 // We need to check whether the user has posted in the qanda forum.
1945                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1946                                                     // the user is allowed to see in this forum.
1947                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1948                     foreach ($discussionspostedin as $d) {
1949                         $forum->onlydiscussions[] = $d->id;
1950                     }
1951                 }
1952             }
1954             $readableforums[$forum->id] = $forum;
1955         }
1957         unset($modinfo);
1959     } // End foreach $courses
1961     return $readableforums;
1964 /**
1965  * Returns a list of posts found using an array of search terms.
1966  *
1967  * @global object
1968  * @global object
1969  * @global object
1970  * @param array $searchterms array of search terms, e.g. word +word -word
1971  * @param int $courseid if 0, we search through the whole site
1972  * @param int $limitfrom
1973  * @param int $limitnum
1974  * @param int &$totalcount
1975  * @param string $extrasql
1976  * @return array|bool Array of posts found or false
1977  */
1978 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1979                             &$totalcount, $extrasql='') {
1980     global $CFG, $DB, $USER;
1981     require_once($CFG->libdir.'/searchlib.php');
1983     $forums = forum_get_readable_forums($USER->id, $courseid);
1985     if (count($forums) == 0) {
1986         $totalcount = 0;
1987         return false;
1988     }
1990     $now = round(time(), -2); // db friendly
1992     $fullaccess = array();
1993     $where = array();
1994     $params = array();
1996     foreach ($forums as $forumid => $forum) {
1997         $select = array();
1999         if (!$forum->viewhiddentimedposts) {
2000             $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
2001             $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
2002         }
2004         $cm = $forum->cm;
2005         $context = $forum->context;
2007         if ($forum->type == 'qanda'
2008             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
2009             if (!empty($forum->onlydiscussions)) {
2010                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
2011                 $params = array_merge($params, $discussionid_params);
2012                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
2013             } else {
2014                 $select[] = "p.parent = 0";
2015             }
2016         }
2018         if (!empty($forum->onlygroups)) {
2019             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
2020             $params = array_merge($params, $groupid_params);
2021             $select[] = "d.groupid $groupid_sql";
2022         }
2024         if ($select) {
2025             $selects = implode(" AND ", $select);
2026             $where[] = "(d.forum = :forum{$forumid} AND $selects)";
2027             $params['forum'.$forumid] = $forumid;
2028         } else {
2029             $fullaccess[] = $forumid;
2030         }
2031     }
2033     if ($fullaccess) {
2034         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
2035         $params = array_merge($params, $fullid_params);
2036         $where[] = "(d.forum $fullid_sql)";
2037     }
2039     $selectdiscussion = "(".implode(" OR ", $where).")";
2041     $messagesearch = '';
2042     $searchstring = '';
2044     // Need to concat these back together for parser to work.
2045     foreach($searchterms as $searchterm){
2046         if ($searchstring != '') {
2047             $searchstring .= ' ';
2048         }
2049         $searchstring .= $searchterm;
2050     }
2052     // We need to allow quoted strings for the search. The quotes *should* be stripped
2053     // by the parser, but this should be examined carefully for security implications.
2054     $searchstring = str_replace("\\\"","\"",$searchstring);
2055     $parser = new search_parser();
2056     $lexer = new search_lexer($parser);
2058     if ($lexer->parse($searchstring)) {
2059         $parsearray = $parser->get_parsed_array();
2060     // Experimental feature under 1.8! MDL-8830
2061     // Use alternative text searches if defined
2062     // This feature only works under mysql until properly implemented for other DBs
2063     // Requires manual creation of text index for forum_posts before enabling it:
2064     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2065     // Experimental feature under 1.8! MDL-8830
2066         if (!empty($CFG->forum_usetextsearches)) {
2067             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2068                                                  'p.userid', 'u.id', 'u.firstname',
2069                                                  'u.lastname', 'p.modified', 'd.forum');
2070         } else {
2071             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2072                                                  'p.userid', 'u.id', 'u.firstname',
2073                                                  'u.lastname', 'p.modified', 'd.forum');
2074         }
2075         $params = array_merge($params, $msparams);
2076     }
2078     $fromsql = "{forum_posts} p,
2079                   {forum_discussions} d,
2080                   {user} u";
2082     $selectsql = " $messagesearch
2083                AND p.discussion = d.id
2084                AND p.userid = u.id
2085                AND $selectdiscussion
2086                    $extrasql";
2088     $countsql = "SELECT COUNT(*)
2089                    FROM $fromsql
2090                   WHERE $selectsql";
2092     $searchsql = "SELECT p.*,
2093                          d.forum,
2094                          u.firstname,
2095                          u.lastname,
2096                          u.email,
2097                          u.picture,
2098                          u.imagealt
2099                     FROM $fromsql
2100                    WHERE $selectsql
2101                 ORDER BY p.modified DESC";
2103     $totalcount = $DB->count_records_sql($countsql, $params);
2105     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2108 /**
2109  * Returns a list of ratings for a particular post - sorted.
2110  *
2111  * TODO: Check if this function is actually used anywhere.
2112  * Up until the fix for MDL-27471 this function wasn't even returning.
2113  *
2114  * @param stdClass $context
2115  * @param int $postid
2116  * @param string $sort
2117  * @return array Array of ratings or false
2118  */
2119 function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
2120     $options = new stdClass;
2121     $options->context = $context;
2122     $options->component = 'mod_forum';
2123     $options->ratingarea = 'post';
2124     $options->itemid = $postid;
2125     $options->sort = "ORDER BY $sort";
2127     $rm = new rating_manager();
2128     return $rm->get_all_ratings_for_item($options);
2131 /**
2132  * Returns a list of all new posts that have not been mailed yet
2133  *
2134  * @param int $starttime posts created after this time
2135  * @param int $endtime posts created before this
2136  * @param int $now used for timed discussions only
2137  * @return array
2138  */
2139 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2140     global $CFG, $DB;
2142     $params = array();
2143     $params['mailed'] = FORUM_MAILED_PENDING;
2144     $params['ptimestart'] = $starttime;
2145     $params['ptimeend'] = $endtime;
2146     $params['mailnow'] = 1;
2148     if (!empty($CFG->forum_enabletimedposts)) {
2149         if (empty($now)) {
2150             $now = time();
2151         }
2152         $timedsql = "AND (d.timestart < :dtimestart AND (d.timeend = 0 OR d.timeend > :dtimeend))";
2153         $params['dtimestart'] = $now;
2154         $params['dtimeend'] = $now;
2155     } else {
2156         $timedsql = "";
2157     }
2159     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2160                                  FROM {forum_posts} p
2161                                  JOIN {forum_discussions} d ON d.id = p.discussion
2162                                  WHERE p.mailed = :mailed
2163                                  AND p.created >= :ptimestart
2164                                  AND (p.created < :ptimeend OR p.mailnow = :mailnow)
2165                                  $timedsql
2166                                  ORDER BY p.modified ASC", $params);
2169 /**
2170  * Marks posts before a certain time as being mailed already
2171  *
2172  * @global object
2173  * @global object
2174  * @param int $endtime
2175  * @param int $now Defaults to time()
2176  * @return bool
2177  */
2178 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2179     global $CFG, $DB;
2181     if (empty($now)) {
2182         $now = time();
2183     }
2185     $params = array();
2186     $params['mailedsuccess'] = FORUM_MAILED_SUCCESS;
2187     $params['now'] = $now;
2188     $params['endtime'] = $endtime;
2189     $params['mailnow'] = 1;
2190     $params['mailedpending'] = FORUM_MAILED_PENDING;
2192     if (empty($CFG->forum_enabletimedposts)) {
2193         return $DB->execute("UPDATE {forum_posts}
2194                              SET mailed = :mailedsuccess
2195                              WHERE (created < :endtime OR mailnow = :mailnow)
2196                              AND mailed = :mailedpending", $params);
2197     } else {
2198         return $DB->execute("UPDATE {forum_posts}
2199                              SET mailed = :mailedsuccess
2200                              WHERE discussion NOT IN (SELECT d.id
2201                                                       FROM {forum_discussions} d
2202                                                       WHERE d.timestart > :now)
2203                              AND (created < :endtime OR mailnow = :mailnow)
2204                              AND mailed = :mailedpending", $params);
2205     }
2208 /**
2209  * Get all the posts for a user in a forum suitable for forum_print_post
2210  *
2211  * @global object
2212  * @global object
2213  * @uses CONTEXT_MODULE
2214  * @return array
2215  */
2216 function forum_get_user_posts($forumid, $userid) {
2217     global $CFG, $DB;
2219     $timedsql = "";
2220     $params = array($forumid, $userid);
2222     if (!empty($CFG->forum_enabletimedposts)) {
2223         $cm = get_coursemodule_from_instance('forum', $forumid);
2224         if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2225             $now = time();
2226             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2227             $params[] = $now;
2228             $params[] = $now;
2229         }
2230     }
2232     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2233                               FROM {forum} f
2234                                    JOIN {forum_discussions} d ON d.forum = f.id
2235                                    JOIN {forum_posts} p       ON p.discussion = d.id
2236                                    JOIN {user} u              ON u.id = p.userid
2237                              WHERE f.id = ?
2238                                    AND p.userid = ?
2239                                    $timedsql
2240                           ORDER BY p.modified ASC", $params);
2243 /**
2244  * Get all the discussions user participated in
2245  *
2246  * @global object
2247  * @global object
2248  * @uses CONTEXT_MODULE
2249  * @param int $forumid
2250  * @param int $userid
2251  * @return array Array or false
2252  */
2253 function forum_get_user_involved_discussions($forumid, $userid) {
2254     global $CFG, $DB;
2256     $timedsql = "";
2257     $params = array($forumid, $userid);
2258     if (!empty($CFG->forum_enabletimedposts)) {
2259         $cm = get_coursemodule_from_instance('forum', $forumid);
2260         if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2261             $now = time();
2262             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2263             $params[] = $now;
2264             $params[] = $now;
2265         }
2266     }
2268     return $DB->get_records_sql("SELECT DISTINCT d.*
2269                               FROM {forum} f
2270                                    JOIN {forum_discussions} d ON d.forum = f.id
2271                                    JOIN {forum_posts} p       ON p.discussion = d.id
2272                              WHERE f.id = ?
2273                                    AND p.userid = ?
2274                                    $timedsql", $params);
2277 /**
2278  * Get all the posts for a user in a forum suitable for forum_print_post
2279  *
2280  * @global object
2281  * @global object
2282  * @param int $forumid
2283  * @param int $userid
2284  * @return array of counts or false
2285  */
2286 function forum_count_user_posts($forumid, $userid) {
2287     global $CFG, $DB;
2289     $timedsql = "";
2290     $params = array($forumid, $userid);
2291     if (!empty($CFG->forum_enabletimedposts)) {
2292         $cm = get_coursemodule_from_instance('forum', $forumid);
2293         if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2294             $now = time();
2295             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2296             $params[] = $now;
2297             $params[] = $now;
2298         }
2299     }
2301     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2302                              FROM {forum} f
2303                                   JOIN {forum_discussions} d ON d.forum = f.id
2304                                   JOIN {forum_posts} p       ON p.discussion = d.id
2305                                   JOIN {user} u              ON u.id = p.userid
2306                             WHERE f.id = ?
2307                                   AND p.userid = ?
2308                                   $timedsql", $params);
2311 /**
2312  * Given a log entry, return the forum post details for it.
2313  *
2314  * @global object
2315  * @global object
2316  * @param object $log
2317  * @return array|null
2318  */
2319 function forum_get_post_from_log($log) {
2320     global $CFG, $DB;
2322     if ($log->action == "add post") {
2324         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2325                                            u.firstname, u.lastname, u.email, u.picture
2326                                  FROM {forum_discussions} d,
2327                                       {forum_posts} p,
2328                                       {forum} f,
2329                                       {user} u
2330                                 WHERE p.id = ?
2331                                   AND d.id = p.discussion
2332                                   AND p.userid = u.id
2333                                   AND u.deleted <> '1'
2334                                   AND f.id = d.forum", array($log->info));
2337     } else if ($log->action == "add discussion") {
2339         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2340                                            u.firstname, u.lastname, u.email, u.picture
2341                                  FROM {forum_discussions} d,
2342                                       {forum_posts} p,
2343                                       {forum} f,
2344                                       {user} u
2345                                 WHERE d.id = ?
2346                                   AND d.firstpost = p.id
2347                                   AND p.userid = u.id
2348                                   AND u.deleted <> '1'
2349                                   AND f.id = d.forum", array($log->info));
2350     }
2351     return NULL;
2354 /**
2355  * Given a discussion id, return the first post from the discussion
2356  *
2357  * @global object
2358  * @global object
2359  * @param int $dicsussionid
2360  * @return array
2361  */
2362 function forum_get_firstpost_from_discussion($discussionid) {
2363     global $CFG, $DB;
2365     return $DB->get_record_sql("SELECT p.*
2366                              FROM {forum_discussions} d,
2367                                   {forum_posts} p
2368                             WHERE d.id = ?
2369                               AND d.firstpost = p.id ", array($discussionid));
2372 /**
2373  * Returns an array of counts of replies to each discussion
2374  *
2375  * @global object
2376  * @global object
2377  * @param int $forumid
2378  * @param string $forumsort
2379  * @param int $limit
2380  * @param int $page
2381  * @param int $perpage
2382  * @return array
2383  */
2384 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2385     global $CFG, $DB;
2387     if ($limit > 0) {
2388         $limitfrom = 0;
2389         $limitnum  = $limit;
2390     } else if ($page != -1) {
2391         $limitfrom = $page*$perpage;
2392         $limitnum  = $perpage;
2393     } else {
2394         $limitfrom = 0;
2395         $limitnum  = 0;
2396     }
2398     if ($forumsort == "") {
2399         $orderby = "";
2400         $groupby = "";
2402     } else {
2403         $orderby = "ORDER BY $forumsort";
2404         $groupby = ", ".strtolower($forumsort);
2405         $groupby = str_replace('desc', '', $groupby);
2406         $groupby = str_replace('asc', '', $groupby);
2407     }
2409     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2410         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2411                   FROM {forum_posts} p
2412                        JOIN {forum_discussions} d ON p.discussion = d.id
2413                  WHERE p.parent > 0 AND d.forum = ?
2414               GROUP BY p.discussion";
2415         return $DB->get_records_sql($sql, array($forumid));
2417     } else {
2418         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2419                   FROM {forum_posts} p
2420                        JOIN {forum_discussions} d ON p.discussion = d.id
2421                  WHERE d.forum = ?
2422               GROUP BY p.discussion $groupby
2423               $orderby";
2424         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2425     }
2428 /**
2429  * @global object
2430  * @global object
2431  * @global object
2432  * @staticvar array $cache
2433  * @param object $forum
2434  * @param object $cm
2435  * @param object $course
2436  * @return mixed
2437  */
2438 function forum_count_discussions($forum, $cm, $course) {
2439     global $CFG, $DB, $USER;
2441     static $cache = array();
2443     $now = round(time(), -2); // db cache friendliness
2445     $params = array($course->id);
2447     if (!isset($cache[$course->id])) {
2448         if (!empty($CFG->forum_enabletimedposts)) {
2449             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2450             $params[] = $now;
2451             $params[] = $now;
2452         } else {
2453             $timedsql = "";
2454         }
2456         $sql = "SELECT f.id, COUNT(d.id) as dcount
2457                   FROM {forum} f
2458                        JOIN {forum_discussions} d ON d.forum = f.id
2459                  WHERE f.course = ?
2460                        $timedsql
2461               GROUP BY f.id";
2463         if ($counts = $DB->get_records_sql($sql, $params)) {
2464             foreach ($counts as $count) {
2465                 $counts[$count->id] = $count->dcount;
2466             }
2467             $cache[$course->id] = $counts;
2468         } else {
2469             $cache[$course->id] = array();
2470         }
2471     }
2473     if (empty($cache[$course->id][$forum->id])) {
2474         return 0;
2475     }
2477     $groupmode = groups_get_activity_groupmode($cm, $course);
2479     if ($groupmode != SEPARATEGROUPS) {
2480         return $cache[$course->id][$forum->id];
2481     }
2483     if (has_capability('moodle/site:accessallgroups', context_module::instance($cm->id))) {
2484         return $cache[$course->id][$forum->id];
2485     }
2487     require_once($CFG->dirroot.'/course/lib.php');
2489     $modinfo = get_fast_modinfo($course);
2490     if (is_null($modinfo->groups)) {
2491         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2492     }
2494     if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2495         $mygroups = $modinfo->groups[$cm->groupingid];
2496     } else {
2497         $mygroups = false; // Will be set below
2498     }
2500     // add all groups posts
2501     if (empty($mygroups)) {
2502         $mygroups = array(-1=>-1);
2503     } else {
2504         $mygroups[-1] = -1;
2505     }
2507     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2508     $params[] = $forum->id;
2510     if (!empty($CFG->forum_enabletimedposts)) {
2511         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2512         $params[] = $now;
2513         $params[] = $now;
2514     } else {
2515         $timedsql = "";
2516     }
2518     $sql = "SELECT COUNT(d.id)
2519               FROM {forum_discussions} d
2520              WHERE d.groupid $mygroups_sql AND d.forum = ?
2521                    $timedsql";
2523     return $DB->get_field_sql($sql, $params);
2526 /**
2527  * How many posts by other users are unrated by a given user in the given discussion?
2528  *
2529  * TODO: Is this function still used anywhere?
2530  *
2531  * @param int $discussionid
2532  * @param int $userid
2533  * @return mixed
2534  */
2535 function forum_count_unrated_posts($discussionid, $userid) {
2536     global $CFG, $DB;
2538     $sql = "SELECT COUNT(*) as num
2539               FROM {forum_posts}
2540              WHERE parent > 0
2541                AND discussion = :discussionid
2542                AND userid <> :userid";
2543     $params = array('discussionid' => $discussionid, 'userid' => $userid);
2544     $posts = $DB->get_record_sql($sql, $params);
2545     if ($posts) {
2546         $sql = "SELECT count(*) as num
2547                   FROM {forum_posts} p,
2548                        {rating} r
2549                  WHERE p.discussion = :discussionid AND
2550                        p.id = r.itemid AND
2551                        r.userid = userid AND
2552                        r.component = 'mod_forum' AND
2553                        r.ratingarea = 'post'";
2554         $rated = $DB->get_record_sql($sql, $params);
2555         if ($rated) {
2556             if ($posts->num > $rated->num) {
2557                 return $posts->num - $rated->num;
2558             } else {
2559                 return 0;    // Just in case there was a counting error
2560             }
2561         } else {
2562             return $posts->num;
2563         }
2564     } else {
2565         return 0;
2566     }
2569 /**
2570  * Get all discussions in a forum
2571  *
2572  * @global object
2573  * @global object
2574  * @global object
2575  * @uses CONTEXT_MODULE
2576  * @uses VISIBLEGROUPS
2577  * @param object $cm
2578  * @param string $forumsort
2579  * @param bool $fullpost
2580  * @param int $unused
2581  * @param int $limit
2582  * @param bool $userlastmodified
2583  * @param int $page
2584  * @param int $perpage
2585  * @return array
2586  */
2587 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2588     global $CFG, $DB, $USER;
2590     $timelimit = '';
2592     $now = round(time(), -2);
2593     $params = array($cm->instance);
2595     $modcontext = context_module::instance($cm->id);
2597     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2598         return array();
2599     }
2601     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2603         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2604             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2605             $params[] = $now;
2606             $params[] = $now;
2607             if (isloggedin()) {
2608                 $timelimit .= " OR d.userid = ?";
2609                 $params[] = $USER->id;
2610             }
2611             $timelimit .= ")";
2612         }
2613     }
2615     if ($limit > 0) {
2616         $limitfrom = 0;
2617         $limitnum  = $limit;
2618     } else if ($page != -1) {
2619         $limitfrom = $page*$perpage;
2620         $limitnum  = $perpage;
2621     } else {
2622         $limitfrom = 0;
2623         $limitnum  = 0;
2624     }
2626     $groupmode    = groups_get_activity_groupmode($cm);
2627     $currentgroup = groups_get_activity_group($cm);
2629     if ($groupmode) {
2630         if (empty($modcontext)) {
2631             $modcontext = context_module::instance($cm->id);
2632         }
2634         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2635             if ($currentgroup) {
2636                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2637                 $params[] = $currentgroup;
2638             } else {
2639                 $groupselect = "";
2640             }
2642         } else {
2643             //seprate groups without access all
2644             if ($currentgroup) {
2645                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2646                 $params[] = $currentgroup;
2647             } else {
2648                 $groupselect = "AND d.groupid = -1";
2649             }
2650         }
2651     } else {
2652         $groupselect = "";
2653     }
2656     if (empty($forumsort)) {
2657         $forumsort = "d.timemodified DESC";
2658     }
2659     if (empty($fullpost)) {
2660         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2661     } else {
2662         $postdata = "p.*";
2663     }
2665     if (empty($userlastmodified)) {  // We don't need to know this
2666         $umfields = "";
2667         $umtable  = "";
2668     } else {
2669         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2670         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2671     }
2673     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2674                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2675               FROM {forum_discussions} d
2676                    JOIN {forum_posts} p ON p.discussion = d.id
2677                    JOIN {user} u ON p.userid = u.id
2678                    $umtable
2679              WHERE d.forum = ? AND p.parent = 0
2680                    $timelimit $groupselect
2681           ORDER BY $forumsort";
2682     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2685 /**
2686  *
2687  * @global object
2688  * @global object
2689  * @global object
2690  * @uses CONTEXT_MODULE
2691  * @uses VISIBLEGROUPS
2692  * @param object $cm
2693  * @return array
2694  */
2695 function forum_get_discussions_unread($cm) {
2696     global $CFG, $DB, $USER;
2698     $now = round(time(), -2);
2699     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2701     $params = array();
2702     $groupmode    = groups_get_activity_groupmode($cm);
2703     $currentgroup = groups_get_activity_group($cm);
2705     if ($groupmode) {
2706         $modcontext = context_module::instance($cm->id);
2708         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2709             if ($currentgroup) {
2710                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2711                 $params['currentgroup'] = $currentgroup;
2712             } else {
2713                 $groupselect = "";
2714             }
2716         } else {
2717             //separate groups without access all
2718             if ($currentgroup) {
2719                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2720                 $params['currentgroup'] = $currentgroup;
2721             } else {
2722                 $groupselect = "AND d.groupid = -1";
2723             }
2724         }
2725     } else {
2726         $groupselect = "";
2727     }
2729     if (!empty($CFG->forum_enabletimedposts)) {
2730         $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2731         $params['now1'] = $now;
2732         $params['now2'] = $now;
2733     } else {
2734         $timedsql = "";
2735     }
2737     $sql = "SELECT d.id, COUNT(p.id) AS unread
2738               FROM {forum_discussions} d
2739                    JOIN {forum_posts} p     ON p.discussion = d.id
2740                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2741              WHERE d.forum = {$cm->instance}
2742                    AND p.modified >= :cutoffdate AND r.id is NULL
2743                    $groupselect
2744                    $timedsql
2745           GROUP BY d.id";
2746     $params['cutoffdate'] = $cutoffdate;
2748     if ($unreads = $DB->get_records_sql($sql, $params)) {
2749         foreach ($unreads as $unread) {
2750             $unreads[$unread->id] = $unread->unread;
2751         }
2752         return $unreads;
2753     } else {
2754         return array();
2755     }
2758 /**
2759  * @global object
2760  * @global object
2761  * @global object
2762  * @uses CONEXT_MODULE
2763  * @uses VISIBLEGROUPS
2764  * @param object $cm
2765  * @return array
2766  */
2767 function forum_get_discussions_count($cm) {
2768     global $CFG, $DB, $USER;
2770     $now = round(time(), -2);
2771     $params = array($cm->instance);
2772     $groupmode    = groups_get_activity_groupmode($cm);
2773     $currentgroup = groups_get_activity_group($cm);
2775     if ($groupmode) {
2776         $modcontext = context_module::instance($cm->id);
2778         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2779             if ($currentgroup) {
2780                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2781                 $params[] = $currentgroup;
2782             } else {
2783                 $groupselect = "";
2784             }
2786         } else {
2787             //seprate groups without access all
2788             if ($currentgroup) {
2789                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2790                 $params[] = $currentgroup;
2791             } else {
2792                 $groupselect = "AND d.groupid = -1";
2793             }
2794         }
2795     } else {
2796         $groupselect = "";
2797     }
2799     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2801     $timelimit = "";
2803     if (!empty($CFG->forum_enabletimedposts)) {
2805         $modcontext = context_module::instance($cm->id);
2807         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2808             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2809             $params[] = $now;
2810             $params[] = $now;
2811             if (isloggedin()) {
2812                 $timelimit .= " OR d.userid = ?";
2813                 $params[] = $USER->id;
2814             }
2815             $timelimit .= ")";
2816         }
2817     }
2819     $sql = "SELECT COUNT(d.id)
2820               FROM {forum_discussions} d
2821                    JOIN {forum_posts} p ON p.discussion = d.id
2822              WHERE d.forum = ? AND p.parent = 0
2823                    $groupselect $timelimit";
2825     return $DB->get_field_sql($sql, $params);
2829 /**
2830  * Get all discussions started by a particular user in a course (or group)
2831  * This function no longer used ...
2832  *
2833  * @todo Remove this function if no longer used
2834  * @global object
2835  * @global object
2836  * @param int $courseid
2837  * @param int $userid
2838  * @param int $groupid
2839  * @return array
2840  */
2841 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2842     global $CFG, $DB;
2843     $params = array($courseid, $userid);
2844     if ($groupid) {
2845         $groupselect = " AND d.groupid = ? ";
2846         $params[] = $groupid;
2847     } else  {
2848         $groupselect = "";
2849     }
2851     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2852                                    f.type as forumtype, f.name as forumname, f.id as forumid
2853                               FROM {forum_discussions} d,
2854                                    {forum_posts} p,
2855                                    {user} u,
2856                                    {forum} f
2857                              WHERE d.course = ?
2858                                AND p.discussion = d.id
2859                                AND p.parent = 0
2860                                AND p.userid = u.id
2861                                AND u.id = ?
2862                                AND d.forum = f.id $groupselect
2863                           ORDER BY p.created DESC", $params);
2866 /**
2867  * Get the list of potential subscribers to a forum.
2868  *
2869  * @param object $forumcontext the forum context.
2870  * @param integer $groupid the id of a group, or 0 for all groups.
2871  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2872  * @param string $sort sort order. As for get_users_by_capability.
2873  * @return array list of users.
2874  */
2875 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort = '') {
2876     global $DB;
2878     // only active enrolled users or everybody on the frontpage
2879     list($esql, $params) = get_enrolled_sql($forumcontext, 'mod/forum:allowforcesubscribe', $groupid, true);
2880     if (!$sort) {
2881         list($sort, $sortparams) = users_order_by_sql('u');
2882         $params = array_merge($params, $sortparams);
2883     }
2885     $sql = "SELECT $fields
2886               FROM {user} u
2887               JOIN ($esql) je ON je.id = u.id
2888           ORDER BY $sort";
2890     return $DB->get_records_sql($sql, $params);
2893 /**
2894  * Returns list of user objects that are subscribed to this forum
2895  *
2896  * @global object
2897  * @global object
2898  * @param object $course the course
2899  * @param forum $forum the forum
2900  * @param integer $groupid group id, or 0 for all.
2901  * @param object $context the forum context, to save re-fetching it where possible.
2902  * @param string $fields requested user fields (with "u." table prefix)
2903  * @return array list of users.
2904  */
2905 function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
2906     global $CFG, $DB;
2908     if (empty($fields)) {
2909         $fields ="u.id,
2910                   u.username,
2911                   u.firstname,
2912                   u.lastname,
2913                   u.maildisplay,
2914                   u.mailformat,
2915                   u.maildigest,
2916                   u.imagealt,
2917                   u.email,
2918                   u.emailstop,
2919                   u.city,
2920                   u.country,
2921                   u.lastaccess,
2922                   u.lastlogin,
2923                   u.picture,
2924                   u.timezone,
2925                   u.theme,
2926                   u.lang,
2927                   u.trackforums,
2928                   u.mnethostid";
2929     }
2931     if (empty($context)) {
2932         $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2933         $context = context_module::instance($cm->id);
2934     }
2936     if (forum_is_forcesubscribed($forum)) {
2937         $results = forum_get_potential_subscribers($context, $groupid, $fields, "u.email ASC");
2939     } else {
2940         // only active enrolled users or everybody on the frontpage
2941         list($esql, $params) = get_enrolled_sql($context, '', $groupid, true);
2942         $params['forumid'] = $forum->id;
2943         $results = $DB->get_records_sql("SELECT $fields
2944                                            FROM {user} u
2945                                            JOIN ($esql) je ON je.id = u.id
2946                                            JOIN {forum_subscriptions} s ON s.userid = u.id
2947                                           WHERE s.forum = :forumid
2948                                        ORDER BY u.email ASC", $params);
2949     }
2951     // Guest user should never be subscribed to a forum.
2952     unset($results[$CFG->siteguest]);
2954     return $results;
2959 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2962 /**
2963  * @global object
2964  * @global object
2965  * @param int $courseid
2966  * @param string $type
2967  */
2968 function forum_get_course_forum($courseid, $type) {
2969 // How to set up special 1-per-course forums
2970     global $CFG, $DB, $OUTPUT, $USER;
2972     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2973         // There should always only be ONE, but with the right combination of
2974         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2975         foreach ($forums as $forum) {
2976             return $forum;   // ie the first one
2977         }
2978     }
2980     // Doesn't exist, so create one now.
2981     $forum = new stdClass();
2982     $forum->course = $courseid;
2983     $forum->type = "$type";
2984     if (!empty($USER->htmleditor)) {
2985         $forum->introformat = $USER->htmleditor;
2986     }
2987     switch ($forum->type) {
2988         case "news":
2989             $forum->name  = get_string("namenews", "forum");
2990             $forum->intro = get_string("intronews", "forum");
2991             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2992             $forum->assessed = 0;
2993             if ($courseid == SITEID) {
2994                 $forum->name  = get_string("sitenews");
2995                 $forum->forcesubscribe = 0;
2996             }
2997             break;
2998         case "social":
2999             $forum->name  = get_string("namesocial", "forum");
3000             $forum->intro = get_string("introsocial", "forum");
3001             $forum->assessed = 0;
3002             $forum->forcesubscribe = 0;
3003             break;
3004         case "blog":
3005             $forum->name = get_string('blogforum', 'forum');
3006             $forum->intro = get_string('introblog', 'forum');
3007             $forum->assessed = 0;
3008             $forum->forcesubscribe = 0;
3009             break;
3010         default:
3011             echo $OUTPUT->notification("That forum type doesn't exist!");
3012             return false;
3013             break;
3014     }
3016     $forum->timemodified = time();
3017     $forum->id = $DB->insert_record("forum", $forum);
3019     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
3020         echo $OUTPUT->notification("Could not find forum module!!");
3021         return false;
3022     }
3023     $mod = new stdClass();
3024     $mod->course = $courseid;
3025     $mod->module = $module->id;
3026     $mod->instance = $forum->id;
3027     $mod->section = 0;
3028     include_once("$CFG->dirroot/course/lib.php");
3029     if (! $mod->coursemodule = add_course_module($mod) ) {
3030         echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
3031         return false;
3032     }
3033     $sectionid = course_add_cm_to_section($courseid, $mod->coursemodule, 0);
3034     return $DB->get_record("forum", array("id" => "$forum->id"));
3038 /**
3039  * Given the data about a posting, builds up the HTML to display it and
3040  * returns the HTML in a string.  This is designed for sending via HTML email.
3041  *
3042  * @global object
3043  * @param object $course
3044  * @param object $cm
3045  * @param object $forum
3046  * @param object $discussion
3047  * @param object $post
3048  * @param object $userform
3049  * @param object $userto
3050  * @param bool $ownpost
3051  * @param bool $reply
3052  * @param bool $link
3053  * @param bool $rate
3054  * @param string $footer
3055  * @return string
3056  */
3057 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
3058                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
3060     global $CFG, $OUTPUT;
3062     $modcontext = context_module::instance($cm->id);
3064     if (!isset($userto->viewfullnames[$forum->id])) {
3065         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3066     } else {
3067         $viewfullnames = $userto->viewfullnames[$forum->id];
3068     }
3070     // add absolute file links
3071     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3073     // format the post body
3074     $options = new stdClass();
3075     $options->para = true;
3076     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3078     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3080     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3081     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3082     $output .= '</td>';
3084     if ($post->parent) {
3085         $output .= '<td class="topic">';
3086     } else {
3087         $output .= '<td class="topic starter">';
3088     }
3089     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3091     $fullname = fullname($userfrom, $viewfullnames);
3092     $by = new stdClass();
3093     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3094     $by->date = userdate($post->modified, '', $userto->timezone);
3095     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3097     $output .= '</td></tr>';
3099     $output .= '<tr><td class="left side" valign="top">';
3101     if (isset($userfrom->groups)) {
3102         $groups = $userfrom->groups[$forum->id];
3103     } else {
3104         $groups = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3105     }
3107     if ($groups) {
3108         $output .= print_group_picture($groups, $course->id, false, true, true);
3109     } else {
3110         $output .= '&nbsp;';
3111     }
3113     $output .= '</td><td class="content">';
3115     $attachments = forum_print_attachments($post, $cm, 'html');
3116     if ($attachments !== '') {
3117         $output .= '<div class="attachments">';
3118         $output .= $attachments;
3119         $output .= '</div>';
3120     }
3122     $output .= $formattedtext;
3124 // Commands
3125     $commands = array();
3127     if ($post->parent) {
3128         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3129                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3130     }
3132     if ($reply) {
3133         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3134                       get_string('reply', 'forum').'</a>';
3135     }
3137     $output .= '<div class="commands">';
3138     $output .= implode(' | ', $commands);
3139     $output .= '</div>';
3141 // Context link to post if required
3142     if ($link) {
3143         $output .= '<div class="link">';
3144         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3145                      get_string('postincontext', 'forum').'</a>';
3146         $output .= '</div>';
3147     }
3149     if ($footer) {
3150         $output .= '<div class="footer">'.$footer.'</div>';
3151     }
3152     $output .= '</td></tr></table>'."\n\n";
3154     return $output;
3157 /**
3158  * Print a forum post
3159  *
3160  * @global object
3161  * @global object
3162  * @uses FORUM_MODE_THREADED
3163  * @uses PORTFOLIO_FORMAT_PLAINHTML
3164  * @uses PORTFOLIO_FORMAT_FILE
3165  * @uses PORTFOLIO_FORMAT_RICHHTML
3166  * @uses PORTFOLIO_ADD_TEXT_LINK
3167  * @uses CONTEXT_MODULE
3168  * @param object $post The post to print.
3169  * @param object $discussion
3170  * @param object $forum
3171  * @param object $cm
3172  * @param object $course
3173  * @param boolean $ownpost Whether this post belongs to the current user.
3174  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3175  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3176  * @param string $footer Extra stuff to print after the message.
3177  * @param string $highlight Space-separated list of terms to highlight.
3178  * @param int $post_read true, false or -99. If we already know whether this user
3179  *          has read this post, pass that in, otherwise, pass in -99, and this
3180  *          function will work it out.
3181  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3182  *          the current user can't see this post, if this argument is true
3183  *          (the default) then print a dummy 'you can't see this post' post.
3184  *          If false, don't output anything at all.
3185  * @param bool|null $istracked
3186  * @return void
3187  */
3188 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3189                           $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3190     global $USER, $CFG, $OUTPUT;
3192     require_once($CFG->libdir . '/filelib.php');
3194     // String cache
3195     static $str;
3197     $modcontext = context_module::instance($cm->id);
3199     $post->course = $course->id;
3200     $post->forum  = $forum->id;
3201     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3202     if (!empty($CFG->enableplagiarism)) {
3203         require_once($CFG->libdir.'/plagiarismlib.php');
3204         $post->message .= plagiarism_get_links(array('userid' => $post->userid,
3205             'content' => $post->message,
3206             'cmid' => $cm->id,
3207             'course' => $post->course,
3208             'forum' => $post->forum));
3209     }
3211     // caching
3212     if (!isset($cm->cache)) {
3213         $cm->cache = new stdClass;
3214     }
3216     if (!isset($cm->cache->caps)) {
3217         $cm->cache->caps = array();
3218         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3219         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3220         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3221         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3222         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3223         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3224         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3225         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3226         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3227     }
3229     if (!isset($cm->uservisible)) {
3230         $cm->uservisible = coursemodule_visible_for_user($cm);
3231     }
3233     if ($istracked && is_null($postisread)) {
3234         $postisread = forum_tp_is_post_read($USER->id, $post);
3235     }
3237     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3238         $output = '';
3239         if (!$dummyifcantsee) {
3240             if ($return) {
3241                 return $output;
3242             }
3243             echo $output;
3244             return;
3245         }
3246         $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3247         $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'));
3248         $output .= html_writer::start_tag('div', array('class'=>'row header'));
3249         $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3250         if ($post->parent) {
3251             $output .= html_writer::start_tag('div', array('class'=>'topic'));
3252         } else {
3253             $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3254         }
3255         $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class'=>'subject')); // Subject
3256         $output .= html_writer::tag('div', get_string('forumauthorhidden','forum'), array('class'=>'author')); // author
3257         $output .= html_writer::end_tag('div');
3258         $output .= html_writer::end_tag('div'); // row
3259         $output .= html_writer::start_tag('div', array('class'=>'row'));
3260         $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3261         $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3262         $output .= html_writer::end_tag('div'); // row
3263         $output .= html_writer::end_tag('div'); // forumpost
3265         if ($return) {
3266             return $output;
3267         }
3268         echo $output;
3269         return;
3270     }
3272     if (empty($str)) {
3273         $str = new stdClass;
3274         $str->edit         = get_string('edit', 'forum');
3275         $str->delete       = get_string('delete', 'forum');
3276         $str->reply        = get_string('reply', 'forum');
3277         $str->parent       = get_string('parent', 'forum');
3278         $str->pruneheading = get_string('pruneheading', 'forum');
3279         $str->prune        = get_string('prune', 'forum');
3280         $str->displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3281         $str->markread     = get_string('markread', 'forum');
3282         $str->markunread   = get_string('markunread', 'forum');
3283     }
3285     $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3287     // Build an object that represents the posting user
3288     $postuser = new stdClass;
3289     $postuser->id        = $post->userid;
3290     $postuser->firstname = $post->firstname;
3291     $postuser->lastname  = $post->lastname;
3292     $postuser->imagealt  = $post->imagealt;
3293     $postuser->picture   = $post->picture;
3294     $postuser->email     = $post->email;
3295     // Some handy things for later on
3296     $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3297     $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3299     // Prepare the groups the posting user belongs to
3300     if (isset($cm->cache->usersgroups)) {
3301         $groups = array();
3302         if (isset($cm->cache->usersgroups[$post->userid])) {
3303             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3304                 $groups[$gid] = $cm->cache->groups[$gid];
3305             }
3306         }
3307     } else {
3308         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3309     }
3311     // Prepare the attachements for the post, files then images
3312     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3314     // Determine if we need to shorten this post
3315     $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3318     // Prepare an array of commands
3319     $commands = array();
3321     // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3322     // Don't display the mark read / unread controls in this case.
3323     if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3324         $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3325         $text = $str->markunread;
3326         if (!$postisread) {
3327             $url->param('mark', 'read');
3328             $text = $str->markread;
3329         }
3330         if ($str->displaymode == FORUM_MODE_THREADED) {
3331             $url->param('parent', $post->parent);
3332         } else {
3333             $url->set_anchor('p'.$post->id);
3334         }
3335         $commands[] = array('url'=>$url, 'text'=>$text);
3336     }
3338     // Zoom in to the parent specifically
3339     if ($post->parent) {
3340         $url = new moodle_url($discussionlink);
3341         if ($str->displaymode == FORUM_MODE_THREADED) {
3342             $url->param('parent', $post->parent);
3343         } else {
3344             $url->set_anchor('p'.$post->parent);
3345         }
3346         $commands[] = array('url'=>$url, 'text'=>$str->parent);
3347     }
3349     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3350     $age = time() - $post->created;
3351     if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3352         $age = 0;
3353     }
3355     if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
3356         if (has_capability('moodle/course:manageactivities', $modcontext)) {
3357             // The first post in single simple is the forum description.
3358             $commands[] = array('url'=>new moodle_url('/course/modedit.php', array('update'=>$cm->id, 'sesskey'=>sesskey(), 'return'=>1)), 'text'=>$str->edit);
3359         }
3360     } else if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3361         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3362     }
3364     if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3365         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3366     }
3368     if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
3369         // Do not allow deleting of first post in single simple type.
3370     } else if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3371         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3372     }
3374     if ($reply) {
3375         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php#mformforum', array('reply'=>$post->id)), 'text'=>$str->reply);
3376     }
3378     if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
3379         $p = array('postid' => $post->id);
3380         require_once($CFG->libdir.'/portfoliolib.php');
3381         $button = new portfolio_add_button();
3382         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), 'mod_forum');
3383         if (empty($attachments)) {
3384             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3385         } else {
3386             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3387         }
3389         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3390         if (!empty($porfoliohtml)) {
3391             $commands[] = $porfoliohtml;
3392         }
3393     }
3394     // Finished building commands
3397     // Begin output
3399     $output  = '';
3401     if ($istracked) {
3402         if ($postisread) {
3403             $forumpostclass = ' read';
3404         } else {
3405             $forumpostclass = ' unread';
3406             $output .= html_writer::tag('a', '', array('name'=>'unread'));
3407         }
3408     } else {
3409         // ignore trackign status if not tracked or tracked param missing
3410         $forumpostclass = '';
3411     }
3413     $topicclass = '';
3414     if (empty($post->parent)) {
3415         $topicclass = ' firstpost starter';
3416     }
3418     $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3419     $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass));
3420     $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3421     $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3422     $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3423     $output .= html_writer::end_tag('div');
3426     $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3428     $postsubject = $post->subject;
3429     if (empty($post->subjectnoformat)) {
3430         $postsubject = format_string($postsubject);
3431     }
3432     $output .= html_writer::tag('div', $postsubject, array('class'=>'subject'));
3434     $by = new stdClass();
3435     $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3436     $by->date = userdate($post->modified);
3437     $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author'));
3439     $output .= html_writer::end_tag('div'); //topic
3440     $output .= html_writer::end_tag('div'); //row
3442     $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3443     $output .= html_writer::start_tag('div', array('class'=>'left'));
3445     $groupoutput = '';
3446     if ($groups) {
3447         $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3448     }
3449     if (empty($groupoutput)) {
3450         $groupoutput = '&nbsp;';
3451     }
3452     $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3454     $output .= html_writer::end_tag('div'); //left side
3455     $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3456     $output .= html_writer::start_tag('div', array('class'=>'content'));
3457     if (!empty($attachments)) {
3458         $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3459     }
3461     $options = new stdClass;
3462     $options->para    = false;
3463     $options->trusted = $post->messagetrust;
3464     $options->context = $modcontext;
3465     if ($shortenpost) {
3466         // Prepare shortened version
3467         $postclass    = 'shortenedpost';
3468         $postcontent  = format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3469         $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
3470         $postcontent .= html_writer::tag('div', '('.get_string('numwords', 'moodle', count_words($post->message)).')',
3471             array('class'=>'post-word-count'));
3472     } else {
3473         // Prepare whole post
3474         $postclass    = 'fullpost';
3475         $postcontent  = format_text($post->message, $post->messageformat, $options, $course->id);
3476         if (!empty($highlight)) {
3477             $postcontent = highlight($highlight, $postcontent);
3478         }
3479         if (!empty($forum->displaywordcount)) {
3480             $postcontent .= html_writer::tag('div', get_string('numwords', 'moodle', count_words($post->message)),
3481                 array('class'=>'post-word-count'));
3482         }
3483         $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
3484     }
3486     // Output the post content
3487     $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
3488     $output .= html_writer::end_tag('div'); // Content
3489     $output .= html_writer::end_tag('div'); // Content mask
3490     $output .= html_writer::end_tag('div'); // Row
3492     $output .= html_writer::start_tag('div', array('class'=>'row side'));