d850e57682c02eac72d536c70353c092cc785199
[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->dirroot.'/user/selector/lib.php');
29 /// CONSTANTS ///////////////////////////////////////////////////////////
31 define('FORUM_MODE_FLATOLDEST', 1);
32 define('FORUM_MODE_FLATNEWEST', -1);
33 define('FORUM_MODE_THREADED', 2);
34 define('FORUM_MODE_NESTED', 3);
36 define('FORUM_CHOOSESUBSCRIBE', 0);
37 define('FORUM_FORCESUBSCRIBE', 1);
38 define('FORUM_INITIALSUBSCRIBE', 2);
39 define('FORUM_DISALLOWSUBSCRIBE',3);
41 define('FORUM_TRACKING_OFF', 0);
42 define('FORUM_TRACKING_OPTIONAL', 1);
43 define('FORUM_TRACKING_ON', 2);
45 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
47 /**
48  * Given an object containing all the necessary data,
49  * (defined by the form in mod_form.php) this function
50  * will create a new instance and return the id number
51  * of the new instance.
52  *
53  * @global object
54  * @global object
55  * @param object $forum add forum instance (with magic quotes)
56  * @return int intance id
57  */
58 function forum_add_instance($forum, $mform) {
59     global $CFG, $DB;
61     $forum->timemodified = time();
63     if (empty($forum->assessed)) {
64         $forum->assessed = 0;
65     }
67     if (empty($forum->ratingtime) or empty($forum->assessed)) {
68         $forum->assesstimestart  = 0;
69         $forum->assesstimefinish = 0;
70     }
72     $forum->id = $DB->insert_record('forum', $forum);
73     $modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule);
75     if ($forum->type == 'single') {  // Create related discussion.
76         $discussion = new stdClass();
77         $discussion->course        = $forum->course;
78         $discussion->forum         = $forum->id;
79         $discussion->name          = $forum->name;
80         $discussion->assessed      = $forum->assessed;
81         $discussion->message       = $forum->intro;
82         $discussion->messageformat = $forum->introformat;
83         $discussion->messagetrust  = trusttext_trusted(get_context_instance(CONTEXT_COURSE, $forum->course));
84         $discussion->mailnow       = false;
85         $discussion->groupid       = -1;
87         $message = '';
89         $discussion->id = forum_add_discussion($discussion, null, $message);
91         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
92             // ugly hack - we need to copy the files somehow
93             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
94             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
96             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
97             $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
98         }
99     }
101     if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
102     /// all users should be subscribed initially
103     /// Note: forum_get_potential_subscribers should take the forum context,
104     /// but that does not exist yet, becuase the forum is only half build at this
105     /// stage. However, because the forum is brand new, we know that there are
106     /// no role assignments or overrides in the forum context, so using the
107     /// course context gives the same list of users.
108         $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
109         foreach ($users as $user) {
110             forum_subscribe($user->id, $forum->id);
111         }
112     }
114     forum_grade_item_update($forum);
116     return $forum->id;
120 /**
121  * Given an object containing all the necessary data,
122  * (defined by the form in mod_form.php) this function
123  * will update an existing instance with new data.
124  *
125  * @global object
126  * @param object $forum forum instance (with magic quotes)
127  * @return bool success
128  */
129 function forum_update_instance($forum, $mform) {
130     global $DB, $OUTPUT, $USER;
132     $forum->timemodified = time();
133     $forum->id           = $forum->instance;
135     if (empty($forum->assessed)) {
136         $forum->assessed = 0;
137     }
139     if (empty($forum->ratingtime) or empty($forum->assessed)) {
140         $forum->assesstimestart  = 0;
141         $forum->assesstimefinish = 0;
142     }
144     $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
146     // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
147     // if  scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
148     // 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
149     if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
150         forum_update_grades($forum); // recalculate grades for the forum
151     }
153     if ($forum->type == 'single') {  // Update related discussion and post.
154         if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
155             if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC')) {
156                 echo $OUTPUT->notification('Warning! There is more than one discussion in this forum - using the most recent');
157                 $discussion = array_pop($discussions);
158             } else {
159                 // try to recover by creating initial discussion - MDL-16262
160                 $discussion = new stdClass();
161                 $discussion->course          = $forum->course;
162                 $discussion->forum           = $forum->id;
163                 $discussion->name            = $forum->name;
164                 $discussion->assessed        = $forum->assessed;
165                 $discussion->message         = $forum->intro;
166                 $discussion->messageformat   = $forum->introformat;
167                 $discussion->messagetrust    = true;
168                 $discussion->mailnow         = false;
169                 $discussion->groupid         = -1;
171                 $message = '';
173                 forum_add_discussion($discussion, null, $message);
175                 if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
176                     print_error('cannotadd', 'forum');
177                 }
178             }
179         }
180         if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
181             print_error('cannotfindfirstpost', 'forum');
182         }
184         $cm         = get_coursemodule_from_instance('forum', $forum->id);
185         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
187         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
188             // ugly hack - we need to copy the files somehow
189             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
190             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
192             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
193         }
195         $post->subject       = $forum->name;
196         $post->message       = $forum->intro;
197         $post->messageformat = $forum->introformat;
198         $post->messagetrust  = trusttext_trusted($modcontext);
199         $post->modified      = $forum->timemodified;
200         $post->userid        = $USER->id;    // MDL-18599, so that current teacher can take ownership of activities
202         $DB->update_record('forum_posts', $post);
203         $discussion->name = $forum->name;
204         $DB->update_record('forum_discussions', $discussion);
205     }
207     $DB->update_record('forum', $forum);
209     forum_grade_item_update($forum);
211     return true;
215 /**
216  * Given an ID of an instance of this module,
217  * this function will permanently delete the instance
218  * and any data that depends on it.
219  *
220  * @global object
221  * @param int $id forum instance id
222  * @return bool success
223  */
224 function forum_delete_instance($id) {
225     global $DB;
227     if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
228         return false;
229     }
230     if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
231         return false;
232     }
233     if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
234         return false;
235     }
237     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
239     // now get rid of all files
240     $fs = get_file_storage();
241     $fs->delete_area_files($context->id);
243     $result = true;
245     if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
246         foreach ($discussions as $discussion) {
247             if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
248                 $result = false;
249             }
250         }
251     }
253     if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
254         $result = false;
255     }
257     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
259     if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
260         $result = false;
261     }
263     forum_grade_item_delete($forum);
265     return $result;
269 /**
270  * Indicates API features that the forum supports.
271  *
272  * @uses FEATURE_GROUPS
273  * @uses FEATURE_GROUPINGS
274  * @uses FEATURE_GROUPMEMBERSONLY
275  * @uses FEATURE_MOD_INTRO
276  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
277  * @uses FEATURE_COMPLETION_HAS_RULES
278  * @uses FEATURE_GRADE_HAS_GRADE
279  * @uses FEATURE_GRADE_OUTCOMES
280  * @param string $feature
281  * @return mixed True if yes (some features may use other values)
282  */
283 function forum_supports($feature) {
284     switch($feature) {
285         case FEATURE_GROUPS:                  return true;
286         case FEATURE_GROUPINGS:               return true;
287         case FEATURE_GROUPMEMBERSONLY:        return true;
288         case FEATURE_MOD_INTRO:               return true;
289         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
290         case FEATURE_COMPLETION_HAS_RULES:    return true;
291         case FEATURE_GRADE_HAS_GRADE:         return true;
292         case FEATURE_GRADE_OUTCOMES:          return true;
293         case FEATURE_RATE:                    return true;
294         case FEATURE_BACKUP_MOODLE2:          return true;
296         default: return null;
297     }
301 /**
302  * Obtains the automatic completion state for this forum based on any conditions
303  * in forum settings.
304  *
305  * @global object
306  * @global object
307  * @param object $course Course
308  * @param object $cm Course-module
309  * @param int $userid User ID
310  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
311  * @return bool True if completed, false if not. (If no conditions, then return
312  *   value depends on comparison type)
313  */
314 function forum_get_completion_state($course,$cm,$userid,$type) {
315     global $CFG,$DB;
317     // Get forum details
318     if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
319         throw new Exception("Can't find forum {$cm->instance}");
320     }
322     $result=$type; // Default return value
324     $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
325     $postcountsql="
326 SELECT
327     COUNT(1)
328 FROM
329     {forum_posts} fp
330     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
331 WHERE
332     fp.userid=:userid AND fd.forum=:forumid";
334     if ($forum->completiondiscussions) {
335         $value = $forum->completiondiscussions <=
336                  $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
337         if ($type == COMPLETION_AND) {
338             $result = $result && $value;
339         } else {
340             $result = $result || $value;
341         }
342     }
343     if ($forum->completionreplies) {
344         $value = $forum->completionreplies <=
345                  $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
346         if ($type==COMPLETION_AND) {
347             $result = $result && $value;
348         } else {
349             $result = $result || $value;
350         }
351     }
352     if ($forum->completionposts) {
353         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
354         if ($type == COMPLETION_AND) {
355             $result = $result && $value;
356         } else {
357             $result = $result || $value;
358         }
359     }
361     return $result;
365 /**
366  * Function to be run periodically according to the moodle cron
367  * Finds all posts that have yet to be mailed out, and mails them
368  * out to all subscribers
369  *
370  * @global object
371  * @global object
372  * @global object
373  * @uses CONTEXT_MODULE
374  * @uses CONTEXT_COURSE
375  * @uses SITEID
376  * @uses FORMAT_PLAIN
377  * @return void
378  */
379 function forum_cron() {
380     global $CFG, $USER, $DB;
382     $site = get_site();
384     // all users that are subscribed to any post that needs sending
385     $users = array();
387     // status arrays
388     $mailcount  = array();
389     $errorcount = array();
391     // caches
392     $discussions     = array();
393     $forums          = array();
394     $courses         = array();
395     $coursemodules   = array();
396     $subscribedusers = array();
399     // Posts older than 2 days will not be mailed.  This is to avoid the problem where
400     // cron has not been running for a long time, and then suddenly people are flooded
401     // with mail from the past few weeks or months
402     $timenow   = time();
403     $endtime   = $timenow - $CFG->maxeditingtime;
404     $starttime = $endtime - 48 * 3600;   // Two days earlier
406     if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
407         // Mark them all now as being mailed.  It's unlikely but possible there
408         // might be an error later so that a post is NOT actually mailed out,
409         // but since mail isn't crucial, we can accept this risk.  Doing it now
410         // prevents the risk of duplicated mails, which is a worse problem.
412         if (!forum_mark_old_posts_as_mailed($endtime)) {
413             mtrace('Errors occurred while trying to mark some posts as being mailed.');
414             return false;  // Don't continue trying to mail them, in case we are in a cron loop
415         }
417         // checking post validity, and adding users to loop through later
418         foreach ($posts as $pid => $post) {
420             $discussionid = $post->discussion;
421             if (!isset($discussions[$discussionid])) {
422                 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
423                     $discussions[$discussionid] = $discussion;
424                 } else {
425                     mtrace('Could not find discussion '.$discussionid);
426                     unset($posts[$pid]);
427                     continue;
428                 }
429             }
430             $forumid = $discussions[$discussionid]->forum;
431             if (!isset($forums[$forumid])) {
432                 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
433                     $forums[$forumid] = $forum;
434                 } else {
435                     mtrace('Could not find forum '.$forumid);
436                     unset($posts[$pid]);
437                     continue;
438                 }
439             }
440             $courseid = $forums[$forumid]->course;
441             if (!isset($courses[$courseid])) {
442                 if ($course = $DB->get_record('course', array('id' => $courseid))) {
443                     $courses[$courseid] = $course;
444                 } else {
445                     mtrace('Could not find course '.$courseid);
446                     unset($posts[$pid]);
447                     continue;
448                 }
449             }
450             if (!isset($coursemodules[$forumid])) {
451                 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
452                     $coursemodules[$forumid] = $cm;
453                 } else {
454                     mtrace('Could not find course module for forum '.$forumid);
455                     unset($posts[$pid]);
456                     continue;
457                 }
458             }
461             // caching subscribed users of each forum
462             if (!isset($subscribedusers[$forumid])) {
463                 $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
464                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext, "u.*")) {
465                     foreach ($subusers as $postuser) {
466                         unset($postuser->description); // not necessary
467                         // this user is subscribed to this forum
468                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
469                         // this user is a user we have to process later
470                         $users[$postuser->id] = $postuser;
471                     }
472                     unset($subusers); // release memory
473                 }
474             }
476             $mailcount[$pid] = 0;
477             $errorcount[$pid] = 0;
478         }
479     }
481     if ($users && $posts) {
483         $urlinfo = parse_url($CFG->wwwroot);
484         $hostname = $urlinfo['host'];
486         foreach ($users as $userto) {
488             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
490             // set this so that the capabilities are cached, and environment matches receiving user
491             cron_setup_user($userto);
493             mtrace('Processing user '.$userto->id);
495             // init caches
496             $userto->viewfullnames = array();
497             $userto->canpost       = array();
498             $userto->markposts     = array();
500             // reset the caches
501             foreach ($coursemodules as $forumid=>$unused) {
502                 $coursemodules[$forumid]->cache       = new stdClass();
503                 $coursemodules[$forumid]->cache->caps = array();
504                 unset($coursemodules[$forumid]->uservisible);
505             }
507             foreach ($posts as $pid => $post) {
509                 // Set up the environment for the post, discussion, forum, course
510                 $discussion = $discussions[$post->discussion];
511                 $forum      = $forums[$discussion->forum];
512                 $course     = $courses[$forum->course];
513                 $cm         =& $coursemodules[$forum->id];
515                 // Do some checks  to see if we can bail out now
516                 // Only active enrolled users are in the list of subscribers
517                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
518                     continue; // user does not subscribe to this forum
519                 }
521                 // Don't send email if the forum is Q&A and the user has not posted
522                 if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id)) {
523                     mtrace('Did not email '.$userto->id.' because user has not posted in discussion');
524                     continue;
525                 }
527                 // Get info about the sending user
528                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
529                     $userfrom = $users[$post->userid];
530                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
531                     unset($userfrom->description); // not necessary
532                     $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
533                 } else {
534                     mtrace('Could not find user '.$post->userid);
535                     continue;
536                 }
538                 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
540                 // setup global $COURSE properly - needed for roles and languages
541                 cron_setup_user($userto, $course);
543                 // Fill caches
544                 if (!isset($userto->viewfullnames[$forum->id])) {
545                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
546                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
547                 }
548                 if (!isset($userto->canpost[$discussion->id])) {
549                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
550                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
551                 }
552                 if (!isset($userfrom->groups[$forum->id])) {
553                     if (!isset($userfrom->groups)) {
554                         $userfrom->groups = array();
555                         $users[$userfrom->id]->groups = array();
556                     }
557                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
558                     $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
559                 }
561                 // Make sure groups allow this user to see this email
562                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
563                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
564                         continue;                           // Be safe and don't send it to anyone
565                     }
567                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
568                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
569                         continue;
570                     }
571                 }
573                 // Make sure we're allowed to see it...
574                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
575                     mtrace('user '.$userto->id. ' can not see '.$post->id);
576                     continue;
577                 }
579                 // OK so we need to send the email.
581                 // Does the user want this post in a digest?  If so postpone it for now.
582                 if ($userto->maildigest > 0) {
583                     // This user wants the mails to be in digest form
584                     $queue = new stdClass();
585                     $queue->userid       = $userto->id;
586                     $queue->discussionid = $discussion->id;
587                     $queue->postid       = $post->id;
588                     $queue->timemodified = $post->created;
589                     $DB->insert_record('forum_queue', $queue);
590                     continue;
591                 }
594                 // Prepare to actually send the post now, and build up the content
596                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
598                 $userfrom->customheaders = array (  // Headers to make emails easier to track
599                            'Precedence: Bulk',
600                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
601                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
602                            'Message-ID: <moodlepost'.$post->id.'@'.$hostname.'>',
603                            'X-Course-Id: '.$course->id,
604                            'X-Course-Name: '.format_string($course->fullname, true)
605                 );
607                 if ($post->parent) {  // This post is a reply, so add headers for threading (see MDL-22551)
608                     $userfrom->customheaders[] = 'In-Reply-To: <moodlepost'.$post->parent.'@'.$hostname.'>';
609                     $userfrom->customheaders[] = 'References: <moodlepost'.$post->parent.'@'.$hostname.'>';
610                 }
612                 $postsubject = "$course->shortname: ".format_string($post->subject,true);
613                 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
614                 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
616                 // Send the post now!
618                 mtrace('Sending ', '');
620                 $eventdata = new stdClass();
621                 $eventdata->component        = 'mod_forum';
622                 $eventdata->name             = 'posts';
623                 $eventdata->userfrom         = $userfrom;
624                 $eventdata->userto           = $userto;
625                 $eventdata->subject          = $postsubject;
626                 $eventdata->fullmessage      = $posttext;
627                 $eventdata->fullmessageformat = FORMAT_PLAIN;
628                 $eventdata->fullmessagehtml  = $posthtml;
629                 $eventdata->notification = 1;
631                 $smallmessagestrings = new stdClass();
632                 $smallmessagestrings->user = fullname($userfrom);
633                 $smallmessagestrings->forumname = "{$course->shortname}: ".format_string($forum->name,true).": ".$discussion->name;
634                 $smallmessagestrings->message = $post->message;
635                 //make sure strings are in message recipients language
636                 $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
638                 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
639                 $eventdata->contexturlname = $discussion->name;
641                 $mailresult = message_send($eventdata);
642                 if (!$mailresult){
643                     mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
644                          " ($userto->email) .. not trying again.");
645                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
646                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
647                     $errorcount[$post->id]++;
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];
726                 if (!isset($posts[$digestpost->postid])) {
727                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
728                         $posts[$digestpost->postid] = $post;
729                     } else {
730                         continue;
731                     }
732                 }
733                 $discussionid = $digestpost->discussionid;
734                 if (!isset($discussions[$discussionid])) {
735                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
736                         $discussions[$discussionid] = $discussion;
737                     } else {
738                         continue;
739                     }
740                 }
741                 $forumid = $discussions[$discussionid]->forum;
742                 if (!isset($forums[$forumid])) {
743                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
744                         $forums[$forumid] = $forum;
745                     } else {
746                         continue;
747                     }
748                 }
750                 $courseid = $forums[$forumid]->course;
751                 if (!isset($courses[$courseid])) {
752                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
753                         $courses[$courseid] = $course;
754                     } else {
755                         continue;
756                     }
757                 }
759                 if (!isset($coursemodules[$forumid])) {
760                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
761                         $coursemodules[$forumid] = $cm;
762                     } else {
763                         continue;
764                     }
765                 }
766                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
767                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
768             }
769             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
771             // Data collected, start sending out emails to each user
772             foreach ($userdiscussions as $userid => $thesediscussions) {
774                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
776                 cron_setup_user();
778                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
780                 // First of all delete all the queue entries for this user
781                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
782                 $userto = $users[$userid];
784                 // Override the language and timezone of the "current" user, so that
785                 // mail is customised for the receiver.
786                 cron_setup_user($userto);
788                 // init caches
789                 $userto->viewfullnames = array();
790                 $userto->canpost       = array();
791                 $userto->markposts     = array();
793                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
795                 $headerdata = new stdClass();
796                 $headerdata->sitename = format_string($site->fullname, true);
797                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
799                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
800                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
802                 $posthtml = "<head>";
803 /*                foreach ($CFG->stylesheets as $stylesheet) {
804                     //TODO: MDL-21120
805                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
806                 }*/
807                 $posthtml .= "</head>\n<body id=\"email\">\n";
808                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
810                 foreach ($thesediscussions as $discussionid) {
812                     @set_time_limit(120);   // to be reset for each post
814                     $discussion = $discussions[$discussionid];
815                     $forum      = $forums[$discussion->forum];
816                     $course     = $courses[$forum->course];
817                     $cm         = $coursemodules[$forum->id];
819                     //override language
820                     cron_setup_user($userto, $course);
822                     // Fill caches
823                     if (!isset($userto->viewfullnames[$forum->id])) {
824                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
825                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
826                     }
827                     if (!isset($userto->canpost[$discussion->id])) {
828                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
829                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
830                     }
832                     $strforums      = get_string('forums', 'forum');
833                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
834                     $canreply       = $userto->canpost[$discussion->id];
836                     $posttext .= "\n \n";
837                     $posttext .= '=====================================================================';
838                     $posttext .= "\n \n";
839                     $posttext .= "$course->shortname -> $strforums -> ".format_string($forum->name,true);
840                     if ($discussion->name != $forum->name) {
841                         $posttext  .= " -> ".format_string($discussion->name,true);
842                     }
843                     $posttext .= "\n";
845                     $posthtml .= "<p><font face=\"sans-serif\">".
846                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> -> ".
847                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
848                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
849                     if ($discussion->name == $forum->name) {
850                         $posthtml .= "</font></p>";
851                     } else {
852                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
853                     }
854                     $posthtml .= '<p>';
856                     $postsarray = $discussionposts[$discussionid];
857                     sort($postsarray);
859                     foreach ($postsarray as $postid) {
860                         $post = $posts[$postid];
862                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
863                             $userfrom = $users[$post->userid];
864                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
865                             $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
866                         } else {
867                             mtrace('Could not find user '.$post->userid);
868                             continue;
869                         }
871                         if (!isset($userfrom->groups[$forum->id])) {
872                             if (!isset($userfrom->groups)) {
873                                 $userfrom->groups = array();
874                                 $users[$userfrom->id]->groups = array();
875                             }
876                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
877                             $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
878                         }
880                         $userfrom->customheaders = array ("Precedence: Bulk");
882                         if ($userto->maildigest == 2) {
883                             // Subjects only
884                             $by = new stdClass();
885                             $by->name = fullname($userfrom);
886                             $by->date = userdate($post->modified);
887                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
888                             $posttext .= "\n---------------------------------------------------------------------";
890                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
891                             $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>';
893                         } else {
894                             // The full treatment
895                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
896                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
898                         // Create an array of postid's for this user to mark as read.
899                             if (!$CFG->forum_usermarksread) {
900                                 $userto->markposts[$post->id] = $post->id;
901                             }
902                         }
903                     }
904                     if ($canunsubscribe) {
905                         $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>";
906                     } else {
907                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
908                     }
909                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
910                 }
911                 $posthtml .= '</body>';
913                 if (empty($userto->mailformat) || $userto->mailformat != 1) {
914                     // This user DOESN'T want to receive HTML
915                     $posthtml = '';
916                 }
918                 $attachment = $attachname='';
919                 $usetrueaddress = true;
920                 //directly email forum digests rather than sending them via messaging
921                 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname, $usetrueaddress, $CFG->forum_replytouser);
923                 if (!$mailresult) {
924                     mtrace("ERROR!");
925                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
926                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
927                 } else {
928                     mtrace("success.");
929                     $usermailcount++;
931                     // Mark post as read if forum_usermarksread is set off
932                     forum_tp_mark_posts_read($userto, $userto->markposts);
933                 }
934             }
935         }
936     /// We have finishied all digest emails, update $CFG->digestmailtimelast
937         set_config('digestmailtimelast', $timenow);
938     }
940     cron_setup_user();
942     if (!empty($usermailcount)) {
943         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
944     }
946     if (!empty($CFG->forum_lastreadclean)) {
947         $timenow = time();
948         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
949             set_config('forum_lastreadclean', $timenow);
950             mtrace('Removing old forum read tracking info...');
951             forum_tp_clean_read_records();
952         }
953     } else {
954         set_config('forum_lastreadclean', time());
955     }
958     return true;
961 /**
962  * Builds and returns the body of the email notification in plain text.
963  *
964  * @global object
965  * @global object
966  * @uses CONTEXT_MODULE
967  * @param object $course
968  * @param object $cm
969  * @param object $forum
970  * @param object $discussion
971  * @param object $post
972  * @param object $userfrom
973  * @param object $userto
974  * @param boolean $bare
975  * @return string The email body in plain text format.
976  */
977 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
978     global $CFG, $USER;
980     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
982     if (!isset($userto->viewfullnames[$forum->id])) {
983         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
984     } else {
985         $viewfullnames = $userto->viewfullnames[$forum->id];
986     }
988     if (!isset($userto->canpost[$discussion->id])) {
989         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
990     } else {
991         $canreply = $userto->canpost[$discussion->id];
992     }
994     $by = New stdClass;
995     $by->name = fullname($userfrom, $viewfullnames);
996     $by->date = userdate($post->modified, "", $userto->timezone);
998     $strbynameondate = get_string('bynameondate', 'forum', $by);
1000     $strforums = get_string('forums', 'forum');
1002     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1004     $posttext = '';
1006     if (!$bare) {
1007         $posttext  = "$course->shortname -> $strforums -> ".format_string($forum->name,true);
1009         if ($discussion->name != $forum->name) {
1010             $posttext  .= " -> ".format_string($discussion->name,true);
1011         }
1012     }
1014     // add absolute file links
1015     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
1017     $posttext .= "\n---------------------------------------------------------------------\n";
1018     $posttext .= format_string($post->subject,true);
1019     if ($bare) {
1020         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1021     }
1022     $posttext .= "\n".$strbynameondate."\n";
1023     $posttext .= "---------------------------------------------------------------------\n";
1024     $posttext .= format_text_email($post->message, $post->messageformat);
1025     $posttext .= "\n\n";
1026     $posttext .= forum_print_attachments($post, $cm, "text");
1028     if (!$bare && $canreply) {
1029         $posttext .= "---------------------------------------------------------------------\n";
1030         $posttext .= get_string("postmailinfo", "forum", $course->shortname)."\n";
1031         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1032     }
1033     if (!$bare && $canunsubscribe) {
1034         $posttext .= "\n---------------------------------------------------------------------\n";
1035         $posttext .= get_string("unsubscribe", "forum");
1036         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1037     }
1039     return $posttext;
1042 /**
1043  * Builds and returns the body of the email notification in html format.
1044  *
1045  * @global object
1046  * @param object $course
1047  * @param object $cm
1048  * @param object $forum
1049  * @param object $discussion
1050  * @param object $post
1051  * @param object $userfrom
1052  * @param object $userto
1053  * @return string The email text in HTML format
1054  */
1055 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1056     global $CFG;
1058     if ($userto->mailformat != 1) {  // Needs to be HTML
1059         return '';
1060     }
1062     if (!isset($userto->canpost[$discussion->id])) {
1063         $canreply = forum_user_can_post($forum, $discussion, $userto);
1064     } else {
1065         $canreply = $userto->canpost[$discussion->id];
1066     }
1068     $strforums = get_string('forums', 'forum');
1069     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1071     $posthtml = '<head>';
1072 /*    foreach ($CFG->stylesheets as $stylesheet) {
1073         //TODO: MDL-21120
1074         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1075     }*/
1076     $posthtml .= '</head>';
1077     $posthtml .= "\n<body id=\"email\">\n\n";
1079     $posthtml .= '<div class="navbar">'.
1080     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$course->shortname.'</a> &raquo; '.
1081     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1082     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1083     if ($discussion->name == $forum->name) {
1084         $posthtml .= '</div>';
1085     } else {
1086         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1087                      format_string($discussion->name,true).'</a></div>';
1088     }
1089     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1091     if ($canunsubscribe) {
1092         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1093                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1094                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1095     }
1097     $posthtml .= '</body>';
1099     return $posthtml;
1103 /**
1104  *
1105  * @param object $course
1106  * @param object $user
1107  * @param object $mod TODO this is not used in this function, refactor
1108  * @param object $forum
1109  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1110  */
1111 function forum_user_outline($course, $user, $mod, $forum) {
1112     global $CFG;
1113     require_once("$CFG->libdir/gradelib.php");
1114     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1115     if (empty($grades->items[0]->grades)) {
1116         $grade = false;
1117     } else {
1118         $grade = reset($grades->items[0]->grades);
1119     }
1121     $count = forum_count_user_posts($forum->id, $user->id);
1123     if ($count && $count->postcount > 0) {
1124         $result = new stdClass();
1125         $result->info = get_string("numposts", "forum", $count->postcount);
1126         $result->time = $count->lastpost;
1127         if ($grade) {
1128             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1129         }
1130         return $result;
1131     } else if ($grade) {
1132         $result = new stdClass();
1133         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1135         //datesubmitted == time created. dategraded == time modified or time overridden
1136         //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
1137         //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
1138         if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1139             $result->time = $grade->dategraded;
1140         } else {
1141             $result->time = $grade->datesubmitted;
1142         }
1144         return $result;
1145     }
1146     return NULL;
1150 /**
1151  * @global object
1152  * @global object
1153  * @param object $coure
1154  * @param object $user
1155  * @param object $mod
1156  * @param object $forum
1157  */
1158 function forum_user_complete($course, $user, $mod, $forum) {
1159     global $CFG,$USER, $OUTPUT;
1160     require_once("$CFG->libdir/gradelib.php");
1162     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1163     if (!empty($grades->items[0]->grades)) {
1164         $grade = reset($grades->items[0]->grades);
1165         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1166         if ($grade->str_feedback) {
1167             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1168         }
1169     }
1171     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1173         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1174             print_error('invalidcoursemodule');
1175         }
1176         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1178         foreach ($posts as $post) {
1179             if (!isset($discussions[$post->discussion])) {
1180                 continue;
1181             }
1182             $discussion = $discussions[$post->discussion];
1184             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1185         }
1186     } else {
1187         echo "<p>".get_string("noposts", "forum")."</p>";
1188     }
1196 /**
1197  * @global object
1198  * @global object
1199  * @global object
1200  * @param array $courses
1201  * @param array $htmlarray
1202  */
1203 function forum_print_overview($courses,&$htmlarray) {
1204     global $USER, $CFG, $DB, $SESSION;
1206     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1207         return array();
1208     }
1210     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1211         return;
1212     }
1215     // get all forum logs in ONE query (much better!)
1216     $params = array();
1217     $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {log} l "
1218         ." JOIN {course_modules} cm ON cm.id = cmid "
1219         ." WHERE (";
1220     foreach ($courses as $course) {
1221         $sql .= '(l.course = ? AND l.time > ?) OR ';
1222         $params[] = $course->id;
1223         $params[] = $course->lastaccess;
1224     }
1225     $sql = substr($sql,0,-3); // take off the last OR
1227     $sql .= ") AND l.module = 'forum' AND action = 'add post' "
1228         ." AND userid != ? GROUP BY cmid,l.course,instance";
1230     $params[] = $USER->id;
1232     if (!$new = $DB->get_records_sql($sql, $params)) {
1233         $new = array(); // avoid warnings
1234     }
1236     // also get all forum tracking stuff ONCE.
1237     $trackingforums = array();
1238     foreach ($forums as $forum) {
1239         if (forum_tp_can_track_forums($forum)) {
1240             $trackingforums[$forum->id] = $forum;
1241         }
1242     }
1244     if (count($trackingforums) > 0) {
1245         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1246         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1247             ' FROM {forum_posts} p '.
1248             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1249             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1250         $params = array($USER->id);
1252         foreach ($trackingforums as $track) {
1253             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1254             $params[] = $track->id;
1255             if (isset($SESSION->currentgroup[$track->course])) {
1256                 $groupid =  $SESSION->currentgroup[$track->course];
1257             } else {
1258                 $groupid = groups_get_all_groups($track->course, $USER->id);
1259                 if (is_array($groupid)) {
1260                     $groupid = array_shift(array_keys($groupid));
1261                     $SESSION->currentgroup[$track->course] = $groupid;
1262                 } else {
1263                     $groupid = 0;
1264                 }
1265             }
1266             $params[] = $groupid;
1267         }
1268         $sql = substr($sql,0,-3); // take off the last OR
1269         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1270         $params[] = $cutoffdate;
1272         if (!$unread = $DB->get_records_sql($sql, $params)) {
1273             $unread = array();
1274         }
1275     } else {
1276         $unread = array();
1277     }
1279     if (empty($unread) and empty($new)) {
1280         return;
1281     }
1283     $strforum = get_string('modulename','forum');
1284     $strnumunread = get_string('overviewnumunread','forum');
1285     $strnumpostssince = get_string('overviewnumpostssince','forum');
1287     foreach ($forums as $forum) {
1288         $str = '';
1289         $count = 0;
1290         $thisunread = 0;
1291         $showunread = false;
1292         // either we have something from logs, or trackposts, or nothing.
1293         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1294             $count = $new[$forum->id]->count;
1295         }
1296         if (array_key_exists($forum->id,$unread)) {
1297             $thisunread = $unread[$forum->id]->count;
1298             $showunread = true;
1299         }
1300         if ($count > 0 || $thisunread > 0) {
1301             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1302                 $forum->name.'</a></div>';
1303             $str .= '<div class="info"><span class="postsincelogin">';
1304             $str .= $count.' '.$strnumpostssince."</span>";
1305             if (!empty($showunread)) {
1306                 $str .= '<div class="unreadposts">'.$thisunread .' '.$strnumunread.'</div>';
1307             }
1308             $str .= '</div></div>';
1309         }
1310         if (!empty($str)) {
1311             if (!array_key_exists($forum->course,$htmlarray)) {
1312                 $htmlarray[$forum->course] = array();
1313             }
1314             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1315                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1316             }
1317             $htmlarray[$forum->course]['forum'] .= $str;
1318         }
1319     }
1322 /**
1323  * Given a course and a date, prints a summary of all the new
1324  * messages posted in the course since that date
1325  *
1326  * @global object
1327  * @global object
1328  * @global object
1329  * @uses CONTEXT_MODULE
1330  * @uses VISIBLEGROUPS
1331  * @param object $course
1332  * @param bool $viewfullnames capability
1333  * @param int $timestart
1334  * @return bool success
1335  */
1336 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1337     global $CFG, $USER, $DB, $OUTPUT;
1339     // do not use log table if possible, it may be huge and is expensive to join with other tables
1341     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1342                                               d.timestart, d.timeend, d.userid AS duserid,
1343                                               u.firstname, u.lastname, u.email, u.picture
1344                                          FROM {forum_posts} p
1345                                               JOIN {forum_discussions} d ON d.id = p.discussion
1346                                               JOIN {forum} f             ON f.id = d.forum
1347                                               JOIN {user} u              ON u.id = p.userid
1348                                         WHERE p.created > ? AND f.course = ?
1349                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1350          return false;
1351     }
1353     $modinfo =& get_fast_modinfo($course);
1355     $groupmodes = array();
1356     $cms    = array();
1358     $strftimerecent = get_string('strftimerecent');
1360     $printposts = array();
1361     foreach ($posts as $post) {
1362         if (!isset($modinfo->instances['forum'][$post->forum])) {
1363             // not visible
1364             continue;
1365         }
1366         $cm = $modinfo->instances['forum'][$post->forum];
1367         if (!$cm->uservisible) {
1368             continue;
1369         }
1370         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1372         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1373             continue;
1374         }
1376         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1377           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1378             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1379                 continue;
1380             }
1381         }
1383         $groupmode = groups_get_activity_groupmode($cm, $course);
1385         if ($groupmode) {
1386             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1387                 // oki (Open discussions have groupid -1)
1388             } else {
1389                 // separate mode
1390                 if (isguestuser()) {
1391                     // shortcut
1392                     continue;
1393                 }
1395                 if (is_null($modinfo->groups)) {
1396                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1397                 }
1399                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1400                     continue;
1401                 }
1402             }
1403         }
1405         $printposts[] = $post;
1406     }
1407     unset($posts);
1409     if (!$printposts) {
1410         return false;
1411     }
1413     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1414     echo "\n<ul class='unlist'>\n";
1416     foreach ($printposts as $post) {
1417         $subjectclass = empty($post->parent) ? ' bold' : '';
1419         echo '<li><div class="head">'.
1420                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1421                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1422              '</div>';
1423         echo '<div class="info'.$subjectclass.'">';
1424         if (empty($post->parent)) {
1425             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1426         } else {
1427             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1428         }
1429         $post->subject = break_up_long_words(format_string($post->subject, true));
1430         echo $post->subject;
1431         echo "</a>\"</div></li>\n";
1432     }
1434     echo "</ul>\n";
1436     return true;
1439 /**
1440  * Return grade for given user or all users.
1441  *
1442  * @global object
1443  * @global object
1444  * @param object $forum
1445  * @param int $userid optional user id, 0 means all users
1446  * @return array array of grades, false if none
1447  */
1448 function forum_get_user_grades($forum, $userid=0) {
1449     global $CFG;
1451     require_once($CFG->dirroot.'/rating/lib.php');
1452     $rm = new rating_manager();
1454     $ratingoptions = new stdclass();
1456     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1457     $ratingoptions->modulename = 'forum';
1458     $ratingoptions->moduleid   = $forum->id;
1459     //$ratingoptions->cmidnumber = $forum->cmidnumber;
1461     $ratingoptions->userid = $userid;
1462     $ratingoptions->aggregationmethod = $forum->assessed;
1463     $ratingoptions->scaleid = $forum->scale;
1464     $ratingoptions->itemtable = 'forum_posts';
1465     $ratingoptions->itemtableusercolumn = 'userid';
1467     return $rm->get_user_grades($ratingoptions);
1470 /**
1471  * Update activity grades
1472  *
1473  * @global object
1474  * @global object
1475  * @param object $forum
1476  * @param int $userid specific user only, 0 means all
1477  * @param boolean $nullifnone return null if grade does not exist
1478  * @return void
1479  */
1480 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1481     global $CFG, $DB;
1482     require_once($CFG->libdir.'/gradelib.php');
1484     if (!$forum->assessed) {
1485         forum_grade_item_update($forum);
1487     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1488         forum_grade_item_update($forum, $grades);
1490     } else if ($userid and $nullifnone) {
1491         $grade = new stdClass();
1492         $grade->userid   = $userid;
1493         $grade->rawgrade = NULL;
1494         forum_grade_item_update($forum, $grade);
1496     } else {
1497         forum_grade_item_update($forum);
1498     }
1501 /**
1502  * Update all grades in gradebook.
1503  * @global object
1504  */
1505 function forum_upgrade_grades() {
1506     global $DB;
1508     $sql = "SELECT COUNT('x')
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     $count = $DB->count_records_sql($sql);
1513     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1514               FROM {forum} f, {course_modules} cm, {modules} m
1515              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1516     $rs = $DB->get_recordset_sql($sql);
1517     if ($rs->valid()) {
1518         $pbar = new progress_bar('forumupgradegrades', 500, true);
1519         $i=0;
1520         foreach ($rs as $forum) {
1521             $i++;
1522             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1523             forum_update_grades($forum, 0, false);
1524             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1525         }
1526     }
1527     $rs->close();
1530 /**
1531  * Create/update grade item for given forum
1532  *
1533  * @global object
1534  * @uses GRADE_TYPE_NONE
1535  * @uses GRADE_TYPE_VALUE
1536  * @uses GRADE_TYPE_SCALE
1537  * @param object $forum object with extra cmidnumber
1538  * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
1539  * @return int 0 if ok
1540  */
1541 function forum_grade_item_update($forum, $grades=NULL) {
1542     global $CFG;
1543     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1544         require_once($CFG->libdir.'/gradelib.php');
1545     }
1547     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1549     if (!$forum->assessed or $forum->scale == 0) {
1550         $params['gradetype'] = GRADE_TYPE_NONE;
1552     } else if ($forum->scale > 0) {
1553         $params['gradetype'] = GRADE_TYPE_VALUE;
1554         $params['grademax']  = $forum->scale;
1555         $params['grademin']  = 0;
1557     } else if ($forum->scale < 0) {
1558         $params['gradetype'] = GRADE_TYPE_SCALE;
1559         $params['scaleid']   = -$forum->scale;
1560     }
1562     if ($grades  === 'reset') {
1563         $params['reset'] = true;
1564         $grades = NULL;
1565     }
1567     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1570 /**
1571  * Delete grade item for given forum
1572  *
1573  * @global object
1574  * @param object $forum object
1575  * @return object grade_item
1576  */
1577 function forum_grade_item_delete($forum) {
1578     global $CFG;
1579     require_once($CFG->libdir.'/gradelib.php');
1581     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1585 /**
1586  * Returns the users with data in one forum
1587  * (users with records in forum_subscriptions, forum_posts, students)
1588  *
1589  * @global object
1590  * @global object
1591  * @param int $forumid
1592  * @return mixed array or false if none
1593  */
1594 function forum_get_participants($forumid) {
1596     global $CFG, $DB;
1598     //Get students from forum_subscriptions
1599     $st_subscriptions = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1600                                          FROM {user} u,
1601                                               {forum_subscriptions} s
1602                                          WHERE s.forum = ? AND
1603                                                u.id = s.userid", array($forumid));
1604     //Get students from forum_posts
1605     $st_posts = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1606                                  FROM {user} u,
1607                                       {forum_discussions} d,
1608                                       {forum_posts} p
1609                                  WHERE d.forum = ? AND
1610                                        p.discussion = d.id AND
1611                                        u.id = p.userid", array($forumid));
1613     //Get students from the ratings table
1614     $st_ratings = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1615                                    FROM {user} u,
1616                                         {forum_discussions} d,
1617                                         {forum_posts} p,
1618                                         {ratings} r
1619                                    WHERE d.forum = ? AND
1620                                          p.discussion = d.id AND
1621                                          r.post = p.id AND
1622                                          u.id = r.userid", array($forumid));
1624     //Add st_posts to st_subscriptions
1625     if ($st_posts) {
1626         foreach ($st_posts as $st_post) {
1627             $st_subscriptions[$st_post->id] = $st_post;
1628         }
1629     }
1630     //Add st_ratings to st_subscriptions
1631     if ($st_ratings) {
1632         foreach ($st_ratings as $st_rating) {
1633             $st_subscriptions[$st_rating->id] = $st_rating;
1634         }
1635     }
1636     //Return st_subscriptions array (it contains an array of unique users)
1637     return ($st_subscriptions);
1640 /**
1641  * This function returns if a scale is being used by one forum
1642  *
1643  * @global object
1644  * @param int $forumid
1645  * @param int $scaleid negative number
1646  * @return bool
1647  */
1648 function forum_scale_used ($forumid,$scaleid) {
1649     global $DB;
1650     $return = false;
1652     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1654     if (!empty($rec) && !empty($scaleid)) {
1655         $return = true;
1656     }
1658     return $return;
1661 /**
1662  * Checks if scale is being used by any instance of forum
1663  *
1664  * This is used to find out if scale used anywhere
1665  *
1666  * @global object
1667  * @param $scaleid int
1668  * @return boolean True if the scale is used by any forum
1669  */
1670 function forum_scale_used_anywhere($scaleid) {
1671     global $DB;
1672     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1673         return true;
1674     } else {
1675         return false;
1676     }
1679 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1681 /**
1682  * Gets a post with all info ready for forum_print_post
1683  * Most of these joins are just to get the forum id
1684  *
1685  * @global object
1686  * @global object
1687  * @param int $postid
1688  * @return mixed array of posts or false
1689  */
1690 function forum_get_post_full($postid) {
1691     global $CFG, $DB;
1693     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1694                              FROM {forum_posts} p
1695                                   JOIN {forum_discussions} d ON p.discussion = d.id
1696                                   LEFT JOIN {user} u ON p.userid = u.id
1697                             WHERE p.id = ?", array($postid));
1700 /**
1701  * Gets posts with all info ready for forum_print_post
1702  * We pass forumid in because we always know it so no need to make a
1703  * complicated join to find it out.
1704  *
1705  * @global object
1706  * @global object
1707  * @return mixed array of posts or false
1708  */
1709 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1710     global $CFG, $DB;
1712     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1713                               FROM {forum_posts} p
1714                          LEFT JOIN {user} u ON p.userid = u.id
1715                              WHERE p.discussion = ?
1716                                AND p.parent > 0 $sort", array($discussion));
1719 /**
1720  * Gets all posts in discussion including top parent.
1721  *
1722  * @global object
1723  * @global object
1724  * @global object
1725  * @param int $discussionid
1726  * @param string $sort
1727  * @param bool $tracking does user track the forum?
1728  * @return array of posts
1729  */
1730 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1731     global $CFG, $DB, $USER;
1733     $tr_sel  = "";
1734     $tr_join = "";
1735     $params = array();
1737     if ($tracking) {
1738         $now = time();
1739         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1740         $tr_sel  = ", fr.id AS postread";
1741         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1742         $params[] = $USER->id;
1743     }
1745     $params[] = $discussionid;
1746     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1747                                      FROM {forum_posts} p
1748                                           LEFT JOIN {user} u ON p.userid = u.id
1749                                           $tr_join
1750                                     WHERE p.discussion = ?
1751                                  ORDER BY $sort", $params)) {
1752         return array();
1753     }
1755     foreach ($posts as $pid=>$p) {
1756         if ($tracking) {
1757             if (forum_tp_is_post_old($p)) {
1758                  $posts[$pid]->postread = true;
1759             }
1760         }
1761         if (!$p->parent) {
1762             continue;
1763         }
1764         if (!isset($posts[$p->parent])) {
1765             continue; // parent does not exist??
1766         }
1767         if (!isset($posts[$p->parent]->children)) {
1768             $posts[$p->parent]->children = array();
1769         }
1770         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1771     }
1773     return $posts;
1776 /**
1777  * Gets posts with all info ready for forum_print_post
1778  * We pass forumid in because we always know it so no need to make a
1779  * complicated join to find it out.
1780  *
1781  * @global object
1782  * @global object
1783  * @param int $parent
1784  * @param int $forumid
1785  * @return array
1786  */
1787 function forum_get_child_posts($parent, $forumid) {
1788     global $CFG, $DB;
1790     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1791                               FROM {forum_posts} p
1792                          LEFT JOIN {user} u ON p.userid = u.id
1793                              WHERE p.parent = ?
1794                           ORDER BY p.created ASC", array($parent));
1797 /**
1798  * An array of forum objects that the user is allowed to read/search through.
1799  *
1800  * @global object
1801  * @global object
1802  * @global object
1803  * @param int $userid
1804  * @param int $courseid if 0, we look for forums throughout the whole site.
1805  * @return array of forum objects, or false if no matches
1806  *         Forum objects have the following attributes:
1807  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1808  *         viewhiddentimedposts
1809  */
1810 function forum_get_readable_forums($userid, $courseid=0) {
1812     global $CFG, $DB, $USER;
1813     require_once($CFG->dirroot.'/course/lib.php');
1815     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1816         print_error('notinstalled', 'forum');
1817     }
1819     if ($courseid) {
1820         $courses = $DB->get_records('course', array('id' => $courseid));
1821     } else {
1822         // If no course is specified, then the user can see SITE + his courses.
1823         $courses1 = $DB->get_records('course', array('id' => SITEID));
1824         $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
1825         $courses = array_merge($courses1, $courses2);
1826     }
1827     if (!$courses) {
1828         return array();
1829     }
1831     $readableforums = array();
1833     foreach ($courses as $course) {
1835         $modinfo =& get_fast_modinfo($course);
1836         if (is_null($modinfo->groups)) {
1837             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1838         }
1840         if (empty($modinfo->instances['forum'])) {
1841             // hmm, no forums?
1842             continue;
1843         }
1845         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1847         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1848             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1849                 continue;
1850             }
1851             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1852             $forum = $courseforums[$forumid];
1853             $forum->context = $context;
1854             $forum->cm = $cm;
1856             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1857                 continue;
1858             }
1860          /// group access
1861             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1862                 if (is_null($modinfo->groups)) {
1863                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1864                 }
1865                 if (isset($modinfo->groups[$cm->groupingid])) {
1866                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1867                     $forum->onlygroups[] = -1;
1868                 } else {
1869                     $forum->onlygroups = array(-1);
1870                 }
1871             }
1873         /// hidden timed discussions
1874             $forum->viewhiddentimedposts = true;
1875             if (!empty($CFG->forum_enabletimedposts)) {
1876                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1877                     $forum->viewhiddentimedposts = false;
1878                 }
1879             }
1881         /// qanda access
1882             if ($forum->type == 'qanda'
1883                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1885                 // We need to check whether the user has posted in the qanda forum.
1886                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1887                                                     // the user is allowed to see in this forum.
1888                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1889                     foreach ($discussionspostedin as $d) {
1890                         $forum->onlydiscussions[] = $d->id;
1891                     }
1892                 }
1893             }
1895             $readableforums[$forum->id] = $forum;
1896         }
1898         unset($modinfo);
1900     } // End foreach $courses
1902     return $readableforums;
1905 /**
1906  * Returns a list of posts found using an array of search terms.
1907  *
1908  * @global object
1909  * @global object
1910  * @global object
1911  * @param array $searchterms array of search terms, e.g. word +word -word
1912  * @param int $courseid if 0, we search through the whole site
1913  * @param int $limitfrom
1914  * @param int $limitnum
1915  * @param int &$totalcount
1916  * @param string $extrasql
1917  * @return array|bool Array of posts found or false
1918  */
1919 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1920                             &$totalcount, $extrasql='') {
1921     global $CFG, $DB, $USER;
1922     require_once($CFG->libdir.'/searchlib.php');
1924     $forums = forum_get_readable_forums($USER->id, $courseid);
1926     if (count($forums) == 0) {
1927         $totalcount = 0;
1928         return false;
1929     }
1931     $now = round(time(), -2); // db friendly
1933     $fullaccess = array();
1934     $where = array();
1935     $params = array();
1937     foreach ($forums as $forumid => $forum) {
1938         $select = array();
1940         if (!$forum->viewhiddentimedposts) {
1941             $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
1942             $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
1943         }
1945         $cm = $forum->cm;
1946         $context = $forum->context;
1948         if ($forum->type == 'qanda'
1949             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1950             if (!empty($forum->onlydiscussions)) {
1951                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
1952                 $params = array_merge($params, $discussionid_params);
1953                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
1954             } else {
1955                 $select[] = "p.parent = 0";
1956             }
1957         }
1959         if (!empty($forum->onlygroups)) {
1960             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
1961             $params = array_merge($params, $groupid_params);
1962             $select[] = "d.groupid $groupid_sql";
1963         }
1965         if ($select) {
1966             $selects = implode(" AND ", $select);
1967             $where[] = "(d.forum = :forum{$forumid} AND $selects)";
1968             $params['forum'.$forumid] = $forumid;
1969         } else {
1970             $fullaccess[] = $forumid;
1971         }
1972     }
1974     if ($fullaccess) {
1975         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
1976         $params = array_merge($params, $fullid_params);
1977         $where[] = "(d.forum $fullid_sql)";
1978     }
1980     $selectdiscussion = "(".implode(" OR ", $where).")";
1982     $messagesearch = '';
1983     $searchstring = '';
1985     // Need to concat these back together for parser to work.
1986     foreach($searchterms as $searchterm){
1987         if ($searchstring != '') {
1988             $searchstring .= ' ';
1989         }
1990         $searchstring .= $searchterm;
1991     }
1993     // We need to allow quoted strings for the search. The quotes *should* be stripped
1994     // by the parser, but this should be examined carefully for security implications.
1995     $searchstring = str_replace("\\\"","\"",$searchstring);
1996     $parser = new search_parser();
1997     $lexer = new search_lexer($parser);
1999     if ($lexer->parse($searchstring)) {
2000         $parsearray = $parser->get_parsed_array();
2001     // Experimental feature under 1.8! MDL-8830
2002     // Use alternative text searches if defined
2003     // This feature only works under mysql until properly implemented for other DBs
2004     // Requires manual creation of text index for forum_posts before enabling it:
2005     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2006     // Experimental feature under 1.8! MDL-8830
2007         if (!empty($CFG->forum_usetextsearches)) {
2008             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2009                                                  'p.userid', 'u.id', 'u.firstname',
2010                                                  'u.lastname', 'p.modified', 'd.forum');
2011         } else {
2012             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2013                                                  'p.userid', 'u.id', 'u.firstname',
2014                                                  'u.lastname', 'p.modified', 'd.forum');
2015         }
2016         $params = array_merge($params, $msparams);
2017     }
2019     $fromsql = "{forum_posts} p,
2020                   {forum_discussions} d,
2021                   {user} u";
2023     $selectsql = " $messagesearch
2024                AND p.discussion = d.id
2025                AND p.userid = u.id
2026                AND $selectdiscussion
2027                    $extrasql";
2029     $countsql = "SELECT COUNT(*)
2030                    FROM $fromsql
2031                   WHERE $selectsql";
2033     $searchsql = "SELECT p.*,
2034                          d.forum,
2035                          u.firstname,
2036                          u.lastname,
2037                          u.email,
2038                          u.picture,
2039                          u.imagealt,
2040                          u.email
2041                     FROM $fromsql
2042                    WHERE $selectsql
2043                 ORDER BY p.modified DESC";
2045     $totalcount = $DB->count_records_sql($countsql, $params);
2047     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2050 /**
2051  * Returns a list of ratings for a particular post - sorted.
2052  *
2053  * @global object
2054  * @global object
2055  * @param int $postid
2056  * @param string $sort
2057  * @return array Array of ratings or false
2058  */
2059 function forum_get_ratings($context, $postid, $sort="u.firstname ASC") {
2060     global $PAGE;
2062     $options = new stdclass();
2063     $options->context = $PAGE->context;
2064     $options->itemid = $postid;
2065     $options->sort = "ORDER BY $sort";
2067     $rm = new rating_manager();
2068     $rm->get_all_ratings_for_item($options);
2071 /**
2072  * Returns a list of all new posts that have not been mailed yet
2073  *
2074  * @global object
2075  * @global object
2076  * @param int $starttime posts created after this time
2077  * @param int $endtime posts created before this
2078  * @param int $now used for timed discussions only
2079  * @return array
2080  */
2081 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2082     global $CFG, $DB;
2084     $params = array($starttime, $endtime);
2085     if (!empty($CFG->forum_enabletimedposts)) {
2086         if (empty($now)) {
2087             $now = time();
2088         }
2089         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2090         $params[] = $now;
2091         $params[] = $now;
2092     } else {
2093         $timedsql = "";
2094     }
2096     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2097                               FROM {forum_posts} p
2098                                    JOIN {forum_discussions} d ON d.id = p.discussion
2099                              WHERE p.mailed = 0
2100                                    AND p.created >= ?
2101                                    AND (p.created < ? OR p.mailnow = 1)
2102                                    $timedsql
2103                           ORDER BY p.modified ASC", $params);
2106 /**
2107  * Marks posts before a certain time as being mailed already
2108  *
2109  * @global object
2110  * @global object
2111  * @param int $endtime
2112  * @param int $now Defaults to time()
2113  * @return bool
2114  */
2115 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2116     global $CFG, $DB;
2117     if (empty($now)) {
2118         $now = time();
2119     }
2121     if (empty($CFG->forum_enabletimedposts)) {
2122         return $DB->execute("UPDATE {forum_posts}
2123                                SET mailed = '1'
2124                              WHERE (created < ? OR mailnow = 1)
2125                                    AND mailed = 0", array($endtime));
2127     } else {
2128         return $DB->execute("UPDATE {forum_posts}
2129                                SET mailed = '1'
2130                              WHERE discussion NOT IN (SELECT d.id
2131                                                         FROM {forum_discussions} d
2132                                                        WHERE d.timestart > ?)
2133                                    AND (created < ? OR mailnow = 1)
2134                                    AND mailed = 0", array($now, $endtime));
2135     }
2138 /**
2139  * Get all the posts for a user in a forum suitable for forum_print_post
2140  *
2141  * @global object
2142  * @global object
2143  * @uses CONTEXT_MODULE
2144  * @return array
2145  */
2146 function forum_get_user_posts($forumid, $userid) {
2147     global $CFG, $DB;
2149     $timedsql = "";
2150     $params = array($forumid, $userid);
2152     if (!empty($CFG->forum_enabletimedposts)) {
2153         $cm = get_coursemodule_from_instance('forum', $forumid);
2154         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2155             $now = time();
2156             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2157             $params[] = $now;
2158             $params[] = $now;
2159         }
2160     }
2162     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2163                               FROM {forum} f
2164                                    JOIN {forum_discussions} d ON d.forum = f.id
2165                                    JOIN {forum_posts} p       ON p.discussion = d.id
2166                                    JOIN {user} u              ON u.id = p.userid
2167                              WHERE f.id = ?
2168                                    AND p.userid = ?
2169                                    $timedsql
2170                           ORDER BY p.modified ASC", $params);
2173 /**
2174  * Get all the discussions user participated in
2175  *
2176  * @global object
2177  * @global object
2178  * @uses CONTEXT_MODULE
2179  * @param int $forumid
2180  * @param int $userid
2181  * @return array Array or false
2182  */
2183 function forum_get_user_involved_discussions($forumid, $userid) {
2184     global $CFG, $DB;
2186     $timedsql = "";
2187     $params = array($forumid, $userid);
2188     if (!empty($CFG->forum_enabletimedposts)) {
2189         $cm = get_coursemodule_from_instance('forum', $forumid);
2190         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2191             $now = time();
2192             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2193             $params[] = $now;
2194             $params[] = $now;
2195         }
2196     }
2198     return $DB->get_records_sql("SELECT DISTINCT d.*
2199                               FROM {forum} f
2200                                    JOIN {forum_discussions} d ON d.forum = f.id
2201                                    JOIN {forum_posts} p       ON p.discussion = d.id
2202                              WHERE f.id = ?
2203                                    AND p.userid = ?
2204                                    $timedsql", $params);
2207 /**
2208  * Get all the posts for a user in a forum suitable for forum_print_post
2209  *
2210  * @global object
2211  * @global object
2212  * @param int $forumid
2213  * @param int $userid
2214  * @return array of counts or false
2215  */
2216 function forum_count_user_posts($forumid, $userid) {
2217     global $CFG, $DB;
2219     $timedsql = "";
2220     $params = array($forumid, $userid);
2221     if (!empty($CFG->forum_enabletimedposts)) {
2222         $cm = get_coursemodule_from_instance('forum', $forumid);
2223         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2224             $now = time();
2225             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2226             $params[] = $now;
2227             $params[] = $now;
2228         }
2229     }
2231     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2232                              FROM {forum} f
2233                                   JOIN {forum_discussions} d ON d.forum = f.id
2234                                   JOIN {forum_posts} p       ON p.discussion = d.id
2235                                   JOIN {user} u              ON u.id = p.userid
2236                             WHERE f.id = ?
2237                                   AND p.userid = ?
2238                                   $timedsql", $params);
2241 /**
2242  * Given a log entry, return the forum post details for it.
2243  *
2244  * @global object
2245  * @global object
2246  * @param object $log
2247  * @return array|null
2248  */
2249 function forum_get_post_from_log($log) {
2250     global $CFG, $DB;
2252     if ($log->action == "add post") {
2254         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2255                                            u.firstname, u.lastname, u.email, u.picture
2256                                  FROM {forum_discussions} d,
2257                                       {forum_posts} p,
2258                                       {forum} f,
2259                                       {user} u
2260                                 WHERE p.id = ?
2261                                   AND d.id = p.discussion
2262                                   AND p.userid = u.id
2263                                   AND u.deleted <> '1'
2264                                   AND f.id = d.forum", array($log->info));
2267     } else if ($log->action == "add discussion") {
2269         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2270                                            u.firstname, u.lastname, u.email, u.picture
2271                                  FROM {forum_discussions} d,
2272                                       {forum_posts} p,
2273                                       {forum} f,
2274                                       {user} u
2275                                 WHERE d.id = ?
2276                                   AND d.firstpost = p.id
2277                                   AND p.userid = u.id
2278                                   AND u.deleted <> '1'
2279                                   AND f.id = d.forum", array($log->info));
2280     }
2281     return NULL;
2284 /**
2285  * Given a discussion id, return the first post from the discussion
2286  *
2287  * @global object
2288  * @global object
2289  * @param int $dicsussionid
2290  * @return array
2291  */
2292 function forum_get_firstpost_from_discussion($discussionid) {
2293     global $CFG, $DB;
2295     return $DB->get_record_sql("SELECT p.*
2296                              FROM {forum_discussions} d,
2297                                   {forum_posts} p
2298                             WHERE d.id = ?
2299                               AND d.firstpost = p.id ", array($discussionid));
2302 /**
2303  * Returns an array of counts of replies to each discussion
2304  *
2305  * @global object
2306  * @global object
2307  * @param int $forumid
2308  * @param string $forumsort
2309  * @param int $limit
2310  * @param int $page
2311  * @param int $perpage
2312  * @return array
2313  */
2314 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2315     global $CFG, $DB;
2317     if ($limit > 0) {
2318         $limitfrom = 0;
2319         $limitnum  = $limit;
2320     } else if ($page != -1) {
2321         $limitfrom = $page*$perpage;
2322         $limitnum  = $perpage;
2323     } else {
2324         $limitfrom = 0;
2325         $limitnum  = 0;
2326     }
2328     if ($forumsort == "") {
2329         $orderby = "";
2330         $groupby = "";
2332     } else {
2333         $orderby = "ORDER BY $forumsort";
2334         $groupby = ", ".strtolower($forumsort);
2335         $groupby = str_replace('desc', '', $groupby);
2336         $groupby = str_replace('asc', '', $groupby);
2337     }
2339     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2340         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2341                   FROM {forum_posts} p
2342                        JOIN {forum_discussions} d ON p.discussion = d.id
2343                  WHERE p.parent > 0 AND d.forum = ?
2344               GROUP BY p.discussion";
2345         return $DB->get_records_sql($sql, array($forumid));
2347     } else {
2348         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2349                   FROM {forum_posts} p
2350                        JOIN {forum_discussions} d ON p.discussion = d.id
2351                  WHERE d.forum = ?
2352               GROUP BY p.discussion $groupby
2353               $orderby";
2354         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2355     }
2358 /**
2359  * @global object
2360  * @global object
2361  * @global object
2362  * @staticvar array $cache
2363  * @param object $forum
2364  * @param object $cm
2365  * @param object $course
2366  * @return mixed
2367  */
2368 function forum_count_discussions($forum, $cm, $course) {
2369     global $CFG, $DB, $USER;
2371     static $cache = array();
2373     $now = round(time(), -2); // db cache friendliness
2375     $params = array($course->id);
2377     if (!isset($cache[$course->id])) {
2378         if (!empty($CFG->forum_enabletimedposts)) {
2379             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2380             $params[] = $now;
2381             $params[] = $now;
2382         } else {
2383             $timedsql = "";
2384         }
2386         $sql = "SELECT f.id, COUNT(d.id) as dcount
2387                   FROM {forum} f
2388                        JOIN {forum_discussions} d ON d.forum = f.id
2389                  WHERE f.course = ?
2390                        $timedsql
2391               GROUP BY f.id";
2393         if ($counts = $DB->get_records_sql($sql, $params)) {
2394             foreach ($counts as $count) {
2395                 $counts[$count->id] = $count->dcount;
2396             }
2397             $cache[$course->id] = $counts;
2398         } else {
2399             $cache[$course->id] = array();
2400         }
2401     }
2403     if (empty($cache[$course->id][$forum->id])) {
2404         return 0;
2405     }
2407     $groupmode = groups_get_activity_groupmode($cm, $course);
2409     if ($groupmode != SEPARATEGROUPS) {
2410         return $cache[$course->id][$forum->id];
2411     }
2413     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2414         return $cache[$course->id][$forum->id];
2415     }
2417     require_once($CFG->dirroot.'/course/lib.php');
2419     $modinfo =& get_fast_modinfo($course);
2420     if (is_null($modinfo->groups)) {
2421         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2422     }
2424     if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2425         $mygroups = $modinfo->groups[$cm->groupingid];
2426     } else {
2427         $mygroups = false; // Will be set below
2428     }
2430     // add all groups posts
2431     if (empty($mygroups)) {
2432         $mygroups = array(-1=>-1);
2433     } else {
2434         $mygroups[-1] = -1;
2435     }
2437     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2438     $params[] = $forum->id;
2440     if (!empty($CFG->forum_enabletimedposts)) {
2441         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2442         $params[] = $now;
2443         $params[] = $now;
2444     } else {
2445         $timedsql = "";
2446     }
2448     $sql = "SELECT COUNT(d.id)
2449               FROM {forum_discussions} d
2450              WHERE d.groupid $mygroups_sql AND d.forum = ?
2451                    $timedsql";
2453     return $DB->get_field_sql($sql, $params);
2456 /**
2457  * How many posts by other users are unrated by a given user in the given discussion?
2458  *
2459  * @global object
2460  * @global object
2461  * @param int $discussionid
2462  * @param int $userid
2463  * @return mixed
2464  */
2465 function forum_count_unrated_posts($discussionid, $userid) {
2466     global $CFG, $DB;
2467     if ($posts = $DB->get_record_sql("SELECT count(*) as num
2468                                    FROM {forum_posts}
2469                                   WHERE parent > 0
2470                                     AND discussion = ?
2471                                     AND userid <> ? ", array($discussionid, $userid))) {
2473         if ($rated = $DB->get_record_sql("SELECT count(*) as num
2474                                        FROM {forum_posts} p,
2475                                             {rating} r
2476                                       WHERE p.discussion = ?
2477                                         AND p.id = r.itemid
2478                                         AND r.userid = ?", array($discussionid, $userid))) {
2479             $difference = $posts->num - $rated->num;
2480             if ($difference > 0) {
2481                 return $difference;
2482             } else {
2483                 return 0;    // Just in case there was a counting error
2484             }
2485         } else {
2486             return $posts->num;
2487         }
2488     } else {
2489         return 0;
2490     }
2493 /**
2494  * Get all discussions in a forum
2495  *
2496  * @global object
2497  * @global object
2498  * @global object
2499  * @uses CONTEXT_MODULE
2500  * @uses VISIBLEGROUPS
2501  * @param object $cm
2502  * @param string $forumsort
2503  * @param bool $fullpost
2504  * @param int $unused
2505  * @param int $limit
2506  * @param bool $userlastmodified
2507  * @param int $page
2508  * @param int $perpage
2509  * @return array
2510  */
2511 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2512     global $CFG, $DB, $USER;
2514     $timelimit = '';
2516     $now = round(time(), -2);
2517     $params = array($cm->instance);
2519     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2521     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2522         return array();
2523     }
2525     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2527         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2528             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2529             $params[] = $now;
2530             $params[] = $now;
2531             if (isloggedin()) {
2532                 $timelimit .= " OR d.userid = ?";
2533                 $params[] = $USER->id;
2534             }
2535             $timelimit .= ")";
2536         }
2537     }
2539     if ($limit > 0) {
2540         $limitfrom = 0;
2541         $limitnum  = $limit;
2542     } else if ($page != -1) {
2543         $limitfrom = $page*$perpage;
2544         $limitnum  = $perpage;
2545     } else {
2546         $limitfrom = 0;
2547         $limitnum  = 0;
2548     }
2550     $groupmode    = groups_get_activity_groupmode($cm);
2551     $currentgroup = groups_get_activity_group($cm);
2553     if ($groupmode) {
2554         if (empty($modcontext)) {
2555             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2556         }
2558         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2559             if ($currentgroup) {
2560                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2561                 $params[] = $currentgroup;
2562             } else {
2563                 $groupselect = "";
2564             }
2566         } else {
2567             //seprate groups without access all
2568             if ($currentgroup) {
2569                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2570                 $params[] = $currentgroup;
2571             } else {
2572                 $groupselect = "AND d.groupid = -1";
2573             }
2574         }
2575     } else {
2576         $groupselect = "";
2577     }
2580     if (empty($forumsort)) {
2581         $forumsort = "d.timemodified DESC";
2582     }
2583     if (empty($fullpost)) {
2584         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2585     } else {
2586         $postdata = "p.*";
2587     }
2589     if (empty($userlastmodified)) {  // We don't need to know this
2590         $umfields = "";
2591         $umtable  = "";
2592     } else {
2593         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2594         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2595     }
2597     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2598                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2599               FROM {forum_discussions} d
2600                    JOIN {forum_posts} p ON p.discussion = d.id
2601                    JOIN {user} u ON p.userid = u.id
2602                    $umtable
2603              WHERE d.forum = ? AND p.parent = 0
2604                    $timelimit $groupselect
2605           ORDER BY $forumsort";
2606     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2609 /**
2610  *
2611  * @global object
2612  * @global object
2613  * @global object
2614  * @uses CONTEXT_MODULE
2615  * @uses VISIBLEGROUPS
2616  * @param object $cm
2617  * @return array
2618  */
2619 function forum_get_discussions_unread($cm) {
2620     global $CFG, $DB, $USER;
2622     $now = round(time(), -2);
2623     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2625     $params = array();
2626     $groupmode    = groups_get_activity_groupmode($cm);
2627     $currentgroup = groups_get_activity_group($cm);
2629     if ($groupmode) {
2630         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2632         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2633             if ($currentgroup) {
2634                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2635                 $params['currentgroup'] = $currentgroup;
2636             } else {
2637                 $groupselect = "";
2638             }
2640         } else {
2641             //separate groups without access all
2642             if ($currentgroup) {
2643                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2644                 $params['currentgroup'] = $currentgroup;
2645             } else {
2646                 $groupselect = "AND d.groupid = -1";
2647             }
2648         }
2649     } else {
2650         $groupselect = "";
2651     }
2653     if (!empty($CFG->forum_enabletimedposts)) {
2654         $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2655         $params['now1'] = $now;
2656         $params['now2'] = $now;
2657     } else {
2658         $timedsql = "";
2659     }
2661     $sql = "SELECT d.id, COUNT(p.id) AS unread
2662               FROM {forum_discussions} d
2663                    JOIN {forum_posts} p     ON p.discussion = d.id
2664                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2665              WHERE d.forum = {$cm->instance}
2666                    AND p.modified >= :cutoffdate AND r.id is NULL
2667                    $groupselect
2668                    $timedsql
2669           GROUP BY d.id";
2670     $params['cutoffdate'] = $cutoffdate;
2672     if ($unreads = $DB->get_records_sql($sql, $params)) {
2673         foreach ($unreads as $unread) {
2674             $unreads[$unread->id] = $unread->unread;
2675         }
2676         return $unreads;
2677     } else {
2678         return array();
2679     }
2682 /**
2683  * @global object
2684  * @global object
2685  * @global object
2686  * @uses CONEXT_MODULE
2687  * @uses VISIBLEGROUPS
2688  * @param object $cm
2689  * @return array
2690  */
2691 function forum_get_discussions_count($cm) {
2692     global $CFG, $DB, $USER;
2694     $now = round(time(), -2);
2695     $params = array($cm->instance);
2696     $groupmode    = groups_get_activity_groupmode($cm);
2697     $currentgroup = groups_get_activity_group($cm);
2699     if ($groupmode) {
2700         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2702         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2703             if ($currentgroup) {
2704                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2705                 $params[] = $currentgroup;
2706             } else {
2707                 $groupselect = "";
2708             }
2710         } else {
2711             //seprate groups without access all
2712             if ($currentgroup) {
2713                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2714                 $params[] = $currentgroup;
2715             } else {
2716                 $groupselect = "AND d.groupid = -1";
2717             }
2718         }
2719     } else {
2720         $groupselect = "";
2721     }
2723     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2725     $timelimit = "";
2727     if (!empty($CFG->forum_enabletimedposts)) {
2729         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2731         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2732             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2733             $params[] = $now;
2734             $params[] = $now;
2735             if (isloggedin()) {
2736                 $timelimit .= " OR d.userid = ?";
2737                 $params[] = $USER->id;
2738             }
2739             $timelimit .= ")";
2740         }
2741     }
2743     $sql = "SELECT COUNT(d.id)
2744               FROM {forum_discussions} d
2745                    JOIN {forum_posts} p ON p.discussion = d.id
2746              WHERE d.forum = ? AND p.parent = 0
2747                    $groupselect $timelimit";
2749     return $DB->get_field_sql($sql, $params);
2753 /**
2754  * Get all discussions started by a particular user in a course (or group)
2755  * This function no longer used ...
2756  *
2757  * @todo Remove this function if no longer used
2758  * @global object
2759  * @global object
2760  * @param int $courseid
2761  * @param int $userid
2762  * @param int $groupid
2763  * @return array
2764  */
2765 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2766     global $CFG, $DB;
2767     $params = array($courseid, $userid);
2768     if ($groupid) {
2769         $groupselect = " AND d.groupid = ? ";
2770         $params[] = $groupid;
2771     } else  {
2772         $groupselect = "";
2773     }
2775     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2776                                    f.type as forumtype, f.name as forumname, f.id as forumid
2777                               FROM {forum_discussions} d,
2778                                    {forum_posts} p,
2779                                    {user} u,
2780                                    {forum} f
2781                              WHERE d.course = ?
2782                                AND p.discussion = d.id
2783                                AND p.parent = 0
2784                                AND p.userid = u.id
2785                                AND u.id = ?
2786                                AND d.forum = f.id $groupselect
2787                           ORDER BY p.created DESC", $params);
2790 /**
2791  * Get the list of potential subscribers to a forum.
2792  *
2793  * @param object $forumcontext the forum context.
2794  * @param integer $groupid the id of a group, or 0 for all groups.
2795  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2796  * @param string $sort sort order. As for get_users_by_capability.
2797  * @return array list of users.
2798  */
2799 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2800     global $DB;
2802     // only active enrolled users or everybody on the frontpage with this capability
2803     list($esql, $params) = get_enrolled_sql($forumcontext, 'mod/forum:initialsubscriptions', $groupid, true);
2805     $sql = "SELECT $fields
2806               FROM {user} u
2807               JOIN ($esql) je ON je.id = u.id";
2808     if ($sort) {
2809         $sql = "$sql ORDER BY $sort";
2810     } else {
2811         $sql = "$sql ORDER BY u.lastname ASC, u.firstname ASC";
2812     }
2814     return $DB->get_records_sql($sql, $params);
2817 /**
2818  * Returns list of user objects that are subscribed to this forum
2819  *
2820  * @global object
2821  * @global object
2822  * @param object $course the course
2823  * @param forum $forum the forum
2824  * @param integer $groupid group id, or 0 for all.
2825  * @param object $context the forum context, to save re-fetching it where possible.
2826  * @param string $fields requested user fields (with "u." table prefix)
2827  * @return array list of users.
2828  */
2829 function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
2830     global $CFG, $DB;
2832     if (empty($fields)) {
2833         $fields ="u.id,
2834                   u.username,
2835                   u.firstname,
2836                   u.lastname,
2837                   u.maildisplay,
2838                   u.mailformat,
2839                   u.maildigest,
2840                   u.imagealt,
2841                   u.email,
2842                   u.city,
2843                   u.country,
2844                   u.lastaccess,
2845                   u.lastlogin,
2846                   u.picture,
2847                   u.timezone,
2848                   u.theme,
2849                   u.lang,
2850                   u.trackforums,
2851                   u.mnethostid";
2852     }
2854     if (empty($context)) {
2855         $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2856         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2857     }
2859     if (forum_is_forcesubscribed($forum)) {
2860         $results = forum_get_potential_subscribers($context, $groupid, $fields, "u.email ASC");
2862     } else {
2863         // only active enrolled users or everybody on the frontpage
2864         list($esql, $params) = get_enrolled_sql($context, '', $groupid, true);
2865         $params['forumid'] = $forum->id;
2866         $results = $DB->get_records_sql("SELECT $fields
2867                                            FROM {user} u
2868                                            JOIN ($esql) je ON je.id = u.id
2869                                            JOIN {forum_subscriptions} s ON s.userid = u.id
2870                                           WHERE s.forum = :forumid
2871                                        ORDER BY u.email ASC", $params);
2872     }
2874     // Guest user should never be subscribed to a forum.
2875     unset($results[$CFG->siteguest]);
2877     return $results;
2882 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2885 /**
2886  * @global object
2887  * @global object
2888  * @param int $courseid
2889  * @param string $type
2890  */
2891 function forum_get_course_forum($courseid, $type) {
2892 // How to set up special 1-per-course forums
2893     global $CFG, $DB, $OUTPUT;
2895     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2896         // There should always only be ONE, but with the right combination of
2897         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2898         foreach ($forums as $forum) {
2899             return $forum;   // ie the first one
2900         }
2901     }
2903     // Doesn't exist, so create one now.
2904     $forum->course = $courseid;
2905     $forum->type = "$type";
2906     switch ($forum->type) {
2907         case "news":
2908             $forum->name  = get_string("namenews", "forum");
2909             $forum->intro = get_string("intronews", "forum");
2910             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2911             $forum->assessed = 0;
2912             if ($courseid == SITEID) {
2913                 $forum->name  = get_string("sitenews");
2914                 $forum->forcesubscribe = 0;
2915             }
2916             break;
2917         case "social":
2918             $forum->name  = get_string("namesocial", "forum");
2919             $forum->intro = get_string("introsocial", "forum");
2920             $forum->assessed = 0;
2921             $forum->forcesubscribe = 0;
2922             break;
2923         case "blog":
2924             $forum->name = get_string('blogforum', 'forum');
2925             $forum->intro = get_string('introblog', 'forum');
2926             $forum->assessed = 0;
2927             $forum->forcesubscribe = 0;
2928             break;
2929         default:
2930             echo $OUTPUT->notification("That forum type doesn't exist!");
2931             return false;
2932             break;
2933     }
2935     $forum->timemodified = time();
2936     $forum->id = $DB->insert_record("forum", $forum);
2938     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2939         echo $OUTPUT->notification("Could not find forum module!!");
2940         return false;
2941     }
2942     $mod = new stdClass();
2943     $mod->course = $courseid;
2944     $mod->module = $module->id;
2945     $mod->instance = $forum->id;
2946     $mod->section = 0;
2947     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
2948         echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
2949         return false;
2950     }
2951     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
2952         echo $OUTPUT->notification("Could not add the new course module to that section");
2953         return false;
2954     }
2955     $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule));
2957     include_once("$CFG->dirroot/course/lib.php");
2958     rebuild_course_cache($courseid);
2960     return $DB->get_record("forum", array("id" => "$forum->id"));
2964 /**
2965  * Given the data about a posting, builds up the HTML to display it and
2966  * returns the HTML in a string.  This is designed for sending via HTML email.
2967  *
2968  * @global object
2969  * @param object $course
2970  * @param object $cm
2971  * @param object $forum
2972  * @param object $discussion
2973  * @param object $post
2974  * @param object $userform
2975  * @param object $userto
2976  * @param bool $ownpost
2977  * @param bool $reply
2978  * @param bool $link
2979  * @param bool $rate
2980  * @param string $footer
2981  * @return string
2982  */
2983 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
2984                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
2986     global $CFG, $OUTPUT;
2988     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2990     if (!isset($userto->viewfullnames[$forum->id])) {
2991         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
2992     } else {
2993         $viewfullnames = $userto->viewfullnames[$forum->id];
2994     }
2996     // add absolute file links
2997     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
2999     // format the post body
3000     $options = new stdClass();
3001     $options->para = true;
3002     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3004     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3006     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3007     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3008     $output .= '</td>';
3010     if ($post->parent) {
3011         $output .= '<td class="topic">';
3012     } else {
3013         $output .= '<td class="topic starter">';
3014     }
3015     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3017     $fullname = fullname($userfrom, $viewfullnames);
3018     $by = new stdClass();
3019     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3020     $by->date = userdate($post->modified, '', $userto->timezone);
3021     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3023     $output .= '</td></tr>';
3025     $output .= '<tr><td class="left side" valign="top">';
3027     if (isset($userfrom->groups)) {
3028         $groups = $userfrom->groups[$forum->id];
3029     } else {
3030         $groups = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3031     }
3033     if ($groups) {
3034         $output .= print_group_picture($groups, $course->id, false, true, true);
3035     } else {
3036         $output .= '&nbsp;';
3037     }
3039     $output .= '</td><td class="content">';
3041     $attachments = forum_print_attachments($post, $cm, 'html');
3042     if ($attachments !== '') {
3043         $output .= '<div class="attachments">';
3044         $output .= $attachments;
3045         $output .= '</div>';
3046     }
3048     $output .= $formattedtext;
3050 // Commands
3051     $commands = array();
3053     if ($post->parent) {
3054         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3055                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3056     }
3058     if ($reply) {
3059         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3060                       get_string('reply', 'forum').'</a>';
3061     }
3063     $output .= '<div class="commands">';
3064     $output .= implode(' | ', $commands);
3065     $output .= '</div>';
3067 // Context link to post if required
3068     if ($link) {
3069         $output .= '<div class="link">';
3070         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3071                      get_string('postincontext', 'forum').'</a>';
3072         $output .= '</div>';
3073     }
3075     if ($footer) {
3076         $output .= '<div class="footer">'.$footer.'</div>';
3077     }
3078     $output .= '</td></tr></table>'."\n\n";
3080     return $output;
3083 /**
3084  * Print a forum post
3085  *
3086  * @global object
3087  * @global object
3088  * @uses FORUM_MODE_THREADED
3089  * @uses PORTFOLIO_FORMAT_PLAINHTML
3090  * @uses PORTFOLIO_FORMAT_FILE
3091  * @uses PORTFOLIO_FORMAT_RICHHTML
3092  * @uses PORTFOLIO_ADD_TEXT_LINK
3093  * @uses CONTEXT_MODULE
3094  * @param object $post The post to print.
3095  * @param object $discussion
3096  * @param object $forum
3097  * @param object $cm
3098  * @param object $course
3099  * @param boolean $ownpost Whether this post belongs to the current user.
3100  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3101  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3102  * @param string $footer Extra stuff to print after the message.
3103  * @param string $highlight Space-separated list of terms to highlight.
3104  * @param int $post_read true, false or -99. If we already know whether this user
3105  *          has read this post, pass that in, otherwise, pass in -99, and this
3106  *          function will work it out.
3107  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3108  *          the current user can't see this post, if this argument is true
3109  *          (the default) then print a dummy 'you can't see this post' post.
3110  *          If false, don't output anything at all.
3111  * @param bool|null $istracked
3112  * @return void
3113  */
3114 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3115                           $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3116     global $USER, $CFG, $OUTPUT;
3118     require_once($CFG->libdir . '/filelib.php');
3120     // String cache
3121     static $str;
3123     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3125     $post->course = $course->id;
3126     $post->forum  = $forum->id;
3127     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3129     // caching
3130     if (!isset($cm->cache)) {
3131         $cm->cache = new stdClass;
3132     }
3134     if (!isset($cm->cache->caps)) {
3135         $cm->cache->caps = array();
3136         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3137         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3138         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3139         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3140         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3141         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3142         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3143         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3144         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3145     }
3147     if (!isset($cm->uservisible)) {
3148         $cm->uservisible = coursemodule_visible_for_user($cm);
3149     }
3151     if ($istracked && is_null($postisread)) {
3152         $postisread = forum_tp_is_post_read($USER->id, $post);
3153     }
3155     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3156         $output = '';
3157         if (!$dummyifcantsee) {
3158             if ($return) {
3159                 return $output;
3160             }
3161             echo $output;
3162             return;
3163         }
3164         $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3165         $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'));
3166         $output .= html_writer::start_tag('div', array('class'=>'row header'));
3167         $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3168         if ($post->parent) {
3169             $output .= html_writer::start_tag('div', array('class'=>'topic'));
3170         } else {
3171             $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3172         }
3173         $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class'=>'subject')); // Subject
3174         $output .= html_writer::tag('div', get_string('forumauthorhidden','forum'), array('class'=>'author')); // author
3175         $output .= html_writer::end_tag('div');
3176         $output .= html_writer::end_tag('div'); // row
3177         $output .= html_writer::start_tag('div', array('class'=>'row'));
3178         $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3179         $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3180         $output .= html_writer::end_tag('div'); // row
3181         $output .= html_writer::end_tag('div'); // forumpost
3183         if ($return) {
3184             return $output;
3185         }
3186         echo $output;
3187         return;
3188     }
3190     if (empty($str)) {
3191         $str = new stdClass;
3192         $str->edit         = get_string('edit', 'forum');
3193         $str->delete       = get_string('delete', 'forum');
3194         $str->reply        = get_string('reply', 'forum');
3195         $str->parent       = get_string('parent', 'forum');
3196         $str->pruneheading = get_string('pruneheading', 'forum');
3197         $str->prune        = get_string('prune', 'forum');
3198         $str->displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3199         $str->markread     = get_string('markread', 'forum');
3200         $str->markunread   = get_string('markunread', 'forum');
3201     }
3203     $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3205     // Build an object that represents the posting user
3206     $postuser = new stdClass;
3207     $postuser->id        = $post->userid;
3208     $postuser->firstname = $post->firstname;
3209     $postuser->lastname  = $post->lastname;
3210     $postuser->imagealt  = $post->imagealt;
3211     $postuser->picture   = $post->picture;
3212     $postuser->email     = $post->email;
3213     // Some handy things for later on
3214     $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3215     $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3217     // Prepare the groups the posting user belongs to
3218     if (isset($cm->cache->usersgroups)) {
3219         $groups = array();
3220         if (isset($cm->cache->usersgroups[$post->userid])) {
3221             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3222                 $groups[$gid] = $cm->cache->groups[$gid];
3223             }
3224         }
3225     } else {
3226         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3227     }
3229     // Prepare the attachements for the post, files then images
3230     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3232     // Determine if we need to shorten this post
3233     $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3236     // Prepare an array of commands
3237     $commands = array();
3239     // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3240     // Don't display the mark read / unread controls in this case.
3241     if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3242         $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3243         $text = $str->markunread;
3244         if (!$postisread) {
3245             $url->param('mark', 'read');
3246             $text = $str->markread;
3247         }
3248         if ($str->displaymode == FORUM_MODE_THREADED) {
3249             $url->param('parent', $post->parent);
3250         } else {
3251             $url->set_anchor('p'.$post->id);
3252         }
3253         $commands[] = array('url'=>$url, 'text'=>$text);
3254     }
3256     // Zoom in to the parent specifically
3257     if ($post->parent) {
3258         $url = new moodle_url($discussionlink);
3259         if ($str->displaymode == FORUM_MODE_THREADED) {
3260             $url->param('parent', $post->parent);
3261         } else {
3262             $url->set_anchor('p'.$post->parent);
3263         }
3264         $commands[] = array('url'=>$url, 'text'=>$str->parent);
3265     }
3267     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3268     $age = time() - $post->created;
3269     if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3270         $age = 0;
3271     }
3272     if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3273         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3274     }
3276     if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3277         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3278     }
3280     if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3281         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3282     }
3284     if ($reply) {
3285         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('reply'=>$post->id)), 'text'=>$str->reply);
3286     }
3288     if ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost'])) {
3289         $p = array('postid' => $post->id);
3290         require_once($CFG->libdir.'/portfoliolib.php');
3291         $button = new portfolio_add_button();
3292         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php');
3293         if (empty($attachments)) {
3294             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3295         } else {
3296             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3297         }
3299         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3300         if (!empty($porfoliohtml)) {
3301             $commands[] = $porfoliohtml;
3302         }
3303     }
3304     // Finished building commands
3307     // Begin output
3309     $output  = '';
3311     if ($istracked) {
3312         if ($postisread) {
3313             $forumpostclass = ' read';
3314         } else {
3315             $forumpostclass = ' unread';
3316             $output .= html_writer::tag('a', '', array('name'=>'unread'));
3317         }
3318     } else {
3319         // ignore trackign status if not tracked or tracked param missing
3320         $forumpostclass = '';
3321     }
3323     $topicclass = '';
3324     if (empty($post->parent)) {
3325         $topicclass = ' firstpost starter';
3326     }
3328     $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3329     $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass));
3330     $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3331     $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3332     $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3333     $output .= html_writer::end_tag('div');
3336     $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3338     $postsubject = $post->subject;
3339     if (empty($post->subjectnoformat)) {
3340         $postsubject = format_string($postsubject);
3341     }
3342     $output .= html_writer::tag('div', $postsubject, array('class'=>'subject'));
3344     $by = new stdClass();
3345     $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3346     $by->date = userdate($post->modified);
3347     $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author'));
3349     $output .= html_writer::end_tag('div'); //topic
3350     $output .= html_writer::end_tag('div'); //row
3352     $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3353     $output .= html_writer::start_tag('div', array('class'=>'left'));
3355     $groupoutput = '';
3356     if ($groups) {
3357         $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3358     }
3359     if (empty($groupoutput)) {
3360         $groupoutput = '&nbsp;';
3361     }
3362     $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3364     $output .= html_writer::end_tag('div'); //left side
3365     $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3366     $output .= html_writer::start_tag('div', array('class'=>'content'));
3367     if (!empty($attachments)) {
3368         $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3369     }
3371     $options = new stdClass;
3372     $options->para    = false;
3373     $options->trusted = $post->messagetrust;
3374     $options->context = $modcontext;
3375     if ($shortenpost) {
3376         // Prepare shortened version
3377         $postclass    = 'shortenedpost';
3378         $postcontent  = format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3379         $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
3380         $postcontent .= html_writer::tag('span', '('.get_string('numwords', 'moodle', count_words(strip_tags($post->message))).')...', array('class'=>'post-word-count'));
3381     } else {
3382         // Prepare whole post
3383         $postclass    = 'fullpost';
3384         $postcontent  = format_text($post->message, $post->messageformat, $options, $course->id);
3385         if (!empty($highlight)) {
3386             $postcontent = highlight($highlight, $postcontent);
3387         }
3388         $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
3389     }
3390     // Output the post content
3391     $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
3392     $output .= html_writer::end_tag('div'); // Content
3393     $output .= html_writer::end_tag('div'); // Content mask
3394     $output .= html_writer::end_tag('div'); // Row
3396     $output .= html_writer::start_tag('div', array('class'=>'row side'));
3397     $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
3398     $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
3400     // Output ratings
3401     if (!empty($post->rating)) {
3402         $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
3403     }
3405     // Output the commands
3406     $commandhtml = array();
3407     foreach ($commands as $command) {
3408         if (is_array($command)) {
3409             $commandhtml[] = html_writer::link($command['url'], $command['text']);
3410         } else {
3411             $commandhtml[] = $command;
3412         }
3413     }
3414     $output .= html_writer::tag('div', implode(' | ', $commandhtml), array('class'=>'commands'));
3416     // Output link to post if required
3417     if ($link) {
3418         if ($post->replies == 1) {
3419             $replystring = get_string('repliesone', 'forum', $post->replies);
3420         } else {
3421             $replystring = get_string('repliesmany', 'forum', $post->replies);
3422         }
3424         $output .= html_writer::start_tag('div', array('class'=>'link'));
3425         $output .= html_writer::link($discussionlink, get_string('discussthistopic', 'forum'));
3426         $output .= '&nbsp;('.$replystring.')';
3427         $output .= html_writer::end_tag('div'); // link
3428     }
3430     // Output footer if required
3431     if ($footer) {
3432         $output .= html_writer::tag('div', $footer, array('class'=>'footer'));
3433     }
3435     // Close remaining open divs
3436     $output .= html_writer::end_tag('div'); // content
3437     $output .= html_writer::end_tag('div'); // row
3438     $output .= html_writer::end_tag('div'); // forumpost
3440     // Mark the forum post as read if required
3441     if ($istracked && !$CFG->forum_usermarksread && !$postisread) {
3442         forum_tp_mark_post_read($USER->id, $post, $forum->id);
3443     }
3445     if ($return) {
3446         return $output;
3447     }
3448     echo $output;
3449     return;
3452 /**
3453  * Return rating related permissions
3454  * @param string $options the context id
3455  * @return array an associative array of the user's rating permissions
3456  */
3457 function forum_rating_permissions($contextid) {
3458     $context = get_context_instance_by_id($contextid);
3460     if (!$context) {
3461         print_error('invalidcontext');
3462         return null;
3463     } else {
3464         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));
3465     }
3468 /**
3469  * Validates a submitted rating
3470  * @param array $params submitted data
3471  *            context => object the context in which the rated items exists [required]
3472  *            itemid => int the ID of the object being rated [required]
3473  *            scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
3474  *            rating => int the submitted rating [required]
3475  *            rateduserid => int the id of the user whose items have been rated. NOT the user who submitted the ratings. 0 to update all. [required]
3476  *            aggregation => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [required]
3477  * @return boolean true if the rating is valid. Will throw rating_exception if not
3478  */
3479 function forum_rating_validate($params) {
3480     global $DB, $USER;
3482     if (!array_key_exists('itemid', $params)
3483             || !array_key_exists('context', $params)
3484             || !array_key_exists('rateduserid', $params)
3485             || !array_key_exists('scaleid', $params)) {
3486         throw new rating_exception('missingparameter');
3487     }
3489     $forumsql = "SELECT f.id as fid, f.course, f.scale, d.id as did, p.userid as userid, p.created, f.assesstimestart, f.assesstimefinish, d.groupid
3490                    FROM {forum_posts} p
3491                    JOIN {forum_discussions} d ON p.discussion = d.id
3492                    JOIN {forum} f ON d.forum = f.id
3493                   WHERE p.id = :itemid";
3494     $forumparams = array('itemid'=>$params['itemid']);
3495     if (!$info = $DB->get_record_sql($forumsql, $forumparams)) {
3496         //item doesn't exist
3497         throw new rating_exception('invaliditemid');
3498     }
3500     if ($info->scale != $params['scaleid']) {
3501         //the scale being submitted doesnt match the one in the database
3502         throw new rating_exception('invalidscaleid');
3503     }
3505     if ($info->userid == $USER->id) {
3506         //user is attempting to rate their own post
3507         throw new rating_exception('nopermissiontorate');
3508     }
3510     if ($info->userid != $params['rateduserid']) {
3511         //supplied user ID doesnt match the user ID from the database
3512         throw new rating_exception('invaliduserid');
3513     }
3515     //check that the submitted rating is valid for the scale
3516     if ($params['rating'] < 0) {
3517         throw new rating_exception('invalidnum');
3518     } else if ($info->scale < 0) {
3519         //its a custom scale
3520         $scalerecord = $DB->get_record('scale', array('id' => -$params['scaleid']));
3521         if ($scalerecord) {
3522             $scalearray = explode(',', $scalerecord->scale);
3523             if ($params['rating'] > count($scalearray)) {
3524                 throw new rating_exception('invalidnum');
3525             }
3526         } else {
3527             throw new rating_exception('invalidscaleid');
3528         }
3529     } else if ($params['rating'] > $info->scale) {
3530         //if its numeric and submitted rating is above maximum
3531         throw new rating_exception('invalidnum');
3532     }
3534     //check the item we're rating was created in the assessable time window
3535     if (!empty($info->assesstimestart) && !empty($info->assesstimefinish)) {
3536         if ($info->timecreated < $info->assesstimestart || $info->timecreated > $info->assesstimefinish) {
3537             throw new rating_exception('notavailable');
3538         }
3539     }
3541     $forumid = $info->fid;
3542     $discussionid = $info->did;
3543     $groupid = $info->groupid;
3544     $courseid = $info->course;
3546     $cm = get_coursemodule_from_instance('forum', $forumid);
3547     if (empty($cm)) {
3548         throw new rating_exception('unknowncontext');
3549     }
3550     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
3552     //if the supplied context doesnt match the item's context
3553     if (empty($context) || $context->id != $params['context']->id) {
3554         throw new rating_exception('invalidcontext');
3555     }
3557     // Make sure groups allow this user to see the item they're rating
3558     $course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
3559     if ($groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
3560         if (!groups_group_exists($groupid)) { // Can't find group
3561             throw new rating_exception('cannotfindgroup');//something is wrong
3562         }
3564         if (!groups_is_member($groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
3565             // do not allow rating of posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
3566             throw new rating_exception('notmemberofgroup');
3567         }
3568     }
3570     //need to load the full objects here as ajax scripts don't like
3571     //the debugging messages produced by forum_user_can_see_post() if you just supply IDs
3572     if (!$forum = $DB->get_record('forum',array('id'=>$forumid))) {
3573         throw new rating_exception('invalidrecordunknown');
3574     }
3575     if (!$post = $DB->get_record('forum_posts',array('id'=>$params['itemid']))) {
3576         throw new rating_exception('invalidrecordunknown');
3577     }
3578     if (!$discussion = $DB->get_record('forum_discussions',array('id'=>$discussionid))) {
3579         throw new rating_exception('invalidrecordunknown');
3580     }
3582     //perform some final capability checks
3583     if( !forum_user_can_see_post($forum, $discussion, $post, $USER, $cm)) {
3584         throw new rating_exception('nopermissiontorate');
3585     }
3587     return true;
3591 /**
3592  * This function prints the overview of a discussion in the forum listing.
3593  * It needs some discussion information and some post information, these
3594  * happen to be combined for efficiency in the $post parameter by the function
3595  * that calls this one: forum_print_latest_discussions()
3596  *
3597  * @global object
3598  * @global object
3599  * @param object $post The post object (passed by reference for speed).
3600  * @param object $forum The forum object.
3601  * @param int $group Current group.
3602  * @param string $datestring Format to use for the dates.
3603  * @param boolean $cantrack Is tracking enabled for this forum.
3604  * @param boolean $forumtracked Is the user tracking this forum.
3605  * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
3606  */
3607 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
3608                                         $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) {
3610     global $USER, $CFG, $OUTPUT;
3612     static $rowcount;
3613     static $strmarkalldread;
3615     if (empty($modcontext)) {
3616         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3617             print_error('invalidcoursemodule');
3618         }
3619         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3620     }
3622     if (!isset($rowcount)) {
3623         $rowcount = 0;
3624         $strmarkalldread = get_string('markalldread', 'forum');
3625     } else {
3626         $rowcount = ($rowcount + 1) % 2;
3627     }
3629     $post->subject = format_string($post->subject,true);
3631     echo "\n\n";
3632     echo '<tr class="discussion r'.$rowcount.'">';
3634     // Topic
3635     echo '<td class="topic starter">';
3636     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.$post->subject.'</a>';
3637     echo "</td>\n";
3639     // Picture
3640     $postuser = new stdClass();
3641     $postuser->id = $post->userid;
3642     $postuser->firstname = $post->firstname;
3643     $postuser->lastname = $post->lastname;
3644     $postuser->imagealt = $post->imagealt;
3645     $postuser->picture = $post->picture;
3646     $postuser->email = $post->email;
3648     echo '<td class="picture">';
3649     echo $OUTPUT->user_picture($postuser, array('courseid'=>$forum->course));
3650     echo "</td>\n";
3652     // User name
3653     $fullname = fullname($post, has_capability('moodle/site:viewfullnames', $modcontext));
3654     echo '<td class="author">';
3655     echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->userid.'&amp;course='.$forum->course.'">'.$fullname.'</a>';
3656     echo "</td>\n";
3658     // Group picture
3659     if ($group !== -1) {  // Groups are active - group is a group data object or NULL
3660         echo '<td class="picture group">';
3661         if (!empty($group->picture) and empty($group->hidepicture)) {
3662             print_group_picture($group, $forum->course, false, false, true);
3663         } else if (isset($group->id)) {
3664             if($canviewparticipants) {
3665                 echo '<a href="'.$CFG->wwwroot.'/user/index.php?id='.$forum->course.'&amp;group='.$group->id.'">'.$group->name.'</a>';
3666             } else {
3667                 echo $group->name;
3668             }
3669         }
3670         echo "</td>\n";
3671     }
3673     if (has_capability('mod/forum:viewdiscussion', $modcontext)) {   // Show the column with replies
3674         echo '<td class="replies">';
3675         echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3676         echo $post->replies.'</a>';
3677         echo "</td>\n";
3679         if ($cantrack) {
3680             echo '<td class="replies">';
3681             if ($forumtracked) {
3682                 if ($post->unread > 0) {
3683                     echo '<span class="unread">';
3684                     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#unread">';
3685                     echo $post->unread;
3686                     echo '</a>';
3687                     echo '<a title="'.$strmarkalldread.'" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
3688                          $forum->id.'&amp;d='.$post->discussion.'&amp;mark=read&amp;returnpage=view.php">' .
3689                          '<img src="'.$OUTPUT->pix_url('t/clear') . '" class="iconsmall" alt="'.$strmarkalldread.'" /></a>';
3690                     echo '</span>';
3691                 } else {
3692                     echo '<span class="read">';
3693                     echo $post->unread;
3694                     echo '</span>';
3695                 }
3696             } else {
3697                 echo '<span class="read">';
3698                 echo '-';
3699                 echo '</span>';
3700             }
3701             echo "</td>\n";
3702         }
3703     }
3705     echo '<td class="lastpost">';
3706     $usedate = (empty($post->timemodified)) ? $post->modified : $post->timemodified;  // Just in case
3707     $parenturl = (empty($post->lastpostid)) ? '' : '&amp;parent='.$post->lastpostid;
3708     $usermodified = new stdClass();
3709     $usermodified->id        = $post->usermodified;
3710     $usermodified->firstname = $post->umfirstname;
3711     $usermodified->lastname  = $post->umlastname;
3712     echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->usermodified.'&amp;course='.$forum->course.'">'.
3713          fullname($usermodified).'</a><br />';
3714     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.$parenturl.'">'.
3715           userdate($usedate, $datestring).'</a>';
3716     echo "</td>\n";
3718     echo "</tr>\n\n";
3723 /**
3724  * Given a post object that we already know has a long message
3725  * this function truncates the message nicely to the first
3726  * sane place between $CFG->forum_longpost and $CFG->forum_shortpost
3727  *
3728  * @global object
3729  * @param string $message
3730  * @return string
3731  */
3732 function forum_shorten_post($message) {
3734    global $CFG;
3736    $i = 0;
3737    $tag = false;
3738    $length = strlen($message);
3739    $count = 0;
3740    $stopzone = false;
3741    $truncate = 0;
3743    for ($i=0; $i<$length; $i++) {
3744        $char = $message[$i];
3746        switch ($char) {
3747            case "<":
3748                $tag = true;
3749                break;
3750            case ">":
3751                $tag = false;
3752                break;
3753            default:
3754                if (!$tag) {
3755                    if ($stopzone) {
3756                        if ($char == ".") {
3757                            $truncate = $i+1;
3758                            break 2;
3759                        }
3760                    }
3761                    $count++;
3762                }
3763                break;
3764        }
3765        if (!$stopzone) {
3766            if ($count > $CFG->forum_shortpost) {
3767                $stopzone = true;
3768            }
3769        }
3770    }
3772    if (!$truncate) {
3773        $truncate = $i;
3774    }
3776    return substr($message, 0, $truncate);
3779 /**
3780  * Print the drop down that allows the user to select how they want to have
3781  * the discussion displayed.
3782  *
3783  * @param int $id forum id if $forumtype is 'single',
3784  *              discussion id for any other forum type
3785  * @param mixed $mode forum layout mode
3786  * @param string $forumtype optional
3787  */
3788 function forum_print_mode_form($id, $mode, $forumtype='') {
3789     global $OUTPUT;
3790     if ($forumtype == 'single') {
3791         $select = new single_select(new moodle_url("/mod/forum/view.php", array('f'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode");
3792         $select->class = "forummode";
3793     } else {
3794         $select = new single_select(new moodle_url("/mod/forum/discuss.php", array('d'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode");
3795     }
3796     echo $OUTPUT->render($select);
3799 /**
3800  * @global object
3801  * @param object $course
3802  * @param string $search
3803  * @return string
3804  */
3805 function forum_search_form($course, $search='') {
3806     global $CFG, $OUTPUT;
3808     $output  = '<div class="forumsearch">';
3809     $output .= '<form action="'.$CFG->wwwroot.'/mod/forum/search.php" style="display:inline">';
3810     $output .= '<fieldset class="invisiblefieldset">';
3811     $output .= $OUTPUT->help_icon('search');
3812     $output .= '<label class="accesshide" for="search" >'.get_string('search', 'forum').'</label>';
3813     $output .= '<input id="search" name="search" type="text" size="18" value="'.s($search, true).'" alt="search" />';
3814     $output .= '<label class="accesshide" for="searchforums" >'.get_string('searchforums', 'forum').'</label>';
3815     $output .= '<input id="searchforums" value="'.get_string('searchforums', 'forum').'" type="submit" />';
3816     $output .= '<input name="id" type="hidden" value="'.$course->id.'" />';
3817     $output .= '</fieldset>';
3818     $output .= '</form>';
3819     $output .= '</div>';
3821     return $output;
3825 /**
3826  * @global object
3827  * @global object
3828  */
3829 function forum_set_return() {
3830     global $CFG, $SESSION;
3832     if (! isset($SESSION->fromdiscussion)) {
3833         if (!empty($_SERVER['HTTP_REFERER'])) {
3834             $referer = $_SERVER['HTTP_REFERER'];
3835         } else {
3836             $referer = "";
3837         }
3838         // If the referer is NOT a login screen then save it.
3839         if (! strncasecmp("$CFG->wwwroot/login", $referer, 300)) {
3840             $SESSION->fromdiscussion = $_SERVER["HTTP_REFERER"];
3841         }
3842     }
3846 /**
3847  * @global object
3848  * @param string $default
3849  * @return string
3850  */
3851 function forum_go_back_to($default) {
3852     global $SESSION;
3854     if (!empty($SESSION->fromdiscussion)) {
3855         $returnto = $SESSION->fromdiscussion;
3856         unset($SESSION->fromdiscussion);
3857         return $returnto;
3858     } else {
3859         return $default;
3860     }
3863 /**
3864  * Given a discussion object that is being moved to $forumto,
3865  * this function checks all posts in that discussion
3866  * for attachments, and if any are found, these are
3867  * moved to the new forum directory.
3868  *
3869  * @global object
3870  * @param object $discussion
3871  * @param int $forumfrom source forum id
3872  * @param int $forumto target forum id
3873  * @return bool success
3874  */
3875 function forum_move_attachments($discussion, $forumfrom, $forumto) {
3876     global $DB;
3878     $fs = get_file_storage();
3880     $newcm = get_coursemodule_from_instance('forum', $forumto);
3881     $oldcm = get_coursemodule_from_instance('forum', $forumfrom);
3883     $newcontext = get_context_instance(CONTEXT_MODULE, $newcm->id);
3884     $oldcontext = get_context_instance(CONTEXT_MODULE, $oldcm->id);
3886     // loop through all posts, better not use attachment flag ;-)
3887     if ($posts = $DB->get_records('forum_posts', array('discussion'=>$discussion->id), '', 'id, attachment')) {
3888         foreach ($posts as $post) {
3889             $fs->move_area_files_to_new_context($oldcontext->id,
3890                     $newcontext->id, 'mod_forum', 'post', $post->id);
3891             $attachmentsmoved = $fs->move_area_files_to_new_context($oldcontext->id,
3892                     $newcontext->id, 'mod_forum', 'attachment', $post->id);
3893             if ($attachmentsmoved > 0 && $post->attachment != '1') {
3894                 // Weird - let's fix it
3895                 $post->attachment = '1';
3896                 $DB->update_record('forum_posts', $post);
3897             } else if ($attachmentsmoved == 0 && $post->attachment != '') {
3898                 // Weird - let's fix it
3899                 $post->attachment = '';
3900                 $DB->update_record('forum_posts', $post);
3901             }
3902         }
3903     }
3905     return true;
3908 /**
3909  * Returns attachments as formated text/html optionally with separate images
3910  *
3911  * @global object
3912  * @global object
3913  * @global object
3914  * @param object $post
3915  * @param object $cm
3916  * @param string $type html/text/separateimages
3917  * @return mixed string or array of (html text withouth images and image HTML)
3918  */
3919 function forum_print_attachments($post, $cm, $type) {
3920     global $CFG, $DB, $USER, $OUTPUT;
3922     if (empty($post->attachment)) {
3923         return $type !== 'separateimages' ? '' : array('', '');
3924     }
3926     if (!in_array($type, array('separateimages', 'html', 'text'))) {
3927         return $type !== 'separateimages' ? '' : array('', '');
3928     }
3930     if (!$context = get_context_instance(CONTEXT_MODULE, $cm->id)) {
3931         return $type !== 'separateimages' ? '' : array('', '');
3932     }
3933     $strattachment = get_string('attachment', 'forum');
3935     $fs = get_file_storage();
3937     $imagereturn = '';
3938     $output = '';
3940     $canexport = (has_capability('mod/forum:exportpost', $context) || ($post->userid == $USER->id && has_capability('mod/forum:exportownpost', $context)));
3942     require_once($CFG->libdir.'/portfoliolib.php');
3943     if ($files = $fs->get_area_files($context->id, 'mod_forum', 'attachment', $post->id, "timemodified", false)) {
3944         $button = new portfolio_add_button();
3945         foreach ($files as $file) {
3946             $filename = $file->get_filename();
3947             $mimetype = $file->get_mimetype();
3948             $iconimage = '<img src="'.$OUTPUT->pix_url(file_mimetype_icon($mimetype)).'" class="icon" alt="'.$mimetype.'" />';
3949             $path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$context->id.'/mod_forum/attachment/'.$post->id.'/'.$filename);
3951             if ($type == 'html') {
3952                 $output .= "<a href=\"$path\">$iconimage</a> ";
3953                 $output .= "<a href=\"$path\">".s($filename)."</a>";
3954                 if ($canexport) {
3955                     $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php');
3956                     $button->set_format_by_file($file);
3957                     $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
3958                 }
3959                 $output .= "<br />";
3961             } else if ($type == 'text') {
3962                 $output .= "$strattachment ".s($filename).":\n$path\n";
3964             } else { //'returnimages'
3965                 if (in_array($mimetype, array('image/gif', 'image/jpeg', 'image/png'))) {
3966                     // Image attachments don't get printed as links
3967                     $imagereturn .= "<br /><img src=\"$path\" alt=\"\" />";
3968                     if ($canexport) {
3969                         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php');
3970                         $button->set_format_by_file($file);
3971                         $imagereturn .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
3972                     }
3973                 } else {
3974                     $output .= "<a href=\"$path\">$iconimage</a> ";
3975                     $output .= format_text("<a href=\"$path\">".s($filename)."</a>", FORMAT_HTML, array('context'=>$context));
3976                     if ($canexport) {
3977                         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php');
3978                         $button->set_format_by_file($file);
3979                         $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
3980                     }
3981                     $output .= '<br />';
3982                 }
3983             }
3984         }
3985     }
3987     if ($type !== 'separateimages') {
3988         return $output;
3990     } else {
3991         return array($output, $imagereturn);
3992     }
3995 /**
3996  * Lists all browsable file areas
3997  *
3998  * @param object $course
3999  * @param object $cm
4000  * @param object $context
4001  * @return array
4002  */
4003 function forum_get_file_areas($course, $cm, $context) {
4004     $areas = array();
4005     return $areas;
4008 /**
4009  * Serves the forum attachments. Implements needed access control ;-)
4010  *
4011  * @param object $course
4012  * @param object $cm
4013  * @param object $context
4014  * @param string $filearea
4015  * @param array $args
4016  * @param bool $forcedownload
4017  * @return bool false if file not found, does not return if found - justsend the file
4018  */
4019 function forum_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) {
4020     global $CFG, $DB;
4022     if ($context->contextlevel != CONTEXT_MODULE) {
4023         return false;
4024     }
4026     require_course_login($course, true, $cm);
4028     $fileareas = array('attachment', 'post');
4029     if (!in_array($filearea, $fileareas)) {
4030         return false;
4031     }
4033     $postid = (int)array_shift($args);
4035     if (!$post = $DB->get_record('forum_posts', array('id'=>$postid))) {
4036         return false;
4037     }
4039     if (!$discussion = $DB->get_record('forum_discussions', array('id'=>$post->discussion))) {
4040         return false;
4041     }
4043     if (!$forum = $DB->get_record('forum', array('id'=>$cm->instance))) {
4044         return false;
4045     }
4047     $fs = get_file_storage();
4048     $relativepath = implode('/', $args);
4049     $fullpath = "/$context->id/mod_forum/$filearea/$postid/$relativepath";
4050     if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
4051         return false;
4052     }
4054     // Make sure groups allow this user to see this file
4055     if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
4056         if (!groups_group_exists($discussion->groupid)) { // Can't find group
4057             return false;                           // Be safe and don't send it to anyone
4058         }
4060         if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
4061             // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
4062             return false;
4063         }
4064     }
4066     // Make sure we're allowed to see it...
4067     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
4068         return false;
4069     }
4072     // finally send the file
4073     send_stored_file($file, 0, 0, true); // download MUST be forced - security!
4076 /**
4077  * If successful, this function returns the name of the file
4078  *
4079  * @global object
4080  * @param object $post is a full post record, including course and forum
4081  * @param object $forum
4082  * @param object $cm
4083  * @param mixed $mform
4084  * @param string $message
4085  * @return bool
4086  */
4087 function forum_add_attachment($post, $forum, $cm, $mform=null, &$message=null) {
4088     global $DB;
4090     if (empty($mform)) {
4091         return false;
4092     }
4094     if (empty($post->attachments)) {
4095         return true;   // Nothing to do
4096     }
4098     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4100     $info = file_get_draft_area_info($post->attachments);
4101     $present = ($info['filecount']>0) ? '1' : '';
4102     file_save_draft_area_files($post->attachments, $context->id, 'mod_forum', 'attachment', $post->id);
4104     $DB->set_field('forum_posts', 'attachment', $present, array('id'=>$post->id));
4106     return true;
4109 /**
4110  * Add a new post in an existing discussion.
4111  *
4112  * @global object
4113  * @global object
4114  * @global object
4115  * @param object $post
4116  * @param mixed $mform
4117  * @param string $message
4118  * @return int
4119  */
4120 function forum_add_new_post($post, $mform, &$message) {
4121     global $USER, $CFG, $DB;
4123     $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion));
4124     $forum      = $DB->get_record('forum', array('id' => $discussion->forum));
4125     $cm         = get_coursemodule_from_instance('forum', $forum->id);
4126     $context    = get_context_instance(CONTEXT_MODULE, $cm->id);
4128     $post->created    = $post->modified = time();
4129     $post->mailed     = "0";
4130     $post->userid     = $USER->id;
4131     $post->attachment = "";