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