Merge branch 'MDL-26562-23' of git://github.com/vadimonus/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');
1384     foreach ($forums as $forum) {
1385         $str = '';
1386         $count = 0;
1387         $thisunread = 0;
1388         $showunread = false;
1389         // either we have something from logs, or trackposts, or nothing.
1390         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1391             $count = $new[$forum->id]->count;
1392         }
1393         if (array_key_exists($forum->id,$unread)) {
1394             $thisunread = $unread[$forum->id]->count;
1395             $showunread = true;
1396         }
1397         if ($count > 0 || $thisunread > 0) {
1398             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1399                 $forum->name.'</a></div>';
1400             $str .= '<div class="info"><span class="postsincelogin">';
1401             $str .= get_string('overviewnumpostssince', 'forum', $count)."</span>";
1402             if (!empty($showunread)) {
1403                 $str .= '<div class="unreadposts">'.get_string('overviewnumunread', 'forum', $thisunread).'</div>';
1404             }
1405             $str .= '</div></div>';
1406         }
1407         if (!empty($str)) {
1408             if (!array_key_exists($forum->course,$htmlarray)) {
1409                 $htmlarray[$forum->course] = array();
1410             }
1411             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1412                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1413             }
1414             $htmlarray[$forum->course]['forum'] .= $str;
1415         }
1416     }
1419 /**
1420  * Given a course and a date, prints a summary of all the new
1421  * messages posted in the course since that date
1422  *
1423  * @global object
1424  * @global object
1425  * @global object
1426  * @uses CONTEXT_MODULE
1427  * @uses VISIBLEGROUPS
1428  * @param object $course
1429  * @param bool $viewfullnames capability
1430  * @param int $timestart
1431  * @return bool success
1432  */
1433 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1434     global $CFG, $USER, $DB, $OUTPUT;
1436     // do not use log table if possible, it may be huge and is expensive to join with other tables
1438     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1439                                               d.timestart, d.timeend, d.userid AS duserid,
1440                                               u.firstname, u.lastname, u.email, u.picture
1441                                          FROM {forum_posts} p
1442                                               JOIN {forum_discussions} d ON d.id = p.discussion
1443                                               JOIN {forum} f             ON f.id = d.forum
1444                                               JOIN {user} u              ON u.id = p.userid
1445                                         WHERE p.created > ? AND f.course = ?
1446                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1447          return false;
1448     }
1450     $modinfo = get_fast_modinfo($course);
1452     $groupmodes = array();
1453     $cms    = array();
1455     $strftimerecent = get_string('strftimerecent');
1457     $printposts = array();
1458     foreach ($posts as $post) {
1459         if (!isset($modinfo->instances['forum'][$post->forum])) {
1460             // not visible
1461             continue;
1462         }
1463         $cm = $modinfo->instances['forum'][$post->forum];
1464         if (!$cm->uservisible) {
1465             continue;
1466         }
1467         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1469         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1470             continue;
1471         }
1473         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1474           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1475             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1476                 continue;
1477             }
1478         }
1480         $groupmode = groups_get_activity_groupmode($cm, $course);
1482         if ($groupmode) {
1483             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1484                 // oki (Open discussions have groupid -1)
1485             } else {
1486                 // separate mode
1487                 if (isguestuser()) {
1488                     // shortcut
1489                     continue;
1490                 }
1492                 if (is_null($modinfo->groups)) {
1493                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1494                 }
1496                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1497                     continue;
1498                 }
1499             }
1500         }
1502         $printposts[] = $post;
1503     }
1504     unset($posts);
1506     if (!$printposts) {
1507         return false;
1508     }
1510     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1511     echo "\n<ul class='unlist'>\n";
1513     foreach ($printposts as $post) {
1514         $subjectclass = empty($post->parent) ? ' bold' : '';
1516         echo '<li><div class="head">'.
1517                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1518                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1519              '</div>';
1520         echo '<div class="info'.$subjectclass.'">';
1521         if (empty($post->parent)) {
1522             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1523         } else {
1524             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1525         }
1526         $post->subject = break_up_long_words(format_string($post->subject, true));
1527         echo $post->subject;
1528         echo "</a>\"</div></li>\n";
1529     }
1531     echo "</ul>\n";
1533     return true;
1536 /**
1537  * Return grade for given user or all users.
1538  *
1539  * @global object
1540  * @global object
1541  * @param object $forum
1542  * @param int $userid optional user id, 0 means all users
1543  * @return array array of grades, false if none
1544  */
1545 function forum_get_user_grades($forum, $userid = 0) {
1546     global $CFG;
1548     require_once($CFG->dirroot.'/rating/lib.php');
1550     $ratingoptions = new stdClass;
1551     $ratingoptions->component = 'mod_forum';
1552     $ratingoptions->ratingarea = 'post';
1554     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1555     $ratingoptions->modulename = 'forum';
1556     $ratingoptions->moduleid   = $forum->id;
1557     $ratingoptions->userid = $userid;
1558     $ratingoptions->aggregationmethod = $forum->assessed;
1559     $ratingoptions->scaleid = $forum->scale;
1560     $ratingoptions->itemtable = 'forum_posts';
1561     $ratingoptions->itemtableusercolumn = 'userid';
1563     $rm = new rating_manager();
1564     return $rm->get_user_grades($ratingoptions);
1567 /**
1568  * Update activity grades
1569  *
1570  * @category grade
1571  * @param object $forum
1572  * @param int $userid specific user only, 0 means all
1573  * @param boolean $nullifnone return null if grade does not exist
1574  * @return void
1575  */
1576 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1577     global $CFG, $DB;
1578     require_once($CFG->libdir.'/gradelib.php');
1580     if (!$forum->assessed) {
1581         forum_grade_item_update($forum);
1583     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1584         forum_grade_item_update($forum, $grades);
1586     } else if ($userid and $nullifnone) {
1587         $grade = new stdClass();
1588         $grade->userid   = $userid;
1589         $grade->rawgrade = NULL;
1590         forum_grade_item_update($forum, $grade);
1592     } else {
1593         forum_grade_item_update($forum);
1594     }
1597 /**
1598  * Update all grades in gradebook.
1599  * @global object
1600  */
1601 function forum_upgrade_grades() {
1602     global $DB;
1604     $sql = "SELECT COUNT('x')
1605               FROM {forum} f, {course_modules} cm, {modules} m
1606              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1607     $count = $DB->count_records_sql($sql);
1609     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1610               FROM {forum} f, {course_modules} cm, {modules} m
1611              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1612     $rs = $DB->get_recordset_sql($sql);
1613     if ($rs->valid()) {
1614         $pbar = new progress_bar('forumupgradegrades', 500, true);
1615         $i=0;
1616         foreach ($rs as $forum) {
1617             $i++;
1618             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1619             forum_update_grades($forum, 0, false);
1620             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1621         }
1622     }
1623     $rs->close();
1626 /**
1627  * Create/update grade item for given forum
1628  *
1629  * @category grade
1630  * @uses GRADE_TYPE_NONE
1631  * @uses GRADE_TYPE_VALUE
1632  * @uses GRADE_TYPE_SCALE
1633  * @param stdClass $forum Forum object with extra cmidnumber
1634  * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
1635  * @return int 0 if ok
1636  */
1637 function forum_grade_item_update($forum, $grades=NULL) {
1638     global $CFG;
1639     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1640         require_once($CFG->libdir.'/gradelib.php');
1641     }
1643     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1645     if (!$forum->assessed or $forum->scale == 0) {
1646         $params['gradetype'] = GRADE_TYPE_NONE;
1648     } else if ($forum->scale > 0) {
1649         $params['gradetype'] = GRADE_TYPE_VALUE;
1650         $params['grademax']  = $forum->scale;
1651         $params['grademin']  = 0;
1653     } else if ($forum->scale < 0) {
1654         $params['gradetype'] = GRADE_TYPE_SCALE;
1655         $params['scaleid']   = -$forum->scale;
1656     }
1658     if ($grades  === 'reset') {
1659         $params['reset'] = true;
1660         $grades = NULL;
1661     }
1663     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1666 /**
1667  * Delete grade item for given forum
1668  *
1669  * @category grade
1670  * @param stdClass $forum Forum object
1671  * @return grade_item
1672  */
1673 function forum_grade_item_delete($forum) {
1674     global $CFG;
1675     require_once($CFG->libdir.'/gradelib.php');
1677     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1681 /**
1682  * This function returns if a scale is being used by one forum
1683  *
1684  * @global object
1685  * @param int $forumid
1686  * @param int $scaleid negative number
1687  * @return bool
1688  */
1689 function forum_scale_used ($forumid,$scaleid) {
1690     global $DB;
1691     $return = false;
1693     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1695     if (!empty($rec) && !empty($scaleid)) {
1696         $return = true;
1697     }
1699     return $return;
1702 /**
1703  * Checks if scale is being used by any instance of forum
1704  *
1705  * This is used to find out if scale used anywhere
1706  *
1707  * @global object
1708  * @param $scaleid int
1709  * @return boolean True if the scale is used by any forum
1710  */
1711 function forum_scale_used_anywhere($scaleid) {
1712     global $DB;
1713     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1714         return true;
1715     } else {
1716         return false;
1717     }
1720 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1722 /**
1723  * Gets a post with all info ready for forum_print_post
1724  * Most of these joins are just to get the forum id
1725  *
1726  * @global object
1727  * @global object
1728  * @param int $postid
1729  * @return mixed array of posts or false
1730  */
1731 function forum_get_post_full($postid) {
1732     global $CFG, $DB;
1734     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1735                              FROM {forum_posts} p
1736                                   JOIN {forum_discussions} d ON p.discussion = d.id
1737                                   LEFT JOIN {user} u ON p.userid = u.id
1738                             WHERE p.id = ?", array($postid));
1741 /**
1742  * Gets posts with all info ready for forum_print_post
1743  * We pass forumid in because we always know it so no need to make a
1744  * complicated join to find it out.
1745  *
1746  * @global object
1747  * @global object
1748  * @return mixed array of posts or false
1749  */
1750 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1751     global $CFG, $DB;
1753     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1754                               FROM {forum_posts} p
1755                          LEFT JOIN {user} u ON p.userid = u.id
1756                              WHERE p.discussion = ?
1757                                AND p.parent > 0 $sort", array($discussion));
1760 /**
1761  * Gets all posts in discussion including top parent.
1762  *
1763  * @global object
1764  * @global object
1765  * @global object
1766  * @param int $discussionid
1767  * @param string $sort
1768  * @param bool $tracking does user track the forum?
1769  * @return array of posts
1770  */
1771 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1772     global $CFG, $DB, $USER;
1774     $tr_sel  = "";
1775     $tr_join = "";
1776     $params = array();
1778     if ($tracking) {
1779         $now = time();
1780         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1781         $tr_sel  = ", fr.id AS postread";
1782         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1783         $params[] = $USER->id;
1784     }
1786     $params[] = $discussionid;
1787     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1788                                      FROM {forum_posts} p
1789                                           LEFT JOIN {user} u ON p.userid = u.id
1790                                           $tr_join
1791                                     WHERE p.discussion = ?
1792                                  ORDER BY $sort", $params)) {
1793         return array();
1794     }
1796     foreach ($posts as $pid=>$p) {
1797         if ($tracking) {
1798             if (forum_tp_is_post_old($p)) {
1799                  $posts[$pid]->postread = true;
1800             }
1801         }
1802         if (!$p->parent) {
1803             continue;
1804         }
1805         if (!isset($posts[$p->parent])) {
1806             continue; // parent does not exist??
1807         }
1808         if (!isset($posts[$p->parent]->children)) {
1809             $posts[$p->parent]->children = array();
1810         }
1811         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1812     }
1814     return $posts;
1817 /**
1818  * Gets posts with all info ready for forum_print_post
1819  * We pass forumid in because we always know it so no need to make a
1820  * complicated join to find it out.
1821  *
1822  * @global object
1823  * @global object
1824  * @param int $parent
1825  * @param int $forumid
1826  * @return array
1827  */
1828 function forum_get_child_posts($parent, $forumid) {
1829     global $CFG, $DB;
1831     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1832                               FROM {forum_posts} p
1833                          LEFT JOIN {user} u ON p.userid = u.id
1834                              WHERE p.parent = ?
1835                           ORDER BY p.created ASC", array($parent));
1838 /**
1839  * An array of forum objects that the user is allowed to read/search through.
1840  *
1841  * @global object
1842  * @global object
1843  * @global object
1844  * @param int $userid
1845  * @param int $courseid if 0, we look for forums throughout the whole site.
1846  * @return array of forum objects, or false if no matches
1847  *         Forum objects have the following attributes:
1848  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1849  *         viewhiddentimedposts
1850  */
1851 function forum_get_readable_forums($userid, $courseid=0) {
1853     global $CFG, $DB, $USER;
1854     require_once($CFG->dirroot.'/course/lib.php');
1856     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1857         print_error('notinstalled', 'forum');
1858     }
1860     if ($courseid) {
1861         $courses = $DB->get_records('course', array('id' => $courseid));
1862     } else {
1863         // If no course is specified, then the user can see SITE + his courses.
1864         $courses1 = $DB->get_records('course', array('id' => SITEID));
1865         $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
1866         $courses = array_merge($courses1, $courses2);
1867     }
1868     if (!$courses) {
1869         return array();
1870     }
1872     $readableforums = array();
1874     foreach ($courses as $course) {
1876         $modinfo = get_fast_modinfo($course);
1877         if (is_null($modinfo->groups)) {
1878             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1879         }
1881         if (empty($modinfo->instances['forum'])) {
1882             // hmm, no forums?
1883             continue;
1884         }
1886         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1888         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1889             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1890                 continue;
1891             }
1892             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1893             $forum = $courseforums[$forumid];
1894             $forum->context = $context;
1895             $forum->cm = $cm;
1897             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1898                 continue;
1899             }
1901          /// group access
1902             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1903                 if (is_null($modinfo->groups)) {
1904                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1905                 }
1906                 if (isset($modinfo->groups[$cm->groupingid])) {
1907                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1908                     $forum->onlygroups[] = -1;
1909                 } else {
1910                     $forum->onlygroups = array(-1);
1911                 }
1912             }
1914         /// hidden timed discussions
1915             $forum->viewhiddentimedposts = true;
1916             if (!empty($CFG->forum_enabletimedposts)) {
1917                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1918                     $forum->viewhiddentimedposts = false;
1919                 }
1920             }
1922         /// qanda access
1923             if ($forum->type == 'qanda'
1924                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1926                 // We need to check whether the user has posted in the qanda forum.
1927                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1928                                                     // the user is allowed to see in this forum.
1929                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1930                     foreach ($discussionspostedin as $d) {
1931                         $forum->onlydiscussions[] = $d->id;
1932                     }
1933                 }
1934             }
1936             $readableforums[$forum->id] = $forum;
1937         }
1939         unset($modinfo);
1941     } // End foreach $courses
1943     return $readableforums;
1946 /**
1947  * Returns a list of posts found using an array of search terms.
1948  *
1949  * @global object
1950  * @global object
1951  * @global object
1952  * @param array $searchterms array of search terms, e.g. word +word -word
1953  * @param int $courseid if 0, we search through the whole site
1954  * @param int $limitfrom
1955  * @param int $limitnum
1956  * @param int &$totalcount
1957  * @param string $extrasql
1958  * @return array|bool Array of posts found or false
1959  */
1960 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1961                             &$totalcount, $extrasql='') {
1962     global $CFG, $DB, $USER;
1963     require_once($CFG->libdir.'/searchlib.php');
1965     $forums = forum_get_readable_forums($USER->id, $courseid);
1967     if (count($forums) == 0) {
1968         $totalcount = 0;
1969         return false;
1970     }
1972     $now = round(time(), -2); // db friendly
1974     $fullaccess = array();
1975     $where = array();
1976     $params = array();
1978     foreach ($forums as $forumid => $forum) {
1979         $select = array();
1981         if (!$forum->viewhiddentimedposts) {
1982             $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
1983             $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
1984         }
1986         $cm = $forum->cm;
1987         $context = $forum->context;
1989         if ($forum->type == 'qanda'
1990             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1991             if (!empty($forum->onlydiscussions)) {
1992                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
1993                 $params = array_merge($params, $discussionid_params);
1994                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
1995             } else {
1996                 $select[] = "p.parent = 0";
1997             }
1998         }
2000         if (!empty($forum->onlygroups)) {
2001             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
2002             $params = array_merge($params, $groupid_params);
2003             $select[] = "d.groupid $groupid_sql";
2004         }
2006         if ($select) {
2007             $selects = implode(" AND ", $select);
2008             $where[] = "(d.forum = :forum{$forumid} AND $selects)";
2009             $params['forum'.$forumid] = $forumid;
2010         } else {
2011             $fullaccess[] = $forumid;
2012         }
2013     }
2015     if ($fullaccess) {
2016         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
2017         $params = array_merge($params, $fullid_params);
2018         $where[] = "(d.forum $fullid_sql)";
2019     }
2021     $selectdiscussion = "(".implode(" OR ", $where).")";
2023     $messagesearch = '';
2024     $searchstring = '';
2026     // Need to concat these back together for parser to work.
2027     foreach($searchterms as $searchterm){
2028         if ($searchstring != '') {
2029             $searchstring .= ' ';
2030         }
2031         $searchstring .= $searchterm;
2032     }
2034     // We need to allow quoted strings for the search. The quotes *should* be stripped
2035     // by the parser, but this should be examined carefully for security implications.
2036     $searchstring = str_replace("\\\"","\"",$searchstring);
2037     $parser = new search_parser();
2038     $lexer = new search_lexer($parser);
2040     if ($lexer->parse($searchstring)) {
2041         $parsearray = $parser->get_parsed_array();
2042     // Experimental feature under 1.8! MDL-8830
2043     // Use alternative text searches if defined
2044     // This feature only works under mysql until properly implemented for other DBs
2045     // Requires manual creation of text index for forum_posts before enabling it:
2046     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2047     // Experimental feature under 1.8! MDL-8830
2048         if (!empty($CFG->forum_usetextsearches)) {
2049             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2050                                                  'p.userid', 'u.id', 'u.firstname',
2051                                                  'u.lastname', 'p.modified', 'd.forum');
2052         } else {
2053             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2054                                                  'p.userid', 'u.id', 'u.firstname',
2055                                                  'u.lastname', 'p.modified', 'd.forum');
2056         }
2057         $params = array_merge($params, $msparams);
2058     }
2060     $fromsql = "{forum_posts} p,
2061                   {forum_discussions} d,
2062                   {user} u";
2064     $selectsql = " $messagesearch
2065                AND p.discussion = d.id
2066                AND p.userid = u.id
2067                AND $selectdiscussion
2068                    $extrasql";
2070     $countsql = "SELECT COUNT(*)
2071                    FROM $fromsql
2072                   WHERE $selectsql";
2074     $searchsql = "SELECT p.*,
2075                          d.forum,
2076                          u.firstname,
2077                          u.lastname,
2078                          u.email,
2079                          u.picture,
2080                          u.imagealt,
2081                          u.email
2082                     FROM $fromsql
2083                    WHERE $selectsql
2084                 ORDER BY p.modified DESC";
2086     $totalcount = $DB->count_records_sql($countsql, $params);
2088     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2091 /**
2092  * Returns a list of ratings for a particular post - sorted.
2093  *
2094  * TODO: Check if this function is actually used anywhere.
2095  * Up until the fix for MDL-27471 this function wasn't even returning.
2096  *
2097  * @param stdClass $context
2098  * @param int $postid
2099  * @param string $sort
2100  * @return array Array of ratings or false
2101  */
2102 function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
2103     $options = new stdClass;
2104     $options->context = $context;
2105     $options->component = 'mod_forum';
2106     $options->ratingarea = 'post';
2107     $options->itemid = $postid;
2108     $options->sort = "ORDER BY $sort";
2110     $rm = new rating_manager();
2111     return $rm->get_all_ratings_for_item($options);
2114 /**
2115  * Returns a list of all new posts that have not been mailed yet
2116  *
2117  * @param int $starttime posts created after this time
2118  * @param int $endtime posts created before this
2119  * @param int $now used for timed discussions only
2120  * @return array
2121  */
2122 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2123     global $CFG, $DB;
2125     $params = array($starttime, $endtime);
2126     if (!empty($CFG->forum_enabletimedposts)) {
2127         if (empty($now)) {
2128             $now = time();
2129         }
2130         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2131         $params[] = $now;
2132         $params[] = $now;
2133     } else {
2134         $timedsql = "";
2135     }
2137     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2138                               FROM {forum_posts} p
2139                                    JOIN {forum_discussions} d ON d.id = p.discussion
2140                              WHERE p.mailed = 0
2141                                    AND p.created >= ?
2142                                    AND (p.created < ? OR p.mailnow = 1)
2143                                    $timedsql
2144                           ORDER BY p.modified ASC", $params);
2147 /**
2148  * Marks posts before a certain time as being mailed already
2149  *
2150  * @global object
2151  * @global object
2152  * @param int $endtime
2153  * @param int $now Defaults to time()
2154  * @return bool
2155  */
2156 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2157     global $CFG, $DB;
2158     if (empty($now)) {
2159         $now = time();
2160     }
2162     if (empty($CFG->forum_enabletimedposts)) {
2163         return $DB->execute("UPDATE {forum_posts}
2164                                SET mailed = '1'
2165                              WHERE (created < ? OR mailnow = 1)
2166                                    AND mailed = 0", array($endtime));
2168     } else {
2169         return $DB->execute("UPDATE {forum_posts}
2170                                SET mailed = '1'
2171                              WHERE discussion NOT IN (SELECT d.id
2172                                                         FROM {forum_discussions} d
2173                                                        WHERE d.timestart > ?)
2174                                    AND (created < ? OR mailnow = 1)
2175                                    AND mailed = 0", array($now, $endtime));
2176     }
2179 /**
2180  * Get all the posts for a user in a forum suitable for forum_print_post
2181  *
2182  * @global object
2183  * @global object
2184  * @uses CONTEXT_MODULE
2185  * @return array
2186  */
2187 function forum_get_user_posts($forumid, $userid) {
2188     global $CFG, $DB;
2190     $timedsql = "";
2191     $params = array($forumid, $userid);
2193     if (!empty($CFG->forum_enabletimedposts)) {
2194         $cm = get_coursemodule_from_instance('forum', $forumid);
2195         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2196             $now = time();
2197             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2198             $params[] = $now;
2199             $params[] = $now;
2200         }
2201     }
2203     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2204                               FROM {forum} f
2205                                    JOIN {forum_discussions} d ON d.forum = f.id
2206                                    JOIN {forum_posts} p       ON p.discussion = d.id
2207                                    JOIN {user} u              ON u.id = p.userid
2208                              WHERE f.id = ?
2209                                    AND p.userid = ?
2210                                    $timedsql
2211                           ORDER BY p.modified ASC", $params);
2214 /**
2215  * Get all the discussions user participated in
2216  *
2217  * @global object
2218  * @global object
2219  * @uses CONTEXT_MODULE
2220  * @param int $forumid
2221  * @param int $userid
2222  * @return array Array or false
2223  */
2224 function forum_get_user_involved_discussions($forumid, $userid) {
2225     global $CFG, $DB;
2227     $timedsql = "";
2228     $params = array($forumid, $userid);
2229     if (!empty($CFG->forum_enabletimedposts)) {
2230         $cm = get_coursemodule_from_instance('forum', $forumid);
2231         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2232             $now = time();
2233             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2234             $params[] = $now;
2235             $params[] = $now;
2236         }
2237     }
2239     return $DB->get_records_sql("SELECT DISTINCT d.*
2240                               FROM {forum} f
2241                                    JOIN {forum_discussions} d ON d.forum = f.id
2242                                    JOIN {forum_posts} p       ON p.discussion = d.id
2243                              WHERE f.id = ?
2244                                    AND p.userid = ?
2245                                    $timedsql", $params);
2248 /**
2249  * Get all the posts for a user in a forum suitable for forum_print_post
2250  *
2251  * @global object
2252  * @global object
2253  * @param int $forumid
2254  * @param int $userid
2255  * @return array of counts or false
2256  */
2257 function forum_count_user_posts($forumid, $userid) {
2258     global $CFG, $DB;
2260     $timedsql = "";
2261     $params = array($forumid, $userid);
2262     if (!empty($CFG->forum_enabletimedposts)) {
2263         $cm = get_coursemodule_from_instance('forum', $forumid);
2264         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2265             $now = time();
2266             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2267             $params[] = $now;
2268             $params[] = $now;
2269         }
2270     }
2272     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2273                              FROM {forum} f
2274                                   JOIN {forum_discussions} d ON d.forum = f.id
2275                                   JOIN {forum_posts} p       ON p.discussion = d.id
2276                                   JOIN {user} u              ON u.id = p.userid
2277                             WHERE f.id = ?
2278                                   AND p.userid = ?
2279                                   $timedsql", $params);
2282 /**
2283  * Given a log entry, return the forum post details for it.
2284  *
2285  * @global object
2286  * @global object
2287  * @param object $log
2288  * @return array|null
2289  */
2290 function forum_get_post_from_log($log) {
2291     global $CFG, $DB;
2293     if ($log->action == "add post") {
2295         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2296                                            u.firstname, u.lastname, u.email, u.picture
2297                                  FROM {forum_discussions} d,
2298                                       {forum_posts} p,
2299                                       {forum} f,
2300                                       {user} u
2301                                 WHERE p.id = ?
2302                                   AND d.id = p.discussion
2303                                   AND p.userid = u.id
2304                                   AND u.deleted <> '1'
2305                                   AND f.id = d.forum", array($log->info));
2308     } else if ($log->action == "add discussion") {
2310         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2311                                            u.firstname, u.lastname, u.email, u.picture
2312                                  FROM {forum_discussions} d,
2313                                       {forum_posts} p,
2314                                       {forum} f,
2315                                       {user} u
2316                                 WHERE d.id = ?
2317                                   AND d.firstpost = p.id
2318                                   AND p.userid = u.id
2319                                   AND u.deleted <> '1'
2320                                   AND f.id = d.forum", array($log->info));
2321     }
2322     return NULL;
2325 /**
2326  * Given a discussion id, return the first post from the discussion
2327  *
2328  * @global object
2329  * @global object
2330  * @param int $dicsussionid
2331  * @return array
2332  */
2333 function forum_get_firstpost_from_discussion($discussionid) {
2334     global $CFG, $DB;
2336     return $DB->get_record_sql("SELECT p.*
2337                              FROM {forum_discussions} d,
2338                                   {forum_posts} p
2339                             WHERE d.id = ?
2340                               AND d.firstpost = p.id ", array($discussionid));
2343 /**
2344  * Returns an array of counts of replies to each discussion
2345  *
2346  * @global object
2347  * @global object
2348  * @param int $forumid
2349  * @param string $forumsort
2350  * @param int $limit
2351  * @param int $page
2352  * @param int $perpage
2353  * @return array
2354  */
2355 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2356     global $CFG, $DB;
2358     if ($limit > 0) {
2359         $limitfrom = 0;
2360         $limitnum  = $limit;
2361     } else if ($page != -1) {
2362         $limitfrom = $page*$perpage;
2363         $limitnum  = $perpage;
2364     } else {
2365         $limitfrom = 0;
2366         $limitnum  = 0;
2367     }
2369     if ($forumsort == "") {
2370         $orderby = "";
2371         $groupby = "";
2373     } else {
2374         $orderby = "ORDER BY $forumsort";
2375         $groupby = ", ".strtolower($forumsort);
2376         $groupby = str_replace('desc', '', $groupby);
2377         $groupby = str_replace('asc', '', $groupby);
2378     }
2380     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2381         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2382                   FROM {forum_posts} p
2383                        JOIN {forum_discussions} d ON p.discussion = d.id
2384                  WHERE p.parent > 0 AND d.forum = ?
2385               GROUP BY p.discussion";
2386         return $DB->get_records_sql($sql, array($forumid));
2388     } else {
2389         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2390                   FROM {forum_posts} p
2391                        JOIN {forum_discussions} d ON p.discussion = d.id
2392                  WHERE d.forum = ?
2393               GROUP BY p.discussion $groupby
2394               $orderby";
2395         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2396     }
2399 /**
2400  * @global object
2401  * @global object
2402  * @global object
2403  * @staticvar array $cache
2404  * @param object $forum
2405  * @param object $cm
2406  * @param object $course
2407  * @return mixed
2408  */
2409 function forum_count_discussions($forum, $cm, $course) {
2410     global $CFG, $DB, $USER;
2412     static $cache = array();
2414     $now = round(time(), -2); // db cache friendliness
2416     $params = array($course->id);
2418     if (!isset($cache[$course->id])) {
2419         if (!empty($CFG->forum_enabletimedposts)) {
2420             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2421             $params[] = $now;
2422             $params[] = $now;
2423         } else {
2424             $timedsql = "";
2425         }
2427         $sql = "SELECT f.id, COUNT(d.id) as dcount
2428                   FROM {forum} f
2429                        JOIN {forum_discussions} d ON d.forum = f.id
2430                  WHERE f.course = ?
2431                        $timedsql
2432               GROUP BY f.id";
2434         if ($counts = $DB->get_records_sql($sql, $params)) {
2435             foreach ($counts as $count) {
2436                 $counts[$count->id] = $count->dcount;
2437             }
2438             $cache[$course->id] = $counts;
2439         } else {
2440             $cache[$course->id] = array();
2441         }
2442     }
2444     if (empty($cache[$course->id][$forum->id])) {
2445         return 0;
2446     }
2448     $groupmode = groups_get_activity_groupmode($cm, $course);
2450     if ($groupmode != SEPARATEGROUPS) {
2451         return $cache[$course->id][$forum->id];
2452     }
2454     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2455         return $cache[$course->id][$forum->id];
2456     }
2458     require_once($CFG->dirroot.'/course/lib.php');
2460     $modinfo = get_fast_modinfo($course);
2461     if (is_null($modinfo->groups)) {
2462         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2463     }
2465     if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2466         $mygroups = $modinfo->groups[$cm->groupingid];
2467     } else {
2468         $mygroups = false; // Will be set below
2469     }
2471     // add all groups posts
2472     if (empty($mygroups)) {
2473         $mygroups = array(-1=>-1);
2474     } else {
2475         $mygroups[-1] = -1;
2476     }
2478     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2479     $params[] = $forum->id;
2481     if (!empty($CFG->forum_enabletimedposts)) {
2482         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2483         $params[] = $now;
2484         $params[] = $now;
2485     } else {
2486         $timedsql = "";
2487     }
2489     $sql = "SELECT COUNT(d.id)
2490               FROM {forum_discussions} d
2491              WHERE d.groupid $mygroups_sql AND d.forum = ?
2492                    $timedsql";
2494     return $DB->get_field_sql($sql, $params);
2497 /**
2498  * How many posts by other users are unrated by a given user in the given discussion?
2499  *
2500  * TODO: Is this function still used anywhere?
2501  *
2502  * @param int $discussionid
2503  * @param int $userid
2504  * @return mixed
2505  */
2506 function forum_count_unrated_posts($discussionid, $userid) {
2507     global $CFG, $DB;
2509     $sql = "SELECT COUNT(*) as num
2510               FROM {forum_posts}
2511              WHERE parent > 0
2512                AND discussion = :discussionid
2513                AND userid <> :userid";
2514     $params = array('discussionid' => $discussionid, 'userid' => $userid);
2515     $posts = $DB->get_record_sql($sql, $params);
2516     if ($posts) {
2517         $sql = "SELECT count(*) as num
2518                   FROM {forum_posts} p,
2519                        {rating} r
2520                  WHERE p.discussion = :discussionid AND
2521                        p.id = r.itemid AND
2522                        r.userid = userid AND
2523                        r.component = 'mod_forum' AND
2524                        r.ratingarea = 'post'";
2525         $rated = $DB->get_record_sql($sql, $params);
2526         if ($rated) {
2527             if ($posts->num > $rated->num) {
2528                 return $posts->num - $rated->num;
2529             } else {
2530                 return 0;    // Just in case there was a counting error
2531             }
2532         } else {
2533             return $posts->num;
2534         }
2535     } else {
2536         return 0;
2537     }
2540 /**
2541  * Get all discussions in a forum
2542  *
2543  * @global object
2544  * @global object
2545  * @global object
2546  * @uses CONTEXT_MODULE
2547  * @uses VISIBLEGROUPS
2548  * @param object $cm
2549  * @param string $forumsort
2550  * @param bool $fullpost
2551  * @param int $unused
2552  * @param int $limit
2553  * @param bool $userlastmodified
2554  * @param int $page
2555  * @param int $perpage
2556  * @return array
2557  */
2558 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2559     global $CFG, $DB, $USER;
2561     $timelimit = '';
2563     $now = round(time(), -2);
2564     $params = array($cm->instance);
2566     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2568     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2569         return array();
2570     }
2572     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2574         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2575             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2576             $params[] = $now;
2577             $params[] = $now;
2578             if (isloggedin()) {
2579                 $timelimit .= " OR d.userid = ?";
2580                 $params[] = $USER->id;
2581             }
2582             $timelimit .= ")";
2583         }
2584     }
2586     if ($limit > 0) {
2587         $limitfrom = 0;
2588         $limitnum  = $limit;
2589     } else if ($page != -1) {
2590         $limitfrom = $page*$perpage;
2591         $limitnum  = $perpage;
2592     } else {
2593         $limitfrom = 0;
2594         $limitnum  = 0;
2595     }
2597     $groupmode    = groups_get_activity_groupmode($cm);
2598     $currentgroup = groups_get_activity_group($cm);
2600     if ($groupmode) {
2601         if (empty($modcontext)) {
2602             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2603         }
2605         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2606             if ($currentgroup) {
2607                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2608                 $params[] = $currentgroup;
2609             } else {
2610                 $groupselect = "";
2611             }
2613         } else {
2614             //seprate groups without access all
2615             if ($currentgroup) {
2616                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2617                 $params[] = $currentgroup;
2618             } else {
2619                 $groupselect = "AND d.groupid = -1";
2620             }
2621         }
2622     } else {
2623         $groupselect = "";
2624     }
2627     if (empty($forumsort)) {
2628         $forumsort = "d.timemodified DESC";
2629     }
2630     if (empty($fullpost)) {
2631         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2632     } else {
2633         $postdata = "p.*";
2634     }
2636     if (empty($userlastmodified)) {  // We don't need to know this
2637         $umfields = "";
2638         $umtable  = "";
2639     } else {
2640         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2641         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2642     }
2644     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2645                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2646               FROM {forum_discussions} d
2647                    JOIN {forum_posts} p ON p.discussion = d.id
2648                    JOIN {user} u ON p.userid = u.id
2649                    $umtable
2650              WHERE d.forum = ? AND p.parent = 0
2651                    $timelimit $groupselect
2652           ORDER BY $forumsort";
2653     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2656 /**
2657  *
2658  * @global object
2659  * @global object
2660  * @global object
2661  * @uses CONTEXT_MODULE
2662  * @uses VISIBLEGROUPS
2663  * @param object $cm
2664  * @return array
2665  */
2666 function forum_get_discussions_unread($cm) {
2667     global $CFG, $DB, $USER;
2669     $now = round(time(), -2);
2670     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2672     $params = array();
2673     $groupmode    = groups_get_activity_groupmode($cm);
2674     $currentgroup = groups_get_activity_group($cm);
2676     if ($groupmode) {
2677         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2679         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2680             if ($currentgroup) {
2681                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2682                 $params['currentgroup'] = $currentgroup;
2683             } else {
2684                 $groupselect = "";
2685             }
2687         } else {
2688             //separate groups without access all
2689             if ($currentgroup) {
2690                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2691                 $params['currentgroup'] = $currentgroup;
2692             } else {
2693                 $groupselect = "AND d.groupid = -1";
2694             }
2695         }
2696     } else {
2697         $groupselect = "";
2698     }
2700     if (!empty($CFG->forum_enabletimedposts)) {
2701         $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2702         $params['now1'] = $now;
2703         $params['now2'] = $now;
2704     } else {
2705         $timedsql = "";
2706     }
2708     $sql = "SELECT d.id, COUNT(p.id) AS unread
2709               FROM {forum_discussions} d
2710                    JOIN {forum_posts} p     ON p.discussion = d.id
2711                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2712              WHERE d.forum = {$cm->instance}
2713                    AND p.modified >= :cutoffdate AND r.id is NULL
2714                    $groupselect
2715                    $timedsql
2716           GROUP BY d.id";
2717     $params['cutoffdate'] = $cutoffdate;
2719     if ($unreads = $DB->get_records_sql($sql, $params)) {
2720         foreach ($unreads as $unread) {
2721             $unreads[$unread->id] = $unread->unread;
2722         }
2723         return $unreads;
2724     } else {
2725         return array();
2726     }
2729 /**
2730  * @global object
2731  * @global object
2732  * @global object
2733  * @uses CONEXT_MODULE
2734  * @uses VISIBLEGROUPS
2735  * @param object $cm
2736  * @return array
2737  */
2738 function forum_get_discussions_count($cm) {
2739     global $CFG, $DB, $USER;
2741     $now = round(time(), -2);
2742     $params = array($cm->instance);
2743     $groupmode    = groups_get_activity_groupmode($cm);
2744     $currentgroup = groups_get_activity_group($cm);
2746     if ($groupmode) {
2747         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2749         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2750             if ($currentgroup) {
2751                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2752                 $params[] = $currentgroup;
2753             } else {
2754                 $groupselect = "";
2755             }
2757         } else {
2758             //seprate groups without access all
2759             if ($currentgroup) {
2760                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2761                 $params[] = $currentgroup;
2762             } else {
2763                 $groupselect = "AND d.groupid = -1";
2764             }
2765         }
2766     } else {
2767         $groupselect = "";
2768     }
2770     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2772     $timelimit = "";
2774     if (!empty($CFG->forum_enabletimedposts)) {
2776         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2778         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2779             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2780             $params[] = $now;
2781             $params[] = $now;
2782             if (isloggedin()) {
2783                 $timelimit .= " OR d.userid = ?";
2784                 $params[] = $USER->id;
2785             }
2786             $timelimit .= ")";
2787         }
2788     }
2790     $sql = "SELECT COUNT(d.id)
2791               FROM {forum_discussions} d
2792                    JOIN {forum_posts} p ON p.discussion = d.id
2793              WHERE d.forum = ? AND p.parent = 0
2794                    $groupselect $timelimit";
2796     return $DB->get_field_sql($sql, $params);
2800 /**
2801  * Get all discussions started by a particular user in a course (or group)
2802  * This function no longer used ...
2803  *
2804  * @todo Remove this function if no longer used
2805  * @global object
2806  * @global object
2807  * @param int $courseid
2808  * @param int $userid
2809  * @param int $groupid
2810  * @return array
2811  */
2812 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2813     global $CFG, $DB;
2814     $params = array($courseid, $userid);
2815     if ($groupid) {
2816         $groupselect = " AND d.groupid = ? ";
2817         $params[] = $groupid;
2818     } else  {
2819         $groupselect = "";
2820     }
2822     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2823                                    f.type as forumtype, f.name as forumname, f.id as forumid
2824                               FROM {forum_discussions} d,
2825                                    {forum_posts} p,
2826                                    {user} u,
2827                                    {forum} f
2828                              WHERE d.course = ?
2829                                AND p.discussion = d.id
2830                                AND p.parent = 0
2831                                AND p.userid = u.id
2832                                AND u.id = ?
2833                                AND d.forum = f.id $groupselect
2834                           ORDER BY p.created DESC", $params);
2837 /**
2838  * Get the list of potential subscribers to a forum.
2839  *
2840  * @param object $forumcontext the forum context.
2841  * @param integer $groupid the id of a group, or 0 for all groups.
2842  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2843  * @param string $sort sort order. As for get_users_by_capability.
2844  * @return array list of users.
2845  */
2846 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2847     global $DB;
2849     // only active enrolled users or everybody on the frontpage
2850     list($esql, $params) = get_enrolled_sql($forumcontext, '', $groupid, true);
2852     $sql = "SELECT $fields
2853               FROM {user} u
2854               JOIN ($esql) je ON je.id = u.id";
2855     if ($sort) {
2856         $sql = "$sql ORDER BY $sort";
2857     } else {
2858         $sql = "$sql ORDER BY u.lastname ASC, u.firstname ASC";
2859     }
2861     return $DB->get_records_sql($sql, $params);
2864 /**
2865  * Returns list of user objects that are subscribed to this forum
2866  *
2867  * @global object
2868  * @global object
2869  * @param object $course the course
2870  * @param forum $forum the forum
2871  * @param integer $groupid group id, or 0 for all.
2872  * @param object $context the forum context, to save re-fetching it where possible.
2873  * @param string $fields requested user fields (with "u." table prefix)
2874  * @return array list of users.
2875  */
2876 function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
2877     global $CFG, $DB;
2879     if (empty($fields)) {
2880         $fields ="u.id,
2881                   u.username,
2882                   u.firstname,
2883                   u.lastname,
2884                   u.maildisplay,
2885                   u.mailformat,
2886                   u.maildigest,
2887                   u.imagealt,
2888                   u.email,
2889                   u.emailstop,
2890                   u.city,
2891                   u.country,
2892                   u.lastaccess,
2893                   u.lastlogin,
2894                   u.picture,
2895                   u.timezone,
2896                   u.theme,
2897                   u.lang,
2898                   u.trackforums,
2899                   u.mnethostid";
2900     }
2902     if (empty($context)) {
2903         $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2904         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2905     }
2907     if (forum_is_forcesubscribed($forum)) {
2908         $results = forum_get_potential_subscribers($context, $groupid, $fields, "u.email ASC");
2910     } else {
2911         // only active enrolled users or everybody on the frontpage
2912         list($esql, $params) = get_enrolled_sql($context, '', $groupid, true);
2913         $params['forumid'] = $forum->id;
2914         $results = $DB->get_records_sql("SELECT $fields
2915                                            FROM {user} u
2916                                            JOIN ($esql) je ON je.id = u.id
2917                                            JOIN {forum_subscriptions} s ON s.userid = u.id
2918                                           WHERE s.forum = :forumid
2919                                        ORDER BY u.email ASC", $params);
2920     }
2922     // Guest user should never be subscribed to a forum.
2923     unset($results[$CFG->siteguest]);
2925     return $results;
2930 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2933 /**
2934  * @global object
2935  * @global object
2936  * @param int $courseid
2937  * @param string $type
2938  */
2939 function forum_get_course_forum($courseid, $type) {
2940 // How to set up special 1-per-course forums
2941     global $CFG, $DB, $OUTPUT;
2943     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2944         // There should always only be ONE, but with the right combination of
2945         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2946         foreach ($forums as $forum) {
2947             return $forum;   // ie the first one
2948         }
2949     }
2951     // Doesn't exist, so create one now.
2952     $forum = new stdClass();
2953     $forum->course = $courseid;
2954     $forum->type = "$type";
2955     switch ($forum->type) {
2956         case "news":
2957             $forum->name  = get_string("namenews", "forum");
2958             $forum->intro = get_string("intronews", "forum");
2959             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2960             $forum->assessed = 0;
2961             if ($courseid == SITEID) {
2962                 $forum->name  = get_string("sitenews");
2963                 $forum->forcesubscribe = 0;
2964             }
2965             break;
2966         case "social":
2967             $forum->name  = get_string("namesocial", "forum");
2968             $forum->intro = get_string("introsocial", "forum");
2969             $forum->assessed = 0;
2970             $forum->forcesubscribe = 0;
2971             break;
2972         case "blog":
2973             $forum->name = get_string('blogforum', 'forum');
2974             $forum->intro = get_string('introblog', 'forum');
2975             $forum->assessed = 0;
2976             $forum->forcesubscribe = 0;
2977             break;
2978         default:
2979             echo $OUTPUT->notification("That forum type doesn't exist!");
2980             return false;
2981             break;
2982     }
2984     $forum->timemodified = time();
2985     $forum->id = $DB->insert_record("forum", $forum);
2987     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2988         echo $OUTPUT->notification("Could not find forum module!!");
2989         return false;
2990     }
2991     $mod = new stdClass();
2992     $mod->course = $courseid;
2993     $mod->module = $module->id;
2994     $mod->instance = $forum->id;
2995     $mod->section = 0;
2996     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
2997         echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
2998         return false;
2999     }
3000     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
3001         echo $OUTPUT->notification("Could not add the new course module to that section");
3002         return false;
3003     }
3004     $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule));
3006     include_once("$CFG->dirroot/course/lib.php");
3007     rebuild_course_cache($courseid);
3009     return $DB->get_record("forum", array("id" => "$forum->id"));
3013 /**
3014  * Given the data about a posting, builds up the HTML to display it and
3015  * returns the HTML in a string.  This is designed for sending via HTML email.
3016  *
3017  * @global object
3018  * @param object $course
3019  * @param object $cm
3020  * @param object $forum
3021  * @param object $discussion
3022  * @param object $post
3023  * @param object $userform
3024  * @param object $userto
3025  * @param bool $ownpost
3026  * @param bool $reply
3027  * @param bool $link
3028  * @param bool $rate
3029  * @param string $footer
3030  * @return string
3031  */
3032 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
3033                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
3035     global $CFG, $OUTPUT;
3037     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3039     if (!isset($userto->viewfullnames[$forum->id])) {
3040         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3041     } else {
3042         $viewfullnames = $userto->viewfullnames[$forum->id];
3043     }
3045     // add absolute file links
3046     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3048     // format the post body
3049     $options = new stdClass();
3050     $options->para = true;
3051     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3053     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3055     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3056     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3057     $output .= '</td>';
3059     if ($post->parent) {
3060         $output .= '<td class="topic">';
3061     } else {
3062         $output .= '<td class="topic starter">';
3063     }
3064     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3066     $fullname = fullname($userfrom, $viewfullnames);
3067     $by = new stdClass();
3068     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3069     $by->date = userdate($post->modified, '', $userto->timezone);
3070     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3072     $output .= '</td></tr>';
3074     $output .= '<tr><td class="left side" valign="top">';
3076     if (isset($userfrom->groups)) {
3077         $groups = $userfrom->groups[$forum->id];
3078     } else {
3079         $groups = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3080     }
3082     if ($groups) {
3083         $output .= print_group_picture($groups, $course->id, false, true, true);
3084     } else {
3085         $output .= '&nbsp;';
3086     }
3088     $output .= '</td><td class="content">';
3090     $attachments = forum_print_attachments($post, $cm, 'html');
3091     if ($attachments !== '') {
3092         $output .= '<div class="attachments">';
3093         $output .= $attachments;
3094         $output .= '</div>';
3095     }
3097     $output .= $formattedtext;
3099 // Commands
3100     $commands = array();
3102     if ($post->parent) {
3103         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3104                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3105     }
3107     if ($reply) {
3108         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3109                       get_string('reply', 'forum').'</a>';
3110     }
3112     $output .= '<div class="commands">';
3113     $output .= implode(' | ', $commands);
3114     $output .= '</div>';
3116 // Context link to post if required
3117     if ($link) {
3118         $output .= '<div class="link">';
3119         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3120                      get_string('postincontext', 'forum').'</a>';
3121         $output .= '</div>';
3122     }
3124     if ($footer) {
3125         $output .= '<div class="footer">'.$footer.'</div>';
3126     }
3127     $output .= '</td></tr></table>'."\n\n";
3129     return $output;
3132 /**
3133  * Print a forum post
3134  *
3135  * @global object
3136  * @global object
3137  * @uses FORUM_MODE_THREADED
3138  * @uses PORTFOLIO_FORMAT_PLAINHTML
3139  * @uses PORTFOLIO_FORMAT_FILE
3140  * @uses PORTFOLIO_FORMAT_RICHHTML
3141  * @uses PORTFOLIO_ADD_TEXT_LINK
3142  * @uses CONTEXT_MODULE
3143  * @param object $post The post to print.
3144  * @param object $discussion
3145  * @param object $forum
3146  * @param object $cm
3147  * @param object $course
3148  * @param boolean $ownpost Whether this post belongs to the current user.
3149  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3150  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3151  * @param string $footer Extra stuff to print after the message.
3152  * @param string $highlight Space-separated list of terms to highlight.
3153  * @param int $post_read true, false or -99. If we already know whether this user
3154  *          has read this post, pass that in, otherwise, pass in -99, and this
3155  *          function will work it out.
3156  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3157  *          the current user can't see this post, if this argument is true
3158  *          (the default) then print a dummy 'you can't see this post' post.
3159  *          If false, don't output anything at all.
3160  * @param bool|null $istracked
3161  * @return void
3162  */
3163 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3164                           $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3165     global $USER, $CFG, $OUTPUT;
3167     require_once($CFG->libdir . '/filelib.php');
3169     // String cache
3170     static $str;
3172     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3174     $post->course = $course->id;
3175     $post->forum  = $forum->id;
3176     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3178     // caching
3179     if (!isset($cm->cache)) {
3180         $cm->cache = new stdClass;
3181     }
3183     if (!isset($cm->cache->caps)) {
3184         $cm->cache->caps = array();
3185         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3186         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3187         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3188         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3189         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3190         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3191         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3192         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3193         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3194     }
3196     if (!isset($cm->uservisible)) {
3197         $cm->uservisible = coursemodule_visible_for_user($cm);
3198     }
3200     if ($istracked && is_null($postisread)) {
3201         $postisread = forum_tp_is_post_read($USER->id, $post);
3202     }
3204     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3205         $output = '';
3206         if (!$dummyifcantsee) {
3207             if ($return) {
3208                 return $output;
3209             }
3210             echo $output;
3211             return;
3212         }
3213         $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3214         $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'));
3215         $output .= html_writer::start_tag('div', array('class'=>'row header'));
3216         $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3217         if ($post->parent) {
3218             $output .= html_writer::start_tag('div', array('class'=>'topic'));
3219         } else {
3220             $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3221         }
3222         $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class'=>'subject')); // Subject
3223         $output .= html_writer::tag('div', get_string('forumauthorhidden','forum'), array('class'=>'author')); // author
3224         $output .= html_writer::end_tag('div');
3225         $output .= html_writer::end_tag('div'); // row
3226         $output .= html_writer::start_tag('div', array('class'=>'row'));
3227         $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3228         $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3229         $output .= html_writer::end_tag('div'); // row
3230         $output .= html_writer::end_tag('div'); // forumpost
3232         if ($return) {
3233             return $output;
3234         }
3235         echo $output;
3236         return;
3237     }
3239     if (empty($str)) {
3240         $str = new stdClass;
3241         $str->edit         = get_string('edit', 'forum');
3242         $str->delete       = get_string('delete', 'forum');
3243         $str->reply        = get_string('reply', 'forum');
3244         $str->parent       = get_string('parent', 'forum');
3245         $str->pruneheading = get_string('pruneheading', 'forum');
3246         $str->prune        = get_string('prune', 'forum');
3247         $str->displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3248         $str->markread     = get_string('markread', 'forum');
3249         $str->markunread   = get_string('markunread', 'forum');
3250     }
3252     $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3254     // Build an object that represents the posting user
3255     $postuser = new stdClass;
3256     $postuser->id        = $post->userid;
3257     $postuser->firstname = $post->firstname;
3258     $postuser->lastname  = $post->lastname;
3259     $postuser->imagealt  = $post->imagealt;
3260     $postuser->picture   = $post->picture;
3261     $postuser->email     = $post->email;
3262     // Some handy things for later on
3263     $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3264     $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3266     // Prepare the groups the posting user belongs to
3267     if (isset($cm->cache->usersgroups)) {
3268         $groups = array();
3269         if (isset($cm->cache->usersgroups[$post->userid])) {
3270             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3271                 $groups[$gid] = $cm->cache->groups[$gid];
3272             }
3273         }
3274     } else {
3275         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3276     }
3278     // Prepare the attachements for the post, files then images
3279     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3281     // Determine if we need to shorten this post
3282     $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3285     // Prepare an array of commands
3286     $commands = array();
3288     // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3289     // Don't display the mark read / unread controls in this case.
3290     if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3291         $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3292         $text = $str->markunread;
3293         if (!$postisread) {
3294             $url->param('mark', 'read');
3295             $text = $str->markread;
3296         }
3297         if ($str->displaymode == FORUM_MODE_THREADED) {
3298             $url->param('parent', $post->parent);
3299         } else {
3300             $url->set_anchor('p'.$post->id);
3301         }
3302         $commands[] = array('url'=>$url, 'text'=>$text);
3303     }
3305     // Zoom in to the parent specifically
3306     if ($post->parent) {
3307         $url = new moodle_url($discussionlink);
3308         if ($str->displaymode == FORUM_MODE_THREADED) {
3309             $url->param('parent', $post->parent);
3310         } else {
3311             $url->set_anchor('p'.$post->parent);
3312         }
3313         $commands[] = array('url'=>$url, 'text'=>$str->parent);
3314     }
3316     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3317     $age = time() - $post->created;
3318     if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3319         $age = 0;
3320     }
3321     if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3322         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3323     }
3325     if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3326         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3327     }
3329     if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3330         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3331     }
3333     if ($reply) {
3334         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('reply'=>$post->id)), 'text'=>$str->reply);
3335     }
3337     if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
3338         $p = array('postid' => $post->id);
3339         require_once($CFG->libdir.'/portfoliolib.php');
3340         $button = new portfolio_add_button();
3341         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php');
3342         if (empty($attachments)) {
3343             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3344         } else {
3345             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3346         }
3348         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3349         if (!empty($porfoliohtml)) {
3350             $commands[] = $porfoliohtml;
3351         }
3352     }
3353     // Finished building commands
3356     // Begin output
3358     $output  = '';
3360     if ($istracked) {
3361         if ($postisread) {
3362             $forumpostclass = ' read';
3363         } else {
3364             $forumpostclass = ' unread';
3365             $output .= html_writer::tag('a', '', array('name'=>'unread'));
3366         }
3367     } else {
3368         // ignore trackign status if not tracked or tracked param missing
3369         $forumpostclass = '';
3370     }
3372     $topicclass = '';
3373     if (empty($post->parent)) {
3374         $topicclass = ' firstpost starter';
3375     }
3377     $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3378     $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass));
3379     $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3380     $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3381     $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3382     $output .= html_writer::end_tag('div');
3385     $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3387     $postsubject = $post->subject;
3388     if (empty($post->subjectnoformat)) {
3389         $postsubject = format_string($postsubject);
3390     }
3391     $output .= html_writer::tag('div', $postsubject, array('class'=>'subject'));
3393     $by = new stdClass();
3394     $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3395     $by->date = userdate($post->modified);
3396     $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author'));
3398     $output .= html_writer::end_tag('div'); //topic
3399     $output .= html_writer::end_tag('div'); //row
3401     $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3402     $output .= html_writer::start_tag('div', array('class'=>'left'));
3404     $groupoutput = '';
3405     if ($groups) {
3406         $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3407     }
3408     if (empty($groupoutput)) {
3409         $groupoutput = '&nbsp;';
3410     }
3411     $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3413     $output .= html_writer::end_tag('div'); //left side
3414     $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3415     $output .= html_writer::start_tag('div', array('class'=>'content'));
3416     if (!empty($attachments)) {
3417         $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3418     }
3420     $options = new stdClass;
3421     $options->para    = false;
3422     $options->trusted = $post->messagetrust;
3423     $options->context = $modcontext;
3424     if ($shortenpost) {
3425         // Prepare shortened version
3426         $postclass    = 'shortenedpost';
3427         $postcontent  = format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3428         $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
3429         $postcontent .= html_writer::tag('span', '('.get_string('numwords', 'moodle', count_words(strip_tags($post->message))).')...', array('class'=>'post-word-count'));
3430     } else {
3431         // Prepare whole post
3432         $postclass    = 'fullpost';
3433         $postcontent  = format_text($post->message, $post->messageformat, $options, $course->id);
3434         if (!empty($highlight)) {
3435             $postcontent = highlight($highlight, $postcontent);
3436         }
3437         $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
3438     }
3439     // Output the post content
3440     $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
3441     $output .= html_writer::end_tag('div'); // Content
3442     $output .= html_writer::end_tag('div'); // Content mask
3443     $output .= html_writer::end_tag('div'); // Row
3445     $output .= html_writer::start_tag('div', array('class'=>'row side'));
3446     $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
3447     $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
3449     // Output ratings
3450     if (!empty($post->rating)) {
3451         $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
3452     }
3454     // Output the commands
3455     $commandhtml = array();
3456     foreach ($commands as $command) {
3457         if (is_array($command)) {
3458             $commandhtml[] = html_writer::link($command['url'], $command['text']);
3459         } else {
3460             $commandhtml[] = $command;
3461         }
3462     }
3463     $output .= html_writer::tag('div', implode(' | ', $commandhtml), array('class'=>'commands'));
3465     // Output link to post if required
3466     if ($link) {
3467         if ($post->replies == 1) {
3468             $replystring = get_string('repliesone', 'forum', $post->replies);
3469         } else {
3470             $replystring = get_string('repliesmany', 'forum', $post->replies);
3471         }
3473         $output .= html_writer::start_tag('div', array('class'=>'link'));
3474         $output .= html_writer::link($discussionlink, get_string('discussthistopic', 'forum'));
3475         $output .= '&nbsp;('.$replystring.')';
3476         $output .= html_writer::end_tag('div'); // link
3477     }
3479     // Output footer if required
3480     if ($footer) {
3481         $output .= html_writer::tag('div', $footer, array('class'=>'footer'));
3482     }
3484     // Close remaining open divs
3485     $output .= html_writer::end_tag('div'); // content
3486     $output .= html_writer::end_tag('div'); // row
3487     $output .= html_writer::end_tag('div'); // forumpost
3489     // Mark the forum post as read if required
3490     if ($istracked && !$CFG->forum_usermarksread && !$postisread) {
3491         forum_tp_mark_post_read($USER->id, $post, $forum->id);
3492     }
3494     if ($return) {
3495         return $output;