Merge branch 'wip-mdl-23335-master' of https://github.com/deraadt/moodle
[moodle.git] / mod / forum / lib.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * @package    mod
19  * @subpackage forum
20  * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
21  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22  */
24 defined('MOODLE_INTERNAL') || die();
26 /** Include required files */
27 require_once($CFG->libdir.'/filelib.php');
28 require_once($CFG->libdir.'/eventslib.php');
29 require_once($CFG->dirroot.'/user/selector/lib.php');
31 /// CONSTANTS ///////////////////////////////////////////////////////////
33 define('FORUM_MODE_FLATOLDEST', 1);
34 define('FORUM_MODE_FLATNEWEST', -1);
35 define('FORUM_MODE_THREADED', 2);
36 define('FORUM_MODE_NESTED', 3);
38 define('FORUM_CHOOSESUBSCRIBE', 0);
39 define('FORUM_FORCESUBSCRIBE', 1);
40 define('FORUM_INITIALSUBSCRIBE', 2);
41 define('FORUM_DISALLOWSUBSCRIBE',3);
43 define('FORUM_TRACKING_OFF', 0);
44 define('FORUM_TRACKING_OPTIONAL', 1);
45 define('FORUM_TRACKING_ON', 2);
47 define('FORUM_MAILED_PENDING', 0);
48 define('FORUM_MAILED_SUCCESS', 1);
49 define('FORUM_MAILED_ERROR', 2);
51 if (!defined('FORUM_CRON_USER_CACHE')) {
52     /** Defines how many full user records are cached in forum cron. */
53     define('FORUM_CRON_USER_CACHE', 5000);
54 }
56 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
58 /**
59  * Given an object containing all the necessary data,
60  * (defined by the form in mod_form.php) this function
61  * will create a new instance and return the id number
62  * of the new instance.
63  *
64  * @param stdClass $forum add forum instance
65  * @param mod_forum_mod_form $mform
66  * @return int intance id
67  */
68 function forum_add_instance($forum, $mform = null) {
69     global $CFG, $DB;
71     $forum->timemodified = time();
73     if (empty($forum->assessed)) {
74         $forum->assessed = 0;
75     }
77     if (empty($forum->ratingtime) or empty($forum->assessed)) {
78         $forum->assesstimestart  = 0;
79         $forum->assesstimefinish = 0;
80     }
82     $forum->id = $DB->insert_record('forum', $forum);
83     $modcontext = context_module::instance($forum->coursemodule);
85     if ($forum->type == 'single') {  // Create related discussion.
86         $discussion = new stdClass();
87         $discussion->course        = $forum->course;
88         $discussion->forum         = $forum->id;
89         $discussion->name          = $forum->name;
90         $discussion->assessed      = $forum->assessed;
91         $discussion->message       = $forum->intro;
92         $discussion->messageformat = $forum->introformat;
93         $discussion->messagetrust  = trusttext_trusted(context_course::instance($forum->course));
94         $discussion->mailnow       = false;
95         $discussion->groupid       = -1;
97         $message = '';
99         $discussion->id = forum_add_discussion($discussion, null, $message);
101         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
102             // Ugly hack - we need to copy the files somehow.
103             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
104             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
106             $options = array('subdirs'=>true); // Use the same options as intro field!
107             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
108             $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
109         }
110     }
112     if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
113         $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email');
114         foreach ($users as $user) {
115             forum_subscribe($user->id, $forum->id);
116         }
117     }
119     forum_grade_item_update($forum);
121     return $forum->id;
125 /**
126  * Given an object containing all the necessary data,
127  * (defined by the form in mod_form.php) this function
128  * will update an existing instance with new data.
129  *
130  * @global object
131  * @param object $forum forum instance (with magic quotes)
132  * @return bool success
133  */
134 function forum_update_instance($forum, $mform) {
135     global $DB, $OUTPUT, $USER;
137     $forum->timemodified = time();
138     $forum->id           = $forum->instance;
140     if (empty($forum->assessed)) {
141         $forum->assessed = 0;
142     }
144     if (empty($forum->ratingtime) or empty($forum->assessed)) {
145         $forum->assesstimestart  = 0;
146         $forum->assesstimefinish = 0;
147     }
149     $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
151     // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
152     // if  scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
153     // 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
154     if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
155         forum_update_grades($forum); // recalculate grades for the forum
156     }
158     if ($forum->type == 'single') {  // Update related discussion and post.
159         $discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC');
160         if (!empty($discussions)) {
161             if (count($discussions) > 1) {
162                 echo $OUTPUT->notification(get_string('warnformorepost', 'forum'));
163             }
164             $discussion = array_pop($discussions);
165         } else {
166             // try to recover by creating initial discussion - MDL-16262
167             $discussion = new stdClass();
168             $discussion->course          = $forum->course;
169             $discussion->forum           = $forum->id;
170             $discussion->name            = $forum->name;
171             $discussion->assessed        = $forum->assessed;
172             $discussion->message         = $forum->intro;
173             $discussion->messageformat   = $forum->introformat;
174             $discussion->messagetrust    = true;
175             $discussion->mailnow         = false;
176             $discussion->groupid         = -1;
178             $message = '';
180             forum_add_discussion($discussion, null, $message);
182             if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
183                 print_error('cannotadd', 'forum');
184             }
185         }
186         if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
187             print_error('cannotfindfirstpost', 'forum');
188         }
190         $cm         = get_coursemodule_from_instance('forum', $forum->id);
191         $modcontext = context_module::instance($cm->id, MUST_EXIST);
193         $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
194         $post->subject       = $forum->name;
195         $post->message       = $forum->intro;
196         $post->messageformat = $forum->introformat;
197         $post->messagetrust  = trusttext_trusted($modcontext);
198         $post->modified      = $forum->timemodified;
199         $post->userid        = $USER->id;    // MDL-18599, so that current teacher can take ownership of activities.
201         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
202             // Ugly hack - we need to copy the files somehow.
203             $options = array('subdirs'=>true); // Use the same options as intro field!
204             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
205         }
207         $DB->update_record('forum_posts', $post);
208         $discussion->name = $forum->name;
209         $DB->update_record('forum_discussions', $discussion);
210     }
212     $DB->update_record('forum', $forum);
214     $modcontext = context_module::instance($forum->coursemodule);
215     if (($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) && ($oldforum->forcesubscribe <> $forum->forcesubscribe)) {
216         $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
217         foreach ($users as $user) {
218             forum_subscribe($user->id, $forum->id);
219         }
220     }
222     forum_grade_item_update($forum);
224     return true;
228 /**
229  * Given an ID of an instance of this module,
230  * this function will permanently delete the instance
231  * and any data that depends on it.
232  *
233  * @global object
234  * @param int $id forum instance id
235  * @return bool success
236  */
237 function forum_delete_instance($id) {
238     global $DB;
240     if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
241         return false;
242     }
243     if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
244         return false;
245     }
246     if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
247         return false;
248     }
250     $context = context_module::instance($cm->id);
252     // now get rid of all files
253     $fs = get_file_storage();
254     $fs->delete_area_files($context->id);
256     $result = true;
258     if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
259         foreach ($discussions as $discussion) {
260             if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
261                 $result = false;
262             }
263         }
264     }
266     if (!$DB->delete_records('forum_digests', array('forum' => $forum->id))) {
267         $result = false;
268     }
270     if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
271         $result = false;
272     }
274     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
276     if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
277         $result = false;
278     }
280     forum_grade_item_delete($forum);
282     return $result;
286 /**
287  * Indicates API features that the forum supports.
288  *
289  * @uses FEATURE_GROUPS
290  * @uses FEATURE_GROUPINGS
291  * @uses FEATURE_GROUPMEMBERSONLY
292  * @uses FEATURE_MOD_INTRO
293  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
294  * @uses FEATURE_COMPLETION_HAS_RULES
295  * @uses FEATURE_GRADE_HAS_GRADE
296  * @uses FEATURE_GRADE_OUTCOMES
297  * @param string $feature
298  * @return mixed True if yes (some features may use other values)
299  */
300 function forum_supports($feature) {
301     switch($feature) {
302         case FEATURE_GROUPS:                  return true;
303         case FEATURE_GROUPINGS:               return true;
304         case FEATURE_GROUPMEMBERSONLY:        return true;
305         case FEATURE_MOD_INTRO:               return true;
306         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
307         case FEATURE_COMPLETION_HAS_RULES:    return true;
308         case FEATURE_GRADE_HAS_GRADE:         return true;
309         case FEATURE_GRADE_OUTCOMES:          return true;
310         case FEATURE_RATE:                    return true;
311         case FEATURE_BACKUP_MOODLE2:          return true;
312         case FEATURE_SHOW_DESCRIPTION:        return true;
313         case FEATURE_PLAGIARISM:              return true;
315         default: return null;
316     }
320 /**
321  * Obtains the automatic completion state for this forum based on any conditions
322  * in forum settings.
323  *
324  * @global object
325  * @global object
326  * @param object $course Course
327  * @param object $cm Course-module
328  * @param int $userid User ID
329  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
330  * @return bool True if completed, false if not. (If no conditions, then return
331  *   value depends on comparison type)
332  */
333 function forum_get_completion_state($course,$cm,$userid,$type) {
334     global $CFG,$DB;
336     // Get forum details
337     if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
338         throw new Exception("Can't find forum {$cm->instance}");
339     }
341     $result=$type; // Default return value
343     $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
344     $postcountsql="
345 SELECT
346     COUNT(1)
347 FROM
348     {forum_posts} fp
349     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
350 WHERE
351     fp.userid=:userid AND fd.forum=:forumid";
353     if ($forum->completiondiscussions) {
354         $value = $forum->completiondiscussions <=
355                  $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
356         if ($type == COMPLETION_AND) {
357             $result = $result && $value;
358         } else {
359             $result = $result || $value;
360         }
361     }
362     if ($forum->completionreplies) {
363         $value = $forum->completionreplies <=
364                  $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
365         if ($type==COMPLETION_AND) {
366             $result = $result && $value;
367         } else {
368             $result = $result || $value;
369         }
370     }
371     if ($forum->completionposts) {
372         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
373         if ($type == COMPLETION_AND) {
374             $result = $result && $value;
375         } else {
376             $result = $result || $value;
377         }
378     }
380     return $result;
383 /**
384  * Create a message-id string to use in the custom headers of forum notification emails
385  *
386  * message-id is used by email clients to identify emails and to nest conversations
387  *
388  * @param int $postid The ID of the forum post we are notifying the user about
389  * @param int $usertoid The ID of the user being notified
390  * @param string $hostname The server's hostname
391  * @return string A unique message-id
392  */
393 function forum_get_email_message_id($postid, $usertoid, $hostname) {
394     return '<'.hash('sha256',$postid.'to'.$usertoid).'@'.$hostname.'>';
397 /**
398  * Removes properties from user record that are not necessary
399  * for sending post notifications.
400  * @param stdClass $user
401  * @return void, $user parameter is modified
402  */
403 function forum_cron_minimise_user_record(stdClass $user) {
405     // We store large amount of users in one huge array,
406     // make sure we do not store info there we do not actually need
407     // in mail generation code or messaging.
409     unset($user->institution);
410     unset($user->department);
411     unset($user->address);
412     unset($user->city);
413     unset($user->url);
414     unset($user->currentlogin);
415     unset($user->description);
416     unset($user->descriptionformat);
419 /**
420  * Function to be run periodically according to the moodle cron
421  * Finds all posts that have yet to be mailed out, and mails them
422  * out to all subscribers
423  *
424  * @global object
425  * @global object
426  * @global object
427  * @uses CONTEXT_MODULE
428  * @uses CONTEXT_COURSE
429  * @uses SITEID
430  * @uses FORMAT_PLAIN
431  * @return void
432  */
433 function forum_cron() {
434     global $CFG, $USER, $DB;
436     $site = get_site();
438     // All users that are subscribed to any post that needs sending,
439     // please increase $CFG->extramemorylimit on large sites that
440     // send notifications to a large number of users.
441     $users = array();
442     $userscount = 0; // Cached user counter - count($users) in PHP is horribly slow!!!
444     // status arrays
445     $mailcount  = array();
446     $errorcount = array();
448     // caches
449     $discussions     = array();
450     $forums          = array();
451     $courses         = array();
452     $coursemodules   = array();
453     $subscribedusers = array();
456     // Posts older than 2 days will not be mailed.  This is to avoid the problem where
457     // cron has not been running for a long time, and then suddenly people are flooded
458     // with mail from the past few weeks or months
459     $timenow   = time();
460     $endtime   = $timenow - $CFG->maxeditingtime;
461     $starttime = $endtime - 48 * 3600;   // Two days earlier
463     // Get the list of forum subscriptions for per-user per-forum maildigest settings.
464     $digestsset = $DB->get_recordset('forum_digests', null, '', 'id, userid, forum, maildigest');
465     $digests = array();
466     foreach ($digestsset as $thisrow) {
467         if (!isset($digests[$thisrow->forum])) {
468             $digests[$thisrow->forum] = array();
469         }
470         $digests[$thisrow->forum][$thisrow->userid] = $thisrow->maildigest;
471     }
472     $digestsset->close();
474     if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
475         // Mark them all now as being mailed.  It's unlikely but possible there
476         // might be an error later so that a post is NOT actually mailed out,
477         // but since mail isn't crucial, we can accept this risk.  Doing it now
478         // prevents the risk of duplicated mails, which is a worse problem.
480         if (!forum_mark_old_posts_as_mailed($endtime)) {
481             mtrace('Errors occurred while trying to mark some posts as being mailed.');
482             return false;  // Don't continue trying to mail them, in case we are in a cron loop
483         }
485         // checking post validity, and adding users to loop through later
486         foreach ($posts as $pid => $post) {
488             $discussionid = $post->discussion;
489             if (!isset($discussions[$discussionid])) {
490                 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
491                     $discussions[$discussionid] = $discussion;
492                 } else {
493                     mtrace('Could not find discussion '.$discussionid);
494                     unset($posts[$pid]);
495                     continue;
496                 }
497             }
498             $forumid = $discussions[$discussionid]->forum;
499             if (!isset($forums[$forumid])) {
500                 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
501                     $forums[$forumid] = $forum;
502                 } else {
503                     mtrace('Could not find forum '.$forumid);
504                     unset($posts[$pid]);
505                     continue;
506                 }
507             }
508             $courseid = $forums[$forumid]->course;
509             if (!isset($courses[$courseid])) {
510                 if ($course = $DB->get_record('course', array('id' => $courseid))) {
511                     $courses[$courseid] = $course;
512                 } else {
513                     mtrace('Could not find course '.$courseid);
514                     unset($posts[$pid]);
515                     continue;
516                 }
517             }
518             if (!isset($coursemodules[$forumid])) {
519                 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
520                     $coursemodules[$forumid] = $cm;
521                 } else {
522                     mtrace('Could not find course module for forum '.$forumid);
523                     unset($posts[$pid]);
524                     continue;
525                 }
526             }
529             // caching subscribed users of each forum
530             if (!isset($subscribedusers[$forumid])) {
531                 $modcontext = context_module::instance($coursemodules[$forumid]->id);
532                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext, "u.*")) {
533                     foreach ($subusers as $postuser) {
534                         // this user is subscribed to this forum
535                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
536                         $userscount++;
537                         if ($userscount > FORUM_CRON_USER_CACHE) {
538                             // Store minimal user info.
539                             $minuser = new stdClass();
540                             $minuser->id = $postuser->id;
541                             $users[$postuser->id] = $minuser;
542                         } else {
543                             // Cache full user record.
544                             forum_cron_minimise_user_record($postuser);
545                             $users[$postuser->id] = $postuser;
546                         }
547                     }
548                     // Release memory.
549                     unset($subusers);
550                     unset($postuser);
551                 }
552             }
554             $mailcount[$pid] = 0;
555             $errorcount[$pid] = 0;
556         }
557     }
559     if ($users && $posts) {
561         $urlinfo = parse_url($CFG->wwwroot);
562         $hostname = $urlinfo['host'];
564         foreach ($users as $userto) {
566             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
568             mtrace('Processing user '.$userto->id);
570             // Init user caches - we keep the cache for one cycle only,
571             // otherwise it could consume too much memory.
572             if (isset($userto->username)) {
573                 $userto = clone($userto);
574             } else {
575                 $userto = $DB->get_record('user', array('id' => $userto->id));
576                 forum_cron_minimise_user_record($userto);
577             }
578             $userto->viewfullnames = array();
579             $userto->canpost       = array();
580             $userto->markposts     = array();
582             // set this so that the capabilities are cached, and environment matches receiving user
583             cron_setup_user($userto);
585             // reset the caches
586             foreach ($coursemodules as $forumid=>$unused) {
587                 $coursemodules[$forumid]->cache       = new stdClass();
588                 $coursemodules[$forumid]->cache->caps = array();
589                 unset($coursemodules[$forumid]->uservisible);
590             }
592             foreach ($posts as $pid => $post) {
594                 // Set up the environment for the post, discussion, forum, course
595                 $discussion = $discussions[$post->discussion];
596                 $forum      = $forums[$discussion->forum];
597                 $course     = $courses[$forum->course];
598                 $cm         =& $coursemodules[$forum->id];
600                 // Do some checks  to see if we can bail out now
601                 // Only active enrolled users are in the list of subscribers
602                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
603                     continue; // user does not subscribe to this forum
604                 }
606                 // Don't send email if the forum is Q&A and the user has not posted
607                 // Initial topics are still mailed
608                 if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id) && $pid != $discussion->firstpost) {
609                     mtrace('Did not email '.$userto->id.' because user has not posted in discussion');
610                     continue;
611                 }
613                 // Get info about the sending user
614                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
615                     $userfrom = $users[$post->userid];
616                     if (!isset($userfrom->idnumber)) {
617                         // Minimalised user info, fetch full record.
618                         $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
619                         forum_cron_minimise_user_record($userfrom);
620                     }
622                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
623                     forum_cron_minimise_user_record($userfrom);
624                     // Fetch only once if possible, we can add it to user list, it will be skipped anyway.
625                     if ($userscount <= FORUM_CRON_USER_CACHE) {
626                         $userscount++;
627                         $users[$userfrom->id] = $userfrom;
628                     }
630                 } else {
631                     mtrace('Could not find user '.$post->userid);
632                     continue;
633                 }
635                 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
637                 // setup global $COURSE properly - needed for roles and languages
638                 cron_setup_user($userto, $course);
640                 // Fill caches
641                 if (!isset($userto->viewfullnames[$forum->id])) {
642                     $modcontext = context_module::instance($cm->id);
643                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
644                 }
645                 if (!isset($userto->canpost[$discussion->id])) {
646                     $modcontext = context_module::instance($cm->id);
647                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
648                 }
649                 if (!isset($userfrom->groups[$forum->id])) {
650                     if (!isset($userfrom->groups)) {
651                         $userfrom->groups = array();
652                         if (isset($users[$userfrom->id])) {
653                             $users[$userfrom->id]->groups = array();
654                         }
655                     }
656                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
657                     if (isset($users[$userfrom->id])) {
658                         $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
659                     }
660                 }
662                 // Make sure groups allow this user to see this email
663                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
664                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
665                         continue;                           // Be safe and don't send it to anyone
666                     }
668                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
669                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
670                         continue;
671                     }
672                 }
674                 // Make sure we're allowed to see it...
675                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
676                     mtrace('user '.$userto->id. ' can not see '.$post->id);
677                     continue;
678                 }
680                 // OK so we need to send the email.
682                 // Does the user want this post in a digest?  If so postpone it for now.
683                 $maildigest = forum_get_user_maildigest_bulk($digests, $userto, $forum->id);
685                 if ($maildigest > 0) {
686                     // This user wants the mails to be in digest form
687                     $queue = new stdClass();
688                     $queue->userid       = $userto->id;
689                     $queue->discussionid = $discussion->id;
690                     $queue->postid       = $post->id;
691                     $queue->timemodified = $post->created;
692                     $DB->insert_record('forum_queue', $queue);
693                     continue;
694                 }
697                 // Prepare to actually send the post now, and build up the content
699                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
701                 $userfrom->customheaders = array (  // Headers to make emails easier to track
702                            'Precedence: Bulk',
703                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
704                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
705                            'Message-ID: '.forum_get_email_message_id($post->id, $userto->id, $hostname),
706                            'X-Course-Id: '.$course->id,
707                            'X-Course-Name: '.format_string($course->fullname, true)
708                 );
710                 if ($post->parent) {  // This post is a reply, so add headers for threading (see MDL-22551)
711                     $userfrom->customheaders[] = 'In-Reply-To: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
712                     $userfrom->customheaders[] = 'References: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
713                 }
715                 $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
717                 $postsubject = html_to_text("$shortname: ".format_string($post->subject, true));
718                 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
719                 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
721                 // Send the post now!
723                 mtrace('Sending ', '');
725                 $eventdata = new stdClass();
726                 $eventdata->component        = 'mod_forum';
727                 $eventdata->name             = 'posts';
728                 $eventdata->userfrom         = $userfrom;
729                 $eventdata->userto           = $userto;
730                 $eventdata->subject          = $postsubject;
731                 $eventdata->fullmessage      = $posttext;
732                 $eventdata->fullmessageformat = FORMAT_PLAIN;
733                 $eventdata->fullmessagehtml  = $posthtml;
734                 $eventdata->notification = 1;
736                 // If forum_replytouser is not set then send mail using the noreplyaddress.
737                 if (empty($CFG->forum_replytouser)) {
738                     // Clone userfrom as it is referenced by $users.
739                     $cloneduserfrom = clone($userfrom);
740                     $cloneduserfrom->email = $CFG->noreplyaddress;
741                     $eventdata->userfrom = $cloneduserfrom;
742                 }
744                 $smallmessagestrings = new stdClass();
745                 $smallmessagestrings->user = fullname($userfrom);
746                 $smallmessagestrings->forumname = "$shortname: ".format_string($forum->name,true).": ".$discussion->name;
747                 $smallmessagestrings->message = $post->message;
748                 //make sure strings are in message recipients language
749                 $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
751                 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
752                 $eventdata->contexturlname = $discussion->name;
754                 $mailresult = message_send($eventdata);
755                 if (!$mailresult){
756                     mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
757                          " ($userto->email) .. not trying again.");
758                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
759                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
760                     $errorcount[$post->id]++;
761                 } else {
762                     $mailcount[$post->id]++;
764                 // Mark post as read if forum_usermarksread is set off
765                     if (!$CFG->forum_usermarksread) {
766                         $userto->markposts[$post->id] = $post->id;
767                     }
768                 }
770                 mtrace('post '.$post->id. ': '.$post->subject);
771             }
773             // mark processed posts as read
774             forum_tp_mark_posts_read($userto, $userto->markposts);
775             unset($userto);
776         }
777     }
779     if ($posts) {
780         foreach ($posts as $post) {
781             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
782             if ($errorcount[$post->id]) {
783                 $DB->set_field('forum_posts', 'mailed', FORUM_MAILED_ERROR, array('id' => $post->id));
784             }
785         }
786     }
788     // release some memory
789     unset($subscribedusers);
790     unset($mailcount);
791     unset($errorcount);
793     cron_setup_user();
795     $sitetimezone = $CFG->timezone;
797     // Now see if there are any digest mails waiting to be sent, and if we should send them
799     mtrace('Starting digest processing...');
801     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
803     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
804         set_config('digestmailtimelast', 0);
805     }
807     $timenow = time();
808     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
810     // Delete any really old ones (normally there shouldn't be any)
811     $weekago = $timenow - (7 * 24 * 3600);
812     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
813     mtrace ('Cleaned old digest records');
815     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
817         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
819         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
821         if ($digestposts_rs->valid()) {
823             // We have work to do
824             $usermailcount = 0;
826             //caches - reuse the those filled before too
827             $discussionposts = array();
828             $userdiscussions = array();
830             foreach ($digestposts_rs as $digestpost) {
831                 if (!isset($posts[$digestpost->postid])) {
832                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
833                         $posts[$digestpost->postid] = $post;
834                     } else {
835                         continue;
836                     }
837                 }
838                 $discussionid = $digestpost->discussionid;
839                 if (!isset($discussions[$discussionid])) {
840                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
841                         $discussions[$discussionid] = $discussion;
842                     } else {
843                         continue;
844                     }
845                 }
846                 $forumid = $discussions[$discussionid]->forum;
847                 if (!isset($forums[$forumid])) {
848                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
849                         $forums[$forumid] = $forum;
850                     } else {
851                         continue;
852                     }
853                 }
855                 $courseid = $forums[$forumid]->course;
856                 if (!isset($courses[$courseid])) {
857                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
858                         $courses[$courseid] = $course;
859                     } else {
860                         continue;
861                     }
862                 }
864                 if (!isset($coursemodules[$forumid])) {
865                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
866                         $coursemodules[$forumid] = $cm;
867                     } else {
868                         continue;
869                     }
870                 }
871                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
872                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
873             }
874             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
876             // Data collected, start sending out emails to each user
877             foreach ($userdiscussions as $userid => $thesediscussions) {
879                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
881                 cron_setup_user();
883                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
885                 // First of all delete all the queue entries for this user
886                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
888                 // Init user caches - we keep the cache for one cycle only,
889                 // otherwise it would unnecessarily consume memory.
890                 if (array_key_exists($userid, $users) and isset($users[$userid]->username)) {
891                     $userto = clone($users[$userid]);
892                 } else {
893                     $userto = $DB->get_record('user', array('id' => $userid));
894                     forum_cron_minimise_user_record($userto);
895                 }
896                 $userto->viewfullnames = array();
897                 $userto->canpost       = array();
898                 $userto->markposts     = array();
900                 // Override the language and timezone of the "current" user, so that
901                 // mail is customised for the receiver.
902                 cron_setup_user($userto);
904                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
906                 $headerdata = new stdClass();
907                 $headerdata->sitename = format_string($site->fullname, true);
908                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
910                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
911                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
913                 $posthtml = "<head>";
914 /*                foreach ($CFG->stylesheets as $stylesheet) {
915                     //TODO: MDL-21120
916                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
917                 }*/
918                 $posthtml .= "</head>\n<body id=\"email\">\n";
919                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
921                 foreach ($thesediscussions as $discussionid) {
923                     @set_time_limit(120);   // to be reset for each post
925                     $discussion = $discussions[$discussionid];
926                     $forum      = $forums[$discussion->forum];
927                     $course     = $courses[$forum->course];
928                     $cm         = $coursemodules[$forum->id];
930                     //override language
931                     cron_setup_user($userto, $course);
933                     // Fill caches
934                     if (!isset($userto->viewfullnames[$forum->id])) {
935                         $modcontext = context_module::instance($cm->id);
936                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
937                     }
938                     if (!isset($userto->canpost[$discussion->id])) {
939                         $modcontext = context_module::instance($cm->id);
940                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
941                     }
943                     $strforums      = get_string('forums', 'forum');
944                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
945                     $canreply       = $userto->canpost[$discussion->id];
946                     $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
948                     $posttext .= "\n \n";
949                     $posttext .= '=====================================================================';
950                     $posttext .= "\n \n";
951                     $posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
952                     if ($discussion->name != $forum->name) {
953                         $posttext  .= " -> ".format_string($discussion->name,true);
954                     }
955                     $posttext .= "\n";
956                     $posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
957                     $posttext .= "\n";
959                     $posthtml .= "<p><font face=\"sans-serif\">".
960                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
961                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
962                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
963                     if ($discussion->name == $forum->name) {
964                         $posthtml .= "</font></p>";
965                     } else {
966                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
967                     }
968                     $posthtml .= '<p>';
970                     $postsarray = $discussionposts[$discussionid];
971                     sort($postsarray);
973                     foreach ($postsarray as $postid) {
974                         $post = $posts[$postid];
976                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
977                             $userfrom = $users[$post->userid];
978                             if (!isset($userfrom->idnumber)) {
979                                 $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
980                                 forum_cron_minimise_user_record($userfrom);
981                             }
983                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
984                             forum_cron_minimise_user_record($userfrom);
985                             if ($userscount <= FORUM_CRON_USER_CACHE) {
986                                 $userscount++;
987                                 $users[$userfrom->id] = $userfrom;
988                             }
990                         } else {
991                             mtrace('Could not find user '.$post->userid);
992                             continue;
993                         }
995                         if (!isset($userfrom->groups[$forum->id])) {
996                             if (!isset($userfrom->groups)) {
997                                 $userfrom->groups = array();
998                                 if (isset($users[$userfrom->id])) {
999                                     $users[$userfrom->id]->groups = array();
1000                                 }
1001                             }
1002                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
1003                             if (isset($users[$userfrom->id])) {
1004                                 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
1005                             }
1006                         }
1008                         $userfrom->customheaders = array ("Precedence: Bulk");
1010                         $maildigest = forum_get_user_maildigest_bulk($digests, $userto, $forum->id);
1011                         if ($maildigest == 2) {
1012                             // Subjects and link only
1013                             $posttext .= "\n";
1014                             $posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
1015                             $by = new stdClass();
1016                             $by->name = fullname($userfrom);
1017                             $by->date = userdate($post->modified);
1018                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
1019                             $posttext .= "\n---------------------------------------------------------------------";
1021                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
1022                             $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>';
1024                         } else {
1025                             // The full treatment
1026                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
1027                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1029                         // Create an array of postid's for this user to mark as read.
1030                             if (!$CFG->forum_usermarksread) {
1031                                 $userto->markposts[$post->id] = $post->id;
1032                             }
1033                         }
1034                     }
1035                     $footerlinks = array();
1036                     if ($canunsubscribe) {
1037                         $footerlinks[] = "<a href=\"$CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\">" . get_string("unsubscribe", "forum") . "</a>";
1038                     } else {
1039                         $footerlinks[] = get_string("everyoneissubscribed", "forum");
1040                     }
1041                     $footerlinks[] = "<a href='{$CFG->wwwroot}/mod/forum/index.php?id={$forum->course}'>" . get_string("digestmailpost", "forum") . '</a>';
1042                     $posthtml .= "\n<div class='mdl-right'><font size=\"1\">" . implode('&nbsp;', $footerlinks) . '</font></div>';
1043                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
1044                 }
1045                 $posthtml .= '</body>';
1047                 if (empty($userto->mailformat) || $userto->mailformat != 1) {
1048                     // This user DOESN'T want to receive HTML
1049                     $posthtml = '';
1050                 }
1052                 $attachment = $attachname='';
1053                 // Directly email forum digests rather than sending them via messaging, use the
1054                 // site shortname as 'from name', the noreply address will be used by email_to_user.
1055                 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname);
1057                 if (!$mailresult) {
1058                     mtrace("ERROR!");
1059                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
1060                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
1061                 } else {
1062                     mtrace("success.");
1063                     $usermailcount++;
1065                     // Mark post as read if forum_usermarksread is set off
1066                     forum_tp_mark_posts_read($userto, $userto->markposts);
1067                 }
1068             }
1069         }
1070     /// We have finishied all digest emails, update $CFG->digestmailtimelast
1071         set_config('digestmailtimelast', $timenow);
1072     }
1074     cron_setup_user();
1076     if (!empty($usermailcount)) {
1077         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
1078     }
1080     if (!empty($CFG->forum_lastreadclean)) {
1081         $timenow = time();
1082         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
1083             set_config('forum_lastreadclean', $timenow);
1084             mtrace('Removing old forum read tracking info...');
1085             forum_tp_clean_read_records();
1086         }
1087     } else {
1088         set_config('forum_lastreadclean', time());
1089     }
1092     return true;
1095 /**
1096  * Builds and returns the body of the email notification in plain text.
1097  *
1098  * @global object
1099  * @global object
1100  * @uses CONTEXT_MODULE
1101  * @param object $course
1102  * @param object $cm
1103  * @param object $forum
1104  * @param object $discussion
1105  * @param object $post
1106  * @param object $userfrom
1107  * @param object $userto
1108  * @param boolean $bare
1109  * @return string The email body in plain text format.
1110  */
1111 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
1112     global $CFG, $USER;
1114     $modcontext = context_module::instance($cm->id);
1116     if (!isset($userto->viewfullnames[$forum->id])) {
1117         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
1118     } else {
1119         $viewfullnames = $userto->viewfullnames[$forum->id];
1120     }
1122     if (!isset($userto->canpost[$discussion->id])) {
1123         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
1124     } else {
1125         $canreply = $userto->canpost[$discussion->id];
1126     }
1128     $by = New stdClass;
1129     $by->name = fullname($userfrom, $viewfullnames);
1130     $by->date = userdate($post->modified, "", $userto->timezone);
1132     $strbynameondate = get_string('bynameondate', 'forum', $by);
1134     $strforums = get_string('forums', 'forum');
1136     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1138     $posttext = '';
1140     if (!$bare) {
1141         $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
1142         $posttext  = "$shortname -> $strforums -> ".format_string($forum->name,true);
1144         if ($discussion->name != $forum->name) {
1145             $posttext  .= " -> ".format_string($discussion->name,true);
1146         }
1147     }
1149     // add absolute file links
1150     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
1152     $posttext .= "\n";
1153     $posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
1154     $posttext .= "\n---------------------------------------------------------------------\n";
1155     $posttext .= format_string($post->subject,true);
1156     if ($bare) {
1157         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1158     }
1159     $posttext .= "\n".$strbynameondate."\n";
1160     $posttext .= "---------------------------------------------------------------------\n";
1161     $posttext .= format_text_email($post->message, $post->messageformat);
1162     $posttext .= "\n\n";
1163     $posttext .= forum_print_attachments($post, $cm, "text");
1165     if (!$bare && $canreply) {
1166         $posttext .= "---------------------------------------------------------------------\n";
1167         $posttext .= get_string("postmailinfo", "forum", $shortname)."\n";
1168         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1169     }
1170     if (!$bare && $canunsubscribe) {
1171         $posttext .= "\n---------------------------------------------------------------------\n";
1172         $posttext .= get_string("unsubscribe", "forum");
1173         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1174     }
1176     $posttext .= "\n---------------------------------------------------------------------\n";
1177     $posttext .= get_string("digestmailpost", "forum");
1178     $posttext .= ": {$CFG->wwwroot}/mod/forum/index.php?id={$forum->course}\n";
1180     return $posttext;
1183 /**
1184  * Builds and returns the body of the email notification in html format.
1185  *
1186  * @global object
1187  * @param object $course
1188  * @param object $cm
1189  * @param object $forum
1190  * @param object $discussion
1191  * @param object $post
1192  * @param object $userfrom
1193  * @param object $userto
1194  * @return string The email text in HTML format
1195  */
1196 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1197     global $CFG;
1199     if ($userto->mailformat != 1) {  // Needs to be HTML
1200         return '';
1201     }
1203     if (!isset($userto->canpost[$discussion->id])) {
1204         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course);
1205     } else {
1206         $canreply = $userto->canpost[$discussion->id];
1207     }
1209     $strforums = get_string('forums', 'forum');
1210     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1211     $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
1213     $posthtml = '<head>';
1214 /*    foreach ($CFG->stylesheets as $stylesheet) {
1215         //TODO: MDL-21120
1216         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1217     }*/
1218     $posthtml .= '</head>';
1219     $posthtml .= "\n<body id=\"email\">\n\n";
1221     $posthtml .= '<div class="navbar">'.
1222     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$shortname.'</a> &raquo; '.
1223     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1224     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1225     if ($discussion->name == $forum->name) {
1226         $posthtml .= '</div>';
1227     } else {
1228         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1229                      format_string($discussion->name,true).'</a></div>';
1230     }
1231     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1233     $footerlinks = array();
1234     if ($canunsubscribe) {
1235         $footerlinks[] = '<a href="' . $CFG->wwwroot . '/mod/forum/subscribe.php?id=' . $forum->id . '">' . get_string('unsubscribe', 'forum') . '</a>';
1236         $footerlinks[] = '<a href="' . $CFG->wwwroot . '/mod/forum/unsubscribeall.php">' . get_string('unsubscribeall', 'forum') . '</a>';
1237     }
1238     $footerlinks[] = "<a href='{$CFG->wwwroot}/mod/forum/index.php?id={$forum->course}'>" . get_string('digestmailpost', 'forum') . '</a>';
1239     $posthtml .= '<hr /><div class="mdl-align unsubscribelink">' . implode('&nbsp;', $footerlinks) . '</div>';
1241     $posthtml .= '</body>';
1243     return $posthtml;
1247 /**
1248  *
1249  * @param object $course
1250  * @param object $user
1251  * @param object $mod TODO this is not used in this function, refactor
1252  * @param object $forum
1253  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1254  */
1255 function forum_user_outline($course, $user, $mod, $forum) {
1256     global $CFG;
1257     require_once("$CFG->libdir/gradelib.php");
1258     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1259     if (empty($grades->items[0]->grades)) {
1260         $grade = false;
1261     } else {
1262         $grade = reset($grades->items[0]->grades);
1263     }
1265     $count = forum_count_user_posts($forum->id, $user->id);
1267     if ($count && $count->postcount > 0) {
1268         $result = new stdClass();
1269         $result->info = get_string("numposts", "forum", $count->postcount);
1270         $result->time = $count->lastpost;
1271         if ($grade) {
1272             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1273         }
1274         return $result;
1275     } else if ($grade) {
1276         $result = new stdClass();
1277         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1279         //datesubmitted == time created. dategraded == time modified or time overridden
1280         //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
1281         //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
1282         if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1283             $result->time = $grade->dategraded;
1284         } else {
1285             $result->time = $grade->datesubmitted;
1286         }
1288         return $result;
1289     }
1290     return NULL;
1294 /**
1295  * @global object
1296  * @global object
1297  * @param object $coure
1298  * @param object $user
1299  * @param object $mod
1300  * @param object $forum
1301  */
1302 function forum_user_complete($course, $user, $mod, $forum) {
1303     global $CFG,$USER, $OUTPUT;
1304     require_once("$CFG->libdir/gradelib.php");
1306     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1307     if (!empty($grades->items[0]->grades)) {
1308         $grade = reset($grades->items[0]->grades);
1309         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1310         if ($grade->str_feedback) {
1311             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1312         }
1313     }
1315     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1317         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1318             print_error('invalidcoursemodule');
1319         }
1320         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1322         foreach ($posts as $post) {
1323             if (!isset($discussions[$post->discussion])) {
1324                 continue;
1325             }
1326             $discussion = $discussions[$post->discussion];
1328             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1329         }
1330     } else {
1331         echo "<p>".get_string("noposts", "forum")."</p>";
1332     }
1340 /**
1341  * @global object
1342  * @global object
1343  * @global object
1344  * @param array $courses
1345  * @param array $htmlarray
1346  */
1347 function forum_print_overview($courses,&$htmlarray) {
1348     global $USER, $CFG, $DB, $SESSION;
1350     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1351         return array();
1352     }
1354     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1355         return;
1356     }
1358     // Courses to search for new posts
1359     $coursessqls = array();
1360     $params = array();
1361     foreach ($courses as $course) {
1363         // If the user has never entered into the course all posts are pending
1364         if ($course->lastaccess == 0) {
1365             $coursessqls[] = '(f.course = ?)';
1366             $params[] = $course->id;
1368         // Only posts created after the course last access
1369         } else {
1370             $coursessqls[] = '(f.course = ? AND p.created > ?)';
1371             $params[] = $course->id;
1372             $params[] = $course->lastaccess;
1373         }
1374     }
1375     $params[] = $USER->id;
1376     $coursessql = implode(' OR ', $coursessqls);
1378     $sql = "SELECT f.id, COUNT(*) as count "
1379                 .'FROM {forum} f '
1380                 .'JOIN {forum_discussions} d ON d.forum  = f.id '
1381                 .'JOIN {forum_posts} p ON p.discussion = d.id '
1382                 ."WHERE ($coursessql) "
1383                 .'AND p.userid != ? '
1384                 .'GROUP BY f.id';
1386     if (!$new = $DB->get_records_sql($sql, $params)) {
1387         $new = array(); // avoid warnings
1388     }
1390     // also get all forum tracking stuff ONCE.
1391     $trackingforums = array();
1392     foreach ($forums as $forum) {
1393         if (forum_tp_can_track_forums($forum)) {
1394             $trackingforums[$forum->id] = $forum;
1395         }
1396     }
1398     if (count($trackingforums) > 0) {
1399         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1400         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1401             ' FROM {forum_posts} p '.
1402             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1403             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1404         $params = array($USER->id);
1406         foreach ($trackingforums as $track) {
1407             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1408             $params[] = $track->id;
1409             if (isset($SESSION->currentgroup[$track->course])) {
1410                 $groupid =  $SESSION->currentgroup[$track->course];
1411             } else {
1412                 // get first groupid
1413                 $groupids = groups_get_all_groups($track->course, $USER->id);
1414                 if ($groupids) {
1415                     reset($groupids);
1416                     $groupid = key($groupids);
1417                     $SESSION->currentgroup[$track->course] = $groupid;
1418                 } else {
1419                     $groupid = 0;
1420                 }
1421                 unset($groupids);
1422             }
1423             $params[] = $groupid;
1424         }
1425         $sql = substr($sql,0,-3); // take off the last OR
1426         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1427         $params[] = $cutoffdate;
1429         if (!$unread = $DB->get_records_sql($sql, $params)) {
1430             $unread = array();
1431         }
1432     } else {
1433         $unread = array();
1434     }
1436     if (empty($unread) and empty($new)) {
1437         return;
1438     }
1440     $strforum = get_string('modulename','forum');
1442     foreach ($forums as $forum) {
1443         $str = '';
1444         $count = 0;
1445         $thisunread = 0;
1446         $showunread = false;
1447         // either we have something from logs, or trackposts, or nothing.
1448         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1449             $count = $new[$forum->id]->count;
1450         }
1451         if (array_key_exists($forum->id,$unread)) {
1452             $thisunread = $unread[$forum->id]->count;
1453             $showunread = true;
1454         }
1455         if ($count > 0 || $thisunread > 0) {
1456             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1457                 $forum->name.'</a></div>';
1458             $str .= '<div class="info"><span class="postsincelogin">';
1459             $str .= get_string('overviewnumpostssince', 'forum', $count)."</span>";
1460             if (!empty($showunread)) {
1461                 $str .= '<div class="unreadposts">'.get_string('overviewnumunread', 'forum', $thisunread).'</div>';
1462             }
1463             $str .= '</div></div>';
1464         }
1465         if (!empty($str)) {
1466             if (!array_key_exists($forum->course,$htmlarray)) {
1467                 $htmlarray[$forum->course] = array();
1468             }
1469             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1470                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1471             }
1472             $htmlarray[$forum->course]['forum'] .= $str;
1473         }
1474     }
1477 /**
1478  * Given a course and a date, prints a summary of all the new
1479  * messages posted in the course since that date
1480  *
1481  * @global object
1482  * @global object
1483  * @global object
1484  * @uses CONTEXT_MODULE
1485  * @uses VISIBLEGROUPS
1486  * @param object $course
1487  * @param bool $viewfullnames capability
1488  * @param int $timestart
1489  * @return bool success
1490  */
1491 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1492     global $CFG, $USER, $DB, $OUTPUT;
1494     // do not use log table if possible, it may be huge and is expensive to join with other tables
1496     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1497                                               d.timestart, d.timeend, d.userid AS duserid,
1498                                               u.firstname, u.lastname, u.email, u.picture
1499                                          FROM {forum_posts} p
1500                                               JOIN {forum_discussions} d ON d.id = p.discussion
1501                                               JOIN {forum} f             ON f.id = d.forum
1502                                               JOIN {user} u              ON u.id = p.userid
1503                                         WHERE p.created > ? AND f.course = ?
1504                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1505          return false;
1506     }
1508     $modinfo = get_fast_modinfo($course);
1510     $groupmodes = array();
1511     $cms    = array();
1513     $strftimerecent = get_string('strftimerecent');
1515     $printposts = array();
1516     foreach ($posts as $post) {
1517         if (!isset($modinfo->instances['forum'][$post->forum])) {
1518             // not visible
1519             continue;
1520         }
1521         $cm = $modinfo->instances['forum'][$post->forum];
1522         if (!$cm->uservisible) {
1523             continue;
1524         }
1525         $context = context_module::instance($cm->id);
1527         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1528             continue;
1529         }
1531         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1532           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1533             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1534                 continue;
1535             }
1536         }
1538         $groupmode = groups_get_activity_groupmode($cm, $course);
1540         if ($groupmode) {
1541             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1542                 // oki (Open discussions have groupid -1)
1543             } else {
1544                 // separate mode
1545                 if (isguestuser()) {
1546                     // shortcut
1547                     continue;
1548                 }
1550                 if (!in_array($post->groupid, $modinfo->get_groups($cm->groupingid))) {
1551                     continue;
1552                 }
1553             }
1554         }
1556         $printposts[] = $post;
1557     }
1558     unset($posts);
1560     if (!$printposts) {
1561         return false;
1562     }
1564     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1565     echo "\n<ul class='unlist'>\n";
1567     foreach ($printposts as $post) {
1568         $subjectclass = empty($post->parent) ? ' bold' : '';
1570         echo '<li><div class="head">'.
1571                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1572                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1573              '</div>';
1574         echo '<div class="info'.$subjectclass.'">';
1575         if (empty($post->parent)) {
1576             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1577         } else {
1578             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1579         }
1580         $post->subject = break_up_long_words(format_string($post->subject, true));
1581         echo $post->subject;
1582         echo "</a>\"</div></li>\n";
1583     }
1585     echo "</ul>\n";
1587     return true;
1590 /**
1591  * Return grade for given user or all users.
1592  *
1593  * @global object
1594  * @global object
1595  * @param object $forum
1596  * @param int $userid optional user id, 0 means all users
1597  * @return array array of grades, false if none
1598  */
1599 function forum_get_user_grades($forum, $userid = 0) {
1600     global $CFG;
1602     require_once($CFG->dirroot.'/rating/lib.php');
1604     $ratingoptions = new stdClass;
1605     $ratingoptions->component = 'mod_forum';
1606     $ratingoptions->ratingarea = 'post';
1608     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1609     $ratingoptions->modulename = 'forum';
1610     $ratingoptions->moduleid   = $forum->id;
1611     $ratingoptions->userid = $userid;
1612     $ratingoptions->aggregationmethod = $forum->assessed;
1613     $ratingoptions->scaleid = $forum->scale;
1614     $ratingoptions->itemtable = 'forum_posts';
1615     $ratingoptions->itemtableusercolumn = 'userid';
1617     $rm = new rating_manager();
1618     return $rm->get_user_grades($ratingoptions);
1621 /**
1622  * Update activity grades
1623  *
1624  * @category grade
1625  * @param object $forum
1626  * @param int $userid specific user only, 0 means all
1627  * @param boolean $nullifnone return null if grade does not exist
1628  * @return void
1629  */
1630 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1631     global $CFG, $DB;
1632     require_once($CFG->libdir.'/gradelib.php');
1634     if (!$forum->assessed) {
1635         forum_grade_item_update($forum);
1637     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1638         forum_grade_item_update($forum, $grades);
1640     } else if ($userid and $nullifnone) {
1641         $grade = new stdClass();
1642         $grade->userid   = $userid;
1643         $grade->rawgrade = NULL;
1644         forum_grade_item_update($forum, $grade);
1646     } else {
1647         forum_grade_item_update($forum);
1648     }
1651 /**
1652  * Update all grades in gradebook.
1653  * @global object
1654  */
1655 function forum_upgrade_grades() {
1656     global $DB;
1658     $sql = "SELECT COUNT('x')
1659               FROM {forum} f, {course_modules} cm, {modules} m
1660              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1661     $count = $DB->count_records_sql($sql);
1663     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1664               FROM {forum} f, {course_modules} cm, {modules} m
1665              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1666     $rs = $DB->get_recordset_sql($sql);
1667     if ($rs->valid()) {
1668         $pbar = new progress_bar('forumupgradegrades', 500, true);
1669         $i=0;
1670         foreach ($rs as $forum) {
1671             $i++;
1672             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1673             forum_update_grades($forum, 0, false);
1674             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1675         }
1676     }
1677     $rs->close();
1680 /**
1681  * Create/update grade item for given forum
1682  *
1683  * @category grade
1684  * @uses GRADE_TYPE_NONE
1685  * @uses GRADE_TYPE_VALUE
1686  * @uses GRADE_TYPE_SCALE
1687  * @param stdClass $forum Forum object with extra cmidnumber
1688  * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
1689  * @return int 0 if ok
1690  */
1691 function forum_grade_item_update($forum, $grades=NULL) {
1692     global $CFG;
1693     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1694         require_once($CFG->libdir.'/gradelib.php');
1695     }
1697     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1699     if (!$forum->assessed or $forum->scale == 0) {
1700         $params['gradetype'] = GRADE_TYPE_NONE;
1702     } else if ($forum->scale > 0) {
1703         $params['gradetype'] = GRADE_TYPE_VALUE;
1704         $params['grademax']  = $forum->scale;
1705         $params['grademin']  = 0;
1707     } else if ($forum->scale < 0) {
1708         $params['gradetype'] = GRADE_TYPE_SCALE;
1709         $params['scaleid']   = -$forum->scale;
1710     }
1712     if ($grades  === 'reset') {
1713         $params['reset'] = true;
1714         $grades = NULL;
1715     }
1717     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1720 /**
1721  * Delete grade item for given forum
1722  *
1723  * @category grade
1724  * @param stdClass $forum Forum object
1725  * @return grade_item
1726  */
1727 function forum_grade_item_delete($forum) {
1728     global $CFG;
1729     require_once($CFG->libdir.'/gradelib.php');
1731     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1735 /**
1736  * This function returns if a scale is being used by one forum
1737  *
1738  * @global object
1739  * @param int $forumid
1740  * @param int $scaleid negative number
1741  * @return bool
1742  */
1743 function forum_scale_used ($forumid,$scaleid) {
1744     global $DB;
1745     $return = false;
1747     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1749     if (!empty($rec) && !empty($scaleid)) {
1750         $return = true;
1751     }
1753     return $return;
1756 /**
1757  * Checks if scale is being used by any instance of forum
1758  *
1759  * This is used to find out if scale used anywhere
1760  *
1761  * @global object
1762  * @param $scaleid int
1763  * @return boolean True if the scale is used by any forum
1764  */
1765 function forum_scale_used_anywhere($scaleid) {
1766     global $DB;
1767     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1768         return true;
1769     } else {
1770         return false;
1771     }
1774 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1776 /**
1777  * Gets a post with all info ready for forum_print_post
1778  * Most of these joins are just to get the forum id
1779  *
1780  * @global object
1781  * @global object
1782  * @param int $postid
1783  * @return mixed array of posts or false
1784  */
1785 function forum_get_post_full($postid) {
1786     global $CFG, $DB;
1788     $allnames = get_all_user_name_fields(true, 'u');
1789     return $DB->get_record_sql("SELECT p.*, d.forum, $allnames, u.email, u.picture, u.imagealt
1790                              FROM {forum_posts} p
1791                                   JOIN {forum_discussions} d ON p.discussion = d.id
1792                                   LEFT JOIN {user} u ON p.userid = u.id
1793                             WHERE p.id = ?", array($postid));
1796 /**
1797  * Gets posts with all info ready for forum_print_post
1798  * We pass forumid in because we always know it so no need to make a
1799  * complicated join to find it out.
1800  *
1801  * @global object
1802  * @global object
1803  * @return mixed array of posts or false
1804  */
1805 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1806     global $CFG, $DB;
1808     $allnames = get_all_user_name_fields(true, 'u');
1809     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, $allnames, u.email, u.picture, u.imagealt
1810                               FROM {forum_posts} p
1811                          LEFT JOIN {user} u ON p.userid = u.id
1812                              WHERE p.discussion = ?
1813                                AND p.parent > 0 $sort", array($discussion));
1816 /**
1817  * Gets all posts in discussion including top parent.
1818  *
1819  * @global object
1820  * @global object
1821  * @global object
1822  * @param int $discussionid
1823  * @param string $sort
1824  * @param bool $tracking does user track the forum?
1825  * @return array of posts
1826  */
1827 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1828     global $CFG, $DB, $USER;
1830     $tr_sel  = "";
1831     $tr_join = "";
1832     $params = array();
1834     if ($tracking) {
1835         $now = time();
1836         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1837         $tr_sel  = ", fr.id AS postread";
1838         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1839         $params[] = $USER->id;
1840     }
1842     $allnames = get_all_user_name_fields(true, 'u');
1843     $params[] = $discussionid;
1844     if (!$posts = $DB->get_records_sql("SELECT p.*, $allnames, u.email, u.picture, u.imagealt $tr_sel
1845                                      FROM {forum_posts} p
1846                                           LEFT JOIN {user} u ON p.userid = u.id
1847                                           $tr_join
1848                                     WHERE p.discussion = ?
1849                                  ORDER BY $sort", $params)) {
1850         return array();
1851     }
1853     foreach ($posts as $pid=>$p) {
1854         if ($tracking) {
1855             if (forum_tp_is_post_old($p)) {
1856                  $posts[$pid]->postread = true;
1857             }
1858         }
1859         if (!$p->parent) {
1860             continue;
1861         }
1862         if (!isset($posts[$p->parent])) {
1863             continue; // parent does not exist??
1864         }
1865         if (!isset($posts[$p->parent]->children)) {
1866             $posts[$p->parent]->children = array();
1867         }
1868         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1869     }
1871     return $posts;
1874 /**
1875  * Gets posts with all info ready for forum_print_post
1876  * We pass forumid in because we always know it so no need to make a
1877  * complicated join to find it out.
1878  *
1879  * @global object
1880  * @global object
1881  * @param int $parent
1882  * @param int $forumid
1883  * @return array
1884  */
1885 function forum_get_child_posts($parent, $forumid) {
1886     global $CFG, $DB;
1888     $allnames = get_all_user_name_fields(true, 'u');
1889     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, $allnames, u.email, u.picture, u.imagealt
1890                               FROM {forum_posts} p
1891                          LEFT JOIN {user} u ON p.userid = u.id
1892                              WHERE p.parent = ?
1893                           ORDER BY p.created ASC", array($parent));
1896 /**
1897  * An array of forum objects that the user is allowed to read/search through.
1898  *
1899  * @global object
1900  * @global object
1901  * @global object
1902  * @param int $userid
1903  * @param int $courseid if 0, we look for forums throughout the whole site.
1904  * @return array of forum objects, or false if no matches
1905  *         Forum objects have the following attributes:
1906  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1907  *         viewhiddentimedposts
1908  */
1909 function forum_get_readable_forums($userid, $courseid=0) {
1911     global $CFG, $DB, $USER;
1912     require_once($CFG->dirroot.'/course/lib.php');
1914     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1915         print_error('notinstalled', 'forum');
1916     }
1918     if ($courseid) {
1919         $courses = $DB->get_records('course', array('id' => $courseid));
1920     } else {
1921         // If no course is specified, then the user can see SITE + his courses.
1922         $courses1 = $DB->get_records('course', array('id' => SITEID));
1923         $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
1924         $courses = array_merge($courses1, $courses2);
1925     }
1926     if (!$courses) {
1927         return array();
1928     }
1930     $readableforums = array();
1932     foreach ($courses as $course) {
1934         $modinfo = get_fast_modinfo($course);
1936         if (empty($modinfo->instances['forum'])) {
1937             // hmm, no forums?
1938             continue;
1939         }
1941         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1943         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1944             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1945                 continue;
1946             }
1947             $context = context_module::instance($cm->id);
1948             $forum = $courseforums[$forumid];
1949             $forum->context = $context;
1950             $forum->cm = $cm;
1952             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1953                 continue;
1954             }
1956          /// group access
1957             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1959                 $forum->onlygroups = $modinfo->get_groups($cm->groupingid);
1960                 $forum->onlygroups[] = -1;
1961             }
1963         /// hidden timed discussions
1964             $forum->viewhiddentimedposts = true;
1965             if (!empty($CFG->forum_enabletimedposts)) {
1966                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1967                     $forum->viewhiddentimedposts = false;
1968                 }
1969             }
1971         /// qanda access
1972             if ($forum->type == 'qanda'
1973                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1975                 // We need to check whether the user has posted in the qanda forum.
1976                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1977                                                     // the user is allowed to see in this forum.
1978                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1979                     foreach ($discussionspostedin as $d) {
1980                         $forum->onlydiscussions[] = $d->id;
1981                     }
1982                 }
1983             }
1985             $readableforums[$forum->id] = $forum;
1986         }
1988         unset($modinfo);
1990     } // End foreach $courses
1992     return $readableforums;
1995 /**
1996  * Returns a list of posts found using an array of search terms.
1997  *
1998  * @global object
1999  * @global object
2000  * @global object
2001  * @param array $searchterms array of search terms, e.g. word +word -word
2002  * @param int $courseid if 0, we search through the whole site
2003  * @param int $limitfrom
2004  * @param int $limitnum
2005  * @param int &$totalcount
2006  * @param string $extrasql
2007  * @return array|bool Array of posts found or false
2008  */
2009 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
2010                             &$totalcount, $extrasql='') {
2011     global $CFG, $DB, $USER;
2012     require_once($CFG->libdir.'/searchlib.php');
2014     $forums = forum_get_readable_forums($USER->id, $courseid);
2016     if (count($forums) == 0) {
2017         $totalcount = 0;
2018         return false;
2019     }
2021     $now = round(time(), -2); // db friendly
2023     $fullaccess = array();
2024     $where = array();
2025     $params = array();
2027     foreach ($forums as $forumid => $forum) {
2028         $select = array();
2030         if (!$forum->viewhiddentimedposts) {
2031             $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
2032             $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
2033         }
2035         $cm = $forum->cm;
2036         $context = $forum->context;
2038         if ($forum->type == 'qanda'
2039             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
2040             if (!empty($forum->onlydiscussions)) {
2041                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
2042                 $params = array_merge($params, $discussionid_params);
2043                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
2044             } else {
2045                 $select[] = "p.parent = 0";
2046             }
2047         }
2049         if (!empty($forum->onlygroups)) {
2050             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
2051             $params = array_merge($params, $groupid_params);
2052             $select[] = "d.groupid $groupid_sql";
2053         }
2055         if ($select) {
2056             $selects = implode(" AND ", $select);
2057             $where[] = "(d.forum = :forum{$forumid} AND $selects)";
2058             $params['forum'.$forumid] = $forumid;
2059         } else {
2060             $fullaccess[] = $forumid;
2061         }
2062     }
2064     if ($fullaccess) {
2065         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
2066         $params = array_merge($params, $fullid_params);
2067         $where[] = "(d.forum $fullid_sql)";
2068     }
2070     $selectdiscussion = "(".implode(" OR ", $where).")";
2072     $messagesearch = '';
2073     $searchstring = '';
2075     // Need to concat these back together for parser to work.
2076     foreach($searchterms as $searchterm){
2077         if ($searchstring != '') {
2078             $searchstring .= ' ';
2079         }
2080         $searchstring .= $searchterm;
2081     }
2083     // We need to allow quoted strings for the search. The quotes *should* be stripped
2084     // by the parser, but this should be examined carefully for security implications.
2085     $searchstring = str_replace("\\\"","\"",$searchstring);
2086     $parser = new search_parser();
2087     $lexer = new search_lexer($parser);
2089     if ($lexer->parse($searchstring)) {
2090         $parsearray = $parser->get_parsed_array();
2091     // Experimental feature under 1.8! MDL-8830
2092     // Use alternative text searches if defined
2093     // This feature only works under mysql until properly implemented for other DBs
2094     // Requires manual creation of text index for forum_posts before enabling it:
2095     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2096     // Experimental feature under 1.8! MDL-8830
2097         if (!empty($CFG->forum_usetextsearches)) {
2098             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2099                                                  'p.userid', 'u.id', 'u.firstname',
2100                                                  'u.lastname', 'p.modified', 'd.forum');
2101         } else {
2102             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2103                                                  'p.userid', 'u.id', 'u.firstname',
2104                                                  'u.lastname', 'p.modified', 'd.forum');
2105         }
2106         $params = array_merge($params, $msparams);
2107     }
2109     $fromsql = "{forum_posts} p,
2110                   {forum_discussions} d,
2111                   {user} u";
2113     $selectsql = " $messagesearch
2114                AND p.discussion = d.id
2115                AND p.userid = u.id
2116                AND $selectdiscussion
2117                    $extrasql";
2119     $countsql = "SELECT COUNT(*)
2120                    FROM $fromsql
2121                   WHERE $selectsql";
2123     $allnames = get_all_user_name_fields(true, 'u');
2124     $searchsql = "SELECT p.*,
2125                          d.forum,
2126                          $allnames,
2127                          u.email,
2128                          u.picture,
2129                          u.imagealt
2130                     FROM $fromsql
2131                    WHERE $selectsql
2132                 ORDER BY p.modified DESC";
2134     $totalcount = $DB->count_records_sql($countsql, $params);
2136     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2139 /**
2140  * Returns a list of ratings for a particular post - sorted.
2141  *
2142  * TODO: Check if this function is actually used anywhere.
2143  * Up until the fix for MDL-27471 this function wasn't even returning.
2144  *
2145  * @param stdClass $context
2146  * @param int $postid
2147  * @param string $sort
2148  * @return array Array of ratings or false
2149  */
2150 function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
2151     $options = new stdClass;
2152     $options->context = $context;
2153     $options->component = 'mod_forum';
2154     $options->ratingarea = 'post';
2155     $options->itemid = $postid;
2156     $options->sort = "ORDER BY $sort";
2158     $rm = new rating_manager();
2159     return $rm->get_all_ratings_for_item($options);
2162 /**
2163  * Returns a list of all new posts that have not been mailed yet
2164  *
2165  * @param int $starttime posts created after this time
2166  * @param int $endtime posts created before this
2167  * @param int $now used for timed discussions only
2168  * @return array
2169  */
2170 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2171     global $CFG, $DB;
2173     $params = array();
2174     $params['mailed'] = FORUM_MAILED_PENDING;
2175     $params['ptimestart'] = $starttime;
2176     $params['ptimeend'] = $endtime;
2177     $params['mailnow'] = 1;
2179     if (!empty($CFG->forum_enabletimedposts)) {
2180         if (empty($now)) {
2181             $now = time();
2182         }
2183         $timedsql = "AND (d.timestart < :dtimestart AND (d.timeend = 0 OR d.timeend > :dtimeend))";
2184         $params['dtimestart'] = $now;
2185         $params['dtimeend'] = $now;
2186     } else {
2187         $timedsql = "";
2188     }
2190     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2191                                  FROM {forum_posts} p
2192                                  JOIN {forum_discussions} d ON d.id = p.discussion
2193                                  WHERE p.mailed = :mailed
2194                                  AND p.created >= :ptimestart
2195                                  AND (p.created < :ptimeend OR p.mailnow = :mailnow)
2196                                  $timedsql
2197                                  ORDER BY p.modified ASC", $params);
2200 /**
2201  * Marks posts before a certain time as being mailed already
2202  *
2203  * @global object
2204  * @global object
2205  * @param int $endtime
2206  * @param int $now Defaults to time()
2207  * @return bool
2208  */
2209 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2210     global $CFG, $DB;
2212     if (empty($now)) {
2213         $now = time();
2214     }
2216     $params = array();
2217     $params['mailedsuccess'] = FORUM_MAILED_SUCCESS;
2218     $params['now'] = $now;
2219     $params['endtime'] = $endtime;
2220     $params['mailnow'] = 1;
2221     $params['mailedpending'] = FORUM_MAILED_PENDING;
2223     if (empty($CFG->forum_enabletimedposts)) {
2224         return $DB->execute("UPDATE {forum_posts}
2225                              SET mailed = :mailedsuccess
2226                              WHERE (created < :endtime OR mailnow = :mailnow)
2227                              AND mailed = :mailedpending", $params);
2228     } else {
2229         return $DB->execute("UPDATE {forum_posts}
2230                              SET mailed = :mailedsuccess
2231                              WHERE discussion NOT IN (SELECT d.id
2232                                                       FROM {forum_discussions} d
2233                                                       WHERE d.timestart > :now)
2234                              AND (created < :endtime OR mailnow = :mailnow)
2235                              AND mailed = :mailedpending", $params);
2236     }
2239 /**
2240  * Get all the posts for a user in a forum suitable for forum_print_post
2241  *
2242  * @global object
2243  * @global object
2244  * @uses CONTEXT_MODULE
2245  * @return array
2246  */
2247 function forum_get_user_posts($forumid, $userid) {
2248     global $CFG, $DB;
2250     $timedsql = "";
2251     $params = array($forumid, $userid);
2253     if (!empty($CFG->forum_enabletimedposts)) {
2254         $cm = get_coursemodule_from_instance('forum', $forumid);
2255         if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2256             $now = time();
2257             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2258             $params[] = $now;
2259             $params[] = $now;
2260         }
2261     }
2263     $allnames = get_all_user_name_fields(true, 'u');
2264     return $DB->get_records_sql("SELECT p.*, d.forum, $allnames, u.email, u.picture, u.imagealt
2265                               FROM {forum} f
2266                                    JOIN {forum_discussions} d ON d.forum = f.id
2267                                    JOIN {forum_posts} p       ON p.discussion = d.id
2268                                    JOIN {user} u              ON u.id = p.userid
2269                              WHERE f.id = ?
2270                                    AND p.userid = ?
2271                                    $timedsql
2272                           ORDER BY p.modified ASC", $params);
2275 /**
2276  * Get all the discussions user participated in
2277  *
2278  * @global object
2279  * @global object
2280  * @uses CONTEXT_MODULE
2281  * @param int $forumid
2282  * @param int $userid
2283  * @return array Array or false
2284  */
2285 function forum_get_user_involved_discussions($forumid, $userid) {
2286     global $CFG, $DB;
2288     $timedsql = "";
2289     $params = array($forumid, $userid);
2290     if (!empty($CFG->forum_enabletimedposts)) {
2291         $cm = get_coursemodule_from_instance('forum', $forumid);
2292         if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2293             $now = time();
2294             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2295             $params[] = $now;
2296             $params[] = $now;
2297         }
2298     }
2300     return $DB->get_records_sql("SELECT DISTINCT d.*
2301                               FROM {forum} f
2302                                    JOIN {forum_discussions} d ON d.forum = f.id
2303                                    JOIN {forum_posts} p       ON p.discussion = d.id
2304                              WHERE f.id = ?
2305                                    AND p.userid = ?
2306                                    $timedsql", $params);
2309 /**
2310  * Get all the posts for a user in a forum suitable for forum_print_post
2311  *
2312  * @global object
2313  * @global object
2314  * @param int $forumid
2315  * @param int $userid
2316  * @return array of counts or false
2317  */
2318 function forum_count_user_posts($forumid, $userid) {
2319     global $CFG, $DB;
2321     $timedsql = "";
2322     $params = array($forumid, $userid);
2323     if (!empty($CFG->forum_enabletimedposts)) {
2324         $cm = get_coursemodule_from_instance('forum', $forumid);
2325         if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2326             $now = time();
2327             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2328             $params[] = $now;
2329             $params[] = $now;
2330         }
2331     }
2333     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2334                              FROM {forum} f
2335                                   JOIN {forum_discussions} d ON d.forum = f.id
2336                                   JOIN {forum_posts} p       ON p.discussion = d.id
2337                                   JOIN {user} u              ON u.id = p.userid
2338                             WHERE f.id = ?
2339                                   AND p.userid = ?
2340                                   $timedsql", $params);
2343 /**
2344  * Given a log entry, return the forum post details for it.
2345  *
2346  * @global object
2347  * @global object
2348  * @param object $log
2349  * @return array|null
2350  */
2351 function forum_get_post_from_log($log) {
2352     global $CFG, $DB;
2354     $allnames = get_all_user_name_fields(true, 'u');
2355     if ($log->action == "add post") {
2357         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid, $allnames, u.email, u.picture
2358                                  FROM {forum_discussions} d,
2359                                       {forum_posts} p,
2360                                       {forum} f,
2361                                       {user} u
2362                                 WHERE p.id = ?
2363                                   AND d.id = p.discussion
2364                                   AND p.userid = u.id
2365                                   AND u.deleted <> '1'
2366                                   AND f.id = d.forum", array($log->info));
2369     } else if ($log->action == "add discussion") {
2371         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid, $allnames, u.email, u.picture
2372                                  FROM {forum_discussions} d,
2373                                       {forum_posts} p,
2374                                       {forum} f,
2375                                       {user} u
2376                                 WHERE d.id = ?
2377                                   AND d.firstpost = p.id
2378                                   AND p.userid = u.id
2379                                   AND u.deleted <> '1'
2380                                   AND f.id = d.forum", array($log->info));
2381     }
2382     return NULL;
2385 /**
2386  * Given a discussion id, return the first post from the discussion
2387  *
2388  * @global object
2389  * @global object
2390  * @param int $dicsussionid
2391  * @return array
2392  */
2393 function forum_get_firstpost_from_discussion($discussionid) {
2394     global $CFG, $DB;
2396     return $DB->get_record_sql("SELECT p.*
2397                              FROM {forum_discussions} d,
2398                                   {forum_posts} p
2399                             WHERE d.id = ?
2400                               AND d.firstpost = p.id ", array($discussionid));
2403 /**
2404  * Returns an array of counts of replies to each discussion
2405  *
2406  * @global object
2407  * @global object
2408  * @param int $forumid
2409  * @param string $forumsort
2410  * @param int $limit
2411  * @param int $page
2412  * @param int $perpage
2413  * @return array
2414  */
2415 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2416     global $CFG, $DB;
2418     if ($limit > 0) {
2419         $limitfrom = 0;
2420         $limitnum  = $limit;
2421     } else if ($page != -1) {
2422         $limitfrom = $page*$perpage;
2423         $limitnum  = $perpage;
2424     } else {
2425         $limitfrom = 0;
2426         $limitnum  = 0;
2427     }
2429     if ($forumsort == "") {
2430         $orderby = "";
2431         $groupby = "";
2433     } else {
2434         $orderby = "ORDER BY $forumsort";
2435         $groupby = ", ".strtolower($forumsort);
2436         $groupby = str_replace('desc', '', $groupby);
2437         $groupby = str_replace('asc', '', $groupby);
2438     }
2440     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2441         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2442                   FROM {forum_posts} p
2443                        JOIN {forum_discussions} d ON p.discussion = d.id
2444                  WHERE p.parent > 0 AND d.forum = ?
2445               GROUP BY p.discussion";
2446         return $DB->get_records_sql($sql, array($forumid));
2448     } else {
2449         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2450                   FROM {forum_posts} p
2451                        JOIN {forum_discussions} d ON p.discussion = d.id
2452                  WHERE d.forum = ?
2453               GROUP BY p.discussion $groupby
2454               $orderby";
2455         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2456     }
2459 /**
2460  * @global object
2461  * @global object
2462  * @global object
2463  * @staticvar array $cache
2464  * @param object $forum
2465  * @param object $cm
2466  * @param object $course
2467  * @return mixed
2468  */
2469 function forum_count_discussions($forum, $cm, $course) {
2470     global $CFG, $DB, $USER;
2472     static $cache = array();
2474     $now = round(time(), -2); // db cache friendliness
2476     $params = array($course->id);
2478     if (!isset($cache[$course->id])) {
2479         if (!empty($CFG->forum_enabletimedposts)) {
2480             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2481             $params[] = $now;
2482             $params[] = $now;
2483         } else {
2484             $timedsql = "";
2485         }
2487         $sql = "SELECT f.id, COUNT(d.id) as dcount
2488                   FROM {forum} f
2489                        JOIN {forum_discussions} d ON d.forum = f.id
2490                  WHERE f.course = ?
2491                        $timedsql
2492               GROUP BY f.id";
2494         if ($counts = $DB->get_records_sql($sql, $params)) {
2495             foreach ($counts as $count) {
2496                 $counts[$count->id] = $count->dcount;
2497             }
2498             $cache[$course->id] = $counts;
2499         } else {
2500             $cache[$course->id] = array();
2501         }
2502     }
2504     if (empty($cache[$course->id][$forum->id])) {
2505         return 0;
2506     }
2508     $groupmode = groups_get_activity_groupmode($cm, $course);
2510     if ($groupmode != SEPARATEGROUPS) {
2511         return $cache[$course->id][$forum->id];
2512     }
2514     if (has_capability('moodle/site:accessallgroups', context_module::instance($cm->id))) {
2515         return $cache[$course->id][$forum->id];
2516     }
2518     require_once($CFG->dirroot.'/course/lib.php');
2520     $modinfo = get_fast_modinfo($course);
2522     $mygroups = $modinfo->get_groups($cm->groupingid);
2524     // add all groups posts
2525     $mygroups[-1] = -1;
2527     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2528     $params[] = $forum->id;
2530     if (!empty($CFG->forum_enabletimedposts)) {
2531         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2532         $params[] = $now;
2533         $params[] = $now;
2534     } else {
2535         $timedsql = "";
2536     }
2538     $sql = "SELECT COUNT(d.id)
2539               FROM {forum_discussions} d
2540              WHERE d.groupid $mygroups_sql AND d.forum = ?
2541                    $timedsql";
2543     return $DB->get_field_sql($sql, $params);
2546 /**
2547  * How many posts by other users are unrated by a given user in the given discussion?
2548  *
2549  * TODO: Is this function still used anywhere?
2550  *
2551  * @param int $discussionid
2552  * @param int $userid
2553  * @return mixed
2554  */
2555 function forum_count_unrated_posts($discussionid, $userid) {
2556     global $CFG, $DB;
2558     $sql = "SELECT COUNT(*) as num
2559               FROM {forum_posts}
2560              WHERE parent > 0
2561                AND discussion = :discussionid
2562                AND userid <> :userid";
2563     $params = array('discussionid' => $discussionid, 'userid' => $userid);
2564     $posts = $DB->get_record_sql($sql, $params);
2565     if ($posts) {
2566         $sql = "SELECT count(*) as num
2567                   FROM {forum_posts} p,
2568                        {rating} r
2569                  WHERE p.discussion = :discussionid AND
2570                        p.id = r.itemid AND
2571                        r.userid = userid AND
2572                        r.component = 'mod_forum' AND
2573                        r.ratingarea = 'post'";
2574         $rated = $DB->get_record_sql($sql, $params);
2575         if ($rated) {
2576             if ($posts->num > $rated->num) {
2577                 return $posts->num - $rated->num;
2578             } else {
2579                 return 0;    // Just in case there was a counting error
2580             }
2581         } else {
2582             return $posts->num;
2583         }
2584     } else {
2585         return 0;
2586     }
2589 /**
2590  * Get all discussions in a forum
2591  *
2592  * @global object
2593  * @global object
2594  * @global object
2595  * @uses CONTEXT_MODULE
2596  * @uses VISIBLEGROUPS
2597  * @param object $cm
2598  * @param string $forumsort
2599  * @param bool $fullpost
2600  * @param int $unused
2601  * @param int $limit
2602  * @param bool $userlastmodified
2603  * @param int $page
2604  * @param int $perpage
2605  * @return array
2606  */
2607 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2608     global $CFG, $DB, $USER;
2610     $timelimit = '';
2612     $now = round(time(), -2);
2613     $params = array($cm->instance);
2615     $modcontext = context_module::instance($cm->id);
2617     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2618         return array();
2619     }
2621     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2623         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2624             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2625             $params[] = $now;
2626             $params[] = $now;
2627             if (isloggedin()) {
2628                 $timelimit .= " OR d.userid = ?";
2629                 $params[] = $USER->id;
2630             }
2631             $timelimit .= ")";
2632         }
2633     }
2635     if ($limit > 0) {
2636         $limitfrom = 0;
2637         $limitnum  = $limit;
2638     } else if ($page != -1) {
2639         $limitfrom = $page*$perpage;
2640         $limitnum  = $perpage;
2641     } else {
2642         $limitfrom = 0;
2643         $limitnum  = 0;
2644     }
2646     $groupmode    = groups_get_activity_groupmode($cm);
2647     $currentgroup = groups_get_activity_group($cm);
2649     if ($groupmode) {
2650         if (empty($modcontext)) {
2651             $modcontext = context_module::instance($cm->id);
2652         }
2654         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2655             if ($currentgroup) {
2656                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2657                 $params[] = $currentgroup;
2658             } else {
2659                 $groupselect = "";
2660             }
2662         } else {
2663             //seprate groups without access all
2664             if ($currentgroup) {
2665                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2666                 $params[] = $currentgroup;
2667             } else {
2668                 $groupselect = "AND d.groupid = -1";
2669             }
2670         }
2671     } else {
2672         $groupselect = "";
2673     }
2676     if (empty($forumsort)) {
2677         $forumsort = "d.timemodified DESC";
2678     }
2679     if (empty($fullpost)) {
2680         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2681     } else {
2682         $postdata = "p.*";
2683     }
2685     if (empty($userlastmodified)) {  // We don't need to know this
2686         $umfields = "";
2687         $umtable  = "";
2688     } else {
2689         $umfields = '';
2690         $umnames = get_all_user_name_fields();
2691         foreach ($umnames as $umname) {
2692             $umfields .= ', um.' . $umname . ' AS um' . $umname;
2693         }
2694         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2695     }
2697     $allnames = get_all_user_name_fields(true, 'u');
2698     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend, $allnames,
2699                    u.email, u.picture, u.imagealt $umfields
2700               FROM {forum_discussions} d
2701                    JOIN {forum_posts} p ON p.discussion = d.id
2702                    JOIN {user} u ON p.userid = u.id
2703                    $umtable
2704              WHERE d.forum = ? AND p.parent = 0
2705                    $timelimit $groupselect
2706           ORDER BY $forumsort";
2707     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2710 /**
2711  *
2712  * @global object
2713  * @global object
2714  * @global object
2715  * @uses CONTEXT_MODULE
2716  * @uses VISIBLEGROUPS
2717  * @param object $cm
2718  * @return array
2719  */
2720 function forum_get_discussions_unread($cm) {
2721     global $CFG, $DB, $USER;
2723     $now = round(time(), -2);
2724     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2726     $params = array();
2727     $groupmode    = groups_get_activity_groupmode($cm);
2728     $currentgroup = groups_get_activity_group($cm);
2730     if ($groupmode) {
2731         $modcontext = context_module::instance($cm->id);
2733         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2734             if ($currentgroup) {
2735                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2736                 $params['currentgroup'] = $currentgroup;
2737             } else {
2738                 $groupselect = "";
2739             }
2741         } else {
2742             //separate groups without access all
2743             if ($currentgroup) {
2744                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2745                 $params['currentgroup'] = $currentgroup;
2746             } else {
2747                 $groupselect = "AND d.groupid = -1";
2748             }
2749         }
2750     } else {
2751         $groupselect = "";
2752     }
2754     if (!empty($CFG->forum_enabletimedposts)) {
2755         $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2756         $params['now1'] = $now;
2757         $params['now2'] = $now;
2758     } else {
2759         $timedsql = "";
2760     }
2762     $sql = "SELECT d.id, COUNT(p.id) AS unread
2763               FROM {forum_discussions} d
2764                    JOIN {forum_posts} p     ON p.discussion = d.id
2765                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2766              WHERE d.forum = {$cm->instance}
2767                    AND p.modified >= :cutoffdate AND r.id is NULL
2768                    $groupselect
2769                    $timedsql
2770           GROUP BY d.id";
2771     $params['cutoffdate'] = $cutoffdate;
2773     if ($unreads = $DB->get_records_sql($sql, $params)) {
2774         foreach ($unreads as $unread) {
2775             $unreads[$unread->id] = $unread->unread;
2776         }
2777         return $unreads;
2778     } else {
2779         return array();
2780     }
2783 /**
2784  * @global object
2785  * @global object
2786  * @global object
2787  * @uses CONEXT_MODULE
2788  * @uses VISIBLEGROUPS
2789  * @param object $cm
2790  * @return array
2791  */
2792 function forum_get_discussions_count($cm) {
2793     global $CFG, $DB, $USER;
2795     $now = round(time(), -2);
2796     $params = array($cm->instance);
2797     $groupmode    = groups_get_activity_groupmode($cm);
2798     $currentgroup = groups_get_activity_group($cm);
2800     if ($groupmode) {
2801         $modcontext = context_module::instance($cm->id);
2803         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2804             if ($currentgroup) {
2805                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2806                 $params[] = $currentgroup;
2807             } else {
2808                 $groupselect = "";
2809             }
2811         } else {
2812             //seprate groups without access all
2813             if ($currentgroup) {
2814                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2815                 $params[] = $currentgroup;
2816             } else {
2817                 $groupselect = "AND d.groupid = -1";
2818             }
2819         }
2820     } else {
2821         $groupselect = "";
2822     }
2824     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2826     $timelimit = "";
2828     if (!empty($CFG->forum_enabletimedposts)) {
2830         $modcontext = context_module::instance($cm->id);
2832         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2833             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2834             $params[] = $now;
2835             $params[] = $now;
2836             if (isloggedin()) {
2837                 $timelimit .= " OR d.userid = ?";
2838                 $params[] = $USER->id;
2839             }
2840             $timelimit .= ")";
2841         }
2842     }
2844     $sql = "SELECT COUNT(d.id)
2845               FROM {forum_discussions} d
2846                    JOIN {forum_posts} p ON p.discussion = d.id
2847              WHERE d.forum = ? AND p.parent = 0
2848                    $groupselect $timelimit";
2850     return $DB->get_field_sql($sql, $params);
2854 /**
2855  * Get all discussions started by a particular user in a course (or group)
2856  * This function no longer used ...
2857  *
2858  * @todo Remove this function if no longer used
2859  * @global object
2860  * @global object
2861  * @param int $courseid
2862  * @param int $userid
2863  * @param int $groupid
2864  * @return array
2865  */
2866 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2867     global $CFG, $DB;
2868     $params = array($courseid, $userid);
2869     if ($groupid) {
2870         $groupselect = " AND d.groupid = ? ";
2871         $params[] = $groupid;
2872     } else  {
2873         $groupselect = "";
2874     }
2876     $allnames = get_all_user_name_fields(true, 'u');
2877     return $DB->get_records_sql("SELECT p.*, d.groupid, $allnames, u.email, u.picture, u.imagealt,
2878                                    f.type as forumtype, f.name as forumname, f.id as forumid
2879                               FROM {forum_discussions} d,
2880                                    {forum_posts} p,
2881                                    {user} u,
2882                                    {forum} f
2883                              WHERE d.course = ?
2884                                AND p.discussion = d.id
2885                                AND p.parent = 0
2886                                AND p.userid = u.id
2887                                AND u.id = ?
2888                                AND d.forum = f.id $groupselect
2889                           ORDER BY p.created DESC", $params);
2892 /**
2893  * Get the list of potential subscribers to a forum.
2894  *
2895  * @param object $forumcontext the forum context.
2896  * @param integer $groupid the id of a group, or 0 for all groups.
2897  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2898  * @param string $sort sort order. As for get_users_by_capability.
2899  * @return array list of users.
2900  */
2901 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort = '') {
2902     global $DB;
2904     // only active enrolled users or everybody on the frontpage
2905     list($esql, $params) = get_enrolled_sql($forumcontext, 'mod/forum:allowforcesubscribe', $groupid, true);
2906     if (!$sort) {
2907         list($sort, $sortparams) = users_order_by_sql('u');
2908         $params = array_merge($params, $sortparams);
2909     }
2911     $sql = "SELECT $fields
2912               FROM {user} u
2913               JOIN ($esql) je ON je.id = u.id
2914           ORDER BY $sort";
2916     return $DB->get_records_sql($sql, $params);
2919 /**
2920  * Returns list of user objects that are subscribed to this forum
2921  *
2922  * @global object
2923  * @global object
2924  * @param object $course the course
2925  * @param forum $forum the forum
2926  * @param integer $groupid group id, or 0 for all.
2927  * @param object $context the forum context, to save re-fetching it where possible.
2928  * @param string $fields requested user fields (with "u." table prefix)
2929  * @return array list of users.
2930  */
2931 function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
2932     global $CFG, $DB;
2934     $allnames = get_all_user_name_fields(true, 'u');
2935     if (empty($fields)) {
2936         $fields ="u.id,
2937                   u.username,
2938                   $allnames,
2939                   u.maildisplay,
2940                   u.mailformat,
2941                   u.maildigest,
2942                   u.imagealt,
2943                   u.email,
2944                   u.emailstop,
2945                   u.city,
2946                   u.country,
2947                   u.lastaccess,
2948                   u.lastlogin,
2949                   u.picture,
2950                   u.timezone,
2951                   u.theme,
2952                   u.lang,
2953                   u.trackforums,
2954                   u.mnethostid";
2955     }
2957     if (empty($context)) {
2958         $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2959         $context = context_module::instance($cm->id);
2960     }
2962     if (forum_is_forcesubscribed($forum)) {
2963         $results = forum_get_potential_subscribers($context, $groupid, $fields, "u.email ASC");
2965     } else {
2966         // only active enrolled users or everybody on the frontpage
2967         list($esql, $params) = get_enrolled_sql($context, '', $groupid, true);
2968         $params['forumid'] = $forum->id;
2969         $results = $DB->get_records_sql("SELECT $fields
2970                                            FROM {user} u
2971                                            JOIN ($esql) je ON je.id = u.id
2972                                            JOIN {forum_subscriptions} s ON s.userid = u.id
2973                                           WHERE s.forum = :forumid
2974                                        ORDER BY u.email ASC", $params);
2975     }
2977     // Guest user should never be subscribed to a forum.
2978     unset($results[$CFG->siteguest]);
2980     return $results;
2985 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2988 /**
2989  * @global object
2990  * @global object
2991  * @param int $courseid
2992  * @param string $type
2993  */
2994 function forum_get_course_forum($courseid, $type) {
2995 // How to set up special 1-per-course forums
2996     global $CFG, $DB, $OUTPUT, $USER;
2998     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2999         // There should always only be ONE, but with the right combination of
3000         // errors there might be more.  In this case, just return the oldest one (lowest ID).
3001         foreach ($forums as $forum) {
3002             return $forum;   // ie the first one
3003         }
3004     }
3006     // Doesn't exist, so create one now.
3007     $forum = new stdClass();
3008     $forum->course = $courseid;
3009     $forum->type = "$type";
3010     if (!empty($USER->htmleditor)) {
3011         $forum->introformat = $USER->htmleditor;
3012     }
3013     switch ($forum->type) {
3014         case "news":
3015             $forum->name  = get_string("namenews", "forum");
3016             $forum->intro = get_string("intronews", "forum");
3017             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
3018             $forum->assessed = 0;
3019             if ($courseid == SITEID) {
3020                 $forum->name  = get_string("sitenews");
3021                 $forum->forcesubscribe = 0;
3022             }
3023             break;
3024         case "social":
3025             $forum->name  = get_string("namesocial", "forum");
3026             $forum->intro = get_string("introsocial", "forum");
3027             $forum->assessed = 0;
3028             $forum->forcesubscribe = 0;
3029             break;
3030         case "blog":
3031             $forum->name = get_string('blogforum', 'forum');
3032             $forum->intro = get_string('introblog', 'forum');
3033             $forum->assessed = 0;
3034             $forum->forcesubscribe = 0;
3035             break;
3036         default:
3037             echo $OUTPUT->notification("That forum type doesn't exist!");
3038             return false;
3039             break;
3040     }
3042     $forum->timemodified = time();
3043     $forum->id = $DB->insert_record("forum", $forum);
3045     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
3046         echo $OUTPUT->notification("Could not find forum module!!");
3047         return false;
3048     }
3049     $mod = new stdClass();
3050     $mod->course = $courseid;
3051     $mod->module = $module->id;
3052     $mod->instance = $forum->id;
3053     $mod->section = 0;
3054     include_once("$CFG->dirroot/course/lib.php");
3055     if (! $mod->coursemodule = add_course_module($mod) ) {
3056         echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
3057         return false;
3058     }
3059     $sectionid = course_add_cm_to_section($courseid, $mod->coursemodule, 0);
3060     return $DB->get_record("forum", array("id" => "$forum->id"));
3064 /**
3065  * Given the data about a posting, builds up the HTML to display it and
3066  * returns the HTML in a string.  This is designed for sending via HTML email.
3067  *
3068  * @global object
3069  * @param object $course
3070  * @param object $cm
3071  * @param object $forum
3072  * @param object $discussion
3073  * @param object $post
3074  * @param object $userform
3075  * @param object $userto
3076  * @param bool $ownpost
3077  * @param bool $reply
3078  * @param bool $link
3079  * @param bool $rate
3080  * @param string $footer
3081  * @return string
3082  */
3083 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
3084                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
3086     global $CFG, $OUTPUT;
3088     $modcontext = context_module::instance($cm->id);
3090     if (!isset($userto->viewfullnames[$forum->id])) {
3091         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3092     } else {
3093         $viewfullnames = $userto->viewfullnames[$forum->id];
3094     }
3096     // add absolute file links
3097     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3099     // format the post body
3100     $options = new stdClass();
3101     $options->para = true;
3102     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3104     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3106     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3107     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3108     $output .= '</td>';
3110     if ($post->parent) {
3111         $output .= '<td class="topic">';
3112     } else {
3113         $output .= '<td class="topic starter">';
3114     }
3115     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3117     $fullname = fullname($userfrom, $viewfullnames);
3118     $by = new stdClass();
3119     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3120     $by->date = userdate($post->modified, '', $userto->timezone);
3121     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3123     $output .= '</td></tr>';
3125     $output .= '<tr><td class="left side" valign="top">';
3127     if (isset($userfrom->groups)) {
3128         $groups = $userfrom->groups[$forum->id];
3129     } else {
3130         $groups = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3131     }
3133     if ($groups) {
3134         $output .= print_group_picture($groups, $course->id, false, true, true);
3135     } else {
3136         $output .= '&nbsp;';
3137     }
3139     $output .= '</td><td class="content">';
3141     $attachments = forum_print_attachments($post, $cm, 'html');
3142     if ($attachments !== '') {
3143         $output .= '<div class="attachments">';
3144         $output .= $attachments;
3145         $output .= '</div>';
3146     }
3148     $output .= $formattedtext;
3150 // Commands
3151     $commands = array();
3153     if ($post->parent) {
3154         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3155                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3156     }
3158     if ($reply) {
3159         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3160                       get_string('reply', 'forum').'</a>';
3161     }
3163     $output .= '<div class="commands">';
3164     $output .= implode(' | ', $commands);
3165     $output .= '</div>';
3167 // Context link to post if required
3168     if ($link) {
3169         $output .= '<div class="link">';
3170         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3171                      get_string('postincontext', 'forum').'</a>';
3172         $output .= '</div>';
3173     }
3175     if ($footer) {
3176         $output .= '<div class="footer">'.$footer.'</div>';
3177     }
3178     $output .= '</td></tr></table>'."\n\n";
3180     return $output;
3183 /**
3184  * Print a forum post
3185  *
3186  * @global object
3187  * @global object
3188  * @uses FORUM_MODE_THREADED
3189  * @uses PORTFOLIO_FORMAT_PLAINHTML
3190  * @uses PORTFOLIO_FORMAT_FILE
3191  * @uses PORTFOLIO_FORMAT_RICHHTML
3192  * @uses PORTFOLIO_ADD_TEXT_LINK
3193  * @uses CONTEXT_MODULE
3194  * @param object $post The post to print.
3195  * @param object $discussion
3196  * @param object $forum
3197  * @param object $cm
3198  * @param object $course
3199  * @param boolean $ownpost Whether this post belongs to the current user.
3200  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3201  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3202  * @param string $footer Extra stuff to print after the message.
3203  * @param string $highlight Space-separated list of terms to highlight.
3204  * @param int $post_read true, false or -99. If we already know whether this user
3205  *          has read this post, pass that in, otherwise, pass in -99, and this
3206  *          function will work it out.
3207  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3208  *          the current user can't see this post, if this argument is true
3209  *          (the default) then print a dummy 'you can't see this post' post.
3210  *          If false, don't output anything at all.
3211  * @param bool|null $istracked
3212  * @return void
3213  */
3214 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3215                           $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3216     global $USER, $CFG, $OUTPUT;
3218     require_once($CFG->libdir . '/filelib.php');
3220     // String cache
3221     static $str;
3223     $modcontext = context_module::instance($cm->id);
3225     $post->course = $course->id;
3226     $post->forum  = $forum->id;
3227     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3228     if (!empty($CFG->enableplagiarism)) {
3229         require_once($CFG->libdir.'/plagiarismlib.php');
3230         $post->message .= plagiarism_get_links(array('userid' => $post->userid,
3231             'content' => $post->message,
3232             'cmid' => $cm->id,
3233             'course' => $post->course,
3234             'forum' => $post->forum));
3235     }
3237     // caching
3238     if (!isset($cm->cache)) {
3239         $cm->cache = new stdClass;
3240     }
3242     if (!isset($cm->cache->caps)) {
3243         $cm->cache->caps = array();
3244         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3245         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3246         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3247         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3248         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3249         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3250         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3251         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3252         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3253     }
3255     if (!isset($cm->uservisible)) {
3256         $cm->uservisible = coursemodule_visible_for_user($cm);
3257     }
3259     if ($istracked && is_null($postisread)) {
3260         $postisread = forum_tp_is_post_read($USER->id, $post);
3261     }
3263     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3264         $output = '';
3265         if (!$dummyifcantsee) {
3266             if ($return) {
3267                 return $output;
3268             }
3269             echo $output;
3270             return;
3271         }
3272         $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3273         $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'));
3274         $output .= html_writer::start_tag('div', array('class'=>'row header'));
3275         $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3276         if ($post->parent) {
3277             $output .= html_writer::start_tag('div', array('class'=>'topic'));
3278         } else {
3279             $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3280         }
3281         $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class'=>'subject')); // Subject
3282         $output .= html_writer::tag('div', get_string('forumauthorhidden','forum'), array('class'=>'author')); // author
3283         $output .= html_writer::end_tag('div');
3284         $output .= html_writer::end_tag('div'); // row
3285         $output .= html_writer::start_tag('div', array('class'=>'row'));
3286         $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3287         $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3288         $output .= html_writer::end_tag('div'); // row
3289         $output .= html_writer::end_tag('div'); // forumpost
3291         if ($return) {
3292             return $output;
3293         }
3294         echo $output;
3295         return;
3296     }
3298     if (empty($str)) {
3299         $str = new stdClass;
3300         $str->edit         = get_string('edit', 'forum');
3301         $str->delete       = get_string('delete', 'forum');
3302         $str->reply        = get_string('reply', 'forum');
3303         $str->parent       = get_string('parent', 'forum');
3304         $str->pruneheading = get_string('pruneheading', 'forum');
3305         $str->prune        = get_string('prune', 'forum');
3306         $str->displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3307         $str->markread     = get_string('markread', 'forum');
3308         $str->markunread   = get_string('markunread', 'forum');
3309     }
3311     $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3313     // Build an object that represents the posting user
3314     $postuser = new stdClass;
3315     $postuser->id        = $post->userid;
3316     foreach (get_all_user_name_fields() as $addname) {
3317         $postuser->$addname  = $post->$addname;
3318     }
3319     $postuser->imagealt  = $post->imagealt;
3320     $postuser->picture   = $post->picture;
3321     $postuser->email     = $post->email;
3322     // Some handy things for later on
3323     $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3324     $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3326     // Prepare the groups the posting user belongs to
3327     if (isset($cm->cache->usersgroups)) {
3328         $groups = array();
3329         if (isset($cm->cache->usersgroups[$post->userid])) {
3330             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3331                 $groups[$gid] = $cm->cache->groups[$gid];
3332             }
3333         }
3334     } else {
3335         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3336     }
3338     // Prepare the attachements for the post, files then images
3339     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3341     // Determine if we need to shorten this post
3342     $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3345     // Prepare an array of commands
3346     $commands = array();
3348     // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3349     // Don't display the mark read / unread controls in this case.
3350     if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3351         $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3352         $text = $str->markunread;
3353         if (!$postisread) {
3354             $url->param('mark', 'read');
3355             $text = $str->markread;
3356         }
3357         if ($str->displaymode == FORUM_MODE_THREADED) {
3358             $url->param('parent', $post->parent);
3359         } else {
3360             $url->set_anchor('p'.$post->id);
3361         }
3362         $commands[] = array('url'=>$url, 'text'=>$text);
3363     }
3365     // Zoom in to the parent specifically
3366     if ($post->parent) {
3367         $url = new moodle_url($discussionlink);
3368         if ($str->displaymode == FORUM_MODE_THREADED) {
3369             $url->param('parent', $post->parent);
3370         } else {
3371             $url->set_anchor('p'.$post->parent);
3372         }
3373         $commands[] = array('url'=>$url, 'text'=>$str->parent);
3374     }
3376     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3377     $age = time() - $post->created;
3378     if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3379         $age = 0;
3380     }
3382     if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
3383         if (has_capability('moodle/course:manageactivities', $modcontext)) {
3384             // The first post in single simple is the forum description.
3385             $commands[] = array('url'=>new moodle_url('/course/modedit.php', array('update'=>$cm->id, 'sesskey'=>sesskey(), 'return'=>1)), 'text'=>$str->edit);
3386         }
3387     } else if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3388         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3389     }
3391     if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3392         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3393     }
3395     if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
3396         // Do not allow deleting of first post in single simple type.
3397     } else if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3398         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3399     }
3401     if ($reply) {
3402         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php#mformforum', array('reply'=>$post->id)), 'text'=>$str->reply);
3403     }
3405     if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
3406         $p = array('postid' => $post->id);
3407         require_once($CFG->libdir.'/portfoliolib.php');
3408         $button = new portfolio_add_button();
3409         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), 'mod_forum');
3410         if (empty($attachments)) {
3411             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3412         } else {
3413             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3414         }
3416         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3417         if (!empty($porfoliohtml)) {
3418             $commands[] = $porfoliohtml;
3419         }
3420     }
3421     // Finished building commands
3424     // Begin output
3426     $output  = '';
3428     if ($istracked) {
3429         if ($postisread) {
3430             $forumpostclass = ' read';
3431         } else {
3432             $forumpostclass = ' unread';
3433             $output .= html_writer::tag('a', '', array('name'=>'unread'));
3434         }
3435     } else {
3436         // ignore trackign status if not tracked or tracked param missing
3437         $forumpostclass = '';
3438     }
3440     $topicclass = '';
3441     if (empty($post->parent)) {
3442         $topicclass = ' firstpost starter';
3443     }
3445     $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3446     $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass));
3447     $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3448     $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3449     $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3450     $output .= html_writer::end_tag('div');
3453     $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3455     $postsubject = $post->subject;
3456     if (empty($post->subjectnoformat)) {
3457         $postsubject = format_string($postsubject);
3458     }
3459     $output .= html_writer::tag('div', $postsubject, array('class'=>'subject'));
3461     $by = new stdClass();
3462     $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3463     $by->date = userdate($post->modified);
3464     $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author'));
3466     $output .= html_writer::end_tag('div'); //topic
3467     $output .= html_writer::end_tag('div'); //row
3469     $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3470     $output .= html_writer::start_tag('div', array('class'=>'left'));
3472     $groupoutput = '';
3473     if ($groups) {
3474         $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3475     }
3476     if (empty($groupoutput)) {
3477         $groupoutput = '&nbsp;';
3478     }
3479     $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3481     $output .= html_writer::end_tag('div'); //left side
3482     $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3483     $output .= html_writer::start_tag('div', array('class'=>'content'));
3484     if (!empty($attachments)) {
3485         $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3486     }
3488     $options = new stdClass;
3489     $options->para    = false;
3490     $options->trusted = $post->messagetrust;
3491     $options->context = $modcontext;
3492     if ($shortenpost) {
3493         // Prepare shortened version by filtering the text then shortening it.
3494         $postclass