f8f0fea16ca9054ebf034402d8516e28b10ea151
[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                 // If forum_replytouser is not set then send mail using the noreplyaddress.
709                 if (empty($CFG->forum_replytouser)) {
710                     // Clone userfrom as it is referenced by $users.
711                     $cloneduserfrom = clone($userfrom);
712                     $cloneduserfrom->email = $CFG->noreplyaddress;
713                     $eventdata->userfrom = $cloneduserfrom;
714                 }
716                 $smallmessagestrings = new stdClass();
717                 $smallmessagestrings->user = fullname($userfrom);
718                 $smallmessagestrings->forumname = "$shortname: ".format_string($forum->name,true).": ".$discussion->name;
719                 $smallmessagestrings->message = $post->message;
720                 //make sure strings are in message recipients language
721                 $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
723                 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
724                 $eventdata->contexturlname = $discussion->name;
726                 $mailresult = message_send($eventdata);
727                 if (!$mailresult){
728                     mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
729                          " ($userto->email) .. not trying again.");
730                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
731                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
732                     $errorcount[$post->id]++;
733                 } else {
734                     $mailcount[$post->id]++;
736                 // Mark post as read if forum_usermarksread is set off
737                     if (!$CFG->forum_usermarksread) {
738                         $userto->markposts[$post->id] = $post->id;
739                     }
740                 }
742                 mtrace('post '.$post->id. ': '.$post->subject);
743             }
745             // mark processed posts as read
746             forum_tp_mark_posts_read($userto, $userto->markposts);
747             unset($userto);
748         }
749     }
751     if ($posts) {
752         foreach ($posts as $post) {
753             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
754             if ($errorcount[$post->id]) {
755                 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
756             }
757         }
758     }
760     // release some memory
761     unset($subscribedusers);
762     unset($mailcount);
763     unset($errorcount);
765     cron_setup_user();
767     $sitetimezone = $CFG->timezone;
769     // Now see if there are any digest mails waiting to be sent, and if we should send them
771     mtrace('Starting digest processing...');
773     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
775     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
776         set_config('digestmailtimelast', 0);
777     }
779     $timenow = time();
780     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
782     // Delete any really old ones (normally there shouldn't be any)
783     $weekago = $timenow - (7 * 24 * 3600);
784     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
785     mtrace ('Cleaned old digest records');
787     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
789         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
791         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
793         if ($digestposts_rs->valid()) {
795             // We have work to do
796             $usermailcount = 0;
798             //caches - reuse the those filled before too
799             $discussionposts = array();
800             $userdiscussions = array();
802             foreach ($digestposts_rs as $digestpost) {
803                 if (!isset($posts[$digestpost->postid])) {
804                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
805                         $posts[$digestpost->postid] = $post;
806                     } else {
807                         continue;
808                     }
809                 }
810                 $discussionid = $digestpost->discussionid;
811                 if (!isset($discussions[$discussionid])) {
812                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
813                         $discussions[$discussionid] = $discussion;
814                     } else {
815                         continue;
816                     }
817                 }
818                 $forumid = $discussions[$discussionid]->forum;
819                 if (!isset($forums[$forumid])) {
820                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
821                         $forums[$forumid] = $forum;
822                     } else {
823                         continue;
824                     }
825                 }
827                 $courseid = $forums[$forumid]->course;
828                 if (!isset($courses[$courseid])) {
829                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
830                         $courses[$courseid] = $course;
831                     } else {
832                         continue;
833                     }
834                 }
836                 if (!isset($coursemodules[$forumid])) {
837                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
838                         $coursemodules[$forumid] = $cm;
839                     } else {
840                         continue;
841                     }
842                 }
843                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
844                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
845             }
846             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
848             // Data collected, start sending out emails to each user
849             foreach ($userdiscussions as $userid => $thesediscussions) {
851                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
853                 cron_setup_user();
855                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
857                 // First of all delete all the queue entries for this user
858                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
860                 // Init user caches - we keep the cache for one cycle only,
861                 // otherwise it would unnecessarily consume memory.
862                 if (array_key_exists($userid, $users) and isset($users[$userid]->username)) {
863                     $userto = clone($users[$userid]);
864                 } else {
865                     $userto = $DB->get_record('user', array('id' => $userid));
866                     forum_cron_minimise_user_record($userto);
867                 }
868                 $userto->viewfullnames = array();
869                 $userto->canpost       = array();
870                 $userto->markposts     = array();
872                 // Override the language and timezone of the "current" user, so that
873                 // mail is customised for the receiver.
874                 cron_setup_user($userto);
876                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
878                 $headerdata = new stdClass();
879                 $headerdata->sitename = format_string($site->fullname, true);
880                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
882                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
883                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
885                 $posthtml = "<head>";
886 /*                foreach ($CFG->stylesheets as $stylesheet) {
887                     //TODO: MDL-21120
888                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
889                 }*/
890                 $posthtml .= "</head>\n<body id=\"email\">\n";
891                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
893                 foreach ($thesediscussions as $discussionid) {
895                     @set_time_limit(120);   // to be reset for each post
897                     $discussion = $discussions[$discussionid];
898                     $forum      = $forums[$discussion->forum];
899                     $course     = $courses[$forum->course];
900                     $cm         = $coursemodules[$forum->id];
902                     //override language
903                     cron_setup_user($userto, $course);
905                     // Fill caches
906                     if (!isset($userto->viewfullnames[$forum->id])) {
907                         $modcontext = context_module::instance($cm->id);
908                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
909                     }
910                     if (!isset($userto->canpost[$discussion->id])) {
911                         $modcontext = context_module::instance($cm->id);
912                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
913                     }
915                     $strforums      = get_string('forums', 'forum');
916                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
917                     $canreply       = $userto->canpost[$discussion->id];
918                     $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
920                     $posttext .= "\n \n";
921                     $posttext .= '=====================================================================';
922                     $posttext .= "\n \n";
923                     $posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
924                     if ($discussion->name != $forum->name) {
925                         $posttext  .= " -> ".format_string($discussion->name,true);
926                     }
927                     $posttext .= "\n";
929                     $posthtml .= "<p><font face=\"sans-serif\">".
930                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
931                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
932                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
933                     if ($discussion->name == $forum->name) {
934                         $posthtml .= "</font></p>";
935                     } else {
936                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
937                     }
938                     $posthtml .= '<p>';
940                     $postsarray = $discussionposts[$discussionid];
941                     sort($postsarray);
943                     foreach ($postsarray as $postid) {
944                         $post = $posts[$postid];
946                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
947                             $userfrom = $users[$post->userid];
948                             if (!isset($userfrom->idnumber)) {
949                                 $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
950                                 forum_cron_minimise_user_record($userfrom);
951                             }
953                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
954                             forum_cron_minimise_user_record($userfrom);
955                             if ($userscount <= FORUM_CRON_USER_CACHE) {
956                                 $userscount++;
957                                 $users[$userfrom->id] = $userfrom;
958                             }
960                         } else {
961                             mtrace('Could not find user '.$post->userid);
962                             continue;
963                         }
965                         if (!isset($userfrom->groups[$forum->id])) {
966                             if (!isset($userfrom->groups)) {
967                                 $userfrom->groups = array();
968                                 if (isset($users[$userfrom->id])) {
969                                     $users[$userfrom->id]->groups = array();
970                                 }
971                             }
972                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
973                             if (isset($users[$userfrom->id])) {
974                                 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
975                             }
976                         }
978                         $userfrom->customheaders = array ("Precedence: Bulk");
980                         if ($userto->maildigest == 2) {
981                             // Subjects only
982                             $by = new stdClass();
983                             $by->name = fullname($userfrom);
984                             $by->date = userdate($post->modified);
985                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
986                             $posttext .= "\n---------------------------------------------------------------------";
988                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
989                             $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>';
991                         } else {
992                             // The full treatment
993                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
994                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
996                         // Create an array of postid's for this user to mark as read.
997                             if (!$CFG->forum_usermarksread) {
998                                 $userto->markposts[$post->id] = $post->id;
999                             }
1000                         }
1001                     }
1002                     if ($canunsubscribe) {
1003                         $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>";
1004                     } else {
1005                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
1006                     }
1007                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
1008                 }
1009                 $posthtml .= '</body>';
1011                 if (empty($userto->mailformat) || $userto->mailformat != 1) {
1012                     // This user DOESN'T want to receive HTML
1013                     $posthtml = '';
1014                 }
1016                 $attachment = $attachname='';
1017                 // Directly email forum digests rather than sending them via messaging, use the
1018                 // site shortname as 'from name', the noreply address will be used by email_to_user.
1019                 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname);
1021                 if (!$mailresult) {
1022                     mtrace("ERROR!");
1023                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
1024                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
1025                 } else {
1026                     mtrace("success.");
1027                     $usermailcount++;
1029                     // Mark post as read if forum_usermarksread is set off
1030                     forum_tp_mark_posts_read($userto, $userto->markposts);
1031                 }
1032             }
1033         }
1034     /// We have finishied all digest emails, update $CFG->digestmailtimelast
1035         set_config('digestmailtimelast', $timenow);
1036     }
1038     cron_setup_user();
1040     if (!empty($usermailcount)) {
1041         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
1042     }
1044     if (!empty($CFG->forum_lastreadclean)) {
1045         $timenow = time();
1046         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
1047             set_config('forum_lastreadclean', $timenow);
1048             mtrace('Removing old forum read tracking info...');
1049             forum_tp_clean_read_records();
1050         }
1051     } else {
1052         set_config('forum_lastreadclean', time());
1053     }
1056     return true;
1059 /**
1060  * Builds and returns the body of the email notification in plain text.
1061  *
1062  * @global object
1063  * @global object
1064  * @uses CONTEXT_MODULE
1065  * @param object $course
1066  * @param object $cm
1067  * @param object $forum
1068  * @param object $discussion
1069  * @param object $post
1070  * @param object $userfrom
1071  * @param object $userto
1072  * @param boolean $bare
1073  * @return string The email body in plain text format.
1074  */
1075 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
1076     global $CFG, $USER;
1078     $modcontext = context_module::instance($cm->id);
1080     if (!isset($userto->viewfullnames[$forum->id])) {
1081         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
1082     } else {
1083         $viewfullnames = $userto->viewfullnames[$forum->id];
1084     }
1086     if (!isset($userto->canpost[$discussion->id])) {
1087         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
1088     } else {
1089         $canreply = $userto->canpost[$discussion->id];
1090     }
1092     $by = New stdClass;
1093     $by->name = fullname($userfrom, $viewfullnames);
1094     $by->date = userdate($post->modified, "", $userto->timezone);
1096     $strbynameondate = get_string('bynameondate', 'forum', $by);
1098     $strforums = get_string('forums', 'forum');
1100     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1102     $posttext = '';
1104     if (!$bare) {
1105         $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
1106         $posttext  = "$shortname -> $strforums -> ".format_string($forum->name,true);
1108         if ($discussion->name != $forum->name) {
1109             $posttext  .= " -> ".format_string($discussion->name,true);
1110         }
1111     }
1113     // add absolute file links
1114     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
1116     $posttext .= "\n---------------------------------------------------------------------\n";
1117     $posttext .= format_string($post->subject,true);
1118     if ($bare) {
1119         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1120     }
1121     $posttext .= "\n".$strbynameondate."\n";
1122     $posttext .= "---------------------------------------------------------------------\n";
1123     $posttext .= format_text_email($post->message, $post->messageformat);
1124     $posttext .= "\n\n";
1125     $posttext .= forum_print_attachments($post, $cm, "text");
1127     if (!$bare && $canreply) {
1128         $posttext .= "---------------------------------------------------------------------\n";
1129         $posttext .= get_string("postmailinfo", "forum", $shortname)."\n";
1130         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1131     }
1132     if (!$bare && $canunsubscribe) {
1133         $posttext .= "\n---------------------------------------------------------------------\n";
1134         $posttext .= get_string("unsubscribe", "forum");
1135         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1136     }
1138     return $posttext;
1141 /**
1142  * Builds and returns the body of the email notification in html format.
1143  *
1144  * @global object
1145  * @param object $course
1146  * @param object $cm
1147  * @param object $forum
1148  * @param object $discussion
1149  * @param object $post
1150  * @param object $userfrom
1151  * @param object $userto
1152  * @return string The email text in HTML format
1153  */
1154 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1155     global $CFG;
1157     if ($userto->mailformat != 1) {  // Needs to be HTML
1158         return '';
1159     }
1161     if (!isset($userto->canpost[$discussion->id])) {
1162         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course);
1163     } else {
1164         $canreply = $userto->canpost[$discussion->id];
1165     }
1167     $strforums = get_string('forums', 'forum');
1168     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1169     $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
1171     $posthtml = '<head>';
1172 /*    foreach ($CFG->stylesheets as $stylesheet) {
1173         //TODO: MDL-21120
1174         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1175     }*/
1176     $posthtml .= '</head>';
1177     $posthtml .= "\n<body id=\"email\">\n\n";
1179     $posthtml .= '<div class="navbar">'.
1180     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$shortname.'</a> &raquo; '.
1181     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1182     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1183     if ($discussion->name == $forum->name) {
1184         $posthtml .= '</div>';
1185     } else {
1186         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1187                      format_string($discussion->name,true).'</a></div>';
1188     }
1189     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1191     if ($canunsubscribe) {
1192         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1193                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1194                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1195     }
1197     $posthtml .= '</body>';
1199     return $posthtml;
1203 /**
1204  *
1205  * @param object $course
1206  * @param object $user
1207  * @param object $mod TODO this is not used in this function, refactor
1208  * @param object $forum
1209  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1210  */
1211 function forum_user_outline($course, $user, $mod, $forum) {
1212     global $CFG;
1213     require_once("$CFG->libdir/gradelib.php");
1214     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1215     if (empty($grades->items[0]->grades)) {
1216         $grade = false;
1217     } else {
1218         $grade = reset($grades->items[0]->grades);
1219     }
1221     $count = forum_count_user_posts($forum->id, $user->id);
1223     if ($count && $count->postcount > 0) {
1224         $result = new stdClass();
1225         $result->info = get_string("numposts", "forum", $count->postcount);
1226         $result->time = $count->lastpost;
1227         if ($grade) {
1228             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1229         }
1230         return $result;
1231     } else if ($grade) {
1232         $result = new stdClass();
1233         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1235         //datesubmitted == time created. dategraded == time modified or time overridden
1236         //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
1237         //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
1238         if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1239             $result->time = $grade->dategraded;
1240         } else {
1241             $result->time = $grade->datesubmitted;
1242         }
1244         return $result;
1245     }
1246     return NULL;
1250 /**
1251  * @global object
1252  * @global object
1253  * @param object $coure
1254  * @param object $user
1255  * @param object $mod
1256  * @param object $forum
1257  */
1258 function forum_user_complete($course, $user, $mod, $forum) {
1259     global $CFG,$USER, $OUTPUT;
1260     require_once("$CFG->libdir/gradelib.php");
1262     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1263     if (!empty($grades->items[0]->grades)) {
1264         $grade = reset($grades->items[0]->grades);
1265         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1266         if ($grade->str_feedback) {
1267             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1268         }
1269     }
1271     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1273         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1274             print_error('invalidcoursemodule');
1275         }
1276         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1278         foreach ($posts as $post) {
1279             if (!isset($discussions[$post->discussion])) {
1280                 continue;
1281             }
1282             $discussion = $discussions[$post->discussion];
1284             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1285         }
1286     } else {
1287         echo "<p>".get_string("noposts", "forum")."</p>";
1288     }
1296 /**
1297  * @global object
1298  * @global object
1299  * @global object
1300  * @param array $courses
1301  * @param array $htmlarray
1302  */
1303 function forum_print_overview($courses,&$htmlarray) {
1304     global $USER, $CFG, $DB, $SESSION;
1306     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1307         return array();
1308     }
1310     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1311         return;
1312     }
1314     // Courses to search for new posts
1315     $coursessqls = array();
1316     $params = array();
1317     foreach ($courses as $course) {
1319         // If the user has never entered into the course all posts are pending
1320         if ($course->lastaccess == 0) {
1321             $coursessqls[] = '(f.course = ?)';
1322             $params[] = $course->id;
1324         // Only posts created after the course last access
1325         } else {
1326             $coursessqls[] = '(f.course = ? AND p.created > ?)';
1327             $params[] = $course->id;
1328             $params[] = $course->lastaccess;
1329         }
1330     }
1331     $params[] = $USER->id;
1332     $coursessql = implode(' OR ', $coursessqls);
1334     $sql = "SELECT f.id, COUNT(*) as count "
1335                 .'FROM {forum} f '
1336                 .'JOIN {forum_discussions} d ON d.forum  = f.id '
1337                 .'JOIN {forum_posts} p ON p.discussion = d.id '
1338                 ."WHERE ($coursessql) "
1339                 .'AND p.userid != ? '
1340                 .'GROUP BY f.id';
1342     if (!$new = $DB->get_records_sql($sql, $params)) {
1343         $new = array(); // avoid warnings
1344     }
1346     // also get all forum tracking stuff ONCE.
1347     $trackingforums = array();
1348     foreach ($forums as $forum) {
1349         if (forum_tp_can_track_forums($forum)) {
1350             $trackingforums[$forum->id] = $forum;
1351         }
1352     }
1354     if (count($trackingforums) > 0) {
1355         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1356         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1357             ' FROM {forum_posts} p '.
1358             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1359             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1360         $params = array($USER->id);
1362         foreach ($trackingforums as $track) {
1363             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1364             $params[] = $track->id;
1365             if (isset($SESSION->currentgroup[$track->course])) {
1366                 $groupid =  $SESSION->currentgroup[$track->course];
1367             } else {
1368                 // get first groupid
1369                 $groupids = groups_get_all_groups($track->course, $USER->id);
1370                 if ($groupids) {
1371                     reset($groupids);
1372                     $groupid = key($groupids);
1373                     $SESSION->currentgroup[$track->course] = $groupid;
1374                 } else {
1375                     $groupid = 0;
1376                 }
1377                 unset($groupids);
1378             }
1379             $params[] = $groupid;
1380         }
1381         $sql = substr($sql,0,-3); // take off the last OR
1382         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1383         $params[] = $cutoffdate;
1385         if (!$unread = $DB->get_records_sql($sql, $params)) {
1386             $unread = array();
1387         }
1388     } else {
1389         $unread = array();
1390     }
1392     if (empty($unread) and empty($new)) {
1393         return;
1394     }
1396     $strforum = get_string('modulename','forum');
1398     foreach ($forums as $forum) {
1399         $str = '';
1400         $count = 0;
1401         $thisunread = 0;
1402         $showunread = false;
1403         // either we have something from logs, or trackposts, or nothing.
1404         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1405             $count = $new[$forum->id]->count;
1406         }
1407         if (array_key_exists($forum->id,$unread)) {
1408             $thisunread = $unread[$forum->id]->count;
1409             $showunread = true;
1410         }
1411         if ($count > 0 || $thisunread > 0) {
1412             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1413                 $forum->name.'</a></div>';
1414             $str .= '<div class="info"><span class="postsincelogin">';
1415             $str .= get_string('overviewnumpostssince', 'forum', $count)."</span>";
1416             if (!empty($showunread)) {
1417                 $str .= '<div class="unreadposts">'.get_string('overviewnumunread', 'forum', $thisunread).'</div>';
1418             }
1419             $str .= '</div></div>';
1420         }
1421         if (!empty($str)) {
1422             if (!array_key_exists($forum->course,$htmlarray)) {
1423                 $htmlarray[$forum->course] = array();
1424             }
1425             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1426                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1427             }
1428             $htmlarray[$forum->course]['forum'] .= $str;
1429         }
1430     }
1433 /**
1434  * Given a course and a date, prints a summary of all the new
1435  * messages posted in the course since that date
1436  *
1437  * @global object
1438  * @global object
1439  * @global object
1440  * @uses CONTEXT_MODULE
1441  * @uses VISIBLEGROUPS
1442  * @param object $course
1443  * @param bool $viewfullnames capability
1444  * @param int $timestart
1445  * @return bool success
1446  */
1447 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1448     global $CFG, $USER, $DB, $OUTPUT;
1450     // do not use log table if possible, it may be huge and is expensive to join with other tables
1452     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1453                                               d.timestart, d.timeend, d.userid AS duserid,
1454                                               u.firstname, u.lastname, u.email, u.picture
1455                                          FROM {forum_posts} p
1456                                               JOIN {forum_discussions} d ON d.id = p.discussion
1457                                               JOIN {forum} f             ON f.id = d.forum
1458                                               JOIN {user} u              ON u.id = p.userid
1459                                         WHERE p.created > ? AND f.course = ?
1460                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1461          return false;
1462     }
1464     $modinfo = get_fast_modinfo($course);
1466     $groupmodes = array();
1467     $cms    = array();
1469     $strftimerecent = get_string('strftimerecent');
1471     $printposts = array();
1472     foreach ($posts as $post) {
1473         if (!isset($modinfo->instances['forum'][$post->forum])) {
1474             // not visible
1475             continue;
1476         }
1477         $cm = $modinfo->instances['forum'][$post->forum];
1478         if (!$cm->uservisible) {
1479             continue;
1480         }
1481         $context = context_module::instance($cm->id);
1483         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1484             continue;
1485         }
1487         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1488           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1489             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1490                 continue;
1491             }
1492         }
1494         $groupmode = groups_get_activity_groupmode($cm, $course);
1496         if ($groupmode) {
1497             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1498                 // oki (Open discussions have groupid -1)
1499             } else {
1500                 // separate mode
1501                 if (isguestuser()) {
1502                     // shortcut
1503                     continue;
1504                 }
1506                 if (is_null($modinfo->groups)) {
1507                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1508                 }
1510                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1511                     continue;
1512                 }
1513             }
1514         }
1516         $printposts[] = $post;
1517     }
1518     unset($posts);
1520     if (!$printposts) {
1521         return false;
1522     }
1524     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1525     echo "\n<ul class='unlist'>\n";
1527     foreach ($printposts as $post) {
1528         $subjectclass = empty($post->parent) ? ' bold' : '';
1530         echo '<li><div class="head">'.
1531                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1532                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1533              '</div>';
1534         echo '<div class="info'.$subjectclass.'">';
1535         if (empty($post->parent)) {
1536             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1537         } else {
1538             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1539         }
1540         $post->subject = break_up_long_words(format_string($post->subject, true));
1541         echo $post->subject;
1542         echo "</a>\"</div></li>\n";
1543     }
1545     echo "</ul>\n";
1547     return true;
1550 /**
1551  * Return grade for given user or all users.
1552  *
1553  * @global object
1554  * @global object
1555  * @param object $forum
1556  * @param int $userid optional user id, 0 means all users
1557  * @return array array of grades, false if none
1558  */
1559 function forum_get_user_grades($forum, $userid = 0) {
1560     global $CFG;
1562     require_once($CFG->dirroot.'/rating/lib.php');
1564     $ratingoptions = new stdClass;
1565     $ratingoptions->component = 'mod_forum';
1566     $ratingoptions->ratingarea = 'post';
1568     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1569     $ratingoptions->modulename = 'forum';
1570     $ratingoptions->moduleid   = $forum->id;
1571     $ratingoptions->userid = $userid;
1572     $ratingoptions->aggregationmethod = $forum->assessed;
1573     $ratingoptions->scaleid = $forum->scale;
1574     $ratingoptions->itemtable = 'forum_posts';
1575     $ratingoptions->itemtableusercolumn = 'userid';
1577     $rm = new rating_manager();
1578     return $rm->get_user_grades($ratingoptions);
1581 /**
1582  * Update activity grades
1583  *
1584  * @category grade
1585  * @param object $forum
1586  * @param int $userid specific user only, 0 means all
1587  * @param boolean $nullifnone return null if grade does not exist
1588  * @return void
1589  */
1590 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1591     global $CFG, $DB;
1592     require_once($CFG->libdir.'/gradelib.php');
1594     if (!$forum->assessed) {
1595         forum_grade_item_update($forum);
1597     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1598         forum_grade_item_update($forum, $grades);
1600     } else if ($userid and $nullifnone) {
1601         $grade = new stdClass();
1602         $grade->userid   = $userid;
1603         $grade->rawgrade = NULL;
1604         forum_grade_item_update($forum, $grade);
1606     } else {
1607         forum_grade_item_update($forum);
1608     }
1611 /**
1612  * Update all grades in gradebook.
1613  * @global object
1614  */
1615 function forum_upgrade_grades() {
1616     global $DB;
1618     $sql = "SELECT COUNT('x')
1619               FROM {forum} f, {course_modules} cm, {modules} m
1620              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1621     $count = $DB->count_records_sql($sql);
1623     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1624               FROM {forum} f, {course_modules} cm, {modules} m
1625              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1626     $rs = $DB->get_recordset_sql($sql);
1627     if ($rs->valid()) {
1628         $pbar = new progress_bar('forumupgradegrades', 500, true);
1629         $i=0;
1630         foreach ($rs as $forum) {
1631             $i++;
1632             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1633             forum_update_grades($forum, 0, false);
1634             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1635         }
1636     }
1637     $rs->close();
1640 /**
1641  * Create/update grade item for given forum
1642  *
1643  * @category grade
1644  * @uses GRADE_TYPE_NONE
1645  * @uses GRADE_TYPE_VALUE
1646  * @uses GRADE_TYPE_SCALE
1647  * @param stdClass $forum Forum object with extra cmidnumber
1648  * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
1649  * @return int 0 if ok
1650  */
1651 function forum_grade_item_update($forum, $grades=NULL) {
1652     global $CFG;
1653     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1654         require_once($CFG->libdir.'/gradelib.php');
1655     }
1657     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1659     if (!$forum->assessed or $forum->scale == 0) {
1660         $params['gradetype'] = GRADE_TYPE_NONE;
1662     } else if ($forum->scale > 0) {
1663         $params['gradetype'] = GRADE_TYPE_VALUE;
1664         $params['grademax']  = $forum->scale;
1665         $params['grademin']  = 0;
1667     } else if ($forum->scale < 0) {
1668         $params['gradetype'] = GRADE_TYPE_SCALE;
1669         $params['scaleid']   = -$forum->scale;
1670     }
1672     if ($grades  === 'reset') {
1673         $params['reset'] = true;
1674         $grades = NULL;
1675     }
1677     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1680 /**
1681  * Delete grade item for given forum
1682  *
1683  * @category grade
1684  * @param stdClass $forum Forum object
1685  * @return grade_item
1686  */
1687 function forum_grade_item_delete($forum) {
1688     global $CFG;
1689     require_once($CFG->libdir.'/gradelib.php');
1691     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1695 /**
1696  * This function returns if a scale is being used by one forum
1697  *
1698  * @global object
1699  * @param int $forumid
1700  * @param int $scaleid negative number
1701  * @return bool
1702  */
1703 function forum_scale_used ($forumid,$scaleid) {
1704     global $DB;
1705     $return = false;
1707     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1709     if (!empty($rec) && !empty($scaleid)) {
1710         $return = true;
1711     }
1713     return $return;
1716 /**
1717  * Checks if scale is being used by any instance of forum
1718  *
1719  * This is used to find out if scale used anywhere
1720  *
1721  * @global object
1722  * @param $scaleid int
1723  * @return boolean True if the scale is used by any forum
1724  */
1725 function forum_scale_used_anywhere($scaleid) {
1726     global $DB;
1727     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1728         return true;
1729     } else {
1730         return false;
1731     }
1734 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1736 /**
1737  * Gets a post with all info ready for forum_print_post
1738  * Most of these joins are just to get the forum id
1739  *
1740  * @global object
1741  * @global object
1742  * @param int $postid
1743  * @return mixed array of posts or false
1744  */
1745 function forum_get_post_full($postid) {
1746     global $CFG, $DB;
1748     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1749                              FROM {forum_posts} p
1750                                   JOIN {forum_discussions} d ON p.discussion = d.id
1751                                   LEFT JOIN {user} u ON p.userid = u.id
1752                             WHERE p.id = ?", array($postid));
1755 /**
1756  * Gets posts with all info ready for forum_print_post
1757  * We pass forumid in because we always know it so no need to make a
1758  * complicated join to find it out.
1759  *
1760  * @global object
1761  * @global object
1762  * @return mixed array of posts or false
1763  */
1764 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1765     global $CFG, $DB;
1767     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1768                               FROM {forum_posts} p
1769                          LEFT JOIN {user} u ON p.userid = u.id
1770                              WHERE p.discussion = ?
1771                                AND p.parent > 0 $sort", array($discussion));
1774 /**
1775  * Gets all posts in discussion including top parent.
1776  *
1777  * @global object
1778  * @global object
1779  * @global object
1780  * @param int $discussionid
1781  * @param string $sort
1782  * @param bool $tracking does user track the forum?
1783  * @return array of posts
1784  */
1785 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1786     global $CFG, $DB, $USER;
1788     $tr_sel  = "";
1789     $tr_join = "";
1790     $params = array();
1792     if ($tracking) {
1793         $now = time();
1794         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1795         $tr_sel  = ", fr.id AS postread";
1796         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1797         $params[] = $USER->id;
1798     }
1800     $params[] = $discussionid;
1801     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1802                                      FROM {forum_posts} p
1803                                           LEFT JOIN {user} u ON p.userid = u.id
1804                                           $tr_join
1805                                     WHERE p.discussion = ?
1806                                  ORDER BY $sort", $params)) {
1807         return array();
1808     }
1810     foreach ($posts as $pid=>$p) {
1811         if ($tracking) {
1812             if (forum_tp_is_post_old($p)) {
1813                  $posts[$pid]->postread = true;
1814             }
1815         }
1816         if (!$p->parent) {
1817             continue;
1818         }
1819         if (!isset($posts[$p->parent])) {
1820             continue; // parent does not exist??
1821         }
1822         if (!isset($posts[$p->parent]->children)) {
1823             $posts[$p->parent]->children = array();
1824         }
1825         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1826     }
1828     return $posts;
1831 /**
1832  * Gets posts with all info ready for forum_print_post
1833  * We pass forumid in because we always know it so no need to make a
1834  * complicated join to find it out.
1835  *
1836  * @global object
1837  * @global object
1838  * @param int $parent
1839  * @param int $forumid
1840  * @return array
1841  */
1842 function forum_get_child_posts($parent, $forumid) {
1843     global $CFG, $DB;
1845     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1846                               FROM {forum_posts} p
1847                          LEFT JOIN {user} u ON p.userid = u.id
1848                              WHERE p.parent = ?
1849                           ORDER BY p.created ASC", array($parent));
1852 /**
1853  * An array of forum objects that the user is allowed to read/search through.
1854  *
1855  * @global object
1856  * @global object
1857  * @global object
1858  * @param int $userid
1859  * @param int $courseid if 0, we look for forums throughout the whole site.
1860  * @return array of forum objects, or false if no matches
1861  *         Forum objects have the following attributes:
1862  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1863  *         viewhiddentimedposts
1864  */
1865 function forum_get_readable_forums($userid, $courseid=0) {
1867     global $CFG, $DB, $USER;
1868     require_once($CFG->dirroot.'/course/lib.php');
1870     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1871         print_error('notinstalled', 'forum');
1872     }
1874     if ($courseid) {
1875         $courses = $DB->get_records('course', array('id' => $courseid));
1876     } else {
1877         // If no course is specified, then the user can see SITE + his courses.
1878         $courses1 = $DB->get_records('course', array('id' => SITEID));
1879         $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
1880         $courses = array_merge($courses1, $courses2);
1881     }
1882     if (!$courses) {
1883         return array();
1884     }
1886     $readableforums = array();
1888     foreach ($courses as $course) {
1890         $modinfo = get_fast_modinfo($course);
1891         if (is_null($modinfo->groups)) {
1892             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1893         }
1895         if (empty($modinfo->instances['forum'])) {
1896             // hmm, no forums?
1897             continue;
1898         }
1900         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1902         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1903             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1904                 continue;
1905             }
1906             $context = context_module::instance($cm->id);
1907             $forum = $courseforums[$forumid];
1908             $forum->context = $context;
1909             $forum->cm = $cm;
1911             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1912                 continue;
1913             }
1915          /// group access
1916             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1917                 if (is_null($modinfo->groups)) {
1918                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1919                 }
1920                 if (isset($modinfo->groups[$cm->groupingid])) {
1921                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1922                     $forum->onlygroups[] = -1;
1923                 } else {
1924                     $forum->onlygroups = array(-1);
1925                 }
1926             }
1928         /// hidden timed discussions
1929             $forum->viewhiddentimedposts = true;
1930             if (!empty($CFG->forum_enabletimedposts)) {
1931                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1932                     $forum->viewhiddentimedposts = false;
1933                 }
1934             }
1936         /// qanda access
1937             if ($forum->type == 'qanda'
1938                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1940                 // We need to check whether the user has posted in the qanda forum.
1941                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1942                                                     // the user is allowed to see in this forum.
1943                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1944                     foreach ($discussionspostedin as $d) {
1945                         $forum->onlydiscussions[] = $d->id;
1946                     }
1947                 }
1948             }
1950             $readableforums[$forum->id] = $forum;
1951         }
1953         unset($modinfo);
1955     } // End foreach $courses
1957     return $readableforums;
1960 /**
1961  * Returns a list of posts found using an array of search terms.
1962  *
1963  * @global object
1964  * @global object
1965  * @global object
1966  * @param array $searchterms array of search terms, e.g. word +word -word
1967  * @param int $courseid if 0, we search through the whole site
1968  * @param int $limitfrom
1969  * @param int $limitnum
1970  * @param int &$totalcount
1971  * @param string $extrasql
1972  * @return array|bool Array of posts found or false
1973  */
1974 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1975                             &$totalcount, $extrasql='') {
1976     global $CFG, $DB, $USER;
1977     require_once($CFG->libdir.'/searchlib.php');
1979     $forums = forum_get_readable_forums($USER->id, $courseid);
1981     if (count($forums) == 0) {
1982         $totalcount = 0;
1983         return false;
1984     }
1986     $now = round(time(), -2); // db friendly
1988     $fullaccess = array();
1989     $where = array();
1990     $params = array();
1992     foreach ($forums as $forumid => $forum) {
1993         $select = array();
1995         if (!$forum->viewhiddentimedposts) {
1996             $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
1997             $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
1998         }
2000         $cm = $forum->cm;
2001         $context = $forum->context;
2003         if ($forum->type == 'qanda'
2004             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
2005             if (!empty($forum->onlydiscussions)) {
2006                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
2007                 $params = array_merge($params, $discussionid_params);
2008                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
2009             } else {
2010                 $select[] = "p.parent = 0";
2011             }
2012         }
2014         if (!empty($forum->onlygroups)) {
2015             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
2016             $params = array_merge($params, $groupid_params);
2017             $select[] = "d.groupid $groupid_sql";
2018         }
2020         if ($select) {
2021             $selects = implode(" AND ", $select);
2022             $where[] = "(d.forum = :forum{$forumid} AND $selects)";
2023             $params['forum'.$forumid] = $forumid;
2024         } else {
2025             $fullaccess[] = $forumid;
2026         }
2027     }
2029     if ($fullaccess) {
2030         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
2031         $params = array_merge($params, $fullid_params);
2032         $where[] = "(d.forum $fullid_sql)";
2033     }
2035     $selectdiscussion = "(".implode(" OR ", $where).")";
2037     $messagesearch = '';
2038     $searchstring = '';
2040     // Need to concat these back together for parser to work.
2041     foreach($searchterms as $searchterm){
2042         if ($searchstring != '') {
2043             $searchstring .= ' ';
2044         }
2045         $searchstring .= $searchterm;
2046     }
2048     // We need to allow quoted strings for the search. The quotes *should* be stripped
2049     // by the parser, but this should be examined carefully for security implications.
2050     $searchstring = str_replace("\\\"","\"",$searchstring);
2051     $parser = new search_parser();
2052     $lexer = new search_lexer($parser);
2054     if ($lexer->parse($searchstring)) {
2055         $parsearray = $parser->get_parsed_array();
2056     // Experimental feature under 1.8! MDL-8830
2057     // Use alternative text searches if defined
2058     // This feature only works under mysql until properly implemented for other DBs
2059     // Requires manual creation of text index for forum_posts before enabling it:
2060     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2061     // Experimental feature under 1.8! MDL-8830
2062         if (!empty($CFG->forum_usetextsearches)) {
2063             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2064                                                  'p.userid', 'u.id', 'u.firstname',
2065                                                  'u.lastname', 'p.modified', 'd.forum');
2066         } else {
2067             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2068                                                  'p.userid', 'u.id', 'u.firstname',
2069                                                  'u.lastname', 'p.modified', 'd.forum');
2070         }
2071         $params = array_merge($params, $msparams);
2072     }
2074     $fromsql = "{forum_posts} p,
2075                   {forum_discussions} d,
2076                   {user} u";
2078     $selectsql = " $messagesearch
2079                AND p.discussion = d.id
2080                AND p.userid = u.id
2081                AND $selectdiscussion
2082                    $extrasql";
2084     $countsql = "SELECT COUNT(*)
2085                    FROM $fromsql
2086                   WHERE $selectsql";
2088     $searchsql = "SELECT p.*,
2089                          d.forum,
2090                          u.firstname,
2091                          u.lastname,
2092                          u.email,
2093                          u.picture,
2094                          u.imagealt
2095                     FROM $fromsql
2096                    WHERE $selectsql
2097                 ORDER BY p.modified DESC";
2099     $totalcount = $DB->count_records_sql($countsql, $params);
2101     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2104 /**
2105  * Returns a list of ratings for a particular post - sorted.
2106  *
2107  * TODO: Check if this function is actually used anywhere.
2108  * Up until the fix for MDL-27471 this function wasn't even returning.
2109  *
2110  * @param stdClass $context
2111  * @param int $postid
2112  * @param string $sort
2113  * @return array Array of ratings or false
2114  */
2115 function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
2116     $options = new stdClass;
2117     $options->context = $context;
2118     $options->component = 'mod_forum';
2119     $options->ratingarea = 'post';
2120     $options->itemid = $postid;
2121     $options->sort = "ORDER BY $sort";
2123     $rm = new rating_manager();
2124     return $rm->get_all_ratings_for_item($options);
2127 /**
2128  * Returns a list of all new posts that have not been mailed yet
2129  *
2130  * @param int $starttime posts created after this time
2131  * @param int $endtime posts created before this
2132  * @param int $now used for timed discussions only
2133  * @return array
2134  */
2135 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2136     global $CFG, $DB;
2138     $params = array($starttime, $endtime);
2139     if (!empty($CFG->forum_enabletimedposts)) {
2140         if (empty($now)) {
2141             $now = time();
2142         }
2143         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2144         $params[] = $now;
2145         $params[] = $now;
2146     } else {
2147         $timedsql = "";
2148     }
2150     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2151                               FROM {forum_posts} p
2152                                    JOIN {forum_discussions} d ON d.id = p.discussion
2153                              WHERE p.mailed = 0
2154                                    AND p.created >= ?
2155                                    AND (p.created < ? OR p.mailnow = 1)
2156                                    $timedsql
2157                           ORDER BY p.modified ASC", $params);
2160 /**
2161  * Marks posts before a certain time as being mailed already
2162  *
2163  * @global object
2164  * @global object
2165  * @param int $endtime
2166  * @param int $now Defaults to time()
2167  * @return bool
2168  */
2169 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2170     global $CFG, $DB;
2171     if (empty($now)) {
2172         $now = time();
2173     }
2175     if (empty($CFG->forum_enabletimedposts)) {
2176         return $DB->execute("UPDATE {forum_posts}
2177                                SET mailed = '1'
2178                              WHERE (created < ? OR mailnow = 1)
2179                                    AND mailed = 0", array($endtime));
2181     } else {
2182         return $DB->execute("UPDATE {forum_posts}
2183                                SET mailed = '1'
2184                              WHERE discussion NOT IN (SELECT d.id
2185                                                         FROM {forum_discussions} d
2186                                                        WHERE d.timestart > ?)
2187                                    AND (created < ? OR mailnow = 1)
2188                                    AND mailed = 0", array($now, $endtime));
2189     }
2192 /**
2193  * Get all the posts for a user in a forum suitable for forum_print_post
2194  *
2195  * @global object
2196  * @global object
2197  * @uses CONTEXT_MODULE
2198  * @return array
2199  */
2200 function forum_get_user_posts($forumid, $userid) {
2201     global $CFG, $DB;
2203     $timedsql = "";
2204     $params = array($forumid, $userid);
2206     if (!empty($CFG->forum_enabletimedposts)) {
2207         $cm = get_coursemodule_from_instance('forum', $forumid);
2208         if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2209             $now = time();
2210             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2211             $params[] = $now;
2212             $params[] = $now;
2213         }
2214     }
2216     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2217                               FROM {forum} f
2218                                    JOIN {forum_discussions} d ON d.forum = f.id
2219                                    JOIN {forum_posts} p       ON p.discussion = d.id
2220                                    JOIN {user} u              ON u.id = p.userid
2221                              WHERE f.id = ?
2222                                    AND p.userid = ?
2223                                    $timedsql
2224                           ORDER BY p.modified ASC", $params);
2227 /**
2228  * Get all the discussions user participated in
2229  *
2230  * @global object
2231  * @global object
2232  * @uses CONTEXT_MODULE
2233  * @param int $forumid
2234  * @param int $userid
2235  * @return array Array or false
2236  */
2237 function forum_get_user_involved_discussions($forumid, $userid) {
2238     global $CFG, $DB;
2240     $timedsql = "";
2241     $params = array($forumid, $userid);
2242     if (!empty($CFG->forum_enabletimedposts)) {
2243         $cm = get_coursemodule_from_instance('forum', $forumid);
2244         if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2245             $now = time();
2246             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2247             $params[] = $now;
2248             $params[] = $now;
2249         }
2250     }
2252     return $DB->get_records_sql("SELECT DISTINCT d.*
2253                               FROM {forum} f
2254                                    JOIN {forum_discussions} d ON d.forum = f.id
2255                                    JOIN {forum_posts} p       ON p.discussion = d.id
2256                              WHERE f.id = ?
2257                                    AND p.userid = ?
2258                                    $timedsql", $params);
2261 /**
2262  * Get all the posts for a user in a forum suitable for forum_print_post
2263  *
2264  * @global object
2265  * @global object
2266  * @param int $forumid
2267  * @param int $userid
2268  * @return array of counts or false
2269  */
2270 function forum_count_user_posts($forumid, $userid) {
2271     global $CFG, $DB;
2273     $timedsql = "";
2274     $params = array($forumid, $userid);
2275     if (!empty($CFG->forum_enabletimedposts)) {
2276         $cm = get_coursemodule_from_instance('forum', $forumid);
2277         if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2278             $now = time();
2279             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2280             $params[] = $now;
2281             $params[] = $now;
2282         }
2283     }
2285     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2286                              FROM {forum} f
2287                                   JOIN {forum_discussions} d ON d.forum = f.id
2288                                   JOIN {forum_posts} p       ON p.discussion = d.id
2289                                   JOIN {user} u              ON u.id = p.userid
2290                             WHERE f.id = ?
2291                                   AND p.userid = ?
2292                                   $timedsql", $params);
2295 /**
2296  * Given a log entry, return the forum post details for it.
2297  *
2298  * @global object
2299  * @global object
2300  * @param object $log
2301  * @return array|null
2302  */
2303 function forum_get_post_from_log($log) {
2304     global $CFG, $DB;
2306     if ($log->action == "add post") {
2308         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2309                                            u.firstname, u.lastname, u.email, u.picture
2310                                  FROM {forum_discussions} d,
2311                                       {forum_posts} p,
2312                                       {forum} f,
2313                                       {user} u
2314                                 WHERE p.id = ?
2315                                   AND d.id = p.discussion
2316                                   AND p.userid = u.id
2317                                   AND u.deleted <> '1'
2318                                   AND f.id = d.forum", array($log->info));
2321     } else if ($log->action == "add discussion") {
2323         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2324                                            u.firstname, u.lastname, u.email, u.picture
2325                                  FROM {forum_discussions} d,
2326                                       {forum_posts} p,
2327                                       {forum} f,
2328                                       {user} u
2329                                 WHERE d.id = ?
2330                                   AND d.firstpost = p.id
2331                                   AND p.userid = u.id
2332                                   AND u.deleted <> '1'
2333                                   AND f.id = d.forum", array($log->info));
2334     }
2335     return NULL;
2338 /**
2339  * Given a discussion id, return the first post from the discussion
2340  *
2341  * @global object
2342  * @global object
2343  * @param int $dicsussionid
2344  * @return array
2345  */
2346 function forum_get_firstpost_from_discussion($discussionid) {
2347     global $CFG, $DB;
2349     return $DB->get_record_sql("SELECT p.*
2350                              FROM {forum_discussions} d,
2351                                   {forum_posts} p
2352                             WHERE d.id = ?
2353                               AND d.firstpost = p.id ", array($discussionid));
2356 /**
2357  * Returns an array of counts of replies to each discussion
2358  *
2359  * @global object
2360  * @global object
2361  * @param int $forumid
2362  * @param string $forumsort
2363  * @param int $limit
2364  * @param int $page
2365  * @param int $perpage
2366  * @return array
2367  */
2368 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2369     global $CFG, $DB;
2371     if ($limit > 0) {
2372         $limitfrom = 0;
2373         $limitnum  = $limit;
2374     } else if ($page != -1) {
2375         $limitfrom = $page*$perpage;
2376         $limitnum  = $perpage;
2377     } else {
2378         $limitfrom = 0;
2379         $limitnum  = 0;
2380     }
2382     if ($forumsort == "") {
2383         $orderby = "";
2384         $groupby = "";
2386     } else {
2387         $orderby = "ORDER BY $forumsort";
2388         $groupby = ", ".strtolower($forumsort);
2389         $groupby = str_replace('desc', '', $groupby);
2390         $groupby = str_replace('asc', '', $groupby);
2391     }
2393     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2394         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2395                   FROM {forum_posts} p
2396                        JOIN {forum_discussions} d ON p.discussion = d.id
2397                  WHERE p.parent > 0 AND d.forum = ?
2398               GROUP BY p.discussion";
2399         return $DB->get_records_sql($sql, array($forumid));
2401     } else {
2402         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2403                   FROM {forum_posts} p
2404                        JOIN {forum_discussions} d ON p.discussion = d.id
2405                  WHERE d.forum = ?
2406               GROUP BY p.discussion $groupby
2407               $orderby";
2408         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2409     }
2412 /**
2413  * @global object
2414  * @global object
2415  * @global object
2416  * @staticvar array $cache
2417  * @param object $forum
2418  * @param object $cm
2419  * @param object $course
2420  * @return mixed
2421  */
2422 function forum_count_discussions($forum, $cm, $course) {
2423     global $CFG, $DB, $USER;
2425     static $cache = array();
2427     $now = round(time(), -2); // db cache friendliness
2429     $params = array($course->id);
2431     if (!isset($cache[$course->id])) {
2432         if (!empty($CFG->forum_enabletimedposts)) {
2433             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2434             $params[] = $now;
2435             $params[] = $now;
2436         } else {
2437             $timedsql = "";
2438         }
2440         $sql = "SELECT f.id, COUNT(d.id) as dcount
2441                   FROM {forum} f
2442                        JOIN {forum_discussions} d ON d.forum = f.id
2443                  WHERE f.course = ?
2444                        $timedsql
2445               GROUP BY f.id";
2447         if ($counts = $DB->get_records_sql($sql, $params)) {
2448             foreach ($counts as $count) {
2449                 $counts[$count->id] = $count->dcount;
2450             }
2451             $cache[$course->id] = $counts;
2452         } else {
2453             $cache[$course->id] = array();
2454         }
2455     }
2457     if (empty($cache[$course->id][$forum->id])) {
2458         return 0;
2459     }
2461     $groupmode = groups_get_activity_groupmode($cm, $course);
2463     if ($groupmode != SEPARATEGROUPS) {
2464         return $cache[$course->id][$forum->id];
2465     }
2467     if (has_capability('moodle/site:accessallgroups', context_module::instance($cm->id))) {
2468         return $cache[$course->id][$forum->id];
2469     }
2471     require_once($CFG->dirroot.'/course/lib.php');
2473     $modinfo = get_fast_modinfo($course);
2474     if (is_null($modinfo->groups)) {
2475         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2476     }
2478     if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2479         $mygroups = $modinfo->groups[$cm->groupingid];
2480     } else {
2481         $mygroups = false; // Will be set below
2482     }
2484     // add all groups posts
2485     if (empty($mygroups)) {
2486         $mygroups = array(-1=>-1);
2487     } else {
2488         $mygroups[-1] = -1;
2489     }
2491     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2492     $params[] = $forum->id;
2494     if (!empty($CFG->forum_enabletimedposts)) {
2495         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2496         $params[] = $now;
2497         $params[] = $now;
2498     } else {
2499         $timedsql = "";
2500     }
2502     $sql = "SELECT COUNT(d.id)
2503               FROM {forum_discussions} d
2504              WHERE d.groupid $mygroups_sql AND d.forum = ?
2505                    $timedsql";
2507     return $DB->get_field_sql($sql, $params);
2510 /**
2511  * How many posts by other users are unrated by a given user in the given discussion?
2512  *
2513  * TODO: Is this function still used anywhere?
2514  *
2515  * @param int $discussionid
2516  * @param int $userid
2517  * @return mixed
2518  */
2519 function forum_count_unrated_posts($discussionid, $userid) {
2520     global $CFG, $DB;
2522     $sql = "SELECT COUNT(*) as num
2523               FROM {forum_posts}
2524              WHERE parent > 0
2525                AND discussion = :discussionid
2526                AND userid <> :userid";
2527     $params = array('discussionid' => $discussionid, 'userid' => $userid);
2528     $posts = $DB->get_record_sql($sql, $params);
2529     if ($posts) {
2530         $sql = "SELECT count(*) as num
2531                   FROM {forum_posts} p,
2532                        {rating} r
2533                  WHERE p.discussion = :discussionid AND
2534                        p.id = r.itemid AND
2535                        r.userid = userid AND
2536                        r.component = 'mod_forum' AND
2537                        r.ratingarea = 'post'";
2538         $rated = $DB->get_record_sql($sql, $params);
2539         if ($rated) {
2540             if ($posts->num > $rated->num) {
2541                 return $posts->num - $rated->num;
2542             } else {
2543                 return 0;    // Just in case there was a counting error
2544             }
2545         } else {
2546             return $posts->num;
2547         }
2548     } else {
2549         return 0;
2550     }
2553 /**
2554  * Get all discussions in a forum
2555  *
2556  * @global object
2557  * @global object
2558  * @global object
2559  * @uses CONTEXT_MODULE
2560  * @uses VISIBLEGROUPS
2561  * @param object $cm
2562  * @param string $forumsort
2563  * @param bool $fullpost
2564  * @param int $unused
2565  * @param int $limit
2566  * @param bool $userlastmodified
2567  * @param int $page
2568  * @param int $perpage
2569  * @return array
2570  */
2571 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2572     global $CFG, $DB, $USER;
2574     $timelimit = '';
2576     $now = round(time(), -2);
2577     $params = array($cm->instance);
2579     $modcontext = context_module::instance($cm->id);
2581     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2582         return array();
2583     }
2585     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2587         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2588             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2589             $params[] = $now;
2590             $params[] = $now;
2591             if (isloggedin()) {
2592                 $timelimit .= " OR d.userid = ?";
2593                 $params[] = $USER->id;
2594             }
2595             $timelimit .= ")";
2596         }
2597     }
2599     if ($limit > 0) {
2600         $limitfrom = 0;
2601         $limitnum  = $limit;
2602     } else if ($page != -1) {
2603         $limitfrom = $page*$perpage;
2604         $limitnum  = $perpage;
2605     } else {
2606         $limitfrom = 0;
2607         $limitnum  = 0;
2608     }
2610     $groupmode    = groups_get_activity_groupmode($cm);
2611     $currentgroup = groups_get_activity_group($cm);
2613     if ($groupmode) {
2614         if (empty($modcontext)) {
2615             $modcontext = context_module::instance($cm->id);
2616         }
2618         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2619             if ($currentgroup) {
2620                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2621                 $params[] = $currentgroup;
2622             } else {
2623                 $groupselect = "";
2624             }
2626         } else {
2627             //seprate groups without access all
2628             if ($currentgroup) {
2629                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2630                 $params[] = $currentgroup;
2631             } else {
2632                 $groupselect = "AND d.groupid = -1";
2633             }
2634         }
2635     } else {
2636         $groupselect = "";
2637     }
2640     if (empty($forumsort)) {
2641         $forumsort = "d.timemodified DESC";
2642     }
2643     if (empty($fullpost)) {
2644         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2645     } else {
2646         $postdata = "p.*";
2647     }
2649     if (empty($userlastmodified)) {  // We don't need to know this
2650         $umfields = "";
2651         $umtable  = "";
2652     } else {
2653         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2654         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2655     }
2657     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2658                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2659               FROM {forum_discussions} d
2660                    JOIN {forum_posts} p ON p.discussion = d.id
2661                    JOIN {user} u ON p.userid = u.id
2662                    $umtable
2663              WHERE d.forum = ? AND p.parent = 0
2664                    $timelimit $groupselect
2665           ORDER BY $forumsort";
2666     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2669 /**
2670  *
2671  * @global object
2672  * @global object
2673  * @global object
2674  * @uses CONTEXT_MODULE
2675  * @uses VISIBLEGROUPS
2676  * @param object $cm
2677  * @return array
2678  */
2679 function forum_get_discussions_unread($cm) {
2680     global $CFG, $DB, $USER;
2682     $now = round(time(), -2);
2683     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2685     $params = array();
2686     $groupmode    = groups_get_activity_groupmode($cm);
2687     $currentgroup = groups_get_activity_group($cm);
2689     if ($groupmode) {
2690         $modcontext = context_module::instance($cm->id);
2692         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2693             if ($currentgroup) {
2694                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2695                 $params['currentgroup'] = $currentgroup;
2696             } else {
2697                 $groupselect = "";
2698             }
2700         } else {
2701             //separate groups without access all
2702             if ($currentgroup) {
2703                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2704                 $params['currentgroup'] = $currentgroup;
2705             } else {
2706                 $groupselect = "AND d.groupid = -1";
2707             }
2708         }
2709     } else {
2710         $groupselect = "";
2711     }
2713     if (!empty($CFG->forum_enabletimedposts)) {
2714         $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2715         $params['now1'] = $now;
2716         $params['now2'] = $now;
2717     } else {
2718         $timedsql = "";
2719     }
2721     $sql = "SELECT d.id, COUNT(p.id) AS unread
2722               FROM {forum_discussions} d
2723                    JOIN {forum_posts} p     ON p.discussion = d.id
2724                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2725              WHERE d.forum = {$cm->instance}
2726                    AND p.modified >= :cutoffdate AND r.id is NULL
2727                    $groupselect
2728                    $timedsql
2729           GROUP BY d.id";
2730     $params['cutoffdate'] = $cutoffdate;
2732     if ($unreads = $DB->get_records_sql($sql, $params)) {
2733         foreach ($unreads as $unread) {
2734             $unreads[$unread->id] = $unread->unread;
2735         }
2736         return $unreads;
2737     } else {
2738         return array();
2739     }
2742 /**
2743  * @global object
2744  * @global object
2745  * @global object
2746  * @uses CONEXT_MODULE
2747  * @uses VISIBLEGROUPS
2748  * @param object $cm
2749  * @return array
2750  */
2751 function forum_get_discussions_count($cm) {
2752     global $CFG, $DB, $USER;
2754     $now = round(time(), -2);
2755     $params = array($cm->instance);
2756     $groupmode    = groups_get_activity_groupmode($cm);
2757     $currentgroup = groups_get_activity_group($cm);
2759     if ($groupmode) {
2760         $modcontext = context_module::instance($cm->id);
2762         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2763             if ($currentgroup) {
2764                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2765                 $params[] = $currentgroup;
2766             } else {
2767                 $groupselect = "";
2768             }
2770         } else {
2771             //seprate groups without access all
2772             if ($currentgroup) {
2773                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2774                 $params[] = $currentgroup;
2775             } else {
2776                 $groupselect = "AND d.groupid = -1";
2777             }
2778         }
2779     } else {
2780         $groupselect = "";
2781     }
2783     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2785     $timelimit = "";
2787     if (!empty($CFG->forum_enabletimedposts)) {
2789         $modcontext = context_module::instance($cm->id);
2791         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2792             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2793             $params[] = $now;
2794             $params[] = $now;
2795             if (isloggedin()) {
2796                 $timelimit .= " OR d.userid = ?";
2797                 $params[] = $USER->id;
2798             }
2799             $timelimit .= ")";
2800         }
2801     }
2803     $sql = "SELECT COUNT(d.id)
2804               FROM {forum_discussions} d
2805                    JOIN {forum_posts} p ON p.discussion = d.id
2806              WHERE d.forum = ? AND p.parent = 0
2807                    $groupselect $timelimit";
2809     return $DB->get_field_sql($sql, $params);
2813 /**
2814  * Get all discussions started by a particular user in a course (or group)
2815  * This function no longer used ...
2816  *
2817  * @todo Remove this function if no longer used
2818  * @global object
2819  * @global object
2820  * @param int $courseid
2821  * @param int $userid
2822  * @param int $groupid
2823  * @return array
2824  */
2825 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2826     global $CFG, $DB;
2827     $params = array($courseid, $userid);
2828     if ($groupid) {
2829         $groupselect = " AND d.groupid = ? ";
2830         $params[] = $groupid;
2831     } else  {
2832         $groupselect = "";
2833     }
2835     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2836                                    f.type as forumtype, f.name as forumname, f.id as forumid
2837                               FROM {forum_discussions} d,
2838                                    {forum_posts} p,
2839                                    {user} u,
2840                                    {forum} f
2841                              WHERE d.course = ?
2842                                AND p.discussion = d.id
2843                                AND p.parent = 0
2844                                AND p.userid = u.id
2845                                AND u.id = ?
2846                                AND d.forum = f.id $groupselect
2847                           ORDER BY p.created DESC", $params);
2850 /**
2851  * Get the list of potential subscribers to a forum.
2852  *
2853  * @param object $forumcontext the forum context.
2854  * @param integer $groupid the id of a group, or 0 for all groups.
2855  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2856  * @param string $sort sort order. As for get_users_by_capability.
2857  * @return array list of users.
2858  */
2859 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort = '') {
2860     global $DB;
2862     // only active enrolled users or everybody on the frontpage
2863     list($esql, $params) = get_enrolled_sql($forumcontext, 'mod/forum:allowforcesubscribe', $groupid, true);
2864     if (!$sort) {
2865         list($sort, $sortparams) = users_order_by_sql('u');
2866         $params = array_merge($params, $sortparams);
2867     }
2869     $sql = "SELECT $fields
2870               FROM {user} u
2871               JOIN ($esql) je ON je.id = u.id
2872           ORDER BY $sort";
2874     return $DB->get_records_sql($sql, $params);
2877 /**
2878  * Returns list of user objects that are subscribed to this forum
2879  *
2880  * @global object
2881  * @global object
2882  * @param object $course the course
2883  * @param forum $forum the forum
2884  * @param integer $groupid group id, or 0 for all.
2885  * @param object $context the forum context, to save re-fetching it where possible.
2886  * @param string $fields requested user fields (with "u." table prefix)
2887  * @return array list of users.
2888  */
2889 function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
2890     global $CFG, $DB;
2892     if (empty($fields)) {
2893         $fields ="u.id,
2894                   u.username,
2895                   u.firstname,
2896                   u.lastname,
2897                   u.maildisplay,
2898                   u.mailformat,
2899                   u.maildigest,
2900                   u.imagealt,
2901                   u.email,
2902                   u.emailstop,
2903                   u.city,
2904                   u.country,
2905                   u.lastaccess,
2906                   u.lastlogin,
2907                   u.picture,
2908                   u.timezone,
2909                   u.theme,
2910                   u.lang,
2911                   u.trackforums,
2912                   u.mnethostid";
2913     }
2915     if (empty($context)) {
2916         $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2917         $context = context_module::instance($cm->id);
2918     }
2920     if (forum_is_forcesubscribed($forum)) {
2921         $results = forum_get_potential_subscribers($context, $groupid, $fields, "u.email ASC");
2923     } else {
2924         // only active enrolled users or everybody on the frontpage
2925         list($esql, $params) = get_enrolled_sql($context, '', $groupid, true);
2926         $params['forumid'] = $forum->id;
2927         $results = $DB->get_records_sql("SELECT $fields
2928                                            FROM {user} u
2929                                            JOIN ($esql) je ON je.id = u.id
2930                                            JOIN {forum_subscriptions} s ON s.userid = u.id
2931                                           WHERE s.forum = :forumid
2932                                        ORDER BY u.email ASC", $params);
2933     }
2935     // Guest user should never be subscribed to a forum.
2936     unset($results[$CFG->siteguest]);
2938     return $results;
2943 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2946 /**
2947  * @global object
2948  * @global object
2949  * @param int $courseid
2950  * @param string $type
2951  */
2952 function forum_get_course_forum($courseid, $type) {
2953 // How to set up special 1-per-course forums
2954     global $CFG, $DB, $OUTPUT, $USER;
2956     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2957         // There should always only be ONE, but with the right combination of
2958         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2959         foreach ($forums as $forum) {
2960             return $forum;   // ie the first one
2961         }
2962     }
2964     // Doesn't exist, so create one now.
2965     $forum = new stdClass();
2966     $forum->course = $courseid;
2967     $forum->type = "$type";
2968     if (!empty($USER->htmleditor)) {
2969         $forum->introformat = $USER->htmleditor;
2970     }
2971     switch ($forum->type) {
2972         case "news":
2973             $forum->name  = get_string("namenews", "forum");
2974             $forum->intro = get_string("intronews", "forum");
2975             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2976             $forum->assessed = 0;
2977             if ($courseid == SITEID) {
2978                 $forum->name  = get_string("sitenews");
2979                 $forum->forcesubscribe = 0;
2980             }
2981             break;
2982         case "social":
2983             $forum->name  = get_string("namesocial", "forum");
2984             $forum->intro = get_string("introsocial", "forum");
2985             $forum->assessed = 0;
2986             $forum->forcesubscribe = 0;
2987             break;
2988         case "blog":
2989             $forum->name = get_string('blogforum', 'forum');
2990             $forum->intro = get_string('introblog', 'forum');
2991             $forum->assessed = 0;
2992             $forum->forcesubscribe = 0;
2993             break;
2994         default:
2995             echo $OUTPUT->notification("That forum type doesn't exist!");
2996             return false;
2997             break;
2998     }
3000     $forum->timemodified = time();
3001     $forum->id = $DB->insert_record("forum", $forum);
3003     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
3004         echo $OUTPUT->notification("Could not find forum module!!");
3005         return false;
3006     }
3007     $mod = new stdClass();
3008     $mod->course = $courseid;
3009     $mod->module = $module->id;
3010     $mod->instance = $forum->id;
3011     $mod->section = 0;
3012     include_once("$CFG->dirroot/course/lib.php");
3013     if (! $mod->coursemodule = add_course_module($mod) ) {
3014         echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
3015         return false;
3016     }
3017     $sectionid = course_add_cm_to_section($courseid, $mod->coursemodule, 0);
3018     return $DB->get_record("forum", array("id" => "$forum->id"));
3022 /**
3023  * Given the data about a posting, builds up the HTML to display it and
3024  * returns the HTML in a string.  This is designed for sending via HTML email.
3025  *
3026  * @global object
3027  * @param object $course
3028  * @param object $cm
3029  * @param object $forum
3030  * @param object $discussion
3031  * @param object $post
3032  * @param object $userform
3033  * @param object $userto
3034  * @param bool $ownpost
3035  * @param bool $reply
3036  * @param bool $link
3037  * @param bool $rate
3038  * @param string $footer
3039  * @return string
3040  */
3041 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
3042                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
3044     global $CFG, $OUTPUT;
3046     $modcontext = context_module::instance($cm->id);
3048     if (!isset($userto->viewfullnames[$forum->id])) {
3049         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3050     } else {
3051         $viewfullnames = $userto->viewfullnames[$forum->id];
3052     }
3054     // add absolute file links
3055     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3057     // format the post body
3058     $options = new stdClass();
3059     $options->para = true;
3060     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3062     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3064     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3065     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3066     $output .= '</td>';
3068     if ($post->parent) {
3069         $output .= '<td class="topic">';
3070     } else {
3071         $output .= '<td class="topic starter">';
3072     }
3073     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3075     $fullname = fullname($userfrom, $viewfullnames);
3076     $by = new stdClass();
3077     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3078     $by->date = userdate($post->modified, '', $userto->timezone);
3079     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3081     $output .= '</td></tr>';
3083     $output .= '<tr><td class="left side" valign="top">';
3085     if (isset($userfrom->groups)) {
3086         $groups = $userfrom->groups[$forum->id];
3087     } else {
3088         $groups = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3089     }
3091     if ($groups) {
3092         $output .= print_group_picture($groups, $course->id, false, true, true);
3093     } else {
3094         $output .= '&nbsp;';
3095     }
3097     $output .= '</td><td class="content">';
3099     $attachments = forum_print_attachments($post, $cm, 'html');
3100     if ($attachments !== '') {
3101         $output .= '<div class="attachments">';
3102         $output .= $attachments;
3103         $output .= '</div>';
3104     }
3106     $output .= $formattedtext;
3108 // Commands
3109     $commands = array();
3111     if ($post->parent) {
3112         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3113                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3114     }
3116     if ($reply) {
3117         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3118                       get_string('reply', 'forum').'</a>';
3119     }
3121     $output .= '<div class="commands">';
3122     $output .= implode(' | ', $commands);
3123     $output .= '</div>';
3125 // Context link to post if required
3126     if ($link) {
3127         $output .= '<div class="link">';
3128         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3129                      get_string('postincontext', 'forum').'</a>';
3130         $output .= '</div>';
3131     }
3133     if ($footer) {
3134         $output .= '<div class="footer">'.$footer.'</div>';
3135     }
3136     $output .= '</td></tr></table>'."\n\n";
3138     return $output;
3141 /**
3142  * Print a forum post
3143  *
3144  * @global object
3145  * @global object
3146  * @uses FORUM_MODE_THREADED
3147  * @uses PORTFOLIO_FORMAT_PLAINHTML
3148  * @uses PORTFOLIO_FORMAT_FILE
3149  * @uses PORTFOLIO_FORMAT_RICHHTML
3150  * @uses PORTFOLIO_ADD_TEXT_LINK
3151  * @uses CONTEXT_MODULE
3152  * @param object $post The post to print.
3153  * @param object $discussion
3154  * @param object $forum
3155  * @param object $cm
3156  * @param object $course
3157  * @param boolean $ownpost Whether this post belongs to the current user.
3158  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3159  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3160  * @param string $footer Extra stuff to print after the message.
3161  * @param string $highlight Space-separated list of terms to highlight.
3162  * @param int $post_read true, false or -99. If we already know whether this user
3163  *          has read this post, pass that in, otherwise, pass in -99, and this
3164  *          function will work it out.
3165  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3166  *          the current user can't see this post, if this argument is true
3167  *          (the default) then print a dummy 'you can't see this post' post.
3168  *          If false, don't output anything at all.
3169  * @param bool|null $istracked
3170  * @return void
3171  */
3172 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3173                           $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3174     global $USER, $CFG, $OUTPUT;
3176     require_once($CFG->libdir . '/filelib.php');
3178     // String cache
3179     static $str;
3181     $modcontext = context_module::instance($cm->id);
3183     $post->course = $course->id;
3184     $post->forum  = $forum->id;
3185     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3186     if (!empty($CFG->enableplagiarism)) {
3187         require_once($CFG->libdir.'/plagiarismlib.php');
3188         $post->message .= plagiarism_get_links(array('userid' => $post->userid,
3189             'content' => $post->message,
3190             'cmid' => $cm->id,
3191             'course' => $post->course,
3192             'forum' => $post->forum));
3193     }
3195     // caching
3196     if (!isset($cm->cache)) {
3197         $cm->cache = new stdClass;
3198     }
3200     if (!isset($cm->cache->caps)) {
3201         $cm->cache->caps = array();
3202         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3203         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3204         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3205         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3206         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3207         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3208         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3209         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3210         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3211     }
3213     if (!isset($cm->uservisible)) {
3214         $cm->uservisible = coursemodule_visible_for_user($cm);
3215     }
3217     if ($istracked && is_null($postisread)) {
3218         $postisread = forum_tp_is_post_read($USER->id, $post);
3219     }
3221     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3222         $output = '';
3223         if (!$dummyifcantsee) {
3224             if ($return) {
3225                 return $output;
3226             }
3227             echo $output;
3228             return;
3229         }
3230         $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3231         $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'));
3232         $output .= html_writer::start_tag('div', array('class'=>'row header'));
3233         $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3234         if ($post->parent) {
3235             $output .= html_writer::start_tag('div', array('class'=>'topic'));
3236         } else {
3237             $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3238         }
3239         $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class'=>'subject')); // Subject
3240         $output .= html_writer::tag('div', get_string('forumauthorhidden','forum'), array('class'=>'author')); // author
3241         $output .= html_writer::end_tag('div');
3242         $output .= html_writer::end_tag('div'); // row
3243         $output .= html_writer::start_tag('div', array('class'=>'row'));
3244         $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3245         $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3246         $output .= html_writer::end_tag('div'); // row
3247         $output .= html_writer::end_tag('div'); // forumpost
3249         if ($return) {
3250             return $output;
3251         }
3252         echo $output;
3253         return;
3254     }
3256     if (empty($str)) {
3257         $str = new stdClass;
3258         $str->edit         = get_string('edit', 'forum');
3259         $str->delete       = get_string('delete', 'forum');
3260         $str->reply        = get_string('reply', 'forum');
3261         $str->parent       = get_string('parent', 'forum');
3262         $str->pruneheading = get_string('pruneheading', 'forum');
3263         $str->prune        = get_string('prune', 'forum');
3264         $str->displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3265         $str->markread     = get_string('markread', 'forum');
3266         $str->markunread   = get_string('markunread', 'forum');
3267     }
3269     $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3271     // Build an object that represents the posting user
3272     $postuser = new stdClass;
3273     $postuser->id        = $post->userid;
3274     $postuser->firstname = $post->firstname;
3275     $postuser->lastname  = $post->lastname;
3276     $postuser->imagealt  = $post->imagealt;
3277     $postuser->picture   = $post->picture;
3278     $postuser->email     = $post->email;
3279     // Some handy things for later on
3280     $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3281     $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3283     // Prepare the groups the posting user belongs to
3284     if (isset($cm->cache->usersgroups)) {
3285         $groups = array();
3286         if (isset($cm->cache->usersgroups[$post->userid])) {
3287             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3288                 $groups[$gid] = $cm->cache->groups[$gid];
3289             }
3290         }
3291     } else {
3292         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3293     }
3295     // Prepare the attachements for the post, files then images
3296     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3298     // Determine if we need to shorten this post
3299     $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3302     // Prepare an array of commands
3303     $commands = array();
3305     // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3306     // Don't display the mark read / unread controls in this case.
3307     if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3308         $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3309         $text = $str->markunread;
3310         if (!$postisread) {
3311             $url->param('mark', 'read');
3312             $text = $str->markread;
3313         }
3314         if ($str->displaymode == FORUM_MODE_THREADED) {
3315             $url->param('parent', $post->parent);
3316         } else {
3317             $url->set_anchor('p'.$post->id);
3318         }
3319         $commands[] = array('url'=>$url, 'text'=>$text);
3320     }
3322     // Zoom in to the parent specifically
3323     if ($post->parent) {
3324         $url = new moodle_url($discussionlink);
3325         if ($str->displaymode == FORUM_MODE_THREADED) {
3326             $url->param('parent', $post->parent);
3327         } else {
3328             $url->set_anchor('p'.$post->parent);
3329         }
3330         $commands[] = array('url'=>$url, 'text'=>$str->parent);
3331     }
3333     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3334     $age = time() - $post->created;
3335     if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3336         $age = 0;
3337     }
3339     if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
3340         if (has_capability('moodle/course:manageactivities', $modcontext)) {
3341             // The first post in single simple is the forum description.
3342             $commands[] = array('url'=>new moodle_url('/course/modedit.php', array('update'=>$cm->id, 'sesskey'=>sesskey(), 'return'=>1)), 'text'=>$str->edit);
3343         }
3344     } else if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3345         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3346     }
3348     if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3349         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3350     }
3352     if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
3353         // Do not allow deleting of first post in single simple type.
3354     } else if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3355         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3356     }
3358     if ($reply) {
3359         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php#mformforum', array('reply'=>$post->id)), 'text'=>$str->reply);
3360     }
3362     if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
3363         $p = array('postid' => $post->id);
3364         require_once($CFG->libdir.'/portfoliolib.php');
3365         $button = new portfolio_add_button();
3366         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), 'mod_forum');
3367         if (empty($attachments)) {
3368             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3369         } else {
3370             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3371         }
3373         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3374         if (!empty($porfoliohtml)) {
3375             $commands[] = $porfoliohtml;
3376         }
3377     }
3378     // Finished building commands
3381     // Begin output
3383     $output  = '';
3385     if ($istracked) {
3386         if ($postisread) {
3387             $forumpostclass = ' read';
3388         } else {
3389             $forumpostclass = ' unread';
3390             $output .= html_writer::tag('a', '', array('name'=>'unread'));
3391         }
3392     } else {
3393         // ignore trackign status if not tracked or tracked param missing
3394         $forumpostclass = '';
3395     }
3397     $topicclass = '';
3398     if (empty($post->parent)) {
3399         $topicclass = ' firstpost starter';
3400     }
3402     $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3403     $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass));
3404     $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3405     $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3406     $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3407     $output .= html_writer::end_tag('div');
3410     $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3412     $postsubject = $post->subject;
3413     if (empty($post->subjectnoformat)) {
3414         $postsubject = format_string($postsubject);
3415     }
3416     $output .= html_writer::tag('div', $postsubject, array('class'=>'subject'));
3418     $by = new stdClass();
3419     $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3420     $by->date = userdate($post->modified);
3421     $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author'));
3423     $output .= html_writer::end_tag('div'); //topic
3424     $output .= html_writer::end_tag('div'); //row
3426     $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3427     $output .= html_writer::start_tag('div', array('class'=>'left'));
3429     $groupoutput = '';
3430     if ($groups) {
3431         $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3432     }
3433     if (empty($groupoutput)) {
3434         $groupoutput = '&nbsp;';
3435     }
3436     $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3438     $output .= html_writer::end_tag('div'); //left side
3439     $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3440     $output .= html_writer::start_tag('div', array('class'=>'content'));
3441     if (!empty($attachments)) {
3442         $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3443     }
3445     $options = new stdClass;
3446     $options->para    = false;
3447     $options->trusted = $post->messagetrust;
3448     $options->context = $modcontext;
3449     if ($shortenpost) {
3450         // Prepare shortened version
3451         $postclass    = 'shortenedpost';
3452         $postcontent  = format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3453         $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
3454         $postcontent .= html_writer::tag('span', '('.get_string('numwords', 'moodle', count_words(strip_tags($post->message))).')...', array('class'=>'post-word-count'));
3455     } else {
3456         // Prepare whole post
3457         $postclass    = 'fullpost';
3458         $postcontent  = format_text($post->message, $post->messageformat, $options, $course->id);
3459         if (!empty($highlight)) {
3460             $postcontent = highlight($highlight, $postcontent);
3461         }
3462         $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
3463     }
3464     // Output the post content
3465     $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
3466     $output .= html_writer::end_tag('div'); // Content
3467     $output .= html_writer::end_tag('div'); // Content mask
3468     $output .= html_writer::end_tag('div'); // Row
3470     $output .= html_writer::start_tag('div', array('class'=>'row side'));
3471     $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
3472     $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
3474     // Output ratings
3475     if (!empty($post->rating)) {
3476         $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
3477     }
3479     // Output the commands
3480     $commandhtml = array();
3481     foreach ($commands as $command) {
3482         if (is_array($command)) {
3483             $commandhtml[] = html_writer::link($command['url'], $command['text']);
3484         } else {
3485             $commandhtml[] = $command;
3486         }
3487     }
3488     $output .= html_writer::tag('div', implode(' | ', $commandhtml), array('class'=>'commands'));
3490     // Output link to post if required
3491     if ($link) {
3492         if ($post->replies == 1) {
3493             $replystring = get_string('repliesone', 'forum', $post->replies);
3494         } else {
3495             $replystring = get_string('repliesmany', 'forum', $post->replies);
3496         }
3498         $output .= html_writer::start_tag('div', array('class'=>'link'));
3499         $output .= html_writer::link($discussionlink, get_string('discussthistopic', 'forum'));
3500         $output .= '&nbsp;('.$replystring.')';
3501         $output .= html_writer::end_tag('div'); // link
3502     }
3504     // Output footer if required
3505     if ($footer) {
3506         $output .= html_writer::tag('div', $footer, array('class'=>'footer'));
3507     }
3509     // Close remaining open divs
3510     $output .= html_writer::end_tag('div'); // content
3511     $output .= html_writer::end_tag('div'); // row
3512     $output .= html_writer::end_tag('div'); // forumpost
3514     // Mark the forum post as read if required
3515     if ($istracked && !$CFG->forum_usermarksread && !$postisread) {
3516         forum_tp_mark_post_read($USER->id, $post, $forum->id);
3517     }
3519     if ($return) {
3520         return $output;
3521     }
3522     echo $output;
3523     return;
3526 /**
3527  * Return rating related permissions
3528  *
3529  * @param string $options the context id
3530  * @return array an associative array of the user's rating permissions
3531  */
3532 function forum_rating_permissions($contextid, $component, $ratingarea) {
3533     $context = context::instance_by_id($contextid, MUST_EXIST);
3534     if ($component != 'mod_forum' || $ratingarea != 'post') {
3535         // We don't know about this component/ratingarea so just return null to get the
3536         // default restrictive permissions.
3537         return null;
3538     }
3539     return array(
3540         'view'    => has_capability('mod/forum:viewrating', $context),
3541         'viewany' => has_capability('mod/forum:viewanyrating', $context),
3542         'viewall' => has_capability('mod/forum:viewallratings', $context),
3543         'rate'    => has_capability('mod/forum:rate', $context)
3544     );
3547 /**
3548  * Validates a submitted rating
3549  * @param array $params submitted data
3550  *            context => object the context in which the rated items exists [required]
3551  *            component => The component for this module - should always be mod_forum [required]
3552  *            ratingarea => object the context in which the rated items exists [required]
3553  *            itemid => int the ID of the object being rated [required]
3554  *            scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
3555  *            rating => int the submitted rating [required]
3556  *            rateduserid => int the id of the user whose items have been rated. NOT the user who submitted the ratings. 0 to update all. [required]
3557  *            aggregation => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [required]
3558  * @return boolean true if the rating is valid. Will throw rating_exception if not
3559  */
3560 function forum_rating_validate($params) {
3561     global $DB, $USER;
3563     // Check the component is mod_forum
3564     if ($params['component'] != 'mod_forum') {
3565         throw new rating_exception('invalidcomponent');
3566     }
3568     // Check the ratingarea is post (the only rating area in forum)
3569     if ($params['ratingarea'] != 'post') {
3570         throw new rating_exception('invalidratingarea');
3571     }
3573     // Check the rateduserid is not the current user .. you can't rate your own posts
3574     if ($params['rateduserid'] == $USER->id) {
3575         throw new rating_exception('nopermissiontorate');
3576     }
3578     // Fetch all the related records ... we need to do this anyway to call forum_user_can_see_post
3579     $post = $DB->get_record('forum_posts', array('id' => $params['itemid'], 'userid' => $params['rateduserid']), '*', MUST_EXIST);
3580     $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion), '*', MUST_EXIST);
3581     $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
3582     $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
3583     $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id , false, MUST_EXIST);
3584     $context = context_module::instance($cm->id);
3586     // Make sure the context provided is the context of the forum
3587     if ($context->id != $params['context']->id) {
3588         throw new rating_exception('invalidcontext');
3589     }
3591     if ($forum->scale != $params['scaleid']) {
3592         //the scale being submitted doesnt match the one in the database
3593         throw new rating_exception('invalidscaleid');
3594     }
3596     // check the item we're rating was created in the assessable time window
3597     if (!empty($forum->assesstimestart) && !empty($forum->assesstimefinish)) {
3598         if ($post->created < $forum->assesstimestart || $post->created > $forum->assesstimefinish) {
3599             throw new rating_exception('notavailable');
3600         }
3601     }
3603     //check that the submitted rating is valid for the scale
3605     // lower limit
3606     if ($params['rating'] < 0  && $params['rating'] != RATING_UNSET_RATING) {
3607         throw new rating_exception('invalidnum');
3608     }
3610     // upper limit
3611     if ($forum->scale < 0) {
3612         //its a custom scale
3613         $scalerecord = $DB->get_record('scale', array('id' => -$forum->scale));
3614         if ($scalerecord) {
3615             $scalearray = explode(',', $scalerecord->scale);
3616             if ($params['rating'] > count($scalearray)) {
3617                 throw new rating_exception('invalidnum');
3618             }
3619         } else {
3620             throw new rating_exception('invalidscaleid');
3621         }
3622     } else if ($params['rating'] > $forum->scale) {
3623         //if its numeric and submitted rating is above maximum
3624         throw new rating_exception('invalidnum');
3625     }
3627     // Make sure groups allow this user to see the item they're rating
3628     if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
3629         if (!groups_group_exists($discussion->groupid)) { // Can't find group
3630             throw new rating_exception('cannotfindgroup');//something is wrong
3631         }
3633         if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
3634             // do not allow rating of posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
3635             throw new rating_exception('notmemberofgroup');
3636         }
3637     }
3639     // perform some final capability checks
3640     if (!forum_user_can_see_post($forum, $discussion, $post, $USER, $cm)) {
3641         throw new rating_exception('nopermissiontorate');
3642     }
3644     return true;
3648 /**
3649  * This function prints the overview of a discussion in the forum listing.
3650  * It needs some discussion information and some post information, these
3651  * happen to be combined for efficiency in the $post parameter by the function
3652  * that calls this one: forum_print_latest_discussions()
3653  *
3654  * @global object
3655  * @global object
3656  * @param object $post The post object (passed by reference for speed).
3657  * @param object $forum The forum object.
3658  * @param int $group Current group.
3659  * @param string $datestring Format to use for the dates.
3660  * @param boolean $cantrack Is tracking enabled for this forum.
3661  * @param boolean $forumtracked Is the user tracking this forum.
3662  * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
3663  */
3664 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
3665                                         $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) {
3667     global $USER, $CFG, $OUTPUT;
3669     static $rowcount;
3670     static $strmarkalldread;
3672     if (empty($modcontext)) {
3673         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3674             print_error('invalidcoursemodule');
3675         }
3676         $modcontext = context_module::instance($cm->id);
3677     }
3679     if (!isset($rowcount)) {
3680         $rowcount = 0;
3681         $strmarkalldread = get_string('markalldread', 'forum');
3682     } else {
3683         $rowcount = ($rowcount + 1) % 2;
3684     }
3686     $post->subject = format_string($post->subject,true);
3688     echo "\n\n";
3689     echo '<tr class="discussion r'.$rowcount.'">';
3691     // Topic
3692     echo '<td class="topic starter">';
3693     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.$post->subject.'</a>';
3694     echo "</td>\n";
3696     // Picture
3697     $postuser = new stdClass();
3698     $postuser->id = $post->userid;
3699     $postuser->firstname = $post->firstname;
3700     $postuser->lastname = $post->lastname;
3701     $postuser->imagealt = $post->imagealt;
3702     $postuser->picture = $post->picture;
3703     $postuser->email = $post->email;
3705     echo '<td class="picture">';
3706     echo $OUTPUT->user_picture($postuser, array('courseid'=>$forum->course));
3707     echo "</td>\n";
3709     // User name
3710     $fullname = fullname($post, has_capability('moodle/site:viewfullnames', $modcontext));
3711     echo '<td class="author">';
3712     echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->userid.'&amp;course='.$forum->course.'">'.$fullname.'</a>';
3713     echo "</td>\n";
3715     // Group picture
3716     if ($group !== -1) {  // Groups are active - group is a group data object or NULL
3717         echo '<td class="picture group">';
3718         if (!empty($group->picture) and empty($group->hidepicture)) {
3719             print_group_picture($group, $forum->course, false, false, true);
3720         } else if (isset($group->id)) {
3721             if($canviewparticipants) {
3722                 echo '<a href="'.$CFG->wwwroot.'/user/index.php?id='.$forum->course.'&amp;group='.$group->id.'">'.$group->name.'</a>';
3723             } else {
3724                 echo $group->name;
3725             }
3726         }
3727         echo "</td>\n";
3728     }
3730     if (has_capability('mod/forum:viewdiscussion', $modcontext)) {   // Show the column with replies
3731         echo '<td class="replies">';
3732         echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3733         echo $post->replies.'</a>';
3734         echo "</td>\n";
3736         if ($cantrack) {
3737             echo '<td class="replies">';
3738             if ($forumtracked) {
3739                 if ($post->unread > 0) {
3740                     echo '<span class="unread">';
3741                     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#unread">';
3742                     echo $post->unread;
3743                     echo '</a>';
3744                     echo '<a title="'.$strmarkalldread.'" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
3745                          $forum->id.'&amp;d='.$post->discussion.'&amp;mark=read&amp;returnpage=view.php">' .
3746                          '<img src="'.$OUTPUT->pix_url('t/markasread') . '" class="iconsmall" alt="'.$strmarkalldread.'" /></a>';
3747                     echo '</span>';
3748                 } else {
3749                     echo '<span class="read">';
3750                     echo $post->unread;
3751                     echo '</span>';
3752                 }
3753             } else {
3754                 echo '<span class="read">';
3755                 echo '-';
3756                 echo '</span>';
3757             }
3758             echo "</td>\n";
3759         }
3760     }
3762     echo '<td class="lastpost">';
3763     $usedate = (empty($post->timemodified)) ? $post->modified : $post->timemodified;  // Just in case
3764     $parenturl = (empty($post->lastpostid)) ? '' : '&amp;parent='.$post->lastpostid;
3765     $usermodified = new stdClass();
3766     $usermodified->id        = $post->usermodified;
3767     $usermodified->firstname = $post->umfirstname;
3768     $usermodified->lastname  = $post->umlastname;
3769     echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->usermodified.'&amp;course='.$forum->course.'">'.
3770          fullname($usermodified).'</a><br />';
3771     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.$parenturl.'">'.
3772           userdate($usedate, $datestring).'</a>';
3773     echo "</td>\n";
3775     echo "</tr>\n\n";
3780 /**
3781  * Given a post object that we already know has a long message
3782  * this function truncates the message nicely to the first
3783  * sane place between $CFG->forum_longpost and $CFG->forum_shortpost
3784  *
3785  * @global object
3786  * @param string $message
3787  * @return string
3788  */
3789 function forum_shorten_post($message) {
3791    global $CFG;
3793    $i = 0;
3794    $tag = false;
3795    $length = strlen($message);
3796    $count = 0;
3797    $stopzone = false;
3798    $truncate = 0;
3800    for ($i=0; $i<$length; $i++) {
3801        $char = $message[$i];
3803        switch ($char) {
3804            case "<":
3805                $tag = true;
3806                break;
3807            case ">":
3808                $tag = false;
3809                break;
3810            default:
3811                if (!$tag) {
3812                    if ($stopzone) {
3813                        if ($char == ".") {
3814                            $truncate = $i+1;
3815                            break 2;
3816                        }
3817                    }
3818                    $count++;
3819                }
3820                break;
3821        }
3822        if (!$stopzone) {
3823            if ($count > $CFG->forum_shortpost) {
3824                $stopzone = true;
3825            }
3826        }
3827    }
3829    if (!$truncate) {
3830        $truncate = $i;
3831    }
3833    return substr($message, 0, $truncate);
3836 /**
3837  * Print the drop down that allows the user to select how they want to have
3838  * the discussion displayed.
3839  *
3840  * @param int $id forum id if $forumtype is 'single',
3841  *              discussion id for any other forum type
3842  * @param mixed $mode forum layout mode
3843  * @param string $forumtype optional
3844  */
3845 function forum_print_mode_form($id, $mode, $forumtype='') {
3846     global $OUTPUT;
3847     if ($forumtype == 'single') {
3848         $select = new single_select(new moodle_url("/mod/forum/view.php", array('f'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode");
3849         $select->set_label(get_string('displaymode', 'forum'), array('class' => 'accesshide'));
3850         $select->class = "forummode";
3851     } else {
3852         $select = new single_select(new moodle_url("/mod/forum/discuss.php", array('d'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode");
3853         $select->set_label(get_string('displaymode', 'forum'), array('class' => 'accesshide'));
3854     }
3855     echo $OUTPUT->render($select);
3858 /**
3859  * @global object
3860  * @param object $course
3861  * @param string $search
3862  * @return string
3863  */
3864 function forum_search_form($course, $search='') {
3865     global $CFG, $OUTPUT;
3867     $output  = '<div class="forumsearch">';
3868     $output .= '<form action="'.$CFG->wwwroot.'/mod/forum/search.php" style="display:inline">';
3869     $output .= '<fieldset class="invisiblefieldset">';
3870     $output .= $OUTPUT->help_icon('search');
3871     $output .= '<label class="accesshide" for="search" >'.get_string('search', 'forum').'</label>';
3872     $output .= '<input id="search" name="search" type="text" size="18" value="'.s($search, true).'" alt="search" />';
3873     $output .= '<label class="accesshide" for="searchforums" >'.get_string('searchforums', 'forum').'</label>';
3874     $output .= '<input id="searchforums" value="'.get_string('searchforums', 'forum').'" type="submit" />';
3875     $output .= '<input name="id" type="hidden" value="'.$course->id.'" />';
3876     $output .= '</fieldset>';
3877     $output .= '</form>';
3878     $output .= '</div>';
3880     return $output;
3884 /**
3885  * @global object
3886  * @global object
3887  */
3888 function forum_set_return() {
3889     global $CFG, $SESSION;
3891     if (! isset($SESSION->fromdiscussion)) {
3892         if (!empty($_SERVER['HTTP_REFERER'])) {
3893             $referer = $_SERVER['HTTP_REFERER'];
3894         } else {
3895             $referer = "";
3896         }
3897         // If the referer is NOT a login screen then save it.
3898         if (! strncasecmp("$CFG->wwwroot/login", $referer, 300)) {
3899             $SESSION->fromdiscussion = $_SERVER["HTTP_REFERER"];
3900         }
3901     }
3905 /**
3906  * @global object
3907  * @param string $default
3908  * @return string
3909  */
3910 function forum_go_back_to($default) {
3911     global $SESSION;
3913     if (!empty($SESSION->fromdiscussion)) {
3914         $returnto = $SESSION->fromdiscussion;
3915         unset($SESSION->fromdiscussion);
3916         return $returnto;
3917     } else {
3918         return $default;
3919     }
3922 /**
3923  * Given a discussion object that is being moved to $forumto,
3924  * this function checks all posts in that discussion
3925  * for attachments, and if any are found, these are
3926  * moved to the new forum directory.
3927  *
3928  * @global object
3929  * @param object $discussion
3930  * @param int $forumfrom source forum id
3931  * @param int $forumto target forum id
3932  * @return bool success
3933  */
3934 function forum_move_attachments($discussion, $forumfrom, $forumto) {
3935     global $DB;
3937     $fs = get_file_storage();
3939     $newcm = get_coursemodule_from_instance('forum', $forumto);
3940     $oldcm = get_coursemodule_from_instance('forum', $forumfrom);
3942     $newcontext = context_module::instance($newcm->id);
3943     $oldcontext = context_module::instance($oldcm->id);
3945     // loop through all posts, better not use attachment flag ;-)
3946     if ($posts = $DB->get_records('forum_posts', array('discussion'=>$discussion->id), '', 'id, attachment')) {
3947         foreach ($posts as $post) {
3948             $fs->move_area_files_to_new_context($oldcontext->id,
3949                     $newcontext->id, 'mod_forum', 'post', $post->id);
3950             $attachmentsmoved = $fs->move_area_files_to_new_context($oldcontext->id,
3951                     $newcontext->id, 'mod_forum', 'attachment', $post->id);
3952             if ($attachmentsmoved > 0 && $post->attachment != '1') {
3953                 // Weird - let's fix it
3954                 $post->attachment = '1';
3955                 $DB->update_record('forum_posts', $post);
3956             } else if ($attachmentsmoved == 0 && $post->attachment != '') {
3957                 // Weird - let's fix it
3958                 $post->attachment = '';
3959                 $DB->update_record('forum_posts', $post);
3960             }
3961         }
3962     }
3964     return true;
3967 /**
3968  * Returns attachments as formated text/html optionally with separate images
3969  *
3970  * @global object
3971  * @global object
3972  * @global object
3973  * @param object $post
3974  * @param object $cm
3975  * @param string $type html/text/separateimages
3976  * @return mixed string or array of (html text withouth images and image HTML)
3977  */
3978 function forum_print_attachments($post, $cm, $type) {
3979     global $CFG, $DB, $USER, $OUTPUT;
3981     if (empty($post->attachment)) {
3982         return $type !== 'separateimages' ? '' : array('', '');
3983     }
3985     if (!in_array($type, array('separateimages', 'html', 'text'))) {
3986         return $type !== 'separateimages' ? '' : array('', '');
3987     }
3989     if (!$context = context_module::instance($cm->id)) {
3990         return $type !== 'separateimages' ? '' : array('', '');
3991     }
3992     $strattachment = get_string('attachment', 'forum');
3994     $fs = get_file_storage();
3996     $imagereturn = '';
3997     $output = '';
3999     $canexport = !empty($CFG->enableportfolios) && (has_capability('mod/forum:exportpost', $context) || ($post->userid == $USER->id && has_capability('mod/forum:exportownpost', $context)));
4001     if ($canexport) {
4002         require_once($CFG->libdir.'/portfoliolib.php');
4003     }
4005     $files = $fs->get_area_files($context->id, 'mod_forum', 'attachment', $post->id, "timemodified", false);
4006     if ($files) {
4007         if ($canexport) {
4008             $button = new portfolio_add_button();
4009         }
4010         foreach ($files as $file) {
4011             $filename = $file->get_filename();
4012             $mimetype = $file->get_mimetype();
4013             $iconimage = $OUTPUT->pix_icon(file_file_icon($file), get_mimetype_description($file), 'moodle', array('class' => 'icon'));
4014             $path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$context->id.'/mod_forum/attachment/'.$post->id.'/'.$filename);
4016             if ($type == 'html') {
4017                 $output .= "<a href=\"$path\">$iconimage</a> ";
4018                 $output .= "<a href=\"$path\">".s($filename)."</a>";
4019                 if ($canexport) {
4020                     $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), 'mod_forum');
4021                     $button->set_format_by_file($file);
4022                     $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
4023                 }
4024                 $output .= "<br />";
4026             } else if ($type == 'text') {
4027                 $output .= "$strattachment ".s($filename).":\n$path\n";
4029             } else { //'returnimages'
4030                 if (in_array($mimetype, array('image/gif', 'image/jpeg', 'image/png'))) {
4031                     // Image attachments don't get printed as links
4032                     $imagereturn .= "<br /><img src=\"$path\" alt=\"\" />";
4033                     if ($canexport) {
4034                         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), 'mod_forum');
4035                         $button->set_format_by_file($file);
4036                         $imagereturn .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
4037                     }
4038                 } else {
4039                     $output .= "<a href=\"$path\">$iconimage</a> ";
4040                     $output .= format_text("<a href=\"$path\">".s($filename)."</a>", FORMAT_HTML, array('context'=>$context));
4041                     if ($canexport) {
4042                         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), 'mod_forum');
4043                         $button->set_format_by_file($file);
4044                         $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
4045                     }
4046                     $output .= '<br />';
4047                 }
4048             }
4050             if (!empty($CFG->enableplagiarism)) {
4051                 require_once($CFG->libdir.'/plagiarismlib.php');
4052                 $output .= plagiarism_get_links(array('userid' => $post->userid,
4053                     'file' => $file,
4054                     'cmid' => $cm->id,
4055                     'course' => $post->course,
4056                     'forum' => $post->forum));
4057                 $output .= '<br />';
4058             }
4059         }
4060     }
4062     if ($type !== 'separateimages') {
4063         return $output;
4065     } else {
4066         return array($output, $imagereturn);
4067     }
4070 ////////////////////////////////////////////////////////////////////////////////
4071 // File API                                                                   //
4072 ////////////////////////////////////////////////////////////////////////////////
4074 /**
4075  * Lists all browsable file areas
4076  *
4077  * @package  mod_forum
4078  * @category files
4079  * @param stdClass $course course object
4080  * @param stdClass $cm course module object
4081  * @param stdClass $context context object
4082  * @return array
4083  */
4084 function forum_get_file_areas($course, $cm, $context) {
4085     return array(
4086         'attachment' => get_string('areaattachment', 'mod_forum'),
4087         'post' => get_string('areapost', 'mod_forum'),
4088     );
4091 /**
4092  * File browsing support for forum module.
4093  *
4094  * @package  mod_forum
4095  * @category files
4096  * @param stdClass $browser file browser object
4097  * @param stdClass $areas file areas
4098  * @param stdClass $course course object
4099  * @param stdClass $cm course module
4100  * @param stdClass $context context module
4101  * @param string $filearea file area
4102  * @param int $itemid item ID
4103  * @param string $filepath file path
4104  * @param string $filename file name
4105  * @return file_info instance or null if not found
4106  */
4107 function forum_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
4108     global $CFG, $DB, $USER;
4110     if ($context->contextlevel != CONTEXT_MODULE) {
4111         return null;
4112     }
4114     // filearea mus