MDL-41191 forum: convert use of deprecated function
[moodle.git] / mod / forum / lib.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * @package    mod
19  * @subpackage forum
20  * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
21  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22  */
24 defined('MOODLE_INTERNAL') || die();
26 /** Include required files */
27 require_once($CFG->libdir.'/filelib.php');
28 require_once($CFG->libdir.'/eventslib.php');
29 require_once($CFG->dirroot.'/user/selector/lib.php');
30 require_once($CFG->dirroot.'/mod/forum/post_form.php');
32 /// CONSTANTS ///////////////////////////////////////////////////////////
34 define('FORUM_MODE_FLATOLDEST', 1);
35 define('FORUM_MODE_FLATNEWEST', -1);
36 define('FORUM_MODE_THREADED', 2);
37 define('FORUM_MODE_NESTED', 3);
39 define('FORUM_CHOOSESUBSCRIBE', 0);
40 define('FORUM_FORCESUBSCRIBE', 1);
41 define('FORUM_INITIALSUBSCRIBE', 2);
42 define('FORUM_DISALLOWSUBSCRIBE',3);
44 define('FORUM_TRACKING_OFF', 0);
45 define('FORUM_TRACKING_OPTIONAL', 1);
46 define('FORUM_TRACKING_ON', 2);
48 define('FORUM_MAILED_PENDING', 0);
49 define('FORUM_MAILED_SUCCESS', 1);
50 define('FORUM_MAILED_ERROR', 2);
52 if (!defined('FORUM_CRON_USER_CACHE')) {
53     /** Defines how many full user records are cached in forum cron. */
54     define('FORUM_CRON_USER_CACHE', 5000);
55 }
57 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
59 /**
60  * Given an object containing all the necessary data,
61  * (defined by the form in mod_form.php) this function
62  * will create a new instance and return the id number
63  * of the new instance.
64  *
65  * @param stdClass $forum add forum instance
66  * @param mod_forum_mod_form $mform
67  * @return int intance id
68  */
69 function forum_add_instance($forum, $mform = null) {
70     global $CFG, $DB;
72     $forum->timemodified = time();
74     if (empty($forum->assessed)) {
75         $forum->assessed = 0;
76     }
78     if (empty($forum->ratingtime) or empty($forum->assessed)) {
79         $forum->assesstimestart  = 0;
80         $forum->assesstimefinish = 0;
81     }
83     $forum->id = $DB->insert_record('forum', $forum);
84     $modcontext = context_module::instance($forum->coursemodule);
86     if ($forum->type == 'single') {  // Create related discussion.
87         $discussion = new stdClass();
88         $discussion->course        = $forum->course;
89         $discussion->forum         = $forum->id;
90         $discussion->name          = $forum->name;
91         $discussion->assessed      = $forum->assessed;
92         $discussion->message       = $forum->intro;
93         $discussion->messageformat = $forum->introformat;
94         $discussion->messagetrust  = trusttext_trusted(context_course::instance($forum->course));
95         $discussion->mailnow       = false;
96         $discussion->groupid       = -1;
98         $message = '';
100         $discussion->id = forum_add_discussion($discussion, null, $message);
102         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
103             // Ugly hack - we need to copy the files somehow.
104             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
105             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
107             $options = array('subdirs'=>true); // Use the same options as intro field!
108             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
109             $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
110         }
111     }
113     if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
114         $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email');
115         foreach ($users as $user) {
116             forum_subscribe($user->id, $forum->id);
117         }
118     }
120     forum_grade_item_update($forum);
122     return $forum->id;
126 /**
127  * Given an object containing all the necessary data,
128  * (defined by the form in mod_form.php) this function
129  * will update an existing instance with new data.
130  *
131  * @global object
132  * @param object $forum forum instance (with magic quotes)
133  * @return bool success
134  */
135 function forum_update_instance($forum, $mform) {
136     global $DB, $OUTPUT, $USER;
138     $forum->timemodified = time();
139     $forum->id           = $forum->instance;
141     if (empty($forum->assessed)) {
142         $forum->assessed = 0;
143     }
145     if (empty($forum->ratingtime) or empty($forum->assessed)) {
146         $forum->assesstimestart  = 0;
147         $forum->assesstimefinish = 0;
148     }
150     $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
152     // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
153     // if  scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
154     // for count and sum aggregation types the grade we check to make sure they do not exceed the scale (i.e. max score) when calculating the grade
155     if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
156         forum_update_grades($forum); // recalculate grades for the forum
157     }
159     if ($forum->type == 'single') {  // Update related discussion and post.
160         $discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC');
161         if (!empty($discussions)) {
162             if (count($discussions) > 1) {
163                 echo $OUTPUT->notification(get_string('warnformorepost', 'forum'));
164             }
165             $discussion = array_pop($discussions);
166         } else {
167             // try to recover by creating initial discussion - MDL-16262
168             $discussion = new stdClass();
169             $discussion->course          = $forum->course;
170             $discussion->forum           = $forum->id;
171             $discussion->name            = $forum->name;
172             $discussion->assessed        = $forum->assessed;
173             $discussion->message         = $forum->intro;
174             $discussion->messageformat   = $forum->introformat;
175             $discussion->messagetrust    = true;
176             $discussion->mailnow         = false;
177             $discussion->groupid         = -1;
179             $message = '';
181             forum_add_discussion($discussion, null, $message);
183             if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
184                 print_error('cannotadd', 'forum');
185             }
186         }
187         if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
188             print_error('cannotfindfirstpost', 'forum');
189         }
191         $cm         = get_coursemodule_from_instance('forum', $forum->id);
192         $modcontext = context_module::instance($cm->id, MUST_EXIST);
194         $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
195         $post->subject       = $forum->name;
196         $post->message       = $forum->intro;
197         $post->messageformat = $forum->introformat;
198         $post->messagetrust  = trusttext_trusted($modcontext);
199         $post->modified      = $forum->timemodified;
200         $post->userid        = $USER->id;    // MDL-18599, so that current teacher can take ownership of activities.
202         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
203             // Ugly hack - we need to copy the files somehow.
204             $options = array('subdirs'=>true); // Use the same options as intro field!
205             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
206         }
208         $DB->update_record('forum_posts', $post);
209         $discussion->name = $forum->name;
210         $DB->update_record('forum_discussions', $discussion);
211     }
213     $DB->update_record('forum', $forum);
215     $modcontext = context_module::instance($forum->coursemodule);
216     if (($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) && ($oldforum->forcesubscribe <> $forum->forcesubscribe)) {
217         $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
218         foreach ($users as $user) {
219             forum_subscribe($user->id, $forum->id);
220         }
221     }
223     forum_grade_item_update($forum);
225     return true;
229 /**
230  * Given an ID of an instance of this module,
231  * this function will permanently delete the instance
232  * and any data that depends on it.
233  *
234  * @global object
235  * @param int $id forum instance id
236  * @return bool success
237  */
238 function forum_delete_instance($id) {
239     global $DB;
241     if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
242         return false;
243     }
244     if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
245         return false;
246     }
247     if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
248         return false;
249     }
251     $context = context_module::instance($cm->id);
253     // now get rid of all files
254     $fs = get_file_storage();
255     $fs->delete_area_files($context->id);
257     $result = true;
259     if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
260         foreach ($discussions as $discussion) {
261             if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
262                 $result = false;
263             }
264         }
265     }
267     if (!$DB->delete_records('forum_digests', array('forum' => $forum->id))) {
268         $result = false;
269     }
271     if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
272         $result = false;
273     }
275     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
277     if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
278         $result = false;
279     }
281     forum_grade_item_delete($forum);
283     return $result;
287 /**
288  * Indicates API features that the forum supports.
289  *
290  * @uses FEATURE_GROUPS
291  * @uses FEATURE_GROUPINGS
292  * @uses FEATURE_GROUPMEMBERSONLY
293  * @uses FEATURE_MOD_INTRO
294  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
295  * @uses FEATURE_COMPLETION_HAS_RULES
296  * @uses FEATURE_GRADE_HAS_GRADE
297  * @uses FEATURE_GRADE_OUTCOMES
298  * @param string $feature
299  * @return mixed True if yes (some features may use other values)
300  */
301 function forum_supports($feature) {
302     switch($feature) {
303         case FEATURE_GROUPS:                  return true;
304         case FEATURE_GROUPINGS:               return true;
305         case FEATURE_GROUPMEMBERSONLY:        return true;
306         case FEATURE_MOD_INTRO:               return true;
307         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
308         case FEATURE_COMPLETION_HAS_RULES:    return true;
309         case FEATURE_GRADE_HAS_GRADE:         return true;
310         case FEATURE_GRADE_OUTCOMES:          return true;
311         case FEATURE_RATE:                    return true;
312         case FEATURE_BACKUP_MOODLE2:          return true;
313         case FEATURE_SHOW_DESCRIPTION:        return true;
314         case FEATURE_PLAGIARISM:              return true;
316         default: return null;
317     }
321 /**
322  * Obtains the automatic completion state for this forum based on any conditions
323  * in forum settings.
324  *
325  * @global object
326  * @global object
327  * @param object $course Course
328  * @param object $cm Course-module
329  * @param int $userid User ID
330  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
331  * @return bool True if completed, false if not. (If no conditions, then return
332  *   value depends on comparison type)
333  */
334 function forum_get_completion_state($course,$cm,$userid,$type) {
335     global $CFG,$DB;
337     // Get forum details
338     if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
339         throw new Exception("Can't find forum {$cm->instance}");
340     }
342     $result=$type; // Default return value
344     $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
345     $postcountsql="
346 SELECT
347     COUNT(1)
348 FROM
349     {forum_posts} fp
350     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
351 WHERE
352     fp.userid=:userid AND fd.forum=:forumid";
354     if ($forum->completiondiscussions) {
355         $value = $forum->completiondiscussions <=
356                  $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
357         if ($type == COMPLETION_AND) {
358             $result = $result && $value;
359         } else {
360             $result = $result || $value;
361         }
362     }
363     if ($forum->completionreplies) {
364         $value = $forum->completionreplies <=
365                  $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
366         if ($type==COMPLETION_AND) {
367             $result = $result && $value;
368         } else {
369             $result = $result || $value;
370         }
371     }
372     if ($forum->completionposts) {
373         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
374         if ($type == COMPLETION_AND) {
375             $result = $result && $value;
376         } else {
377             $result = $result || $value;
378         }
379     }
381     return $result;
384 /**
385  * Create a message-id string to use in the custom headers of forum notification emails
386  *
387  * message-id is used by email clients to identify emails and to nest conversations
388  *
389  * @param int $postid The ID of the forum post we are notifying the user about
390  * @param int $usertoid The ID of the user being notified
391  * @param string $hostname The server's hostname
392  * @return string A unique message-id
393  */
394 function forum_get_email_message_id($postid, $usertoid, $hostname) {
395     return '<'.hash('sha256',$postid.'to'.$usertoid).'@'.$hostname.'>';
398 /**
399  * Removes properties from user record that are not necessary
400  * for sending post notifications.
401  * @param stdClass $user
402  * @return void, $user parameter is modified
403  */
404 function forum_cron_minimise_user_record(stdClass $user) {
406     // We store large amount of users in one huge array,
407     // make sure we do not store info there we do not actually need
408     // in mail generation code or messaging.
410     unset($user->institution);
411     unset($user->department);
412     unset($user->address);
413     unset($user->city);
414     unset($user->url);
415     unset($user->currentlogin);
416     unset($user->description);
417     unset($user->descriptionformat);
420 /**
421  * Function to be run periodically according to the moodle cron
422  * Finds all posts that have yet to be mailed out, and mails them
423  * out to all subscribers
424  *
425  * @global object
426  * @global object
427  * @global object
428  * @uses CONTEXT_MODULE
429  * @uses CONTEXT_COURSE
430  * @uses SITEID
431  * @uses FORMAT_PLAIN
432  * @return void
433  */
434 function forum_cron() {
435     global $CFG, $USER, $DB;
437     $site = get_site();
439     // All users that are subscribed to any post that needs sending,
440     // please increase $CFG->extramemorylimit on large sites that
441     // send notifications to a large number of users.
442     $users = array();
443     $userscount = 0; // Cached user counter - count($users) in PHP is horribly slow!!!
445     // status arrays
446     $mailcount  = array();
447     $errorcount = array();
449     // caches
450     $discussions     = array();
451     $forums          = array();
452     $courses         = array();
453     $coursemodules   = array();
454     $subscribedusers = array();
457     // Posts older than 2 days will not be mailed.  This is to avoid the problem where
458     // cron has not been running for a long time, and then suddenly people are flooded
459     // with mail from the past few weeks or months
460     $timenow   = time();
461     $endtime   = $timenow - $CFG->maxeditingtime;
462     $starttime = $endtime - 48 * 3600;   // Two days earlier
464     // Get the list of forum subscriptions for per-user per-forum maildigest settings.
465     $digestsset = $DB->get_recordset('forum_digests', null, '', 'id, userid, forum, maildigest');
466     $digests = array();
467     foreach ($digestsset as $thisrow) {
468         if (!isset($digests[$thisrow->forum])) {
469             $digests[$thisrow->forum] = array();
470         }
471         $digests[$thisrow->forum][$thisrow->userid] = $thisrow->maildigest;
472     }
473     $digestsset->close();
475     if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
476         // Mark them all now as being mailed.  It's unlikely but possible there
477         // might be an error later so that a post is NOT actually mailed out,
478         // but since mail isn't crucial, we can accept this risk.  Doing it now
479         // prevents the risk of duplicated mails, which is a worse problem.
481         if (!forum_mark_old_posts_as_mailed($endtime)) {
482             mtrace('Errors occurred while trying to mark some posts as being mailed.');
483             return false;  // Don't continue trying to mail them, in case we are in a cron loop
484         }
486         // checking post validity, and adding users to loop through later
487         foreach ($posts as $pid => $post) {
489             $discussionid = $post->discussion;
490             if (!isset($discussions[$discussionid])) {
491                 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
492                     $discussions[$discussionid] = $discussion;
493                 } else {
494                     mtrace('Could not find discussion '.$discussionid);
495                     unset($posts[$pid]);
496                     continue;
497                 }
498             }
499             $forumid = $discussions[$discussionid]->forum;
500             if (!isset($forums[$forumid])) {
501                 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
502                     $forums[$forumid] = $forum;
503                 } else {
504                     mtrace('Could not find forum '.$forumid);
505                     unset($posts[$pid]);
506                     continue;
507                 }
508             }
509             $courseid = $forums[$forumid]->course;
510             if (!isset($courses[$courseid])) {
511                 if ($course = $DB->get_record('course', array('id' => $courseid))) {
512                     $courses[$courseid] = $course;
513                 } else {
514                     mtrace('Could not find course '.$courseid);
515                     unset($posts[$pid]);
516                     continue;
517                 }
518             }
519             if (!isset($coursemodules[$forumid])) {
520                 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
521                     $coursemodules[$forumid] = $cm;
522                 } else {
523                     mtrace('Could not find course module for forum '.$forumid);
524                     unset($posts[$pid]);
525                     continue;
526                 }
527             }
530             // caching subscribed users of each forum
531             if (!isset($subscribedusers[$forumid])) {
532                 $modcontext = context_module::instance($coursemodules[$forumid]->id);
533                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext, "u.*")) {
534                     foreach ($subusers as $postuser) {
535                         // this user is subscribed to this forum
536                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
537                         $userscount++;
538                         if ($userscount > FORUM_CRON_USER_CACHE) {
539                             // Store minimal user info.
540                             $minuser = new stdClass();
541                             $minuser->id = $postuser->id;
542                             $users[$postuser->id] = $minuser;
543                         } else {
544                             // Cache full user record.
545                             forum_cron_minimise_user_record($postuser);
546                             $users[$postuser->id] = $postuser;
547                         }
548                     }
549                     // Release memory.
550                     unset($subusers);
551                     unset($postuser);
552                 }
553             }
555             $mailcount[$pid] = 0;
556             $errorcount[$pid] = 0;
557         }
558     }
560     if ($users && $posts) {
562         $urlinfo = parse_url($CFG->wwwroot);
563         $hostname = $urlinfo['host'];
565         foreach ($users as $userto) {
567             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
569             mtrace('Processing user '.$userto->id);
571             // Init user caches - we keep the cache for one cycle only,
572             // otherwise it could consume too much memory.
573             if (isset($userto->username)) {
574                 $userto = clone($userto);
575             } else {
576                 $userto = $DB->get_record('user', array('id' => $userto->id));
577                 forum_cron_minimise_user_record($userto);
578             }
579             $userto->viewfullnames = array();
580             $userto->canpost       = array();
581             $userto->markposts     = array();
583             // set this so that the capabilities are cached, and environment matches receiving user
584             cron_setup_user($userto);
586             // reset the caches
587             foreach ($coursemodules as $forumid=>$unused) {
588                 $coursemodules[$forumid]->cache       = new stdClass();
589                 $coursemodules[$forumid]->cache->caps = array();
590                 unset($coursemodules[$forumid]->uservisible);
591             }
593             foreach ($posts as $pid => $post) {
595                 // Set up the environment for the post, discussion, forum, course
596                 $discussion = $discussions[$post->discussion];
597                 $forum      = $forums[$discussion->forum];
598                 $course     = $courses[$forum->course];
599                 $cm         =& $coursemodules[$forum->id];
601                 // Do some checks  to see if we can bail out now
602                 // Only active enrolled users are in the list of subscribers
603                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
604                     continue; // user does not subscribe to this forum
605                 }
607                 // Don't send email if the forum is Q&A and the user has not posted
608                 // Initial topics are still mailed
609                 if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id) && $pid != $discussion->firstpost) {
610                     mtrace('Did not email '.$userto->id.' because user has not posted in discussion');
611                     continue;
612                 }
614                 // Get info about the sending user
615                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
616                     $userfrom = $users[$post->userid];
617                     if (!isset($userfrom->idnumber)) {
618                         // Minimalised user info, fetch full record.
619                         $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
620                         forum_cron_minimise_user_record($userfrom);
621                     }
623                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
624                     forum_cron_minimise_user_record($userfrom);
625                     // Fetch only once if possible, we can add it to user list, it will be skipped anyway.
626                     if ($userscount <= FORUM_CRON_USER_CACHE) {
627                         $userscount++;
628                         $users[$userfrom->id] = $userfrom;
629                     }
631                 } else {
632                     mtrace('Could not find user '.$post->userid);
633                     continue;
634                 }
636                 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
638                 // setup global $COURSE properly - needed for roles and languages
639                 cron_setup_user($userto, $course);
641                 // Fill caches
642                 if (!isset($userto->viewfullnames[$forum->id])) {
643                     $modcontext = context_module::instance($cm->id);
644                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
645                 }
646                 if (!isset($userto->canpost[$discussion->id])) {
647                     $modcontext = context_module::instance($cm->id);
648                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
649                 }
650                 if (!isset($userfrom->groups[$forum->id])) {
651                     if (!isset($userfrom->groups)) {
652                         $userfrom->groups = array();
653                         if (isset($users[$userfrom->id])) {
654                             $users[$userfrom->id]->groups = array();
655                         }
656                     }
657                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
658                     if (isset($users[$userfrom->id])) {
659                         $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
660                     }
661                 }
663                 // Make sure groups allow this user to see this email
664                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
665                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
666                         continue;                           // Be safe and don't send it to anyone
667                     }
669                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
670                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
671                         continue;
672                     }
673                 }
675                 // Make sure we're allowed to see it...
676                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
677                     mtrace('user '.$userto->id. ' can not see '.$post->id);
678                     continue;
679                 }
681                 // OK so we need to send the email.
683                 // Does the user want this post in a digest?  If so postpone it for now.
684                 $maildigest = forum_get_user_maildigest_bulk($digests, $userto, $forum->id);
686                 if ($maildigest > 0) {
687                     // This user wants the mails to be in digest form
688                     $queue = new stdClass();
689                     $queue->userid       = $userto->id;
690                     $queue->discussionid = $discussion->id;
691                     $queue->postid       = $post->id;
692                     $queue->timemodified = $post->created;
693                     $DB->insert_record('forum_queue', $queue);
694                     continue;
695                 }
698                 // Prepare to actually send the post now, and build up the content
700                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
702                 $userfrom->customheaders = array (  // Headers to make emails easier to track
703                            'Precedence: Bulk',
704                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
705                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
706                            'Message-ID: '.forum_get_email_message_id($post->id, $userto->id, $hostname),
707                            'X-Course-Id: '.$course->id,
708                            'X-Course-Name: '.format_string($course->fullname, true)
709                 );
711                 if ($post->parent) {  // This post is a reply, so add headers for threading (see MDL-22551)
712                     $userfrom->customheaders[] = 'In-Reply-To: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
713                     $userfrom->customheaders[] = 'References: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
714                 }
716                 $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
718                 $postsubject = html_to_text("$shortname: ".format_string($post->subject, true));
719                 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
720                 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
722                 // Send the post now!
724                 mtrace('Sending ', '');
726                 $eventdata = new stdClass();
727                 $eventdata->component        = 'mod_forum';
728                 $eventdata->name             = 'posts';
729                 $eventdata->userfrom         = $userfrom;
730                 $eventdata->userto           = $userto;
731                 $eventdata->subject          = $postsubject;
732                 $eventdata->fullmessage      = $posttext;
733                 $eventdata->fullmessageformat = FORMAT_PLAIN;
734                 $eventdata->fullmessagehtml  = $posthtml;
735                 $eventdata->notification = 1;
737                 // If forum_replytouser is not set then send mail using the noreplyaddress.
738                 if (empty($CFG->forum_replytouser)) {
739                     // Clone userfrom as it is referenced by $users.
740                     $cloneduserfrom = clone($userfrom);
741                     $cloneduserfrom->email = $CFG->noreplyaddress;
742                     $eventdata->userfrom = $cloneduserfrom;
743                 }
745                 $smallmessagestrings = new stdClass();
746                 $smallmessagestrings->user = fullname($userfrom);
747                 $smallmessagestrings->forumname = "$shortname: ".format_string($forum->name,true).": ".$discussion->name;
748                 $smallmessagestrings->message = $post->message;
749                 //make sure strings are in message recipients language
750                 $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
752                 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
753                 $eventdata->contexturlname = $discussion->name;
755                 $mailresult = message_send($eventdata);
756                 if (!$mailresult){
757                     mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
758                          " ($userto->email) .. not trying again.");
759                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
760                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
761                     $errorcount[$post->id]++;
762                 } else {
763                     $mailcount[$post->id]++;
765                 // Mark post as read if forum_usermarksread is set off
766                     if (!$CFG->forum_usermarksread) {
767                         $userto->markposts[$post->id] = $post->id;
768                     }
769                 }
771                 mtrace('post '.$post->id. ': '.$post->subject);
772             }
774             // mark processed posts as read
775             forum_tp_mark_posts_read($userto, $userto->markposts);
776             unset($userto);
777         }
778     }
780     if ($posts) {
781         foreach ($posts as $post) {
782             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
783             if ($errorcount[$post->id]) {
784                 $DB->set_field('forum_posts', 'mailed', FORUM_MAILED_ERROR, array('id' => $post->id));
785             }
786         }
787     }
789     // release some memory
790     unset($subscribedusers);
791     unset($mailcount);
792     unset($errorcount);
794     cron_setup_user();
796     $sitetimezone = $CFG->timezone;
798     // Now see if there are any digest mails waiting to be sent, and if we should send them
800     mtrace('Starting digest processing...');
802     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
804     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
805         set_config('digestmailtimelast', 0);
806     }
808     $timenow = time();
809     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
811     // Delete any really old ones (normally there shouldn't be any)
812     $weekago = $timenow - (7 * 24 * 3600);
813     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
814     mtrace ('Cleaned old digest records');
816     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
818         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
820         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
822         if ($digestposts_rs->valid()) {
824             // We have work to do
825             $usermailcount = 0;
827             //caches - reuse the those filled before too
828             $discussionposts = array();
829             $userdiscussions = array();
831             foreach ($digestposts_rs as $digestpost) {
832                 if (!isset($posts[$digestpost->postid])) {
833                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
834                         $posts[$digestpost->postid] = $post;
835                     } else {
836                         continue;
837                     }
838                 }
839                 $discussionid = $digestpost->discussionid;
840                 if (!isset($discussions[$discussionid])) {
841                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
842                         $discussions[$discussionid] = $discussion;
843                     } else {
844                         continue;
845                     }
846                 }
847                 $forumid = $discussions[$discussionid]->forum;
848                 if (!isset($forums[$forumid])) {
849                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
850                         $forums[$forumid] = $forum;
851                     } else {
852                         continue;
853                     }
854                 }
856                 $courseid = $forums[$forumid]->course;
857                 if (!isset($courses[$courseid])) {
858                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
859                         $courses[$courseid] = $course;
860                     } else {
861                         continue;
862                     }
863                 }
865                 if (!isset($coursemodules[$forumid])) {
866                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
867                         $coursemodules[$forumid] = $cm;
868                     } else {
869                         continue;
870                     }
871                 }
872                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
873                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
874             }
875             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
877             // Data collected, start sending out emails to each user
878             foreach ($userdiscussions as $userid => $thesediscussions) {
880                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
882                 cron_setup_user();
884                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
886                 // First of all delete all the queue entries for this user
887                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
889                 // Init user caches - we keep the cache for one cycle only,
890                 // otherwise it would unnecessarily consume memory.
891                 if (array_key_exists($userid, $users) and isset($users[$userid]->username)) {
892                     $userto = clone($users[$userid]);
893                 } else {
894                     $userto = $DB->get_record('user', array('id' => $userid));
895                     forum_cron_minimise_user_record($userto);
896                 }
897                 $userto->viewfullnames = array();
898                 $userto->canpost       = array();
899                 $userto->markposts     = array();
901                 // Override the language and timezone of the "current" user, so that
902                 // mail is customised for the receiver.
903                 cron_setup_user($userto);
905                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
907                 $headerdata = new stdClass();
908                 $headerdata->sitename = format_string($site->fullname, true);
909                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
911                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
912                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
914                 $posthtml = "<head>";
915 /*                foreach ($CFG->stylesheets as $stylesheet) {
916                     //TODO: MDL-21120
917                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
918                 }*/
919                 $posthtml .= "</head>\n<body id=\"email\">\n";
920                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
922                 foreach ($thesediscussions as $discussionid) {
924                     @set_time_limit(120);   // to be reset for each post
926                     $discussion = $discussions[$discussionid];
927                     $forum      = $forums[$discussion->forum];
928                     $course     = $courses[$forum->course];
929                     $cm         = $coursemodules[$forum->id];
931                     //override language
932                     cron_setup_user($userto, $course);
934                     // Fill caches
935                     if (!isset($userto->viewfullnames[$forum->id])) {
936                         $modcontext = context_module::instance($cm->id);
937                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
938                     }
939                     if (!isset($userto->canpost[$discussion->id])) {
940                         $modcontext = context_module::instance($cm->id);
941                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
942                     }
944                     $strforums      = get_string('forums', 'forum');
945                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
946                     $canreply       = $userto->canpost[$discussion->id];
947                     $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
949                     $posttext .= "\n \n";
950                     $posttext .= '=====================================================================';
951                     $posttext .= "\n \n";
952                     $posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
953                     if ($discussion->name != $forum->name) {
954                         $posttext  .= " -> ".format_string($discussion->name,true);
955                     }
956                     $posttext .= "\n";
957                     $posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
958                     $posttext .= "\n";
960                     $posthtml .= "<p><font face=\"sans-serif\">".
961                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
962                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
963                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
964                     if ($discussion->name == $forum->name) {
965                         $posthtml .= "</font></p>";
966                     } else {
967                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
968                     }
969                     $posthtml .= '<p>';
971                     $postsarray = $discussionposts[$discussionid];
972                     sort($postsarray);
974                     foreach ($postsarray as $postid) {
975                         $post = $posts[$postid];
977                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
978                             $userfrom = $users[$post->userid];
979                             if (!isset($userfrom->idnumber)) {
980                                 $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
981                                 forum_cron_minimise_user_record($userfrom);
982                             }
984                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
985                             forum_cron_minimise_user_record($userfrom);
986                             if ($userscount <= FORUM_CRON_USER_CACHE) {
987                                 $userscount++;
988                                 $users[$userfrom->id] = $userfrom;
989                             }
991                         } else {
992                             mtrace('Could not find user '.$post->userid);
993                             continue;
994                         }
996                         if (!isset($userfrom->groups[$forum->id])) {
997                             if (!isset($userfrom->groups)) {
998                                 $userfrom->groups = array();
999                                 if (isset($users[$userfrom->id])) {
1000                                     $users[$userfrom->id]->groups = array();
1001                                 }
1002                             }
1003                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
1004                             if (isset($users[$userfrom->id])) {
1005                                 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
1006                             }
1007                         }
1009                         $userfrom->customheaders = array ("Precedence: Bulk");
1011                         $maildigest = forum_get_user_maildigest_bulk($digests, $userto, $forum->id);
1012                         if ($maildigest == 2) {
1013                             // Subjects and link only
1014                             $posttext .= "\n";
1015                             $posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
1016                             $by = new stdClass();
1017                             $by->name = fullname($userfrom);
1018                             $by->date = userdate($post->modified);
1019                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
1020                             $posttext .= "\n---------------------------------------------------------------------";
1022                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
1023                             $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>';
1025                         } else {
1026                             // The full treatment
1027                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
1028                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1030                         // Create an array of postid's for this user to mark as read.
1031                             if (!$CFG->forum_usermarksread) {
1032                                 $userto->markposts[$post->id] = $post->id;
1033                             }
1034                         }
1035                     }
1036                     $footerlinks = array();
1037                     if ($canunsubscribe) {
1038                         $footerlinks[] = "<a href=\"$CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\">" . get_string("unsubscribe", "forum") . "</a>";
1039                     } else {
1040                         $footerlinks[] = get_string("everyoneissubscribed", "forum");
1041                     }
1042                     $footerlinks[] = "<a href='{$CFG->wwwroot}/mod/forum/index.php?id={$forum->course}'>" . get_string("digestmailpost", "forum") . '</a>';
1043                     $posthtml .= "\n<div class='mdl-right'><font size=\"1\">" . implode('&nbsp;', $footerlinks) . '</font></div>';
1044                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
1045                 }
1046                 $posthtml .= '</body>';
1048                 if (empty($userto->mailformat) || $userto->mailformat != 1) {
1049                     // This user DOESN'T want to receive HTML
1050                     $posthtml = '';
1051                 }
1053                 $attachment = $attachname='';
1054                 // Directly email forum digests rather than sending them via messaging, use the
1055                 // site shortname as 'from name', the noreply address will be used by email_to_user.
1056                 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname);
1058                 if (!$mailresult) {
1059                     mtrace("ERROR!");
1060                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
1061                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
1062                 } else {
1063                     mtrace("success.");
1064                     $usermailcount++;
1066                     // Mark post as read if forum_usermarksread is set off
1067                     forum_tp_mark_posts_read($userto, $userto->markposts);
1068                 }
1069             }
1070         }
1071     /// We have finishied all digest emails, update $CFG->digestmailtimelast
1072         set_config('digestmailtimelast', $timenow);
1073     }
1075     cron_setup_user();
1077     if (!empty($usermailcount)) {
1078         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
1079     }
1081     if (!empty($CFG->forum_lastreadclean)) {
1082         $timenow = time();
1083         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
1084             set_config('forum_lastreadclean', $timenow);
1085             mtrace('Removing old forum read tracking info...');
1086             forum_tp_clean_read_records();
1087         }
1088     } else {
1089         set_config('forum_lastreadclean', time());
1090     }
1093     return true;
1096 /**
1097  * Builds and returns the body of the email notification in plain text.
1098  *
1099  * @global object
1100  * @global object
1101  * @uses CONTEXT_MODULE
1102  * @param object $course
1103  * @param object $cm
1104  * @param object $forum
1105  * @param object $discussion
1106  * @param object $post
1107  * @param object $userfrom
1108  * @param object $userto
1109  * @param boolean $bare
1110  * @return string The email body in plain text format.
1111  */
1112 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
1113     global $CFG, $USER;
1115     $modcontext = context_module::instance($cm->id);
1117     if (!isset($userto->viewfullnames[$forum->id])) {
1118         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
1119     } else {
1120         $viewfullnames = $userto->viewfullnames[$forum->id];
1121     }
1123     if (!isset($userto->canpost[$discussion->id])) {
1124         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
1125     } else {
1126         $canreply = $userto->canpost[$discussion->id];
1127     }
1129     $by = New stdClass;
1130     $by->name = fullname($userfrom, $viewfullnames);
1131     $by->date = userdate($post->modified, "", $userto->timezone);
1133     $strbynameondate = get_string('bynameondate', 'forum', $by);
1135     $strforums = get_string('forums', 'forum');
1137     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1139     $posttext = '';
1141     if (!$bare) {
1142         $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
1143         $posttext  = "$shortname -> $strforums -> ".format_string($forum->name,true);
1145         if ($discussion->name != $forum->name) {
1146             $posttext  .= " -> ".format_string($discussion->name,true);
1147         }
1148     }
1150     // add absolute file links
1151     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
1153     $posttext .= "\n";
1154     $posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
1155     $posttext .= "\n---------------------------------------------------------------------\n";
1156     $posttext .= format_string($post->subject,true);
1157     if ($bare) {
1158         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1159     }
1160     $posttext .= "\n".$strbynameondate."\n";
1161     $posttext .= "---------------------------------------------------------------------\n";
1162     $posttext .= format_text_email($post->message, $post->messageformat);
1163     $posttext .= "\n\n";
1164     $posttext .= forum_print_attachments($post, $cm, "text");
1166     if (!$bare && $canreply) {
1167         $posttext .= "---------------------------------------------------------------------\n";
1168         $posttext .= get_string("postmailinfo", "forum", $shortname)."\n";
1169         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1170     }
1171     if (!$bare && $canunsubscribe) {
1172         $posttext .= "\n---------------------------------------------------------------------\n";
1173         $posttext .= get_string("unsubscribe", "forum");
1174         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1175     }
1177     $posttext .= "\n---------------------------------------------------------------------\n";
1178     $posttext .= get_string("digestmailpost", "forum");
1179     $posttext .= ": {$CFG->wwwroot}/mod/forum/index.php?id={$forum->course}\n";
1181     return $posttext;
1184 /**
1185  * Builds and returns the body of the email notification in html format.
1186  *
1187  * @global object
1188  * @param object $course
1189  * @param object $cm
1190  * @param object $forum
1191  * @param object $discussion
1192  * @param object $post
1193  * @param object $userfrom
1194  * @param object $userto
1195  * @return string The email text in HTML format
1196  */
1197 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1198     global $CFG;
1200     if ($userto->mailformat != 1) {  // Needs to be HTML
1201         return '';
1202     }
1204     if (!isset($userto->canpost[$discussion->id])) {
1205         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course);
1206     } else {
1207         $canreply = $userto->canpost[$discussion->id];
1208     }
1210     $strforums = get_string('forums', 'forum');
1211     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1212     $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
1214     $posthtml = '<head>';
1215 /*    foreach ($CFG->stylesheets as $stylesheet) {
1216         //TODO: MDL-21120
1217         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1218     }*/
1219     $posthtml .= '</head>';
1220     $posthtml .= "\n<body id=\"email\">\n\n";
1222     $posthtml .= '<div class="navbar">'.
1223     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$shortname.'</a> &raquo; '.
1224     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1225     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1226     if ($discussion->name == $forum->name) {
1227         $posthtml .= '</div>';
1228     } else {
1229         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1230                      format_string($discussion->name,true).'</a></div>';
1231     }
1232     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1234     $footerlinks = array();
1235     if ($canunsubscribe) {
1236         $footerlinks[] = '<a href="' . $CFG->wwwroot . '/mod/forum/subscribe.php?id=' . $forum->id . '">' . get_string('unsubscribe', 'forum') . '</a>';
1237         $footerlinks[] = '<a href="' . $CFG->wwwroot . '/mod/forum/unsubscribeall.php">' . get_string('unsubscribeall', 'forum') . '</a>';
1238     }
1239     $footerlinks[] = "<a href='{$CFG->wwwroot}/mod/forum/index.php?id={$forum->course}'>" . get_string('digestmailpost', 'forum') . '</a>';
1240     $posthtml .= '<hr /><div class="mdl-align unsubscribelink">' . implode('&nbsp;', $footerlinks) . '</div>';
1242     $posthtml .= '</body>';
1244     return $posthtml;
1248 /**
1249  *
1250  * @param object $course
1251  * @param object $user
1252  * @param object $mod TODO this is not used in this function, refactor
1253  * @param object $forum
1254  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1255  */
1256 function forum_user_outline($course, $user, $mod, $forum) {
1257     global $CFG;
1258     require_once("$CFG->libdir/gradelib.php");
1259     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1260     if (empty($grades->items[0]->grades)) {
1261         $grade = false;
1262     } else {
1263         $grade = reset($grades->items[0]->grades);
1264     }
1266     $count = forum_count_user_posts($forum->id, $user->id);
1268     if ($count && $count->postcount > 0) {
1269         $result = new stdClass();
1270         $result->info = get_string("numposts", "forum", $count->postcount);
1271         $result->time = $count->lastpost;
1272         if ($grade) {
1273             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1274         }
1275         return $result;
1276     } else if ($grade) {
1277         $result = new stdClass();
1278         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1280         //datesubmitted == time created. dategraded == time modified or time overridden
1281         //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
1282         //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
1283         if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1284             $result->time = $grade->dategraded;
1285         } else {
1286             $result->time = $grade->datesubmitted;
1287         }
1289         return $result;
1290     }
1291     return NULL;
1295 /**
1296  * @global object
1297  * @global object
1298  * @param object $coure
1299  * @param object $user
1300  * @param object $mod
1301  * @param object $forum
1302  */
1303 function forum_user_complete($course, $user, $mod, $forum) {
1304     global $CFG,$USER, $OUTPUT;
1305     require_once("$CFG->libdir/gradelib.php");
1307     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1308     if (!empty($grades->items[0]->grades)) {
1309         $grade = reset($grades->items[0]->grades);
1310         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1311         if ($grade->str_feedback) {
1312             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1313         }
1314     }
1316     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1318         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1319             print_error('invalidcoursemodule');
1320         }
1321         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1323         foreach ($posts as $post) {
1324             if (!isset($discussions[$post->discussion])) {
1325                 continue;
1326             }
1327             $discussion = $discussions[$post->discussion];
1329             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1330         }
1331     } else {
1332         echo "<p>".get_string("noposts", "forum")."</p>";
1333     }
1341 /**
1342  * @global object
1343  * @global object
1344  * @global object
1345  * @param array $courses
1346  * @param array $htmlarray
1347  */
1348 function forum_print_overview($courses,&$htmlarray) {
1349     global $USER, $CFG, $DB, $SESSION;
1351     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1352         return array();
1353     }
1355     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1356         return;
1357     }
1359     // Courses to search for new posts
1360     $coursessqls = array();
1361     $params = array();
1362     foreach ($courses as $course) {
1364         // If the user has never entered into the course all posts are pending
1365         if ($course->lastaccess == 0) {
1366             $coursessqls[] = '(f.course = ?)';
1367             $params[] = $course->id;
1369         // Only posts created after the course last access
1370         } else {
1371             $coursessqls[] = '(f.course = ? AND p.created > ?)';
1372             $params[] = $course->id;
1373             $params[] = $course->lastaccess;
1374         }
1375     }
1376     $params[] = $USER->id;
1377     $coursessql = implode(' OR ', $coursessqls);
1379     $sql = "SELECT f.id, COUNT(*) as count "
1380                 .'FROM {forum} f '
1381                 .'JOIN {forum_discussions} d ON d.forum  = f.id '
1382                 .'JOIN {forum_posts} p ON p.discussion = d.id '
1383                 ."WHERE ($coursessql) "
1384                 .'AND p.userid != ? '
1385                 .'GROUP BY f.id';
1387     if (!$new = $DB->get_records_sql($sql, $params)) {
1388         $new = array(); // avoid warnings
1389     }
1391     // also get all forum tracking stuff ONCE.
1392     $trackingforums = array();
1393     foreach ($forums as $forum) {
1394         if (forum_tp_can_track_forums($forum)) {
1395             $trackingforums[$forum->id] = $forum;
1396         }
1397     }
1399     if (count($trackingforums) > 0) {
1400         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1401         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1402             ' FROM {forum_posts} p '.
1403             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1404             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1405         $params = array($USER->id);
1407         foreach ($trackingforums as $track) {
1408             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1409             $params[] = $track->id;
1410             if (isset($SESSION->currentgroup[$track->course])) {
1411                 $groupid =  $SESSION->currentgroup[$track->course];
1412             } else {
1413                 // get first groupid
1414                 $groupids = groups_get_all_groups($track->course, $USER->id);
1415                 if ($groupids) {
1416                     reset($groupids);
1417                     $groupid = key($groupids);
1418                     $SESSION->currentgroup[$track->course] = $groupid;
1419                 } else {
1420                     $groupid = 0;
1421                 }
1422                 unset($groupids);
1423             }
1424             $params[] = $groupid;
1425         }
1426         $sql = substr($sql,0,-3); // take off the last OR
1427         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1428         $params[] = $cutoffdate;
1430         if (!$unread = $DB->get_records_sql($sql, $params)) {
1431             $unread = array();
1432         }
1433     } else {
1434         $unread = array();
1435     }
1437     if (empty($unread) and empty($new)) {
1438         return;
1439     }
1441     $strforum = get_string('modulename','forum');
1443     foreach ($forums as $forum) {
1444         $str = '';
1445         $count = 0;
1446         $thisunread = 0;
1447         $showunread = false;
1448         // either we have something from logs, or trackposts, or nothing.
1449         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1450             $count = $new[$forum->id]->count;
1451         }
1452         if (array_key_exists($forum->id,$unread)) {
1453             $thisunread = $unread[$forum->id]->count;
1454             $showunread = true;
1455         }
1456         if ($count > 0 || $thisunread > 0) {
1457             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1458                 $forum->name.'</a></div>';
1459             $str .= '<div class="info"><span class="postsincelogin">';
1460             $str .= get_string('overviewnumpostssince', 'forum', $count)."</span>";
1461             if (!empty($showunread)) {
1462                 $str .= '<div class="unreadposts">'.get_string('overviewnumunread', 'forum', $thisunread).'</div>';
1463             }
1464             $str .= '</div></div>';
1465         }
1466         if (!empty($str)) {
1467             if (!array_key_exists($forum->course,$htmlarray)) {
1468                 $htmlarray[$forum->course] = array();
1469             }
1470             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1471                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1472             }
1473             $htmlarray[$forum->course]['forum'] .= $str;
1474         }
1475     }
1478 /**
1479  * Given a course and a date, prints a summary of all the new
1480  * messages posted in the course since that date
1481  *
1482  * @global object
1483  * @global object
1484  * @global object
1485  * @uses CONTEXT_MODULE
1486  * @uses VISIBLEGROUPS
1487  * @param object $course
1488  * @param bool $viewfullnames capability
1489  * @param int $timestart
1490  * @return bool success
1491  */
1492 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1493     global $CFG, $USER, $DB, $OUTPUT;
1495     // do not use log table if possible, it may be huge and is expensive to join with other tables
1497     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1498                                               d.timestart, d.timeend, d.userid AS duserid,
1499                                               u.firstname, u.lastname, u.email, u.picture
1500                                          FROM {forum_posts} p
1501                                               JOIN {forum_discussions} d ON d.id = p.discussion
1502                                               JOIN {forum} f             ON f.id = d.forum
1503                                               JOIN {user} u              ON u.id = p.userid
1504                                         WHERE p.created > ? AND f.course = ?
1505                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1506          return false;
1507     }
1509     $modinfo = get_fast_modinfo($course);
1511     $groupmodes = array();
1512     $cms    = array();
1514     $strftimerecent = get_string('strftimerecent');
1516     $printposts = array();
1517     foreach ($posts as $post) {
1518         if (!isset($modinfo->instances['forum'][$post->forum])) {
1519             // not visible
1520             continue;
1521         }
1522         $cm = $modinfo->instances['forum'][$post->forum];
1523         if (!$cm->uservisible) {
1524             continue;
1525         }
1526         $context = context_module::instance($cm->id);
1528         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1529             continue;
1530         }
1532         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1533           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1534             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1535                 continue;
1536             }
1537         }
1539         $groupmode = groups_get_activity_groupmode($cm, $course);
1541         if ($groupmode) {
1542             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1543                 // oki (Open discussions have groupid -1)
1544             } else {
1545                 // separate mode
1546                 if (isguestuser()) {
1547                     // shortcut
1548                     continue;
1549                 }
1551                 if (!in_array($post->groupid, $modinfo->get_groups($cm->groupingid))) {
1552                     continue;
1553                 }
1554             }
1555         }
1557         $printposts[] = $post;
1558     }
1559     unset($posts);
1561     if (!$printposts) {
1562         return false;
1563     }
1565     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1566     echo "\n<ul class='unlist'>\n";
1568     foreach ($printposts as $post) {
1569         $subjectclass = empty($post->parent) ? ' bold' : '';
1571         echo '<li><div class="head">'.
1572                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1573                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1574              '</div>';
1575         echo '<div class="info'.$subjectclass.'">';
1576         if (empty($post->parent)) {
1577             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1578         } else {
1579             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1580         }
1581         $post->subject = break_up_long_words(format_string($post->subject, true));
1582         echo $post->subject;
1583         echo "</a>\"</div></li>\n";
1584     }
1586     echo "</ul>\n";
1588     return true;
1591 /**
1592  * Return grade for given user or all users.
1593  *
1594  * @global object
1595  * @global object
1596  * @param object $forum
1597  * @param int $userid optional user id, 0 means all users
1598  * @return array array of grades, false if none
1599  */
1600 function forum_get_user_grades($forum, $userid = 0) {
1601     global $CFG;
1603     require_once($CFG->dirroot.'/rating/lib.php');
1605     $ratingoptions = new stdClass;
1606     $ratingoptions->component = 'mod_forum';
1607     $ratingoptions->ratingarea = 'post';
1609     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1610     $ratingoptions->modulename = 'forum';
1611     $ratingoptions->moduleid   = $forum->id;
1612     $ratingoptions->userid = $userid;
1613     $ratingoptions->aggregationmethod = $forum->assessed;
1614     $ratingoptions->scaleid = $forum->scale;
1615     $ratingoptions->itemtable = 'forum_posts';
1616     $ratingoptions->itemtableusercolumn = 'userid';
1618     $rm = new rating_manager();
1619     return $rm->get_user_grades($ratingoptions);
1622 /**
1623  * Update activity grades
1624  *
1625  * @category grade
1626  * @param object $forum
1627  * @param int $userid specific user only, 0 means all
1628  * @param boolean $nullifnone return null if grade does not exist
1629  * @return void
1630  */
1631 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1632     global $CFG, $DB;
1633     require_once($CFG->libdir.'/gradelib.php');
1635     if (!$forum->assessed) {
1636         forum_grade_item_update($forum);
1638     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1639         forum_grade_item_update($forum, $grades);
1641     } else if ($userid and $nullifnone) {
1642         $grade = new stdClass();
1643         $grade->userid   = $userid;
1644         $grade->rawgrade = NULL;
1645         forum_grade_item_update($forum, $grade);
1647     } else {
1648         forum_grade_item_update($forum);
1649     }
1652 /**
1653  * Update all grades in gradebook.
1654  * @global object
1655  */
1656 function forum_upgrade_grades() {
1657     global $DB;
1659     $sql = "SELECT COUNT('x')
1660               FROM {forum} f, {course_modules} cm, {modules} m
1661              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1662     $count = $DB->count_records_sql($sql);
1664     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1665               FROM {forum} f, {course_modules} cm, {modules} m
1666              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1667     $rs = $DB->get_recordset_sql($sql);
1668     if ($rs->valid()) {
1669         $pbar = new progress_bar('forumupgradegrades', 500, true);
1670         $i=0;
1671         foreach ($rs as $forum) {
1672             $i++;
1673             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1674             forum_update_grades($forum, 0, false);
1675             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1676         }
1677     }
1678     $rs->close();
1681 /**
1682  * Create/update grade item for given forum
1683  *
1684  * @category grade
1685  * @uses GRADE_TYPE_NONE
1686  * @uses GRADE_TYPE_VALUE
1687  * @uses GRADE_TYPE_SCALE
1688  * @param stdClass $forum Forum object with extra cmidnumber
1689  * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
1690  * @return int 0 if ok
1691  */
1692 function forum_grade_item_update($forum, $grades=NULL) {
1693     global $CFG;
1694     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1695         require_once($CFG->libdir.'/gradelib.php');
1696     }
1698     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1700     if (!$forum->assessed or $forum->scale == 0) {
1701         $params['gradetype'] = GRADE_TYPE_NONE;
1703     } else if ($forum->scale > 0) {
1704         $params['gradetype'] = GRADE_TYPE_VALUE;
1705         $params['grademax']  = $forum->scale;
1706         $params['grademin']  = 0;
1708     } else if ($forum->scale < 0) {
1709         $params['gradetype'] = GRADE_TYPE_SCALE;
1710         $params['scaleid']   = -$forum->scale;
1711     }
1713     if ($grades  === 'reset') {
1714         $params['reset'] = true;
1715         $grades = NULL;
1716     }
1718     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1721 /**
1722  * Delete grade item for given forum
1723  *
1724  * @category grade
1725  * @param stdClass $forum Forum object
1726  * @return grade_item
1727  */
1728 function forum_grade_item_delete($forum) {
1729     global $CFG;
1730     require_once($CFG->libdir.'/gradelib.php');
1732     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1736 /**
1737  * This function returns if a scale is being used by one forum
1738  *
1739  * @global object
1740  * @param int $forumid
1741  * @param int $scaleid negative number
1742  * @return bool
1743  */
1744 function forum_scale_used ($forumid,$scaleid) {
1745     global $DB;
1746     $return = false;
1748     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1750     if (!empty($rec) && !empty($scaleid)) {
1751         $return = true;
1752     }
1754     return $return;
1757 /**
1758  * Checks if scale is being used by any instance of forum
1759  *
1760  * This is used to find out if scale used anywhere
1761  *
1762  * @global object
1763  * @param $scaleid int
1764  * @return boolean True if the scale is used by any forum
1765  */
1766 function forum_scale_used_anywhere($scaleid) {
1767     global $DB;
1768     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1769         return true;
1770     } else {
1771         return false;
1772     }
1775 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1777 /**
1778  * Gets a post with all info ready for forum_print_post
1779  * Most of these joins are just to get the forum id
1780  *
1781  * @global object
1782  * @global object
1783  * @param int $postid
1784  * @return mixed array of posts or false
1785  */
1786 function forum_get_post_full($postid) {
1787     global $CFG, $DB;
1789     $allnames = get_all_user_name_fields(true, 'u');
1790     return $DB->get_record_sql("SELECT p.*, d.forum, $allnames, u.email, u.picture, u.imagealt
1791                              FROM {forum_posts} p
1792                                   JOIN {forum_discussions} d ON p.discussion = d.id
1793                                   LEFT JOIN {user} u ON p.userid = u.id
1794                             WHERE p.id = ?", array($postid));
1797 /**
1798  * Gets posts with all info ready for forum_print_post
1799  * We pass forumid in because we always know it so no need to make a
1800  * complicated join to find it out.
1801  *
1802  * @global object
1803  * @global object
1804  * @return mixed array of posts or false
1805  */
1806 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1807     global $CFG, $DB;
1809     $allnames = get_all_user_name_fields(true, 'u');
1810     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, $allnames, u.email, u.picture, u.imagealt
1811                               FROM {forum_posts} p
1812                          LEFT JOIN {user} u ON p.userid = u.id
1813                              WHERE p.discussion = ?
1814                                AND p.parent > 0 $sort", array($discussion));
1817 /**
1818  * Gets all posts in discussion including top parent.
1819  *
1820  * @global object
1821  * @global object
1822  * @global object
1823  * @param int $discussionid
1824  * @param string $sort
1825  * @param bool $tracking does user track the forum?
1826  * @return array of posts
1827  */
1828 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1829     global $CFG, $DB, $USER;
1831     $tr_sel  = "";
1832     $tr_join = "";
1833     $params = array();
1835     if ($tracking) {
1836         $now = time();
1837         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1838         $tr_sel  = ", fr.id AS postread";
1839         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1840         $params[] = $USER->id;
1841     }
1843     $allnames = get_all_user_name_fields(true, 'u');
1844     $params[] = $discussionid;
1845     if (!$posts = $DB->get_records_sql("SELECT p.*, $allnames, u.email, u.picture, u.imagealt $tr_sel
1846                                      FROM {forum_posts} p
1847                                           LEFT JOIN {user} u ON p.userid = u.id
1848                                           $tr_join
1849                                     WHERE p.discussion = ?
1850                                  ORDER BY $sort", $params)) {
1851         return array();
1852     }
1854     foreach ($posts as $pid=>$p) {
1855         if ($tracking) {
1856             if (forum_tp_is_post_old($p)) {
1857                  $posts[$pid]->postread = true;
1858             }
1859         }
1860         if (!$p->parent) {
1861             continue;
1862         }
1863         if (!isset($posts[$p->parent])) {
1864             continue; // parent does not exist??
1865         }
1866         if (!isset($posts[$p->parent]->children)) {
1867             $posts[$p->parent]->children = array();
1868         }
1869         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1870     }
1872     return $posts;
1875 /**
1876  * Gets posts with all info ready for forum_print_post
1877  * We pass forumid in because we always know it so no need to make a
1878  * complicated join to find it out.
1879  *
1880  * @global object
1881  * @global object
1882  * @param int $parent
1883  * @param int $forumid
1884  * @return array
1885  */
1886 function forum_get_child_posts($parent, $forumid) {
1887     global $CFG, $DB;
1889     $allnames = get_all_user_name_fields(true, 'u');
1890     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, $allnames, u.email, u.picture, u.imagealt
1891                               FROM {forum_posts} p
1892                          LEFT JOIN {user} u ON p.userid = u.id
1893                              WHERE p.parent = ?
1894                           ORDER BY p.created ASC", array($parent));
1897 /**
1898  * An array of forum objects that the user is allowed to read/search through.
1899  *
1900  * @global object
1901  * @global object
1902  * @global object
1903  * @param int $userid
1904  * @param int $courseid if 0, we look for forums throughout the whole site.
1905  * @return array of forum objects, or false if no matches
1906  *         Forum objects have the following attributes:
1907  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1908  *         viewhiddentimedposts
1909  */
1910 function forum_get_readable_forums($userid, $courseid=0) {
1912     global $CFG, $DB, $USER;
1913     require_once($CFG->dirroot.'/course/lib.php');
1915     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1916         print_error('notinstalled', 'forum');
1917     }
1919     if ($courseid) {
1920         $courses = $DB->get_records('course', array('id' => $courseid));
1921     } else {
1922         // If no course is specified, then the user can see SITE + his courses.
1923         $courses1 = $DB->get_records('course', array('id' => SITEID));
1924         $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
1925         $courses = array_merge($courses1, $courses2);
1926     }
1927     if (!$courses) {
1928         return array();
1929     }
1931     $readableforums = array();
1933     foreach ($courses as $course) {
1935         $modinfo = get_fast_modinfo($course);
1937         if (empty($modinfo->instances['forum'])) {
1938             // hmm, no forums?
1939             continue;
1940         }
1942         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1944         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1945             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1946                 continue;
1947             }
1948             $context = context_module::instance($cm->id);
1949             $forum = $courseforums[$forumid];
1950             $forum->context = $context;
1951             $forum->cm = $cm;
1953             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1954                 continue;
1955             }
1957          /// group access
1958             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1960                 $forum->onlygroups = $modinfo->get_groups($cm->groupingid);
1961                 $forum->onlygroups[] = -1;
1962             }
1964         /// hidden timed discussions
1965             $forum->viewhiddentimedposts = true;
1966             if (!empty($CFG->forum_enabletimedposts)) {
1967                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1968                     $forum->viewhiddentimedposts = false;
1969                 }
1970             }
1972         /// qanda access
1973             if ($forum->type == 'qanda'
1974                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1976                 // We need to check whether the user has posted in the qanda forum.
1977                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1978                                                     // the user is allowed to see in this forum.
1979                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1980                     foreach ($discussionspostedin as $d) {
1981                         $forum->onlydiscussions[] = $d->id;
1982                     }
1983                 }
1984             }
1986             $readableforums[$forum->id] = $forum;
1987         }
1989         unset($modinfo);
1991     } // End foreach $courses
1993     return $readableforums;
1996 /**
1997  * Returns a list of posts found using an array of search terms.
1998  *
1999  * @global object
2000  * @global object
2001  * @global object
2002  * @param array $searchterms array of search terms, e.g. word +word -word
2003  * @param int $courseid if 0, we search through the whole site
2004  * @param int $limitfrom
2005  * @param int $limitnum
2006  * @param int &$totalcount
2007  * @param string $extrasql
2008  * @return array|bool Array of posts found or false
2009  */
2010 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
2011                             &$totalcount, $extrasql='') {
2012     global $CFG, $DB, $USER;
2013     require_once($CFG->libdir.'/searchlib.php');
2015     $forums = forum_get_readable_forums($USER->id, $courseid);
2017     if (count($forums) == 0) {
2018         $totalcount = 0;
2019         return false;
2020     }
2022     $now = round(time(), -2); // db friendly
2024     $fullaccess = array();
2025     $where = array();
2026     $params = array();
2028     foreach ($forums as $forumid => $forum) {
2029         $select = array();
2031         if (!$forum->viewhiddentimedposts) {
2032             $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
2033             $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
2034         }
2036         $cm = $forum->cm;
2037         $context = $forum->context;
2039         if ($forum->type == 'qanda'
2040             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
2041             if (!empty($forum->onlydiscussions)) {
2042                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
2043                 $params = array_merge($params, $discussionid_params);
2044                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
2045             } else {
2046                 $select[] = "p.parent = 0";
2047             }
2048         }
2050         if (!empty($forum->onlygroups)) {
2051             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
2052             $params = array_merge($params, $groupid_params);
2053             $select[] = "d.groupid $groupid_sql";
2054         }
2056         if ($select) {
2057             $selects = implode(" AND ", $select);
2058             $where[] = "(d.forum = :forum{$forumid} AND $selects)";
2059             $params['forum'.$forumid] = $forumid;
2060         } else {
2061             $fullaccess[] = $forumid;
2062         }
2063     }
2065     if ($fullaccess) {
2066         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
2067         $params = array_merge($params, $fullid_params);
2068         $where[] = "(d.forum $fullid_sql)";
2069     }
2071     $selectdiscussion = "(".implode(" OR ", $where).")";
2073     $messagesearch = '';
2074     $searchstring = '';
2076     // Need to concat these back together for parser to work.
2077     foreach($searchterms as $searchterm){
2078         if ($searchstring != '') {
2079             $searchstring .= ' ';
2080         }
2081         $searchstring .= $searchterm;
2082     }
2084     // We need to allow quoted strings for the search. The quotes *should* be stripped
2085     // by the parser, but this should be examined carefully for security implications.
2086     $searchstring = str_replace("\\\"","\"",$searchstring);
2087     $parser = new search_parser();
2088     $lexer = new search_lexer($parser);
2090     if ($lexer->parse($searchstring)) {
2091         $parsearray = $parser->get_parsed_array();
2092     // Experimental feature under 1.8! MDL-8830
2093     // Use alternative text searches if defined
2094     // This feature only works under mysql until properly implemented for other DBs
2095     // Requires manual creation of text index for forum_posts before enabling it:
2096     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2097     // Experimental feature under 1.8! MDL-8830
2098         if (!empty($CFG->forum_usetextsearches)) {
2099             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2100                                                  'p.userid', 'u.id', 'u.firstname',
2101                                                  'u.lastname', 'p.modified', 'd.forum');
2102         } else {
2103             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2104                                                  'p.userid', 'u.id', 'u.firstname',
2105                                                  'u.lastname', 'p.modified', 'd.forum');
2106         }
2107         $params = array_merge($params, $msparams);
2108     }
2110     $fromsql = "{forum_posts} p,
2111                   {forum_discussions} d,
2112                   {user} u";
2114     $selectsql = " $messagesearch
2115                AND p.discussion = d.id
2116                AND p.userid = u.id
2117                AND $selectdiscussion
2118                    $extrasql";
2120     $countsql = "SELECT COUNT(*)
2121                    FROM $fromsql
2122                   WHERE $selectsql";
2124     $allnames = get_all_user_name_fields(true, 'u');
2125     $searchsql = "SELECT p.*,
2126                          d.forum,
2127                          $allnames,
2128                          u.email,
2129                          u.picture,
2130                          u.imagealt
2131                     FROM $fromsql
2132                    WHERE $selectsql
2133                 ORDER BY p.modified DESC";
2135     $totalcount = $DB->count_records_sql($countsql, $params);
2137     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2140 /**
2141  * Returns a list of ratings for a particular post - sorted.
2142  *
2143  * TODO: Check if this function is actually used anywhere.
2144  * Up until the fix for MDL-27471 this function wasn't even returning.
2145  *
2146  * @param stdClass $context
2147  * @param int $postid
2148  * @param string $sort
2149  * @return array Array of ratings or false
2150  */
2151 function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
2152     $options = new stdClass;
2153     $options->context = $context;
2154     $options->component = 'mod_forum';
2155     $options->ratingarea = 'post';
2156     $options->itemid = $postid;
2157     $options->sort = "ORDER BY $sort";
2159     $rm = new rating_manager();
2160     return $rm->get_all_ratings_for_item($options);
2163 /**
2164  * Returns a list of all new posts that have not been mailed yet
2165  *
2166  * @param int $starttime posts created after this time
2167  * @param int $endtime posts created before this
2168  * @param int $now used for timed discussions only
2169  * @return array
2170  */
2171 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2172     global $CFG, $DB;
2174     $params = array();
2175     $params['mailed'] = FORUM_MAILED_PENDING;
2176     $params['ptimestart'] = $starttime;
2177     $params['ptimeend'] = $endtime;
2178     $params['mailnow'] = 1;
2180     if (!empty($CFG->forum_enabletimedposts)) {
2181         if (empty($now)) {
2182             $now = time();
2183         }
2184         $timedsql = "AND (d.timestart < :dtimestart AND (d.timeend = 0 OR d.timeend > :dtimeend))";
2185         $params['dtimestart'] = $now;
2186         $params['dtimeend'] = $now;
2187     } else {
2188         $timedsql = "";
2189     }
2191     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2192                                  FROM {forum_posts} p
2193                                  JOIN {forum_discussions} d ON d.id = p.discussion
2194                                  WHERE p.mailed = :mailed
2195                                  AND p.created >= :ptimestart
2196                                  AND (p.created < :ptimeend OR p.mailnow = :mailnow)
2197                                  $timedsql
2198                                  ORDER BY p.modified ASC", $params);
2201 /**
2202  * Marks posts before a certain time as being mailed already
2203  *
2204  * @global object
2205  * @global object
2206  * @param int $endtime
2207  * @param int $now Defaults to time()
2208  * @return bool
2209  */
2210 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2211     global $CFG, $DB;
2213     if (empty($now)) {
2214         $now = time();
2215     }
2217     $params = array();
2218     $params['mailedsuccess'] = FORUM_MAILED_SUCCESS;
2219     $params['now'] = $now;
2220     $params['endtime'] = $endtime;
2221     $params['mailnow'] = 1;
2222     $params['mailedpending'] = FORUM_MAILED_PENDING;
2224     if (empty($CFG->forum_enabletimedposts)) {
2225         return $DB->execute("UPDATE {forum_posts}
2226                              SET mailed = :mailedsuccess
2227                              WHERE (created < :endtime OR mailnow = :mailnow)
2228                              AND mailed = :mailedpending", $params);
2229     } else {
2230         return $DB->execute("UPDATE {forum_posts}
2231                              SET mailed = :mailedsuccess
2232                              WHERE discussion NOT IN (SELECT d.id
2233                                                       FROM {forum_discussions} d
2234                                                       WHERE d.timestart > :now)
2235                              AND (created < :endtime OR mailnow = :mailnow)
2236                              AND mailed = :mailedpending", $params);
2237     }
2240 /**
2241  * Get all the posts for a user in a forum suitable for forum_print_post
2242  *
2243  * @global object
2244  * @global object
2245  * @uses CONTEXT_MODULE
2246  * @return array
2247  */
2248 function forum_get_user_posts($forumid, $userid) {
2249     global $CFG, $DB;
2251     $timedsql = "";
2252     $params = array($forumid, $userid);
2254     if (!empty($CFG->forum_enabletimedposts)) {
2255         $cm = get_coursemodule_from_instance('forum', $forumid);
2256         if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2257             $now = time();
2258             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2259             $params[] = $now;
2260             $params[] = $now;
2261         }
2262     }
2264     $allnames = get_all_user_name_fields(true, 'u');
2265     return $DB->get_records_sql("SELECT p.*, d.forum, $allnames, u.email, u.picture, u.imagealt
2266                               FROM {forum} f
2267                                    JOIN {forum_discussions} d ON d.forum = f.id
2268                                    JOIN {forum_posts} p       ON p.discussion = d.id
2269                                    JOIN {user} u              ON u.id = p.userid
2270                              WHERE f.id = ?
2271                                    AND p.userid = ?
2272                                    $timedsql
2273                           ORDER BY p.modified ASC", $params);
2276 /**
2277  * Get all the discussions user participated in
2278  *
2279  * @global object
2280  * @global object
2281  * @uses CONTEXT_MODULE
2282  * @param int $forumid
2283  * @param int $userid
2284  * @return array Array or false
2285  */
2286 function forum_get_user_involved_discussions($forumid, $userid) {
2287     global $CFG, $DB;
2289     $timedsql = "";
2290     $params = array($forumid, $userid);
2291     if (!empty($CFG->forum_enabletimedposts)) {
2292         $cm = get_coursemodule_from_instance('forum', $forumid);
2293         if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2294             $now = time();
2295             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2296             $params[] = $now;
2297             $params[] = $now;
2298         }
2299     }
2301     return $DB->get_records_sql("SELECT DISTINCT d.*
2302                               FROM {forum} f
2303                                    JOIN {forum_discussions} d ON d.forum = f.id
2304                                    JOIN {forum_posts} p       ON p.discussion = d.id
2305                              WHERE f.id = ?
2306                                    AND p.userid = ?
2307                                    $timedsql", $params);
2310 /**
2311  * Get all the posts for a user in a forum suitable for forum_print_post
2312  *
2313  * @global object
2314  * @global object
2315  * @param int $forumid
2316  * @param int $userid
2317  * @return array of counts or false
2318  */
2319 function forum_count_user_posts($forumid, $userid) {
2320     global $CFG, $DB;
2322     $timedsql = "";
2323     $params = array($forumid, $userid);
2324     if (!empty($CFG->forum_enabletimedposts)) {
2325         $cm = get_coursemodule_from_instance('forum', $forumid);
2326         if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2327             $now = time();
2328             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2329             $params[] = $now;
2330             $params[] = $now;
2331         }
2332     }
2334     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2335                              FROM {forum} f
2336                                   JOIN {forum_discussions} d ON d.forum = f.id
2337                                   JOIN {forum_posts} p       ON p.discussion = d.id
2338                                   JOIN {user} u              ON u.id = p.userid
2339                             WHERE f.id = ?
2340                                   AND p.userid = ?
2341                                   $timedsql", $params);
2344 /**
2345  * Given a log entry, return the forum post details for it.
2346  *
2347  * @global object
2348  * @global object
2349  * @param object $log
2350  * @return array|null
2351  */
2352 function forum_get_post_from_log($log) {
2353     global $CFG, $DB;
2355     $allnames = get_all_user_name_fields(true, 'u');
2356     if ($log->action == "add post") {
2358         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid, $allnames, u.email, u.picture
2359                                  FROM {forum_discussions} d,
2360                                       {forum_posts} p,
2361                                       {forum} f,
2362                                       {user} u
2363                                 WHERE p.id = ?
2364                                   AND d.id = p.discussion
2365                                   AND p.userid = u.id
2366                                   AND u.deleted <> '1'
2367                                   AND f.id = d.forum", array($log->info));
2370     } else if ($log->action == "add discussion") {
2372         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid, $allnames, u.email, u.picture
2373                                  FROM {forum_discussions} d,
2374                                       {forum_posts} p,
2375                                       {forum} f,
2376                                       {user} u
2377                                 WHERE d.id = ?
2378                                   AND d.firstpost = p.id
2379                                   AND p.userid = u.id
2380                                   AND u.deleted <> '1'
2381                                   AND f.id = d.forum", array($log->info));
2382     }
2383     return NULL;
2386 /**
2387  * Given a discussion id, return the first post from the discussion
2388  *
2389  * @global object
2390  * @global object
2391  * @param int $dicsussionid
2392  * @return array
2393  */
2394 function forum_get_firstpost_from_discussion($discussionid) {
2395     global $CFG, $DB;
2397     return $DB->get_record_sql("SELECT p.*
2398                              FROM {forum_discussions} d,
2399                                   {forum_posts} p
2400                             WHERE d.id = ?
2401                               AND d.firstpost = p.id ", array($discussionid));
2404 /**
2405  * Returns an array of counts of replies to each discussion
2406  *
2407  * @global object
2408  * @global object
2409  * @param int $forumid
2410  * @param string $forumsort
2411  * @param int $limit
2412  * @param int $page
2413  * @param int $perpage
2414  * @return array
2415  */
2416 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2417     global $CFG, $DB;
2419     if ($limit > 0) {
2420         $limitfrom = 0;
2421         $limitnum  = $limit;
2422     } else if ($page != -1) {
2423         $limitfrom = $page*$perpage;
2424         $limitnum  = $perpage;
2425     } else {
2426         $limitfrom = 0;
2427         $limitnum  = 0;
2428     }
2430     if ($forumsort == "") {
2431         $orderby = "";
2432         $groupby = "";
2434     } else {
2435         $orderby = "ORDER BY $forumsort";
2436         $groupby = ", ".strtolower($forumsort);
2437         $groupby = str_replace('desc', '', $groupby);
2438         $groupby = str_replace('asc', '', $groupby);
2439     }
2441     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2442         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2443                   FROM {forum_posts} p
2444                        JOIN {forum_discussions} d ON p.discussion = d.id
2445                  WHERE p.parent > 0 AND d.forum = ?
2446               GROUP BY p.discussion";
2447         return $DB->get_records_sql($sql, array($forumid));
2449     } else {
2450         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2451                   FROM {forum_posts} p
2452                        JOIN {forum_discussions} d ON p.discussion = d.id
2453                  WHERE d.forum = ?
2454               GROUP BY p.discussion $groupby
2455               $orderby";
2456         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2457     }
2460 /**
2461  * @global object
2462  * @global object
2463  * @global object
2464  * @staticvar array $cache
2465  * @param object $forum
2466  * @param object $cm
2467  * @param object $course
2468  * @return mixed
2469  */
2470 function forum_count_discussions($forum, $cm, $course) {
2471     global $CFG, $DB, $USER;
2473     static $cache = array();
2475     $now = round(time(), -2); // db cache friendliness
2477     $params = array($course->id);
2479     if (!isset($cache[$course->id])) {
2480         if (!empty($CFG->forum_enabletimedposts)) {
2481             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2482             $params[] = $now;
2483             $params[] = $now;
2484         } else {
2485             $timedsql = "";
2486         }
2488         $sql = "SELECT f.id, COUNT(d.id) as dcount
2489                   FROM {forum} f
2490                        JOIN {forum_discussions} d ON d.forum = f.id
2491                  WHERE f.course = ?
2492                        $timedsql
2493               GROUP BY f.id";
2495         if ($counts = $DB->get_records_sql($sql, $params)) {
2496             foreach ($counts as $count) {
2497                 $counts[$count->id] = $count->dcount;
2498             }
2499             $cache[$course->id] = $counts;
2500         } else {
2501             $cache[$course->id] = array();
2502         }
2503     }
2505     if (empty($cache[$course->id][$forum->id])) {
2506         return 0;
2507     }
2509     $groupmode = groups_get_activity_groupmode($cm, $course);
2511     if ($groupmode != SEPARATEGROUPS) {
2512         return $cache[$course->id][$forum->id];
2513     }
2515     if (has_capability('moodle/site:accessallgroups', context_module::instance($cm->id))) {
2516         return $cache[$course->id][$forum->id];
2517     }
2519     require_once($CFG->dirroot.'/course/lib.php');
2521     $modinfo = get_fast_modinfo($course);
2523     $mygroups = $modinfo->get_groups($cm->groupingid);
2525     // add all groups posts
2526     $mygroups[-1] = -1;
2528     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2529     $params[] = $forum->id;
2531     if (!empty($CFG->forum_enabletimedposts)) {
2532         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2533         $params[] = $now;
2534         $params[] = $now;
2535     } else {
2536         $timedsql = "";
2537     }
2539     $sql = "SELECT COUNT(d.id)
2540               FROM {forum_discussions} d
2541              WHERE d.groupid $mygroups_sql AND d.forum = ?
2542                    $timedsql";
2544     return $DB->get_field_sql($sql, $params);
2547 /**
2548  * How many posts by other users are unrated by a given user in the given discussion?
2549  *
2550  * TODO: Is this function still used anywhere?
2551  *
2552  * @param int $discussionid
2553  * @param int $userid
2554  * @return mixed
2555  */
2556 function forum_count_unrated_posts($discussionid, $userid) {
2557     global $CFG, $DB;
2559     $sql = "SELECT COUNT(*) as num
2560               FROM {forum_posts}
2561              WHERE parent > 0
2562                AND discussion = :discussionid
2563                AND userid <> :userid";
2564     $params = array('discussionid' => $discussionid, 'userid' => $userid);
2565     $posts = $DB->get_record_sql($sql, $params);
2566     if ($posts) {
2567         $sql = "SELECT count(*) as num
2568                   FROM {forum_posts} p,
2569                        {rating} r
2570                  WHERE p.discussion = :discussionid AND
2571                        p.id = r.itemid AND
2572                        r.userid = userid AND
2573                        r.component = 'mod_forum' AND
2574                        r.ratingarea = 'post'";
2575         $rated = $DB->get_record_sql($sql, $params);
2576         if ($rated) {
2577             if ($posts->num > $rated->num) {
2578                 return $posts->num - $rated->num;
2579             } else {
2580                 return 0;    // Just in case there was a counting error
2581             }
2582         } else {
2583             return $posts->num;
2584         }
2585     } else {
2586         return 0;
2587     }
2590 /**
2591  * Get all discussions in a forum
2592  *
2593  * @global object
2594  * @global object
2595  * @global object
2596  * @uses CONTEXT_MODULE
2597  * @uses VISIBLEGROUPS
2598  * @param object $cm
2599  * @param string $forumsort
2600  * @param bool $fullpost
2601  * @param int $unused
2602  * @param int $limit
2603  * @param bool $userlastmodified
2604  * @param int $page
2605  * @param int $perpage
2606  * @return array
2607  */
2608 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2609     global $CFG, $DB, $USER;
2611     $timelimit = '';
2613     $now = round(time(), -2);
2614     $params = array($cm->instance);
2616     $modcontext = context_module::instance($cm->id);
2618     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2619         return array();
2620     }
2622     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2624         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2625             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2626             $params[] = $now;
2627             $params[] = $now;
2628             if (isloggedin()) {
2629                 $timelimit .= " OR d.userid = ?";
2630                 $params[] = $USER->id;
2631             }
2632             $timelimit .= ")";
2633         }
2634     }
2636     if ($limit > 0) {
2637         $limitfrom = 0;
2638         $limitnum  = $limit;
2639     } else if ($page != -1) {
2640         $limitfrom = $page*$perpage;
2641         $limitnum  = $perpage;
2642     } else {
2643         $limitfrom = 0;
2644         $limitnum  = 0;
2645     }
2647     $groupmode    = groups_get_activity_groupmode($cm);
2648     $currentgroup = groups_get_activity_group($cm);
2650     if ($groupmode) {
2651         if (empty($modcontext)) {
2652             $modcontext = context_module::instance($cm->id);
2653         }
2655         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2656             if ($currentgroup) {
2657                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2658                 $params[] = $currentgroup;
2659             } else {
2660                 $groupselect = "";
2661             }
2663         } else {
2664             //seprate groups without access all
2665             if ($currentgroup) {
2666                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2667                 $params[] = $currentgroup;
2668             } else {
2669                 $groupselect = "AND d.groupid = -1";
2670             }
2671         }
2672     } else {
2673         $groupselect = "";
2674     }
2677     if (empty($forumsort)) {
2678         $forumsort = "d.timemodified DESC";
2679     }
2680     if (empty($fullpost)) {
2681         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2682     } else {
2683         $postdata = "p.*";
2684     }
2686     if (empty($userlastmodified)) {  // We don't need to know this
2687         $umfields = "";
2688         $umtable  = "";
2689     } else {
2690         $umfields = '';
2691         $umnames = get_all_user_name_fields();
2692         foreach ($umnames as $umname) {
2693             $umfields .= ', um.' . $umname . ' AS um' . $umname;
2694         }
2695         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2696     }
2698     $allnames = get_all_user_name_fields(true, 'u');
2699     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend, $allnames,
2700                    u.email, u.picture, u.imagealt $umfields
2701               FROM {forum_discussions} d
2702                    JOIN {forum_posts} p ON p.discussion = d.id
2703                    JOIN {user} u ON p.userid = u.id
2704                    $umtable
2705              WHERE d.forum = ? AND p.parent = 0
2706                    $timelimit $groupselect
2707           ORDER BY $forumsort";
2708     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2711 /**
2712  *
2713  * @global object
2714  * @global object
2715  * @global object
2716  * @uses CONTEXT_MODULE
2717  * @uses VISIBLEGROUPS
2718  * @param object $cm
2719  * @return array
2720  */
2721 function forum_get_discussions_unread($cm) {
2722     global $CFG, $DB, $USER;
2724     $now = round(time(), -2);
2725     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2727     $params = array();
2728     $groupmode    = groups_get_activity_groupmode($cm);
2729     $currentgroup = groups_get_activity_group($cm);
2731     if ($groupmode) {
2732         $modcontext = context_module::instance($cm->id);
2734         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2735             if ($currentgroup) {
2736                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2737                 $params['currentgroup'] = $currentgroup;
2738             } else {
2739                 $groupselect = "";
2740             }
2742         } else {
2743             //separate groups without access all
2744             if ($currentgroup) {
2745                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2746                 $params['currentgroup'] = $currentgroup;
2747             } else {
2748                 $groupselect = "AND d.groupid = -1";
2749             }
2750         }
2751     } else {
2752         $groupselect = "";
2753     }
2755     if (!empty($CFG->forum_enabletimedposts)) {
2756         $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2757         $params['now1'] = $now;
2758         $params['now2'] = $now;
2759     } else {
2760         $timedsql = "";
2761     }
2763     $sql = "SELECT d.id, COUNT(p.id) AS unread
2764               FROM {forum_discussions} d
2765                    JOIN {forum_posts} p     ON p.discussion = d.id
2766                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2767              WHERE d.forum = {$cm->instance}
2768                    AND p.modified >= :cutoffdate AND r.id is NULL
2769                    $groupselect
2770                    $timedsql
2771           GROUP BY d.id";
2772     $params['cutoffdate'] = $cutoffdate;
2774     if ($unreads = $DB->get_records_sql($sql, $params)) {
2775         foreach ($unreads as $unread) {
2776             $unreads[$unread->id] = $unread->unread;
2777         }
2778         return $unreads;
2779     } else {
2780         return array();
2781     }
2784 /**
2785  * @global object
2786  * @global object
2787  * @global object
2788  * @uses CONEXT_MODULE
2789  * @uses VISIBLEGROUPS
2790  * @param object $cm
2791  * @return array
2792  */
2793 function forum_get_discussions_count($cm) {
2794     global $CFG, $DB, $USER;
2796     $now = round(time(), -2);
2797     $params = array($cm->instance);
2798     $groupmode    = groups_get_activity_groupmode($cm);
2799     $currentgroup = groups_get_activity_group($cm);
2801     if ($groupmode) {
2802         $modcontext = context_module::instance($cm->id);
2804         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2805             if ($currentgroup) {
2806                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2807                 $params[] = $currentgroup;
2808             } else {
2809                 $groupselect = "";
2810             }
2812         } else {
2813             //seprate groups without access all
2814             if ($currentgroup) {
2815                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2816                 $params[] = $currentgroup;
2817             } else {
2818                 $groupselect = "AND d.groupid = -1";
2819             }
2820         }
2821     } else {
2822         $groupselect = "";
2823     }
2825     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2827     $timelimit = "";
2829     if (!empty($CFG->forum_enabletimedposts)) {
2831         $modcontext = context_module::instance($cm->id);
2833         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2834             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2835             $params[] = $now;
2836             $params[] = $now;
2837             if (isloggedin()) {
2838                 $timelimit .= " OR d.userid = ?";
2839                 $params[] = $USER->id;
2840             }
2841             $timelimit .= ")";
2842         }
2843     }
2845     $sql = "SELECT COUNT(d.id)
2846               FROM {forum_discussions} d
2847                    JOIN {forum_posts} p ON p.discussion = d.id
2848              WHERE d.forum = ? AND p.parent = 0
2849                    $groupselect $timelimit";
2851     return $DB->get_field_sql($sql, $params);
2855 /**
2856  * Get all discussions started by a particular user in a course (or group)
2857  * This function no longer used ...
2858  *
2859  * @todo Remove this function if no longer used
2860  * @global object
2861  * @global object
2862  * @param int $courseid
2863  * @param int $userid
2864  * @param int $groupid
2865  * @return array
2866  */
2867 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2868     global $CFG, $DB;
2869     $params = array($courseid, $userid);
2870     if ($groupid) {
2871         $groupselect = " AND d.groupid = ? ";
2872         $params[] = $groupid;
2873     } else  {
2874         $groupselect = "";
2875     }
2877     $allnames = get_all_user_name_fields(true, 'u');
2878     return $DB->get_records_sql("SELECT p.*, d.groupid, $allnames, u.email, u.picture, u.imagealt,
2879                                    f.type as forumtype, f.name as forumname, f.id as forumid
2880                               FROM {forum_discussions} d,
2881                                    {forum_posts} p,
2882                                    {user} u,
2883                                    {forum} f
2884                              WHERE d.course = ?
2885                                AND p.discussion = d.id
2886                                AND p.parent = 0
2887                                AND p.userid = u.id
2888                                AND u.id = ?
2889                                AND d.forum = f.id $groupselect
2890                           ORDER BY p.created DESC", $params);
2893 /**
2894  * Get the list of potential subscribers to a forum.
2895  *
2896  * @param object $forumcontext the forum context.
2897  * @param integer $groupid the id of a group, or 0 for all groups.
2898  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2899  * @param string $sort sort order. As for get_users_by_capability.
2900  * @return array list of users.
2901  */
2902 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort = '') {
2903     global $DB;
2905     // only active enrolled users or everybody on the frontpage
2906     list($esql, $params) = get_enrolled_sql($forumcontext, 'mod/forum:allowforcesubscribe', $groupid, true);
2907     if (!$sort) {
2908         list($sort, $sortparams) = users_order_by_sql('u');
2909         $params = array_merge($params, $sortparams);
2910     }
2912     $sql = "SELECT $fields
2913               FROM {user} u
2914               JOIN ($esql) je ON je.id = u.id
2915           ORDER BY $sort";
2917     return $DB->get_records_sql($sql, $params);
2920 /**
2921  * Returns list of user objects that are subscribed to this forum
2922  *
2923  * @global object
2924  * @global object
2925  * @param object $course the course
2926  * @param forum $forum the forum
2927  * @param integer $groupid group id, or 0 for all.
2928  * @param object $context the forum context, to save re-fetching it where possible.
2929  * @param string $fields requested user fields (with "u." table prefix)
2930  * @return array list of users.
2931  */
2932 function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
2933     global $CFG, $DB;
2935     $allnames = get_all_user_name_fields(true, 'u');
2936     if (empty($fields)) {
2937         $fields ="u.id,
2938                   u.username,
2939                   $allnames,
2940                   u.maildisplay,
2941                   u.mailformat,
2942                   u.maildigest,
2943                   u.imagealt,
2944                   u.email,
2945                   u.emailstop,
2946                   u.city,
2947                   u.country,
2948                   u.lastaccess,
2949                   u.lastlogin,
2950                   u.picture,
2951                   u.timezone,
2952                   u.theme,
2953                   u.lang,
2954                   u.trackforums,
2955                   u.mnethostid";
2956     }
2958     if (empty($context)) {
2959         $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2960         $context = context_module::instance($cm->id);
2961     }
2963     if (forum_is_forcesubscribed($forum)) {
2964         $results = forum_get_potential_subscribers($context, $groupid, $fields, "u.email ASC");
2966     } else {
2967         // only active enrolled users or everybody on the frontpage
2968         list($esql, $params) = get_enrolled_sql($context, '', $groupid, true);
2969         $params['forumid'] = $forum->id;
2970         $results = $DB->get_records_sql("SELECT $fields
2971                                            FROM {user} u
2972                                            JOIN ($esql) je ON je.id = u.id
2973                                            JOIN {forum_subscriptions} s ON s.userid = u.id
2974                                           WHERE s.forum = :forumid
2975                                        ORDER BY u.email ASC", $params);
2976     }
2978     // Guest user should never be subscribed to a forum.
2979     unset($results[$CFG->siteguest]);
2981     return $results;
2986 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2989 /**
2990  * @global object
2991  * @global object
2992  * @param int $courseid
2993  * @param string $type
2994  */
2995 function forum_get_course_forum($courseid, $type) {
2996 // How to set up special 1-per-course forums
2997     global $CFG, $DB, $OUTPUT, $USER;
2999     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
3000         // There should always only be ONE, but with the right combination of
3001         // errors there might be more.  In this case, just return the oldest one (lowest ID).
3002         foreach ($forums as $forum) {
3003             return $forum;   // ie the first one
3004         }
3005     }
3007     // Doesn't exist, so create one now.
3008     $forum = new stdClass();
3009     $forum->course = $courseid;
3010     $forum->type = "$type";
3011     if (!empty($USER->htmleditor)) {
3012         $forum->introformat = $USER->htmleditor;
3013     }
3014     switch ($forum->type) {
3015         case "news":
3016             $forum->name  = get_string("namenews", "forum");
3017             $forum->intro = get_string("intronews", "forum");
3018             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
3019             $forum->assessed = 0;
3020             if ($courseid == SITEID) {
3021                 $forum->name  = get_string("sitenews");
3022                 $forum->forcesubscribe = 0;
3023             }
3024             break;
3025         case "social":
3026             $forum->name  = get_string("namesocial", "forum");
3027             $forum->intro = get_string("introsocial", "forum");
3028             $forum->assessed = 0;
3029             $forum->forcesubscribe = 0;
3030             break;
3031         case "blog":
3032             $forum->name = get_string('blogforum', 'forum');
3033             $forum->intro = get_string('introblog', 'forum');
3034             $forum->assessed = 0;
3035             $forum->forcesubscribe = 0;
3036             break;
3037         default:
3038             echo $OUTPUT->notification("That forum type doesn't exist!");
3039             return false;
3040             break;
3041     }
3043     $forum->timemodified = time();
3044     $forum->id = $DB->insert_record("forum", $forum);
3046     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
3047         echo $OUTPUT->notification("Could not find forum module!!");
3048         return false;
3049     }
3050     $mod = new stdClass();
3051     $mod->course = $courseid;
3052     $mod->module = $module->id;
3053     $mod->instance = $forum->id;
3054     $mod->section = 0;
3055     include_once("$CFG->dirroot/course/lib.php");
3056     if (! $mod->coursemodule = add_course_module($mod) ) {
3057         echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
3058         return false;
3059     }
3060     $sectionid = course_add_cm_to_section($courseid, $mod->coursemodule, 0);
3061     return $DB->get_record("forum", array("id" => "$forum->id"));
3065 /**
3066  * Given the data about a posting, builds up the HTML to display it and
3067  * returns the HTML in a string.  This is designed for sending via HTML email.
3068  *
3069  * @global object
3070  * @param object $course
3071  * @param object $cm
3072  * @param object $forum
3073  * @param object $discussion
3074  * @param object $post
3075  * @param object $userform
3076  * @param object $userto
3077  * @param bool $ownpost
3078  * @param bool $reply
3079  * @param bool $link
3080  * @param bool $rate
3081  * @param string $footer
3082  * @return string
3083  */
3084 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
3085                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
3087     global $CFG, $OUTPUT;
3089     $modcontext = context_module::instance($cm->id);
3091     if (!isset($userto->viewfullnames[$forum->id])) {
3092         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3093     } else {
3094         $viewfullnames = $userto->viewfullnames[$forum->id];
3095     }
3097     // add absolute file links
3098     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3100     // format the post body
3101     $options = new stdClass();
3102     $options->para = true;
3103     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3105     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3107     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3108     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3109     $output .= '</td>';
3111     if ($post->parent) {
3112         $output .= '<td class="topic">';
3113     } else {
3114         $output .= '<td class="topic starter">';
3115     }
3116     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3118     $fullname = fullname($userfrom, $viewfullnames);
3119     $by = new stdClass();
3120     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3121     $by->date = userdate($post->modified, '', $userto->timezone);
3122     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3124     $output .= '</td></tr>';
3126     $output .= '<tr><td class="left side" valign="top">';
3128     if (isset($userfrom->groups)) {
3129         $groups = $userfrom->groups[$forum->id];
3130     } else {
3131         $groups = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3132     }
3134     if ($groups) {
3135         $output .= print_group_picture($groups, $course->id, false, true, true);
3136     } else {
3137         $output .= '&nbsp;';
3138     }
3140     $output .= '</td><td class="content">';
3142     $attachments = forum_print_attachments($post, $cm, 'html');
3143     if ($attachments !== '') {
3144         $output .= '<div class="attachments">';
3145         $output .= $attachments;
3146         $output .= '</div>';
3147     }
3149     $output .= $formattedtext;
3151 // Commands
3152     $commands = array();
3154     if ($post->parent) {
3155         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3156                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3157     }
3159     if ($reply) {
3160         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3161                       get_string('reply', 'forum').'</a>';
3162     }
3164     $output .= '<div class="commands">';
3165     $output .= implode(' | ', $commands);
3166     $output .= '</div>';
3168 // Context link to post if required
3169     if ($link) {
3170         $output .= '<div class="link">';
3171         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3172                      get_string('postincontext', 'forum').'</a>';
3173         $output .= '</div>';
3174     }
3176     if ($footer) {
3177         $output .= '<div class="footer">'.$footer.'</div>';
3178     }
3179     $output .= '</td></tr></table>'."\n\n";
3181     return $output;
3184 /**
3185  * Print a forum post
3186  *
3187  * @global object
3188  * @global object
3189  * @uses FORUM_MODE_THREADED
3190  * @uses PORTFOLIO_FORMAT_PLAINHTML
3191  * @uses PORTFOLIO_FORMAT_FILE
3192  * @uses PORTFOLIO_FORMAT_RICHHTML
3193  * @uses PORTFOLIO_ADD_TEXT_LINK
3194  * @uses CONTEXT_MODULE
3195  * @param object $post The post to print.
3196  * @param object $discussion
3197  * @param object $forum
3198  * @param object $cm
3199  * @param object $course
3200  * @param boolean $ownpost Whether this post belongs to the current user.
3201  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3202  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3203  * @param string $footer Extra stuff to print after the message.
3204  * @param string $highlight Space-separated list of terms to highlight.
3205  * @param int $post_read true, false or -99. If we already know whether this user
3206  *          has read this post, pass that in, otherwise, pass in -99, and this
3207  *          function will work it out.
3208  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3209  *          the current user can't see this post, if this argument is true
3210  *          (the default) then print a dummy 'you can't see this post' post.
3211  *          If false, don't output anything at all.
3212  * @param bool|null $istracked
3213  * @return void
3214  */
3215 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3216                           $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3217     global $USER, $CFG, $OUTPUT;
3219     require_once($CFG->libdir . '/filelib.php');
3221     // String cache
3222     static $str;
3224     $modcontext = context_module::instance($cm->id);
3226     $post->course = $course->id;
3227     $post->forum  = $forum->id;
3228     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3229     if (!empty($CFG->enableplagiarism)) {
3230         require_once($CFG->libdir.'/plagiarismlib.php');
3231         $post->message .= plagiarism_get_links(array('userid' => $post->userid,
3232             'content' => $post->message,
3233             'cmid' => $cm->id,
3234             'course' => $post->course,
3235             'forum' => $post->forum));
3236     }
3238     // caching
3239     if (!isset($cm->cache)) {
3240         $cm->cache = new stdClass;
3241     }
3243     if (!isset($cm->cache->caps)) {
3244         $cm->cache->caps = array();
3245         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3246         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3247         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3248         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3249         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3250         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3251         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3252         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3253         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3254     }
3256     if (!isset($cm->uservisible)) {
3257         $cm->uservisible = coursemodule_visible_for_user($cm);
3258     }
3260     if ($istracked && is_null($postisread)) {
3261         $postisread = forum_tp_is_post_read($USER->id, $post);
3262     }
3264     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3265         $output = '';
3266         if (!$dummyifcantsee) {
3267             if ($return) {
3268                 return $output;
3269             }
3270             echo $output;
3271             return;
3272         }
3273         $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3274         $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'));
3275         $output .= html_writer::start_tag('div', array('class'=>'row header'));
3276         $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3277         if ($post->parent) {
3278             $output .= html_writer::start_tag('div', array('class'=>'topic'));
3279         } else {
3280             $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3281         }
3282         $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class'=>'subject')); // Subject
3283         $output .= html_writer::tag('div', get_string('forumauthorhidden','forum'), array('class'=>'author')); // author
3284         $output .= html_writer::end_tag('div');
3285         $output .= html_writer::end_tag('div'); // row
3286         $output .= html_writer::start_tag('div', array('class'=>'row'));
3287         $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3288         $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3289         $output .= html_writer::end_tag('div'); // row
3290         $output .= html_writer::end_tag('div'); // forumpost
3292         if ($return) {
3293             return $output;
3294         }
3295         echo $output;
3296         return;
3297     }
3299     if (empty($str)) {
3300         $str = new stdClass;
3301         $str->edit         = get_string('edit', 'forum');
3302         $str->delete       = get_string('delete', 'forum');
3303         $str->reply        = get_string('reply', 'forum');
3304         $str->parent       = get_string('parent', 'forum');
3305         $str->pruneheading = get_string('pruneheading', 'forum');
3306         $str->prune        = get_string('prune', 'forum');
3307         $str->displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3308         $str->markread     = get_string('markread', 'forum');
3309         $str->markunread   = get_string('markunread', 'forum');
3310     }
3312     $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3314     // Build an object that represents the posting user
3315     $postuser = new stdClass;
3316     $postuser->id        = $post->userid;
3317     foreach (get_all_user_name_fields() as $addname) {
3318         $postuser->$addname  = $post->$addname;
3319     }
3320     $postuser->imagealt  = $post->imagealt;
3321     $postuser->picture   = $post->picture;
3322     $postuser->email     = $post->email;
3323     // Some handy things for later on
3324     $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3325     $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3327     // Prepare the groups the posting user belongs to
3328     if (isset($cm->cache->usersgroups)) {
3329         $groups = array();
3330         if (isset($cm->cache->usersgroups[$post->userid])) {
3331             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3332                 $groups[$gid] = $cm->cache->groups[$gid];
3333             }
3334         }
3335     } else {
3336         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3337     }
3339     // Prepare the attachements for the post, files then images
3340     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3342     // Determine if we need to shorten this post
3343     $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3346     // Prepare an array of commands
3347     $commands = array();
3349     // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3350     // Don't display the mark read / unread controls in this case.
3351     if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3352         $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3353         $text = $str->markunread;
3354         if (!$postisread) {
3355             $url->param('mark', 'read');
3356             $text = $str->markread;
3357         }
3358         if ($str->displaymode == FORUM_MODE_THREADED) {
3359             $url->param('parent', $post->parent);
3360         } else {
3361             $url->set_anchor('p'.$post->id);
3362         }
3363         $commands[] = array('url'=>$url, 'text'=>$text);
3364     }
3366     // Zoom in to the parent specifically
3367     if ($post->parent) {
3368         $url = new moodle_url($discussionlink);
3369         if ($str->displaymode == FORUM_MODE_THREADED) {
3370             $url->param('parent', $post->parent);
3371         } else {
3372             $url->set_anchor('p'.$post->parent);
3373         }
3374         $commands[] = array('url'=>$url, 'text'=>$str->parent);
3375     }
3377     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3378     $age = time() - $post->created;
3379     if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3380         $age = 0;
3381     }
3383     if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
3384         if (has_capability('moodle/course:manageactivities', $modcontext)) {
3385             // The first post in single simple is the forum description.
3386             $commands[] = array('url'=>new moodle_url('/course/modedit.php', array('update'=>$cm->id, 'sesskey'=>sesskey(), 'return'=>1)), 'text'=>$str->edit);
3387         }
3388     } else if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3389         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3390     }
3392     if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3393         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3394     }
3396     if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
3397         // Do not allow deleting of first post in single simple type.
3398     } else if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3399         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3400     }
3402     if ($reply) {
3403         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php#mformforum', array('reply'=>$post->id)), 'text'=>$str->reply);
3404     }
3406     if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
3407         $p = array('postid' => $post->id);
3408         require_once($CFG->libdir.'/portfoliolib.php');
3409         $button = new portfolio_add_button();
3410         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), 'mod_forum');
3411         if (empty($attachments)) {
3412             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3413         } else {
3414             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3415         }
3417         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3418         if (!empty($porfoliohtml)) {
3419             $commands[] = $porfoliohtml;
3420         }
3421     }
3422     // Finished building commands
3425     // Begin output
3427     $output  = '';
3429     if ($istracked) {
3430         if ($postisread) {
3431             $forumpostclass = ' read';
3432         } else {
3433             $forumpostclass = ' unread';
3434             $output .= html_writer::tag('a', '', array('name'=>'unread'));
3435         }
3436     } else {
3437         // ignore trackign status if not tracked or tracked param missing
3438         $forumpostclass = '';
3439     }
3441     $topicclass = '';
3442     if (empty($post->parent)) {
3443         $topicclass = ' firstpost starter';
3444     }
3446     $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3447     $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass));
3448     $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3449     $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3450     $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3451     $output .= html_writer::end_tag('div');
3454     $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3456     $postsubject = $post->subject;
3457     if (empty($post->subjectnoformat)) {
3458         $postsubject = format_string($postsubject);
3459     }
3460     $output .= html_writer::tag('div', $postsubject, array('class'=>'subject'));
3462     $by = new stdClass();
3463     $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3464     $by->date = userdate($post->modified);
3465     $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author'));
3467     $output .= html_writer::end_tag('div'); //topic
3468     $output .= html_writer::end_tag('div'); //row
3470     $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3471     $output .= html_writer::start_tag('div', array('class'=>'left'));
3473     $groupoutput = '';
3474     if ($groups) {
3475         $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3476     }
3477     if (empty($groupoutput)) {
3478         $groupoutput = '&nbsp;';
3479     }
3480     $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3482     $output .= html_writer::end_tag('div'); //left side
3483     $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3484     $output .= html_writer::start_tag('div', array('class'=>'content'));
3485     if (!empty($attachments)) {
3486         $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3487     }
3489     $options = new stdClass;
3490     $options->para    = false;
3491     $options->trusted = $post->messagetrust;
3492     $options->context = $modcontext;
3493     if ($shortenpost) {
3494         // Prepare shortened version