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