Merge branch 'wip-mdl-33143' of git://github.com/rajeshtaneja/moodle
[moodle.git] / mod / forum / lib.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * @package    mod
19  * @subpackage forum
20  * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
21  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22  */
24 defined('MOODLE_INTERNAL') || die();
26 /** Include required files */
27 require_once($CFG->libdir.'/filelib.php');
28 require_once($CFG->libdir.'/eventslib.php');
29 require_once($CFG->dirroot.'/user/selector/lib.php');
31 /// CONSTANTS ///////////////////////////////////////////////////////////
33 define('FORUM_MODE_FLATOLDEST', 1);
34 define('FORUM_MODE_FLATNEWEST', -1);
35 define('FORUM_MODE_THREADED', 2);
36 define('FORUM_MODE_NESTED', 3);
38 define('FORUM_CHOOSESUBSCRIBE', 0);
39 define('FORUM_FORCESUBSCRIBE', 1);
40 define('FORUM_INITIALSUBSCRIBE', 2);
41 define('FORUM_DISALLOWSUBSCRIBE',3);
43 define('FORUM_TRACKING_OFF', 0);
44 define('FORUM_TRACKING_OPTIONAL', 1);
45 define('FORUM_TRACKING_ON', 2);
47 if (!defined('FORUM_CRON_USER_CACHE')) {
48     /** Defines how many full user records are cached in forum cron. */
49     define('FORUM_CRON_USER_CACHE', 5000);
50 }
52 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
54 /**
55  * Given an object containing all the necessary data,
56  * (defined by the form in mod_form.php) this function
57  * will create a new instance and return the id number
58  * of the new instance.
59  *
60  * @param stdClass $forum add forum instance
61  * @param mod_forum_mod_form $mform
62  * @return int intance id
63  */
64 function forum_add_instance($forum, $mform = null) {
65     global $CFG, $DB;
67     $forum->timemodified = time();
69     if (empty($forum->assessed)) {
70         $forum->assessed = 0;
71     }
73     if (empty($forum->ratingtime) or empty($forum->assessed)) {
74         $forum->assesstimestart  = 0;
75         $forum->assesstimefinish = 0;
76     }
78     $forum->id = $DB->insert_record('forum', $forum);
79     $modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule);
81     if ($forum->type == 'single') {  // Create related discussion.
82         $discussion = new stdClass();
83         $discussion->course        = $forum->course;
84         $discussion->forum         = $forum->id;
85         $discussion->name          = $forum->name;
86         $discussion->assessed      = $forum->assessed;
87         $discussion->message       = $forum->intro;
88         $discussion->messageformat = $forum->introformat;
89         $discussion->messagetrust  = trusttext_trusted(get_context_instance(CONTEXT_COURSE, $forum->course));
90         $discussion->mailnow       = false;
91         $discussion->groupid       = -1;
93         $message = '';
95         $discussion->id = forum_add_discussion($discussion, null, $message);
97         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
98             // ugly hack - we need to copy the files somehow
99             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
100             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
102             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
103             $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
104         }
105     }
107     if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
108     /// all users should be subscribed initially
109     /// Note: forum_get_potential_subscribers should take the forum context,
110     /// but that does not exist yet, becuase the forum is only half build at this
111     /// stage. However, because the forum is brand new, we know that there are
112     /// no role assignments or overrides in the forum context, so using the
113     /// course context gives the same list of users.
114         $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
115         foreach ($users as $user) {
116             forum_subscribe($user->id, $forum->id);
117         }
118     }
120     forum_grade_item_update($forum);
122     return $forum->id;
126 /**
127  * Given an object containing all the necessary data,
128  * (defined by the form in mod_form.php) this function
129  * will update an existing instance with new data.
130  *
131  * @global object
132  * @param object $forum forum instance (with magic quotes)
133  * @return bool success
134  */
135 function forum_update_instance($forum, $mform) {
136     global $DB, $OUTPUT, $USER;
138     $forum->timemodified = time();
139     $forum->id           = $forum->instance;
141     if (empty($forum->assessed)) {
142         $forum->assessed = 0;
143     }
145     if (empty($forum->ratingtime) or empty($forum->assessed)) {
146         $forum->assesstimestart  = 0;
147         $forum->assesstimefinish = 0;
148     }
150     $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
152     // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
153     // if  scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
154     // for count and sum aggregation types the grade we check to make sure they do not exceed the scale (i.e. max score) when calculating the grade
155     if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
156         forum_update_grades($forum); // recalculate grades for the forum
157     }
159     if ($forum->type == 'single') {  // Update related discussion and post.
160         $discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC');
161         if (!empty($discussions)) {
162             if (count($discussions) > 1) {
163                 echo $OUTPUT->notification(get_string('warnformorepost', 'forum'));
164             }
165             $discussion = array_pop($discussions);
166         } else {
167             // try to recover by creating initial discussion - MDL-16262
168             $discussion = new stdClass();
169             $discussion->course          = $forum->course;
170             $discussion->forum           = $forum->id;
171             $discussion->name            = $forum->name;
172             $discussion->assessed        = $forum->assessed;
173             $discussion->message         = $forum->intro;
174             $discussion->messageformat   = $forum->introformat;
175             $discussion->messagetrust    = true;
176             $discussion->mailnow         = false;
177             $discussion->groupid         = -1;
179             $message = '';
181             forum_add_discussion($discussion, null, $message);
183             if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
184                 print_error('cannotadd', 'forum');
185             }
186         }
187         if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
188             print_error('cannotfindfirstpost', 'forum');
189         }
191         $cm         = get_coursemodule_from_instance('forum', $forum->id);
192         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
194         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
195             // ugly hack - we need to copy the files somehow
196             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
197             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
199             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
200         }
202         $post->subject       = $forum->name;
203         $post->message       = $forum->intro;
204         $post->messageformat = $forum->introformat;
205         $post->messagetrust  = trusttext_trusted($modcontext);
206         $post->modified      = $forum->timemodified;
207         $post->userid        = $USER->id;    // MDL-18599, so that current teacher can take ownership of activities
209         $DB->update_record('forum_posts', $post);
210         $discussion->name = $forum->name;
211         $DB->update_record('forum_discussions', $discussion);
212     }
214     $DB->update_record('forum', $forum);
216     forum_grade_item_update($forum);
218     return true;
222 /**
223  * Given an ID of an instance of this module,
224  * this function will permanently delete the instance
225  * and any data that depends on it.
226  *
227  * @global object
228  * @param int $id forum instance id
229  * @return bool success
230  */
231 function forum_delete_instance($id) {
232     global $DB;
234     if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
235         return false;
236     }
237     if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
238         return false;
239     }
240     if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
241         return false;
242     }
244     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
246     // now get rid of all files
247     $fs = get_file_storage();
248     $fs->delete_area_files($context->id);
250     $result = true;
252     if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
253         foreach ($discussions as $discussion) {
254             if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
255                 $result = false;
256             }
257         }
258     }
260     if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
261         $result = false;
262     }
264     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
266     if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
267         $result = false;
268     }
270     forum_grade_item_delete($forum);
272     return $result;
276 /**
277  * Indicates API features that the forum supports.
278  *
279  * @uses FEATURE_GROUPS
280  * @uses FEATURE_GROUPINGS
281  * @uses FEATURE_GROUPMEMBERSONLY
282  * @uses FEATURE_MOD_INTRO
283  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
284  * @uses FEATURE_COMPLETION_HAS_RULES
285  * @uses FEATURE_GRADE_HAS_GRADE
286  * @uses FEATURE_GRADE_OUTCOMES
287  * @param string $feature
288  * @return mixed True if yes (some features may use other values)
289  */
290 function forum_supports($feature) {
291     switch($feature) {
292         case FEATURE_GROUPS:                  return true;
293         case FEATURE_GROUPINGS:               return true;
294         case FEATURE_GROUPMEMBERSONLY:        return true;
295         case FEATURE_MOD_INTRO:               return true;
296         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
297         case FEATURE_COMPLETION_HAS_RULES:    return true;
298         case FEATURE_GRADE_HAS_GRADE:         return true;
299         case FEATURE_GRADE_OUTCOMES:          return true;
300         case FEATURE_RATE:                    return true;
301         case FEATURE_BACKUP_MOODLE2:          return true;
302         case FEATURE_SHOW_DESCRIPTION:        return true;
304         default: return null;
305     }
309 /**
310  * Obtains the automatic completion state for this forum based on any conditions
311  * in forum settings.
312  *
313  * @global object
314  * @global object
315  * @param object $course Course
316  * @param object $cm Course-module
317  * @param int $userid User ID
318  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
319  * @return bool True if completed, false if not. (If no conditions, then return
320  *   value depends on comparison type)
321  */
322 function forum_get_completion_state($course,$cm,$userid,$type) {
323     global $CFG,$DB;
325     // Get forum details
326     if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
327         throw new Exception("Can't find forum {$cm->instance}");
328     }
330     $result=$type; // Default return value
332     $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
333     $postcountsql="
334 SELECT
335     COUNT(1)
336 FROM
337     {forum_posts} fp
338     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
339 WHERE
340     fp.userid=:userid AND fd.forum=:forumid";
342     if ($forum->completiondiscussions) {
343         $value = $forum->completiondiscussions <=
344                  $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
345         if ($type == COMPLETION_AND) {
346             $result = $result && $value;
347         } else {
348             $result = $result || $value;
349         }
350     }
351     if ($forum->completionreplies) {
352         $value = $forum->completionreplies <=
353                  $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
354         if ($type==COMPLETION_AND) {
355             $result = $result && $value;
356         } else {
357             $result = $result || $value;
358         }
359     }
360     if ($forum->completionposts) {
361         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
362         if ($type == COMPLETION_AND) {
363             $result = $result && $value;
364         } else {
365             $result = $result || $value;
366         }
367     }
369     return $result;
372 /**
373  * Create a message-id string to use in the custom headers of forum notification emails
374  *
375  * message-id is used by email clients to identify emails and to nest conversations
376  *
377  * @param int $postid The ID of the forum post we are notifying the user about
378  * @param int $usertoid The ID of the user being notified
379  * @param string $hostname The server's hostname
380  * @return string A unique message-id
381  */
382 function forum_get_email_message_id($postid, $usertoid, $hostname) {
383     return '<'.hash('sha256',$postid.'to'.$usertoid.'@'.$hostname).'>';
386 /**
387  * Removes properties from user record that are not necessary
388  * for sending post notifications.
389  * @param stdClass $user
390  * @return void, $user parameter is modified
391  */
392 function forum_cron_minimise_user_record(stdClass $user) {
394     // We store large amount of users in one huge array,
395     // make sure we do not store info there we do not actually need
396     // in mail generation code or messaging.
398     unset($user->institution);
399     unset($user->department);
400     unset($user->address);
401     unset($user->city);
402     unset($user->url);
403     unset($user->currentlogin);
404     unset($user->description);
405     unset($user->descriptionformat);
408 /**
409  * Function to be run periodically according to the moodle cron
410  * Finds all posts that have yet to be mailed out, and mails them
411  * out to all subscribers
412  *
413  * @global object
414  * @global object
415  * @global object
416  * @uses CONTEXT_MODULE
417  * @uses CONTEXT_COURSE
418  * @uses SITEID
419  * @uses FORMAT_PLAIN
420  * @return void
421  */
422 function forum_cron() {
423     global $CFG, $USER, $DB;
425     $site = get_site();
427     // All users that are subscribed to any post that needs sending,
428     // please increase $CFG->extramemorylimit on large sites that
429     // send notifications to a large number of users.
430     $users = array();
431     $userscount = 0; // Cached user counter - count($users) in PHP is horribly slow!!!
433     // status arrays
434     $mailcount  = array();
435     $errorcount = array();
437     // caches
438     $discussions     = array();
439     $forums          = array();
440     $courses         = array();
441     $coursemodules   = array();
442     $subscribedusers = array();
445     // Posts older than 2 days will not be mailed.  This is to avoid the problem where
446     // cron has not been running for a long time, and then suddenly people are flooded
447     // with mail from the past few weeks or months
448     $timenow   = time();
449     $endtime   = $timenow - $CFG->maxeditingtime;
450     $starttime = $endtime - 48 * 3600;   // Two days earlier
452     if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
453         // Mark them all now as being mailed.  It's unlikely but possible there
454         // might be an error later so that a post is NOT actually mailed out,
455         // but since mail isn't crucial, we can accept this risk.  Doing it now
456         // prevents the risk of duplicated mails, which is a worse problem.
458         if (!forum_mark_old_posts_as_mailed($endtime)) {
459             mtrace('Errors occurred while trying to mark some posts as being mailed.');
460             return false;  // Don't continue trying to mail them, in case we are in a cron loop
461         }
463         // checking post validity, and adding users to loop through later
464         foreach ($posts as $pid => $post) {
466             $discussionid = $post->discussion;
467             if (!isset($discussions[$discussionid])) {
468                 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
469                     $discussions[$discussionid] = $discussion;
470                 } else {
471                     mtrace('Could not find discussion '.$discussionid);
472                     unset($posts[$pid]);
473                     continue;
474                 }
475             }
476             $forumid = $discussions[$discussionid]->forum;
477             if (!isset($forums[$forumid])) {
478                 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
479                     $forums[$forumid] = $forum;
480                 } else {
481                     mtrace('Could not find forum '.$forumid);
482                     unset($posts[$pid]);
483                     continue;
484                 }
485             }
486             $courseid = $forums[$forumid]->course;
487             if (!isset($courses[$courseid])) {
488                 if ($course = $DB->get_record('course', array('id' => $courseid))) {
489                     $courses[$courseid] = $course;
490                 } else {
491                     mtrace('Could not find course '.$courseid);
492                     unset($posts[$pid]);
493                     continue;
494                 }
495             }
496             if (!isset($coursemodules[$forumid])) {
497                 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
498                     $coursemodules[$forumid] = $cm;
499                 } else {
500                     mtrace('Could not find course module for forum '.$forumid);
501                     unset($posts[$pid]);
502                     continue;
503                 }
504             }
507             // caching subscribed users of each forum
508             if (!isset($subscribedusers[$forumid])) {
509                 $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
510                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext, "u.*")) {
511                     foreach ($subusers as $postuser) {
512                         // this user is subscribed to this forum
513                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
514                         $userscount++;
515                         if ($userscount > FORUM_CRON_USER_CACHE) {
516                             // Store minimal user info.
517                             $minuser = new stdClass();
518                             $minuser->id = $postuser->id;
519                             $users[$postuser->id] = $minuser;
520                         } else {
521                             // Cache full user record.
522                             forum_cron_minimise_user_record($postuser);
523                             $users[$postuser->id] = $postuser;
524                         }
525                     }
526                     // Release memory.
527                     unset($subusers);
528                     unset($postuser);
529                 }
530             }
532             $mailcount[$pid] = 0;
533             $errorcount[$pid] = 0;
534         }
535     }
537     if ($users && $posts) {
539         $urlinfo = parse_url($CFG->wwwroot);
540         $hostname = $urlinfo['host'];
542         foreach ($users as $userto) {
544             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
546             mtrace('Processing user '.$userto->id);
548             // Init user caches - we keep the cache for one cycle only,
549             // otherwise it could consume too much memory.
550             if (isset($userto->username)) {
551                 $userto = clone($userto);
552             } else {
553                 $userto = $DB->get_record('user', array('id' => $userto->id));
554                 forum_cron_minimise_user_record($userto);
555             }
556             $userto->viewfullnames = array();
557             $userto->canpost       = array();
558             $userto->markposts     = array();
560             // set this so that the capabilities are cached, and environment matches receiving user
561             cron_setup_user($userto);
563             // reset the caches
564             foreach ($coursemodules as $forumid=>$unused) {
565                 $coursemodules[$forumid]->cache       = new stdClass();
566                 $coursemodules[$forumid]->cache->caps = array();
567                 unset($coursemodules[$forumid]->uservisible);
568             }
570             foreach ($posts as $pid => $post) {
572                 // Set up the environment for the post, discussion, forum, course
573                 $discussion = $discussions[$post->discussion];
574                 $forum      = $forums[$discussion->forum];
575                 $course     = $courses[$forum->course];
576                 $cm         =& $coursemodules[$forum->id];
578                 // Do some checks  to see if we can bail out now
579                 // Only active enrolled users are in the list of subscribers
580                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
581                     continue; // user does not subscribe to this forum
582                 }
584                 // Don't send email if the forum is Q&A and the user has not posted
585                 // Initial topics are still mailed
586                 if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id) && $pid != $discussion->firstpost) {
587                     mtrace('Did not email '.$userto->id.' because user has not posted in discussion');
588                     continue;
589                 }
591                 // Get info about the sending user
592                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
593                     $userfrom = $users[$post->userid];
594                     if (!isset($userfrom->idnumber)) {
595                         // Minimalised user info, fetch full record.
596                         $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
597                         forum_cron_minimise_user_record($userfrom);
598                     }
600                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
601                     forum_cron_minimise_user_record($userfrom);
602                     // Fetch only once if possible, we can add it to user list, it will be skipped anyway.
603                     if ($userscount <= FORUM_CRON_USER_CACHE) {
604                         $userscount++;
605                         $users[$userfrom->id] = $userfrom;
606                     }
608                 } else {
609                     mtrace('Could not find user '.$post->userid);
610                     continue;
611                 }
613                 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
615                 // setup global $COURSE properly - needed for roles and languages
616                 cron_setup_user($userto, $course);
618                 // Fill caches
619                 if (!isset($userto->viewfullnames[$forum->id])) {
620                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
621                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
622                 }
623                 if (!isset($userto->canpost[$discussion->id])) {
624                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
625                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
626                 }
627                 if (!isset($userfrom->groups[$forum->id])) {
628                     if (!isset($userfrom->groups)) {
629                         $userfrom->groups = array();
630                         if (isset($users[$userfrom->id])) {
631                             $users[$userfrom->id]->groups = array();
632                         }
633                     }
634                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
635                     if (isset($users[$userfrom->id])) {
636                         $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
637                     }
638                 }
640                 // Make sure groups allow this user to see this email
641                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
642                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
643                         continue;                           // Be safe and don't send it to anyone
644                     }
646                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
647                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
648                         continue;
649                     }
650                 }
652                 // Make sure we're allowed to see it...
653                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
654                     mtrace('user '.$userto->id. ' can not see '.$post->id);
655                     continue;
656                 }
658                 // OK so we need to send the email.
660                 // Does the user want this post in a digest?  If so postpone it for now.
661                 if ($userto->maildigest > 0) {
662                     // This user wants the mails to be in digest form
663                     $queue = new stdClass();
664                     $queue->userid       = $userto->id;
665                     $queue->discussionid = $discussion->id;
666                     $queue->postid       = $post->id;
667                     $queue->timemodified = $post->created;
668                     $DB->insert_record('forum_queue', $queue);
669                     continue;
670                 }
673                 // Prepare to actually send the post now, and build up the content
675                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
677                 $userfrom->customheaders = array (  // Headers to make emails easier to track
678                            'Precedence: Bulk',
679                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
680                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
681                            'Message-ID: '.forum_get_email_message_id($post->id, $userto->id, $hostname),
682                            'X-Course-Id: '.$course->id,
683                            'X-Course-Name: '.format_string($course->fullname, true)
684                 );
686                 if ($post->parent) {  // This post is a reply, so add headers for threading (see MDL-22551)
687                     $userfrom->customheaders[] = 'In-Reply-To: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
688                     $userfrom->customheaders[] = 'References: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
689                 }
691                 $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
693                 $postsubject = html_to_text("$shortname: ".format_string($post->subject, true));
694                 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
695                 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
697                 // Send the post now!
699                 mtrace('Sending ', '');
701                 $eventdata = new stdClass();
702                 $eventdata->component        = 'mod_forum';
703                 $eventdata->name             = 'posts';
704                 $eventdata->userfrom         = $userfrom;
705                 $eventdata->userto           = $userto;
706                 $eventdata->subject          = $postsubject;
707                 $eventdata->fullmessage      = $posttext;
708                 $eventdata->fullmessageformat = FORMAT_PLAIN;
709                 $eventdata->fullmessagehtml  = $posthtml;
710                 $eventdata->notification = 1;
712                 $smallmessagestrings = new stdClass();
713                 $smallmessagestrings->user = fullname($userfrom);
714                 $smallmessagestrings->forumname = "$shortname: ".format_string($forum->name,true).": ".$discussion->name;
715                 $smallmessagestrings->message = $post->message;
716                 //make sure strings are in message recipients language
717                 $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
719                 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
720                 $eventdata->contexturlname = $discussion->name;
722                 $mailresult = message_send($eventdata);
723                 if (!$mailresult){
724                     mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
725                          " ($userto->email) .. not trying again.");
726                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
727                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
728                     $errorcount[$post->id]++;
729                 } else {
730                     $mailcount[$post->id]++;
732                 // Mark post as read if forum_usermarksread is set off
733                     if (!$CFG->forum_usermarksread) {
734                         $userto->markposts[$post->id] = $post->id;
735                     }
736                 }
738                 mtrace('post '.$post->id. ': '.$post->subject);
739             }
741             // mark processed posts as read
742             forum_tp_mark_posts_read($userto, $userto->markposts);
743             unset($userto);
744         }
745     }
747     if ($posts) {
748         foreach ($posts as $post) {
749             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
750             if ($errorcount[$post->id]) {
751                 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
752             }
753         }
754     }
756     // release some memory
757     unset($subscribedusers);
758     unset($mailcount);
759     unset($errorcount);
761     cron_setup_user();
763     $sitetimezone = $CFG->timezone;
765     // Now see if there are any digest mails waiting to be sent, and if we should send them
767     mtrace('Starting digest processing...');
769     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
771     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
772         set_config('digestmailtimelast', 0);
773     }
775     $timenow = time();
776     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
778     // Delete any really old ones (normally there shouldn't be any)
779     $weekago = $timenow - (7 * 24 * 3600);
780     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
781     mtrace ('Cleaned old digest records');
783     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
785         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
787         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
789         if ($digestposts_rs->valid()) {
791             // We have work to do
792             $usermailcount = 0;
794             //caches - reuse the those filled before too
795             $discussionposts = array();
796             $userdiscussions = array();
798             foreach ($digestposts_rs as $digestpost) {
799                 if (!isset($posts[$digestpost->postid])) {
800                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
801                         $posts[$digestpost->postid] = $post;
802                     } else {
803                         continue;
804                     }
805                 }
806                 $discussionid = $digestpost->discussionid;
807                 if (!isset($discussions[$discussionid])) {
808                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
809                         $discussions[$discussionid] = $discussion;
810                     } else {
811                         continue;
812                     }
813                 }
814                 $forumid = $discussions[$discussionid]->forum;
815                 if (!isset($forums[$forumid])) {
816                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
817                         $forums[$forumid] = $forum;
818                     } else {
819                         continue;
820                     }
821                 }
823                 $courseid = $forums[$forumid]->course;
824                 if (!isset($courses[$courseid])) {
825                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
826                         $courses[$courseid] = $course;
827                     } else {
828                         continue;
829                     }
830                 }
832                 if (!isset($coursemodules[$forumid])) {
833                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
834                         $coursemodules[$forumid] = $cm;
835                     } else {
836                         continue;
837                     }
838                 }
839                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
840                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
841             }
842             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
844             // Data collected, start sending out emails to each user
845             foreach ($userdiscussions as $userid => $thesediscussions) {
847                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
849                 cron_setup_user();
851                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
853                 // First of all delete all the queue entries for this user
854                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
856                 // Init user caches - we keep the cache for one cycle only,
857                 // otherwise it would unnecessarily consume memory.
858                 if (array_key_exists($userid, $users) and isset($users[$userid]->username)) {
859                     $userto = clone($users[$userid]);
860                 } else {
861                     $userto = $DB->get_record('user', array('id' => $userid));
862                     forum_cron_minimise_user_record($userto);
863                 }
864                 $userto->viewfullnames = array();
865                 $userto->canpost       = array();
866                 $userto->markposts     = array();
868                 // Override the language and timezone of the "current" user, so that
869                 // mail is customised for the receiver.
870                 cron_setup_user($userto);
872                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
874                 $headerdata = new stdClass();
875                 $headerdata->sitename = format_string($site->fullname, true);
876                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
878                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
879                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
881                 $posthtml = "<head>";
882 /*                foreach ($CFG->stylesheets as $stylesheet) {
883                     //TODO: MDL-21120
884                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
885                 }*/
886                 $posthtml .= "</head>\n<body id=\"email\">\n";
887                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
889                 foreach ($thesediscussions as $discussionid) {
891                     @set_time_limit(120);   // to be reset for each post
893                     $discussion = $discussions[$discussionid];
894                     $forum      = $forums[$discussion->forum];
895                     $course     = $courses[$forum->course];
896                     $cm         = $coursemodules[$forum->id];
898                     //override language
899                     cron_setup_user($userto, $course);
901                     // Fill caches
902                     if (!isset($userto->viewfullnames[$forum->id])) {
903                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
904                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
905                     }
906                     if (!isset($userto->canpost[$discussion->id])) {
907                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
908                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
909                     }
911                     $strforums      = get_string('forums', 'forum');
912                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
913                     $canreply       = $userto->canpost[$discussion->id];
914                     $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
916                     $posttext .= "\n \n";
917                     $posttext .= '=====================================================================';
918                     $posttext .= "\n \n";
919                     $posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
920                     if ($discussion->name != $forum->name) {
921                         $posttext  .= " -> ".format_string($discussion->name,true);
922                     }
923                     $posttext .= "\n";
925                     $posthtml .= "<p><font face=\"sans-serif\">".
926                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
927                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
928                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
929                     if ($discussion->name == $forum->name) {
930                         $posthtml .= "</font></p>";
931                     } else {
932                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
933                     }
934                     $posthtml .= '<p>';
936                     $postsarray = $discussionposts[$discussionid];
937                     sort($postsarray);
939                     foreach ($postsarray as $postid) {
940                         $post = $posts[$postid];
942                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
943                             $userfrom = $users[$post->userid];
944                             if (!isset($userfrom->idnumber)) {
945                                 $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
946                                 forum_cron_minimise_user_record($userfrom);
947                             }
949                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
950                             forum_cron_minimise_user_record($userfrom);
951                             if ($userscount <= FORUM_CRON_USER_CACHE) {
952                                 $userscount++;
953                                 $users[$userfrom->id] = $userfrom;
954                             }
956                         } else {
957                             mtrace('Could not find user '.$post->userid);
958                             continue;
959                         }
961                         if (!isset($userfrom->groups[$forum->id])) {
962                             if (!isset($userfrom->groups)) {
963                                 $userfrom->groups = array();
964                                 if (isset($users[$userfrom->id])) {
965                                     $users[$userfrom->id]->groups = array();
966                                 }
967                             }
968                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
969                             if (isset($users[$userfrom->id])) {
970                                 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
971                             }
972                         }
974                         $userfrom->customheaders = array ("Precedence: Bulk");
976                         if ($userto->maildigest == 2) {
977                             // Subjects only
978                             $by = new stdClass();
979                             $by->name = fullname($userfrom);
980                             $by->date = userdate($post->modified);
981                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
982                             $posttext .= "\n---------------------------------------------------------------------";
984                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
985                             $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>';
987                         } else {
988                             // The full treatment
989                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
990                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
992                         // Create an array of postid's for this user to mark as read.
993                             if (!$CFG->forum_usermarksread) {
994                                 $userto->markposts[$post->id] = $post->id;
995                             }
996                         }
997                     }
998                     if ($canunsubscribe) {
999                         $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>";
1000                     } else {
1001                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
1002                     }
1003                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
1004                 }
1005                 $posthtml .= '</body>';
1007                 if (empty($userto->mailformat) || $userto->mailformat != 1) {
1008                     // This user DOESN'T want to receive HTML
1009                     $posthtml = '';
1010                 }
1012                 $attachment = $attachname='';
1013                 $usetrueaddress = true;
1014                 //directly email forum digests rather than sending them via messaging
1015                 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname, $usetrueaddress, $CFG->forum_replytouser);
1017                 if (!$mailresult) {
1018                     mtrace("ERROR!");
1019                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
1020                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
1021                 } else {
1022                     mtrace("success.");
1023                     $usermailcount++;
1025                     // Mark post as read if forum_usermarksread is set off
1026                     forum_tp_mark_posts_read($userto, $userto->markposts);
1027                 }
1028             }
1029         }
1030     /// We have finishied all digest emails, update $CFG->digestmailtimelast
1031         set_config('digestmailtimelast', $timenow);
1032     }
1034     cron_setup_user();
1036     if (!empty($usermailcount)) {
1037         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
1038     }
1040     if (!empty($CFG->forum_lastreadclean)) {
1041         $timenow = time();
1042         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
1043             set_config('forum_lastreadclean', $timenow);
1044             mtrace('Removing old forum read tracking info...');
1045             forum_tp_clean_read_records();
1046         }
1047     } else {
1048         set_config('forum_lastreadclean', time());
1049     }
1052     return true;
1055 /**
1056  * Builds and returns the body of the email notification in plain text.
1057  *
1058  * @global object
1059  * @global object
1060  * @uses CONTEXT_MODULE
1061  * @param object $course
1062  * @param object $cm
1063  * @param object $forum
1064  * @param object $discussion
1065  * @param object $post
1066  * @param object $userfrom
1067  * @param object $userto
1068  * @param boolean $bare
1069  * @return string The email body in plain text format.
1070  */
1071 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
1072     global $CFG, $USER;
1074     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
1076     if (!isset($userto->viewfullnames[$forum->id])) {
1077         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
1078     } else {
1079         $viewfullnames = $userto->viewfullnames[$forum->id];
1080     }
1082     if (!isset($userto->canpost[$discussion->id])) {
1083         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
1084     } else {
1085         $canreply = $userto->canpost[$discussion->id];
1086     }
1088     $by = New stdClass;
1089     $by->name = fullname($userfrom, $viewfullnames);
1090     $by->date = userdate($post->modified, "", $userto->timezone);
1092     $strbynameondate = get_string('bynameondate', 'forum', $by);
1094     $strforums = get_string('forums', 'forum');
1096     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1098     $posttext = '';
1100     if (!$bare) {
1101         $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
1102         $posttext  = "$shortname -> $strforums -> ".format_string($forum->name,true);
1104         if ($discussion->name != $forum->name) {
1105             $posttext  .= " -> ".format_string($discussion->name,true);
1106         }
1107     }
1109     // add absolute file links
1110     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
1112     $posttext .= "\n---------------------------------------------------------------------\n";
1113     $posttext .= format_string($post->subject,true);
1114     if ($bare) {
1115         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1116     }
1117     $posttext .= "\n".$strbynameondate."\n";
1118     $posttext .= "---------------------------------------------------------------------\n";
1119     $posttext .= format_text_email($post->message, $post->messageformat);
1120     $posttext .= "\n\n";
1121     $posttext .= forum_print_attachments($post, $cm, "text");
1123     if (!$bare && $canreply) {
1124         $posttext .= "---------------------------------------------------------------------\n";
1125         $posttext .= get_string("postmailinfo", "forum", $shortname)."\n";
1126         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1127     }
1128     if (!$bare && $canunsubscribe) {
1129         $posttext .= "\n---------------------------------------------------------------------\n";
1130         $posttext .= get_string("unsubscribe", "forum");
1131         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1132     }
1134     return $posttext;
1137 /**
1138  * Builds and returns the body of the email notification in html format.
1139  *
1140  * @global object
1141  * @param object $course
1142  * @param object $cm
1143  * @param object $forum
1144  * @param object $discussion
1145  * @param object $post
1146  * @param object $userfrom
1147  * @param object $userto
1148  * @return string The email text in HTML format
1149  */
1150 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1151     global $CFG;
1153     if ($userto->mailformat != 1) {  // Needs to be HTML
1154         return '';
1155     }
1157     if (!isset($userto->canpost[$discussion->id])) {
1158         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course);
1159     } else {
1160         $canreply = $userto->canpost[$discussion->id];
1161     }
1163     $strforums = get_string('forums', 'forum');
1164     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1165     $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
1167     $posthtml = '<head>';
1168 /*    foreach ($CFG->stylesheets as $stylesheet) {
1169         //TODO: MDL-21120
1170         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1171     }*/
1172     $posthtml .= '</head>';
1173     $posthtml .= "\n<body id=\"email\">\n\n";
1175     $posthtml .= '<div class="navbar">'.
1176     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$shortname.'</a> &raquo; '.
1177     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1178     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1179     if ($discussion->name == $forum->name) {
1180         $posthtml .= '</div>';
1181     } else {
1182         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1183                      format_string($discussion->name,true).'</a></div>';
1184     }
1185     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1187     if ($canunsubscribe) {
1188         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1189                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1190                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1191     }
1193     $posthtml .= '</body>';
1195     return $posthtml;
1199 /**
1200  *
1201  * @param object $course
1202  * @param object $user
1203  * @param object $mod TODO this is not used in this function, refactor
1204  * @param object $forum
1205  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1206  */
1207 function forum_user_outline($course, $user, $mod, $forum) {
1208     global $CFG;
1209     require_once("$CFG->libdir/gradelib.php");
1210     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1211     if (empty($grades->items[0]->grades)) {
1212         $grade = false;
1213     } else {
1214         $grade = reset($grades->items[0]->grades);
1215     }
1217     $count = forum_count_user_posts($forum->id, $user->id);
1219     if ($count && $count->postcount > 0) {
1220         $result = new stdClass();
1221         $result->info = get_string("numposts", "forum", $count->postcount);
1222         $result->time = $count->lastpost;
1223         if ($grade) {
1224             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1225         }
1226         return $result;
1227     } else if ($grade) {
1228         $result = new stdClass();
1229         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1231         //datesubmitted == time created. dategraded == time modified or time overridden
1232         //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
1233         //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
1234         if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1235             $result->time = $grade->dategraded;
1236         } else {
1237             $result->time = $grade->datesubmitted;
1238         }
1240         return $result;
1241     }
1242     return NULL;
1246 /**
1247  * @global object
1248  * @global object
1249  * @param object $coure
1250  * @param object $user
1251  * @param object $mod
1252  * @param object $forum
1253  */
1254 function forum_user_complete($course, $user, $mod, $forum) {
1255     global $CFG,$USER, $OUTPUT;
1256     require_once("$CFG->libdir/gradelib.php");
1258     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1259     if (!empty($grades->items[0]->grades)) {
1260         $grade = reset($grades->items[0]->grades);
1261         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1262         if ($grade->str_feedback) {
1263             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1264         }
1265     }
1267     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1269         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1270             print_error('invalidcoursemodule');
1271         }
1272         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1274         foreach ($posts as $post) {
1275             if (!isset($discussions[$post->discussion])) {
1276                 continue;
1277             }
1278             $discussion = $discussions[$post->discussion];
1280             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1281         }
1282     } else {
1283         echo "<p>".get_string("noposts", "forum")."</p>";
1284     }
1292 /**
1293  * @global object
1294  * @global object
1295  * @global object
1296  * @param array $courses
1297  * @param array $htmlarray
1298  */
1299 function forum_print_overview($courses,&$htmlarray) {
1300     global $USER, $CFG, $DB, $SESSION;
1302     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1303         return array();
1304     }
1306     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1307         return;
1308     }
1311     // get all forum logs in ONE query (much better!)
1312     $params = array();
1313     $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {log} l "
1314         ." JOIN {course_modules} cm ON cm.id = cmid "
1315         ." WHERE (";
1316     foreach ($courses as $course) {
1317         $sql .= '(l.course = ? AND l.time > ?) OR ';
1318         $params[] = $course->id;
1319         $params[] = $course->lastaccess;
1320     }
1321     $sql = substr($sql,0,-3); // take off the last OR
1323     $sql .= ") AND l.module = 'forum' AND action = 'add post' "
1324         ." AND userid != ? GROUP BY cmid,l.course,instance";
1326     $params[] = $USER->id;
1328     if (!$new = $DB->get_records_sql($sql, $params)) {
1329         $new = array(); // avoid warnings
1330     }
1332     // also get all forum tracking stuff ONCE.
1333     $trackingforums = array();
1334     foreach ($forums as $forum) {
1335         if (forum_tp_can_track_forums($forum)) {
1336             $trackingforums[$forum->id] = $forum;
1337         }
1338     }
1340     if (count($trackingforums) > 0) {
1341         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1342         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1343             ' FROM {forum_posts} p '.
1344             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1345             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1346         $params = array($USER->id);
1348         foreach ($trackingforums as $track) {
1349             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1350             $params[] = $track->id;
1351             if (isset($SESSION->currentgroup[$track->course])) {
1352                 $groupid =  $SESSION->currentgroup[$track->course];
1353             } else {
1354                 // get first groupid
1355                 $groupids = groups_get_all_groups($track->course, $USER->id);
1356                 if ($groupids) {
1357                     reset($groupids);
1358                     $groupid = key($groupids);
1359                     $SESSION->currentgroup[$track->course] = $groupid;
1360                 } else {
1361                     $groupid = 0;
1362                 }
1363                 unset($groupids);
1364             }
1365             $params[] = $groupid;
1366         }
1367         $sql = substr($sql,0,-3); // take off the last OR
1368         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1369         $params[] = $cutoffdate;
1371         if (!$unread = $DB->get_records_sql($sql, $params)) {
1372             $unread = array();
1373         }
1374     } else {
1375         $unread = array();
1376     }
1378     if (empty($unread) and empty($new)) {
1379         return;
1380     }
1382     $strforum = get_string('modulename','forum');
1383     $strnumunread = get_string('overviewnumunread','forum');
1384     $strnumpostssince = get_string('overviewnumpostssince','forum');
1386     foreach ($forums as $forum) {
1387         $str = '';
1388         $count = 0;
1389         $thisunread = 0;
1390         $showunread = false;
1391         // either we have something from logs, or trackposts, or nothing.
1392         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1393             $count = $new[$forum->id]->count;
1394         }
1395         if (array_key_exists($forum->id,$unread)) {
1396             $thisunread = $unread[$forum->id]->count;
1397             $showunread = true;
1398         }
1399         if ($count > 0 || $thisunread > 0) {
1400             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1401                 $forum->name.'</a></div>';
1402             $str .= '<div class="info"><span class="postsincelogin">';
1403             $str .= $count.' '.$strnumpostssince."</span>";
1404             if (!empty($showunread)) {
1405                 $str .= '<div class="unreadposts">'.$thisunread .' '.$strnumunread.'</div>';
1406             }
1407             $str .= '</div></div>';
1408         }
1409         if (!empty($str)) {
1410             if (!array_key_exists($forum->course,$htmlarray)) {
1411                 $htmlarray[$forum->course] = array();
1412             }
1413             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1414                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1415             }
1416             $htmlarray[$forum->course]['forum'] .= $str;
1417         }
1418     }
1421 /**
1422  * Given a course and a date, prints a summary of all the new
1423  * messages posted in the course since that date
1424  *
1425  * @global object
1426  * @global object
1427  * @global object
1428  * @uses CONTEXT_MODULE
1429  * @uses VISIBLEGROUPS
1430  * @param object $course
1431  * @param bool $viewfullnames capability
1432  * @param int $timestart
1433  * @return bool success
1434  */
1435 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1436     global $CFG, $USER, $DB, $OUTPUT;
1438     // do not use log table if possible, it may be huge and is expensive to join with other tables
1440     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1441                                               d.timestart, d.timeend, d.userid AS duserid,
1442                                               u.firstname, u.lastname, u.email, u.picture
1443                                          FROM {forum_posts} p
1444                                               JOIN {forum_discussions} d ON d.id = p.discussion
1445                                               JOIN {forum} f             ON f.id = d.forum
1446                                               JOIN {user} u              ON u.id = p.userid
1447                                         WHERE p.created > ? AND f.course = ?
1448                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1449          return false;
1450     }
1452     $modinfo = get_fast_modinfo($course);
1454     $groupmodes = array();
1455     $cms    = array();
1457     $strftimerecent = get_string('strftimerecent');
1459     $printposts = array();
1460     foreach ($posts as $post) {
1461         if (!isset($modinfo->instances['forum'][$post->forum])) {
1462             // not visible
1463             continue;
1464         }
1465         $cm = $modinfo->instances['forum'][$post->forum];
1466         if (!$cm->uservisible) {
1467             continue;
1468         }
1469         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1471         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1472             continue;
1473         }
1475         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1476           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1477             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1478                 continue;
1479             }
1480         }
1482         $groupmode = groups_get_activity_groupmode($cm, $course);
1484         if ($groupmode) {
1485             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1486                 // oki (Open discussions have groupid -1)
1487             } else {
1488                 // separate mode
1489                 if (isguestuser()) {
1490                     // shortcut
1491                     continue;
1492                 }
1494                 if (is_null($modinfo->groups)) {
1495                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1496                 }
1498                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1499                     continue;
1500                 }
1501             }
1502         }
1504         $printposts[] = $post;
1505     }
1506     unset($posts);
1508     if (!$printposts) {
1509         return false;
1510     }
1512     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1513     echo "\n<ul class='unlist'>\n";
1515     foreach ($printposts as $post) {
1516         $subjectclass = empty($post->parent) ? ' bold' : '';
1518         echo '<li><div class="head">'.
1519                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1520                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1521              '</div>';
1522         echo '<div class="info'.$subjectclass.'">';
1523         if (empty($post->parent)) {
1524             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1525         } else {
1526             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1527         }
1528         $post->subject = break_up_long_words(format_string($post->subject, true));
1529         echo $post->subject;
1530         echo "</a>\"</div></li>\n";
1531     }
1533     echo "</ul>\n";
1535     return true;
1538 /**
1539  * Return grade for given user or all users.
1540  *
1541  * @global object
1542  * @global object
1543  * @param object $forum
1544  * @param int $userid optional user id, 0 means all users
1545  * @return array array of grades, false if none
1546  */
1547 function forum_get_user_grades($forum, $userid = 0) {
1548     global $CFG;
1550     require_once($CFG->dirroot.'/rating/lib.php');
1552     $ratingoptions = new stdClass;
1553     $ratingoptions->component = 'mod_forum';
1554     $ratingoptions->ratingarea = 'post';
1556     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1557     $ratingoptions->modulename = 'forum';
1558     $ratingoptions->moduleid   = $forum->id;
1559     $ratingoptions->userid = $userid;
1560     $ratingoptions->aggregationmethod = $forum->assessed;
1561     $ratingoptions->scaleid = $forum->scale;
1562     $ratingoptions->itemtable = 'forum_posts';
1563     $ratingoptions->itemtableusercolumn = 'userid';
1565     $rm = new rating_manager();
1566     return $rm->get_user_grades($ratingoptions);
1569 /**
1570  * Update activity grades
1571  *
1572  * @category grade
1573  * @param object $forum
1574  * @param int $userid specific user only, 0 means all
1575  * @param boolean $nullifnone return null if grade does not exist
1576  * @return void
1577  */
1578 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1579     global $CFG, $DB;
1580     require_once($CFG->libdir.'/gradelib.php');
1582     if (!$forum->assessed) {
1583         forum_grade_item_update($forum);
1585     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1586         forum_grade_item_update($forum, $grades);
1588     } else if ($userid and $nullifnone) {
1589         $grade = new stdClass();
1590         $grade->userid   = $userid;
1591         $grade->rawgrade = NULL;
1592         forum_grade_item_update($forum, $grade);
1594     } else {
1595         forum_grade_item_update($forum);
1596     }
1599 /**
1600  * Update all grades in gradebook.
1601  * @global object
1602  */
1603 function forum_upgrade_grades() {
1604     global $DB;
1606     $sql = "SELECT COUNT('x')
1607               FROM {forum} f, {course_modules} cm, {modules} m
1608              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1609     $count = $DB->count_records_sql($sql);
1611     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1612               FROM {forum} f, {course_modules} cm, {modules} m
1613              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1614     $rs = $DB->get_recordset_sql($sql);
1615     if ($rs->valid()) {
1616         $pbar = new progress_bar('forumupgradegrades', 500, true);
1617         $i=0;
1618         foreach ($rs as $forum) {
1619             $i++;
1620             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1621             forum_update_grades($forum, 0, false);
1622             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1623         }
1624     }
1625     $rs->close();
1628 /**
1629  * Create/update grade item for given forum
1630  *
1631  * @category grade
1632  * @uses GRADE_TYPE_NONE
1633  * @uses GRADE_TYPE_VALUE
1634  * @uses GRADE_TYPE_SCALE
1635  * @param stdClass $forum Forum object with extra cmidnumber
1636  * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
1637  * @return int 0 if ok
1638  */
1639 function forum_grade_item_update($forum, $grades=NULL) {
1640     global $CFG;
1641     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1642         require_once($CFG->libdir.'/gradelib.php');
1643     }
1645     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1647     if (!$forum->assessed or $forum->scale == 0) {
1648         $params['gradetype'] = GRADE_TYPE_NONE;
1650     } else if ($forum->scale > 0) {
1651         $params['gradetype'] = GRADE_TYPE_VALUE;
1652         $params['grademax']  = $forum->scale;
1653         $params['grademin']  = 0;
1655     } else if ($forum->scale < 0) {
1656         $params['gradetype'] = GRADE_TYPE_SCALE;
1657         $params['scaleid']   = -$forum->scale;
1658     }
1660     if ($grades  === 'reset') {
1661         $params['reset'] = true;
1662         $grades = NULL;
1663     }
1665     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1668 /**
1669  * Delete grade item for given forum
1670  *
1671  * @category grade
1672  * @param stdClass $forum Forum object
1673  * @return grade_item
1674  */
1675 function forum_grade_item_delete($forum) {
1676     global $CFG;
1677     require_once($CFG->libdir.'/gradelib.php');
1679     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1683 /**
1684  * This function returns if a scale is being used by one forum
1685  *
1686  * @global object
1687  * @param int $forumid
1688  * @param int $scaleid negative number
1689  * @return bool
1690  */
1691 function forum_scale_used ($forumid,$scaleid) {
1692     global $DB;
1693     $return = false;
1695     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1697     if (!empty($rec) && !empty($scaleid)) {
1698         $return = true;
1699     }
1701     return $return;
1704 /**
1705  * Checks if scale is being used by any instance of forum
1706  *
1707  * This is used to find out if scale used anywhere
1708  *
1709  * @global object
1710  * @param $scaleid int
1711  * @return boolean True if the scale is used by any forum
1712  */
1713 function forum_scale_used_anywhere($scaleid) {
1714     global $DB;
1715     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1716         return true;
1717     } else {
1718         return false;
1719     }
1722 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1724 /**
1725  * Gets a post with all info ready for forum_print_post
1726  * Most of these joins are just to get the forum id
1727  *
1728  * @global object
1729  * @global object
1730  * @param int $postid
1731  * @return mixed array of posts or false
1732  */
1733 function forum_get_post_full($postid) {
1734     global $CFG, $DB;
1736     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1737                              FROM {forum_posts} p
1738                                   JOIN {forum_discussions} d ON p.discussion = d.id
1739                                   LEFT JOIN {user} u ON p.userid = u.id
1740                             WHERE p.id = ?", array($postid));
1743 /**
1744  * Gets posts with all info ready for forum_print_post
1745  * We pass forumid in because we always know it so no need to make a
1746  * complicated join to find it out.
1747  *
1748  * @global object
1749  * @global object
1750  * @return mixed array of posts or false
1751  */
1752 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1753     global $CFG, $DB;
1755     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1756                               FROM {forum_posts} p
1757                          LEFT JOIN {user} u ON p.userid = u.id
1758                              WHERE p.discussion = ?
1759                                AND p.parent > 0 $sort", array($discussion));
1762 /**
1763  * Gets all posts in discussion including top parent.
1764  *
1765  * @global object
1766  * @global object
1767  * @global object
1768  * @param int $discussionid
1769  * @param string $sort
1770  * @param bool $tracking does user track the forum?
1771  * @return array of posts
1772  */
1773 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1774     global $CFG, $DB, $USER;
1776     $tr_sel  = "";
1777     $tr_join = "";
1778     $params = array();
1780     if ($tracking) {
1781         $now = time();
1782         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1783         $tr_sel  = ", fr.id AS postread";
1784         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1785         $params[] = $USER->id;
1786     }
1788     $params[] = $discussionid;
1789     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1790                                      FROM {forum_posts} p
1791                                           LEFT JOIN {user} u ON p.userid = u.id
1792                                           $tr_join
1793                                     WHERE p.discussion = ?
1794                                  ORDER BY $sort", $params)) {
1795         return array();
1796     }
1798     foreach ($posts as $pid=>$p) {
1799         if ($tracking) {
1800             if (forum_tp_is_post_old($p)) {
1801                  $posts[$pid]->postread = true;
1802             }
1803         }
1804         if (!$p->parent) {
1805             continue;
1806         }
1807         if (!isset($posts[$p->parent])) {
1808             continue; // parent does not exist??
1809         }
1810         if (!isset($posts[$p->parent]->children)) {
1811             $posts[$p->parent]->children = array();
1812         }
1813         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1814     }
1816     return $posts;
1819 /**
1820  * Gets posts with all info ready for forum_print_post
1821  * We pass forumid in because we always know it so no need to make a
1822  * complicated join to find it out.
1823  *
1824  * @global object
1825  * @global object
1826  * @param int $parent
1827  * @param int $forumid
1828  * @return array
1829  */
1830 function forum_get_child_posts($parent, $forumid) {
1831     global $CFG, $DB;
1833     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1834                               FROM {forum_posts} p
1835                          LEFT JOIN {user} u ON p.userid = u.id
1836                              WHERE p.parent = ?
1837                           ORDER BY p.created ASC", array($parent));
1840 /**
1841  * An array of forum objects that the user is allowed to read/search through.
1842  *
1843  * @global object
1844  * @global object
1845  * @global object
1846  * @param int $userid
1847  * @param int $courseid if 0, we look for forums throughout the whole site.
1848  * @return array of forum objects, or false if no matches
1849  *         Forum objects have the following attributes:
1850  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1851  *         viewhiddentimedposts
1852  */
1853 function forum_get_readable_forums($userid, $courseid=0) {
1855     global $CFG, $DB, $USER;
1856     require_once($CFG->dirroot.'/course/lib.php');
1858     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1859         print_error('notinstalled', 'forum');
1860     }
1862     if ($courseid) {
1863         $courses = $DB->get_records('course', array('id' => $courseid));
1864     } else {
1865         // If no course is specified, then the user can see SITE + his courses.
1866         $courses1 = $DB->get_records('course', array('id' => SITEID));
1867         $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
1868         $courses = array_merge($courses1, $courses2);
1869     }
1870     if (!$courses) {
1871         return array();
1872     }
1874     $readableforums = array();
1876     foreach ($courses as $course) {
1878         $modinfo = get_fast_modinfo($course);
1879         if (is_null($modinfo->groups)) {
1880             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1881         }
1883         if (empty($modinfo->instances['forum'])) {
1884             // hmm, no forums?
1885             continue;
1886         }
1888         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1890         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1891             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1892                 continue;
1893             }
1894             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1895             $forum = $courseforums[$forumid];
1896             $forum->context = $context;
1897             $forum->cm = $cm;
1899             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1900                 continue;
1901             }
1903          /// group access
1904             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1905                 if (is_null($modinfo->groups)) {
1906                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1907                 }
1908                 if (isset($modinfo->groups[$cm->groupingid])) {
1909                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1910                     $forum->onlygroups[] = -1;
1911                 } else {
1912                     $forum->onlygroups = array(-1);
1913                 }
1914             }
1916         /// hidden timed discussions
1917             $forum->viewhiddentimedposts = true;
1918             if (!empty($CFG->forum_enabletimedposts)) {
1919                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1920                     $forum->viewhiddentimedposts = false;
1921                 }
1922             }
1924         /// qanda access
1925             if ($forum->type == 'qanda'
1926                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1928                 // We need to check whether the user has posted in the qanda forum.
1929                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1930                                                     // the user is allowed to see in this forum.
1931                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1932                     foreach ($discussionspostedin as $d) {
1933                         $forum->onlydiscussions[] = $d->id;
1934                     }
1935                 }
1936             }
1938             $readableforums[$forum->id] = $forum;
1939         }
1941         unset($modinfo);
1943     } // End foreach $courses
1945     return $readableforums;
1948 /**
1949  * Returns a list of posts found using an array of search terms.
1950  *
1951  * @global object
1952  * @global object
1953  * @global object
1954  * @param array $searchterms array of search terms, e.g. word +word -word
1955  * @param int $courseid if 0, we search through the whole site
1956  * @param int $limitfrom
1957  * @param int $limitnum
1958  * @param int &$totalcount
1959  * @param string $extrasql
1960  * @return array|bool Array of posts found or false
1961  */
1962 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1963                             &$totalcount, $extrasql='') {
1964     global $CFG, $DB, $USER;
1965     require_once($CFG->libdir.'/searchlib.php');
1967     $forums = forum_get_readable_forums($USER->id, $courseid);
1969     if (count($forums) == 0) {
1970         $totalcount = 0;
1971         return false;
1972     }
1974     $now = round(time(), -2); // db friendly
1976     $fullaccess = array();
1977     $where = array();
1978     $params = array();
1980     foreach ($forums as $forumid => $forum) {
1981         $select = array();
1983         if (!$forum->viewhiddentimedposts) {
1984             $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
1985             $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
1986         }
1988         $cm = $forum->cm;
1989         $context = $forum->context;
1991         if ($forum->type == 'qanda'
1992             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1993             if (!empty($forum->onlydiscussions)) {
1994                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
1995                 $params = array_merge($params, $discussionid_params);
1996                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
1997             } else {
1998                 $select[] = "p.parent = 0";
1999             }
2000         }
2002         if (!empty($forum->onlygroups)) {
2003             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
2004             $params = array_merge($params, $groupid_params);
2005             $select[] = "d.groupid $groupid_sql";
2006         }
2008         if ($select) {
2009             $selects = implode(" AND ", $select);
2010             $where[] = "(d.forum = :forum{$forumid} AND $selects)";
2011             $params['forum'.$forumid] = $forumid;
2012         } else {
2013             $fullaccess[] = $forumid;
2014         }
2015     }
2017     if ($fullaccess) {
2018         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
2019         $params = array_merge($params, $fullid_params);
2020         $where[] = "(d.forum $fullid_sql)";
2021     }
2023     $selectdiscussion = "(".implode(" OR ", $where).")";
2025     $messagesearch = '';
2026     $searchstring = '';
2028     // Need to concat these back together for parser to work.
2029     foreach($searchterms as $searchterm){
2030         if ($searchstring != '') {
2031             $searchstring .= ' ';
2032         }
2033         $searchstring .= $searchterm;
2034     }
2036     // We need to allow quoted strings for the search. The quotes *should* be stripped
2037     // by the parser, but this should be examined carefully for security implications.
2038     $searchstring = str_replace("\\\"","\"",$searchstring);
2039     $parser = new search_parser();
2040     $lexer = new search_lexer($parser);
2042     if ($lexer->parse($searchstring)) {
2043         $parsearray = $parser->get_parsed_array();
2044     // Experimental feature under 1.8! MDL-8830
2045     // Use alternative text searches if defined
2046     // This feature only works under mysql until properly implemented for other DBs
2047     // Requires manual creation of text index for forum_posts before enabling it:
2048     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2049     // Experimental feature under 1.8! MDL-8830
2050         if (!empty($CFG->forum_usetextsearches)) {
2051             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2052                                                  'p.userid', 'u.id', 'u.firstname',
2053                                                  'u.lastname', 'p.modified', 'd.forum');
2054         } else {
2055             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2056                                                  'p.userid', 'u.id', 'u.firstname',
2057                                                  'u.lastname', 'p.modified', 'd.forum');
2058         }
2059         $params = array_merge($params, $msparams);
2060     }
2062     $fromsql = "{forum_posts} p,
2063                   {forum_discussions} d,
2064                   {user} u";
2066     $selectsql = " $messagesearch
2067                AND p.discussion = d.id
2068                AND p.userid = u.id
2069                AND $selectdiscussion
2070                    $extrasql";
2072     $countsql = "SELECT COUNT(*)
2073                    FROM $fromsql
2074                   WHERE $selectsql";
2076     $searchsql = "SELECT p.*,
2077                          d.forum,
2078                          u.firstname,
2079                          u.lastname,
2080                          u.email,
2081                          u.picture,
2082                          u.imagealt,
2083                          u.email
2084                     FROM $fromsql
2085                    WHERE $selectsql
2086                 ORDER BY p.modified DESC";
2088     $totalcount = $DB->count_records_sql($countsql, $params);
2090     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2093 /**
2094  * Returns a list of ratings for a particular post - sorted.
2095  *
2096  * TODO: Check if this function is actually used anywhere.
2097  * Up until the fix for MDL-27471 this function wasn't even returning.
2098  *
2099  * @param stdClass $context
2100  * @param int $postid
2101  * @param string $sort
2102  * @return array Array of ratings or false
2103  */
2104 function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
2105     $options = new stdClass;
2106     $options->context = $context;
2107     $options->component = 'mod_forum';
2108     $options->ratingarea = 'post';
2109     $options->itemid = $postid;
2110     $options->sort = "ORDER BY $sort";
2112     $rm = new rating_manager();
2113     return $rm->get_all_ratings_for_item($options);
2116 /**
2117  * Returns a list of all new posts that have not been mailed yet
2118  *
2119  * @param int $starttime posts created after this time
2120  * @param int $endtime posts created before this
2121  * @param int $now used for timed discussions only
2122  * @return array
2123  */
2124 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2125     global $CFG, $DB;
2127     $params = array($starttime, $endtime);
2128     if (!empty($CFG->forum_enabletimedposts)) {
2129         if (empty($now)) {
2130             $now = time();
2131         }
2132         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2133         $params[] = $now;
2134         $params[] = $now;
2135     } else {
2136         $timedsql = "";
2137     }
2139     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2140                               FROM {forum_posts} p
2141                                    JOIN {forum_discussions} d ON d.id = p.discussion
2142                              WHERE p.mailed = 0
2143                                    AND p.created >= ?
2144                                    AND (p.created < ? OR p.mailnow = 1)
2145                                    $timedsql
2146                           ORDER BY p.modified ASC", $params);
2149 /**
2150  * Marks posts before a certain time as being mailed already
2151  *
2152  * @global object
2153  * @global object
2154  * @param int $endtime
2155  * @param int $now Defaults to time()
2156  * @return bool
2157  */
2158 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2159     global $CFG, $DB;
2160     if (empty($now)) {
2161         $now = time();
2162     }
2164     if (empty($CFG->forum_enabletimedposts)) {
2165         return $DB->execute("UPDATE {forum_posts}
2166                                SET mailed = '1'
2167                              WHERE (created < ? OR mailnow = 1)
2168                                    AND mailed = 0", array($endtime));
2170     } else {
2171         return $DB->execute("UPDATE {forum_posts}
2172                                SET mailed = '1'
2173                              WHERE discussion NOT IN (SELECT d.id
2174                                                         FROM {forum_discussions} d
2175                                                        WHERE d.timestart > ?)
2176                                    AND (created < ? OR mailnow = 1)
2177                                    AND mailed = 0", array($now, $endtime));
2178     }
2181 /**
2182  * Get all the posts for a user in a forum suitable for forum_print_post
2183  *
2184  * @global object
2185  * @global object
2186  * @uses CONTEXT_MODULE
2187  * @return array
2188  */
2189 function forum_get_user_posts($forumid, $userid) {
2190     global $CFG, $DB;
2192     $timedsql = "";
2193     $params = array($forumid, $userid);
2195     if (!empty($CFG->forum_enabletimedposts)) {
2196         $cm = get_coursemodule_from_instance('forum', $forumid);
2197         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2198             $now = time();
2199             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2200             $params[] = $now;
2201             $params[] = $now;
2202         }
2203     }
2205     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2206                               FROM {forum} f
2207                                    JOIN {forum_discussions} d ON d.forum = f.id
2208                                    JOIN {forum_posts} p       ON p.discussion = d.id
2209                                    JOIN {user} u              ON u.id = p.userid
2210                              WHERE f.id = ?
2211                                    AND p.userid = ?
2212                                    $timedsql
2213                           ORDER BY p.modified ASC", $params);
2216 /**
2217  * Get all the discussions user participated in
2218  *
2219  * @global object
2220  * @global object
2221  * @uses CONTEXT_MODULE
2222  * @param int $forumid
2223  * @param int $userid
2224  * @return array Array or false
2225  */
2226 function forum_get_user_involved_discussions($forumid, $userid) {
2227     global $CFG, $DB;
2229     $timedsql = "";
2230     $params = array($forumid, $userid);
2231     if (!empty($CFG->forum_enabletimedposts)) {
2232         $cm = get_coursemodule_from_instance('forum', $forumid);
2233         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2234             $now = time();
2235             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2236             $params[] = $now;
2237             $params[] = $now;
2238         }
2239     }
2241     return $DB->get_records_sql("SELECT DISTINCT d.*
2242                               FROM {forum} f
2243                                    JOIN {forum_discussions} d ON d.forum = f.id
2244                                    JOIN {forum_posts} p       ON p.discussion = d.id
2245                              WHERE f.id = ?
2246                                    AND p.userid = ?
2247                                    $timedsql", $params);
2250 /**
2251  * Get all the posts for a user in a forum suitable for forum_print_post
2252  *
2253  * @global object
2254  * @global object
2255  * @param int $forumid
2256  * @param int $userid
2257  * @return array of counts or false
2258  */
2259 function forum_count_user_posts($forumid, $userid) {
2260     global $CFG, $DB;
2262     $timedsql = "";
2263     $params = array($forumid, $userid);
2264     if (!empty($CFG->forum_enabletimedposts)) {
2265         $cm = get_coursemodule_from_instance('forum', $forumid);
2266         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2267             $now = time();
2268             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2269             $params[] = $now;
2270             $params[] = $now;
2271         }
2272     }
2274     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2275                              FROM {forum} f
2276                                   JOIN {forum_discussions} d ON d.forum = f.id
2277                                   JOIN {forum_posts} p       ON p.discussion = d.id
2278                                   JOIN {user} u              ON u.id = p.userid
2279                             WHERE f.id = ?
2280                                   AND p.userid = ?
2281                                   $timedsql", $params);
2284 /**
2285  * Given a log entry, return the forum post details for it.
2286  *
2287  * @global object
2288  * @global object
2289  * @param object $log
2290  * @return array|null
2291  */
2292 function forum_get_post_from_log($log) {
2293     global $CFG, $DB;
2295     if ($log->action == "add post") {
2297         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2298                                            u.firstname, u.lastname, u.email, u.picture
2299                                  FROM {forum_discussions} d,
2300                                       {forum_posts} p,
2301                                       {forum} f,
2302                                       {user} u
2303                                 WHERE p.id = ?
2304                                   AND d.id = p.discussion
2305                                   AND p.userid = u.id
2306                                   AND u.deleted <> '1'
2307                                   AND f.id = d.forum", array($log->info));
2310     } else if ($log->action == "add discussion") {
2312         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2313                                            u.firstname, u.lastname, u.email, u.picture
2314                                  FROM {forum_discussions} d,
2315                                       {forum_posts} p,
2316                                       {forum} f,
2317                                       {user} u
2318                                 WHERE d.id = ?
2319                                   AND d.firstpost = p.id
2320                                   AND p.userid = u.id
2321                                   AND u.deleted <> '1'
2322                                   AND f.id = d.forum", array($log->info));
2323     }
2324     return NULL;
2327 /**
2328  * Given a discussion id, return the first post from the discussion
2329  *
2330  * @global object
2331  * @global object
2332  * @param int $dicsussionid
2333  * @return array
2334  */
2335 function forum_get_firstpost_from_discussion($discussionid) {
2336     global $CFG, $DB;
2338     return $DB->get_record_sql("SELECT p.*
2339                              FROM {forum_discussions} d,
2340                                   {forum_posts} p
2341                             WHERE d.id = ?
2342                               AND d.firstpost = p.id ", array($discussionid));
2345 /**
2346  * Returns an array of counts of replies to each discussion
2347  *
2348  * @global object
2349  * @global object
2350  * @param int $forumid
2351  * @param string $forumsort
2352  * @param int $limit
2353  * @param int $page
2354  * @param int $perpage
2355  * @return array
2356  */
2357 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2358     global $CFG, $DB;
2360     if ($limit > 0) {
2361         $limitfrom = 0;
2362         $limitnum  = $limit;
2363     } else if ($page != -1) {
2364         $limitfrom = $page*$perpage;
2365         $limitnum  = $perpage;
2366     } else {
2367         $limitfrom = 0;
2368         $limitnum  = 0;
2369     }
2371     if ($forumsort == "") {
2372         $orderby = "";
2373         $groupby = "";
2375     } else {
2376         $orderby = "ORDER BY $forumsort";
2377         $groupby = ", ".strtolower($forumsort);
2378         $groupby = str_replace('desc', '', $groupby);
2379         $groupby = str_replace('asc', '', $groupby);
2380     }
2382     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2383         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2384                   FROM {forum_posts} p
2385                        JOIN {forum_discussions} d ON p.discussion = d.id
2386                  WHERE p.parent > 0 AND d.forum = ?
2387               GROUP BY p.discussion";
2388         return $DB->get_records_sql($sql, array($forumid));
2390     } else {
2391         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2392                   FROM {forum_posts} p
2393                        JOIN {forum_discussions} d ON p.discussion = d.id
2394                  WHERE d.forum = ?
2395               GROUP BY p.discussion $groupby
2396               $orderby";
2397         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2398     }
2401 /**
2402  * @global object
2403  * @global object
2404  * @global object
2405  * @staticvar array $cache
2406  * @param object $forum
2407  * @param object $cm
2408  * @param object $course
2409  * @return mixed
2410  */
2411 function forum_count_discussions($forum, $cm, $course) {
2412     global $CFG, $DB, $USER;
2414     static $cache = array();
2416     $now = round(time(), -2); // db cache friendliness
2418     $params = array($course->id);
2420     if (!isset($cache[$course->id])) {
2421         if (!empty($CFG->forum_enabletimedposts)) {
2422             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2423             $params[] = $now;
2424             $params[] = $now;
2425         } else {
2426             $timedsql = "";
2427         }
2429         $sql = "SELECT f.id, COUNT(d.id) as dcount
2430                   FROM {forum} f
2431                        JOIN {forum_discussions} d ON d.forum = f.id
2432                  WHERE f.course = ?
2433                        $timedsql
2434               GROUP BY f.id";
2436         if ($counts = $DB->get_records_sql($sql, $params)) {
2437             foreach ($counts as $count) {
2438                 $counts[$count->id] = $count->dcount;
2439             }
2440             $cache[$course->id] = $counts;
2441         } else {
2442             $cache[$course->id] = array();
2443         }
2444     }
2446     if (empty($cache[$course->id][$forum->id])) {
2447         return 0;
2448     }
2450     $groupmode = groups_get_activity_groupmode($cm, $course);
2452     if ($groupmode != SEPARATEGROUPS) {
2453         return $cache[$course->id][$forum->id];
2454     }
2456     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2457         return $cache[$course->id][$forum->id];
2458     }
2460     require_once($CFG->dirroot.'/course/lib.php');
2462     $modinfo = get_fast_modinfo($course);
2463     if (is_null($modinfo->groups)) {
2464         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2465     }
2467     if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2468         $mygroups = $modinfo->groups[$cm->groupingid];
2469     } else {
2470         $mygroups = false; // Will be set below
2471     }
2473     // add all groups posts
2474     if (empty($mygroups)) {
2475         $mygroups = array(-1=>-1);
2476     } else {
2477         $mygroups[-1] = -1;
2478     }
2480     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2481     $params[] = $forum->id;
2483     if (!empty($CFG->forum_enabletimedposts)) {
2484         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2485         $params[] = $now;
2486         $params[] = $now;
2487     } else {
2488         $timedsql = "";
2489     }
2491     $sql = "SELECT COUNT(d.id)
2492               FROM {forum_discussions} d
2493              WHERE d.groupid $mygroups_sql AND d.forum = ?
2494                    $timedsql";
2496     return $DB->get_field_sql($sql, $params);
2499 /**
2500  * How many posts by other users are unrated by a given user in the given discussion?
2501  *
2502  * TODO: Is this function still used anywhere?
2503  *
2504  * @param int $discussionid
2505  * @param int $userid
2506  * @return mixed
2507  */
2508 function forum_count_unrated_posts($discussionid, $userid) {
2509     global $CFG, $DB;
2511     $sql = "SELECT COUNT(*) as num
2512               FROM {forum_posts}
2513              WHERE parent > 0
2514                AND discussion = :discussionid
2515                AND userid <> :userid";
2516     $params = array('discussionid' => $discussionid, 'userid' => $userid);
2517     $posts = $DB->get_record_sql($sql, $params);
2518     if ($posts) {
2519         $sql = "SELECT count(*) as num
2520                   FROM {forum_posts} p,
2521                        {rating} r
2522                  WHERE p.discussion = :discussionid AND
2523                        p.id = r.itemid AND
2524                        r.userid = userid AND
2525                        r.component = 'mod_forum' AND
2526                        r.ratingarea = 'post'";
2527         $rated = $DB->get_record_sql($sql, $params);
2528         if ($rated) {
2529             if ($posts->num > $rated->num) {
2530                 return $posts->num - $rated->num;
2531             } else {
2532                 return 0;    // Just in case there was a counting error
2533             }
2534         } else {
2535             return $posts->num;
2536         }
2537     } else {
2538         return 0;
2539     }
2542 /**
2543  * Get all discussions in a forum
2544  *
2545  * @global object
2546  * @global object
2547  * @global object
2548  * @uses CONTEXT_MODULE
2549  * @uses VISIBLEGROUPS
2550  * @param object $cm
2551  * @param string $forumsort
2552  * @param bool $fullpost
2553  * @param int $unused
2554  * @param int $limit
2555  * @param bool $userlastmodified
2556  * @param int $page
2557  * @param int $perpage
2558  * @return array
2559  */
2560 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2561     global $CFG, $DB, $USER;
2563     $timelimit = '';
2565     $now = round(time(), -2);
2566     $params = array($cm->instance);
2568     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2570     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2571         return array();
2572     }
2574     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2576         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2577             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2578             $params[] = $now;
2579             $params[] = $now;
2580             if (isloggedin()) {
2581                 $timelimit .= " OR d.userid = ?";
2582                 $params[] = $USER->id;
2583             }
2584             $timelimit .= ")";
2585         }
2586     }
2588     if ($limit > 0) {
2589         $limitfrom = 0;
2590         $limitnum  = $limit;
2591     } else if ($page != -1) {
2592         $limitfrom = $page*$perpage;
2593         $limitnum  = $perpage;
2594     } else {
2595         $limitfrom = 0;
2596         $limitnum  = 0;
2597     }
2599     $groupmode    = groups_get_activity_groupmode($cm);
2600     $currentgroup = groups_get_activity_group($cm);
2602     if ($groupmode) {
2603         if (empty($modcontext)) {
2604             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2605         }
2607         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2608             if ($currentgroup) {
2609                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2610                 $params[] = $currentgroup;
2611             } else {
2612                 $groupselect = "";
2613             }
2615         } else {
2616             //seprate groups without access all
2617             if ($currentgroup) {
2618                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2619                 $params[] = $currentgroup;
2620             } else {
2621                 $groupselect = "AND d.groupid = -1";
2622             }
2623         }
2624     } else {
2625         $groupselect = "";
2626     }
2629     if (empty($forumsort)) {
2630         $forumsort = "d.timemodified DESC";
2631     }
2632     if (empty($fullpost)) {
2633         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2634     } else {
2635         $postdata = "p.*";
2636     }
2638     if (empty($userlastmodified)) {  // We don't need to know this
2639         $umfields = "";
2640         $umtable  = "";
2641     } else {
2642         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2643         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2644     }
2646     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2647                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2648               FROM {forum_discussions} d
2649                    JOIN {forum_posts} p ON p.discussion = d.id
2650                    JOIN {user} u ON p.userid = u.id
2651                    $umtable
2652              WHERE d.forum = ? AND p.parent = 0
2653                    $timelimit $groupselect
2654           ORDER BY $forumsort";
2655     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2658 /**
2659  *
2660  * @global object
2661  * @global object
2662  * @global object
2663  * @uses CONTEXT_MODULE
2664  * @uses VISIBLEGROUPS
2665  * @param object $cm
2666  * @return array
2667  */
2668 function forum_get_discussions_unread($cm) {
2669     global $CFG, $DB, $USER;
2671     $now = round(time(), -2);
2672     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2674     $params = array();
2675     $groupmode    = groups_get_activity_groupmode($cm);
2676     $currentgroup = groups_get_activity_group($cm);
2678     if ($groupmode) {
2679         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2681         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2682             if ($currentgroup) {
2683                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2684                 $params['currentgroup'] = $currentgroup;
2685             } else {
2686                 $groupselect = "";
2687             }
2689         } else {
2690             //separate groups without access all
2691             if ($currentgroup) {
2692                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2693                 $params['currentgroup'] = $currentgroup;
2694             } else {
2695                 $groupselect = "AND d.groupid = -1";
2696             }
2697         }
2698     } else {
2699         $groupselect = "";
2700     }
2702     if (!empty($CFG->forum_enabletimedposts)) {
2703         $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2704         $params['now1'] = $now;
2705         $params['now2'] = $now;
2706     } else {
2707         $timedsql = "";
2708     }
2710     $sql = "SELECT d.id, COUNT(p.id) AS unread
2711               FROM {forum_discussions} d
2712                    JOIN {forum_posts} p     ON p.discussion = d.id
2713                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2714              WHERE d.forum = {$cm->instance}
2715                    AND p.modified >= :cutoffdate AND r.id is NULL
2716                    $groupselect
2717                    $timedsql
2718           GROUP BY d.id";
2719     $params['cutoffdate'] = $cutoffdate;
2721     if ($unreads = $DB->get_records_sql($sql, $params)) {
2722         foreach ($unreads as $unread) {
2723             $unreads[$unread->id] = $unread->unread;
2724         }
2725         return $unreads;
2726     } else {
2727         return array();
2728     }
2731 /**
2732  * @global object
2733  * @global object
2734  * @global object
2735  * @uses CONEXT_MODULE
2736  * @uses VISIBLEGROUPS
2737  * @param object $cm
2738  * @return array
2739  */
2740 function forum_get_discussions_count($cm) {
2741     global $CFG, $DB, $USER;
2743     $now = round(time(), -2);
2744     $params = array($cm->instance);
2745     $groupmode    = groups_get_activity_groupmode($cm);
2746     $currentgroup = groups_get_activity_group($cm);
2748     if ($groupmode) {
2749         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2751         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2752             if ($currentgroup) {
2753                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2754                 $params[] = $currentgroup;
2755             } else {
2756                 $groupselect = "";
2757             }
2759         } else {
2760             //seprate groups without access all
2761             if ($currentgroup) {
2762                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2763                 $params[] = $currentgroup;
2764             } else {
2765                 $groupselect = "AND d.groupid = -1";
2766             }
2767         }
2768     } else {
2769         $groupselect = "";
2770     }
2772     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2774     $timelimit = "";
2776     if (!empty($CFG->forum_enabletimedposts)) {
2778         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2780         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2781             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2782             $params[] = $now;
2783             $params[] = $now;
2784             if (isloggedin()) {
2785                 $timelimit .= " OR d.userid = ?";
2786                 $params[] = $USER->id;
2787             }
2788             $timelimit .= ")";
2789         }
2790     }
2792     $sql = "SELECT COUNT(d.id)
2793               FROM {forum_discussions} d
2794                    JOIN {forum_posts} p ON p.discussion = d.id
2795              WHERE d.forum = ? AND p.parent = 0
2796                    $groupselect $timelimit";
2798     return $DB->get_field_sql($sql, $params);
2802 /**
2803  * Get all discussions started by a particular user in a course (or group)
2804  * This function no longer used ...
2805  *
2806  * @todo Remove this function if no longer used
2807  * @global object
2808  * @global object
2809  * @param int $courseid
2810  * @param int $userid
2811  * @param int $groupid
2812  * @return array
2813  */
2814 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2815     global $CFG, $DB;
2816     $params = array($courseid, $userid);
2817     if ($groupid) {
2818         $groupselect = " AND d.groupid = ? ";
2819         $params[] = $groupid;
2820     } else  {
2821         $groupselect = "";
2822     }
2824     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2825                                    f.type as forumtype, f.name as forumname, f.id as forumid
2826                               FROM {forum_discussions} d,
2827                                    {forum_posts} p,
2828                                    {user} u,
2829                                    {forum} f
2830                              WHERE d.course = ?
2831                                AND p.discussion = d.id
2832                                AND p.parent = 0
2833                                AND p.userid = u.id
2834                                AND u.id = ?
2835                                AND d.forum = f.id $groupselect
2836                           ORDER BY p.created DESC", $params);
2839 /**
2840  * Get the list of potential subscribers to a forum.
2841  *
2842  * @param object $forumcontext the forum context.
2843  * @param integer $groupid the id of a group, or 0 for all groups.
2844  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2845  * @param string $sort sort order. As for get_users_by_capability.
2846  * @return array list of users.
2847  */
2848 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2849     global $DB;
2851     // only active enrolled users or everybody on the frontpage
2852     list($esql, $params) = get_enrolled_sql($forumcontext, '', $groupid, true);
2854     $sql = "SELECT $fields
2855               FROM {user} u
2856               JOIN ($esql) je ON je.id = u.id";
2857     if ($sort) {
2858         $sql = "$sql ORDER BY $sort";
2859     } else {
2860         $sql = "$sql ORDER BY u.lastname ASC, u.firstname ASC";
2861     }
2863     return $DB->get_records_sql($sql, $params);
2866 /**
2867  * Returns list of user objects that are subscribed to this forum
2868  *
2869  * @global object
2870  * @global object
2871  * @param object $course the course
2872  * @param forum $forum the forum
2873  * @param integer $groupid group id, or 0 for all.
2874  * @param object $context the forum context, to save re-fetching it where possible.
2875  * @param string $fields requested user fields (with "u." table prefix)
2876  * @return array list of users.
2877  */
2878 function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
2879     global $CFG, $DB;
2881     if (empty($fields)) {
2882         $fields ="u.id,
2883                   u.username,
2884                   u.firstname,
2885                   u.lastname,
2886                   u.maildisplay,
2887                   u.mailformat,
2888                   u.maildigest,
2889                   u.imagealt,
2890                   u.email,
2891                   u.emailstop,
2892                   u.city,
2893                   u.country,
2894                   u.lastaccess,
2895                   u.lastlogin,
2896                   u.picture,
2897                   u.timezone,
2898                   u.theme,
2899                   u.lang,
2900                   u.trackforums,
2901                   u.mnethostid";
2902     }
2904     if (empty($context)) {
2905         $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2906         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2907     }
2909     if (forum_is_forcesubscribed($forum)) {
2910         $results = forum_get_potential_subscribers($context, $groupid, $fields, "u.email ASC");
2912     } else {
2913         // only active enrolled users or everybody on the frontpage
2914         list($esql, $params) = get_enrolled_sql($context, '', $groupid, true);
2915         $params['forumid'] = $forum->id;
2916         $results = $DB->get_records_sql("SELECT $fields
2917                                            FROM {user} u
2918                                            JOIN ($esql) je ON je.id = u.id
2919                                            JOIN {forum_subscriptions} s ON s.userid = u.id
2920                                           WHERE s.forum = :forumid
2921                                        ORDER BY u.email ASC", $params);
2922     }
2924     // Guest user should never be subscribed to a forum.
2925     unset($results[$CFG->siteguest]);
2927     return $results;
2932 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2935 /**
2936  * @global object
2937  * @global object
2938  * @param int $courseid
2939  * @param string $type
2940  */
2941 function forum_get_course_forum($courseid, $type) {
2942 // How to set up special 1-per-course forums
2943     global $CFG, $DB, $OUTPUT;
2945     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2946         // There should always only be ONE, but with the right combination of
2947         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2948         foreach ($forums as $forum) {
2949             return $forum;   // ie the first one
2950         }
2951     }
2953     // Doesn't exist, so create one now.
2954     $forum = new stdClass();
2955     $forum->course = $courseid;
2956     $forum->type = "$type";
2957     switch ($forum->type) {
2958         case "news":
2959             $forum->name  = get_string("namenews", "forum");
2960             $forum->intro = get_string("intronews", "forum");
2961             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2962             $forum->assessed = 0;
2963             if ($courseid == SITEID) {
2964                 $forum->name  = get_string("sitenews");
2965                 $forum->forcesubscribe = 0;
2966             }
2967             break;
2968         case "social":
2969             $forum->name  = get_string("namesocial", "forum");
2970             $forum->intro = get_string("introsocial", "forum");
2971             $forum->assessed = 0;
2972             $forum->forcesubscribe = 0;
2973             break;
2974         case "blog":
2975             $forum->name = get_string('blogforum', 'forum');
2976             $forum->intro = get_string('introblog', 'forum');
2977             $forum->assessed = 0;
2978             $forum->forcesubscribe = 0;
2979             break;
2980         default:
2981             echo $OUTPUT->notification("That forum type doesn't exist!");
2982             return false;
2983             break;
2984     }
2986     $forum->timemodified = time();
2987     $forum->id = $DB->insert_record("forum", $forum);
2989     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2990         echo $OUTPUT->notification("Could not find forum module!!");
2991         return false;
2992     }
2993     $mod = new stdClass();
2994     $mod->course = $courseid;
2995     $mod->module = $module->id;
2996     $mod->instance = $forum->id;
2997     $mod->section = 0;
2998     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
2999         echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
3000         return false;
3001     }
3002     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
3003         echo $OUTPUT->notification("Could not add the new course module to that section");
3004         return false;
3005     }
3006     $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule));
3008     include_once("$CFG->dirroot/course/lib.php");
3009     rebuild_course_cache($courseid);
3011     return $DB->get_record("forum", array("id" => "$forum->id"));
3015 /**
3016  * Given the data about a posting, builds up the HTML to display it and
3017  * returns the HTML in a string.  This is designed for sending via HTML email.
3018  *
3019  * @global object
3020  * @param object $course
3021  * @param object $cm
3022  * @param object $forum
3023  * @param object $discussion
3024  * @param object $post
3025  * @param object $userform
3026  * @param object $userto
3027  * @param bool $ownpost
3028  * @param bool $reply
3029  * @param bool $link
3030  * @param bool $rate
3031  * @param string $footer
3032  * @return string
3033  */
3034 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
3035                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
3037     global $CFG, $OUTPUT;
3039     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3041     if (!isset($userto->viewfullnames[$forum->id])) {
3042         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3043     } else {
3044         $viewfullnames = $userto->viewfullnames[$forum->id];
3045     }
3047     // add absolute file links
3048     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3050     // format the post body
3051     $options = new stdClass();
3052     $options->para = true;
3053     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3055     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3057     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3058     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3059     $output .= '</td>';
3061     if ($post->parent) {
3062         $output .= '<td class="topic">';
3063     } else {
3064         $output .= '<td class="topic starter">';
3065     }
3066     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3068     $fullname = fullname($userfrom, $viewfullnames);
3069     $by = new stdClass();
3070     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3071     $by->date = userdate($post->modified, '', $userto->timezone);
3072     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3074     $output .= '</td></tr>';
3076     $output .= '<tr><td class="left side" valign="top">';
3078     if (isset($userfrom->groups)) {
3079         $groups = $userfrom->groups[$forum->id];
3080     } else {
3081         $groups = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3082     }
3084     if ($groups) {
3085         $output .= print_group_picture($groups, $course->id, false, true, true);
3086     } else {
3087         $output .= '&nbsp;';
3088     }
3090     $output .= '</td><td class="content">';
3092     $attachments = forum_print_attachments($post, $cm, 'html');
3093     if ($attachments !== '') {
3094         $output .= '<div class="attachments">';
3095         $output .= $attachments;
3096         $output .= '</div>';
3097     }
3099     $output .= $formattedtext;
3101 // Commands
3102     $commands = array();
3104     if ($post->parent) {
3105         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3106                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3107     }
3109     if ($reply) {
3110         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3111                       get_string('reply', 'forum').'</a>';
3112     }
3114     $output .= '<div class="commands">';
3115     $output .= implode(' | ', $commands);
3116     $output .= '</div>';
3118 // Context link to post if required
3119     if ($link) {
3120         $output .= '<div class="link">';
3121         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3122                      get_string('postincontext', 'forum').'</a>';
3123         $output .= '</div>';
3124     }
3126     if ($footer) {
3127         $output .= '<div class="footer">'.$footer.'</div>';
3128     }
3129     $output .= '</td></tr></table>'."\n\n";
3131     return $output;
3134 /**
3135  * Print a forum post
3136  *
3137  * @global object
3138  * @global object
3139  * @uses FORUM_MODE_THREADED
3140  * @uses PORTFOLIO_FORMAT_PLAINHTML
3141  * @uses PORTFOLIO_FORMAT_FILE
3142  * @uses PORTFOLIO_FORMAT_RICHHTML
3143  * @uses PORTFOLIO_ADD_TEXT_LINK
3144  * @uses CONTEXT_MODULE
3145  * @param object $post The post to print.
3146  * @param object $discussion
3147  * @param object $forum
3148  * @param object $cm
3149  * @param object $course
3150  * @param boolean $ownpost Whether this post belongs to the current user.
3151  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3152  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3153  * @param string $footer Extra stuff to print after the message.
3154  * @param string $highlight Space-separated list of terms to highlight.
3155  * @param int $post_read true, false or -99. If we already know whether this user
3156  *          has read this post, pass that in, otherwise, pass in -99, and this
3157  *          function will work it out.
3158  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3159  *          the current user can't see this post, if this argument is true
3160  *          (the default) then print a dummy 'you can't see this post' post.
3161  *          If false, don't output anything at all.
3162  * @param bool|null $istracked
3163  * @return void
3164  */
3165 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3166                           $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3167     global $USER, $CFG, $OUTPUT;
3169     require_once($CFG->libdir . '/filelib.php');
3171     // String cache
3172     static $str;
3174     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3176     $post->course = $course->id;
3177     $post->forum  = $forum->id;
3178     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3180     // caching
3181     if (!isset($cm->cache)) {
3182         $cm->cache = new stdClass;
3183     }
3185     if (!isset($cm->cache->caps)) {
3186         $cm->cache->caps = array();
3187         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3188         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3189         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3190         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3191         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3192         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3193         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3194         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3195         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3196     }
3198     if (!isset($cm->uservisible)) {
3199         $cm->uservisible = coursemodule_visible_for_user($cm);
3200     }
3202     if ($istracked && is_null($postisread)) {
3203         $postisread = forum_tp_is_post_read($USER->id, $post);
3204     }
3206     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3207         $output = '';
3208         if (!$dummyifcantsee) {
3209             if ($return) {
3210                 return $output;
3211             }
3212             echo $output;
3213             return;
3214         }
3215         $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3216         $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'));
3217         $output .= html_writer::start_tag('div', array('class'=>'row header'));
3218         $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3219         if ($post->parent) {
3220             $output .= html_writer::start_tag('div', array('class'=>'topic'));
3221         } else {
3222             $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3223         }
3224         $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class'=>'subject')); // Subject
3225         $output .= html_writer::tag('div', get_string('forumauthorhidden','forum'), array('class'=>'author')); // author
3226         $output .= html_writer::end_tag('div');
3227         $output .= html_writer::end_tag('div'); // row
3228         $output .= html_writer::start_tag('div', array('class'=>'row'));
3229         $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3230         $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3231         $output .= html_writer::end_tag('div'); // row
3232         $output .= html_writer::end_tag('div'); // forumpost
3234         if ($return) {
3235             return $output;
3236         }
3237         echo $output;
3238         return;
3239     }
3241     if (empty($str)) {
3242         $str = new stdClass;
3243         $str->edit         = get_string('edit', 'forum');
3244         $str->delete       = get_string('delete', 'forum');
3245         $str->reply        = get_string('reply', 'forum');
3246         $str->parent       = get_string('parent', 'forum');
3247         $str->pruneheading = get_string('pruneheading', 'forum');
3248         $str->prune        = get_string('prune', 'forum');
3249         $str->displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3250         $str->markread     = get_string('markread', 'forum');
3251         $str->markunread   = get_string('markunread', 'forum');
3252     }
3254     $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3256     // Build an object that represents the posting user
3257     $postuser = new stdClass;
3258     $postuser->id        = $post->userid;
3259     $postuser->firstname = $post->firstname;
3260     $postuser->lastname  = $post->lastname;
3261     $postuser->imagealt  = $post->imagealt;
3262     $postuser->picture   = $post->picture;
3263     $postuser->email     = $post->email;
3264     // Some handy things for later on
3265     $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3266     $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3268     // Prepare the groups the posting user belongs to
3269     if (isset($cm->cache->usersgroups)) {
3270         $groups = array();
3271         if (isset($cm->cache->usersgroups[$post->userid])) {
3272             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3273                 $groups[$gid] = $cm->cache->groups[$gid];
3274             }
3275         }
3276     } else {
3277         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3278     }
3280     // Prepare the attachements for the post, files then images
3281     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3283     // Determine if we need to shorten this post
3284     $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3287     // Prepare an array of commands
3288     $commands = array();
3290     // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3291     // Don't display the mark read / unread controls in this case.
3292     if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3293         $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3294         $text = $str->markunread;
3295         if (!$postisread) {
3296             $url->param('mark', 'read');
3297             $text = $str->markread;
3298         }
3299         if ($str->displaymode == FORUM_MODE_THREADED) {
3300             $url->param('parent', $post->parent);
3301         } else {
3302             $url->set_anchor('p'.$post->id);
3303         }
3304         $commands[] = array('url'=>$url, 'text'=>$text);
3305     }
3307     // Zoom in to the parent specifically
3308     if ($post->parent) {
3309         $url = new moodle_url($discussionlink);
3310         if ($str->displaymode == FORUM_MODE_THREADED) {
3311             $url->param('parent', $post->parent);
3312         } else {
3313             $url->set_anchor('p'.$post->parent);
3314         }
3315         $commands[] = array('url'=>$url, 'text'=>$str->parent);
3316     }
3318     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3319     $age = time() - $post->created;
3320     if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3321         $age = 0;
3322     }
3323     if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3324         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3325     }
3327     if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3328         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3329     }
3331     if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3332         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3333     }
3335     if ($reply) {
3336         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('reply'=>$post->id)), 'text'=>$str->reply);
3337     }
3339     if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
3340         $p = array('postid' => $post->id);
3341         require_once($CFG->libdir.'/portfoliolib.php');
3342         $button = new portfolio_add_button();
3343         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php');
3344         if (empty($attachments)) {
3345             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3346         } else {
3347             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3348         }
3350         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3351         if (!empty($porfoliohtml)) {
3352             $commands[] = $porfoliohtml;
3353         }
3354     }
3355     // Finished building commands
3358     // Begin output
3360     $output  = '';
3362     if ($istracked) {
3363         if ($postisread) {
3364             $forumpostclass = ' read';
3365         } else {
3366             $forumpostclass = ' unread';
3367             $output .= html_writer::tag('a', '', array('name'=>'unread'));
3368         }
3369     } else {
3370         // ignore trackign status if not tracked or tracked param missing
3371         $forumpostclass = '';
3372     }
3374     $topicclass = '';
3375     if (empty($post->parent)) {
3376         $topicclass = ' firstpost starter';
3377     }
3379     $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3380     $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass));
3381     $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3382     $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3383     $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3384     $output .= html_writer::end_tag('div');
3387     $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3389     $postsubject = $post->subject;
3390     if (empty($post->subjectnoformat)) {
3391         $postsubject = format_string($postsubject);
3392     }
3393     $output .= html_writer::tag('div', $postsubject, array('class'=>'subject'));
3395     $by = new stdClass();
3396     $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3397     $by->date = userdate($post->modified);
3398     $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author'));
3400     $output .= html_writer::end_tag('div'); //topic
3401     $output .= html_writer::end_tag('div'); //row
3403     $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3404     $output .= html_writer::start_tag('div', array('class'=>'left'));
3406     $groupoutput = '';
3407     if ($groups) {
3408         $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3409     }
3410     if (empty($groupoutput)) {
3411         $groupoutput = '&nbsp;';
3412     }
3413     $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3415     $output .= html_writer::end_tag('div'); //left side
3416     $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3417     $output .= html_writer::start_tag('div', array('class'=>'content'));
3418     if (!empty($attachments)) {
3419         $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3420     }
3422     $options = new stdClass;
3423     $options->para    = false;
3424     $options->trusted = $post->messagetrust;
3425     $options->context = $modcontext;
3426     if ($shortenpost) {
3427         // Prepare shortened version
3428         $postclass    = 'shortenedpost';
3429         $postcontent  = format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3430         $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
3431         $postcontent .= html_writer::tag('span', '('.get_string('numwords', 'moodle', count_words(strip_tags($post->message))).')...', array('class'=>'post-word-count'));
3432     } else {
3433         // Prepare whole post
3434         $postclass    = 'fullpost';
3435         $postcontent  = format_text($post->message, $post->messageformat, $options, $course->id);
3436         if (!empty($highlight)) {
3437             $postcontent = highlight($highlight, $postcontent);
3438         }
3439         $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
3440     }
3441     // Output the post content
3442     $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
3443     $output .= html_writer::end_tag('div'); // Content
3444     $output .= html_writer::end_tag('div'); // Content mask
3445     $output .= html_writer::end_tag('div'); // Row
3447     $output .= html_writer::start_tag('div', array('class'=>'row side'));
3448     $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
3449     $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
3451     // Output ratings
3452     if (!empty($post->rating)) {
3453         $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
3454     }
3456     // Output the commands
3457     $commandhtml = array();
3458     foreach ($commands as $command) {
3459         if (is_array($command)) {
3460             $commandhtml[] = html_writer::link($command['url'], $command['text']);
3461         } else {
3462             $commandhtml[] = $command;
3463         }
3464     }
3465     $output .= html_writer::tag('div', implode(' | ', $commandhtml), array('class'=>'commands'));
3467     // Output link to post if required
3468     if ($link) {
3469         if ($post->replies == 1) {
3470             $replystring = get_string('repliesone', 'forum', $post->replies);
3471         } else {
3472             $replystring = get_string('repliesmany', 'forum', $post->replies);
3473         }
3475         $output .= html_writer::start_tag('div', array('class'=>'link'));
3476         $output .= html_writer::link($discussionlink, get_string('discussthistopic', 'forum'));
3477         $output .= '&nbsp;('.$replystring.')';
3478         $output .= html_writer::end_tag('div'); // link
3479     }
3481     // Output footer if required
3482     if ($footer) {
3483         $output .= html_writer::tag('div', $footer, array('class'=>'footer'));
3484     }
3486     // Close remaining open divs
3487     $output .= html_writer::end_tag('div'); // content
3488     $output .= html_writer::end_tag('div'); // row
3489     $output .= html_writer::end_tag('div'); // forumpost
3491     // Mark the forum post as read if required
3492     if ($istracked && !$CFG->forum_usermarksread && !$postisread) {
3493         forum_tp_mark_post_read($USER->id, $post, $forum->id);
3494     }