005ed086819122679a6ed52175cb687c4ef7c795
[moodle.git] / mod / forum / lib.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * @package mod-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 /** Include required files */
25 require_once($CFG->libdir.'/filelib.php');
26 require_once($CFG->libdir.'/eventslib.php');
27 require_once($CFG->libdir . '/completionlib.php');
28 require_once($CFG->dirroot.'/user/selector/lib.php');
30 /// CONSTANTS ///////////////////////////////////////////////////////////
32 define('FORUM_MODE_FLATOLDEST', 1);
33 define('FORUM_MODE_FLATNEWEST', -1);
34 define('FORUM_MODE_THREADED', 2);
35 define('FORUM_MODE_NESTED', 3);
37 define('FORUM_CHOOSESUBSCRIBE', 0);
38 define('FORUM_FORCESUBSCRIBE', 1);
39 define('FORUM_INITIALSUBSCRIBE', 2);
40 define('FORUM_DISALLOWSUBSCRIBE',3);
42 define('FORUM_TRACKING_OFF', 0);
43 define('FORUM_TRACKING_OPTIONAL', 1);
44 define('FORUM_TRACKING_ON', 2);
46 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
48 /**
49  * Given an object containing all the necessary data,
50  * (defined by the form in mod_form.php) this function
51  * will create a new instance and return the id number
52  * of the new instance.
53  *
54  * @global object
55  * @global object
56  * @param object $forum add forum instance (with magic quotes)
57  * @return int intance id
58  */
59 function forum_add_instance($forum, $mform) {
60     global $CFG, $DB;
62     $forum->timemodified = time();
64     if (empty($forum->assessed)) {
65         $forum->assessed = 0;
66     }
68     if (empty($forum->ratingtime) or empty($forum->assessed)) {
69         $forum->assesstimestart  = 0;
70         $forum->assesstimefinish = 0;
71     }
73     $forum->id = $DB->insert_record('forum', $forum);
74     $modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule);
76     if ($forum->type == 'single') {  // Create related discussion.
77         $discussion = new object();
78         $discussion->course        = $forum->course;
79         $discussion->forum         = $forum->id;
80         $discussion->name          = $forum->name;
81         $discussion->assessed      = $forum->assessed;
82         $discussion->message       = $forum->intro;
83         $discussion->messageformat = $forum->introformat;
84         $discussion->messagetrust  = trusttext_trusted(get_context_instance(CONTEXT_COURSE, $forum->course));
85         $discussion->mailnow       = false;
86         $discussion->groupid       = -1;
88         $message = '';
90         $discussion->id = forum_add_discussion($discussion, null, $message);
92         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
93             // ugly hack - we need to copy the files somehow
94             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
95             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
97             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
98             $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
99         }
100     }
102     if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
103     /// all users should be subscribed initially
104     /// Note: forum_get_potential_subscribers should take the forum context,
105     /// but that does not exist yet, becuase the forum is only half build at this
106     /// stage. However, because the forum is brand new, we know that there are
107     /// no role assignments or overrides in the forum context, so using the
108     /// course context gives the same list of users.
109         $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
110         foreach ($users as $user) {
111             forum_subscribe($user->id, $forum->id);
112         }
113     }
115     forum_grade_item_update($forum);
117     return $forum->id;
121 /**
122  * Given an object containing all the necessary data,
123  * (defined by the form in mod_form.php) this function
124  * will update an existing instance with new data.
125  *
126  * @global object
127  * @param object $forum forum instance (with magic quotes)
128  * @return bool success
129  */
130 function forum_update_instance($forum, $mform) {
131     global $DB, $OUTPUT, $USER;
133     $forum->timemodified = time();
134     $forum->id           = $forum->instance;
136     if (empty($forum->assessed)) {
137         $forum->assessed = 0;
138     }
140     if (empty($forum->ratingtime) or empty($forum->assessed)) {
141         $forum->assesstimestart  = 0;
142         $forum->assesstimefinish = 0;
143     }
145     $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
147     // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
148     // if  scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
149     // 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
150     if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
151         forum_update_grades($forum); // recalculate grades for the forum
152     }
154     if ($forum->type == 'single') {  // Update related discussion and post.
155         if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
156             if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC')) {
157                 echo $OUTPUT->notification('Warning! There is more than one discussion in this forum - using the most recent');
158                 $discussion = array_pop($discussions);
159             } else {
160                 // try to recover by creating initial discussion - MDL-16262
161                 $discussion = new object();
162                 $discussion->course          = $forum->course;
163                 $discussion->forum           = $forum->id;
164                 $discussion->name            = $forum->name;
165                 $discussion->assessed        = $forum->assessed;
166                 $discussion->message         = $forum->intro;
167                 $discussion->messageformat   = $forum->introformat;
168                 $discussion->messagetrust    = true;
169                 $discussion->mailnow         = false;
170                 $discussion->groupid         = -1;
172                 forum_add_discussion($discussion, null, $message);
174                 if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
175                     print_error('cannotadd', 'forum');
176                 }
177             }
178         }
179         if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
180             print_error('cannotfindfirstpost', 'forum');
181         }
183         $cm         = get_coursemodule_from_instance('forum', $forum->id);
184         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
186         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
187             // ugly hack - we need to copy the files somehow
188             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
189             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
191             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
192         }
194         $post->subject       = $forum->name;
195         $post->message       = $forum->intro;
196         $post->messageformat = $forum->introformat;
197         $post->messagetrust  = trusttext_trusted($modcontext);
198         $post->modified      = $forum->timemodified;
199         $post->userid        = $USER->id;    // MDL-18599, so that current teacher can take ownership of activities
201         $DB->update_record('forum_posts', $post);
202         $discussion->name = $forum->name;
203         $DB->update_record('forum_discussions', $discussion);
204     }
206     $DB->update_record('forum', $forum);
208     forum_grade_item_update($forum);
210     return true;
214 /**
215  * Given an ID of an instance of this module,
216  * this function will permanently delete the instance
217  * and any data that depends on it.
218  *
219  * @global object
220  * @param int $id forum instance id
221  * @return bool success
222  */
223 function forum_delete_instance($id) {
224     global $DB;
226     if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
227         return false;
228     }
229     if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
230         return false;
231     }
232     if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
233         return false;
234     }
236     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
238     // now get rid of all files
239     $fs = get_file_storage();
240     $fs->delete_area_files($context->id);
242     $result = true;
244     if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
245         foreach ($discussions as $discussion) {
246             if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
247                 $result = false;
248             }
249         }
250     }
252     if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
253         $result = false;
254     }
256     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
258     if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
259         $result = false;
260     }
262     forum_grade_item_delete($forum);
264     return $result;
268 /**
269  * Indicates API features that the forum supports.
270  *
271  * @uses FEATURE_GROUPS
272  * @uses FEATURE_GROUPINGS
273  * @uses FEATURE_GROUPMEMBERSONLY
274  * @uses FEATURE_MOD_INTRO
275  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
276  * @uses FEATURE_COMPLETION_HAS_RULES
277  * @uses FEATURE_GRADE_HAS_GRADE
278  * @uses FEATURE_GRADE_OUTCOMES
279  * @param string $feature
280  * @return mixed True if yes (some features may use other values)
281  */
282 function forum_supports($feature) {
283     switch($feature) {
284         case FEATURE_GROUPS:                  return true;
285         case FEATURE_GROUPINGS:               return true;
286         case FEATURE_GROUPMEMBERSONLY:        return true;
287         case FEATURE_MOD_INTRO:               return true;
288         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
289         case FEATURE_COMPLETION_HAS_RULES:    return true;
290         case FEATURE_GRADE_HAS_GRADE:         return true;
291         case FEATURE_GRADE_OUTCOMES:          return true;
292         case FEATURE_RATE:                    return true;
293         case FEATURE_BACKUP_MOODLE2:          return true;
295         default: return null;
296     }
300 /**
301  * Obtains the automatic completion state for this forum based on any conditions
302  * in forum settings.
303  *
304  * @global object
305  * @global object
306  * @param object $course Course
307  * @param object $cm Course-module
308  * @param int $userid User ID
309  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
310  * @return bool True if completed, false if not. (If no conditions, then return
311  *   value depends on comparison type)
312  */
313 function forum_get_completion_state($course,$cm,$userid,$type) {
314     global $CFG,$DB;
316     // Get forum details
317     if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
318         throw new Exception("Can't find forum {$cm->instance}");
319     }
321     $result=$type; // Default return value
323     $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
324     $postcountsql="
325 SELECT
326     COUNT(1)
327 FROM
328     {forum_posts} fp
329     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
330 WHERE
331     fp.userid=:userid AND fd.forum=:forumid";
333     if ($forum->completiondiscussions) {
334         $value = $forum->completiondiscussions <=
335                  $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
336         if ($type == COMPLETION_AND) {
337             $result = $result && $value;
338         } else {
339             $result = $result || $value;
340         }
341     }
342     if ($forum->completionreplies) {
343         $value = $forum->completionreplies <=
344                  $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
345         if ($type==COMPLETION_AND) {
346             $result = $result && $value;
347         } else {
348             $result = $result || $value;
349         }
350     }
351     if ($forum->completionposts) {
352         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
353         if ($type == COMPLETION_AND) {
354             $result = $result && $value;
355         } else {
356             $result = $result || $value;
357         }
358     }
360     return $result;
364 /**
365  * Function to be run periodically according to the moodle cron
366  * Finds all posts that have yet to be mailed out, and mails them
367  * out to all subscribers
368  *
369  * @global object
370  * @global object
371  * @global object
372  * @uses CONTEXT_MODULE
373  * @uses CONTEXT_COURSE
374  * @uses SITEID
375  * @uses FORMAT_PLAIN
376  * @return void
377  */
378 function forum_cron() {
379     global $CFG, $USER, $DB;
381     $site = get_site();
383     // all users that are subscribed to any post that needs sending
384     $users = array();
386     // status arrays
387     $mailcount  = array();
388     $errorcount = array();
390     // caches
391     $discussions     = array();
392     $forums          = array();
393     $courses         = array();
394     $coursemodules   = array();
395     $subscribedusers = array();
398     // Posts older than 2 days will not be mailed.  This is to avoid the problem where
399     // cron has not been running for a long time, and then suddenly people are flooded
400     // with mail from the past few weeks or months
401     $timenow   = time();
402     $endtime   = $timenow - $CFG->maxeditingtime;
403     $starttime = $endtime - 48 * 3600;   // Two days earlier
405     if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
406         // Mark them all now as being mailed.  It's unlikely but possible there
407         // might be an error later so that a post is NOT actually mailed out,
408         // but since mail isn't crucial, we can accept this risk.  Doing it now
409         // prevents the risk of duplicated mails, which is a worse problem.
411         if (!forum_mark_old_posts_as_mailed($endtime)) {
412             mtrace('Errors occurred while trying to mark some posts as being mailed.');
413             return false;  // Don't continue trying to mail them, in case we are in a cron loop
414         }
416         // checking post validity, and adding users to loop through later
417         foreach ($posts as $pid => $post) {
419             $discussionid = $post->discussion;
420             if (!isset($discussions[$discussionid])) {
421                 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
422                     $discussions[$discussionid] = $discussion;
423                 } else {
424                     mtrace('Could not find discussion '.$discussionid);
425                     unset($posts[$pid]);
426                     continue;
427                 }
428             }
429             $forumid = $discussions[$discussionid]->forum;
430             if (!isset($forums[$forumid])) {
431                 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
432                     $forums[$forumid] = $forum;
433                 } else {
434                     mtrace('Could not find forum '.$forumid);
435                     unset($posts[$pid]);
436                     continue;
437                 }
438             }
439             $courseid = $forums[$forumid]->course;
440             if (!isset($courses[$courseid])) {
441                 if ($course = $DB->get_record('course', array('id' => $courseid))) {
442                     $courses[$courseid] = $course;
443                 } else {
444                     mtrace('Could not find course '.$courseid);
445                     unset($posts[$pid]);
446                     continue;
447                 }
448             }
449             if (!isset($coursemodules[$forumid])) {
450                 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
451                     $coursemodules[$forumid] = $cm;
452                 } else {
453                     mtrace('Could not course module for forum '.$forumid);
454                     unset($posts[$pid]);
455                     continue;
456                 }
457             }
460             // caching subscribed users of each forum
461             if (!isset($subscribedusers[$forumid])) {
462                 $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
463                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext)) {
464                     foreach ($subusers as $postuser) {
465                         // do not try to mail users with stopped email
466                         if ($postuser->emailstop) {
467                             if (!empty($CFG->forum_logblocked)) {
468                                 add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
469                             }
470                             continue;
471                         }
472                         // this user is subscribed to this forum
473                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
474                         // this user is a user we have to process later
475                         $users[$postuser->id] = $postuser;
476                     }
477                     unset($subusers); // release memory
478                 }
479             }
481             $mailcount[$pid] = 0;
482             $errorcount[$pid] = 0;
483         }
484     }
486     if ($users && $posts) {
488         $urlinfo = parse_url($CFG->wwwroot);
489         $hostname = $urlinfo['host'];
491         foreach ($users as $userto) {
493             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
495             // set this so that the capabilities are cached, and environment matches receiving user
496             cron_setup_user($userto);
498             mtrace('Processing user '.$userto->id);
500             // init caches
501             $userto->viewfullnames = array();
502             $userto->canpost       = array();
503             $userto->markposts     = array();
504             $userto->enrolledin    = array();
506             // reset the caches
507             foreach ($coursemodules as $forumid=>$unused) {
508                 $coursemodules[$forumid]->cache       = new object();
509                 $coursemodules[$forumid]->cache->caps = array();
510                 unset($coursemodules[$forumid]->uservisible);
511             }
513             foreach ($posts as $pid => $post) {
515                 // Set up the environment for the post, discussion, forum, course
516                 $discussion = $discussions[$post->discussion];
517                 $forum      = $forums[$discussion->forum];
518                 $course     = $courses[$forum->course];
519                 $cm         =& $coursemodules[$forum->id];
521                 // Do some checks  to see if we can bail out now
522                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
523                     continue; // user does not subscribe to this forum
524                 }
526                 // Verify user is enrollend in course - if not do not send any email
527                 if (!isset($userto->enrolledin[$course->id])) {
528                     $userto->enrolledin[$course->id] = is_enrolled(get_context_instance(CONTEXT_COURSE, $course->id));
529                 }
530                 if (!$userto->enrolledin[$course->id]) {
531                     // oops - this user should not receive anything from this course
532                     continue;
533                 }
535                 // Get info about the sending user
536                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
537                     $userfrom = $users[$post->userid];
538                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
539                     $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
540                 } else {
541                     mtrace('Could not find user '.$post->userid);
542                     continue;
543                 }
545                 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
547                 // setup global $COURSE properly - needed for roles and languages
548                 cron_setup_user($userto, $course);
550                 // Fill caches
551                 if (!isset($userto->viewfullnames[$forum->id])) {
552                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
553                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
554                 }
555                 if (!isset($userto->canpost[$discussion->id])) {
556                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
557                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
558                 }
559                 if (!isset($userfrom->groups[$forum->id])) {
560                     if (!isset($userfrom->groups)) {
561                         $userfrom->groups = array();
562                         $users[$userfrom->id]->groups = array();
563                     }
564                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
565                     $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
566                 }
568                 // Make sure groups allow this user to see this email
569                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
570                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
571                         continue;                           // Be safe and don't send it to anyone
572                     }
574                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
575                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
576                         continue;
577                     }
578                 }
580                 // Make sure we're allowed to see it...
581                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
582                     mtrace('user '.$userto->id. ' can not see '.$post->id);
583                     continue;
584                 }
586                 // OK so we need to send the email.
588                 // Does the user want this post in a digest?  If so postpone it for now.
589                 if ($userto->maildigest > 0) {
590                     // This user wants the mails to be in digest form
591                     $queue = new object();
592                     $queue->userid       = $userto->id;
593                     $queue->discussionid = $discussion->id;
594                     $queue->postid       = $post->id;
595                     $queue->timemodified = $post->created;
596                     $DB->insert_record('forum_queue', $queue);
597                     continue;
598                 }
601                 // Prepare to actually send the post now, and build up the content
603                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
605                 $userfrom->customheaders = array (  // Headers to make emails easier to track
606                            'Precedence: Bulk',
607                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
608                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
609                            'Message-ID: <moodlepost'.$post->id.'@'.$hostname.'>',
610                            'X-Course-Id: '.$course->id,
611                            'X-Course-Name: '.format_string($course->fullname, true)
612                 );
614                 if ($post->parent) {  // This post is a reply, so add headers for threading (see MDL-22551)
615                     $userfrom->customheaders[] = 'In-Reply-To: <moodlepost'.$post->parent.'@'.$hostname.'>';
616                     $userfrom->customheaders[] = 'References: <moodlepost'.$post->parent.'@'.$hostname.'>';
617                 }
619                 $postsubject = "$course->shortname: ".format_string($post->subject,true);
620                 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
621                 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
623                 // Send the post now!
625                 mtrace('Sending ', '');
627                 $eventdata = new object();
628                 $eventdata->component        = 'mod_forum';
629                 $eventdata->name             = 'posts';
630                 $eventdata->userfrom         = $userfrom;
631                 $eventdata->userto           = $userto;
632                 $eventdata->subject          = $postsubject;
633                 $eventdata->fullmessage      = $posttext;
634                 $eventdata->fullmessageformat = FORMAT_PLAIN;
635                 $eventdata->fullmessagehtml  = $posthtml;
636                 $eventdata->smallmessage     = '';
638                 $mailresult = message_send($eventdata);
639                 if (!$mailresult){
640                     mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
641                          " ($userto->email) .. not trying again.");
642                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
643                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
644                     $errorcount[$post->id]++;
645                 } else if ($mailresult === 'emailstop') {
646                     // should not be reached anymore - see check above
647                     mtrace("Error: mod/forum/lib.php forum_cron(): received 'emailstop' while sending out mail for id $post->id to user $userto->id ($userto->email)");
648                 } else {
649                     $mailcount[$post->id]++;
651                 // Mark post as read if forum_usermarksread is set off
652                     if (!$CFG->forum_usermarksread) {
653                         $userto->markposts[$post->id] = $post->id;
654                     }
655                 }
657                 mtrace('post '.$post->id. ': '.$post->subject);
658             }
660             // mark processed posts as read
661             forum_tp_mark_posts_read($userto, $userto->markposts);
662         }
663     }
665     if ($posts) {
666         foreach ($posts as $post) {
667             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
668             if ($errorcount[$post->id]) {
669                 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
670             }
671         }
672     }
674     // release some memory
675     unset($subscribedusers);
676     unset($mailcount);
677     unset($errorcount);
679     cron_setup_user();
681     $sitetimezone = $CFG->timezone;
683     // Now see if there are any digest mails waiting to be sent, and if we should send them
685     mtrace('Starting digest processing...');
687     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
689     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
690         set_config('digestmailtimelast', 0);
691     }
693     $timenow = time();
694     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
696     // Delete any really old ones (normally there shouldn't be any)
697     $weekago = $timenow - (7 * 24 * 3600);
698     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
699     mtrace ('Cleaned old digest records');
701     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
703         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
705         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
707         if ($digestposts_rs->valid()) {
709             // We have work to do
710             $usermailcount = 0;
712             //caches - reuse the those filled before too
713             $discussionposts = array();
714             $userdiscussions = array();
716             foreach ($digestposts_rs as $digestpost) {
717                 if (!isset($users[$digestpost->userid])) {
718                     if ($user = $DB->get_record('user', array('id' => $digestpost->userid))) {
719                         $users[$digestpost->userid] = $user;
720                     } else {
721                         continue;
722                     }
723                 }
724                 $postuser = $users[$digestpost->userid];
725                 if ($postuser->emailstop) {
726                     if (!empty($CFG->forum_logblocked)) {
727                         add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
728                     }
729                     continue;
730                 }
732                 if (!isset($posts[$digestpost->postid])) {
733                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
734                         $posts[$digestpost->postid] = $post;
735                     } else {
736                         continue;
737                     }
738                 }
739                 $discussionid = $digestpost->discussionid;
740                 if (!isset($discussions[$discussionid])) {
741                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
742                         $discussions[$discussionid] = $discussion;
743                     } else {
744                         continue;
745                     }
746                 }
747                 $forumid = $discussions[$discussionid]->forum;
748                 if (!isset($forums[$forumid])) {
749                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
750                         $forums[$forumid] = $forum;
751                     } else {
752                         continue;
753                     }
754                 }
756                 $courseid = $forums[$forumid]->course;
757                 if (!isset($courses[$courseid])) {
758                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
759                         $courses[$courseid] = $course;
760                     } else {
761                         continue;
762                     }
763                 }
765                 if (!isset($coursemodules[$forumid])) {
766                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
767                         $coursemodules[$forumid] = $cm;
768                     } else {
769                         continue;
770                     }
771                 }
772                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
773                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
774             }
775             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
777             // Data collected, start sending out emails to each user
778             foreach ($userdiscussions as $userid => $thesediscussions) {
780                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
782                 cron_setup_user();
784                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
786                 // First of all delete all the queue entries for this user
787                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
788                 $userto = $users[$userid];
790                 // Override the language and timezone of the "current" user, so that
791                 // mail is customised for the receiver.
792                 cron_setup_user($userto);
794                 // init caches
795                 $userto->viewfullnames = array();
796                 $userto->canpost       = array();
797                 $userto->markposts     = array();
799                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
801                 $headerdata = new object();
802                 $headerdata->sitename = format_string($site->fullname, true);
803                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
805                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
806                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
808                 $posthtml = "<head>";
809 /*                foreach ($CFG->stylesheets as $stylesheet) {
810                     //TODO: MDL-21120
811                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
812                 }*/
813                 $posthtml .= "</head>\n<body id=\"email\">\n";
814                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
816                 foreach ($thesediscussions as $discussionid) {
818                     @set_time_limit(120);   // to be reset for each post
820                     $discussion = $discussions[$discussionid];
821                     $forum      = $forums[$discussion->forum];
822                     $course     = $courses[$forum->course];
823                     $cm         = $coursemodules[$forum->id];
825                     //override language
826                     cron_setup_user($userto, $course);
828                     // Fill caches
829                     if (!isset($userto->viewfullnames[$forum->id])) {
830                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
831                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
832                     }
833                     if (!isset($userto->canpost[$discussion->id])) {
834                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
835                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
836                     }
838                     $strforums      = get_string('forums', 'forum');
839                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
840                     $canreply       = $userto->canpost[$discussion->id];
842                     $posttext .= "\n \n";
843                     $posttext .= '=====================================================================';
844                     $posttext .= "\n \n";
845                     $posttext .= "$course->shortname -> $strforums -> ".format_string($forum->name,true);
846                     if ($discussion->name != $forum->name) {
847                         $posttext  .= " -> ".format_string($discussion->name,true);
848                     }
849                     $posttext .= "\n";
851                     $posthtml .= "<p><font face=\"sans-serif\">".
852                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> -> ".
853                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
854                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
855                     if ($discussion->name == $forum->name) {
856                         $posthtml .= "</font></p>";
857                     } else {
858                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
859                     }
860                     $posthtml .= '<p>';
862                     $postsarray = $discussionposts[$discussionid];
863                     sort($postsarray);
865                     foreach ($postsarray as $postid) {
866                         $post = $posts[$postid];
868                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
869                             $userfrom = $users[$post->userid];
870                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
871                             $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
872                         } else {
873                             mtrace('Could not find user '.$post->userid);
874                             continue;
875                         }
877                         if (!isset($userfrom->groups[$forum->id])) {
878                             if (!isset($userfrom->groups)) {
879                                 $userfrom->groups = array();
880                                 $users[$userfrom->id]->groups = array();
881                             }
882                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
883                             $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
884                         }
886                         $userfrom->customheaders = array ("Precedence: Bulk");
888                         if ($userto->maildigest == 2) {
889                             // Subjects only
890                             $by = new object();
891                             $by->name = fullname($userfrom);
892                             $by->date = userdate($post->modified);
893                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
894                             $posttext .= "\n---------------------------------------------------------------------";
896                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
897                             $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>';
899                         } else {
900                             // The full treatment
901                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
902                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
904                         // Create an array of postid's for this user to mark as read.
905                             if (!$CFG->forum_usermarksread) {
906                                 $userto->markposts[$post->id] = $post->id;
907                             }
908                         }
909                     }
910                     if ($canunsubscribe) {
911                         $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>";
912                     } else {
913                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
914                     }
915                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
916                 }
917                 $posthtml .= '</body>';
919                 if (empty($userto->mailformat) || $userto->mailformat != 1) {
920                     // This user DOESN'T want to receive HTML
921                     $posthtml = '';
922                 }
924                 $attachment = $attachname='';
925                 $usetrueaddress = true;
926                 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname, $usetrueaddress, $CFG->forum_replytouser);
928                 if (!$mailresult) {
929                     mtrace("ERROR!");
930                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
931                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
932                 } else if ($mailresult === 'emailstop') {
933                     // should not happen anymore - see check above
934                 } else {
935                     mtrace("success.");
936                     $usermailcount++;
938                     // Mark post as read if forum_usermarksread is set off
939                     forum_tp_mark_posts_read($userto, $userto->markposts);
940                 }
941             }
942         }
943     /// We have finishied all digest emails, update $CFG->digestmailtimelast
944         set_config('digestmailtimelast', $timenow);
945     }
947     cron_setup_user();
949     if (!empty($usermailcount)) {
950         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
951     }
953     if (!empty($CFG->forum_lastreadclean)) {
954         $timenow = time();
955         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
956             set_config('forum_lastreadclean', $timenow);
957             mtrace('Removing old forum read tracking info...');
958             forum_tp_clean_read_records();
959         }
960     } else {
961         set_config('forum_lastreadclean', time());
962     }
965     return true;
968 /**
969  * Builds and returns the body of the email notification in plain text.
970  *
971  * @global object
972  * @global object
973  * @uses CONTEXT_MODULE
974  * @param object $course
975  * @param object $cm
976  * @param object $forum
977  * @param object $discussion
978  * @param object $post
979  * @param object $userfrom
980  * @param object $userto
981  * @param boolean $bare
982  * @return string The email body in plain text format.
983  */
984 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
985     global $CFG, $USER;
987     if (!isset($userto->viewfullnames[$forum->id])) {
988         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
989         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
990     } else {
991         $viewfullnames = $userto->viewfullnames[$forum->id];
992     }
994     if (!isset($userto->canpost[$discussion->id])) {
995         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
996         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
997     } else {
998         $canreply = $userto->canpost[$discussion->id];
999     }
1001     $by = New stdClass;
1002     $by->name = fullname($userfrom, $viewfullnames);
1003     $by->date = userdate($post->modified, "", $userto->timezone);
1005     $strbynameondate = get_string('bynameondate', 'forum', $by);
1007     $strforums = get_string('forums', 'forum');
1009     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1011     $posttext = '';
1013     if (!$bare) {
1014         $posttext  = "$course->shortname -> $strforums -> ".format_string($forum->name,true);
1016         if ($discussion->name != $forum->name) {
1017             $posttext  .= " -> ".format_string($discussion->name,true);
1018         }
1019     }
1021     $posttext .= "\n---------------------------------------------------------------------\n";
1022     $posttext .= format_string($post->subject,true);
1023     if ($bare) {
1024         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1025     }
1026     $posttext .= "\n".$strbynameondate."\n";
1027     $posttext .= "---------------------------------------------------------------------\n";
1028     $posttext .= format_text_email($post->message, $post->messageformat);
1029     $posttext .= "\n\n";
1030     $posttext .= forum_print_attachments($post, $cm, "text");
1032     if (!$bare && $canreply) {
1033         $posttext .= "---------------------------------------------------------------------\n";
1034         $posttext .= get_string("postmailinfo", "forum", $course->shortname)."\n";
1035         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1036     }
1037     if (!$bare && $canunsubscribe) {
1038         $posttext .= "\n---------------------------------------------------------------------\n";
1039         $posttext .= get_string("unsubscribe", "forum");
1040         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1041     }
1043     return $posttext;
1046 /**
1047  * Builds and returns the body of the email notification in html format.
1048  *
1049  * @global object
1050  * @param object $course
1051  * @param object $cm
1052  * @param object $forum
1053  * @param object $discussion
1054  * @param object $post
1055  * @param object $userfrom
1056  * @param object $userto
1057  * @return string The email text in HTML format
1058  */
1059 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1060     global $CFG;
1062     if ($userto->mailformat != 1) {  // Needs to be HTML
1063         return '';
1064     }
1066     if (!isset($userto->canpost[$discussion->id])) {
1067         $canreply = forum_user_can_post($forum, $discussion, $userto);
1068     } else {
1069         $canreply = $userto->canpost[$discussion->id];
1070     }
1072     $strforums = get_string('forums', 'forum');
1073     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1075     $posthtml = '<head>';
1076 /*    foreach ($CFG->stylesheets as $stylesheet) {
1077         //TODO: MDL-21120
1078         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1079     }*/
1080     $posthtml .= '</head>';
1081     $posthtml .= "\n<body id=\"email\">\n\n";
1083     $posthtml .= '<div class="navbar">'.
1084     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$course->shortname.'</a> &raquo; '.
1085     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1086     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1087     if ($discussion->name == $forum->name) {
1088         $posthtml .= '</div>';
1089     } else {
1090         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1091                      format_string($discussion->name,true).'</a></div>';
1092     }
1093     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1095     if ($canunsubscribe) {
1096         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1097                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1098                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1099     }
1101     $posthtml .= '</body>';
1103     return $posthtml;
1107 /**
1108  *
1109  * @param object $course
1110  * @param object $user
1111  * @param object $mod TODO this is not used in this function, refactor
1112  * @param object $forum
1113  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1114  */
1115 function forum_user_outline($course, $user, $mod, $forum) {
1116     global $CFG;
1117     require_once("$CFG->libdir/gradelib.php");
1118     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1119     if (empty($grades->items[0]->grades)) {
1120         $grade = false;
1121     } else {
1122         $grade = reset($grades->items[0]->grades);
1123     }
1125     $count = forum_count_user_posts($forum->id, $user->id);
1127     if ($count && $count->postcount > 0) {
1128         $result = new object();
1129         $result->info = get_string("numposts", "forum", $count->postcount);
1130         $result->time = $count->lastpost;
1131         if ($grade) {
1132             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1133         }
1134         return $result;
1135     } else if ($grade) {
1136         $result = new object();
1137         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1138         $result->time = $grade->dategraded;
1139         return $result;
1140     }
1141     return NULL;
1145 /**
1146  * @global object
1147  * @global object
1148  * @param object $coure
1149  * @param object $user
1150  * @param object $mod
1151  * @param object $forum
1152  */
1153 function forum_user_complete($course, $user, $mod, $forum) {
1154     global $CFG,$USER, $OUTPUT;
1155     require_once("$CFG->libdir/gradelib.php");
1157     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1158     if (!empty($grades->items[0]->grades)) {
1159         $grade = reset($grades->items[0]->grades);
1160         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1161         if ($grade->str_feedback) {
1162             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1163         }
1164     }
1166     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1168         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1169             print_error('invalidcoursemodule');
1170         }
1171         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1173         foreach ($posts as $post) {
1174             if (!isset($discussions[$post->discussion])) {
1175                 continue;
1176             }
1177             $discussion = $discussions[$post->discussion];
1179             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1180         }
1181     } else {
1182         echo "<p>".get_string("noposts", "forum")."</p>";
1183     }
1191 /**
1192  * @global object
1193  * @global object
1194  * @global object
1195  * @param array $courses
1196  * @param array $htmlarray
1197  */
1198 function forum_print_overview($courses,&$htmlarray) {
1199     global $USER, $CFG, $DB, $SESSION;
1201     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1202         return array();
1203     }
1205     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1206         return;
1207     }
1210     // get all forum logs in ONE query (much better!)
1211     $params = array();
1212     $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {log} l "
1213         ." JOIN {course_modules} cm ON cm.id = cmid "
1214         ." WHERE (";
1215     foreach ($courses as $course) {
1216         $sql .= '(l.course = ? AND l.time > ?) OR ';
1217         $params[] = $course->id;
1218         $params[] = $course->lastaccess;
1219     }
1220     $sql = substr($sql,0,-3); // take off the last OR
1222     $sql .= ") AND l.module = 'forum' AND action = 'add post' "
1223         ." AND userid != ? GROUP BY cmid,l.course,instance";
1225     $params[] = $USER->id;
1227     if (!$new = $DB->get_records_sql($sql, $params)) {
1228         $new = array(); // avoid warnings
1229     }
1231     // also get all forum tracking stuff ONCE.
1232     $trackingforums = array();
1233     foreach ($forums as $forum) {
1234         if (forum_tp_can_track_forums($forum)) {
1235             $trackingforums[$forum->id] = $forum;
1236         }
1237     }
1239     if (count($trackingforums) > 0) {
1240         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1241         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1242             ' FROM {forum_posts} p '.
1243             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1244             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1245         $params = array($USER->id);
1247         foreach ($trackingforums as $track) {
1248             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1249             $params[] = $track->id;
1250             if (isset($SESSION->currentgroup[$track->course])) {
1251                 $groupid =  $SESSION->currentgroup[$track->course];
1252             } else {
1253                 $groupid = groups_get_all_groups($track->course, $USER->id);
1254                 if (is_array($groupid)) {
1255                     $groupid = array_shift(array_keys($groupid));
1256                     $SESSION->currentgroup[$track->course] = $groupid;
1257                 } else {
1258                     $groupid = 0;
1259                 }
1260             }
1261             $params[] = $groupid;
1262         }
1263         $sql = substr($sql,0,-3); // take off the last OR
1264         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1265         $params[] = $cutoffdate;
1267         if (!$unread = $DB->get_records_sql($sql, $params)) {
1268             $unread = array();
1269         }
1270     } else {
1271         $unread = array();
1272     }
1274     if (empty($unread) and empty($new)) {
1275         return;
1276     }
1278     $strforum = get_string('modulename','forum');
1279     $strnumunread = get_string('overviewnumunread','forum');
1280     $strnumpostssince = get_string('overviewnumpostssince','forum');
1282     foreach ($forums as $forum) {
1283         $str = '';
1284         $count = 0;
1285         $thisunread = 0;
1286         $showunread = false;
1287         // either we have something from logs, or trackposts, or nothing.
1288         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1289             $count = $new[$forum->id]->count;
1290         }
1291         if (array_key_exists($forum->id,$unread)) {
1292             $thisunread = $unread[$forum->id]->count;
1293             $showunread = true;
1294         }
1295         if ($count > 0 || $thisunread > 0) {
1296             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1297                 $forum->name.'</a></div>';
1298             $str .= '<div class="info">';
1299             $str .= $count.' '.$strnumpostssince;
1300             if (!empty($showunread)) {
1301                 $str .= '<br />'.$thisunread .' '.$strnumunread;
1302             }
1303             $str .= '</div></div>';
1304         }
1305         if (!empty($str)) {
1306             if (!array_key_exists($forum->course,$htmlarray)) {
1307                 $htmlarray[$forum->course] = array();
1308             }
1309             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1310                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1311             }
1312             $htmlarray[$forum->course]['forum'] .= $str;
1313         }
1314     }
1317 /**
1318  * Given a course and a date, prints a summary of all the new
1319  * messages posted in the course since that date
1320  *
1321  * @global object
1322  * @global object
1323  * @global object
1324  * @uses CONTEXT_MODULE
1325  * @uses VISIBLEGROUPS
1326  * @param object $course
1327  * @param bool $viewfullnames capability
1328  * @param int $timestart
1329  * @return bool success
1330  */
1331 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1332     global $CFG, $USER, $DB, $OUTPUT;
1334     // do not use log table if possible, it may be huge and is expensive to join with other tables
1336     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1337                                               d.timestart, d.timeend, d.userid AS duserid,
1338                                               u.firstname, u.lastname, u.email, u.picture
1339                                          FROM {forum_posts} p
1340                                               JOIN {forum_discussions} d ON d.id = p.discussion
1341                                               JOIN {forum} f             ON f.id = d.forum
1342                                               JOIN {user} u              ON u.id = p.userid
1343                                         WHERE p.created > ? AND f.course = ?
1344                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1345          return false;
1346     }
1348     $modinfo =& get_fast_modinfo($course);
1350     $groupmodes = array();
1351     $cms    = array();
1353     $strftimerecent = get_string('strftimerecent');
1355     $printposts = array();
1356     foreach ($posts as $post) {
1357         if (!isset($modinfo->instances['forum'][$post->forum])) {
1358             // not visible
1359             continue;
1360         }
1361         $cm = $modinfo->instances['forum'][$post->forum];
1362         if (!$cm->uservisible) {
1363             continue;
1364         }
1365         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1367         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1368             continue;
1369         }
1371         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1372           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1373             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1374                 continue;
1375             }
1376         }
1378         $groupmode = groups_get_activity_groupmode($cm, $course);
1380         if ($groupmode) {
1381             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1382                 // oki (Open discussions have groupid -1)
1383             } else {
1384                 // separate mode
1385                 if (isguestuser()) {
1386                     // shortcut
1387                     continue;
1388                 }
1390                 if (is_null($modinfo->groups)) {
1391                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1392                 }
1394                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1395                     continue;
1396                 }
1397             }
1398         }
1400         $printposts[] = $post;
1401     }
1402     unset($posts);
1404     if (!$printposts) {
1405         return false;
1406     }
1408     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1409     echo "\n<ul class='unlist'>\n";
1411     foreach ($printposts as $post) {
1412         $subjectclass = empty($post->parent) ? ' bold' : '';
1414         echo '<li><div class="head">'.
1415                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1416                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1417              '</div>';
1418         echo '<div class="info'.$subjectclass.'">';
1419         if (empty($post->parent)) {
1420             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1421         } else {
1422             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1423         }
1424         $post->subject = break_up_long_words(format_string($post->subject, true));
1425         echo $post->subject;
1426         echo "</a>\"</div></li>\n";
1427     }
1429     echo "</ul>\n";
1431     return true;
1434 /**
1435  * Return grade for given user or all users.
1436  *
1437  * @global object
1438  * @global object
1439  * @param object $forum
1440  * @param int $userid optional user id, 0 means all users
1441  * @return array array of grades, false if none
1442  */
1443 function forum_get_user_grades($forum, $userid=0) {
1444     global $CFG;
1446     require_once($CFG->dirroot.'/rating/lib.php');
1447     $rm = new rating_manager();
1449     $ratingoptions = new stdclass();
1451     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1452     $ratingoptions->modulename = 'forum';
1453     $ratingoptions->moduleid   = $forum->id;
1454     //$ratingoptions->cmidnumber = $forum->cmidnumber;
1456     $ratingoptions->userid = $userid;
1457     $ratingoptions->aggregationmethod = $forum->assessed;
1458     $ratingoptions->scaleid = $forum->scale;
1459     $ratingoptions->itemtable = 'forum_posts';
1460     $ratingoptions->itemtableusercolumn = 'userid';
1462     return $rm->get_user_grades($ratingoptions);
1465 /**
1466  * Update activity grades
1467  *
1468  * @global object
1469  * @global object
1470  * @param object $forum
1471  * @param int $userid specific user only, 0 means all
1472  * @param boolean $nullifnone return null if grade does not exist
1473  * @return void
1474  */
1475 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1476     global $CFG, $DB;
1477     require_once($CFG->libdir.'/gradelib.php');
1479     if (!$forum->assessed) {
1480         forum_grade_item_update($forum);
1482     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1483         forum_grade_item_update($forum, $grades);
1485     } else if ($userid and $nullifnone) {
1486         $grade = new object();
1487         $grade->userid   = $userid;
1488         $grade->rawgrade = NULL;
1489         forum_grade_item_update($forum, $grade);
1491     } else {
1492         forum_grade_item_update($forum);
1493     }
1496 /**
1497  * Update all grades in gradebook.
1498  * @global object
1499  */
1500 function forum_upgrade_grades() {
1501     global $DB;
1503     $sql = "SELECT COUNT('x')
1504               FROM {forum} f, {course_modules} cm, {modules} m
1505              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1506     $count = $DB->count_records_sql($sql);
1508     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1509               FROM {forum} f, {course_modules} cm, {modules} m
1510              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1511     if ($rs = $DB->get_recordset_sql($sql)) {
1512         $pbar = new progress_bar('forumupgradegrades', 500, true);
1513         $i=0;
1514         foreach ($rs as $forum) {
1515             $i++;
1516             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1517             forum_update_grades($forum, 0, false);
1518             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1519         }
1520         $rs->close();
1521     }
1524 /**
1525  * Create/update grade item for given forum
1526  *
1527  * @global object
1528  * @uses GRADE_TYPE_NONE
1529  * @uses GRADE_TYPE_VALUE
1530  * @uses GRADE_TYPE_SCALE
1531  * @param object $forum object with extra cmidnumber
1532  * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
1533  * @return int 0 if ok
1534  */
1535 function forum_grade_item_update($forum, $grades=NULL) {
1536     global $CFG;
1537     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1538         require_once($CFG->libdir.'/gradelib.php');
1539     }
1541     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1543     if (!$forum->assessed or $forum->scale == 0) {
1544         $params['gradetype'] = GRADE_TYPE_NONE;
1546     } else if ($forum->scale > 0) {
1547         $params['gradetype'] = GRADE_TYPE_VALUE;
1548         $params['grademax']  = $forum->scale;
1549         $params['grademin']  = 0;
1551     } else if ($forum->scale < 0) {
1552         $params['gradetype'] = GRADE_TYPE_SCALE;
1553         $params['scaleid']   = -$forum->scale;
1554     }
1556     if ($grades  === 'reset') {
1557         $params['reset'] = true;
1558         $grades = NULL;
1559     }
1561     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1564 /**
1565  * Delete grade item for given forum
1566  *
1567  * @global object
1568  * @param object $forum object
1569  * @return object grade_item
1570  */
1571 function forum_grade_item_delete($forum) {
1572     global $CFG;
1573     require_once($CFG->libdir.'/gradelib.php');
1575     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1579 /**
1580  * Returns the users with data in one forum
1581  * (users with records in forum_subscriptions, forum_posts, students)
1582  *
1583  * @global object
1584  * @global object
1585  * @param int $forumid
1586  * @return mixed array or false if none
1587  */
1588 function forum_get_participants($forumid) {
1590     global $CFG, $DB;
1592     //Get students from forum_subscriptions
1593     $st_subscriptions = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1594                                          FROM {user} u,
1595                                               {forum_subscriptions} s
1596                                          WHERE s.forum = ? AND
1597                                                u.id = s.userid", array($forumid));
1598     //Get students from forum_posts
1599     $st_posts = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1600                                  FROM {user} u,
1601                                       {forum_discussions} d,
1602                                       {forum_posts} p
1603                                  WHERE d.forum = ? AND
1604                                        p.discussion = d.id AND
1605                                        u.id = p.userid", array($forumid));
1607     //Get students from the ratings table
1608     $st_ratings = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1609                                    FROM {user} u,
1610                                         {forum_discussions} d,
1611                                         {forum_posts} p,
1612                                         {ratings} r
1613                                    WHERE d.forum = ? AND
1614                                          p.discussion = d.id AND
1615                                          r.post = p.id AND
1616                                          u.id = r.userid", array($forumid));
1618     //Add st_posts to st_subscriptions
1619     if ($st_posts) {
1620         foreach ($st_posts as $st_post) {
1621             $st_subscriptions[$st_post->id] = $st_post;
1622         }
1623     }
1624     //Add st_ratings to st_subscriptions
1625     if ($st_ratings) {
1626         foreach ($st_ratings as $st_rating) {
1627             $st_subscriptions[$st_rating->id] = $st_rating;
1628         }
1629     }
1630     //Return st_subscriptions array (it contains an array of unique users)
1631     return ($st_subscriptions);
1634 /**
1635  * This function returns if a scale is being used by one forum
1636  *
1637  * @global object
1638  * @param int $forumid
1639  * @param int $scaleid negative number
1640  * @return bool
1641  */
1642 function forum_scale_used ($forumid,$scaleid) {
1643     global $DB;
1644     $return = false;
1646     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1648     if (!empty($rec) && !empty($scaleid)) {
1649         $return = true;
1650     }
1652     return $return;
1655 /**
1656  * Checks if scale is being used by any instance of forum
1657  *
1658  * This is used to find out if scale used anywhere
1659  *
1660  * @global object
1661  * @param $scaleid int
1662  * @return boolean True if the scale is used by any forum
1663  */
1664 function forum_scale_used_anywhere($scaleid) {
1665     global $DB;
1666     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1667         return true;
1668     } else {
1669         return false;
1670     }
1673 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1675 /**
1676  * Gets a post with all info ready for forum_print_post
1677  * Most of these joins are just to get the forum id
1678  *
1679  * @global object
1680  * @global object
1681  * @param int $postid
1682  * @return mixed array of posts or false
1683  */
1684 function forum_get_post_full($postid) {
1685     global $CFG, $DB;
1687     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1688                              FROM {forum_posts} p
1689                                   JOIN {forum_discussions} d ON p.discussion = d.id
1690                                   LEFT JOIN {user} u ON p.userid = u.id
1691                             WHERE p.id = ?", array($postid));
1694 /**
1695  * Gets posts with all info ready for forum_print_post
1696  * We pass forumid in because we always know it so no need to make a
1697  * complicated join to find it out.
1698  *
1699  * @global object
1700  * @global object
1701  * @return mixed array of posts or false
1702  */
1703 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1704     global $CFG, $DB;
1706     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1707                               FROM {forum_posts} p
1708                          LEFT JOIN {user} u ON p.userid = u.id
1709                              WHERE p.discussion = ?
1710                                AND p.parent > 0 $sort", array($discussion));
1713 /**
1714  * Gets all posts in discussion including top parent.
1715  *
1716  * @global object
1717  * @global object
1718  * @global object
1719  * @param int $discussionid
1720  * @param string $sort
1721  * @param bool $tracking does user track the forum?
1722  * @return array of posts
1723  */
1724 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1725     global $CFG, $DB, $USER;
1727     $tr_sel  = "";
1728     $tr_join = "";
1729     $params = array();
1731     if ($tracking) {
1732         $now = time();
1733         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1734         $tr_sel  = ", fr.id AS postread";
1735         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1736         $params[] = $USER->id;
1737     }
1739     $params[] = $discussionid;
1740     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1741                                      FROM {forum_posts} p
1742                                           LEFT JOIN {user} u ON p.userid = u.id
1743                                           $tr_join
1744                                     WHERE p.discussion = ?
1745                                  ORDER BY $sort", $params)) {
1746         return array();
1747     }
1749     foreach ($posts as $pid=>$p) {
1750         if ($tracking) {
1751             if (forum_tp_is_post_old($p)) {
1752                  $posts[$pid]->postread = true;
1753             }
1754         }
1755         if (!$p->parent) {
1756             continue;
1757         }
1758         if (!isset($posts[$p->parent])) {
1759             continue; // parent does not exist??
1760         }
1761         if (!isset($posts[$p->parent]->children)) {
1762             $posts[$p->parent]->children = array();
1763         }
1764         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1765     }
1767     return $posts;
1770 /**
1771  * Gets posts with all info ready for forum_print_post
1772  * We pass forumid in because we always know it so no need to make a
1773  * complicated join to find it out.
1774  *
1775  * @global object
1776  * @global object
1777  * @param int $parent
1778  * @param int $forumid
1779  * @return array
1780  */
1781 function forum_get_child_posts($parent, $forumid) {
1782     global $CFG, $DB;
1784     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1785                               FROM {forum_posts} p
1786                          LEFT JOIN {user} u ON p.userid = u.id
1787                              WHERE p.parent = ?
1788                           ORDER BY p.created ASC", array($parent));
1791 /**
1792  * An array of forum objects that the user is allowed to read/search through.
1793  *
1794  * @global object
1795  * @global object
1796  * @global object
1797  * @param int $userid
1798  * @param int $courseid if 0, we look for forums throughout the whole site.
1799  * @return array of forum objects, or false if no matches
1800  *         Forum objects have the following attributes:
1801  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1802  *         viewhiddentimedposts
1803  */
1804 function forum_get_readable_forums($userid, $courseid=0) {
1806     global $CFG, $DB, $USER;
1807     require_once($CFG->dirroot.'/course/lib.php');
1809     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1810         print_error('notinstalled', 'forum');
1811     }
1813     if ($courseid) {
1814         $courses = $DB->get_records('course', array('id' => $courseid));
1815     } else {
1816         // If no course is specified, then the user can see SITE + his courses.
1817         $courses1 = $DB->get_records('course', array('id' => SITEID));
1818         $courses2 = enrol_get_users_courses($userid, true);
1819         $courses = array_merge($courses1, $courses2);
1820     }
1821     if (!$courses) {
1822         return array();
1823     }
1825     $readableforums = array();
1827     foreach ($courses as $course) {
1829         $modinfo =& get_fast_modinfo($course);
1830         if (is_null($modinfo->groups)) {
1831             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1832         }
1834         if (empty($modinfo->instances['forum'])) {
1835             // hmm, no forums?
1836             continue;
1837         }
1839         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1841         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1842             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1843                 continue;
1844             }
1845             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1846             $forum = $courseforums[$forumid];
1848             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1849                 continue;
1850             }
1852          /// group access
1853             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1854                 if (is_null($modinfo->groups)) {
1855                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1856                 }
1857                 if (isset($modinfo->groups[$cm->groupingid])) {
1858                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1859                     $forum->onlygroups[] = -1;
1860                 } else {
1861                     $forum->onlygroups = array(-1);
1862                 }
1863             }
1865         /// hidden timed discussions
1866             $forum->viewhiddentimedposts = true;
1867             if (!empty($CFG->forum_enabletimedposts)) {
1868                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1869                     $forum->viewhiddentimedposts = false;
1870                 }
1871             }
1873         /// qanda access
1874             if ($forum->type == 'qanda'
1875                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1877                 // We need to check whether the user has posted in the qanda forum.
1878                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1879                                                     // the user is allowed to see in this forum.
1880                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1881                     foreach ($discussionspostedin as $d) {
1882                         $forum->onlydiscussions[] = $d->id;
1883                     }
1884                 }
1885             }
1887             $readableforums[$forum->id] = $forum;
1888         }
1890         unset($modinfo);
1892     } // End foreach $courses
1894     return $readableforums;
1897 /**
1898  * Returns a list of posts found using an array of search terms.
1899  *
1900  * @global object
1901  * @global object
1902  * @global object
1903  * @param array $searchterms array of search terms, e.g. word +word -word
1904  * @param int $courseid if 0, we search through the whole site
1905  * @param int $limitfrom
1906  * @param int $limitnum
1907  * @param int &$totalcount
1908  * @param string $extrasql
1909  * @return array|bool Array of posts found or false
1910  */
1911 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1912                             &$totalcount, $extrasql='') {
1913     global $CFG, $DB, $USER;
1914     require_once($CFG->libdir.'/searchlib.php');
1916     $forums = forum_get_readable_forums($USER->id, $courseid);
1918     if (count($forums) == 0) {
1919         $totalcount = 0;
1920         return false;
1921     }
1923     $now = round(time(), -2); // db friendly
1925     $fullaccess = array();
1926     $where = array();
1927     $params = array();
1929     foreach ($forums as $forumid => $forum) {
1930         $select = array();
1932         if (!$forum->viewhiddentimedposts) {
1933             $select[] = "(d.userid = :userid OR (d.timestart < : AND (d.timeend = 0 OR d.timeend > :timeend)))";
1934             $params = array('userid'=>$USER->id, 'timestart'=>$now, 'timeend'=>$now);
1935         }
1937         $cm = get_coursemodule_from_instance('forum', $forumid);
1938         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1940         if ($forum->type == 'qanda'
1941             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1942             if (!empty($forum->onlydiscussions)) {
1943                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda0');
1944                 $params = array_merge($params, $discussionid_params);
1945                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
1946             } else {
1947                 $select[] = "p.parent = 0";
1948             }
1949         }
1951         if (!empty($forum->onlygroups)) {
1952             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps0');
1953             $params = array_merge($params, $groupid_params);
1954             $select[] = "d.groupid $groupid_sql";
1955         }
1957         if ($select) {
1958             $selects = implode(" AND ", $select);
1959             $where[] = "(d.forum = :forum AND $selects)";
1960             $params['forum'] = $forumid;
1961         } else {
1962             $fullaccess[] = $forumid;
1963         }
1964     }
1966     if ($fullaccess) {
1967         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula0');
1968         $params = array_merge($params, $fullid_params);
1969         $where[] = "(d.forum $fullid_sql)";
1970     }
1972     $selectdiscussion = "(".implode(" OR ", $where).")";
1974     $messagesearch = '';
1975     $searchstring = '';
1977     // Need to concat these back together for parser to work.
1978     foreach($searchterms as $searchterm){
1979         if ($searchstring != '') {
1980             $searchstring .= ' ';
1981         }
1982         $searchstring .= $searchterm;
1983     }
1985     // We need to allow quoted strings for the search. The quotes *should* be stripped
1986     // by the parser, but this should be examined carefully for security implications.
1987     $searchstring = str_replace("\\\"","\"",$searchstring);
1988     $parser = new search_parser();
1989     $lexer = new search_lexer($parser);
1991     if ($lexer->parse($searchstring)) {
1992         $parsearray = $parser->get_parsed_array();
1993     // Experimental feature under 1.8! MDL-8830
1994     // Use alternative text searches if defined
1995     // This feature only works under mysql until properly implemented for other DBs
1996     // Requires manual creation of text index for forum_posts before enabling it:
1997     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
1998     // Experimental feature under 1.8! MDL-8830
1999         if (!empty($CFG->forum_usetextsearches)) {
2000             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2001                                                  'p.userid', 'u.id', 'u.firstname',
2002                                                  'u.lastname', 'p.modified', 'd.forum');
2003         } else {
2004             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2005                                                  'p.userid', 'u.id', 'u.firstname',
2006                                                  'u.lastname', 'p.modified', 'd.forum');
2007         }
2008         $params = array_merge($params, $msparams);
2009     }
2011     $fromsql = "{forum_posts} p,
2012                   {forum_discussions} d,
2013                   {user} u";
2015     $selectsql = " $messagesearch
2016                AND p.discussion = d.id
2017                AND p.userid = u.id
2018                AND $selectdiscussion
2019                    $extrasql";
2021     $countsql = "SELECT COUNT(*)
2022                    FROM $fromsql
2023                   WHERE $selectsql";
2025     $searchsql = "SELECT p.*,
2026                          d.forum,
2027                          u.firstname,
2028                          u.lastname,
2029                          u.email,
2030                          u.picture,
2031                          u.imagealt,
2032                          u.email
2033                     FROM $fromsql
2034                    WHERE $selectsql
2035                 ORDER BY p.modified DESC";
2037     $totalcount = $DB->count_records_sql($countsql, $params);
2039     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2042 /**
2043  * Returns a list of ratings for a particular post - sorted.
2044  *
2045  * @global object
2046  * @global object
2047  * @param int $postid
2048  * @param string $sort
2049  * @return array Array of ratings or false
2050  */
2051 function forum_get_ratings($context, $postid, $sort="u.firstname ASC") {
2052     global $PAGE;
2054     $options = new stdclass();
2055     $options->context = $PAGE->context;
2056     $options->itemid = $postid;
2057     $options->sort = "ORDER BY $sort";
2059     get_all_ratings_for_item($options);
2062 /**
2063  * Returns a list of all new posts that have not been mailed yet
2064  *
2065  * @global object
2066  * @global object
2067  * @param int $starttime posts created after this time
2068  * @param int $endtime posts created before this
2069  * @param int $now used for timed discussions only
2070  * @return array
2071  */
2072 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2073     global $CFG, $DB;
2075     $params = array($starttime, $endtime);
2076     if (!empty($CFG->forum_enabletimedposts)) {
2077         if (empty($now)) {
2078             $now = time();
2079         }
2080         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2081         $params[] = $now;
2082         $params[] = $now;
2083     } else {
2084         $timedsql = "";
2085     }
2087     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2088                               FROM {forum_posts} p
2089                                    JOIN {forum_discussions} d ON d.id = p.discussion
2090                              WHERE p.mailed = 0
2091                                    AND p.created >= ?
2092                                    AND (p.created < ? OR p.mailnow = 1)
2093                                    $timedsql
2094                           ORDER BY p.modified ASC", $params);
2097 /**
2098  * Marks posts before a certain time as being mailed already
2099  *
2100  * @global object
2101  * @global object
2102  * @param int $endtime
2103  * @param int $now Defaults to time()
2104  * @return bool
2105  */
2106 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2107     global $CFG, $DB;
2108     if (empty($now)) {
2109         $now = time();
2110     }
2112     if (empty($CFG->forum_enabletimedposts)) {
2113         return $DB->execute("UPDATE {forum_posts}
2114                                SET mailed = '1'
2115                              WHERE (created < ? OR mailnow = 1)
2116                                    AND mailed = 0", array($endtime));
2118     } else {
2119         return $DB->execute("UPDATE {forum_posts}
2120                                SET mailed = '1'
2121                              WHERE discussion NOT IN (SELECT d.id
2122                                                         FROM {forum_discussions} d
2123                                                        WHERE d.timestart > ?)
2124                                    AND (created < ? OR mailnow = 1)
2125                                    AND mailed = 0", array($now, $endtime));
2126     }
2129 /**
2130  * Get all the posts for a user in a forum suitable for forum_print_post
2131  *
2132  * @global object
2133  * @global object
2134  * @uses CONTEXT_MODULE
2135  * @return array
2136  */
2137 function forum_get_user_posts($forumid, $userid) {
2138     global $CFG, $DB;
2140     $timedsql = "";
2141     $params = array($forumid, $userid);
2143     if (!empty($CFG->forum_enabletimedposts)) {
2144         $cm = get_coursemodule_from_instance('forum', $forumid);
2145         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2146             $now = time();
2147             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2148             $params[] = $now;
2149             $params[] = $now;
2150         }
2151     }
2153     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2154                               FROM {forum} f
2155                                    JOIN {forum_discussions} d ON d.forum = f.id
2156                                    JOIN {forum_posts} p       ON p.discussion = d.id
2157                                    JOIN {user} u              ON u.id = p.userid
2158                              WHERE f.id = ?
2159                                    AND p.userid = ?
2160                                    $timedsql
2161                           ORDER BY p.modified ASC", $params);
2164 /**
2165  * Get all the discussions user participated in
2166  *
2167  * @global object
2168  * @global object
2169  * @uses CONTEXT_MODULE
2170  * @param int $forumid
2171  * @param int $userid
2172  * @return array Array or false
2173  */
2174 function forum_get_user_involved_discussions($forumid, $userid) {
2175     global $CFG, $DB;
2177     $timedsql = "";
2178     $params = array($forumid, $userid);
2179     if (!empty($CFG->forum_enabletimedposts)) {
2180         $cm = get_coursemodule_from_instance('forum', $forumid);
2181         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2182             $now = time();
2183             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2184             $params[] = $now;
2185             $params[] = $now;
2186         }
2187     }
2189     return $DB->get_records_sql("SELECT DISTINCT d.*
2190                               FROM {forum} f
2191                                    JOIN {forum_discussions} d ON d.forum = f.id
2192                                    JOIN {forum_posts} p       ON p.discussion = d.id
2193                              WHERE f.id = ?
2194                                    AND p.userid = ?
2195                                    $timedsql", $params);
2198 /**
2199  * Get all the posts for a user in a forum suitable for forum_print_post
2200  *
2201  * @global object
2202  * @global object
2203  * @param int $forumid
2204  * @param int $userid
2205  * @return array of counts or false
2206  */
2207 function forum_count_user_posts($forumid, $userid) {
2208     global $CFG, $DB;
2210     $timedsql = "";
2211     $params = array($forumid, $userid);
2212     if (!empty($CFG->forum_enabletimedposts)) {
2213         $cm = get_coursemodule_from_instance('forum', $forumid);
2214         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2215             $now = time();
2216             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2217             $params[] = $now;
2218             $params[] = $now;
2219         }
2220     }
2222     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2223                              FROM {forum} f
2224                                   JOIN {forum_discussions} d ON d.forum = f.id
2225                                   JOIN {forum_posts} p       ON p.discussion = d.id
2226                                   JOIN {user} u              ON u.id = p.userid
2227                             WHERE f.id = ?
2228                                   AND p.userid = ?
2229                                   $timedsql", $params);
2232 /**
2233  * Given a log entry, return the forum post details for it.
2234  *
2235  * @global object
2236  * @global object
2237  * @param object $log
2238  * @return array|null
2239  */
2240 function forum_get_post_from_log($log) {
2241     global $CFG, $DB;
2243     if ($log->action == "add post") {
2245         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2246                                            u.firstname, u.lastname, u.email, u.picture
2247                                  FROM {forum_discussions} d,
2248                                       {forum_posts} p,
2249                                       {forum} f,
2250                                       {user} u
2251                                 WHERE p.id = ?
2252                                   AND d.id = p.discussion
2253                                   AND p.userid = u.id
2254                                   AND u.deleted <> '1'
2255                                   AND f.id = d.forum", array($log->info));
2258     } else if ($log->action == "add discussion") {
2260         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2261                                            u.firstname, u.lastname, u.email, u.picture
2262                                  FROM {forum_discussions} d,
2263                                       {forum_posts} p,
2264                                       {forum} f,
2265                                       {user} u
2266                                 WHERE d.id = ?
2267                                   AND d.firstpost = p.id
2268                                   AND p.userid = u.id
2269                                   AND u.deleted <> '1'
2270                                   AND f.id = d.forum", array($log->info));
2271     }
2272     return NULL;
2275 /**
2276  * Given a discussion id, return the first post from the discussion
2277  *
2278  * @global object
2279  * @global object
2280  * @param int $dicsussionid
2281  * @return array
2282  */
2283 function forum_get_firstpost_from_discussion($discussionid) {
2284     global $CFG, $DB;
2286     return $DB->get_record_sql("SELECT p.*
2287                              FROM {forum_discussions} d,
2288                                   {forum_posts} p
2289                             WHERE d.id = ?
2290                               AND d.firstpost = p.id ", array($discussionid));
2293 /**
2294  * Returns an array of counts of replies to each discussion
2295  *
2296  * @global object
2297  * @global object
2298  * @param int $forumid
2299  * @param string $forumsort
2300  * @param int $limit
2301  * @param int $page
2302  * @param int $perpage
2303  * @return array
2304  */
2305 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2306     global $CFG, $DB;
2308     if ($limit > 0) {
2309         $limitfrom = 0;
2310         $limitnum  = $limit;
2311     } else if ($page != -1) {
2312         $limitfrom = $page*$perpage;
2313         $limitnum  = $perpage;
2314     } else {
2315         $limitfrom = 0;
2316         $limitnum  = 0;
2317     }
2319     if ($forumsort == "") {
2320         $orderby = "";
2321         $groupby = "";
2323     } else {
2324         $orderby = "ORDER BY $forumsort";
2325         $groupby = ", ".strtolower($forumsort);
2326         $groupby = str_replace('desc', '', $groupby);
2327         $groupby = str_replace('asc', '', $groupby);
2328     }
2330     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2331         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2332                   FROM {forum_posts} p
2333                        JOIN {forum_discussions} d ON p.discussion = d.id
2334                  WHERE p.parent > 0 AND d.forum = ?
2335               GROUP BY p.discussion";
2336         return $DB->get_records_sql($sql, array($forumid));
2338     } else {
2339         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2340                   FROM {forum_posts} p
2341                        JOIN {forum_discussions} d ON p.discussion = d.id
2342                  WHERE d.forum = ?
2343               GROUP BY p.discussion $groupby
2344               $orderby";
2345         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2346     }
2349 /**
2350  * @global object
2351  * @global object
2352  * @global object
2353  * @staticvar array $cache
2354  * @param object $forum
2355  * @param object $cm
2356  * @param object $course
2357  * @return mixed
2358  */
2359 function forum_count_discussions($forum, $cm, $course) {
2360     global $CFG, $DB, $USER;
2362     static $cache = array();
2364     $now = round(time(), -2); // db cache friendliness
2366     $params = array($course->id);
2368     if (!isset($cache[$course->id])) {
2369         if (!empty($CFG->forum_enabletimedposts)) {
2370             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2371             $params[] = $now;
2372             $params[] = $now;
2373         } else {
2374             $timedsql = "";
2375         }
2377         $sql = "SELECT f.id, COUNT(d.id) as dcount
2378                   FROM {forum} f
2379                        JOIN {forum_discussions} d ON d.forum = f.id
2380                  WHERE f.course = ?
2381                        $timedsql
2382               GROUP BY f.id";
2384         if ($counts = $DB->get_records_sql($sql, $params)) {
2385             foreach ($counts as $count) {
2386                 $counts[$count->id] = $count->dcount;
2387             }
2388             $cache[$course->id] = $counts;
2389         } else {
2390             $cache[$course->id] = array();
2391         }
2392     }
2394     if (empty($cache[$course->id][$forum->id])) {
2395         return 0;
2396     }
2398     $groupmode = groups_get_activity_groupmode($cm, $course);
2400     if ($groupmode != SEPARATEGROUPS) {
2401         return $cache[$course->id][$forum->id];
2402     }
2404     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2405         return $cache[$course->id][$forum->id];
2406     }
2408     require_once($CFG->dirroot.'/course/lib.php');
2410     $modinfo =& get_fast_modinfo($course);
2411     if (is_null($modinfo->groups)) {
2412         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2413     }
2415     if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2416         $mygroups = $modinfo->groups[$cm->groupingid];
2417     } else {
2418         $mygroups = false; // Will be set below
2419     }
2421     // add all groups posts
2422     if (empty($mygroups)) {
2423         $mygroups = array(-1=>-1);
2424     } else {
2425         $mygroups[-1] = -1;
2426     }
2428     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2429     $params[] = $forum->id;
2431     if (!empty($CFG->forum_enabletimedposts)) {
2432         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2433         $params[] = $now;
2434         $params[] = $now;
2435     } else {
2436         $timedsql = "";
2437     }
2439     $sql = "SELECT COUNT(d.id)
2440               FROM {forum_discussions} d
2441              WHERE d.groupid $mygroups_sql AND d.forum = ?
2442                    $timedsql";
2444     return $DB->get_field_sql($sql, $params);
2447 /**
2448  * How many posts by other users are unrated by a given user in the given discussion?
2449  *
2450  * @global object
2451  * @global object
2452  * @param int $discussionid
2453  * @param int $userid
2454  * @return mixed
2455  */
2456 function forum_count_unrated_posts($discussionid, $userid) {
2457     global $CFG, $DB;
2458     if ($posts = $DB->get_record_sql("SELECT count(*) as num
2459                                    FROM {forum_posts}
2460                                   WHERE parent > 0
2461                                     AND discussion = ?
2462                                     AND userid <> ? ", array($discussionid, $userid))) {
2464         if ($rated = $DB->get_record_sql("SELECT count(*) as num
2465                                        FROM {forum_posts} p,
2466                                             {rating} r
2467                                       WHERE p.discussion = ?
2468                                         AND p.id = r.itemid
2469                                         AND r.userid = ?", array($discussionid, $userid))) {
2470             $difference = $posts->num - $rated->num;
2471             if ($difference > 0) {
2472                 return $difference;
2473             } else {
2474                 return 0;    // Just in case there was a counting error
2475             }
2476         } else {
2477             return $posts->num;
2478         }
2479     } else {
2480         return 0;
2481     }
2484 /**
2485  * Get all discussions in a forum
2486  *
2487  * @global object
2488  * @global object
2489  * @global object
2490  * @uses CONTEXT_MODULE
2491  * @uses VISIBLEGROUPS
2492  * @param object $cm
2493  * @param string $forumsort
2494  * @param bool $fullpost
2495  * @param int $unused
2496  * @param int $limit
2497  * @param bool $userlastmodified
2498  * @param int $page
2499  * @param int $perpage
2500  * @return array
2501  */
2502 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2503     global $CFG, $DB, $USER;
2505     $timelimit = '';
2507     $now = round(time(), -2);
2508     $params = array($cm->instance);
2510     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2512     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2513         return array();
2514     }
2516     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2518         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2519             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2520             $params[] = $now;
2521             $params[] = $now;
2522             if (isloggedin()) {
2523                 $timelimit .= " OR d.userid = ?";
2524                 $params[] = $USER->id;
2525             }
2526             $timelimit .= ")";
2527         }
2528     }
2530     if ($limit > 0) {
2531         $limitfrom = 0;
2532         $limitnum  = $limit;
2533     } else if ($page != -1) {
2534         $limitfrom = $page*$perpage;
2535         $limitnum  = $perpage;
2536     } else {
2537         $limitfrom = 0;
2538         $limitnum  = 0;
2539     }
2541     $groupmode    = groups_get_activity_groupmode($cm);
2542     $currentgroup = groups_get_activity_group($cm);
2544     if ($groupmode) {
2545         if (empty($modcontext)) {
2546             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2547         }
2549         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2550             if ($currentgroup) {
2551                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2552                 $params[] = $currentgroup;
2553             } else {
2554                 $groupselect = "";
2555             }
2557         } else {
2558             //seprate groups without access all
2559             if ($currentgroup) {
2560                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2561                 $params[] = $currentgroup;
2562             } else {
2563                 $groupselect = "AND d.groupid = -1";
2564             }
2565         }
2566     } else {
2567         $groupselect = "";
2568     }
2571     if (empty($forumsort)) {
2572         $forumsort = "d.timemodified DESC";
2573     }
2574     if (empty($fullpost)) {
2575         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2576     } else {
2577         $postdata = "p.*";
2578     }
2580     if (empty($userlastmodified)) {  // We don't need to know this
2581         $umfields = "";
2582         $umtable  = "";
2583     } else {
2584         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2585         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2586     }
2588     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2589                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2590               FROM {forum_discussions} d
2591                    JOIN {forum_posts} p ON p.discussion = d.id
2592                    JOIN {user} u ON p.userid = u.id
2593                    $umtable
2594              WHERE d.forum = ? AND p.parent = 0
2595                    $timelimit $groupselect
2596           ORDER BY $forumsort";
2597     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2600 /**
2601  *
2602  * @global object
2603  * @global object
2604  * @global object
2605  * @uses CONTEXT_MODULE
2606  * @uses VISIBLEGROUPS
2607  * @param object $cm
2608  * @return array
2609  */
2610 function forum_get_discussions_unread($cm) {
2611     global $CFG, $DB, $USER;
2613     $now = round(time(), -2);
2614     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2616     $params = array();
2617     $groupmode    = groups_get_activity_groupmode($cm);
2618     $currentgroup = groups_get_activity_group($cm);
2620     if ($groupmode) {
2621         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2623         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2624             if ($currentgroup) {
2625                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2626                 $params['currentgroup'] = $currentgroup;
2627             } else {
2628                 $groupselect = "";
2629             }
2631         } else {
2632             //separate groups without access all
2633             if ($currentgroup) {
2634                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2635                 $params['currentgroup'] = $currentgroup;
2636             } else {
2637                 $groupselect = "AND d.groupid = -1";
2638             }
2639         }
2640     } else {
2641         $groupselect = "";
2642     }
2644     if (!empty($CFG->forum_enabletimedposts)) {
2645         $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2646         $params['now1'] = $now;
2647         $params['now2'] = $now;
2648     } else {
2649         $timedsql = "";
2650     }
2652     $sql = "SELECT d.id, COUNT(p.id) AS unread
2653               FROM {forum_discussions} d
2654                    JOIN {forum_posts} p     ON p.discussion = d.id
2655                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2656              WHERE d.forum = {$cm->instance}
2657                    AND p.modified >= :cutoffdate AND r.id is NULL
2658                    $groupselect
2659                    $timedsql
2660           GROUP BY d.id";
2661     $params['cutoffdate'] = $cutoffdate;
2663     if ($unreads = $DB->get_records_sql($sql, $params)) {
2664         foreach ($unreads as $unread) {
2665             $unreads[$unread->id] = $unread->unread;
2666         }
2667         return $unreads;
2668     } else {
2669         return array();
2670     }
2673 /**
2674  * @global object
2675  * @global object
2676  * @global object
2677  * @uses CONEXT_MODULE
2678  * @uses VISIBLEGROUPS
2679  * @param object $cm
2680  * @return array
2681  */
2682 function forum_get_discussions_count($cm) {
2683     global $CFG, $DB, $USER;
2685     $now = round(time(), -2);
2686     $params = array($cm->instance);
2687     $groupmode    = groups_get_activity_groupmode($cm);
2688     $currentgroup = groups_get_activity_group($cm);
2690     if ($groupmode) {
2691         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2693         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2694             if ($currentgroup) {
2695                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2696                 $params[] = $currentgroup;
2697             } else {
2698                 $groupselect = "";
2699             }
2701         } else {
2702             //seprate groups without access all
2703             if ($currentgroup) {
2704                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2705                 $params[] = $currentgroup;
2706             } else {
2707                 $groupselect = "AND d.groupid = -1";
2708             }
2709         }
2710     } else {
2711         $groupselect = "";
2712     }
2714     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2716     $timelimit = "";
2718     if (!empty($CFG->forum_enabletimedposts)) {
2720         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2722         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2723             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2724             $params[] = $now;
2725             $params[] = $now;
2726             if (isloggedin()) {
2727                 $timelimit .= " OR d.userid = ?";
2728                 $params[] = $USER->id;
2729             }
2730             $timelimit .= ")";
2731         }
2732     }
2734     $sql = "SELECT COUNT(d.id)
2735               FROM {forum_discussions} d
2736                    JOIN {forum_posts} p ON p.discussion = d.id
2737              WHERE d.forum = ? AND p.parent = 0
2738                    $groupselect $timelimit";
2740     return $DB->get_field_sql($sql, $params);
2744 /**
2745  * Get all discussions started by a particular user in a course (or group)
2746  * This function no longer used ...
2747  *
2748  * @todo Remove this function if no longer used
2749  * @global object
2750  * @global object
2751  * @param int $courseid
2752  * @param int $userid
2753  * @param int $groupid
2754  * @return array
2755  */
2756 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2757     global $CFG, $DB;
2758     $params = array($courseid, $userid);
2759     if ($groupid) {
2760         $groupselect = " AND d.groupid = ? ";
2761         $params[] = $groupid;
2762     } else  {
2763         $groupselect = "";
2764     }
2766     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2767                                    f.type as forumtype, f.name as forumname, f.id as forumid
2768                               FROM {forum_discussions} d,
2769                                    {forum_posts} p,
2770                                    {user} u,
2771                                    {forum} f
2772                              WHERE d.course = ?
2773                                AND p.discussion = d.id
2774                                AND p.parent = 0
2775                                AND p.userid = u.id
2776                                AND u.id = ?
2777                                AND d.forum = f.id $groupselect
2778                           ORDER BY p.created DESC", $params);
2781 /**
2782  * Get the list of potential subscribers to a forum.
2783  *
2784  * @param object $forumcontext the forum context.
2785  * @param integer $groupid the id of a group, or 0 for all groups.
2786  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2787  * @param string $sort sort order. As for get_users_by_capability.
2788  * @return array list of users.
2789  */
2790 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2791     return get_users_by_capability($forumcontext, 'mod/forum:initialsubscriptions', $fields, $sort, '', '', $groupid, '', false, true);
2794 /**
2795  * Returns list of user objects that are subscribed to this forum
2796  *
2797  * @global object
2798  * @global object
2799  * @param object $course the course
2800  * @param forum $forum the forum
2801  * @param integer $groupid group id, or 0 for all.
2802  * @param object $context the forum context, to save re-fetching it where possible.
2803  * @return array list of users.
2804  */
2805 function forum_subscribed_users($course, $forum, $groupid=0, $context = NULL) {
2806     global $CFG, $DB;
2807     $params = array($forum->id);
2809     if ($groupid) {
2810         $grouptables = ", {groups_members} gm ";
2811         $groupselect = "AND gm.groupid = ? AND u.id = gm.userid";
2812         $params[] = $groupid;
2813     } else  {
2814         $grouptables = '';
2815         $groupselect = '';
2816     }
2818     $fields ="u.id,
2819               u.username,
2820               u.firstname,
2821               u.lastname,
2822               u.maildisplay,
2823               u.mailformat,
2824               u.maildigest,
2825               u.emailstop,
2826               u.imagealt,
2827               u.email,
2828               u.city,
2829               u.country,
2830               u.lastaccess,
2831               u.lastlogin,
2832               u.picture,
2833               u.timezone,
2834               u.theme,
2835               u.lang,
2836               u.trackforums,
2837               u.mnethostid";
2839     if (forum_is_forcesubscribed($forum)) {
2840         if (empty($context)) {
2841             $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2842             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2843         }
2844         $sort = "u.email ASC";
2845         $results = forum_get_potential_subscribers($context, $groupid, $fields, $sort);
2846     } else {
2847         $results = $DB->get_records_sql("SELECT $fields
2848                               FROM {user} u,
2849                                    {forum_subscriptions} s $grouptables
2850                              WHERE s.forum = ?
2851                                AND s.userid = u.id
2852                                AND u.deleted = 0  $groupselect
2853                           ORDER BY u.email ASC", $params);
2854     }
2856     static $guestid = null;
2858     if (is_null($guestid)) {
2859         if ($guest = guest_user()) {
2860             $guestid = $guest->id;
2861         } else {
2862             $guestid = 0;
2863         }
2864     }
2866     // Guest user should never be subscribed to a forum.
2867     unset($results[$guestid]);
2869     return $results;
2874 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2877 /**
2878  * @global object
2879  * @global object
2880  * @param int $courseid
2881  * @param string $type
2882  */
2883 function forum_get_course_forum($courseid, $type) {
2884 // How to set up special 1-per-course forums
2885     global $CFG, $DB, $OUTPUT;
2887     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2888         // There should always only be ONE, but with the right combination of
2889         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2890         foreach ($forums as $forum) {
2891             return $forum;   // ie the first one
2892         }
2893     }
2895     // Doesn't exist, so create one now.
2896     $forum->course = $courseid;
2897     $forum->type = "$type";
2898     switch ($forum->type) {
2899         case "news":
2900             $forum->name  = get_string("namenews", "forum");
2901             $forum->intro = get_string("intronews", "forum");
2902             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2903             $forum->assessed = 0;
2904             if ($courseid == SITEID) {
2905                 $forum->name  = get_string("sitenews");
2906                 $forum->forcesubscribe = 0;
2907             }
2908             break;
2909         case "social":
2910             $forum->name  = get_string("namesocial", "forum");
2911             $forum->intro = get_string("introsocial", "forum");
2912             $forum->assessed = 0;
2913             $forum->forcesubscribe = 0;
2914             break;
2915         case "blog":
2916             $forum->name = get_string('blogforum', 'forum');
2917             $forum->intro = get_string('introblog', 'forum');
2918             $forum->assessed = 0;
2919             $forum->forcesubscribe = 0;
2920             break;
2921         default:
2922             echo $OUTPUT->notification("That forum type doesn't exist!");
2923             return false;
2924             break;
2925     }
2927     $forum->timemodified = time();
2928     $forum->id = $DB->insert_record("forum", $forum);
2930     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2931         echo $OUTPUT->notification("Could not find forum module!!");
2932         return false;
2933     }
2934     $mod = new object();
2935     $mod->course = $courseid;
2936     $mod->module = $module->id;
2937     $mod->instance = $forum->id;
2938     $mod->section = 0;
2939     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
2940         echo $OUTPUT->notification("Could not add a new course module to the course '" . format_string($course->fullname) . "'");
2941         return false;
2942     }
2943     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
2944         echo $OUTPUT->notification("Could not add the new course module to that section");
2945         return false;
2946     }
2947     $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule));
2949     include_once("$CFG->dirroot/course/lib.php");
2950     rebuild_course_cache($courseid);
2952     return $DB->get_record("forum", array("id" => "$forum->id"));
2956 /**
2957  * Given the data about a posting, builds up the HTML to display it and
2958  * returns the HTML in a string.  This is designed for sending via HTML email.
2959  *
2960  * @global object
2961  * @param object $course
2962  * @param object $cm
2963  * @param object $forum
2964  * @param object $discussion
2965  * @param object $post
2966  * @param object $userform
2967  * @param object $userto
2968  * @param bool $ownpost
2969  * @param bool $reply
2970  * @param bool $link
2971  * @param bool $rate
2972  * @param string $footer
2973  * @return string
2974  */
2975 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
2976                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
2978     global $CFG, $OUTPUT;
2980     if (!isset($userto->viewfullnames[$forum->id])) {
2981         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
2982             print_error('invalidcoursemodule');
2983         }
2984         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2985         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
2986     } else {
2987         $viewfullnames = $userto->viewfullnames[$forum->id];
2988     }
2990     // format the post body
2991     $options = new object();
2992     $options->para = true;
2993     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
2995     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
2997     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
2998     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
2999     $output .= '</td>';
3001     if ($post->parent) {
3002         $output .= '<td class="topic">';
3003     } else {
3004         $output .= '<td class="topic starter">';
3005     }
3006     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3008     $fullname = fullname($userfrom, $viewfullnames);
3009     $by = new object();
3010     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3011     $by->date = userdate($post->modified, '', $userto->timezone);
3012     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3014     $output .= '</td></tr>';
3016     $output .= '<tr><td class="left side" valign="top">';
3018     if (isset($userfrom->groups)) {
3019         $groups = $userfrom->groups[$forum->id];
3020     } else {
3021         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
3022             print_error('invalidcoursemodule');
3023         }
3024         $group = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3025     }
3027     if ($groups) {
3028         $output .= print_group_picture($groups, $course->id, false, true, true);
3029     } else {
3030         $output .= '&nbsp;';
3031     }
3033     $output .= '</td><td class="content">';
3035     $attachments = forum_print_attachments($post, $cm, 'html');
3036     if ($attachments !== '') {
3037         $output .= '<div class="attachments">';
3038         $output .= $attachments;
3039         $output .= '</div>';
3040     }
3042     $output .= $formattedtext;
3044 // Commands
3045     $commands = array();
3047     if ($post->parent) {
3048         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3049                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3050     }
3052     if ($reply) {
3053         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3054                       get_string('reply', 'forum').'</a>';
3055     }
3057     $output .= '<div class="commands">';
3058     $output .= implode(' | ', $commands);
3059     $output .= '</div>';
3061 // Context link to post if required
3062     if ($link) {
3063         $output .= '<div class="link">';
3064         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3065                      get_string('postincontext', 'forum').'</a>';
3066         $output .= '</div>';
3067     }
3069     if ($footer) {
3070         $output .= '<div class="footer">'.$footer.'</div>';
3071     }
3072     $output .= '</td></tr></table>'."\n\n";
3074     return $output;
3077 /**
3078  * Print a forum post
3079  *
3080  * @global object
3081  * @global object
3082  * @uses FORUM_MODE_THREADED
3083  * @uses PORTFOLIO_FORMAT_PLAINHTML
3084  * @uses PORTFOLIO_FORMAT_FILE
3085  * @uses PORTFOLIO_FORMAT_RICHHTML
3086  * @uses PORTFOLIO_ADD_TEXT_LINK
3087  * @uses CONTEXT_MODULE
3088  * @param object $post The post to print.
3089  * @param object $discussion
3090  * @param object $forum
3091  * @param object $cm
3092  * @param object $course
3093  * @param boolean $ownpost Whether this post belongs to the current user.
3094  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3095  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3096  * @param string $footer Extra stuff to print after the message.
3097  * @param string $highlight Space-separated list of terms to highlight.
3098  * @param int $post_read true, false or -99. If we already know whether this user
3099  *          has read this post, pass that in, otherwise, pass in -99, and this
3100  *          function will work it out.
3101  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3102  *          the current user can't see this post, if this argument is true
3103  *          (the default) then print a dummy 'you can't see this post' post.
3104  *          If false, don't output anything at all.
3105  * @param bool|null $istracked
3106  * @return void
3107  */
3108 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3109                           $footer="", $highlight="", $post_read=null, $dummyifcantsee=true, $istracked=null) {
3111     global $USER, $CFG, $OUTPUT;
3113     static $stredit, $strdelete, $strreply, $strparent, $strprune;
3114     static $strpruneheading, $displaymode;
3115     static $strmarkread, $strmarkunread;
3117     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3119     $post->course = $course->id;
3120     $post->forum  = $forum->id;
3121     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3123     // caching
3124     if (!isset($cm->cache)) {
3125         $cm->cache = new object();
3126     }
3128     if (!isset($cm->cache->caps)) {
3129         $cm->cache->caps = array();
3130         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3131         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3132         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3133         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3134         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3135         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3136         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3137         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3138         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3139     }
3141     if (!isset($cm->uservisible)) {
3142         $cm->uservisible = coursemodule_visible_for_user($cm);
3143     }
3145     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3146         if (!$dummyifcantsee) {
3147             return;
3148         }
3149         echo '<a id="p'.$post->id.'"></a>';
3150         echo '<table cellspacing="0" class="forumpost">';
3151         echo '<tr class="header"><td class="picture left">';
3152         //        print_user_picture($post->userid, $courseid, $post->picture);
3153         echo '</td>';
3154         if ($post->parent) {
3155             echo '<td class="topic">';
3156         } else {
3157             echo '<td class="topic starter">';
3158         }
3159         echo '<div class="subject">'.get_string('forumsubjecthidden','forum').'</div>';
3160         echo '<div class="author">';
3161         print_string('forumauthorhidden','forum');
3162         echo '</div></td></tr>';
3164         echo '<tr><td class="left side">';
3165         echo '&nbsp;';
3167         // Actual content
3169         echo '</td><td class="content">'."\n";
3170         echo get_string('forumbodyhidden','forum');
3171         echo '</td></tr></table>';
3172         return;
3173     }
3175     if (empty($stredit)) {
3176         $stredit         = get_string('edit', 'forum');
3177         $strdelete       = get_string('delete', 'forum');
3178         $strreply        = get_string('reply', 'forum');
3179         $strparent       = get_string('parent', 'forum');
3180         $strpruneheading = get_string('pruneheading', 'forum');
3181         $strprune        = get_string('prune', 'forum');
3182         $displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3183         $strmarkread     = get_string('markread', 'forum');
3184         $strmarkunread   = get_string('markunread', 'forum');
3186     }
3188     $read_style = '';
3189     // ignore trackign status if not tracked or tracked param missing
3190     if ($istracked) {
3191         if (is_null($post_read)) {
3192             debugging('fetching post_read info');
3193             $post_read = forum_tp_is_post_read($USER->id, $post);
3194         }
3196         if ($post_read) {
3197             $read_style = ' read';
3198         } else {
3199             $read_style = ' unread';
3200             echo '<a name="unread"></a>';
3201         }
3202     }
3204     echo '<a id="p'.$post->id.'"></a>';
3205     echo '<table cellspacing="0" class="forumpost'.$read_style.'">';
3207     // Picture
3208     $postuser = new object();
3209     $postuser->id        = $post->userid;
3210     $postuser->firstname = $post->firstname;
3211     $postuser->lastname  = $post->lastname;
3212     $postuser->imagealt  = $post->imagealt;
3213     $postuser->picture   = $post->picture;
3214     $postuser->email     = $post->email;
3216     echo '<tr class="header"><td class="picture left">';
3217     echo $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3218     echo '</td>';
3220     if ($post->parent) {
3221         echo '<td class="topic">';
3222     } else {
3223         echo '<td class="topic starter">';
3224     }
3226     if (!empty($post->subjectnoformat)) {
3227         echo '<div class="subject">'.$post->subject.'</div>';
3228     } else {
3229         echo '<div class="subject">'.format_string($post->subject).'</div>';
3230     }
3232     echo '<div class="author">';
3233     $fullname = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3234     $by = new object();
3235     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.
3236                 $post->userid.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3237     $by->date = userdate($post->modified);
3238     print_string('bynameondate', 'forum', $by);
3239     echo '</div></td></tr>';
3241     echo '<tr><td class="left side">';
3242     if (isset($cm->cache->usersgroups)) {
3243         $groups = array();
3244         if (isset($cm->cache->usersgroups[$post->userid])) {
3245             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3246                 $groups[$gid] = $cm->cache->groups[$gid];
3247             }
3248         }
3249     } else {
3250         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3251     }
3253     if ($groups) {
3254         print_group_picture($groups, $course->id, false, false, true);
3255     } else {
3256         echo '&nbsp;';
3257     }
3259 // Actual content
3261     echo '</td><td class="content">'."\n";
3263     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3265     if ($attachments !== '') {
3266         echo '<div class="attachments">';
3267         echo $attachments;
3268         echo '</div>';
3269     }
3271     $options = new object();
3272     $options->para    = false;
3273     $options->trusted = $post->messagetrust;
3274     if ($link and (strlen(strip_tags($post->message)) > $CFG->forum_longpost)) {
3275         // Print shortened version
3276         echo format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3277         $numwords = count_words(strip_tags($post->message));
3278         echo '<div class="posting"><a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3279         echo get_string('readtherest', 'forum');
3280         echo '</a> ('.get_string('numwords', '', $numwords).')...</div>';
3281     } else {
3282         // Print whole message
3283         echo '<div class="posting">';
3284         if ($highlight) {
3285             echo highlight($highlight, format_text($post->message, $post->messageformat, $options, $course->id));
3286         } else {
3287             echo format_text($post->message, $post->messageformat, $options, $course->id);
3288         }
3289         echo '</div>';
3290         echo $attachedimages;
3291     }
3294 // Commands
3296     $commands = array();
3298     if ($istracked) {
3299         // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3300         // Don't display the mark read / unread controls in this case.
3301         if ($CFG->forum_usermarksread and isloggedin()) {
3302             if ($post_read) {
3303                 $mcmd = '&amp;mark=unread&amp;postid='.$post->id;
3304                 $mtxt = $strmarkunread;
3305             } else {
3306                 $mcmd = '&amp;mark=read&amp;postid='.$post->id;
3307                 $mtxt = $strmarkread;
3308             }
3309             if ($displaymode == FORUM_MODE_THREADED) {
3310                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3311                               $post->discussion.'&amp;parent='.$post->id.$mcmd.'">'.$mtxt.'</a>';
3312             } else {
3313                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3314                               $post->discussion.$mcmd.'#p'.$post->id.'">'.$mtxt.'</a>';
3315             }
3316         }
3317     }
3319     if ($post->parent) {  // Zoom in to the parent specifically
3320         if ($displaymode == FORUM_MODE_THREADED) {
3321             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3322                       $post->discussion.'&amp;parent='.$post->parent.'">'.$strparent.'</a>';
3323         } else {
3324             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3325                       $post->discussion.'#p'.$post->parent.'">'.$strparent.'</a>';
3326         }
3327     }
3329     $age = time() - $post->created;
3330     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3331     if (!$post->parent and $forum->type == 'news' and $discussion->timestart > time()) {
3332         $age = 0;
3333     }
3334     $editanypost = $cm->cache->caps['mod/forum:editanypost'];
3336     if ($ownpost or $editanypost) {
3337         if (($age < $CFG->maxeditingtime) or $editanypost) {
3338             $commands[] =  '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?edit='.$post->id.'">'.$stredit.'</a>';
3339         }
3340     }
3342     if ($cm->cache->caps['mod/forum:splitdiscussions']
3343                 && $post->parent && $forum->type != 'single') {
3345         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?prune='.$post->id.
3346                       '" title="'.$strpruneheading.'">'.$strprune.'</a>';
3347     }
3349     if (($ownpost and $age < $CFG->maxeditingtime
3350                 and $cm->cache->caps['mod/forum:deleteownpost'])
3351                 or $cm->cache->caps['mod/forum:deleteanypost']) {
3352         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?delete='.$post->id.'">'.$strdelete.'</a>';
3353     }
3355     if ($reply) {
3356         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.$strreply.'</a>';
3357     }
3359     if ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost'])) {
3360         $p = array(
3361             'postid' => $post->id,
3362         );
3363         require_once($CFG->libdir.'/portfoliolib.php');
3364         $button = new portfolio_add_button();
3365         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php');
3366         if (empty($attachments)) {
3367             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3368         } else {
3369             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3370         }
3372         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3373         if (!empty($porfoliohtml)) {
3374             $commands[] = $porfoliohtml;
3375         }
3376     }
3378     echo '<div class="commands">';
3379     echo implode(' | ', $commands);
3380     echo '</div>';
3383 // Ratings
3384     if (!empty($post->rating)) {
3385         echo $OUTPUT->render($post->rating);
3386     }
3388 // Link to post if required
3390     if ($link) {
3391         echo '<div class="link">';
3392         if ($post->replies == 1) {
3393             $replystring = get_string('repliesone', 'forum', $post->replies);
3394         } else {
3395             $replystring = get_string('repliesmany', 'forum', $post->replies);
3396         }
3397         echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.
3398              get_string('discussthistopic', 'forum').'</a>&nbsp;('.$replystring.')';
3399         echo '</div>';
3400     }
3402     if ($footer) {
3403         echo '<div class="footer">'.$footer.'</div>';
3404     }
3405     echo '</td></tr></table>'."\n\n";
3407     if ($istracked && !$CFG->forum_usermarksread && !$post_read) {
3408         forum_tp_mark_post_read($USER->id, $post, $forum->id);
3409     }
3412 /**
3413  * Return rating related permissions
3414  * @param string $options the context id
3415  * @return array an associative array of the user's rating permissions
3416  */
3417 function forum_rating_permissions($contextid) {
3418     $context = get_context_instance_by_id($contextid);
3420     if (!$context) {
3421         print_error('invalidcontext');
3422         return null;
3423     } else {
3424         return array('view'=>has_capability('mod/forum:viewrating',$context), 'viewany'=>has_capability('mod/forum:viewanyrating',$context), 'viewall'=>has_capability('mod/forum:viewallratings',$context), 'rate'=>has_capability('mod/forum:rate',$context));
3425     }
3428 /**
3429  * Returns the names of the table and columns necessary to check items for ratings
3430  * @return array an array containing the item table, item id and user id columns
3431  */
3432 function forum_rating_item_check_info() {
3433     return array('forum_posts','id','userid');
3437 /**
3438  * This function prints the overview of a discussion in the forum listing.
3439  * It needs some discussion information and some post information, these
3440  * happen to be combined for efficiency in the $post parameter by the function
3441  * that calls this one: forum_print_latest_discussions()
3442  *
3443  * @global object
3444  * @global object
3445  * @param object $post The post object (passed by reference for speed).
3446  * @param object $forum The forum object.
3447  * @param int $group Current group.
3448  * @param string $datestring Format to use for the dates.
3449  * @param boolean $cantrack Is tracking enabled for this forum.
3450  * @param boolean $forumtracked Is the user tracking this forum.
3451  * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
3452  */
3453 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
3454                                         $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) {
3456     global $USER, $CFG, $OUTPUT;
3458     static $rowcount;
3459     static $strmarkalldread;
3461     if (empty($modcontext)) {
3462         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3463             print_error('invalidcoursemodule');
3464         }
3465         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3466     }
3468     if (!isset($rowcount)) {
3469         $rowcount = 0;
3470         $strmarkalldread = get_string('markalldread', 'forum');
3471     } else {
3472         $rowcount = ($rowcount + 1) % 2;
3473     }
3475     $post->subject = format_string($post->subject,true);
3477     echo "\n\n";
3478     echo '<tr class="discussion r'.$rowcount.'">';
3480     // Topic
3481     echo '<td class="topic starter">';
3482     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.$post->subject.'</a>';
3483     echo "</td>\n";
3485     // Picture
3486     $postuser = new object;
3487     $postuser->id = $post->userid;
3488     $postuser->firstname = $post->firstname;
3489     $postuser->lastname = $post->lastname;
3490     $postuser->imagealt = $post->imagealt;
3491     $postuser->picture = $post->picture;
3492     $postuser->email = $post->email;
3494     echo '<td class="picture">';
3495     echo $OUTPUT->user_picture($postuser, array('courseid'=>$forum->course));
3496     echo "</td>\n";
3498     // User name
3499     $fullname = fullname($post, has_capability('moodle/site:viewfullnames', $modcontext));
3500     echo '<td class="author">';
3501     echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->userid.'&amp;course='.$forum->course.'">'.$fullname.'</a>';
3502     echo "</td>\n";
3504     // Group picture
3505     if ($group !== -1) {  // Groups are active - group is a group data object or NULL
3506         echo '<td class="picture group">';
3507         if (!empty($group->picture) and empty($group->hidepicture)) {
3508             print_group_picture($group, $forum->course, false, false, true);
3509         } else if (isset($group->id)) {
3510             if($canviewparticipants) {
3511                 echo '<a href="'.$CFG->wwwroot.'/user/index.php?id='.$forum->course.'&amp;group='.$group->id.'">'.$group->name.'</a>';
3512             } else {
3513                 echo $group->name;
3514             }
3515         }
3516         echo "</td>\n";
3517     }
3519     if (has_capability('mod/forum:viewdiscussion', $modcontext)) {   // Show the column with replies
3520         echo '<td class="replies">';
3521         echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3522         echo $post->replies.'</a>';
3523         echo "</td>\n";
3525         if ($cantrack) {
3526             echo '<td class="replies">';
3527             if ($forumtracked) {
3528                 if ($post->unread > 0) {
3529                     echo '<span class="unread">';
3530                     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#unread">';
3531                     echo $post->unread;
3532                     echo '</a>';
3533                     echo '<a title="'.$strmarkalldread.'" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
3534                          $forum->id.'&amp;d='.$post->discussion.'&amp;mark=read&amp;returnpage=view.php">' .
3535                          '<img src="'.$OUTPUT->pix_url('t/clear') . '" class="iconsmall" alt="'.$strmarkalldread.'" /></a>';
3536                     echo '</span>';
3537                 } else {
3538                     echo '<span class="read">';
3539                     echo $post->unread;
3540                     echo '</span>';
3541                 }
3542             } else {
3543                 echo '<span class="read">';
3544                 echo '-';
3545                 echo '</span>';
3546             }
3547             echo "</td>\n";
3548         }
3549     }
3551     echo '<td class="lastpost">';
3552     $usedate = (empty($post->timemodified)) ? $post->modified : $post->timemodified;  // Just in case
3553     $parenturl = (empty($post->lastpostid)) ? '' : '&amp;parent='.$post->lastpostid;
3554     $usermodified = new object();
3555     $usermodified->id        = $post->usermodified;
3556     $usermodified->firstname = $post->umfirstname;
3557     $usermodified->lastname  = $post->umlastname;
3558     echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->usermodified.'&amp;course='.$forum->course.'">'.
3559          fullname($usermodified).'</a><br />';
3560     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.$parenturl.'">'.
3561           userdate($usedate, $datestring).'</a>';
3562     echo "</td>\n";
3564     echo "</tr>\n\n";
3569 /**
3570  * Given a post object that we already know has a long message
3571  * this function truncates the message nicely to the first
3572  * sane place between $CFG->forum_longpost and $CFG->forum_shortpost
3573  *
3574  * @global object
3575  * @param string $message
3576  * @return string
3577  */
3578 function forum_shorten_post($message) {
3580    global $CFG;
3582    $i = 0;
3583    $tag = false;
3584    $length = strlen($message);
3585    $count = 0;
3586    $stopzone = false;
3587    $truncate = 0;
3589    for ($i=0; $i<$length; $i++) {
3590        $char = $message[$i];
3592        switch ($char) {
3593            case "<":
3594                $tag = true;
3595                break;
3596            case ">":
3597                $tag = false;
3598                break;
3599            default:
3600                if (!$tag) {
3601                    if ($stopzone) {
3602                        if ($char == ".") {
3603                            $truncate = $i+1;
3604                            break 2;
3605                        }
3606                    }
3607                    $count++;
3608                }
3609                break;
3610        }
3611        if (!$stopzone) {
3612            if ($count > $CFG->forum_shortpost) {
3613                $stopzone = true;
3614            }
3615        }
3616    }
3618    if (!$truncate) {
3619        $truncate = $i;
3620    }
3622    return substr($message, 0, $truncate);
3625 /**
3626  * Print the drop down that allows the user to select how they want to have
3627  * the discussion displayed.
3628  *
3629  * @param int $id forum id if $forumtype is 'single',
3630  *              discussion id for any other forum type
3631  * @param mixed $mode forum layout mode
3632  * @param string $forumtype optional
3633  */
3634 function forum_print_mode_form($id, $mode, $forumtype='') {
3635     global $OUTPUT;
3636     if ($forumtype == 'single') {
3637         $select = new single_select(new moodle_url("/mod/forum/view.php", array('f'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode");
3638         $select->class = "forummode";
3639     } else {
3640         $select = new single_select(new moodle_url("/mod/forum/discuss.php", array('d'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode");
3641     }
3642     echo $OUTPUT->render($select);
3645 /**
3646  * @global object
3647  * @param object $course
3648  * @param string $search
3649  * @return string
3650  */
3651 function forum_search_form($course, $search='') {
3652     global $CFG, $OUTPUT;
3654     $output  = '<div class="forumsearch">';
3655     $output .= '<form action="'.$CFG->wwwroot.'/mod/forum/search.php" style="display:inline">';
3656     $output .= '<fieldset class="invisiblefieldset">';
3657     $output .= $OUTPUT->help_icon('search');
3658     $output .= '<label class="accesshide" for="search" >'.get_string('search', 'forum').'</label>';
3659     $output .= '<input id="search" name="search" type="text" size="18" value="'.s($search, true).'" alt="search" />';
3660     $output .= '<label class="accesshide" for="searchforums" >'.get_string('searchforums', 'forum').'</label>';
3661     $output .= '<input id="searchforums" value="'.get_string('searchforums', 'forum').'" type="submit" />';
3662     $output .= '<input name="id" type="hidden" value="'.$course->id.'" />';
3663     $output .= '</fieldset>';
3664     $output .= '</form>';
3665     $output .= '</div>';
3667     return $output;
3671 /**
3672  * @global object
3673  * @global object
3674  */
3675 function forum_set_return() {
3676     global $CFG, $SESSION;
3678     if (! isset($SESSION->fromdiscussion)) {
3679         if (!empty($_SERVER['HTTP_REFERER'])) {
3680             $referer = $_SERVER['HTTP_REFERER'];
3681         } else {
3682             $referer = "";
3683         }
3684         // If the referer is NOT a login screen then save it.
3685         if (! strncasecmp("$CFG->wwwroot/login", $referer, 300)) {
3686             $SESSION->fromdiscussion = $_SERVER["HTTP_REFERER"];
3687         }
3688     }
3692 /**
3693  * @global object
3694  * @param string $default
3695  * @return string
3696  */
3697 function forum_go_back_to($default) {
3698     global $SESSION;
3700     if (!empty($SESSION->fromdiscussion)) {
3701         $returnto = $SESSION->fromdiscussion;
3702         unset($SESSION->fromdiscussion);
3703         return $returnto;
3704     } else {
3705         return $default;
3706     }
3709 /**
3710  * Given a discussion object that is being moved to $forumto,
3711  * this function checks all posts in that discussion
3712  * for attachments, and if any are found, these are
3713  * moved to the new forum directory.
3714  *
3715  * @global object
3716  * @param object $discussion
3717  * @param int $forumfrom source forum id
3718  * @param int $forumto target forum id
3719  * @return bool success
3720  */
3721 function forum_move_attachments($discussion, $forumfrom, $forumto) {
3722     global $DB;
3724     $fs = get_file_storage();
3726     $newcm = get_coursemodule_from_instance('forum', $forumto);
3727     $oldcm = get_coursemodule_from_instance('forum', $forumfrom);
3729     $newcontext = get_context_instance(CONTEXT_MODULE, $newcm->id);
3730     $oldcontext = get_context_instance(CONTEXT_MODULE, $oldcm->id);
3732     // loop through all posts, better not use attachment flag ;-)
3733     if ($posts = $DB->get_records('forum_posts', array('discussion'=>$discussion->id), '', 'id, attachment')) {
3734         foreach ($posts as $post) {
3735             if ($oldfiles = $fs->get_area_files($oldcontext->id, 'mod_forum', 'attachment', $post->id, "id", false)) {
3736                 foreach ($oldfiles as $oldfile) {
3737                     $file_record = new object();
3738                     $file_record->contextid = $newcontext->id;
3739                     $fs->create_file_from_storedfile($file_record, $oldfile);
3740                 }
3741                 $fs->delete_area_files($oldcontext->id, 'mod_forum', 'attachment', $post->id);
3742                 if ($post->attachment != '1') {
3743                     //weird - let's fix it
3744                     $post->attachment = '1';
3745                     $DB->update_record('forum_posts', $post);
3746                 }
3747             } else {
3748                 if ($post->attachment != '') {
3749                     //weird - let's fix it
3750                     $post->attachment = '';
3751                     $DB->update_record('forum_posts', $post);
3752                 }
3753             }
3754         }
3755     }
3757     return true;
3760 /**
3761  * Returns attachments as formated text/html optionally with separate images
3762  *
3763  * @global object
3764  * @global object
3765  * @global object
3766  * @param object $post
3767  * @param object $cm
3768  * @param string $type html/text/separateimages
3769  * @return mixed string or array of (html text withouth images and image HTML)
3770  */
3771 function forum_print_attachments($post, $cm, $type) {
3772     global $CFG, $DB, $USER, $OUTPUT;
3774     if (empty($post->attachment)) {
3775         return $type !== 'separateimages' ? '' : array('', '');
3776     }
3778     if (!in_array($type, array('separateimages', 'html', 'text'))) {
3779         return $type !== 'separateimages' ? '' : array('', '');
3780     }
3782     if (!$context = get_context_instance(CONTEXT_MODULE, $cm->id)) {
3783         return $type !== 'separateimages' ? '' : array('', '');
3784     }
3785     $strattachment = get_string('attachment', 'forum');
3787     $fs = get_file_storage();
3789     $imagereturn = '';
3790     $output = '';
3792     $canexport = (has_capability('mod/forum:exportpost', $context) || ($post->userid == $USER->id && has_capability('mod/forum:exportownpost', $context)));
3794     require_once($CFG->libdir.'/portfoliolib.php');
3795     if ($files = $fs->get_area_files($context->id, 'mod_forum', 'attachment', $post->id, "timemodified", false)) {
3796         $button = new portfolio_add_button();
3797         foreach ($files as $file) {
3798             $filename = $file->get_filename();
3799             $mimetype = $file->get_mimetype();
3800             $iconimage = '<img src="'.$OUTPUT->pix_url(file_mimetype_icon($mimetype)).'" class="icon" alt="'.$mimetype.'" />';
3801             $path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$context->id.'/mod_forum/attachment/'.$post->id.'/'.$filename);
3803             if ($type == 'html') {
3804                 $output .= "<a href=\"$path\">$iconimage</a> ";
3805                 $output .= "<a href=\"$path\">".s($filename)."</a>";
3806                 if ($canexport) {
3807                     $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php');
3808                     $button->set_format_by_file($file);
3809                     $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
3810                 }
3811                 $output .= "<br />";
3813             } else if ($type == 'text') {
3814                 $output .= "$strattachment ".s($filename).":\n$path\n";
3816             } else { //'returnimages'
3817                 if (in_array($mimetype, array('image/gif', 'image/jpeg', 'image/png'))) {
3818                     // Image attachments don't get printed as links
3819                     $imagereturn .= "<br /><img src=\"$path\" alt=\"\" />";
3820                     if ($canexport) {
3821                         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php');
3822                         $button->set_format_by_file($file);
3823                         $imagereturn .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
3824                     }
3825                 } else {
3826                     $output .= "<a href=\"$path\">$iconimage</a> ";
3827                     $output .= format_text("<a href=\"$path\">".s($filename)."</a>", FORMAT_HTML, array('context'=>$context));
3828                     if ($canexport) {
3829                         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php');
3830                         $button->set_format_by_file($file);
3831                         $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
3832                     }
3833                     $output .= '<br />';
3834                 }
3835             }
3836         }
3837     }
3839     if ($type !== 'separateimages') {
3840         return $output;
3842     } else {
3843         return array($output, $imagereturn);
3844     }
3847 /**
3848  * Lists all browsable file areas
3849  *
3850  * @param object $course
3851  * @param object $cm
3852  * @param object $context
3853  * @return array
3854  */
3855 function forum_get_file_areas($course, $cm, $context) {
3856     $areas = array();
3857     return $areas;
3860 /**
3861  * Serves the forum attachments. Implements needed access control ;-)
3862  *
3863  * @param object $course
3864  * @param object $cm
3865  * @param object $context
3866  * @param string $filearea
3867  * @param array $args
3868  * @param bool $forcedownload
3869  * @return bool false if file not found, does not return if found - justsend the file
3870  */
3871 function forum_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) {
3872     global $CFG, $DB;
3874     if ($context->contextlevel != CONTEXT_MODULE) {
3875         return false;
3876     }
3878     require_course_login($course, true, $cm);
3880     $fileareas = array('attachment', 'post');
3881     if (!in_array($filearea, $fileareas)) {
3882         return false;
3883     }
3885     $postid = (int)array_shift($args);
3887     if (!$post = $DB->get_record('forum_posts', array('id'=>$postid))) {
3888         return false;
3889     }
3891     if (!$discussion = $DB->get_record('forum_discussions', array('id'=>$post->discussion))) {
3892         return false;
3893     }
3895     if (!$forum = $DB->get_record('forum', array('id'=>$cm->instance))) {
3896         return false;
3897     }
3899     $fs = get_file_storage();
3900     $relativepath = implode('/', $args);
3901     $fullpath = "/$context->id/mod_forum/$filearea/$postid/$relativepath";
3902     if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
3903         return false;
3904     }
3906     // Make sure groups allow this user to see this file
3907     if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
3908         if (!groups_group_exists($discussion->groupid)) { // Can't find group
3909             return false;                           // Be safe and don't send it to anyone
3910         }
3912         if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
3913             // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
3914             return false;
3915         }
3916     }
3918     // Make sure we're allowed to see it...
3919     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3920         return false;
3921     }
3924     // finally send the file
3925     send_stored_file($file, 0, 0, true); // download MUST be forced - security!
3928 /**
3929  * If successful, this function returns the name of the file
3930  *
3931  * @global object
3932  * @param object $post is a full post record, including course and forum
3933  * @param object $forum
3934  * @param object $cm
3935  * @param mixed $mform
3936  * @param string $message
3937  * @return bool
3938  */
3939 function forum_add_attachment($post, $forum, $cm, $mform=null, &$message=null) {
3940     global $DB;
3942     if (empty($mform)) {
3943         return false;
3944     }
3946     if (empty($post->attachments)) {
3947         return true;   // Nothing to do
3948     }
3950     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
3952     $info = file_get_draft_area_info($post->attachments);
3953     $present = ($info['filecount']>0) ? '1' : '';
3954     file_save_draft_area_files($post->attachments, $context->id, 'mod_forum', 'attachment', $post->id);
3956     $DB->set_field('forum_posts', 'attachment', $present, array('id'=>$post->id));
3958     return true;
3961 /**
3962  * Add a new post in an existing discussion.
3963  *
3964  * @global object
3965  * @global object
3966  * @global object
3967  * @param object $post
3968  * @param mixed $mform
3969  * @param string $message
3970  * @return int
3971  */
3972 function forum_add_new_post($post, $mform, &$message) {
3973     global $USER, $CFG, $DB;
3975     $message = $post->message;
3976     $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion));
3977     $forum      = $DB->get_record('forum', array('id' => $discussion->forum));
3978     $cm         = get_coursemodule_from_instance('forum', $forum->id);
3979     $context    = get_context_instance(CONTEXT_MODULE, $cm->id);
3981     $post->created    = $post->modified = time();
3982     $post->mailed     = "0";
3983     $post->userid     = $USER->id;
3984     $post->attachment = "";
3986     $post->id = $DB->insert_record("forum_posts", $post);
3987     $message = file_save_draft_area_files($post->itemid, $context->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $message);
3988     $DB->set_field('forum_posts', 'message', $message, array('id'=>$post->id));
3989     forum_add_attachment($post, $forum, $cm, $mform, $message);
3991     // Update discussion modified date
3992     $DB->set_field("forum_discussions", "timemodified", $post->modified, array("id" => $post->discussion));
3993     $DB->set_field("forum_discussions", "usermodified", $post->userid, array("id" => $post->discussion));
3995     if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
3996         forum_tp_mark_post_read($post->userid, $post, $post->forum);
3997     }
3999     return $post->id;
4002 /**
4003  * Update a post
4004  *
4005  * @global object
4006  * @global object
4007  * @global object
4008  * @param object $post
4009  * @param mixed $mform
4010  * @param string $message
4011  * @return bool
4012  */
4013 function forum_update_post($post, $mform, &$message) {
4014     global $USER, $CFG, $DB;
4016     $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion));
4017     $forum      = $DB->get_record('forum', array('id' => $discussion->forum));
4018     $cm         = get_coursemodule_from_instance('forum', $forum->id);
4019     $context    = get_context_instance(CONTEXT_MODULE, $cm->id);
4021     $post->modified = time();
4023     $DB->update_record('forum_posts', $post);
4025     $discussion->timemodified = $post->modified; // last modified tracking
4026     $discussion->usermodified = $post->userid;   // last modified tracking
4028     if (!$post->parent) {   // Post is a discussion starter - update discussion title and times too
4029         $discussion->name      = $post->subject;
4030         $discussion->timestart = $post->timestart;
4031         $discussion->timeend   = $post->timeend;
4032     }
4033     $post->message = file_save_draft_area_files($post->itemid, $context->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
4034     $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
4036     $DB->update_record('forum_discussions', $discussion);
4038     forum_add_attachment($post, $forum, $cm, $mform, $message);
4040     if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
4041         forum_tp_mark_post_read($post->userid, $post, $post->forum);
4042     }
4044     return true;
4047 /**
4048  * Given an object containing all the necessary data,
4049  * create a new discussion and return the id
4050  *
4051  * @global object
4052  * @global object
4053  * @global object
4054  * @param object $post
4055  * @param mixed $mform
4056  * @param string $message
4057  * @param int $userid
4058  * @return object
4059  */
4060 function forum_add_discussion($discussion, $mform=null, &$message=null, $userid=null) {
4061     global $USER, $CFG, $DB;
4063     $timenow = time();
4065     if (is_null($userid)) {
4066         $userid = $USER->id;
4067     }
4069     // The first post is stored as a real post, and linked
4070     // to from the discuss entry.
4072     $forum = $DB->get_record('forum', array('id'=>$discussion->forum));
4073     $cm    = get_coursemodule_from_instance('forum', $forum->id);
4075     $post = new object();
4076     $post->discussion    = 0;
4077     $post->parent        = 0;
4078     $post->userid        = $userid;
4079     $post->created       = $timenow;
4080     $post->modified      = $timenow;
4081     $post->mailed        = 0;
4082     $post->subject       = $discussion->name;
4083     $post->message       = $discussion->message;
4084     $post->messageformat = $discussion->messageformat;
4085     $post->messagetrust  = $discussion->messagetrust;
4086     $post->attachments   = isset($discussion->attachments) ? $discussion->attachments : null;
4087     $post->forum         = $forum->id;     // speedup
4088     $post->course        = $forum->course; // speedup
4089     $post->mailnow       = $discussion->mailnow;
4091     $post->id = $DB->insert_record("forum_posts", $post);
4093     // TODO: Fix the calling code so that there always is a $cm when this function is called
4094     if (!empty($cm->id) && !empty($discussion->itemid)) {   // In "single simple discussions" this may not exist yet
4095         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4096         $text = file_save_draft_area_files($discussion->itemid, $context->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
4097         $DB->set_field('forum_posts', 'message', $text, array('id'=>$post->id));
4098     }
4100     // Now do the main entry for the discussion, linking to this first post
4102     $discussion->firstpost    = $post->id;
4103     $discussion->timemodified = $timenow;
4104     $discussion->usermodified = $post->userid;
4105     $discussion->userid       = $userid;
4107     $post->discussion = $DB->insert_record("forum_discussions", $discussion);
4109     // Finally, set the pointer on the post.
4110     $DB->set_field("forum_posts", "discussion", $post->discussion, array("id"=>$post->id));
4112     if (!empty($cm->id)) {
4113         forum_add_attachment($post, $forum, $cm, $mform, $message);
4114     }
4116     if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
4117         forum_tp_mark_post_read($post->userid, $post, $post->forum);
4118     }
4120     return $post->discussion;
4124 /**
4125  * Deletes a discussion and handles all associated cleanup.
4126  *
4127  * @global object
4128  * @param object $discussion Discussion to delete
4129  * @param bool $fulldelete True when deleting entire forum
4130  * @param object $course Course
4131  * @param object $cm Course-module
4132  * @param object $forum Forum
4133  * @return bool
4134  */
4135 function forum_delete_discussion($discussion, $fulldelete, $course, $cm, $forum) {
4136     global $DB;
4137     $result = true;
4139     if ($posts = $DB->get_records("forum_posts", array("discussion" => $discussion->id))) {
4140         foreach ($posts as $post) {
4141             $post->course = $discussion->course;
4142             $post->forum  = $discussion->forum;
4143             if (!forum_delete_post($post, 'ignore', $course, $cm, $forum, $fulldelete)) {
4144                 $result = false;
4145             }
4146         }
4147     }
4149     fo