MDL-29731, MDL-40843 Correctly access course_modinfo->get_groups()
[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     $modcontext = context_module::instance($forum->coursemodule);
216     if (($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) && ($oldforum->forcesubscribe <> $forum->forcesubscribe)) {
217         $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
218         foreach ($users as $user) {
219             forum_subscribe($user->id, $forum->id);
220         }
221     }
223     forum_grade_item_update($forum);
225     return true;
229 /**
230  * Given an ID of an instance of this module,
231  * this function will permanently delete the instance
232  * and any data that depends on it.
233  *
234  * @global object
235  * @param int $id forum instance id
236  * @return bool success
237  */
238 function forum_delete_instance($id) {
239     global $DB;
241     if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
242         return false;
243     }
244     if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
245         return false;
246     }
247     if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
248         return false;
249     }
251     $context = context_module::instance($cm->id);
253     // now get rid of all files
254     $fs = get_file_storage();
255     $fs->delete_area_files($context->id);
257     $result = true;
259     if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
260         foreach ($discussions as $discussion) {
261             if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
262                 $result = false;
263             }
264         }
265     }
267     if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
268         $result = false;
269     }
271     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
273     if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
274         $result = false;
275     }
277     forum_grade_item_delete($forum);
279     return $result;
283 /**
284  * Indicates API features that the forum supports.
285  *
286  * @uses FEATURE_GROUPS
287  * @uses FEATURE_GROUPINGS
288  * @uses FEATURE_GROUPMEMBERSONLY
289  * @uses FEATURE_MOD_INTRO
290  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
291  * @uses FEATURE_COMPLETION_HAS_RULES
292  * @uses FEATURE_GRADE_HAS_GRADE
293  * @uses FEATURE_GRADE_OUTCOMES
294  * @param string $feature
295  * @return mixed True if yes (some features may use other values)
296  */
297 function forum_supports($feature) {
298     switch($feature) {
299         case FEATURE_GROUPS:                  return true;
300         case FEATURE_GROUPINGS:               return true;
301         case FEATURE_GROUPMEMBERSONLY:        return true;
302         case FEATURE_MOD_INTRO:               return true;
303         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
304         case FEATURE_COMPLETION_HAS_RULES:    return true;
305         case FEATURE_GRADE_HAS_GRADE:         return true;
306         case FEATURE_GRADE_OUTCOMES:          return true;
307         case FEATURE_RATE:                    return true;
308         case FEATURE_BACKUP_MOODLE2:          return true;
309         case FEATURE_SHOW_DESCRIPTION:        return true;
310         case FEATURE_PLAGIARISM:              return true;
312         default: return null;
313     }
317 /**
318  * Obtains the automatic completion state for this forum based on any conditions
319  * in forum settings.
320  *
321  * @global object
322  * @global object
323  * @param object $course Course
324  * @param object $cm Course-module
325  * @param int $userid User ID
326  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
327  * @return bool True if completed, false if not. (If no conditions, then return
328  *   value depends on comparison type)
329  */
330 function forum_get_completion_state($course,$cm,$userid,$type) {
331     global $CFG,$DB;
333     // Get forum details
334     if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
335         throw new Exception("Can't find forum {$cm->instance}");
336     }
338     $result=$type; // Default return value
340     $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
341     $postcountsql="
342 SELECT
343     COUNT(1)
344 FROM
345     {forum_posts} fp
346     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
347 WHERE
348     fp.userid=:userid AND fd.forum=:forumid";
350     if ($forum->completiondiscussions) {
351         $value = $forum->completiondiscussions <=
352                  $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
353         if ($type == COMPLETION_AND) {
354             $result = $result && $value;
355         } else {
356             $result = $result || $value;
357         }
358     }
359     if ($forum->completionreplies) {
360         $value = $forum->completionreplies <=
361                  $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
362         if ($type==COMPLETION_AND) {
363             $result = $result && $value;
364         } else {
365             $result = $result || $value;
366         }
367     }
368     if ($forum->completionposts) {
369         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
370         if ($type == COMPLETION_AND) {
371             $result = $result && $value;
372         } else {
373             $result = $result || $value;
374         }
375     }
377     return $result;
380 /**
381  * Create a message-id string to use in the custom headers of forum notification emails
382  *
383  * message-id is used by email clients to identify emails and to nest conversations
384  *
385  * @param int $postid The ID of the forum post we are notifying the user about
386  * @param int $usertoid The ID of the user being notified
387  * @param string $hostname The server's hostname
388  * @return string A unique message-id
389  */
390 function forum_get_email_message_id($postid, $usertoid, $hostname) {
391     return '<'.hash('sha256',$postid.'to'.$usertoid).'@'.$hostname.'>';
394 /**
395  * Removes properties from user record that are not necessary
396  * for sending post notifications.
397  * @param stdClass $user
398  * @return void, $user parameter is modified
399  */
400 function forum_cron_minimise_user_record(stdClass $user) {
402     // We store large amount of users in one huge array,
403     // make sure we do not store info there we do not actually need
404     // in mail generation code or messaging.
406     unset($user->institution);
407     unset($user->department);
408     unset($user->address);
409     unset($user->city);
410     unset($user->url);
411     unset($user->currentlogin);
412     unset($user->description);
413     unset($user->descriptionformat);
416 /**
417  * Function to be run periodically according to the moodle cron
418  * Finds all posts that have yet to be mailed out, and mails them
419  * out to all subscribers
420  *
421  * @global object
422  * @global object
423  * @global object
424  * @uses CONTEXT_MODULE
425  * @uses CONTEXT_COURSE
426  * @uses SITEID
427  * @uses FORMAT_PLAIN
428  * @return void
429  */
430 function forum_cron() {
431     global $CFG, $USER, $DB;
433     $site = get_site();
435     // All users that are subscribed to any post that needs sending,
436     // please increase $CFG->extramemorylimit on large sites that
437     // send notifications to a large number of users.
438     $users = array();
439     $userscount = 0; // Cached user counter - count($users) in PHP is horribly slow!!!
441     // status arrays
442     $mailcount  = array();
443     $errorcount = array();
445     // caches
446     $discussions     = array();
447     $forums          = array();
448     $courses         = array();
449     $coursemodules   = array();
450     $subscribedusers = array();
453     // Posts older than 2 days will not be mailed.  This is to avoid the problem where
454     // cron has not been running for a long time, and then suddenly people are flooded
455     // with mail from the past few weeks or months
456     $timenow   = time();
457     $endtime   = $timenow - $CFG->maxeditingtime;
458     $starttime = $endtime - 48 * 3600;   // Two days earlier
460     if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
461         // Mark them all now as being mailed.  It's unlikely but possible there
462         // might be an error later so that a post is NOT actually mailed out,
463         // but since mail isn't crucial, we can accept this risk.  Doing it now
464         // prevents the risk of duplicated mails, which is a worse problem.
466         if (!forum_mark_old_posts_as_mailed($endtime)) {
467             mtrace('Errors occurred while trying to mark some posts as being mailed.');
468             return false;  // Don't continue trying to mail them, in case we are in a cron loop
469         }
471         // checking post validity, and adding users to loop through later
472         foreach ($posts as $pid => $post) {
474             $discussionid = $post->discussion;
475             if (!isset($discussions[$discussionid])) {
476                 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
477                     $discussions[$discussionid] = $discussion;
478                 } else {
479                     mtrace('Could not find discussion '.$discussionid);
480                     unset($posts[$pid]);
481                     continue;
482                 }
483             }
484             $forumid = $discussions[$discussionid]->forum;
485             if (!isset($forums[$forumid])) {
486                 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
487                     $forums[$forumid] = $forum;
488                 } else {
489                     mtrace('Could not find forum '.$forumid);
490                     unset($posts[$pid]);
491                     continue;
492                 }
493             }
494             $courseid = $forums[$forumid]->course;
495             if (!isset($courses[$courseid])) {
496                 if ($course = $DB->get_record('course', array('id' => $courseid))) {
497                     $courses[$courseid] = $course;
498                 } else {
499                     mtrace('Could not find course '.$courseid);
500                     unset($posts[$pid]);
501                     continue;
502                 }
503             }
504             if (!isset($coursemodules[$forumid])) {
505                 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
506                     $coursemodules[$forumid] = $cm;
507                 } else {
508                     mtrace('Could not find course module for forum '.$forumid);
509                     unset($posts[$pid]);
510                     continue;
511                 }
512             }
515             // caching subscribed users of each forum
516             if (!isset($subscribedusers[$forumid])) {
517                 $modcontext = context_module::instance($coursemodules[$forumid]->id);
518                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext, "u.*")) {
519                     foreach ($subusers as $postuser) {
520                         // this user is subscribed to this forum
521                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
522                         $userscount++;
523                         if ($userscount > FORUM_CRON_USER_CACHE) {
524                             // Store minimal user info.
525                             $minuser = new stdClass();
526                             $minuser->id = $postuser->id;
527                             $users[$postuser->id] = $minuser;
528                         } else {
529                             // Cache full user record.
530                             forum_cron_minimise_user_record($postuser);
531                             $users[$postuser->id] = $postuser;
532                         }
533                     }
534                     // Release memory.
535                     unset($subusers);
536                     unset($postuser);
537                 }
538             }
540             $mailcount[$pid] = 0;
541             $errorcount[$pid] = 0;
542         }
543     }
545     if ($users && $posts) {
547         $urlinfo = parse_url($CFG->wwwroot);
548         $hostname = $urlinfo['host'];
550         foreach ($users as $userto) {
552             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
554             mtrace('Processing user '.$userto->id);
556             // Init user caches - we keep the cache for one cycle only,
557             // otherwise it could consume too much memory.
558             if (isset($userto->username)) {
559                 $userto = clone($userto);
560             } else {
561                 $userto = $DB->get_record('user', array('id' => $userto->id));
562                 forum_cron_minimise_user_record($userto);
563             }
564             $userto->viewfullnames = array();
565             $userto->canpost       = array();
566             $userto->markposts     = array();
568             // set this so that the capabilities are cached, and environment matches receiving user
569             cron_setup_user($userto);
571             // reset the caches
572             foreach ($coursemodules as $forumid=>$unused) {
573                 $coursemodules[$forumid]->cache       = new stdClass();
574                 $coursemodules[$forumid]->cache->caps = array();
575                 unset($coursemodules[$forumid]->uservisible);
576             }
578             foreach ($posts as $pid => $post) {
580                 // Set up the environment for the post, discussion, forum, course
581                 $discussion = $discussions[$post->discussion];
582                 $forum      = $forums[$discussion->forum];
583                 $course     = $courses[$forum->course];
584                 $cm         =& $coursemodules[$forum->id];
586                 // Do some checks  to see if we can bail out now
587                 // Only active enrolled users are in the list of subscribers
588                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
589                     continue; // user does not subscribe to this forum
590                 }
592                 // Don't send email if the forum is Q&A and the user has not posted
593                 // Initial topics are still mailed
594                 if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id) && $pid != $discussion->firstpost) {
595                     mtrace('Did not email '.$userto->id.' because user has not posted in discussion');
596                     continue;
597                 }
599                 // Get info about the sending user
600                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
601                     $userfrom = $users[$post->userid];
602                     if (!isset($userfrom->idnumber)) {
603                         // Minimalised user info, fetch full record.
604                         $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
605                         forum_cron_minimise_user_record($userfrom);
606                     }
608                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
609                     forum_cron_minimise_user_record($userfrom);
610                     // Fetch only once if possible, we can add it to user list, it will be skipped anyway.
611                     if ($userscount <= FORUM_CRON_USER_CACHE) {
612                         $userscount++;
613                         $users[$userfrom->id] = $userfrom;
614                     }
616                 } else {
617                     mtrace('Could not find user '.$post->userid);
618                     continue;
619                 }
621                 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
623                 // setup global $COURSE properly - needed for roles and languages
624                 cron_setup_user($userto, $course);
626                 // Fill caches
627                 if (!isset($userto->viewfullnames[$forum->id])) {
628                     $modcontext = context_module::instance($cm->id);
629                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
630                 }
631                 if (!isset($userto->canpost[$discussion->id])) {
632                     $modcontext = context_module::instance($cm->id);
633                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
634                 }
635                 if (!isset($userfrom->groups[$forum->id])) {
636                     if (!isset($userfrom->groups)) {
637                         $userfrom->groups = array();
638                         if (isset($users[$userfrom->id])) {
639                             $users[$userfrom->id]->groups = array();
640                         }
641                     }
642                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
643                     if (isset($users[$userfrom->id])) {
644                         $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
645                     }
646                 }
648                 // Make sure groups allow this user to see this email
649                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
650                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
651                         continue;                           // Be safe and don't send it to anyone
652                     }
654                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
655                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
656                         continue;
657                     }
658                 }
660                 // Make sure we're allowed to see it...
661                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
662                     mtrace('user '.$userto->id. ' can not see '.$post->id);
663                     continue;
664                 }
666                 // OK so we need to send the email.
668                 // Does the user want this post in a digest?  If so postpone it for now.
669                 if ($userto->maildigest > 0) {
670                     // This user wants the mails to be in digest form
671                     $queue = new stdClass();
672                     $queue->userid       = $userto->id;
673                     $queue->discussionid = $discussion->id;
674                     $queue->postid       = $post->id;
675                     $queue->timemodified = $post->created;
676                     $DB->insert_record('forum_queue', $queue);
677                     continue;
678                 }
681                 // Prepare to actually send the post now, and build up the content
683                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
685                 $userfrom->customheaders = array (  // Headers to make emails easier to track
686                            'Precedence: Bulk',
687                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
688                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
689                            'Message-ID: '.forum_get_email_message_id($post->id, $userto->id, $hostname),
690                            'X-Course-Id: '.$course->id,
691                            'X-Course-Name: '.format_string($course->fullname, true)
692                 );
694                 if ($post->parent) {  // This post is a reply, so add headers for threading (see MDL-22551)
695                     $userfrom->customheaders[] = 'In-Reply-To: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
696                     $userfrom->customheaders[] = 'References: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
697                 }
699                 $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
701                 $postsubject = html_to_text("$shortname: ".format_string($post->subject, true));
702                 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
703                 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
705                 // Send the post now!
707                 mtrace('Sending ', '');
709                 $eventdata = new stdClass();
710                 $eventdata->component        = 'mod_forum';
711                 $eventdata->name             = 'posts';
712                 $eventdata->userfrom         = $userfrom;
713                 $eventdata->userto           = $userto;
714                 $eventdata->subject          = $postsubject;
715                 $eventdata->fullmessage      = $posttext;
716                 $eventdata->fullmessageformat = FORMAT_PLAIN;
717                 $eventdata->fullmessagehtml  = $posthtml;
718                 $eventdata->notification = 1;
720                 // If forum_replytouser is not set then send mail using the noreplyaddress.
721                 if (empty($CFG->forum_replytouser)) {
722                     // Clone userfrom as it is referenced by $users.
723                     $cloneduserfrom = clone($userfrom);
724                     $cloneduserfrom->email = $CFG->noreplyaddress;
725                     $eventdata->userfrom = $cloneduserfrom;
726                 }
728                 $smallmessagestrings = new stdClass();
729                 $smallmessagestrings->user = fullname($userfrom);
730                 $smallmessagestrings->forumname = "$shortname: ".format_string($forum->name,true).": ".$discussion->name;
731                 $smallmessagestrings->message = $post->message;
732                 //make sure strings are in message recipients language
733                 $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
735                 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
736                 $eventdata->contexturlname = $discussion->name;
738                 $mailresult = message_send($eventdata);
739                 if (!$mailresult){
740                     mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
741                          " ($userto->email) .. not trying again.");
742                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
743                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
744                     $errorcount[$post->id]++;
745                 } else {
746                     $mailcount[$post->id]++;
748                 // Mark post as read if forum_usermarksread is set off
749                     if (!$CFG->forum_usermarksread) {
750                         $userto->markposts[$post->id] = $post->id;
751                     }
752                 }
754                 mtrace('post '.$post->id. ': '.$post->subject);
755             }
757             // mark processed posts as read
758             forum_tp_mark_posts_read($userto, $userto->markposts);
759             unset($userto);
760         }
761     }
763     if ($posts) {
764         foreach ($posts as $post) {
765             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
766             if ($errorcount[$post->id]) {
767                 $DB->set_field('forum_posts', 'mailed', FORUM_MAILED_ERROR, array('id' => $post->id));
768             }
769         }
770     }
772     // release some memory
773     unset($subscribedusers);
774     unset($mailcount);
775     unset($errorcount);
777     cron_setup_user();
779     $sitetimezone = $CFG->timezone;
781     // Now see if there are any digest mails waiting to be sent, and if we should send them
783     mtrace('Starting digest processing...');
785     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
787     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
788         set_config('digestmailtimelast', 0);
789     }
791     $timenow = time();
792     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
794     // Delete any really old ones (normally there shouldn't be any)
795     $weekago = $timenow - (7 * 24 * 3600);
796     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
797     mtrace ('Cleaned old digest records');
799     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
801         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
803         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
805         if ($digestposts_rs->valid()) {
807             // We have work to do
808             $usermailcount = 0;
810             //caches - reuse the those filled before too
811             $discussionposts = array();
812             $userdiscussions = array();
814             foreach ($digestposts_rs as $digestpost) {
815                 if (!isset($posts[$digestpost->postid])) {
816                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
817                         $posts[$digestpost->postid] = $post;
818                     } else {
819                         continue;
820                     }
821                 }
822                 $discussionid = $digestpost->discussionid;
823                 if (!isset($discussions[$discussionid])) {
824                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
825                         $discussions[$discussionid] = $discussion;
826                     } else {
827                         continue;
828                     }
829                 }
830                 $forumid = $discussions[$discussionid]->forum;
831                 if (!isset($forums[$forumid])) {
832                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
833                         $forums[$forumid] = $forum;
834                     } else {
835                         continue;
836                     }
837                 }
839                 $courseid = $forums[$forumid]->course;
840                 if (!isset($courses[$courseid])) {
841                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
842                         $courses[$courseid] = $course;
843                     } else {
844                         continue;
845                     }
846                 }
848                 if (!isset($coursemodules[$forumid])) {
849                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
850                         $coursemodules[$forumid] = $cm;
851                     } else {
852                         continue;
853                     }
854                 }
855                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
856                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
857             }
858             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
860             // Data collected, start sending out emails to each user
861             foreach ($userdiscussions as $userid => $thesediscussions) {
863                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
865                 cron_setup_user();
867                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
869                 // First of all delete all the queue entries for this user
870                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
872                 // Init user caches - we keep the cache for one cycle only,
873                 // otherwise it would unnecessarily consume memory.
874                 if (array_key_exists($userid, $users) and isset($users[$userid]->username)) {
875                     $userto = clone($users[$userid]);
876                 } else {
877                     $userto = $DB->get_record('user', array('id' => $userid));
878                     forum_cron_minimise_user_record($userto);
879                 }
880                 $userto->viewfullnames = array();
881                 $userto->canpost       = array();
882                 $userto->markposts     = array();
884                 // Override the language and timezone of the "current" user, so that
885                 // mail is customised for the receiver.
886                 cron_setup_user($userto);
888                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
890                 $headerdata = new stdClass();
891                 $headerdata->sitename = format_string($site->fullname, true);
892                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
894                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
895                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
897                 $posthtml = "<head>";
898 /*                foreach ($CFG->stylesheets as $stylesheet) {
899                     //TODO: MDL-21120
900                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
901                 }*/
902                 $posthtml .= "</head>\n<body id=\"email\">\n";
903                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
905                 foreach ($thesediscussions as $discussionid) {
907                     @set_time_limit(120);   // to be reset for each post
909                     $discussion = $discussions[$discussionid];
910                     $forum      = $forums[$discussion->forum];
911                     $course     = $courses[$forum->course];
912                     $cm         = $coursemodules[$forum->id];
914                     //override language
915                     cron_setup_user($userto, $course);
917                     // Fill caches
918                     if (!isset($userto->viewfullnames[$forum->id])) {
919                         $modcontext = context_module::instance($cm->id);
920                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
921                     }
922                     if (!isset($userto->canpost[$discussion->id])) {
923                         $modcontext = context_module::instance($cm->id);
924                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
925                     }
927                     $strforums      = get_string('forums', 'forum');
928                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
929                     $canreply       = $userto->canpost[$discussion->id];
930                     $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
932                     $posttext .= "\n \n";
933                     $posttext .= '=====================================================================';
934                     $posttext .= "\n \n";
935                     $posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
936                     if ($discussion->name != $forum->name) {
937                         $posttext  .= " -> ".format_string($discussion->name,true);
938                     }
939                     $posttext .= "\n";
940                     $posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
941                     $posttext .= "\n";
943                     $posthtml .= "<p><font face=\"sans-serif\">".
944                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
945                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
946                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
947                     if ($discussion->name == $forum->name) {
948                         $posthtml .= "</font></p>";
949                     } else {
950                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
951                     }
952                     $posthtml .= '<p>';
954                     $postsarray = $discussionposts[$discussionid];
955                     sort($postsarray);
957                     foreach ($postsarray as $postid) {
958                         $post = $posts[$postid];
960                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
961                             $userfrom = $users[$post->userid];
962                             if (!isset($userfrom->idnumber)) {
963                                 $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
964                                 forum_cron_minimise_user_record($userfrom);
965                             }
967                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
968                             forum_cron_minimise_user_record($userfrom);
969                             if ($userscount <= FORUM_CRON_USER_CACHE) {
970                                 $userscount++;
971                                 $users[$userfrom->id] = $userfrom;
972                             }
974                         } else {
975                             mtrace('Could not find user '.$post->userid);
976                             continue;
977                         }
979                         if (!isset($userfrom->groups[$forum->id])) {
980                             if (!isset($userfrom->groups)) {
981                                 $userfrom->groups = array();
982                                 if (isset($users[$userfrom->id])) {
983                                     $users[$userfrom->id]->groups = array();
984                                 }
985                             }
986                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
987                             if (isset($users[$userfrom->id])) {
988                                 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
989                             }
990                         }
992                         $userfrom->customheaders = array ("Precedence: Bulk");
994                         if ($userto->maildigest == 2) {
995                             // Subjects and link only
996                             $posttext .= "\n";
997                             $posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
998                             $by = new stdClass();
999                             $by->name = fullname($userfrom);
1000                             $by->date = userdate($post->modified);
1001                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
1002                             $posttext .= "\n---------------------------------------------------------------------";
1004                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
1005                             $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>';
1007                         } else {
1008                             // The full treatment
1009                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
1010                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1012                         // Create an array of postid's for this user to mark as read.
1013                             if (!$CFG->forum_usermarksread) {
1014                                 $userto->markposts[$post->id] = $post->id;
1015                             }
1016                         }
1017                     }
1018                     if ($canunsubscribe) {
1019                         $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>";
1020                     } else {
1021                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
1022                     }
1023                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
1024                 }
1025                 $posthtml .= '</body>';
1027                 if (empty($userto->mailformat) || $userto->mailformat != 1) {
1028                     // This user DOESN'T want to receive HTML
1029                     $posthtml = '';
1030                 }
1032                 $attachment = $attachname='';
1033                 // Directly email forum digests rather than sending them via messaging, use the
1034                 // site shortname as 'from name', the noreply address will be used by email_to_user.
1035                 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname);
1037                 if (!$mailresult) {
1038                     mtrace("ERROR!");
1039                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
1040                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
1041                 } else {
1042                     mtrace("success.");
1043                     $usermailcount++;
1045                     // Mark post as read if forum_usermarksread is set off
1046                     forum_tp_mark_posts_read($userto, $userto->markposts);
1047                 }
1048             }
1049         }
1050     /// We have finishied all digest emails, update $CFG->digestmailtimelast
1051         set_config('digestmailtimelast', $timenow);
1052     }
1054     cron_setup_user();
1056     if (!empty($usermailcount)) {
1057         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
1058     }
1060     if (!empty($CFG->forum_lastreadclean)) {
1061         $timenow = time();
1062         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
1063             set_config('forum_lastreadclean', $timenow);
1064             mtrace('Removing old forum read tracking info...');
1065             forum_tp_clean_read_records();
1066         }
1067     } else {
1068         set_config('forum_lastreadclean', time());
1069     }
1072     return true;
1075 /**
1076  * Builds and returns the body of the email notification in plain text.
1077  *
1078  * @global object
1079  * @global object
1080  * @uses CONTEXT_MODULE
1081  * @param object $course
1082  * @param object $cm
1083  * @param object $forum
1084  * @param object $discussion
1085  * @param object $post
1086  * @param object $userfrom
1087  * @param object $userto
1088  * @param boolean $bare
1089  * @return string The email body in plain text format.
1090  */
1091 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
1092     global $CFG, $USER;
1094     $modcontext = context_module::instance($cm->id);
1096     if (!isset($userto->viewfullnames[$forum->id])) {
1097         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
1098     } else {
1099         $viewfullnames = $userto->viewfullnames[$forum->id];
1100     }
1102     if (!isset($userto->canpost[$discussion->id])) {
1103         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
1104     } else {
1105         $canreply = $userto->canpost[$discussion->id];
1106     }
1108     $by = New stdClass;
1109     $by->name = fullname($userfrom, $viewfullnames);
1110     $by->date = userdate($post->modified, "", $userto->timezone);
1112     $strbynameondate = get_string('bynameondate', 'forum', $by);
1114     $strforums = get_string('forums', 'forum');
1116     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1118     $posttext = '';
1120     if (!$bare) {
1121         $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
1122         $posttext  = "$shortname -> $strforums -> ".format_string($forum->name,true);
1124         if ($discussion->name != $forum->name) {
1125             $posttext  .= " -> ".format_string($discussion->name,true);
1126         }
1127     }
1129     // add absolute file links
1130     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
1132     $posttext .= "\n";
1133     $posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
1134     $posttext .= "\n---------------------------------------------------------------------\n";
1135     $posttext .= format_string($post->subject,true);
1136     if ($bare) {
1137         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1138     }
1139     $posttext .= "\n".$strbynameondate."\n";
1140     $posttext .= "---------------------------------------------------------------------\n";
1141     $posttext .= format_text_email($post->message, $post->messageformat);
1142     $posttext .= "\n\n";
1143     $posttext .= forum_print_attachments($post, $cm, "text");
1145     if (!$bare && $canreply) {
1146         $posttext .= "---------------------------------------------------------------------\n";
1147         $posttext .= get_string("postmailinfo", "forum", $shortname)."\n";
1148         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1149     }
1150     if (!$bare && $canunsubscribe) {
1151         $posttext .= "\n---------------------------------------------------------------------\n";
1152         $posttext .= get_string("unsubscribe", "forum");
1153         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1154     }
1156     return $posttext;
1159 /**
1160  * Builds and returns the body of the email notification in html format.
1161  *
1162  * @global object
1163  * @param object $course
1164  * @param object $cm
1165  * @param object $forum
1166  * @param object $discussion
1167  * @param object $post
1168  * @param object $userfrom
1169  * @param object $userto
1170  * @return string The email text in HTML format
1171  */
1172 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1173     global $CFG;
1175     if ($userto->mailformat != 1) {  // Needs to be HTML
1176         return '';
1177     }
1179     if (!isset($userto->canpost[$discussion->id])) {
1180         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course);
1181     } else {
1182         $canreply = $userto->canpost[$discussion->id];
1183     }
1185     $strforums = get_string('forums', 'forum');
1186     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1187     $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
1189     $posthtml = '<head>';
1190 /*    foreach ($CFG->stylesheets as $stylesheet) {
1191         //TODO: MDL-21120
1192         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1193     }*/
1194     $posthtml .= '</head>';
1195     $posthtml .= "\n<body id=\"email\">\n\n";
1197     $posthtml .= '<div class="navbar">'.
1198     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$shortname.'</a> &raquo; '.
1199     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1200     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1201     if ($discussion->name == $forum->name) {
1202         $posthtml .= '</div>';
1203     } else {
1204         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1205                      format_string($discussion->name,true).'</a></div>';
1206     }
1207     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1209     if ($canunsubscribe) {
1210         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1211                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1212                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1213     }
1215     $posthtml .= '</body>';
1217     return $posthtml;
1221 /**
1222  *
1223  * @param object $course
1224  * @param object $user
1225  * @param object $mod TODO this is not used in this function, refactor
1226  * @param object $forum
1227  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1228  */
1229 function forum_user_outline($course, $user, $mod, $forum) {
1230     global $CFG;
1231     require_once("$CFG->libdir/gradelib.php");
1232     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1233     if (empty($grades->items[0]->grades)) {
1234         $grade = false;
1235     } else {
1236         $grade = reset($grades->items[0]->grades);
1237     }
1239     $count = forum_count_user_posts($forum->id, $user->id);
1241     if ($count && $count->postcount > 0) {
1242         $result = new stdClass();
1243         $result->info = get_string("numposts", "forum", $count->postcount);
1244         $result->time = $count->lastpost;
1245         if ($grade) {
1246             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1247         }
1248         return $result;
1249     } else if ($grade) {
1250         $result = new stdClass();
1251         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1253         //datesubmitted == time created. dategraded == time modified or time overridden
1254         //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
1255         //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
1256         if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1257             $result->time = $grade->dategraded;
1258         } else {
1259             $result->time = $grade->datesubmitted;
1260         }
1262         return $result;
1263     }
1264     return NULL;
1268 /**
1269  * @global object
1270  * @global object
1271  * @param object $coure
1272  * @param object $user
1273  * @param object $mod
1274  * @param object $forum
1275  */
1276 function forum_user_complete($course, $user, $mod, $forum) {
1277     global $CFG,$USER, $OUTPUT;
1278     require_once("$CFG->libdir/gradelib.php");
1280     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1281     if (!empty($grades->items[0]->grades)) {
1282         $grade = reset($grades->items[0]->grades);
1283         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1284         if ($grade->str_feedback) {
1285             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1286         }
1287     }
1289     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1291         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1292             print_error('invalidcoursemodule');
1293         }
1294         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1296         foreach ($posts as $post) {
1297             if (!isset($discussions[$post->discussion])) {
1298                 continue;
1299             }
1300             $discussion = $discussions[$post->discussion];
1302             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1303         }
1304     } else {
1305         echo "<p>".get_string("noposts", "forum")."</p>";
1306     }
1314 /**
1315  * @global object
1316  * @global object
1317  * @global object
1318  * @param array $courses
1319  * @param array $htmlarray
1320  */
1321 function forum_print_overview($courses,&$htmlarray) {
1322     global $USER, $CFG, $DB, $SESSION;
1324     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1325         return array();
1326     }
1328     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1329         return;
1330     }
1332     // Courses to search for new posts
1333     $coursessqls = array();
1334     $params = array();
1335     foreach ($courses as $course) {
1337         // If the user has never entered into the course all posts are pending
1338         if ($course->lastaccess == 0) {
1339             $coursessqls[] = '(f.course = ?)';
1340             $params[] = $course->id;
1342         // Only posts created after the course last access
1343         } else {
1344             $coursessqls[] = '(f.course = ? AND p.created > ?)';
1345             $params[] = $course->id;
1346             $params[] = $course->lastaccess;
1347         }
1348     }
1349     $params[] = $USER->id;
1350     $coursessql = implode(' OR ', $coursessqls);
1352     $sql = "SELECT f.id, COUNT(*) as count "
1353                 .'FROM {forum} f '
1354                 .'JOIN {forum_discussions} d ON d.forum  = f.id '
1355                 .'JOIN {forum_posts} p ON p.discussion = d.id '
1356                 ."WHERE ($coursessql) "
1357                 .'AND p.userid != ? '
1358                 .'GROUP BY f.id';
1360     if (!$new = $DB->get_records_sql($sql, $params)) {
1361         $new = array(); // avoid warnings
1362     }
1364     // also get all forum tracking stuff ONCE.
1365     $trackingforums = array();
1366     foreach ($forums as $forum) {
1367         if (forum_tp_can_track_forums($forum)) {
1368             $trackingforums[$forum->id] = $forum;
1369         }
1370     }
1372     if (count($trackingforums) > 0) {
1373         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1374         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1375             ' FROM {forum_posts} p '.
1376             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1377             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1378         $params = array($USER->id);
1380         foreach ($trackingforums as $track) {
1381             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1382             $params[] = $track->id;
1383             if (isset($SESSION->currentgroup[$track->course])) {
1384                 $groupid =  $SESSION->currentgroup[$track->course];
1385             } else {
1386                 // get first groupid
1387                 $groupids = groups_get_all_groups($track->course, $USER->id);
1388                 if ($groupids) {
1389                     reset($groupids);
1390                     $groupid = key($groupids);
1391                     $SESSION->currentgroup[$track->course] = $groupid;
1392                 } else {
1393                     $groupid = 0;
1394                 }
1395                 unset($groupids);
1396             }
1397             $params[] = $groupid;
1398         }
1399         $sql = substr($sql,0,-3); // take off the last OR
1400         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1401         $params[] = $cutoffdate;
1403         if (!$unread = $DB->get_records_sql($sql, $params)) {
1404             $unread = array();
1405         }
1406     } else {
1407         $unread = array();
1408     }
1410     if (empty($unread) and empty($new)) {
1411         return;
1412     }
1414     $strforum = get_string('modulename','forum');
1416     foreach ($forums as $forum) {
1417         $str = '';
1418         $count = 0;
1419         $thisunread = 0;
1420         $showunread = false;
1421         // either we have something from logs, or trackposts, or nothing.
1422         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1423             $count = $new[$forum->id]->count;
1424         }
1425         if (array_key_exists($forum->id,$unread)) {
1426             $thisunread = $unread[$forum->id]->count;
1427             $showunread = true;
1428         }
1429         if ($count > 0 || $thisunread > 0) {
1430             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1431                 $forum->name.'</a></div>';
1432             $str .= '<div class="info"><span class="postsincelogin">';
1433             $str .= get_string('overviewnumpostssince', 'forum', $count)."</span>";
1434             if (!empty($showunread)) {
1435                 $str .= '<div class="unreadposts">'.get_string('overviewnumunread', 'forum', $thisunread).'</div>';
1436             }
1437             $str .= '</div></div>';
1438         }
1439         if (!empty($str)) {
1440             if (!array_key_exists($forum->course,$htmlarray)) {
1441                 $htmlarray[$forum->course] = array();
1442             }
1443             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1444                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1445             }
1446             $htmlarray[$forum->course]['forum'] .= $str;
1447         }
1448     }
1451 /**
1452  * Given a course and a date, prints a summary of all the new
1453  * messages posted in the course since that date
1454  *
1455  * @global object
1456  * @global object
1457  * @global object
1458  * @uses CONTEXT_MODULE
1459  * @uses VISIBLEGROUPS
1460  * @param object $course
1461  * @param bool $viewfullnames capability
1462  * @param int $timestart
1463  * @return bool success
1464  */
1465 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1466     global $CFG, $USER, $DB, $OUTPUT;
1468     // do not use log table if possible, it may be huge and is expensive to join with other tables
1470     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1471                                               d.timestart, d.timeend, d.userid AS duserid,
1472                                               u.firstname, u.lastname, u.email, u.picture
1473                                          FROM {forum_posts} p
1474                                               JOIN {forum_discussions} d ON d.id = p.discussion
1475                                               JOIN {forum} f             ON f.id = d.forum
1476                                               JOIN {user} u              ON u.id = p.userid
1477                                         WHERE p.created > ? AND f.course = ?
1478                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1479          return false;
1480     }
1482     $modinfo = get_fast_modinfo($course);
1484     $groupmodes = array();
1485     $cms    = array();
1487     $strftimerecent = get_string('strftimerecent');
1489     $printposts = array();
1490     foreach ($posts as $post) {
1491         if (!isset($modinfo->instances['forum'][$post->forum])) {
1492             // not visible
1493             continue;
1494         }
1495         $cm = $modinfo->instances['forum'][$post->forum];
1496         if (!$cm->uservisible) {
1497             continue;
1498         }
1499         $context = context_module::instance($cm->id);
1501         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1502             continue;
1503         }
1505         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1506           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1507             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1508                 continue;
1509             }
1510         }
1512         $groupmode = groups_get_activity_groupmode($cm, $course);
1514         if ($groupmode) {
1515             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1516                 // oki (Open discussions have groupid -1)
1517             } else {
1518                 // separate mode
1519                 if (isguestuser()) {
1520                     // shortcut
1521                     continue;
1522                 }
1524                 if (!in_array($post->groupid, $modinfo->get_groups($cm->groupingid))) {
1525                     continue;
1526                 }
1527             }
1528         }
1530         $printposts[] = $post;
1531     }
1532     unset($posts);
1534     if (!$printposts) {
1535         return false;
1536     }
1538     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1539     echo "\n<ul class='unlist'>\n";
1541     foreach ($printposts as $post) {
1542         $subjectclass = empty($post->parent) ? ' bold' : '';
1544         echo '<li><div class="head">'.
1545                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1546                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1547              '</div>';
1548         echo '<div class="info'.$subjectclass.'">';
1549         if (empty($post->parent)) {
1550             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1551         } else {
1552             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1553         }
1554         $post->subject = break_up_long_words(format_string($post->subject, true));
1555         echo $post->subject;
1556         echo "</a>\"</div></li>\n";
1557     }
1559     echo "</ul>\n";
1561     return true;
1564 /**
1565  * Return grade for given user or all users.
1566  *
1567  * @global object
1568  * @global object
1569  * @param object $forum
1570  * @param int $userid optional user id, 0 means all users
1571  * @return array array of grades, false if none
1572  */
1573 function forum_get_user_grades($forum, $userid = 0) {
1574     global $CFG;
1576     require_once($CFG->dirroot.'/rating/lib.php');
1578     $ratingoptions = new stdClass;
1579     $ratingoptions->component = 'mod_forum';
1580     $ratingoptions->ratingarea = 'post';
1582     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1583     $ratingoptions->modulename = 'forum';
1584     $ratingoptions->moduleid   = $forum->id;
1585     $ratingoptions->userid = $userid;
1586     $ratingoptions->aggregationmethod = $forum->assessed;
1587     $ratingoptions->scaleid = $forum->scale;
1588     $ratingoptions->itemtable = 'forum_posts';
1589     $ratingoptions->itemtableusercolumn = 'userid';
1591     $rm = new rating_manager();
1592     return $rm->get_user_grades($ratingoptions);
1595 /**
1596  * Update activity grades
1597  *
1598  * @category grade
1599  * @param object $forum
1600  * @param int $userid specific user only, 0 means all
1601  * @param boolean $nullifnone return null if grade does not exist
1602  * @return void
1603  */
1604 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1605     global $CFG, $DB;
1606     require_once($CFG->libdir.'/gradelib.php');
1608     if (!$forum->assessed) {
1609         forum_grade_item_update($forum);
1611     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1612         forum_grade_item_update($forum, $grades);
1614     } else if ($userid and $nullifnone) {
1615         $grade = new stdClass();
1616         $grade->userid   = $userid;
1617         $grade->rawgrade = NULL;
1618         forum_grade_item_update($forum, $grade);
1620     } else {
1621         forum_grade_item_update($forum);
1622     }
1625 /**
1626  * Update all grades in gradebook.
1627  * @global object
1628  */
1629 function forum_upgrade_grades() {
1630     global $DB;
1632     $sql = "SELECT COUNT('x')
1633               FROM {forum} f, {course_modules} cm, {modules} m
1634              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1635     $count = $DB->count_records_sql($sql);
1637     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1638               FROM {forum} f, {course_modules} cm, {modules} m
1639              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1640     $rs = $DB->get_recordset_sql($sql);
1641     if ($rs->valid()) {
1642         $pbar = new progress_bar('forumupgradegrades', 500, true);
1643         $i=0;
1644         foreach ($rs as $forum) {
1645             $i++;
1646             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1647             forum_update_grades($forum, 0, false);
1648             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1649         }
1650     }
1651     $rs->close();
1654 /**
1655  * Create/update grade item for given forum
1656  *
1657  * @category grade
1658  * @uses GRADE_TYPE_NONE
1659  * @uses GRADE_TYPE_VALUE
1660  * @uses GRADE_TYPE_SCALE
1661  * @param stdClass $forum Forum object with extra cmidnumber
1662  * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
1663  * @return int 0 if ok
1664  */
1665 function forum_grade_item_update($forum, $grades=NULL) {
1666     global $CFG;
1667     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1668         require_once($CFG->libdir.'/gradelib.php');
1669     }
1671     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1673     if (!$forum->assessed or $forum->scale == 0) {
1674         $params['gradetype'] = GRADE_TYPE_NONE;
1676     } else if ($forum->scale > 0) {
1677         $params['gradetype'] = GRADE_TYPE_VALUE;
1678         $params['grademax']  = $forum->scale;
1679         $params['grademin']  = 0;
1681     } else if ($forum->scale < 0) {
1682         $params['gradetype'] = GRADE_TYPE_SCALE;
1683         $params['scaleid']   = -$forum->scale;
1684     }
1686     if ($grades  === 'reset') {
1687         $params['reset'] = true;
1688         $grades = NULL;
1689     }
1691     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1694 /**
1695  * Delete grade item for given forum
1696  *
1697  * @category grade
1698  * @param stdClass $forum Forum object
1699  * @return grade_item
1700  */
1701 function forum_grade_item_delete($forum) {
1702     global $CFG;
1703     require_once($CFG->libdir.'/gradelib.php');
1705     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1709 /**
1710  * This function returns if a scale is being used by one forum
1711  *
1712  * @global object
1713  * @param int $forumid
1714  * @param int $scaleid negative number
1715  * @return bool
1716  */
1717 function forum_scale_used ($forumid,$scaleid) {
1718     global $DB;
1719     $return = false;
1721     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1723     if (!empty($rec) && !empty($scaleid)) {
1724         $return = true;
1725     }
1727     return $return;
1730 /**
1731  * Checks if scale is being used by any instance of forum
1732  *
1733  * This is used to find out if scale used anywhere
1734  *
1735  * @global object
1736  * @param $scaleid int
1737  * @return boolean True if the scale is used by any forum
1738  */
1739 function forum_scale_used_anywhere($scaleid) {
1740     global $DB;
1741     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1742         return true;
1743     } else {
1744         return false;
1745     }
1748 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1750 /**
1751  * Gets a post with all info ready for forum_print_post
1752  * Most of these joins are just to get the forum id
1753  *
1754  * @global object
1755  * @global object
1756  * @param int $postid
1757  * @return mixed array of posts or false
1758  */
1759 function forum_get_post_full($postid) {
1760     global $CFG, $DB;
1762     $allnames = get_all_user_name_fields(true, 'u');
1763     return $DB->get_record_sql("SELECT p.*, d.forum, $allnames, u.email, u.picture, u.imagealt
1764                              FROM {forum_posts} p
1765                                   JOIN {forum_discussions} d ON p.discussion = d.id
1766                                   LEFT JOIN {user} u ON p.userid = u.id
1767                             WHERE p.id = ?", array($postid));
1770 /**
1771  * Gets posts with all info ready for forum_print_post
1772  * We pass forumid in because we always know it so no need to make a
1773  * complicated join to find it out.
1774  *
1775  * @global object
1776  * @global object
1777  * @return mixed array of posts or false
1778  */
1779 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1780     global $CFG, $DB;
1782     $allnames = get_all_user_name_fields(true, 'u');
1783     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, $allnames, u.email, u.picture, u.imagealt
1784                               FROM {forum_posts} p
1785                          LEFT JOIN {user} u ON p.userid = u.id
1786                              WHERE p.discussion = ?
1787                                AND p.parent > 0 $sort", array($discussion));
1790 /**
1791  * Gets all posts in discussion including top parent.
1792  *
1793  * @global object
1794  * @global object
1795  * @global object
1796  * @param int $discussionid
1797  * @param string $sort
1798  * @param bool $tracking does user track the forum?
1799  * @return array of posts
1800  */
1801 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1802     global $CFG, $DB, $USER;
1804     $tr_sel  = "";
1805     $tr_join = "";
1806     $params = array();
1808     if ($tracking) {
1809         $now = time();
1810         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1811         $tr_sel  = ", fr.id AS postread";
1812         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1813         $params[] = $USER->id;
1814     }
1816     $allnames = get_all_user_name_fields(true, 'u');
1817     $params[] = $discussionid;
1818     if (!$posts = $DB->get_records_sql("SELECT p.*, $allnames, u.email, u.picture, u.imagealt $tr_sel
1819                                      FROM {forum_posts} p
1820                                           LEFT JOIN {user} u ON p.userid = u.id
1821                                           $tr_join
1822                                     WHERE p.discussion = ?
1823                                  ORDER BY $sort", $params)) {
1824         return array();
1825     }
1827     foreach ($posts as $pid=>$p) {
1828         if ($tracking) {
1829             if (forum_tp_is_post_old($p)) {
1830                  $posts[$pid]->postread = true;
1831             }
1832         }
1833         if (!$p->parent) {
1834             continue;
1835         }
1836         if (!isset($posts[$p->parent])) {
1837             continue; // parent does not exist??
1838         }
1839         if (!isset($posts[$p->parent]->children)) {
1840             $posts[$p->parent]->children = array();
1841         }
1842         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1843     }
1845     return $posts;
1848 /**
1849  * Gets posts with all info ready for forum_print_post
1850  * We pass forumid in because we always know it so no need to make a
1851  * complicated join to find it out.
1852  *
1853  * @global object
1854  * @global object
1855  * @param int $parent
1856  * @param int $forumid
1857  * @return array
1858  */
1859 function forum_get_child_posts($parent, $forumid) {
1860     global $CFG, $DB;
1862     $allnames = get_all_user_name_fields(true, 'u');
1863     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, $allnames, u.email, u.picture, u.imagealt
1864                               FROM {forum_posts} p
1865                          LEFT JOIN {user} u ON p.userid = u.id
1866                              WHERE p.parent = ?
1867                           ORDER BY p.created ASC", array($parent));
1870 /**
1871  * An array of forum objects that the user is allowed to read/search through.
1872  *
1873  * @global object
1874  * @global object
1875  * @global object
1876  * @param int $userid
1877  * @param int $courseid if 0, we look for forums throughout the whole site.
1878  * @return array of forum objects, or false if no matches
1879  *         Forum objects have the following attributes:
1880  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1881  *         viewhiddentimedposts
1882  */
1883 function forum_get_readable_forums($userid, $courseid=0) {
1885     global $CFG, $DB, $USER;
1886     require_once($CFG->dirroot.'/course/lib.php');
1888     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1889         print_error('notinstalled', 'forum');
1890     }
1892     if ($courseid) {
1893         $courses = $DB->get_records('course', array('id' => $courseid));
1894     } else {
1895         // If no course is specified, then the user can see SITE + his courses.
1896         $courses1 = $DB->get_records('course', array('id' => SITEID));
1897         $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
1898         $courses = array_merge($courses1, $courses2);
1899     }
1900     if (!$courses) {
1901         return array();
1902     }
1904     $readableforums = array();
1906     foreach ($courses as $course) {
1908         $modinfo = get_fast_modinfo($course);
1910         if (empty($modinfo->instances['forum'])) {
1911             // hmm, no forums?
1912             continue;
1913         }
1915         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1917         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1918             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1919                 continue;
1920             }
1921             $context = context_module::instance($cm->id);
1922             $forum = $courseforums[$forumid];
1923             $forum->context = $context;
1924             $forum->cm = $cm;
1926             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1927                 continue;
1928             }
1930          /// group access
1931             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1933                 $forum->onlygroups = $modinfo->get_groups($cm->groupingid);
1934                 $forum->onlygroups[] = -1;
1935             }
1937         /// hidden timed discussions
1938             $forum->viewhiddentimedposts = true;
1939             if (!empty($CFG->forum_enabletimedposts)) {
1940                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1941                     $forum->viewhiddentimedposts = false;
1942                 }
1943             }
1945         /// qanda access
1946             if ($forum->type == 'qanda'
1947                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1949                 // We need to check whether the user has posted in the qanda forum.
1950                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1951                                                     // the user is allowed to see in this forum.
1952                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1953                     foreach ($discussionspostedin as $d) {
1954                         $forum->onlydiscussions[] = $d->id;
1955                     }
1956                 }
1957             }
1959             $readableforums[$forum->id] = $forum;
1960         }
1962         unset($modinfo);
1964     } // End foreach $courses
1966     return $readableforums;
1969 /**
1970  * Returns a list of posts found using an array of search terms.
1971  *
1972  * @global object
1973  * @global object
1974  * @global object
1975  * @param array $searchterms array of search terms, e.g. word +word -word
1976  * @param int $courseid if 0, we search through the whole site
1977  * @param int $limitfrom
1978  * @param int $limitnum
1979  * @param int &$totalcount
1980  * @param string $extrasql
1981  * @return array|bool Array of posts found or false
1982  */
1983 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1984                             &$totalcount, $extrasql='') {
1985     global $CFG, $DB, $USER;
1986     require_once($CFG->libdir.'/searchlib.php');
1988     $forums = forum_get_readable_forums($USER->id, $courseid);
1990     if (count($forums) == 0) {
1991         $totalcount = 0;
1992         return false;
1993     }
1995     $now = round(time(), -2); // db friendly
1997     $fullaccess = array();
1998     $where = array();
1999     $params = array();
2001     foreach ($forums as $forumid => $forum) {
2002         $select = array();
2004         if (!$forum->viewhiddentimedposts) {
2005             $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
2006             $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
2007         }
2009         $cm = $forum->cm;
2010         $context = $forum->context;
2012         if ($forum->type == 'qanda'
2013             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
2014             if (!empty($forum->onlydiscussions)) {
2015                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
2016                 $params = array_merge($params, $discussionid_params);
2017                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
2018             } else {
2019                 $select[] = "p.parent = 0";
2020             }
2021         }
2023         if (!empty($forum->onlygroups)) {
2024             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
2025             $params = array_merge($params, $groupid_params);
2026             $select[] = "d.groupid $groupid_sql";
2027         }
2029         if ($select) {
2030             $selects = implode(" AND ", $select);
2031             $where[] = "(d.forum = :forum{$forumid} AND $selects)";
2032             $params['forum'.$forumid] = $forumid;
2033         } else {
2034             $fullaccess[] = $forumid;
2035         }
2036     }
2038     if ($fullaccess) {
2039         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
2040         $params = array_merge($params, $fullid_params);
2041         $where[] = "(d.forum $fullid_sql)";
2042     }
2044     $selectdiscussion = "(".implode(" OR ", $where).")";
2046     $messagesearch = '';
2047     $searchstring = '';
2049     // Need to concat these back together for parser to work.
2050     foreach($searchterms as $searchterm){
2051         if ($searchstring != '') {
2052             $searchstring .= ' ';
2053         }
2054         $searchstring .= $searchterm;
2055     }
2057     // We need to allow quoted strings for the search. The quotes *should* be stripped
2058     // by the parser, but this should be examined carefully for security implications.
2059     $searchstring = str_replace("\\\"","\"",$searchstring);
2060     $parser = new search_parser();
2061     $lexer = new search_lexer($parser);
2063     if ($lexer->parse($searchstring)) {
2064         $parsearray = $parser->get_parsed_array();
2065     // Experimental feature under 1.8! MDL-8830
2066     // Use alternative text searches if defined
2067     // This feature only works under mysql until properly implemented for other DBs
2068     // Requires manual creation of text index for forum_posts before enabling it:
2069     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2070     // Experimental feature under 1.8! MDL-8830
2071         if (!empty($CFG->forum_usetextsearches)) {
2072             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2073                                                  'p.userid', 'u.id', 'u.firstname',
2074                                                  'u.lastname', 'p.modified', 'd.forum');
2075         } else {
2076             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2077                                                  'p.userid', 'u.id', 'u.firstname',
2078                                                  'u.lastname', 'p.modified', 'd.forum');
2079         }
2080         $params = array_merge($params, $msparams);
2081     }
2083     $fromsql = "{forum_posts} p,
2084                   {forum_discussions} d,
2085                   {user} u";
2087     $selectsql = " $messagesearch
2088                AND p.discussion = d.id
2089                AND p.userid = u.id
2090                AND $selectdiscussion
2091                    $extrasql";
2093     $countsql = "SELECT COUNT(*)
2094                    FROM $fromsql
2095                   WHERE $selectsql";
2097     $allnames = get_all_user_name_fields(true, 'u');
2098     $searchsql = "SELECT p.*,
2099                          d.forum,
2100                          $allnames,
2101                          u.email,
2102                          u.picture,
2103                          u.imagealt
2104                     FROM $fromsql
2105                    WHERE $selectsql
2106                 ORDER BY p.modified DESC";
2108     $totalcount = $DB->count_records_sql($countsql, $params);
2110     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2113 /**
2114  * Returns a list of ratings for a particular post - sorted.
2115  *
2116  * TODO: Check if this function is actually used anywhere.
2117  * Up until the fix for MDL-27471 this function wasn't even returning.
2118  *
2119  * @param stdClass $context
2120  * @param int $postid
2121  * @param string $sort
2122  * @return array Array of ratings or false
2123  */
2124 function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
2125     $options = new stdClass;
2126     $options->context = $context;
2127     $options->component = 'mod_forum';
2128     $options->ratingarea = 'post';
2129     $options->itemid = $postid;
2130     $options->sort = "ORDER BY $sort";
2132     $rm = new rating_manager();
2133     return $rm->get_all_ratings_for_item($options);
2136 /**
2137  * Returns a list of all new posts that have not been mailed yet
2138  *
2139  * @param int $starttime posts created after this time
2140  * @param int $endtime posts created before this
2141  * @param int $now used for timed discussions only
2142  * @return array
2143  */
2144 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2145     global $CFG, $DB;
2147     $params = array();
2148     $params['mailed'] = FORUM_MAILED_PENDING;
2149     $params['ptimestart'] = $starttime;
2150     $params['ptimeend'] = $endtime;
2151     $params['mailnow'] = 1;
2153     if (!empty($CFG->forum_enabletimedposts)) {
2154         if (empty($now)) {
2155             $now = time();
2156         }
2157         $timedsql = "AND (d.timestart < :dtimestart AND (d.timeend = 0 OR d.timeend > :dtimeend))";
2158         $params['dtimestart'] = $now;
2159         $params['dtimeend'] = $now;
2160     } else {
2161         $timedsql = "";
2162     }
2164     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2165                                  FROM {forum_posts} p
2166                                  JOIN {forum_discussions} d ON d.id = p.discussion
2167                                  WHERE p.mailed = :mailed
2168                                  AND p.created >= :ptimestart
2169                                  AND (p.created < :ptimeend OR p.mailnow = :mailnow)
2170                                  $timedsql
2171                                  ORDER BY p.modified ASC", $params);
2174 /**
2175  * Marks posts before a certain time as being mailed already
2176  *
2177  * @global object
2178  * @global object
2179  * @param int $endtime
2180  * @param int $now Defaults to time()
2181  * @return bool
2182  */
2183 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2184     global $CFG, $DB;
2186     if (empty($now)) {
2187         $now = time();
2188     }
2190     $params = array();
2191     $params['mailedsuccess'] = FORUM_MAILED_SUCCESS;
2192     $params['now'] = $now;
2193     $params['endtime'] = $endtime;
2194     $params['mailnow'] = 1;
2195     $params['mailedpending'] = FORUM_MAILED_PENDING;
2197     if (empty($CFG->forum_enabletimedposts)) {
2198         return $DB->execute("UPDATE {forum_posts}
2199                              SET mailed = :mailedsuccess
2200                              WHERE (created < :endtime OR mailnow = :mailnow)
2201                              AND mailed = :mailedpending", $params);
2202     } else {
2203         return $DB->execute("UPDATE {forum_posts}
2204                              SET mailed = :mailedsuccess
2205                              WHERE discussion NOT IN (SELECT d.id
2206                                                       FROM {forum_discussions} d
2207                                                       WHERE d.timestart > :now)
2208                              AND (created < :endtime OR mailnow = :mailnow)
2209                              AND mailed = :mailedpending", $params);
2210     }
2213 /**
2214  * Get all the posts for a user in a forum suitable for forum_print_post
2215  *
2216  * @global object
2217  * @global object
2218  * @uses CONTEXT_MODULE
2219  * @return array
2220  */
2221 function forum_get_user_posts($forumid, $userid) {
2222     global $CFG, $DB;
2224     $timedsql = "";
2225     $params = array($forumid, $userid);
2227     if (!empty($CFG->forum_enabletimedposts)) {
2228         $cm = get_coursemodule_from_instance('forum', $forumid);
2229         if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2230             $now = time();
2231             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2232             $params[] = $now;
2233             $params[] = $now;
2234         }
2235     }
2237     $allnames = get_all_user_name_fields(true, 'u');
2238     return $DB->get_records_sql("SELECT p.*, d.forum, $allnames, u.email, u.picture, u.imagealt
2239                               FROM {forum} f
2240                                    JOIN {forum_discussions} d ON d.forum = f.id
2241                                    JOIN {forum_posts} p       ON p.discussion = d.id
2242                                    JOIN {user} u              ON u.id = p.userid
2243                              WHERE f.id = ?
2244                                    AND p.userid = ?
2245                                    $timedsql
2246                           ORDER BY p.modified ASC", $params);
2249 /**
2250  * Get all the discussions user participated in
2251  *
2252  * @global object
2253  * @global object
2254  * @uses CONTEXT_MODULE
2255  * @param int $forumid
2256  * @param int $userid
2257  * @return array Array or false
2258  */
2259 function forum_get_user_involved_discussions($forumid, $userid) {
2260     global $CFG, $DB;
2262     $timedsql = "";
2263     $params = array($forumid, $userid);
2264     if (!empty($CFG->forum_enabletimedposts)) {
2265         $cm = get_coursemodule_from_instance('forum', $forumid);
2266         if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2267             $now = time();
2268             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2269             $params[] = $now;
2270             $params[] = $now;
2271         }
2272     }
2274     return $DB->get_records_sql("SELECT DISTINCT d.*
2275                               FROM {forum} f
2276                                    JOIN {forum_discussions} d ON d.forum = f.id
2277                                    JOIN {forum_posts} p       ON p.discussion = d.id
2278                              WHERE f.id = ?
2279                                    AND p.userid = ?
2280                                    $timedsql", $params);
2283 /**
2284  * Get all the posts for a user in a forum suitable for forum_print_post
2285  *
2286  * @global object
2287  * @global object
2288  * @param int $forumid
2289  * @param int $userid
2290  * @return array of counts or false
2291  */
2292 function forum_count_user_posts($forumid, $userid) {
2293     global $CFG, $DB;
2295     $timedsql = "";
2296     $params = array($forumid, $userid);
2297     if (!empty($CFG->forum_enabletimedposts)) {
2298         $cm = get_coursemodule_from_instance('forum', $forumid);
2299         if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2300             $now = time();
2301             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2302             $params[] = $now;
2303             $params[] = $now;
2304         }
2305     }
2307     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2308                              FROM {forum} f
2309                                   JOIN {forum_discussions} d ON d.forum = f.id
2310                                   JOIN {forum_posts} p       ON p.discussion = d.id
2311                                   JOIN {user} u              ON u.id = p.userid
2312                             WHERE f.id = ?
2313                                   AND p.userid = ?
2314                                   $timedsql", $params);
2317 /**
2318  * Given a log entry, return the forum post details for it.
2319  *
2320  * @global object
2321  * @global object
2322  * @param object $log
2323  * @return array|null
2324  */
2325 function forum_get_post_from_log($log) {
2326     global $CFG, $DB;
2328     $allnames = get_all_user_name_fields(true, 'u');
2329     if ($log->action == "add post") {
2331         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid, $allnames, u.email, u.picture
2332                                  FROM {forum_discussions} d,
2333                                       {forum_posts} p,
2334                                       {forum} f,
2335                                       {user} u
2336                                 WHERE p.id = ?
2337                                   AND d.id = p.discussion
2338                                   AND p.userid = u.id
2339                                   AND u.deleted <> '1'
2340                                   AND f.id = d.forum", array($log->info));
2343     } else if ($log->action == "add discussion") {
2345         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid, $allnames, u.email, u.picture
2346                                  FROM {forum_discussions} d,
2347                                       {forum_posts} p,
2348                                       {forum} f,
2349                                       {user} u
2350                                 WHERE d.id = ?
2351                                   AND d.firstpost = p.id
2352                                   AND p.userid = u.id
2353                                   AND u.deleted <> '1'
2354                                   AND f.id = d.forum", array($log->info));
2355     }
2356     return NULL;
2359 /**
2360  * Given a discussion id, return the first post from the discussion
2361  *
2362  * @global object
2363  * @global object
2364  * @param int $dicsussionid
2365  * @return array
2366  */
2367 function forum_get_firstpost_from_discussion($discussionid) {
2368     global $CFG, $DB;
2370     return $DB->get_record_sql("SELECT p.*
2371                              FROM {forum_discussions} d,
2372                                   {forum_posts} p
2373                             WHERE d.id = ?
2374                               AND d.firstpost = p.id ", array($discussionid));
2377 /**
2378  * Returns an array of counts of replies to each discussion
2379  *
2380  * @global object
2381  * @global object
2382  * @param int $forumid
2383  * @param string $forumsort
2384  * @param int $limit
2385  * @param int $page
2386  * @param int $perpage
2387  * @return array
2388  */
2389 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2390     global $CFG, $DB;
2392     if ($limit > 0) {
2393         $limitfrom = 0;
2394         $limitnum  = $limit;
2395     } else if ($page != -1) {
2396         $limitfrom = $page*$perpage;
2397         $limitnum  = $perpage;
2398     } else {
2399         $limitfrom = 0;
2400         $limitnum  = 0;
2401     }
2403     if ($forumsort == "") {
2404         $orderby = "";
2405         $groupby = "";
2407     } else {
2408         $orderby = "ORDER BY $forumsort";
2409         $groupby = ", ".strtolower($forumsort);
2410         $groupby = str_replace('desc', '', $groupby);
2411         $groupby = str_replace('asc', '', $groupby);
2412     }
2414     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2415         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2416                   FROM {forum_posts} p
2417                        JOIN {forum_discussions} d ON p.discussion = d.id
2418                  WHERE p.parent > 0 AND d.forum = ?
2419               GROUP BY p.discussion";
2420         return $DB->get_records_sql($sql, array($forumid));
2422     } else {
2423         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2424                   FROM {forum_posts} p
2425                        JOIN {forum_discussions} d ON p.discussion = d.id
2426                  WHERE d.forum = ?
2427               GROUP BY p.discussion $groupby
2428               $orderby";
2429         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2430     }
2433 /**
2434  * @global object
2435  * @global object
2436  * @global object
2437  * @staticvar array $cache
2438  * @param object $forum
2439  * @param object $cm
2440  * @param object $course
2441  * @return mixed
2442  */
2443 function forum_count_discussions($forum, $cm, $course) {
2444     global $CFG, $DB, $USER;
2446     static $cache = array();
2448     $now = round(time(), -2); // db cache friendliness
2450     $params = array($course->id);
2452     if (!isset($cache[$course->id])) {
2453         if (!empty($CFG->forum_enabletimedposts)) {
2454             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2455             $params[] = $now;
2456             $params[] = $now;
2457         } else {
2458             $timedsql = "";
2459         }
2461         $sql = "SELECT f.id, COUNT(d.id) as dcount
2462                   FROM {forum} f
2463                        JOIN {forum_discussions} d ON d.forum = f.id
2464                  WHERE f.course = ?
2465                        $timedsql
2466               GROUP BY f.id";
2468         if ($counts = $DB->get_records_sql($sql, $params)) {
2469             foreach ($counts as $count) {
2470                 $counts[$count->id] = $count->dcount;
2471             }
2472             $cache[$course->id] = $counts;
2473         } else {
2474             $cache[$course->id] = array();
2475         }
2476     }
2478     if (empty($cache[$course->id][$forum->id])) {
2479         return 0;
2480     }
2482     $groupmode = groups_get_activity_groupmode($cm, $course);
2484     if ($groupmode != SEPARATEGROUPS) {
2485         return $cache[$course->id][$forum->id];
2486     }
2488     if (has_capability('moodle/site:accessallgroups', context_module::instance($cm->id))) {
2489         return $cache[$course->id][$forum->id];
2490     }
2492     require_once($CFG->dirroot.'/course/lib.php');
2494     $modinfo = get_fast_modinfo($course);
2496     $mygroups = $modinfo->get_groups($cm->groupingid);
2498     // add all groups posts
2499     $mygroups[-1] = -1;
2501     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2502     $params[] = $forum->id;
2504     if (!empty($CFG->forum_enabletimedposts)) {
2505         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2506         $params[] = $now;
2507         $params[] = $now;
2508     } else {
2509         $timedsql = "";
2510     }
2512     $sql = "SELECT COUNT(d.id)
2513               FROM {forum_discussions} d
2514              WHERE d.groupid $mygroups_sql AND d.forum = ?
2515                    $timedsql";
2517     return $DB->get_field_sql($sql, $params);
2520 /**
2521  * How many posts by other users are unrated by a given user in the given discussion?
2522  *
2523  * TODO: Is this function still used anywhere?
2524  *
2525  * @param int $discussionid
2526  * @param int $userid
2527  * @return mixed
2528  */
2529 function forum_count_unrated_posts($discussionid, $userid) {
2530     global $CFG, $DB;
2532     $sql = "SELECT COUNT(*) as num
2533               FROM {forum_posts}
2534              WHERE parent > 0
2535                AND discussion = :discussionid
2536                AND userid <> :userid";
2537     $params = array('discussionid' => $discussionid, 'userid' => $userid);
2538     $posts = $DB->get_record_sql($sql, $params);
2539     if ($posts) {
2540         $sql = "SELECT count(*) as num
2541                   FROM {forum_posts} p,
2542                        {rating} r
2543                  WHERE p.discussion = :discussionid AND
2544                        p.id = r.itemid AND
2545                        r.userid = userid AND
2546                        r.component = 'mod_forum' AND
2547                        r.ratingarea = 'post'";
2548         $rated = $DB->get_record_sql($sql, $params);
2549         if ($rated) {
2550             if ($posts->num > $rated->num) {
2551                 return $posts->num - $rated->num;
2552             } else {
2553                 return 0;    // Just in case there was a counting error
2554             }
2555         } else {
2556             return $posts->num;
2557         }
2558     } else {
2559         return 0;
2560     }
2563 /**
2564  * Get all discussions in a forum
2565  *
2566  * @global object
2567  * @global object
2568  * @global object
2569  * @uses CONTEXT_MODULE
2570  * @uses VISIBLEGROUPS
2571  * @param object $cm
2572  * @param string $forumsort
2573  * @param bool $fullpost
2574  * @param int $unused
2575  * @param int $limit
2576  * @param bool $userlastmodified
2577  * @param int $page
2578  * @param int $perpage
2579  * @return array
2580  */
2581 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2582     global $CFG, $DB, $USER;
2584     $timelimit = '';
2586     $now = round(time(), -2);
2587     $params = array($cm->instance);
2589     $modcontext = context_module::instance($cm->id);
2591     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2592         return array();
2593     }
2595     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2597         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2598             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2599             $params[] = $now;
2600             $params[] = $now;
2601             if (isloggedin()) {
2602                 $timelimit .= " OR d.userid = ?";
2603                 $params[] = $USER->id;
2604             }
2605             $timelimit .= ")";
2606         }
2607     }
2609     if ($limit > 0) {
2610         $limitfrom = 0;
2611         $limitnum  = $limit;
2612     } else if ($page != -1) {
2613         $limitfrom = $page*$perpage;
2614         $limitnum  = $perpage;
2615     } else {
2616         $limitfrom = 0;
2617         $limitnum  = 0;
2618     }
2620     $groupmode    = groups_get_activity_groupmode($cm);
2621     $currentgroup = groups_get_activity_group($cm);
2623     if ($groupmode) {
2624         if (empty($modcontext)) {
2625             $modcontext = context_module::instance($cm->id);
2626         }
2628         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2629             if ($currentgroup) {
2630                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2631                 $params[] = $currentgroup;
2632             } else {
2633                 $groupselect = "";
2634             }
2636         } else {
2637             //seprate groups without access all
2638             if ($currentgroup) {
2639                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2640                 $params[] = $currentgroup;
2641             } else {
2642                 $groupselect = "AND d.groupid = -1";
2643             }
2644         }
2645     } else {
2646         $groupselect = "";
2647     }
2650     if (empty($forumsort)) {
2651         $forumsort = "d.timemodified DESC";
2652     }
2653     if (empty($fullpost)) {
2654         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2655     } else {
2656         $postdata = "p.*";
2657     }
2659     if (empty($userlastmodified)) {  // We don't need to know this
2660         $umfields = "";
2661         $umtable  = "";
2662     } else {
2663         $umfields = '';
2664         $umnames = get_all_user_name_fields();
2665         foreach ($umnames as $umname) {
2666             $umfields .= ', um.' . $umname . ' AS um' . $umname;
2667         }
2668         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2669     }
2671     $allnames = get_all_user_name_fields(true, 'u');
2672     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend, $allnames,
2673                    u.email, u.picture, u.imagealt $umfields
2674               FROM {forum_discussions} d
2675                    JOIN {forum_posts} p ON p.discussion = d.id
2676                    JOIN {user} u ON p.userid = u.id
2677                    $umtable
2678              WHERE d.forum = ? AND p.parent = 0
2679                    $timelimit $groupselect
2680           ORDER BY $forumsort";
2681     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2684 /**
2685  *
2686  * @global object
2687  * @global object
2688  * @global object
2689  * @uses CONTEXT_MODULE
2690  * @uses VISIBLEGROUPS
2691  * @param object $cm
2692  * @return array
2693  */
2694 function forum_get_discussions_unread($cm) {
2695     global $CFG, $DB, $USER;
2697     $now = round(time(), -2);
2698     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2700     $params = array();
2701     $groupmode    = groups_get_activity_groupmode($cm);
2702     $currentgroup = groups_get_activity_group($cm);
2704     if ($groupmode) {
2705         $modcontext = context_module::instance($cm->id);
2707         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2708             if ($currentgroup) {
2709                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2710                 $params['currentgroup'] = $currentgroup;
2711             } else {
2712                 $groupselect = "";
2713             }
2715         } else {
2716             //separate groups without access all
2717             if ($currentgroup) {
2718                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2719                 $params['currentgroup'] = $currentgroup;
2720             } else {
2721                 $groupselect = "AND d.groupid = -1";
2722             }
2723         }
2724     } else {
2725         $groupselect = "";
2726     }
2728     if (!empty($CFG->forum_enabletimedposts)) {
2729         $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2730         $params['now1'] = $now;
2731         $params['now2'] = $now;
2732     } else {
2733         $timedsql = "";
2734     }
2736     $sql = "SELECT d.id, COUNT(p.id) AS unread
2737               FROM {forum_discussions} d
2738                    JOIN {forum_posts} p     ON p.discussion = d.id
2739                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2740              WHERE d.forum = {$cm->instance}
2741                    AND p.modified >= :cutoffdate AND r.id is NULL
2742                    $groupselect
2743                    $timedsql
2744           GROUP BY d.id";
2745     $params['cutoffdate'] = $cutoffdate;
2747     if ($unreads = $DB->get_records_sql($sql, $params)) {
2748         foreach ($unreads as $unread) {
2749             $unreads[$unread->id] = $unread->unread;
2750         }
2751         return $unreads;
2752     } else {
2753         return array();
2754     }
2757 /**
2758  * @global object
2759  * @global object
2760  * @global object
2761  * @uses CONEXT_MODULE
2762  * @uses VISIBLEGROUPS
2763  * @param object $cm
2764  * @return array
2765  */
2766 function forum_get_discussions_count($cm) {
2767     global $CFG, $DB, $USER;
2769     $now = round(time(), -2);
2770     $params = array($cm->instance);
2771     $groupmode    = groups_get_activity_groupmode($cm);
2772     $currentgroup = groups_get_activity_group($cm);
2774     if ($groupmode) {
2775         $modcontext = context_module::instance($cm->id);
2777         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2778             if ($currentgroup) {
2779                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2780                 $params[] = $currentgroup;
2781             } else {
2782                 $groupselect = "";
2783             }
2785         } else {
2786             //seprate groups without access all
2787             if ($currentgroup) {
2788                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2789                 $params[] = $currentgroup;
2790             } else {
2791                 $groupselect = "AND d.groupid = -1";
2792             }
2793         }
2794     } else {
2795         $groupselect = "";
2796     }
2798     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2800     $timelimit = "";
2802     if (!empty($CFG->forum_enabletimedposts)) {
2804         $modcontext = context_module::instance($cm->id);
2806         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2807             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2808             $params[] = $now;
2809             $params[] = $now;
2810             if (isloggedin()) {
2811                 $timelimit .= " OR d.userid = ?";
2812                 $params[] = $USER->id;
2813             }
2814             $timelimit .= ")";
2815         }
2816     }
2818     $sql = "SELECT COUNT(d.id)
2819               FROM {forum_discussions} d
2820                    JOIN {forum_posts} p ON p.discussion = d.id
2821              WHERE d.forum = ? AND p.parent = 0
2822                    $groupselect $timelimit";
2824     return $DB->get_field_sql($sql, $params);
2828 /**
2829  * Get all discussions started by a particular user in a course (or group)
2830  * This function no longer used ...
2831  *
2832  * @todo Remove this function if no longer used
2833  * @global object
2834  * @global object
2835  * @param int $courseid
2836  * @param int $userid
2837  * @param int $groupid
2838  * @return array
2839  */
2840 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2841     global $CFG, $DB;
2842     $params = array($courseid, $userid);
2843     if ($groupid) {
2844         $groupselect = " AND d.groupid = ? ";
2845         $params[] = $groupid;
2846     } else  {
2847         $groupselect = "";
2848     }
2850     $allnames = get_all_user_name_fields(true, 'u');
2851     return $DB->get_records_sql("SELECT p.*, d.groupid, $allnames, 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     $allnames = get_all_user_name_fields(true, 'u');
2909     if (empty($fields)) {
2910         $fields ="u.id,
2911                   u.username,
2912                   $allnames,
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     foreach (get_all_user_name_fields() as $addname) {
3291         $postuser->$addname  = $post->$addname;
3292     }
3293     $postuser->imagealt  = $post->imagealt;
3294     $postuser->picture   = $post->picture;
3295     $postuser->email     = $post->email;
3296     // Some handy things for later on
3297     $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3298     $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3300     // Prepare the groups the posting user belongs to
3301     if (isset($cm->cache->usersgroups)) {
3302         $groups = array();
3303         if (isset($cm->cache->usersgroups[$post->userid])) {
3304             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3305                 $groups[$gid] = $cm->cache->groups[$gid];
3306             }
3307         }
3308     } else {
3309         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3310     }
3312     // Prepare the attachements for the post, files then images
3313     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3315     // Determine if we need to shorten this post
3316     $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3319     // Prepare an array of commands
3320     $commands = array();
3322     // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3323     // Don't display the mark read / unread controls in this case.
3324     if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3325         $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3326         $text = $str->markunread;
3327         if (!$postisread) {
3328             $url->param('mark', 'read');
3329             $text = $str->markread;
3330         }
3331         if ($str->displaymode == FORUM_MODE_THREADED) {
3332             $url->param('parent', $post->parent);
3333         } else {
3334             $url->set_anchor('p'.$post->id);
3335         }
3336         $commands[] = array('url'=>$url, 'text'=>$text);
3337     }
3339     // Zoom in to the parent specifically
3340     if ($post->parent) {
3341         $url = new moodle_url($discussionlink);
3342         if ($str->displaymode == FORUM_MODE_THREADED) {
3343             $url->param('parent', $post->parent);
3344         } else {
3345             $url->set_anchor('p'.$post->parent);
3346         }
3347         $commands[] = array('url'=>$url, 'text'=>$str->parent);
3348     }
3350     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3351     $age = time() - $post->created;
3352     if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3353         $age = 0;
3354     }
3356     if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
3357         if (has_capability('moodle/course:manageactivities', $modcontext)) {
3358             // The first post in single simple is the forum description.
3359             $commands[] = array('url'=>new moodle_url('/course/modedit.php', array('update'=>$cm->id, 'sesskey'=>sesskey(), 'return'=>1)), 'text'=>$str->edit);
3360         }
3361     } else if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3362         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3363     }
3365     if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3366         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3367     }
3369     if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
3370         // Do not allow deleting of first post in single simple type.
3371     } else if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3372         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3373     }
3375     if ($reply) {
3376         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php#mformforum', array('reply'=>$post->id)), 'text'=>$str->reply);
3377     }
3379     if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
3380         $p = array('postid' => $post->id);
3381         require_once($CFG->libdir.'/portfoliolib.php');
3382         $button = new portfolio_add_button();
3383         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), 'mod_forum');
3384         if (empty($attachments)) {
3385             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3386         } else {
3387             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3388         }
3390         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3391         if (!empty($porfoliohtml)) {
3392             $commands[] = $porfoliohtml;
3393         }
3394     }
3395     // Finished building commands
3398     // Begin output
3400     $output  = '';
3402     if ($istracked) {
3403         if ($postisread) {
3404             $forumpostclass = ' read';
3405         } else {
3406             $forumpostclass = ' unread';
3407             $output .= html_writer::tag('a', '', array('name'=>'unread'));
3408         }
3409     } else {
3410         // ignore trackign status if not tracked or tracked param missing
3411         $forumpostclass = '';
3412     }
3414     $topicclass = '';
3415     if (empty($post->parent)) {
3416         $topicclass = ' firstpost starter';
3417     }
3419     $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3420     $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass));
3421     $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3422     $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3423     $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3424     $output .= html_writer::end_tag('div');
3427     $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3429     $postsubject = $post->subject;
3430     if (empty($post->subjectnoformat)) {
3431         $postsubject = format_string($postsubject);
3432     }
3433     $output .= html_writer::tag('div', $postsubject, array('class'=>'subject'));
3435     $by = new stdClass();
3436     $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3437     $by->date = userdate($post->modified);
3438     $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author'));
3440     $output .= html_writer::end_tag('div'); //topic
3441     $output .= html_writer::end_tag('div'); //row
3443     $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3444     $output .= html_writer::start_tag('div', array('class'=>'left'));
3446     $groupoutput = '';
3447     if ($groups) {
3448         $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3449     }
3450     if (empty($groupoutput)) {
3451         $groupoutput = '&nbsp;';
3452     }
3453     $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3455     $output .= html_writer::end_tag('div'); //left side
3456     $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3457     $output .= html_writer::start_tag('div', array('class'=>'content'));
3458     if (!empty($attachments)) {
3459         $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3460     }
3462     $options = new stdClass;
3463     $options->para    = false;
3464     $options->trusted = $post->messagetrust;
3465     $options->context = $modcontext;
3466     if ($shortenpost) {
3467         // Prepare shortened version
3468         $postclass    = 'shortenedpost';
3469         $postcontent  = format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3470         $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
3471         $postcontent .= html_writer::tag('div', '('.get_string('numwords', 'moodle', count_words($post->message)).')',
3472             array('class'=>'post-word-count'));
3473     } else {
3474         // Prepare whole post
3475         $postclass    = 'fullpost';
3476         $postcontent  = format_text($post->message, $post->messageformat, $options, $course->id);
3477         if (!empty($highlight)) {
3478             $postcontent = highlight($highlight, $postcontent);
3479         }
3480         if (!empty($forum->displaywordcount)) {
3481             $postcontent .= html_writer::tag('div', get_string('numwords', 'moodle', count_words($post->message)),
3482                 array('class'=>'post-word-count'));
3483         }
3484         $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
3485     }
3487     // Output the post content
3488     $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
3489     $output .= html_writer::end_tag('div'); // Content
3490     $output .= html_writer::end_tag('div'); // Content mask
3491     $output .= html_writer::end_tag('div'); // Row
3493     $output .= html_writer::start_tag('div', array('class'=>'row side'));
3494     $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));