Merge branch 'MDL-37048-master' of git://github.com/ankitagarwal/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                 $mailresult = message_send($eventdata);
719                 if (!$mailresult){
720                     mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
721                          " ($userto->email) .. not trying again.");
722                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
723                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
724                     $errorcount[$post->id]++;
725                 } else {
726                     $mailcount[$post->id]++;
728                 // Mark post as read if forum_usermarksread is set off
729                     if (!$CFG->forum_usermarksread) {
730                         $userto->markposts[$post->id] = $post->id;
731                     }
732                 }
734                 mtrace('post '.$post->id. ': '.$post->subject);
735             }
737             // mark processed posts as read
738             forum_tp_mark_posts_read($userto, $userto->markposts);
739             unset($userto);
740         }
741     }
743     if ($posts) {
744         foreach ($posts as $post) {
745             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
746             if ($errorcount[$post->id]) {
747                 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
748             }
749         }
750     }
752     // release some memory
753     unset($subscribedusers);
754     unset($mailcount);
755     unset($errorcount);
757     cron_setup_user();
759     $sitetimezone = $CFG->timezone;
761     // Now see if there are any digest mails waiting to be sent, and if we should send them
763     mtrace('Starting digest processing...');
765     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
767     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
768         set_config('digestmailtimelast', 0);
769     }
771     $timenow = time();
772     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
774     // Delete any really old ones (normally there shouldn't be any)
775     $weekago = $timenow - (7 * 24 * 3600);
776     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
777     mtrace ('Cleaned old digest records');
779     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
781         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
783         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
785         if ($digestposts_rs->valid()) {
787             // We have work to do
788             $usermailcount = 0;
790             //caches - reuse the those filled before too
791             $discussionposts = array();
792             $userdiscussions = array();
794             foreach ($digestposts_rs as $digestpost) {
795                 if (!isset($posts[$digestpost->postid])) {
796                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
797                         $posts[$digestpost->postid] = $post;
798                     } else {
799                         continue;
800                     }
801                 }
802                 $discussionid = $digestpost->discussionid;
803                 if (!isset($discussions[$discussionid])) {
804                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
805                         $discussions[$discussionid] = $discussion;
806                     } else {
807                         continue;
808                     }
809                 }
810                 $forumid = $discussions[$discussionid]->forum;
811                 if (!isset($forums[$forumid])) {
812                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
813                         $forums[$forumid] = $forum;
814                     } else {
815                         continue;
816                     }
817                 }
819                 $courseid = $forums[$forumid]->course;
820                 if (!isset($courses[$courseid])) {
821                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
822                         $courses[$courseid] = $course;
823                     } else {
824                         continue;
825                     }
826                 }
828                 if (!isset($coursemodules[$forumid])) {
829                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
830                         $coursemodules[$forumid] = $cm;
831                     } else {
832                         continue;
833                     }
834                 }
835                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
836                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
837             }
838             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
840             // Data collected, start sending out emails to each user
841             foreach ($userdiscussions as $userid => $thesediscussions) {
843                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
845                 cron_setup_user();
847                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
849                 // First of all delete all the queue entries for this user
850                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
852                 // Init user caches - we keep the cache for one cycle only,
853                 // otherwise it would unnecessarily consume memory.
854                 if (array_key_exists($userid, $users) and isset($users[$userid]->username)) {
855                     $userto = clone($users[$userid]);
856                 } else {
857                     $userto = $DB->get_record('user', array('id' => $userid));
858                     forum_cron_minimise_user_record($userto);
859                 }
860                 $userto->viewfullnames = array();
861                 $userto->canpost       = array();
862                 $userto->markposts     = array();
864                 // Override the language and timezone of the "current" user, so that
865                 // mail is customised for the receiver.
866                 cron_setup_user($userto);
868                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
870                 $headerdata = new stdClass();
871                 $headerdata->sitename = format_string($site->fullname, true);
872                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
874                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
875                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
877                 $posthtml = "<head>";
878 /*                foreach ($CFG->stylesheets as $stylesheet) {
879                     //TODO: MDL-21120
880                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
881                 }*/
882                 $posthtml .= "</head>\n<body id=\"email\">\n";
883                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
885                 foreach ($thesediscussions as $discussionid) {
887                     @set_time_limit(120);   // to be reset for each post
889                     $discussion = $discussions[$discussionid];
890                     $forum      = $forums[$discussion->forum];
891                     $course     = $courses[$forum->course];
892                     $cm         = $coursemodules[$forum->id];
894                     //override language
895                     cron_setup_user($userto, $course);
897                     // Fill caches
898                     if (!isset($userto->viewfullnames[$forum->id])) {
899                         $modcontext = context_module::instance($cm->id);
900                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
901                     }
902                     if (!isset($userto->canpost[$discussion->id])) {
903                         $modcontext = context_module::instance($cm->id);
904                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
905                     }
907                     $strforums      = get_string('forums', 'forum');
908                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
909                     $canreply       = $userto->canpost[$discussion->id];
910                     $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
912                     $posttext .= "\n \n";
913                     $posttext .= '=====================================================================';
914                     $posttext .= "\n \n";
915                     $posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
916                     if ($discussion->name != $forum->name) {
917                         $posttext  .= " -> ".format_string($discussion->name,true);
918                     }
919                     $posttext .= "\n";
921                     $posthtml .= "<p><font face=\"sans-serif\">".
922                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
923                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
924                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
925                     if ($discussion->name == $forum->name) {
926                         $posthtml .= "</font></p>";
927                     } else {
928                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
929                     }
930                     $posthtml .= '<p>';
932                     $postsarray = $discussionposts[$discussionid];
933                     sort($postsarray);
935                     foreach ($postsarray as $postid) {
936                         $post = $posts[$postid];
938                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
939                             $userfrom = $users[$post->userid];
940                             if (!isset($userfrom->idnumber)) {
941                                 $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
942                                 forum_cron_minimise_user_record($userfrom);
943                             }
945                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
946                             forum_cron_minimise_user_record($userfrom);
947                             if ($userscount <= FORUM_CRON_USER_CACHE) {
948                                 $userscount++;
949                                 $users[$userfrom->id] = $userfrom;
950                             }
952                         } else {
953                             mtrace('Could not find user '.$post->userid);
954                             continue;
955                         }
957                         if (!isset($userfrom->groups[$forum->id])) {
958                             if (!isset($userfrom->groups)) {
959                                 $userfrom->groups = array();
960                                 if (isset($users[$userfrom->id])) {
961                                     $users[$userfrom->id]->groups = array();
962                                 }
963                             }
964                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
965                             if (isset($users[$userfrom->id])) {
966                                 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
967                             }
968                         }
970                         $userfrom->customheaders = array ("Precedence: Bulk");
972                         if ($userto->maildigest == 2) {
973                             // Subjects only
974                             $by = new stdClass();
975                             $by->name = fullname($userfrom);
976                             $by->date = userdate($post->modified);
977                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
978                             $posttext .= "\n---------------------------------------------------------------------";
980                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
981                             $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>';
983                         } else {
984                             // The full treatment
985                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
986                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
988                         // Create an array of postid's for this user to mark as read.
989                             if (!$CFG->forum_usermarksread) {
990                                 $userto->markposts[$post->id] = $post->id;
991                             }
992                         }
993                     }
994                     if ($canunsubscribe) {
995                         $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>";
996                     } else {
997                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
998                     }
999                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
1000                 }
1001                 $posthtml .= '</body>';
1003                 if (empty($userto->mailformat) || $userto->mailformat != 1) {
1004                     // This user DOESN'T want to receive HTML
1005                     $posthtml = '';
1006                 }
1008                 $attachment = $attachname='';
1009                 $usetrueaddress = true;
1010                 // Directly email forum digests rather than sending them via messaging, use the
1011                 // site shortname as 'from name', the noreply address will be used by email_to_user.
1012                 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname, $usetrueaddress, $CFG->forum_replytouser);
1014                 if (!$mailresult) {
1015                     mtrace("ERROR!");
1016                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
1017                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
1018                 } else {
1019                     mtrace("success.");
1020                     $usermailcount++;
1022                     // Mark post as read if forum_usermarksread is set off
1023                     forum_tp_mark_posts_read($userto, $userto->markposts);
1024                 }
1025             }
1026         }
1027     /// We have finishied all digest emails, update $CFG->digestmailtimelast
1028         set_config('digestmailtimelast', $timenow);
1029     }
1031     cron_setup_user();
1033     if (!empty($usermailcount)) {
1034         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
1035     }
1037     if (!empty($CFG->forum_lastreadclean)) {
1038         $timenow = time();
1039         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
1040             set_config('forum_lastreadclean', $timenow);
1041             mtrace('Removing old forum read tracking info...');
1042             forum_tp_clean_read_records();
1043         }
1044     } else {
1045         set_config('forum_lastreadclean', time());
1046     }
1049     return true;
1052 /**
1053  * Builds and returns the body of the email notification in plain text.
1054  *
1055  * @global object
1056  * @global object
1057  * @uses CONTEXT_MODULE
1058  * @param object $course
1059  * @param object $cm
1060  * @param object $forum
1061  * @param object $discussion
1062  * @param object $post
1063  * @param object $userfrom
1064  * @param object $userto
1065  * @param boolean $bare
1066  * @return string The email body in plain text format.
1067  */
1068 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
1069     global $CFG, $USER;
1071     $modcontext = context_module::instance($cm->id);
1073     if (!isset($userto->viewfullnames[$forum->id])) {
1074         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
1075     } else {
1076         $viewfullnames = $userto->viewfullnames[$forum->id];
1077     }
1079     if (!isset($userto->canpost[$discussion->id])) {
1080         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
1081     } else {
1082         $canreply = $userto->canpost[$discussion->id];
1083     }
1085     $by = New stdClass;
1086     $by->name = fullname($userfrom, $viewfullnames);
1087     $by->date = userdate($post->modified, "", $userto->timezone);
1089     $strbynameondate = get_string('bynameondate', 'forum', $by);
1091     $strforums = get_string('forums', 'forum');
1093     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1095     $posttext = '';
1097     if (!$bare) {
1098         $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
1099         $posttext  = "$shortname -> $strforums -> ".format_string($forum->name,true);
1101         if ($discussion->name != $forum->name) {
1102             $posttext  .= " -> ".format_string($discussion->name,true);
1103         }
1104     }
1106     // add absolute file links
1107     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
1109     $posttext .= "\n---------------------------------------------------------------------\n";
1110     $posttext .= format_string($post->subject,true);
1111     if ($bare) {
1112         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1113     }
1114     $posttext .= "\n".$strbynameondate."\n";
1115     $posttext .= "---------------------------------------------------------------------\n";
1116     $posttext .= format_text_email($post->message, $post->messageformat);
1117     $posttext .= "\n\n";
1118     $posttext .= forum_print_attachments($post, $cm, "text");
1120     if (!$bare && $canreply) {
1121         $posttext .= "---------------------------------------------------------------------\n";
1122         $posttext .= get_string("postmailinfo", "forum", $shortname)."\n";
1123         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1124     }
1125     if (!$bare && $canunsubscribe) {
1126         $posttext .= "\n---------------------------------------------------------------------\n";
1127         $posttext .= get_string("unsubscribe", "forum");
1128         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1129     }
1131     return $posttext;
1134 /**
1135  * Builds and returns the body of the email notification in html format.
1136  *
1137  * @global object
1138  * @param object $course
1139  * @param object $cm
1140  * @param object $forum
1141  * @param object $discussion
1142  * @param object $post
1143  * @param object $userfrom
1144  * @param object $userto
1145  * @return string The email text in HTML format
1146  */
1147 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1148     global $CFG;
1150     if ($userto->mailformat != 1) {  // Needs to be HTML
1151         return '';
1152     }
1154     if (!isset($userto->canpost[$discussion->id])) {
1155         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course);
1156     } else {
1157         $canreply = $userto->canpost[$discussion->id];
1158     }
1160     $strforums = get_string('forums', 'forum');
1161     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1162     $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
1164     $posthtml = '<head>';
1165 /*    foreach ($CFG->stylesheets as $stylesheet) {
1166         //TODO: MDL-21120
1167         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1168     }*/
1169     $posthtml .= '</head>';
1170     $posthtml .= "\n<body id=\"email\">\n\n";
1172     $posthtml .= '<div class="navbar">'.
1173     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$shortname.'</a> &raquo; '.
1174     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1175     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1176     if ($discussion->name == $forum->name) {
1177         $posthtml .= '</div>';
1178     } else {
1179         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1180                      format_string($discussion->name,true).'</a></div>';
1181     }
1182     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1184     if ($canunsubscribe) {
1185         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1186                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1187                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1188     }
1190     $posthtml .= '</body>';
1192     return $posthtml;
1196 /**
1197  *
1198  * @param object $course
1199  * @param object $user
1200  * @param object $mod TODO this is not used in this function, refactor
1201  * @param object $forum
1202  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1203  */
1204 function forum_user_outline($course, $user, $mod, $forum) {
1205     global $CFG;
1206     require_once("$CFG->libdir/gradelib.php");
1207     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1208     if (empty($grades->items[0]->grades)) {
1209         $grade = false;
1210     } else {
1211         $grade = reset($grades->items[0]->grades);
1212     }
1214     $count = forum_count_user_posts($forum->id, $user->id);
1216     if ($count && $count->postcount > 0) {
1217         $result = new stdClass();
1218         $result->info = get_string("numposts", "forum", $count->postcount);
1219         $result->time = $count->lastpost;
1220         if ($grade) {
1221             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1222         }
1223         return $result;
1224     } else if ($grade) {
1225         $result = new stdClass();
1226         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1228         //datesubmitted == time created. dategraded == time modified or time overridden
1229         //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
1230         //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
1231         if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1232             $result->time = $grade->dategraded;
1233         } else {
1234             $result->time = $grade->datesubmitted;
1235         }
1237         return $result;
1238     }
1239     return NULL;
1243 /**
1244  * @global object
1245  * @global object
1246  * @param object $coure
1247  * @param object $user
1248  * @param object $mod
1249  * @param object $forum
1250  */
1251 function forum_user_complete($course, $user, $mod, $forum) {
1252     global $CFG,$USER, $OUTPUT;
1253     require_once("$CFG->libdir/gradelib.php");
1255     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1256     if (!empty($grades->items[0]->grades)) {
1257         $grade = reset($grades->items[0]->grades);
1258         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1259         if ($grade->str_feedback) {
1260             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1261         }
1262     }
1264     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1266         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1267             print_error('invalidcoursemodule');
1268         }
1269         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1271         foreach ($posts as $post) {
1272             if (!isset($discussions[$post->discussion])) {
1273                 continue;
1274             }
1275             $discussion = $discussions[$post->discussion];
1277             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1278         }
1279     } else {
1280         echo "<p>".get_string("noposts", "forum")."</p>";
1281     }
1289 /**
1290  * @global object
1291  * @global object
1292  * @global object
1293  * @param array $courses
1294  * @param array $htmlarray
1295  */
1296 function forum_print_overview($courses,&$htmlarray) {
1297     global $USER, $CFG, $DB, $SESSION;
1299     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1300         return array();
1301     }
1303     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1304         return;
1305     }
1307     // Courses to search for new posts
1308     $coursessqls = array();
1309     $params = array();
1310     foreach ($courses as $course) {
1312         // If the user has never entered into the course all posts are pending
1313         if ($course->lastaccess == 0) {
1314             $coursessqls[] = '(f.course = ?)';
1315             $params[] = $course->id;
1317         // Only posts created after the course last access
1318         } else {
1319             $coursessqls[] = '(f.course = ? AND p.created > ?)';
1320             $params[] = $course->id;
1321             $params[] = $course->lastaccess;
1322         }
1323     }
1324     $params[] = $USER->id;
1325     $coursessql = implode(' OR ', $coursessqls);
1327     $sql = "SELECT f.id, COUNT(*) as count "
1328                 .'FROM {forum} f '
1329                 .'JOIN {forum_discussions} d ON d.forum  = f.id '
1330                 .'JOIN {forum_posts} p ON p.discussion = d.id '
1331                 ."WHERE ($coursessql) "
1332                 .'AND p.userid != ? '
1333                 .'GROUP BY f.id';
1335     if (!$new = $DB->get_records_sql($sql, $params)) {
1336         $new = array(); // avoid warnings
1337     }
1339     // also get all forum tracking stuff ONCE.
1340     $trackingforums = array();
1341     foreach ($forums as $forum) {
1342         if (forum_tp_can_track_forums($forum)) {
1343             $trackingforums[$forum->id] = $forum;
1344         }
1345     }
1347     if (count($trackingforums) > 0) {
1348         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1349         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1350             ' FROM {forum_posts} p '.
1351             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1352             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1353         $params = array($USER->id);
1355         foreach ($trackingforums as $track) {
1356             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1357             $params[] = $track->id;
1358             if (isset($SESSION->currentgroup[$track->course])) {
1359                 $groupid =  $SESSION->currentgroup[$track->course];
1360             } else {
1361                 // get first groupid
1362                 $groupids = groups_get_all_groups($track->course, $USER->id);
1363                 if ($groupids) {
1364                     reset($groupids);
1365                     $groupid = key($groupids);
1366                     $SESSION->currentgroup[$track->course] = $groupid;
1367                 } else {
1368                     $groupid = 0;
1369                 }
1370                 unset($groupids);
1371             }
1372             $params[] = $groupid;
1373         }
1374         $sql = substr($sql,0,-3); // take off the last OR
1375         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1376         $params[] = $cutoffdate;
1378         if (!$unread = $DB->get_records_sql($sql, $params)) {
1379             $unread = array();
1380         }
1381     } else {
1382         $unread = array();
1383     }
1385     if (empty($unread) and empty($new)) {
1386         return;
1387     }
1389     $strforum = get_string('modulename','forum');
1391     foreach ($forums as $forum) {
1392         $str = '';
1393         $count = 0;
1394         $thisunread = 0;
1395         $showunread = false;
1396         // either we have something from logs, or trackposts, or nothing.
1397         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1398             $count = $new[$forum->id]->count;
1399         }
1400         if (array_key_exists($forum->id,$unread)) {
1401             $thisunread = $unread[$forum->id]->count;
1402             $showunread = true;
1403         }
1404         if ($count > 0 || $thisunread > 0) {
1405             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1406                 $forum->name.'</a></div>';
1407             $str .= '<div class="info"><span class="postsincelogin">';
1408             $str .= get_string('overviewnumpostssince', 'forum', $count)."</span>";
1409             if (!empty($showunread)) {
1410                 $str .= '<div class="unreadposts">'.get_string('overviewnumunread', 'forum', $thisunread).'</div>';
1411             }
1412             $str .= '</div></div>';
1413         }
1414         if (!empty($str)) {
1415             if (!array_key_exists($forum->course,$htmlarray)) {
1416                 $htmlarray[$forum->course] = array();
1417             }
1418             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1419                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1420             }
1421             $htmlarray[$forum->course]['forum'] .= $str;
1422         }
1423     }
1426 /**
1427  * Given a course and a date, prints a summary of all the new
1428  * messages posted in the course since that date
1429  *
1430  * @global object
1431  * @global object
1432  * @global object
1433  * @uses CONTEXT_MODULE
1434  * @uses VISIBLEGROUPS
1435  * @param object $course
1436  * @param bool $viewfullnames capability
1437  * @param int $timestart
1438  * @return bool success
1439  */
1440 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1441     global $CFG, $USER, $DB, $OUTPUT;
1443     // do not use log table if possible, it may be huge and is expensive to join with other tables
1445     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1446                                               d.timestart, d.timeend, d.userid AS duserid,
1447                                               u.firstname, u.lastname, u.email, u.picture
1448                                          FROM {forum_posts} p
1449                                               JOIN {forum_discussions} d ON d.id = p.discussion
1450                                               JOIN {forum} f             ON f.id = d.forum
1451                                               JOIN {user} u              ON u.id = p.userid
1452                                         WHERE p.created > ? AND f.course = ?
1453                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1454          return false;
1455     }
1457     $modinfo = get_fast_modinfo($course);
1459     $groupmodes = array();
1460     $cms    = array();
1462     $strftimerecent = get_string('strftimerecent');
1464     $printposts = array();
1465     foreach ($posts as $post) {
1466         if (!isset($modinfo->instances['forum'][$post->forum])) {
1467             // not visible
1468             continue;
1469         }
1470         $cm = $modinfo->instances['forum'][$post->forum];
1471         if (!$cm->uservisible) {
1472             continue;
1473         }
1474         $context = context_module::instance($cm->id);
1476         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1477             continue;
1478         }
1480         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1481           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1482             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1483                 continue;
1484             }
1485         }
1487         $groupmode = groups_get_activity_groupmode($cm, $course);
1489         if ($groupmode) {
1490             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1491                 // oki (Open discussions have groupid -1)
1492             } else {
1493                 // separate mode
1494                 if (isguestuser()) {
1495                     // shortcut
1496                     continue;
1497                 }
1499                 if (is_null($modinfo->groups)) {
1500                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1501                 }
1503                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1504                     continue;
1505                 }
1506             }
1507         }
1509         $printposts[] = $post;
1510     }
1511     unset($posts);
1513     if (!$printposts) {
1514         return false;
1515     }
1517     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1518     echo "\n<ul class='unlist'>\n";
1520     foreach ($printposts as $post) {
1521         $subjectclass = empty($post->parent) ? ' bold' : '';
1523         echo '<li><div class="head">'.
1524                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1525                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1526              '</div>';
1527         echo '<div class="info'.$subjectclass.'">';
1528         if (empty($post->parent)) {
1529             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1530         } else {
1531             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1532         }
1533         $post->subject = break_up_long_words(format_string($post->subject, true));
1534         echo $post->subject;
1535         echo "</a>\"</div></li>\n";
1536     }
1538     echo "</ul>\n";
1540     return true;
1543 /**
1544  * Return grade for given user or all users.
1545  *
1546  * @global object
1547  * @global object
1548  * @param object $forum
1549  * @param int $userid optional user id, 0 means all users
1550  * @return array array of grades, false if none
1551  */
1552 function forum_get_user_grades($forum, $userid = 0) {
1553     global $CFG;
1555     require_once($CFG->dirroot.'/rating/lib.php');
1557     $ratingoptions = new stdClass;
1558     $ratingoptions->component = 'mod_forum';
1559     $ratingoptions->ratingarea = 'post';
1561     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1562     $ratingoptions->modulename = 'forum';
1563     $ratingoptions->moduleid   = $forum->id;
1564     $ratingoptions->userid = $userid;
1565     $ratingoptions->aggregationmethod = $forum->assessed;
1566     $ratingoptions->scaleid = $forum->scale;
1567     $ratingoptions->itemtable = 'forum_posts';
1568     $ratingoptions->itemtableusercolumn = 'userid';
1570     $rm = new rating_manager();
1571     return $rm->get_user_grades($ratingoptions);
1574 /**
1575  * Update activity grades
1576  *
1577  * @category grade
1578  * @param object $forum
1579  * @param int $userid specific user only, 0 means all
1580  * @param boolean $nullifnone return null if grade does not exist
1581  * @return void
1582  */
1583 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1584     global $CFG, $DB;
1585     require_once($CFG->libdir.'/gradelib.php');
1587     if (!$forum->assessed) {
1588         forum_grade_item_update($forum);
1590     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1591         forum_grade_item_update($forum, $grades);
1593     } else if ($userid and $nullifnone) {
1594         $grade = new stdClass();
1595         $grade->userid   = $userid;
1596         $grade->rawgrade = NULL;
1597         forum_grade_item_update($forum, $grade);
1599     } else {
1600         forum_grade_item_update($forum);
1601     }
1604 /**
1605  * Update all grades in gradebook.
1606  * @global object
1607  */
1608 function forum_upgrade_grades() {
1609     global $DB;
1611     $sql = "SELECT COUNT('x')
1612               FROM {forum} f, {course_modules} cm, {modules} m
1613              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1614     $count = $DB->count_records_sql($sql);
1616     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1617               FROM {forum} f, {course_modules} cm, {modules} m
1618              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1619     $rs = $DB->get_recordset_sql($sql);
1620     if ($rs->valid()) {
1621         $pbar = new progress_bar('forumupgradegrades', 500, true);
1622         $i=0;
1623         foreach ($rs as $forum) {
1624             $i++;
1625             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1626             forum_update_grades($forum, 0, false);
1627             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1628         }
1629     }
1630     $rs->close();
1633 /**
1634  * Create/update grade item for given forum
1635  *
1636  * @category grade
1637  * @uses GRADE_TYPE_NONE
1638  * @uses GRADE_TYPE_VALUE
1639  * @uses GRADE_TYPE_SCALE
1640  * @param stdClass $forum Forum object with extra cmidnumber
1641  * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
1642  * @return int 0 if ok
1643  */
1644 function forum_grade_item_update($forum, $grades=NULL) {
1645     global $CFG;
1646     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1647         require_once($CFG->libdir.'/gradelib.php');
1648     }
1650     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1652     if (!$forum->assessed or $forum->scale == 0) {
1653         $params['gradetype'] = GRADE_TYPE_NONE;
1655     } else if ($forum->scale > 0) {
1656         $params['gradetype'] = GRADE_TYPE_VALUE;
1657         $params['grademax']  = $forum->scale;
1658         $params['grademin']  = 0;
1660     } else if ($forum->scale < 0) {
1661         $params['gradetype'] = GRADE_TYPE_SCALE;
1662         $params['scaleid']   = -$forum->scale;
1663     }
1665     if ($grades  === 'reset') {
1666         $params['reset'] = true;
1667         $grades = NULL;
1668     }
1670     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1673 /**
1674  * Delete grade item for given forum
1675  *
1676  * @category grade
1677  * @param stdClass $forum Forum object
1678  * @return grade_item
1679  */
1680 function forum_grade_item_delete($forum) {
1681     global $CFG;
1682     require_once($CFG->libdir.'/gradelib.php');
1684     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1688 /**
1689  * This function returns if a scale is being used by one forum
1690  *
1691  * @global object
1692  * @param int $forumid
1693  * @param int $scaleid negative number
1694  * @return bool
1695  */
1696 function forum_scale_used ($forumid,$scaleid) {
1697     global $DB;
1698     $return = false;
1700     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1702     if (!empty($rec) && !empty($scaleid)) {
1703         $return = true;
1704     }
1706     return $return;
1709 /**
1710  * Checks if scale is being used by any instance of forum
1711  *
1712  * This is used to find out if scale used anywhere
1713  *
1714  * @global object
1715  * @param $scaleid int
1716  * @return boolean True if the scale is used by any forum
1717  */
1718 function forum_scale_used_anywhere($scaleid) {
1719     global $DB;
1720     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1721         return true;
1722     } else {
1723         return false;
1724     }
1727 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1729 /**
1730  * Gets a post with all info ready for forum_print_post
1731  * Most of these joins are just to get the forum id
1732  *
1733  * @global object
1734  * @global object
1735  * @param int $postid
1736  * @return mixed array of posts or false
1737  */
1738 function forum_get_post_full($postid) {
1739     global $CFG, $DB;
1741     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1742                              FROM {forum_posts} p
1743                                   JOIN {forum_discussions} d ON p.discussion = d.id
1744                                   LEFT JOIN {user} u ON p.userid = u.id
1745                             WHERE p.id = ?", array($postid));
1748 /**
1749  * Gets posts with all info ready for forum_print_post
1750  * We pass forumid in because we always know it so no need to make a
1751  * complicated join to find it out.
1752  *
1753  * @global object
1754  * @global object
1755  * @return mixed array of posts or false
1756  */
1757 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1758     global $CFG, $DB;
1760     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1761                               FROM {forum_posts} p
1762                          LEFT JOIN {user} u ON p.userid = u.id
1763                              WHERE p.discussion = ?
1764                                AND p.parent > 0 $sort", array($discussion));
1767 /**
1768  * Gets all posts in discussion including top parent.
1769  *
1770  * @global object
1771  * @global object
1772  * @global object
1773  * @param int $discussionid
1774  * @param string $sort
1775  * @param bool $tracking does user track the forum?
1776  * @return array of posts
1777  */
1778 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1779     global $CFG, $DB, $USER;
1781     $tr_sel  = "";
1782     $tr_join = "";
1783     $params = array();
1785     if ($tracking) {
1786         $now = time();
1787         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1788         $tr_sel  = ", fr.id AS postread";
1789         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1790         $params[] = $USER->id;
1791     }
1793     $params[] = $discussionid;
1794     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1795                                      FROM {forum_posts} p
1796                                           LEFT JOIN {user} u ON p.userid = u.id
1797                                           $tr_join
1798                                     WHERE p.discussion = ?
1799                                  ORDER BY $sort", $params)) {
1800         return array();
1801     }
1803     foreach ($posts as $pid=>$p) {
1804         if ($tracking) {
1805             if (forum_tp_is_post_old($p)) {
1806                  $posts[$pid]->postread = true;
1807             }
1808         }
1809         if (!$p->parent) {
1810             continue;
1811         }
1812         if (!isset($posts[$p->parent])) {
1813             continue; // parent does not exist??
1814         }
1815         if (!isset($posts[$p->parent]->children)) {
1816             $posts[$p->parent]->children = array();
1817         }
1818         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1819     }
1821     return $posts;
1824 /**
1825  * Gets posts with all info ready for forum_print_post
1826  * We pass forumid in because we always know it so no need to make a
1827  * complicated join to find it out.
1828  *
1829  * @global object
1830  * @global object
1831  * @param int $parent
1832  * @param int $forumid
1833  * @return array
1834  */
1835 function forum_get_child_posts($parent, $forumid) {
1836     global $CFG, $DB;
1838     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1839                               FROM {forum_posts} p
1840                          LEFT JOIN {user} u ON p.userid = u.id
1841                              WHERE p.parent = ?
1842                           ORDER BY p.created ASC", array($parent));
1845 /**
1846  * An array of forum objects that the user is allowed to read/search through.
1847  *
1848  * @global object
1849  * @global object
1850  * @global object
1851  * @param int $userid
1852  * @param int $courseid if 0, we look for forums throughout the whole site.
1853  * @return array of forum objects, or false if no matches
1854  *         Forum objects have the following attributes:
1855  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1856  *         viewhiddentimedposts
1857  */
1858 function forum_get_readable_forums($userid, $courseid=0) {
1860     global $CFG, $DB, $USER;
1861     require_once($CFG->dirroot.'/course/lib.php');
1863     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1864         print_error('notinstalled', 'forum');
1865     }
1867     if ($courseid) {
1868         $courses = $DB->get_records('course', array('id' => $courseid));
1869     } else {
1870         // If no course is specified, then the user can see SITE + his courses.
1871         $courses1 = $DB->get_records('course', array('id' => SITEID));
1872         $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
1873         $courses = array_merge($courses1, $courses2);
1874     }
1875     if (!$courses) {
1876         return array();
1877     }
1879     $readableforums = array();
1881     foreach ($courses as $course) {
1883         $modinfo = get_fast_modinfo($course);
1884         if (is_null($modinfo->groups)) {
1885             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1886         }
1888         if (empty($modinfo->instances['forum'])) {
1889             // hmm, no forums?
1890             continue;
1891         }
1893         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1895         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1896             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1897                 continue;
1898             }
1899             $context = context_module::instance($cm->id);
1900             $forum = $courseforums[$forumid];
1901             $forum->context = $context;
1902             $forum->cm = $cm;
1904             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1905                 continue;
1906             }
1908          /// group access
1909             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1910                 if (is_null($modinfo->groups)) {
1911                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1912                 }
1913                 if (isset($modinfo->groups[$cm->groupingid])) {
1914                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1915                     $forum->onlygroups[] = -1;
1916                 } else {
1917                     $forum->onlygroups = array(-1);
1918                 }
1919             }
1921         /// hidden timed discussions
1922             $forum->viewhiddentimedposts = true;
1923             if (!empty($CFG->forum_enabletimedposts)) {
1924                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1925                     $forum->viewhiddentimedposts = false;
1926                 }
1927             }
1929         /// qanda access
1930             if ($forum->type == 'qanda'
1931                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1933                 // We need to check whether the user has posted in the qanda forum.
1934                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1935                                                     // the user is allowed to see in this forum.
1936                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1937                     foreach ($discussionspostedin as $d) {
1938                         $forum->onlydiscussions[] = $d->id;
1939                     }
1940                 }
1941             }
1943             $readableforums[$forum->id] = $forum;
1944         }
1946         unset($modinfo);
1948     } // End foreach $courses
1950     return $readableforums;
1953 /**
1954  * Returns a list of posts found using an array of search terms.
1955  *
1956  * @global object
1957  * @global object
1958  * @global object
1959  * @param array $searchterms array of search terms, e.g. word +word -word
1960  * @param int $courseid if 0, we search through the whole site
1961  * @param int $limitfrom
1962  * @param int $limitnum
1963  * @param int &$totalcount
1964  * @param string $extrasql
1965  * @return array|bool Array of posts found or false
1966  */
1967 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1968                             &$totalcount, $extrasql='') {
1969     global $CFG, $DB, $USER;
1970     require_once($CFG->libdir.'/searchlib.php');
1972     $forums = forum_get_readable_forums($USER->id, $courseid);
1974     if (count($forums) == 0) {
1975         $totalcount = 0;
1976         return false;
1977     }
1979     $now = round(time(), -2); // db friendly
1981     $fullaccess = array();
1982     $where = array();
1983     $params = array();
1985     foreach ($forums as $forumid => $forum) {
1986         $select = array();
1988         if (!$forum->viewhiddentimedposts) {
1989             $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
1990             $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
1991         }
1993         $cm = $forum->cm;
1994         $context = $forum->context;
1996         if ($forum->type == 'qanda'
1997             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1998             if (!empty($forum->onlydiscussions)) {
1999                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
2000                 $params = array_merge($params, $discussionid_params);
2001                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
2002             } else {
2003                 $select[] = "p.parent = 0";
2004             }
2005         }
2007         if (!empty($forum->onlygroups)) {
2008             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
2009             $params = array_merge($params, $groupid_params);
2010             $select[] = "d.groupid $groupid_sql";
2011         }
2013         if ($select) {
2014             $selects = implode(" AND ", $select);
2015             $where[] = "(d.forum = :forum{$forumid} AND $selects)";
2016             $params['forum'.$forumid] = $forumid;
2017         } else {
2018             $fullaccess[] = $forumid;
2019         }
2020     }
2022     if ($fullaccess) {
2023         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
2024         $params = array_merge($params, $fullid_params);
2025         $where[] = "(d.forum $fullid_sql)";
2026     }
2028     $selectdiscussion = "(".implode(" OR ", $where).")";
2030     $messagesearch = '';
2031     $searchstring = '';
2033     // Need to concat these back together for parser to work.
2034     foreach($searchterms as $searchterm){
2035         if ($searchstring != '') {
2036             $searchstring .= ' ';
2037         }
2038         $searchstring .= $searchterm;
2039     }
2041     // We need to allow quoted strings for the search. The quotes *should* be stripped
2042     // by the parser, but this should be examined carefully for security implications.
2043     $searchstring = str_replace("\\\"","\"",$searchstring);
2044     $parser = new search_parser();
2045     $lexer = new search_lexer($parser);
2047     if ($lexer->parse($searchstring)) {
2048         $parsearray = $parser->get_parsed_array();
2049     // Experimental feature under 1.8! MDL-8830
2050     // Use alternative text searches if defined
2051     // This feature only works under mysql until properly implemented for other DBs
2052     // Requires manual creation of text index for forum_posts before enabling it:
2053     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2054     // Experimental feature under 1.8! MDL-8830
2055         if (!empty($CFG->forum_usetextsearches)) {
2056             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2057                                                  'p.userid', 'u.id', 'u.firstname',
2058                                                  'u.lastname', 'p.modified', 'd.forum');
2059         } else {
2060             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2061                                                  'p.userid', 'u.id', 'u.firstname',
2062                                                  'u.lastname', 'p.modified', 'd.forum');
2063         }
2064         $params = array_merge($params, $msparams);
2065     }
2067     $fromsql = "{forum_posts} p,
2068                   {forum_discussions} d,
2069                   {user} u";
2071     $selectsql = " $messagesearch
2072                AND p.discussion = d.id
2073                AND p.userid = u.id
2074                AND $selectdiscussion
2075                    $extrasql";
2077     $countsql = "SELECT COUNT(*)
2078                    FROM $fromsql
2079                   WHERE $selectsql";
2081     $searchsql = "SELECT p.*,
2082                          d.forum,
2083                          u.firstname,
2084                          u.lastname,
2085                          u.email,
2086                          u.picture,
2087                          u.imagealt
2088                     FROM $fromsql
2089                    WHERE $selectsql
2090                 ORDER BY p.modified DESC";
2092     $totalcount = $DB->count_records_sql($countsql, $params);
2094     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2097 /**
2098  * Returns a list of ratings for a particular post - sorted.
2099  *
2100  * TODO: Check if this function is actually used anywhere.
2101  * Up until the fix for MDL-27471 this function wasn't even returning.
2102  *
2103  * @param stdClass $context
2104  * @param int $postid
2105  * @param string $sort
2106  * @return array Array of ratings or false
2107  */
2108 function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
2109     $options = new stdClass;
2110     $options->context = $context;
2111     $options->component = 'mod_forum';
2112     $options->ratingarea = 'post';
2113     $options->itemid = $postid;
2114     $options->sort = "ORDER BY $sort";
2116     $rm = new rating_manager();
2117     return $rm->get_all_ratings_for_item($options);
2120 /**
2121  * Returns a list of all new posts that have not been mailed yet
2122  *
2123  * @param int $starttime posts created after this time
2124  * @param int $endtime posts created before this
2125  * @param int $now used for timed discussions only
2126  * @return array
2127  */
2128 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2129     global $CFG, $DB;
2131     $params = array($starttime, $endtime);
2132     if (!empty($CFG->forum_enabletimedposts)) {
2133         if (empty($now)) {
2134             $now = time();
2135         }
2136         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2137         $params[] = $now;
2138         $params[] = $now;
2139     } else {
2140         $timedsql = "";
2141     }
2143     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2144                               FROM {forum_posts} p
2145                                    JOIN {forum_discussions} d ON d.id = p.discussion
2146                              WHERE p.mailed = 0
2147                                    AND p.created >= ?
2148                                    AND (p.created < ? OR p.mailnow = 1)
2149                                    $timedsql
2150                           ORDER BY p.modified ASC", $params);
2153 /**
2154  * Marks posts before a certain time as being mailed already
2155  *
2156  * @global object
2157  * @global object
2158  * @param int $endtime
2159  * @param int $now Defaults to time()
2160  * @return bool
2161  */
2162 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2163     global $CFG, $DB;
2164     if (empty($now)) {
2165         $now = time();
2166     }
2168     if (empty($CFG->forum_enabletimedposts)) {
2169         return $DB->execute("UPDATE {forum_posts}
2170                                SET mailed = '1'
2171                              WHERE (created < ? OR mailnow = 1)
2172                                    AND mailed = 0", array($endtime));
2174     } else {
2175         return $DB->execute("UPDATE {forum_posts}
2176                                SET mailed = '1'
2177                              WHERE discussion NOT IN (SELECT d.id
2178                                                         FROM {forum_discussions} d
2179                                                        WHERE d.timestart > ?)
2180                                    AND (created < ? OR mailnow = 1)
2181                                    AND mailed = 0", array($now, $endtime));
2182     }
2185 /**
2186  * Get all the posts for a user in a forum suitable for forum_print_post
2187  *
2188  * @global object
2189  * @global object
2190  * @uses CONTEXT_MODULE
2191  * @return array
2192  */
2193 function forum_get_user_posts($forumid, $userid) {
2194     global $CFG, $DB;
2196     $timedsql = "";
2197     $params = array($forumid, $userid);
2199     if (!empty($CFG->forum_enabletimedposts)) {
2200         $cm = get_coursemodule_from_instance('forum', $forumid);
2201         if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2202             $now = time();
2203             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2204             $params[] = $now;
2205             $params[] = $now;
2206         }
2207     }
2209     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2210                               FROM {forum} f
2211                                    JOIN {forum_discussions} d ON d.forum = f.id
2212                                    JOIN {forum_posts} p       ON p.discussion = d.id
2213                                    JOIN {user} u              ON u.id = p.userid
2214                              WHERE f.id = ?
2215                                    AND p.userid = ?
2216                                    $timedsql
2217                           ORDER BY p.modified ASC", $params);
2220 /**
2221  * Get all the discussions user participated in
2222  *
2223  * @global object
2224  * @global object
2225  * @uses CONTEXT_MODULE
2226  * @param int $forumid
2227  * @param int $userid
2228  * @return array Array or false
2229  */
2230 function forum_get_user_involved_discussions($forumid, $userid) {
2231     global $CFG, $DB;
2233     $timedsql = "";
2234     $params = array($forumid, $userid);
2235     if (!empty($CFG->forum_enabletimedposts)) {
2236         $cm = get_coursemodule_from_instance('forum', $forumid);
2237         if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2238             $now = time();
2239             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2240             $params[] = $now;
2241             $params[] = $now;
2242         }
2243     }
2245     return $DB->get_records_sql("SELECT DISTINCT d.*
2246                               FROM {forum} f
2247                                    JOIN {forum_discussions} d ON d.forum = f.id
2248                                    JOIN {forum_posts} p       ON p.discussion = d.id
2249                              WHERE f.id = ?
2250                                    AND p.userid = ?
2251                                    $timedsql", $params);
2254 /**
2255  * Get all the posts for a user in a forum suitable for forum_print_post
2256  *
2257  * @global object
2258  * @global object
2259  * @param int $forumid
2260  * @param int $userid
2261  * @return array of counts or false
2262  */
2263 function forum_count_user_posts($forumid, $userid) {
2264     global $CFG, $DB;
2266     $timedsql = "";
2267     $params = array($forumid, $userid);
2268     if (!empty($CFG->forum_enabletimedposts)) {
2269         $cm = get_coursemodule_from_instance('forum', $forumid);
2270         if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2271             $now = time();
2272             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2273             $params[] = $now;
2274             $params[] = $now;
2275         }
2276     }
2278     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2279                              FROM {forum} f
2280                                   JOIN {forum_discussions} d ON d.forum = f.id
2281                                   JOIN {forum_posts} p       ON p.discussion = d.id
2282                                   JOIN {user} u              ON u.id = p.userid
2283                             WHERE f.id = ?
2284                                   AND p.userid = ?
2285                                   $timedsql", $params);
2288 /**
2289  * Given a log entry, return the forum post details for it.
2290  *
2291  * @global object
2292  * @global object
2293  * @param object $log
2294  * @return array|null
2295  */
2296 function forum_get_post_from_log($log) {
2297     global $CFG, $DB;
2299     if ($log->action == "add post") {
2301         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2302                                            u.firstname, u.lastname, u.email, u.picture
2303                                  FROM {forum_discussions} d,
2304                                       {forum_posts} p,
2305                                       {forum} f,
2306                                       {user} u
2307                                 WHERE p.id = ?
2308                                   AND d.id = p.discussion
2309                                   AND p.userid = u.id
2310                                   AND u.deleted <> '1'
2311                                   AND f.id = d.forum", array($log->info));
2314     } else if ($log->action == "add discussion") {
2316         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2317                                            u.firstname, u.lastname, u.email, u.picture
2318                                  FROM {forum_discussions} d,
2319                                       {forum_posts} p,
2320                                       {forum} f,
2321                                       {user} u
2322                                 WHERE d.id = ?
2323                                   AND d.firstpost = p.id
2324                                   AND p.userid = u.id
2325                                   AND u.deleted <> '1'
2326                                   AND f.id = d.forum", array($log->info));
2327     }
2328     return NULL;
2331 /**
2332  * Given a discussion id, return the first post from the discussion
2333  *
2334  * @global object
2335  * @global object
2336  * @param int $dicsussionid
2337  * @return array
2338  */
2339 function forum_get_firstpost_from_discussion($discussionid) {
2340     global $CFG, $DB;
2342     return $DB->get_record_sql("SELECT p.*
2343                              FROM {forum_discussions} d,
2344                                   {forum_posts} p
2345                             WHERE d.id = ?
2346                               AND d.firstpost = p.id ", array($discussionid));
2349 /**
2350  * Returns an array of counts of replies to each discussion
2351  *
2352  * @global object
2353  * @global object
2354  * @param int $forumid
2355  * @param string $forumsort
2356  * @param int $limit
2357  * @param int $page
2358  * @param int $perpage
2359  * @return array
2360  */
2361 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2362     global $CFG, $DB;
2364     if ($limit > 0) {
2365         $limitfrom = 0;
2366         $limitnum  = $limit;
2367     } else if ($page != -1) {
2368         $limitfrom = $page*$perpage;
2369         $limitnum  = $perpage;
2370     } else {
2371         $limitfrom = 0;
2372         $limitnum  = 0;
2373     }
2375     if ($forumsort == "") {
2376         $orderby = "";
2377         $groupby = "";
2379     } else {
2380         $orderby = "ORDER BY $forumsort";
2381         $groupby = ", ".strtolower($forumsort);
2382         $groupby = str_replace('desc', '', $groupby);
2383         $groupby = str_replace('asc', '', $groupby);
2384     }
2386     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2387         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2388                   FROM {forum_posts} p
2389                        JOIN {forum_discussions} d ON p.discussion = d.id
2390                  WHERE p.parent > 0 AND d.forum = ?
2391               GROUP BY p.discussion";
2392         return $DB->get_records_sql($sql, array($forumid));
2394     } else {
2395         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2396                   FROM {forum_posts} p
2397                        JOIN {forum_discussions} d ON p.discussion = d.id
2398                  WHERE d.forum = ?
2399               GROUP BY p.discussion $groupby
2400               $orderby";
2401         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2402     }
2405 /**
2406  * @global object
2407  * @global object
2408  * @global object
2409  * @staticvar array $cache
2410  * @param object $forum
2411  * @param object $cm
2412  * @param object $course
2413  * @return mixed
2414  */
2415 function forum_count_discussions($forum, $cm, $course) {
2416     global $CFG, $DB, $USER;
2418     static $cache = array();
2420     $now = round(time(), -2); // db cache friendliness
2422     $params = array($course->id);
2424     if (!isset($cache[$course->id])) {
2425         if (!empty($CFG->forum_enabletimedposts)) {
2426             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2427             $params[] = $now;
2428             $params[] = $now;
2429         } else {
2430             $timedsql = "";
2431         }
2433         $sql = "SELECT f.id, COUNT(d.id) as dcount
2434                   FROM {forum} f
2435                        JOIN {forum_discussions} d ON d.forum = f.id
2436                  WHERE f.course = ?
2437                        $timedsql
2438               GROUP BY f.id";
2440         if ($counts = $DB->get_records_sql($sql, $params)) {
2441             foreach ($counts as $count) {
2442                 $counts[$count->id] = $count->dcount;
2443             }
2444             $cache[$course->id] = $counts;
2445         } else {
2446             $cache[$course->id] = array();
2447         }
2448     }
2450     if (empty($cache[$course->id][$forum->id])) {
2451         return 0;
2452     }
2454     $groupmode = groups_get_activity_groupmode($cm, $course);
2456     if ($groupmode != SEPARATEGROUPS) {
2457         return $cache[$course->id][$forum->id];
2458     }
2460     if (has_capability('moodle/site:accessallgroups', context_module::instance($cm->id))) {
2461         return $cache[$course->id][$forum->id];
2462     }
2464     require_once($CFG->dirroot.'/course/lib.php');
2466     $modinfo = get_fast_modinfo($course);
2467     if (is_null($modinfo->groups)) {
2468         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2469     }
2471     if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2472         $mygroups = $modinfo->groups[$cm->groupingid];
2473     } else {
2474         $mygroups = false; // Will be set below
2475     }
2477     // add all groups posts
2478     if (empty($mygroups)) {
2479         $mygroups = array(-1=>-1);
2480     } else {
2481         $mygroups[-1] = -1;
2482     }
2484     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2485     $params[] = $forum->id;
2487     if (!empty($CFG->forum_enabletimedposts)) {
2488         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2489         $params[] = $now;
2490         $params[] = $now;
2491     } else {
2492         $timedsql = "";
2493     }
2495     $sql = "SELECT COUNT(d.id)
2496               FROM {forum_discussions} d
2497              WHERE d.groupid $mygroups_sql AND d.forum = ?
2498                    $timedsql";
2500     return $DB->get_field_sql($sql, $params);
2503 /**
2504  * How many posts by other users are unrated by a given user in the given discussion?
2505  *
2506  * TODO: Is this function still used anywhere?
2507  *
2508  * @param int $discussionid
2509  * @param int $userid
2510  * @return mixed
2511  */
2512 function forum_count_unrated_posts($discussionid, $userid) {
2513     global $CFG, $DB;
2515     $sql = "SELECT COUNT(*) as num
2516               FROM {forum_posts}
2517              WHERE parent > 0
2518                AND discussion = :discussionid
2519                AND userid <> :userid";
2520     $params = array('discussionid' => $discussionid, 'userid' => $userid);
2521     $posts = $DB->get_record_sql($sql, $params);
2522     if ($posts) {
2523         $sql = "SELECT count(*) as num
2524                   FROM {forum_posts} p,
2525                        {rating} r
2526                  WHERE p.discussion = :discussionid AND
2527                        p.id = r.itemid AND
2528                        r.userid = userid AND
2529                        r.component = 'mod_forum' AND
2530                        r.ratingarea = 'post'";
2531         $rated = $DB->get_record_sql($sql, $params);
2532         if ($rated) {
2533             if ($posts->num > $rated->num) {
2534                 return $posts->num - $rated->num;
2535             } else {
2536                 return 0;    // Just in case there was a counting error
2537             }
2538         } else {
2539             return $posts->num;
2540         }
2541     } else {
2542         return 0;
2543     }
2546 /**
2547  * Get all discussions in a forum
2548  *
2549  * @global object
2550  * @global object
2551  * @global object
2552  * @uses CONTEXT_MODULE
2553  * @uses VISIBLEGROUPS
2554  * @param object $cm
2555  * @param string $forumsort
2556  * @param bool $fullpost
2557  * @param int $unused
2558  * @param int $limit
2559  * @param bool $userlastmodified
2560  * @param int $page
2561  * @param int $perpage
2562  * @return array
2563  */
2564 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2565     global $CFG, $DB, $USER;
2567     $timelimit = '';
2569     $now = round(time(), -2);
2570     $params = array($cm->instance);
2572     $modcontext = context_module::instance($cm->id);
2574     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2575         return array();
2576     }
2578     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2580         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2581             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2582             $params[] = $now;
2583             $params[] = $now;
2584             if (isloggedin()) {
2585                 $timelimit .= " OR d.userid = ?";
2586                 $params[] = $USER->id;
2587             }
2588             $timelimit .= ")";
2589         }
2590     }
2592     if ($limit > 0) {
2593         $limitfrom = 0;
2594         $limitnum  = $limit;
2595     } else if ($page != -1) {
2596         $limitfrom = $page*$perpage;
2597         $limitnum  = $perpage;
2598     } else {
2599         $limitfrom = 0;
2600         $limitnum  = 0;
2601     }
2603     $groupmode    = groups_get_activity_groupmode($cm);
2604     $currentgroup = groups_get_activity_group($cm);
2606     if ($groupmode) {
2607         if (empty($modcontext)) {
2608             $modcontext = context_module::instance($cm->id);
2609         }
2611         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2612             if ($currentgroup) {
2613                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2614                 $params[] = $currentgroup;
2615             } else {
2616                 $groupselect = "";
2617             }
2619         } else {
2620             //seprate groups without access all
2621             if ($currentgroup) {
2622                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2623                 $params[] = $currentgroup;
2624             } else {
2625                 $groupselect = "AND d.groupid = -1";
2626             }
2627         }
2628     } else {
2629         $groupselect = "";
2630     }
2633     if (empty($forumsort)) {
2634         $forumsort = "d.timemodified DESC";
2635     }
2636     if (empty($fullpost)) {
2637         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2638     } else {
2639         $postdata = "p.*";
2640     }
2642     if (empty($userlastmodified)) {  // We don't need to know this
2643         $umfields = "";
2644         $umtable  = "";
2645     } else {
2646         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2647         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2648     }
2650     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2651                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2652               FROM {forum_discussions} d
2653                    JOIN {forum_posts} p ON p.discussion = d.id
2654                    JOIN {user} u ON p.userid = u.id
2655                    $umtable
2656              WHERE d.forum = ? AND p.parent = 0
2657                    $timelimit $groupselect
2658           ORDER BY $forumsort";
2659     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2662 /**
2663  *
2664  * @global object
2665  * @global object
2666  * @global object
2667  * @uses CONTEXT_MODULE
2668  * @uses VISIBLEGROUPS
2669  * @param object $cm
2670  * @return array
2671  */
2672 function forum_get_discussions_unread($cm) {
2673     global $CFG, $DB, $USER;
2675     $now = round(time(), -2);
2676     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2678     $params = array();
2679     $groupmode    = groups_get_activity_groupmode($cm);
2680     $currentgroup = groups_get_activity_group($cm);
2682     if ($groupmode) {
2683         $modcontext = context_module::instance($cm->id);
2685         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2686             if ($currentgroup) {
2687                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2688                 $params['currentgroup'] = $currentgroup;
2689             } else {
2690                 $groupselect = "";
2691             }
2693         } else {
2694             //separate groups without access all
2695             if ($currentgroup) {
2696                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2697                 $params['currentgroup'] = $currentgroup;
2698             } else {
2699                 $groupselect = "AND d.groupid = -1";
2700             }
2701         }
2702     } else {
2703         $groupselect = "";
2704     }
2706     if (!empty($CFG->forum_enabletimedposts)) {
2707         $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2708         $params['now1'] = $now;
2709         $params['now2'] = $now;
2710     } else {
2711         $timedsql = "";
2712     }
2714     $sql = "SELECT d.id, COUNT(p.id) AS unread
2715               FROM {forum_discussions} d
2716                    JOIN {forum_posts} p     ON p.discussion = d.id
2717                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2718              WHERE d.forum = {$cm->instance}
2719                    AND p.modified >= :cutoffdate AND r.id is NULL
2720                    $groupselect
2721                    $timedsql
2722           GROUP BY d.id";
2723     $params['cutoffdate'] = $cutoffdate;
2725     if ($unreads = $DB->get_records_sql($sql, $params)) {
2726         foreach ($unreads as $unread) {
2727             $unreads[$unread->id] = $unread->unread;
2728         }
2729         return $unreads;
2730     } else {
2731         return array();
2732     }
2735 /**
2736  * @global object
2737  * @global object
2738  * @global object
2739  * @uses CONEXT_MODULE
2740  * @uses VISIBLEGROUPS
2741  * @param object $cm
2742  * @return array
2743  */
2744 function forum_get_discussions_count($cm) {
2745     global $CFG, $DB, $USER;
2747     $now = round(time(), -2);
2748     $params = array($cm->instance);
2749     $groupmode    = groups_get_activity_groupmode($cm);
2750     $currentgroup = groups_get_activity_group($cm);
2752     if ($groupmode) {
2753         $modcontext = context_module::instance($cm->id);
2755         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2756             if ($currentgroup) {
2757                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2758                 $params[] = $currentgroup;
2759             } else {
2760                 $groupselect = "";
2761             }
2763         } else {
2764             //seprate groups without access all
2765             if ($currentgroup) {
2766                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2767                 $params[] = $currentgroup;
2768             } else {
2769                 $groupselect = "AND d.groupid = -1";
2770             }
2771         }
2772     } else {
2773         $groupselect = "";
2774     }
2776     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2778     $timelimit = "";
2780     if (!empty($CFG->forum_enabletimedposts)) {
2782         $modcontext = context_module::instance($cm->id);
2784         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2785             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2786             $params[] = $now;
2787             $params[] = $now;
2788             if (isloggedin()) {
2789                 $timelimit .= " OR d.userid = ?";
2790                 $params[] = $USER->id;
2791             }
2792             $timelimit .= ")";
2793         }
2794     }
2796     $sql = "SELECT COUNT(d.id)
2797               FROM {forum_discussions} d
2798                    JOIN {forum_posts} p ON p.discussion = d.id
2799              WHERE d.forum = ? AND p.parent = 0
2800                    $groupselect $timelimit";
2802     return $DB->get_field_sql($sql, $params);
2806 /**
2807  * Get all discussions started by a particular user in a course (or group)
2808  * This function no longer used ...
2809  *
2810  * @todo Remove this function if no longer used
2811  * @global object
2812  * @global object
2813  * @param int $courseid
2814  * @param int $userid
2815  * @param int $groupid
2816  * @return array
2817  */
2818 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2819     global $CFG, $DB;
2820     $params = array($courseid, $userid);
2821     if ($groupid) {
2822         $groupselect = " AND d.groupid = ? ";
2823         $params[] = $groupid;
2824     } else  {
2825         $groupselect = "";
2826     }
2828     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2829                                    f.type as forumtype, f.name as forumname, f.id as forumid
2830                               FROM {forum_discussions} d,
2831                                    {forum_posts} p,
2832                                    {user} u,
2833                                    {forum} f
2834                              WHERE d.course = ?
2835                                AND p.discussion = d.id
2836                                AND p.parent = 0
2837                                AND p.userid = u.id
2838                                AND u.id = ?
2839                                AND d.forum = f.id $groupselect
2840                           ORDER BY p.created DESC", $params);
2843 /**
2844  * Get the list of potential subscribers to a forum.
2845  *
2846  * @param object $forumcontext the forum context.
2847  * @param integer $groupid the id of a group, or 0 for all groups.
2848  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2849  * @param string $sort sort order. As for get_users_by_capability.
2850  * @return array list of users.
2851  */
2852 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort = '') {
2853     global $DB;
2855     // only active enrolled users or everybody on the frontpage
2856     list($esql, $params) = get_enrolled_sql($forumcontext, 'mod/forum:allowforcesubscribe', $groupid, true);
2857     if (!$sort) {
2858         list($sort, $sortparams) = users_order_by_sql('u');
2859         $params = array_merge($params, $sortparams);
2860     }
2862     $sql = "SELECT $fields
2863               FROM {user} u
2864               JOIN ($esql) je ON je.id = u.id
2865           ORDER BY $sort";
2867     return $DB->get_records_sql($sql, $params);
2870 /**
2871  * Returns list of user objects that are subscribed to this forum
2872  *
2873  * @global object
2874  * @global object
2875  * @param object $course the course
2876  * @param forum $forum the forum
2877  * @param integer $groupid group id, or 0 for all.
2878  * @param object $context the forum context, to save re-fetching it where possible.
2879  * @param string $fields requested user fields (with "u." table prefix)
2880  * @return array list of users.
2881  */
2882 function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
2883     global $CFG, $DB;
2885     if (empty($fields)) {
2886         $fields ="u.id,
2887                   u.username,
2888                   u.firstname,
2889                   u.lastname,
2890                   u.maildisplay,
2891                   u.mailformat,
2892                   u.maildigest,
2893                   u.imagealt,
2894                   u.email,
2895                   u.emailstop,
2896                   u.city,
2897                   u.country,
2898                   u.lastaccess,
2899                   u.lastlogin,
2900                   u.picture,
2901                   u.timezone,
2902                   u.theme,
2903                   u.lang,
2904                   u.trackforums,
2905                   u.mnethostid";
2906     }
2908     if (empty($context)) {
2909         $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2910         $context = context_module::instance($cm->id);
2911     }
2913     if (forum_is_forcesubscribed($forum)) {
2914         $results = forum_get_potential_subscribers($context, $groupid, $fields, "u.email ASC");
2916     } else {
2917         // only active enrolled users or everybody on the frontpage
2918         list($esql, $params) = get_enrolled_sql($context, '', $groupid, true);
2919         $params['forumid'] = $forum->id;
2920         $results = $DB->get_records_sql("SELECT $fields
2921                                            FROM {user} u
2922                                            JOIN ($esql) je ON je.id = u.id
2923                                            JOIN {forum_subscriptions} s ON s.userid = u.id
2924                                           WHERE s.forum = :forumid
2925                                        ORDER BY u.email ASC", $params);
2926     }
2928     // Guest user should never be subscribed to a forum.
2929     unset($results[$CFG->siteguest]);
2931     return $results;
2936 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2939 /**
2940  * @global object
2941  * @global object
2942  * @param int $courseid
2943  * @param string $type
2944  */
2945 function forum_get_course_forum($courseid, $type) {
2946 // How to set up special 1-per-course forums
2947     global $CFG, $DB, $OUTPUT;
2949     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2950         // There should always only be ONE, but with the right combination of
2951         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2952         foreach ($forums as $forum) {
2953             return $forum;   // ie the first one
2954         }
2955     }
2957     // Doesn't exist, so create one now.
2958     $forum = new stdClass();
2959     $forum->course = $courseid;
2960     $forum->type = "$type";
2961     switch ($forum->type) {
2962         case "news":
2963             $forum->name  = get_string("namenews", "forum");
2964             $forum->intro = get_string("intronews", "forum");
2965             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2966             $forum->assessed = 0;
2967             if ($courseid == SITEID) {
2968                 $forum->name  = get_string("sitenews");
2969                 $forum->forcesubscribe = 0;
2970             }
2971             break;
2972         case "social":
2973             $forum->name  = get_string("namesocial", "forum");
2974             $forum->intro = get_string("introsocial", "forum");
2975             $forum->assessed = 0;
2976             $forum->forcesubscribe = 0;
2977             break;
2978         case "blog":
2979             $forum->name = get_string('blogforum', 'forum');
2980             $forum->intro = get_string('introblog', 'forum');
2981             $forum->assessed = 0;
2982             $forum->forcesubscribe = 0;
2983             break;
2984         default:
2985             echo $OUTPUT->notification("That forum type doesn't exist!");
2986             return false;
2987             break;
2988     }
2990     $forum->timemodified = time();
2991     $forum->id = $DB->insert_record("forum", $forum);
2993     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2994         echo $OUTPUT->notification("Could not find forum module!!");
2995         return false;
2996     }
2997     $mod = new stdClass();
2998     $mod->course = $courseid;
2999     $mod->module = $module->id;
3000     $mod->instance = $forum->id;
3001     $mod->section = 0;
3002     include_once("$CFG->dirroot/course/lib.php");
3003     if (! $mod->coursemodule = add_course_module($mod) ) {
3004         echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
3005         return false;
3006     }
3007     $sectionid = course_add_cm_to_section($courseid, $mod->coursemodule, 0);
3008     return $DB->get_record("forum", array("id" => "$forum->id"));
3012 /**
3013  * Given the data about a posting, builds up the HTML to display it and
3014  * returns the HTML in a string.  This is designed for sending via HTML email.
3015  *
3016  * @global object
3017  * @param object $course
3018  * @param object $cm
3019  * @param object $forum
3020  * @param object $discussion
3021  * @param object $post
3022  * @param object $userform
3023  * @param object $userto
3024  * @param bool $ownpost
3025  * @param bool $reply
3026  * @param bool $link
3027  * @param bool $rate
3028  * @param string $footer
3029  * @return string
3030  */
3031 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
3032                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
3034     global $CFG, $OUTPUT;
3036     $modcontext = context_module::instance($cm->id);
3038     if (!isset($userto->viewfullnames[$forum->id])) {
3039         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3040     } else {
3041         $viewfullnames = $userto->viewfullnames[$forum->id];
3042     }
3044     // add absolute file links
3045     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3047     // format the post body
3048     $options = new stdClass();
3049     $options->para = true;
3050     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3052     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3054     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3055     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3056     $output .= '</td>';
3058     if ($post->parent) {
3059         $output .= '<td class="topic">';
3060     } else {
3061         $output .= '<td class="topic starter">';
3062     }
3063     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3065     $fullname = fullname($userfrom, $viewfullnames);
3066     $by = new stdClass();
3067     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3068     $by->date = userdate($post->modified, '', $userto->timezone);
3069     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3071     $output .= '</td></tr>';
3073     $output .= '<tr><td class="left side" valign="top">';
3075     if (isset($userfrom->groups)) {
3076         $groups = $userfrom->groups[$forum->id];
3077     } else {
3078         $groups = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3079     }
3081     if ($groups) {
3082         $output .= print_group_picture($groups, $course->id, false, true, true);
3083     } else {
3084         $output .= '&nbsp;';
3085     }
3087     $output .= '</td><td class="content">';
3089     $attachments = forum_print_attachments($post, $cm, 'html');
3090     if ($attachments !== '') {
3091         $output .= '<div class="attachments">';
3092         $output .= $attachments;
3093         $output .= '</div>';
3094     }
3096     $output .= $formattedtext;
3098 // Commands
3099     $commands = array();
3101     if ($post->parent) {
3102         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3103                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3104     }
3106     if ($reply) {
3107         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3108                       get_string('reply', 'forum').'</a>';
3109     }
3111     $output .= '<div class="commands">';
3112     $output .= implode(' | ', $commands);
3113     $output .= '</div>';
3115 // Context link to post if required
3116     if ($link) {
3117         $output .= '<div class="link">';
3118         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3119                      get_string('postincontext', 'forum').'</a>';
3120         $output .= '</div>';
3121     }
3123     if ($footer) {
3124         $output .= '<div class="footer">'.$footer.'</div>';
3125     }
3126     $output .= '</td></tr></table>'."\n\n";
3128     return $output;
3131 /**
3132  * Print a forum post
3133  *
3134  * @global object
3135  * @global object
3136  * @uses FORUM_MODE_THREADED
3137  * @uses PORTFOLIO_FORMAT_PLAINHTML
3138  * @uses PORTFOLIO_FORMAT_FILE
3139  * @uses PORTFOLIO_FORMAT_RICHHTML
3140  * @uses PORTFOLIO_ADD_TEXT_LINK
3141  * @uses CONTEXT_MODULE
3142  * @param object $post The post to print.
3143  * @param object $discussion
3144  * @param object $forum
3145  * @param object $cm
3146  * @param object $course
3147  * @param boolean $ownpost Whether this post belongs to the current user.
3148  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3149  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3150  * @param string $footer Extra stuff to print after the message.
3151  * @param string $highlight Space-separated list of terms to highlight.
3152  * @param int $post_read true, false or -99. If we already know whether this user
3153  *          has read this post, pass that in, otherwise, pass in -99, and this
3154  *          function will work it out.
3155  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3156  *          the current user can't see this post, if this argument is true
3157  *          (the default) then print a dummy 'you can't see this post' post.
3158  *          If false, don't output anything at all.
3159  * @param bool|null $istracked
3160  * @return void
3161  */
3162 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3163                           $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3164     global $USER, $CFG, $OUTPUT;
3166     require_once($CFG->libdir . '/filelib.php');
3168     // String cache
3169     static $str;
3171     $modcontext = context_module::instance($cm->id);
3173     $post->course = $course->id;
3174     $post->forum  = $forum->id;
3175     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3176     if (!empty($CFG->enableplagiarism)) {
3177         require_once($CFG->libdir.'/plagiarismlib.php');
3178         $post->message .= plagiarism_get_links(array('userid' => $post->userid,
3179             'content' => $post->message,
3180             'cmid' => $cm->id,
3181             'course' => $post->course,
3182             'forum' => $post->forum));
3183     }
3185     // caching
3186     if (!isset($cm->cache)) {
3187         $cm->cache = new stdClass;
3188     }
3190     if (!isset($cm->cache->caps)) {
3191         $cm->cache->caps = array();
3192         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3193         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3194         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3195         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3196         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3197         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3198         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3199         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3200         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3201     }
3203     if (!isset($cm->uservisible)) {
3204         $cm->uservisible = coursemodule_visible_for_user($cm);
3205     }
3207     if ($istracked && is_null($postisread)) {
3208         $postisread = forum_tp_is_post_read($USER->id, $post);
3209     }
3211     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3212         $output = '';
3213         if (!$dummyifcantsee) {
3214             if ($return) {
3215                 return $output;
3216             }
3217             echo $output;
3218             return;
3219         }
3220         $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3221         $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'));
3222         $output .= html_writer::start_tag('div', array('class'=>'row header'));
3223         $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3224         if ($post->parent) {
3225             $output .= html_writer::start_tag('div', array('class'=>'topic'));
3226         } else {
3227             $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3228         }
3229         $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class'=>'subject')); // Subject
3230         $output .= html_writer::tag('div', get_string('forumauthorhidden','forum'), array('class'=>'author')); // author
3231         $output .= html_writer::end_tag('div');
3232         $output .= html_writer::end_tag('div'); // row
3233         $output .= html_writer::start_tag('div', array('class'=>'row'));
3234         $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3235         $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3236         $output .= html_writer::end_tag('div'); // row
3237         $output .= html_writer::end_tag('div'); // forumpost
3239         if ($return) {
3240             return $output;
3241         }
3242         echo $output;
3243         return;
3244     }
3246     if (empty($str)) {
3247         $str = new stdClass;
3248         $str->edit         = get_string('edit', 'forum');
3249         $str->delete       = get_string('delete', 'forum');
3250         $str->reply        = get_string('reply', 'forum');
3251         $str->parent       = get_string('parent', 'forum');
3252         $str->pruneheading = get_string('pruneheading', 'forum');
3253         $str->prune        = get_string('prune', 'forum');
3254         $str->displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3255         $str->markread     = get_string('markread', 'forum');
3256         $str->markunread   = get_string('markunread', 'forum');
3257     }
3259     $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3261     // Build an object that represents the posting user
3262     $postuser = new stdClass;
3263     $postuser->id        = $post->userid;
3264     $postuser->firstname = $post->firstname;
3265     $postuser->lastname  = $post->lastname;
3266     $postuser->imagealt  = $post->imagealt;
3267     $postuser->picture   = $post->picture;
3268     $postuser->email     = $post->email;
3269     // Some handy things for later on
3270     $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3271     $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3273     // Prepare the groups the posting user belongs to
3274     if (isset($cm->cache->usersgroups)) {
3275         $groups = array();
3276         if (isset($cm->cache->usersgroups[$post->userid])) {
3277             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3278                 $groups[$gid] = $cm->cache->groups[$gid];
3279             }
3280         }
3281     } else {
3282         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3283     }
3285     // Prepare the attachements for the post, files then images
3286     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3288     // Determine if we need to shorten this post
3289     $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3292     // Prepare an array of commands
3293     $commands = array();
3295     // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3296     // Don't display the mark read / unread controls in this case.
3297     if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3298         $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3299         $text = $str->markunread;
3300         if (!$postisread) {
3301             $url->param('mark', 'read');
3302             $text = $str->markread;
3303         }
3304         if ($str->displaymode == FORUM_MODE_THREADED) {
3305             $url->param('parent', $post->parent);
3306         } else {
3307             $url->set_anchor('p'.$post->id);
3308         }
3309         $commands[] = array('url'=>$url, 'text'=>$text);
3310     }
3312     // Zoom in to the parent specifically
3313     if ($post->parent) {
3314         $url = new moodle_url($discussionlink);
3315         if ($str->displaymode == FORUM_MODE_THREADED) {
3316             $url->param('parent', $post->parent);
3317         } else {
3318             $url->set_anchor('p'.$post->parent);
3319         }
3320         $commands[] = array('url'=>$url, 'text'=>$str->parent);
3321     }
3323     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3324     $age = time() - $post->created;
3325     if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3326         $age = 0;
3327     }
3329     if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
3330         if (has_capability('moodle/course:manageactivities', $modcontext)) {
3331             // The first post in single simple is the forum description.
3332             $commands[] = array('url'=>new moodle_url('/course/modedit.php', array('update'=>$cm->id, 'sesskey'=>sesskey(), 'return'=>1)), 'text'=>$str->edit);
3333         }
3334     } else if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3335         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3336     }
3338     if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3339         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3340     }
3342     if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
3343         // Do not allow deleting of first post in single simple type.
3344     } else if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3345         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3346     }
3348     if ($reply) {
3349         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php#mformforum', array('reply'=>$post->id)), 'text'=>$str->reply);
3350     }
3352     if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
3353         $p = array('postid' => $post->id);
3354         require_once($CFG->libdir.'/portfoliolib.php');
3355         $button = new portfolio_add_button();
3356         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), 'mod_forum');
3357         if (empty($attachments)) {
3358             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3359         } else {
3360             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3361         }
3363         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3364         if (!empty($porfoliohtml)) {
3365             $commands[] = $porfoliohtml;
3366         }
3367     }
3368     // Finished building commands
3371     // Begin output
3373     $output  = '';
3375     if ($istracked) {
3376         if ($postisread) {
3377             $forumpostclass = ' read';
3378         } else {
3379             $forumpostclass = ' unread';
3380             $output .= html_writer::tag('a', '', array('name'=>'unread'));
3381         }
3382     } else {
3383         // ignore trackign status if not tracked or tracked param missing
3384         $forumpostclass = '';
3385     }
3387     $topicclass = '';
3388     if (empty($post->parent)) {
3389         $topicclass = ' firstpost starter';
3390     }
3392     $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3393     $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass));
3394     $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3395     $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3396     $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3397     $output .= html_writer::end_tag('div');
3400     $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3402     $postsubject = $post->subject;
3403     if (empty($post->subjectnoformat)) {
3404         $postsubject = format_string($postsubject);
3405     }
3406     $output .= html_writer::tag('div', $postsubject, array('class'=>'subject'));
3408     $by = new stdClass();
3409     $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3410     $by->date = userdate($post->modified);
3411     $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author'));
3413     $output .= html_writer::end_tag('div'); //topic
3414     $output .= html_writer::end_tag('div'); //row
3416     $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3417     $output .= html_writer::start_tag('div', array('class'=>'left'));
3419     $groupoutput = '';
3420     if ($groups) {
3421         $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3422     }
3423     if (empty($groupoutput)) {
3424         $groupoutput = '&nbsp;';
3425     }
3426     $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3428     $output .= html_writer::end_tag('div'); //left side
3429     $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3430     $output .= html_writer::start_tag('div', array('class'=>'content'));
3431     if (!empty($attachments)) {
3432         $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3433     }
3435     $options = new stdClass;
3436     $options->para    = false;
3437     $options->trusted = $post->messagetrust;
3438     $options->context = $modcontext;
3439     if ($shortenpost) {
3440         // Prepare shortened version
3441         $postclass    = 'shortenedpost';
3442         $postcontent  = format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3443         $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
3444         $postcontent .= html_writer::tag('span', '('.get_string('numwords', 'moodle', count_words(strip_tags($post->message))).')...', array('class'=>'post-word-count'));
3445     } else {
3446         // Prepare whole post
3447         $postclass    = 'fullpost';
3448         $postcontent  = format_text($post->message, $post->messageformat, $options, $course->id);
3449         if (!empty($highlight)) {
3450             $postcontent = highlight($highlight, $postcontent);
3451         }
3452         $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
3453     }
3454     // Output the post content
3455     $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
3456     $output .= html_writer::end_tag('div'); // Content
3457     $output .= html_writer::end_tag('div'); // Content mask
3458     $output .= html_writer::end_tag('div'); // Row
3460     $output .= html_writer::start_tag('div', array('class'=>'row side'));
3461     $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
3462     $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
3464     // Output ratings
3465     if (!empty($post->rating)) {
3466         $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
3467     }
3469     // Output the commands
3470     $commandhtml = array();
3471     foreach ($commands as $command) {
3472         if (is_array($command)) {
3473             $commandhtml[] = html_writer::link($command['url'], $command['text']);
3474         } else {
3475             $commandhtml[] = $command;
3476         }
3477     }
3478     $output .= html_writer::tag('div', implode(' | ', $commandhtml), array('class'=>'commands'));
3480     // Output link to post if required
3481     if ($link) {
3482         if ($post->replies == 1) {
3483             $replystring = get_string('repliesone', 'forum', $post->replies);
3484         } else {
3485             $replystring = get_string('repliesmany', 'forum', $post->replies);
3486         }
3488         $output .= html_writer::start_tag('div', array('class'=>'link'));
3489         $output .= html_writer::link($discussionlink, get_string('discussthistopic', 'forum'));
3490         $output .= '&nbsp;('.$replystring.')';
3491         $output .= html_writer::end_tag('div'); // link
3492     }