5819b43605a7776f73d5068d6578321f40d4f325
[moodle.git] / mod / forum / lib.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * @package    mod
19  * @subpackage forum
20  * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
21  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22  */
24 defined('MOODLE_INTERNAL') || die();
26 /** Include required files */
27 require_once($CFG->libdir.'/filelib.php');
28 require_once($CFG->libdir.'/eventslib.php');
29 require_once($CFG->dirroot.'/user/selector/lib.php');
31 /// CONSTANTS ///////////////////////////////////////////////////////////
33 define('FORUM_MODE_FLATOLDEST', 1);
34 define('FORUM_MODE_FLATNEWEST', -1);
35 define('FORUM_MODE_THREADED', 2);
36 define('FORUM_MODE_NESTED', 3);
38 define('FORUM_CHOOSESUBSCRIBE', 0);
39 define('FORUM_FORCESUBSCRIBE', 1);
40 define('FORUM_INITIALSUBSCRIBE', 2);
41 define('FORUM_DISALLOWSUBSCRIBE',3);
43 define('FORUM_TRACKING_OFF', 0);
44 define('FORUM_TRACKING_OPTIONAL', 1);
45 define('FORUM_TRACKING_ON', 2);
47 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
49 /**
50  * Given an object containing all the necessary data,
51  * (defined by the form in mod_form.php) this function
52  * will create a new instance and return the id number
53  * of the new instance.
54  *
55  * @global object
56  * @global object
57  * @param object $forum add forum instance (with magic quotes)
58  * @return int intance id
59  */
60 function forum_add_instance($forum, $mform) {
61     global $CFG, $DB;
63     $forum->timemodified = time();
65     if (empty($forum->assessed)) {
66         $forum->assessed = 0;
67     }
69     if (empty($forum->ratingtime) or empty($forum->assessed)) {
70         $forum->assesstimestart  = 0;
71         $forum->assesstimefinish = 0;
72     }
74     $forum->id = $DB->insert_record('forum', $forum);
75     $modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule);
77     if ($forum->type == 'single') {  // Create related discussion.
78         $discussion = new stdClass();
79         $discussion->course        = $forum->course;
80         $discussion->forum         = $forum->id;
81         $discussion->name          = $forum->name;
82         $discussion->assessed      = $forum->assessed;
83         $discussion->message       = $forum->intro;
84         $discussion->messageformat = $forum->introformat;
85         $discussion->messagetrust  = trusttext_trusted(get_context_instance(CONTEXT_COURSE, $forum->course));
86         $discussion->mailnow       = false;
87         $discussion->groupid       = -1;
89         $message = '';
91         $discussion->id = forum_add_discussion($discussion, null, $message);
93         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
94             // ugly hack - we need to copy the files somehow
95             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
96             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
98             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
99             $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
100         }
101     }
103     if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
104     /// all users should be subscribed initially
105     /// Note: forum_get_potential_subscribers should take the forum context,
106     /// but that does not exist yet, becuase the forum is only half build at this
107     /// stage. However, because the forum is brand new, we know that there are
108     /// no role assignments or overrides in the forum context, so using the
109     /// course context gives the same list of users.
110         $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
111         foreach ($users as $user) {
112             forum_subscribe($user->id, $forum->id);
113         }
114     }
116     forum_grade_item_update($forum);
118     return $forum->id;
122 /**
123  * Given an object containing all the necessary data,
124  * (defined by the form in mod_form.php) this function
125  * will update an existing instance with new data.
126  *
127  * @global object
128  * @param object $forum forum instance (with magic quotes)
129  * @return bool success
130  */
131 function forum_update_instance($forum, $mform) {
132     global $DB, $OUTPUT, $USER;
134     $forum->timemodified = time();
135     $forum->id           = $forum->instance;
137     if (empty($forum->assessed)) {
138         $forum->assessed = 0;
139     }
141     if (empty($forum->ratingtime) or empty($forum->assessed)) {
142         $forum->assesstimestart  = 0;
143         $forum->assesstimefinish = 0;
144     }
146     $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
148     // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
149     // if  scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
150     // for count and sum aggregation types the grade we check to make sure they do not exceed the scale (i.e. max score) when calculating the grade
151     if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
152         forum_update_grades($forum); // recalculate grades for the forum
153     }
155     if ($forum->type == 'single') {  // Update related discussion and post.
156         $discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC');
157         if (!empty($discussions)) {
158             if (count($discussions) > 1) {
159                 echo $OUTPUT->notification(get_string('warnformorepost', 'forum'));
160             }
161             $discussion = array_pop($discussions);
162         } else {
163             // try to recover by creating initial discussion - MDL-16262
164             $discussion = new stdClass();
165             $discussion->course          = $forum->course;
166             $discussion->forum           = $forum->id;
167             $discussion->name            = $forum->name;
168             $discussion->assessed        = $forum->assessed;
169             $discussion->message         = $forum->intro;
170             $discussion->messageformat   = $forum->introformat;
171             $discussion->messagetrust    = true;
172             $discussion->mailnow         = false;
173             $discussion->groupid         = -1;
175             $message = '';
177             forum_add_discussion($discussion, null, $message);
179             if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
180                 print_error('cannotadd', 'forum');
181             }
182         }
183         if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
184             print_error('cannotfindfirstpost', 'forum');
185         }
187         $cm         = get_coursemodule_from_instance('forum', $forum->id);
188         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
190         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
191             // ugly hack - we need to copy the files somehow
192             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
193             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
195             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
196         }
198         $post->subject       = $forum->name;
199         $post->message       = $forum->intro;
200         $post->messageformat = $forum->introformat;
201         $post->messagetrust  = trusttext_trusted($modcontext);
202         $post->modified      = $forum->timemodified;
203         $post->userid        = $USER->id;    // MDL-18599, so that current teacher can take ownership of activities
205         $DB->update_record('forum_posts', $post);
206         $discussion->name = $forum->name;
207         $DB->update_record('forum_discussions', $discussion);
208     }
210     $DB->update_record('forum', $forum);
212     forum_grade_item_update($forum);
214     return true;
218 /**
219  * Given an ID of an instance of this module,
220  * this function will permanently delete the instance
221  * and any data that depends on it.
222  *
223  * @global object
224  * @param int $id forum instance id
225  * @return bool success
226  */
227 function forum_delete_instance($id) {
228     global $DB;
230     if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
231         return false;
232     }
233     if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
234         return false;
235     }
236     if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
237         return false;
238     }
240     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
242     // now get rid of all files
243     $fs = get_file_storage();
244     $fs->delete_area_files($context->id);
246     $result = true;
248     if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
249         foreach ($discussions as $discussion) {
250             if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
251                 $result = false;
252             }
253         }
254     }
256     if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
257         $result = false;
258     }
260     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
262     if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
263         $result = false;
264     }
266     forum_grade_item_delete($forum);
268     return $result;
272 /**
273  * Indicates API features that the forum supports.
274  *
275  * @uses FEATURE_GROUPS
276  * @uses FEATURE_GROUPINGS
277  * @uses FEATURE_GROUPMEMBERSONLY
278  * @uses FEATURE_MOD_INTRO
279  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
280  * @uses FEATURE_COMPLETION_HAS_RULES
281  * @uses FEATURE_GRADE_HAS_GRADE
282  * @uses FEATURE_GRADE_OUTCOMES
283  * @param string $feature
284  * @return mixed True if yes (some features may use other values)
285  */
286 function forum_supports($feature) {
287     switch($feature) {
288         case FEATURE_GROUPS:                  return true;
289         case FEATURE_GROUPINGS:               return true;
290         case FEATURE_GROUPMEMBERSONLY:        return true;
291         case FEATURE_MOD_INTRO:               return true;
292         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
293         case FEATURE_COMPLETION_HAS_RULES:    return true;
294         case FEATURE_GRADE_HAS_GRADE:         return true;
295         case FEATURE_GRADE_OUTCOMES:          return true;
296         case FEATURE_RATE:                    return true;
297         case FEATURE_BACKUP_MOODLE2:          return true;
298         case FEATURE_SHOW_DESCRIPTION:        return true;
300         default: return null;
301     }
305 /**
306  * Obtains the automatic completion state for this forum based on any conditions
307  * in forum settings.
308  *
309  * @global object
310  * @global object
311  * @param object $course Course
312  * @param object $cm Course-module
313  * @param int $userid User ID
314  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
315  * @return bool True if completed, false if not. (If no conditions, then return
316  *   value depends on comparison type)
317  */
318 function forum_get_completion_state($course,$cm,$userid,$type) {
319     global $CFG,$DB;
321     // Get forum details
322     if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
323         throw new Exception("Can't find forum {$cm->instance}");
324     }
326     $result=$type; // Default return value
328     $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
329     $postcountsql="
330 SELECT
331     COUNT(1)
332 FROM
333     {forum_posts} fp
334     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
335 WHERE
336     fp.userid=:userid AND fd.forum=:forumid";
338     if ($forum->completiondiscussions) {
339         $value = $forum->completiondiscussions <=
340                  $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
341         if ($type == COMPLETION_AND) {
342             $result = $result && $value;
343         } else {
344             $result = $result || $value;
345         }
346     }
347     if ($forum->completionreplies) {
348         $value = $forum->completionreplies <=
349                  $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
350         if ($type==COMPLETION_AND) {
351             $result = $result && $value;
352         } else {
353             $result = $result || $value;
354         }
355     }
356     if ($forum->completionposts) {
357         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
358         if ($type == COMPLETION_AND) {
359             $result = $result && $value;
360         } else {
361             $result = $result || $value;
362         }
363     }
365     return $result;
368 function forum_get_email_message_id($postid, $usertoid, $hostname) {
369     return '<moodlepost'.$postid.'to'.$usertoid.'@'.$hostname.'>';
372 /**
373  * Function to be run periodically according to the moodle cron
374  * Finds all posts that have yet to be mailed out, and mails them
375  * out to all subscribers
376  *
377  * @global object
378  * @global object
379  * @global object
380  * @uses CONTEXT_MODULE
381  * @uses CONTEXT_COURSE
382  * @uses SITEID
383  * @uses FORMAT_PLAIN
384  * @return void
385  */
386 function forum_cron() {
387     global $CFG, $USER, $DB;
389     $site = get_site();
391     // all users that are subscribed to any post that needs sending
392     $users = array();
394     // status arrays
395     $mailcount  = array();
396     $errorcount = array();
398     // caches
399     $discussions     = array();
400     $forums          = array();
401     $courses         = array();
402     $coursemodules   = array();
403     $subscribedusers = array();
406     // Posts older than 2 days will not be mailed.  This is to avoid the problem where
407     // cron has not been running for a long time, and then suddenly people are flooded
408     // with mail from the past few weeks or months
409     $timenow   = time();
410     $endtime   = $timenow - $CFG->maxeditingtime;
411     $starttime = $endtime - 48 * 3600;   // Two days earlier
413     if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
414         // Mark them all now as being mailed.  It's unlikely but possible there
415         // might be an error later so that a post is NOT actually mailed out,
416         // but since mail isn't crucial, we can accept this risk.  Doing it now
417         // prevents the risk of duplicated mails, which is a worse problem.
419         if (!forum_mark_old_posts_as_mailed($endtime)) {
420             mtrace('Errors occurred while trying to mark some posts as being mailed.');
421             return false;  // Don't continue trying to mail them, in case we are in a cron loop
422         }
424         // checking post validity, and adding users to loop through later
425         foreach ($posts as $pid => $post) {
427             $discussionid = $post->discussion;
428             if (!isset($discussions[$discussionid])) {
429                 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
430                     $discussions[$discussionid] = $discussion;
431                 } else {
432                     mtrace('Could not find discussion '.$discussionid);
433                     unset($posts[$pid]);
434                     continue;
435                 }
436             }
437             $forumid = $discussions[$discussionid]->forum;
438             if (!isset($forums[$forumid])) {
439                 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
440                     $forums[$forumid] = $forum;
441                 } else {
442                     mtrace('Could not find forum '.$forumid);
443                     unset($posts[$pid]);
444                     continue;
445                 }
446             }
447             $courseid = $forums[$forumid]->course;
448             if (!isset($courses[$courseid])) {
449                 if ($course = $DB->get_record('course', array('id' => $courseid))) {
450                     $courses[$courseid] = $course;
451                 } else {
452                     mtrace('Could not find course '.$courseid);
453                     unset($posts[$pid]);
454                     continue;
455                 }
456             }
457             if (!isset($coursemodules[$forumid])) {
458                 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
459                     $coursemodules[$forumid] = $cm;
460                 } else {
461                     mtrace('Could not find course module for forum '.$forumid);
462                     unset($posts[$pid]);
463                     continue;
464                 }
465             }
468             // caching subscribed users of each forum
469             if (!isset($subscribedusers[$forumid])) {
470                 $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
471                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext, "u.*")) {
472                     foreach ($subusers as $postuser) {
473                         unset($postuser->description); // not necessary
474                         // this user is subscribed to this forum
475                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
476                         // this user is a user we have to process later
477                         $users[$postuser->id] = $postuser;
478                     }
479                     unset($subusers); // release memory
480                 }
481             }
483             $mailcount[$pid] = 0;
484             $errorcount[$pid] = 0;
485         }
486     }
488     if ($users && $posts) {
490         $urlinfo = parse_url($CFG->wwwroot);
491         $hostname = $urlinfo['host'];
493         foreach ($users as $userto) {
495             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
497             // set this so that the capabilities are cached, and environment matches receiving user
498             cron_setup_user($userto);
500             mtrace('Processing user '.$userto->id);
502             // init caches
503             $userto->viewfullnames = array();
504             $userto->canpost       = array();
505             $userto->markposts     = array();
507             // reset the caches
508             foreach ($coursemodules as $forumid=>$unused) {
509                 $coursemodules[$forumid]->cache       = new stdClass();
510                 $coursemodules[$forumid]->cache->caps = array();
511                 unset($coursemodules[$forumid]->uservisible);
512             }
514             foreach ($posts as $pid => $post) {
516                 // Set up the environment for the post, discussion, forum, course
517                 $discussion = $discussions[$post->discussion];
518                 $forum      = $forums[$discussion->forum];
519                 $course     = $courses[$forum->course];
520                 $cm         =& $coursemodules[$forum->id];
522                 // Do some checks  to see if we can bail out now
523                 // Only active enrolled users are in the list of subscribers
524                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
525                     continue; // user does not subscribe to this forum
526                 }
528                 // Don't send email if the forum is Q&A and the user has not posted
529                 if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id)) {
530                     mtrace('Did not email '.$userto->id.' because user has not posted in discussion');
531                     continue;
532                 }
534                 // Get info about the sending user
535                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
536                     $userfrom = $users[$post->userid];
537                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
538                     unset($userfrom->description); // not necessary
539                     $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
540                 } else {
541                     mtrace('Could not find user '.$post->userid);
542                     continue;
543                 }
545                 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
547                 // setup global $COURSE properly - needed for roles and languages
548                 cron_setup_user($userto, $course);
550                 // Fill caches
551                 if (!isset($userto->viewfullnames[$forum->id])) {
552                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
553                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
554                 }
555                 if (!isset($userto->canpost[$discussion->id])) {
556                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
557                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
558                 }
559                 if (!isset($userfrom->groups[$forum->id])) {
560                     if (!isset($userfrom->groups)) {
561                         $userfrom->groups = array();
562                         $users[$userfrom->id]->groups = array();
563                     }
564                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
565                     $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
566                 }
568                 // Make sure groups allow this user to see this email
569                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
570                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
571                         continue;                           // Be safe and don't send it to anyone
572                     }
574                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
575                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
576                         continue;
577                     }
578                 }
580                 // Make sure we're allowed to see it...
581                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
582                     mtrace('user '.$userto->id. ' can not see '.$post->id);
583                     continue;
584                 }
586                 // OK so we need to send the email.
588                 // Does the user want this post in a digest?  If so postpone it for now.
589                 if ($userto->maildigest > 0) {
590                     // This user wants the mails to be in digest form
591                     $queue = new stdClass();
592                     $queue->userid       = $userto->id;
593                     $queue->discussionid = $discussion->id;
594                     $queue->postid       = $post->id;
595                     $queue->timemodified = $post->created;
596                     $DB->insert_record('forum_queue', $queue);
597                     continue;
598                 }
601                 // Prepare to actually send the post now, and build up the content
603                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
605                 $userfrom->customheaders = array (  // Headers to make emails easier to track
606                            'Precedence: Bulk',
607                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
608                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
609                            'Message-ID: '.forum_get_email_message_id($post->id, $userto->id, $hostname),
610                            'X-Course-Id: '.$course->id,
611                            'X-Course-Name: '.format_string($course->fullname, true)
612                 );
614                 if ($post->parent) {  // This post is a reply, so add headers for threading (see MDL-22551)
615                     $userfrom->customheaders[] = 'In-Reply-To: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
616                     $userfrom->customheaders[] = 'References: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
617                 }
619                 $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
621                 $postsubject = "$shortname: ".format_string($post->subject,true);
622                 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
623                 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
625                 // Send the post now!
627                 mtrace('Sending ', '');
629                 $eventdata = new stdClass();
630                 $eventdata->component        = 'mod_forum';
631                 $eventdata->name             = 'posts';
632                 $eventdata->userfrom         = $userfrom;
633                 $eventdata->userto           = $userto;
634                 $eventdata->subject          = $postsubject;
635                 $eventdata->fullmessage      = $posttext;
636                 $eventdata->fullmessageformat = FORMAT_PLAIN;
637                 $eventdata->fullmessagehtml  = $posthtml;
638                 $eventdata->notification = 1;
640                 $smallmessagestrings = new stdClass();
641                 $smallmessagestrings->user = fullname($userfrom);
642                 $smallmessagestrings->forumname = "$shortname: ".format_string($forum->name,true).": ".$discussion->name;
643                 $smallmessagestrings->message = $post->message;
644                 //make sure strings are in message recipients language
645                 $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
647                 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
648                 $eventdata->contexturlname = $discussion->name;
650                 $mailresult = message_send($eventdata);
651                 if (!$mailresult){
652                     mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
653                          " ($userto->email) .. not trying again.");
654                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
655                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
656                     $errorcount[$post->id]++;
657                 } else {
658                     $mailcount[$post->id]++;
660                 // Mark post as read if forum_usermarksread is set off
661                     if (!$CFG->forum_usermarksread) {
662                         $userto->markposts[$post->id] = $post->id;
663                     }
664                 }
666                 mtrace('post '.$post->id. ': '.$post->subject);
667             }
669             // mark processed posts as read
670             forum_tp_mark_posts_read($userto, $userto->markposts);
671         }
672     }
674     if ($posts) {
675         foreach ($posts as $post) {
676             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
677             if ($errorcount[$post->id]) {
678                 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
679             }
680         }
681     }
683     // release some memory
684     unset($subscribedusers);
685     unset($mailcount);
686     unset($errorcount);
688     cron_setup_user();
690     $sitetimezone = $CFG->timezone;
692     // Now see if there are any digest mails waiting to be sent, and if we should send them
694     mtrace('Starting digest processing...');
696     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
698     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
699         set_config('digestmailtimelast', 0);
700     }
702     $timenow = time();
703     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
705     // Delete any really old ones (normally there shouldn't be any)
706     $weekago = $timenow - (7 * 24 * 3600);
707     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
708     mtrace ('Cleaned old digest records');
710     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
712         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
714         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
716         if ($digestposts_rs->valid()) {
718             // We have work to do
719             $usermailcount = 0;
721             //caches - reuse the those filled before too
722             $discussionposts = array();
723             $userdiscussions = array();
725             foreach ($digestposts_rs as $digestpost) {
726                 if (!isset($users[$digestpost->userid])) {
727                     if ($user = $DB->get_record('user', array('id' => $digestpost->userid))) {
728                         $users[$digestpost->userid] = $user;
729                     } else {
730                         continue;
731                     }
732                 }
733                 $postuser = $users[$digestpost->userid];
735                 if (!isset($posts[$digestpost->postid])) {
736                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
737                         $posts[$digestpost->postid] = $post;
738                     } else {
739                         continue;
740                     }
741                 }
742                 $discussionid = $digestpost->discussionid;
743                 if (!isset($discussions[$discussionid])) {
744                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
745                         $discussions[$discussionid] = $discussion;
746                     } else {
747                         continue;
748                     }
749                 }
750                 $forumid = $discussions[$discussionid]->forum;
751                 if (!isset($forums[$forumid])) {
752                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
753                         $forums[$forumid] = $forum;
754                     } else {
755                         continue;
756                     }
757                 }
759                 $courseid = $forums[$forumid]->course;
760                 if (!isset($courses[$courseid])) {
761                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
762                         $courses[$courseid] = $course;
763                     } else {
764                         continue;
765                     }
766                 }
768                 if (!isset($coursemodules[$forumid])) {
769                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
770                         $coursemodules[$forumid] = $cm;
771                     } else {
772                         continue;
773                     }
774                 }
775                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
776                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
777             }
778             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
780             // Data collected, start sending out emails to each user
781             foreach ($userdiscussions as $userid => $thesediscussions) {
783                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
785                 cron_setup_user();
787                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
789                 // First of all delete all the queue entries for this user
790                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
791                 $userto = $users[$userid];
793                 // Override the language and timezone of the "current" user, so that
794                 // mail is customised for the receiver.
795                 cron_setup_user($userto);
797                 // init caches
798                 $userto->viewfullnames = array();
799                 $userto->canpost       = array();
800                 $userto->markposts     = array();
802                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
804                 $headerdata = new stdClass();
805                 $headerdata->sitename = format_string($site->fullname, true);
806                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
808                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
809                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
811                 $posthtml = "<head>";
812 /*                foreach ($CFG->stylesheets as $stylesheet) {
813                     //TODO: MDL-21120
814                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
815                 }*/
816                 $posthtml .= "</head>\n<body id=\"email\">\n";
817                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
819                 foreach ($thesediscussions as $discussionid) {
821                     @set_time_limit(120);   // to be reset for each post
823                     $discussion = $discussions[$discussionid];
824                     $forum      = $forums[$discussion->forum];
825                     $course     = $courses[$forum->course];
826                     $cm         = $coursemodules[$forum->id];
828                     //override language
829                     cron_setup_user($userto, $course);
831                     // Fill caches
832                     if (!isset($userto->viewfullnames[$forum->id])) {
833                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
834                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
835                     }
836                     if (!isset($userto->canpost[$discussion->id])) {
837                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
838                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
839                     }
841                     $strforums      = get_string('forums', 'forum');
842                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
843                     $canreply       = $userto->canpost[$discussion->id];
844                     $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
846                     $posttext .= "\n \n";
847                     $posttext .= '=====================================================================';
848                     $posttext .= "\n \n";
849                     $posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
850                     if ($discussion->name != $forum->name) {
851                         $posttext  .= " -> ".format_string($discussion->name,true);
852                     }
853                     $posttext .= "\n";
855                     $posthtml .= "<p><font face=\"sans-serif\">".
856                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
857                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
858                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
859                     if ($discussion->name == $forum->name) {
860                         $posthtml .= "</font></p>";
861                     } else {
862                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
863                     }
864                     $posthtml .= '<p>';
866                     $postsarray = $discussionposts[$discussionid];
867                     sort($postsarray);
869                     foreach ($postsarray as $postid) {
870                         $post = $posts[$postid];
872                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
873                             $userfrom = $users[$post->userid];
874                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
875                             $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
876                         } else {
877                             mtrace('Could not find user '.$post->userid);
878                             continue;
879                         }
881                         if (!isset($userfrom->groups[$forum->id])) {
882                             if (!isset($userfrom->groups)) {
883                                 $userfrom->groups = array();
884                                 $users[$userfrom->id]->groups = array();
885                             }
886                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
887                             $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
888                         }
890                         $userfrom->customheaders = array ("Precedence: Bulk");
892                         if ($userto->maildigest == 2) {
893                             // Subjects only
894                             $by = new stdClass();
895                             $by->name = fullname($userfrom);
896                             $by->date = userdate($post->modified);
897                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
898                             $posttext .= "\n---------------------------------------------------------------------";
900                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
901                             $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>';
903                         } else {
904                             // The full treatment
905                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
906                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
908                         // Create an array of postid's for this user to mark as read.
909                             if (!$CFG->forum_usermarksread) {
910                                 $userto->markposts[$post->id] = $post->id;
911                             }
912                         }
913                     }
914                     if ($canunsubscribe) {
915                         $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>";
916                     } else {
917                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
918                     }
919                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
920                 }
921                 $posthtml .= '</body>';
923                 if (empty($userto->mailformat) || $userto->mailformat != 1) {
924                     // This user DOESN'T want to receive HTML
925                     $posthtml = '';
926                 }
928                 $attachment = $attachname='';
929                 $usetrueaddress = true;
930                 //directly email forum digests rather than sending them via messaging
931                 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname, $usetrueaddress, $CFG->forum_replytouser);
933                 if (!$mailresult) {
934                     mtrace("ERROR!");
935                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
936                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
937                 } else {
938                     mtrace("success.");
939                     $usermailcount++;
941                     // Mark post as read if forum_usermarksread is set off
942                     forum_tp_mark_posts_read($userto, $userto->markposts);
943                 }
944             }
945         }
946     /// We have finishied all digest emails, update $CFG->digestmailtimelast
947         set_config('digestmailtimelast', $timenow);
948     }
950     cron_setup_user();
952     if (!empty($usermailcount)) {
953         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
954     }
956     if (!empty($CFG->forum_lastreadclean)) {
957         $timenow = time();
958         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
959             set_config('forum_lastreadclean', $timenow);
960             mtrace('Removing old forum read tracking info...');
961             forum_tp_clean_read_records();
962         }
963     } else {
964         set_config('forum_lastreadclean', time());
965     }
968     return true;
971 /**
972  * Builds and returns the body of the email notification in plain text.
973  *
974  * @global object
975  * @global object
976  * @uses CONTEXT_MODULE
977  * @param object $course
978  * @param object $cm
979  * @param object $forum
980  * @param object $discussion
981  * @param object $post
982  * @param object $userfrom
983  * @param object $userto
984  * @param boolean $bare
985  * @return string The email body in plain text format.
986  */
987 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
988     global $CFG, $USER;
990     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
992     if (!isset($userto->viewfullnames[$forum->id])) {
993         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
994     } else {
995         $viewfullnames = $userto->viewfullnames[$forum->id];
996     }
998     if (!isset($userto->canpost[$discussion->id])) {
999         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
1000     } else {
1001         $canreply = $userto->canpost[$discussion->id];
1002     }
1004     $by = New stdClass;
1005     $by->name = fullname($userfrom, $viewfullnames);
1006     $by->date = userdate($post->modified, "", $userto->timezone);
1008     $strbynameondate = get_string('bynameondate', 'forum', $by);
1010     $strforums = get_string('forums', 'forum');
1012     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1014     $posttext = '';
1016     if (!$bare) {
1017         $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
1018         $posttext  = "$shortname -> $strforums -> ".format_string($forum->name,true);
1020         if ($discussion->name != $forum->name) {
1021             $posttext  .= " -> ".format_string($discussion->name,true);
1022         }
1023     }
1025     // add absolute file links
1026     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
1028     $posttext .= "\n---------------------------------------------------------------------\n";
1029     $posttext .= format_string($post->subject,true);
1030     if ($bare) {
1031         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1032     }
1033     $posttext .= "\n".$strbynameondate."\n";
1034     $posttext .= "---------------------------------------------------------------------\n";
1035     $posttext .= format_text_email($post->message, $post->messageformat);
1036     $posttext .= "\n\n";
1037     $posttext .= forum_print_attachments($post, $cm, "text");
1039     if (!$bare && $canreply) {
1040         $posttext .= "---------------------------------------------------------------------\n";
1041         $posttext .= get_string("postmailinfo", "forum", $shortname)."\n";
1042         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1043     }
1044     if (!$bare && $canunsubscribe) {
1045         $posttext .= "\n---------------------------------------------------------------------\n";
1046         $posttext .= get_string("unsubscribe", "forum");
1047         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1048     }
1050     return $posttext;
1053 /**
1054  * Builds and returns the body of the email notification in html format.
1055  *
1056  * @global object
1057  * @param object $course
1058  * @param object $cm
1059  * @param object $forum
1060  * @param object $discussion
1061  * @param object $post
1062  * @param object $userfrom
1063  * @param object $userto
1064  * @return string The email text in HTML format
1065  */
1066 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1067     global $CFG;
1069     if ($userto->mailformat != 1) {  // Needs to be HTML
1070         return '';
1071     }
1073     if (!isset($userto->canpost[$discussion->id])) {
1074         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course);
1075     } else {
1076         $canreply = $userto->canpost[$discussion->id];
1077     }
1079     $strforums = get_string('forums', 'forum');
1080     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1081     $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
1083     $posthtml = '<head>';
1084 /*    foreach ($CFG->stylesheets as $stylesheet) {
1085         //TODO: MDL-21120
1086         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1087     }*/
1088     $posthtml .= '</head>';
1089     $posthtml .= "\n<body id=\"email\">\n\n";
1091     $posthtml .= '<div class="navbar">'.
1092     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$shortname.'</a> &raquo; '.
1093     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1094     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1095     if ($discussion->name == $forum->name) {
1096         $posthtml .= '</div>';
1097     } else {
1098         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1099                      format_string($discussion->name,true).'</a></div>';
1100     }
1101     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1103     if ($canunsubscribe) {
1104         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1105                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1106                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1107     }
1109     $posthtml .= '</body>';
1111     return $posthtml;
1115 /**
1116  *
1117  * @param object $course
1118  * @param object $user
1119  * @param object $mod TODO this is not used in this function, refactor
1120  * @param object $forum
1121  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1122  */
1123 function forum_user_outline($course, $user, $mod, $forum) {
1124     global $CFG;
1125     require_once("$CFG->libdir/gradelib.php");
1126     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1127     if (empty($grades->items[0]->grades)) {
1128         $grade = false;
1129     } else {
1130         $grade = reset($grades->items[0]->grades);
1131     }
1133     $count = forum_count_user_posts($forum->id, $user->id);
1135     if ($count && $count->postcount > 0) {
1136         $result = new stdClass();
1137         $result->info = get_string("numposts", "forum", $count->postcount);
1138         $result->time = $count->lastpost;
1139         if ($grade) {
1140             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1141         }
1142         return $result;
1143     } else if ($grade) {
1144         $result = new stdClass();
1145         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1147         //datesubmitted == time created. dategraded == time modified or time overridden
1148         //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
1149         //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
1150         if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1151             $result->time = $grade->dategraded;
1152         } else {
1153             $result->time = $grade->datesubmitted;
1154         }
1156         return $result;
1157     }
1158     return NULL;
1162 /**
1163  * @global object
1164  * @global object
1165  * @param object $coure
1166  * @param object $user
1167  * @param object $mod
1168  * @param object $forum
1169  */
1170 function forum_user_complete($course, $user, $mod, $forum) {
1171     global $CFG,$USER, $OUTPUT;
1172     require_once("$CFG->libdir/gradelib.php");
1174     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1175     if (!empty($grades->items[0]->grades)) {
1176         $grade = reset($grades->items[0]->grades);
1177         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1178         if ($grade->str_feedback) {
1179             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1180         }
1181     }
1183     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1185         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1186             print_error('invalidcoursemodule');
1187         }
1188         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1190         foreach ($posts as $post) {
1191             if (!isset($discussions[$post->discussion])) {
1192                 continue;
1193             }
1194             $discussion = $discussions[$post->discussion];
1196             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1197         }
1198     } else {
1199         echo "<p>".get_string("noposts", "forum")."</p>";
1200     }
1208 /**
1209  * @global object
1210  * @global object
1211  * @global object
1212  * @param array $courses
1213  * @param array $htmlarray
1214  */
1215 function forum_print_overview($courses,&$htmlarray) {
1216     global $USER, $CFG, $DB, $SESSION;
1218     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1219         return array();
1220     }
1222     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1223         return;
1224     }
1227     // get all forum logs in ONE query (much better!)
1228     $params = array();
1229     $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {log} l "
1230         ." JOIN {course_modules} cm ON cm.id = cmid "
1231         ." WHERE (";
1232     foreach ($courses as $course) {
1233         $sql .= '(l.course = ? AND l.time > ?) OR ';
1234         $params[] = $course->id;
1235         $params[] = $course->lastaccess;
1236     }
1237     $sql = substr($sql,0,-3); // take off the last OR
1239     $sql .= ") AND l.module = 'forum' AND action = 'add post' "
1240         ." AND userid != ? GROUP BY cmid,l.course,instance";
1242     $params[] = $USER->id;
1244     if (!$new = $DB->get_records_sql($sql, $params)) {
1245         $new = array(); // avoid warnings
1246     }
1248     // also get all forum tracking stuff ONCE.
1249     $trackingforums = array();
1250     foreach ($forums as $forum) {
1251         if (forum_tp_can_track_forums($forum)) {
1252             $trackingforums[$forum->id] = $forum;
1253         }
1254     }
1256     if (count($trackingforums) > 0) {
1257         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1258         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1259             ' FROM {forum_posts} p '.
1260             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1261             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1262         $params = array($USER->id);
1264         foreach ($trackingforums as $track) {
1265             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1266             $params[] = $track->id;
1267             if (isset($SESSION->currentgroup[$track->course])) {
1268                 $groupid =  $SESSION->currentgroup[$track->course];
1269             } else {
1270                 $groupid = groups_get_all_groups($track->course, $USER->id);
1271                 if (is_array($groupid)) {
1272                     $groupid = array_shift(array_keys($groupid));
1273                     $SESSION->currentgroup[$track->course] = $groupid;
1274                 } else {
1275                     $groupid = 0;
1276                 }
1277             }
1278             $params[] = $groupid;
1279         }
1280         $sql = substr($sql,0,-3); // take off the last OR
1281         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1282         $params[] = $cutoffdate;
1284         if (!$unread = $DB->get_records_sql($sql, $params)) {
1285             $unread = array();
1286         }
1287     } else {
1288         $unread = array();
1289     }
1291     if (empty($unread) and empty($new)) {
1292         return;
1293     }
1295     $strforum = get_string('modulename','forum');
1296     $strnumunread = get_string('overviewnumunread','forum');
1297     $strnumpostssince = get_string('overviewnumpostssince','forum');
1299     foreach ($forums as $forum) {
1300         $str = '';
1301         $count = 0;
1302         $thisunread = 0;
1303         $showunread = false;
1304         // either we have something from logs, or trackposts, or nothing.
1305         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1306             $count = $new[$forum->id]->count;
1307         }
1308         if (array_key_exists($forum->id,$unread)) {
1309             $thisunread = $unread[$forum->id]->count;
1310             $showunread = true;
1311         }
1312         if ($count > 0 || $thisunread > 0) {
1313             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1314                 $forum->name.'</a></div>';
1315             $str .= '<div class="info"><span class="postsincelogin">';
1316             $str .= $count.' '.$strnumpostssince."</span>";
1317             if (!empty($showunread)) {
1318                 $str .= '<div class="unreadposts">'.$thisunread .' '.$strnumunread.'</div>';
1319             }
1320             $str .= '</div></div>';
1321         }
1322         if (!empty($str)) {
1323             if (!array_key_exists($forum->course,$htmlarray)) {
1324                 $htmlarray[$forum->course] = array();
1325             }
1326             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1327                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1328             }
1329             $htmlarray[$forum->course]['forum'] .= $str;
1330         }
1331     }
1334 /**
1335  * Given a course and a date, prints a summary of all the new
1336  * messages posted in the course since that date
1337  *
1338  * @global object
1339  * @global object
1340  * @global object
1341  * @uses CONTEXT_MODULE
1342  * @uses VISIBLEGROUPS
1343  * @param object $course
1344  * @param bool $viewfullnames capability
1345  * @param int $timestart
1346  * @return bool success
1347  */
1348 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1349     global $CFG, $USER, $DB, $OUTPUT;
1351     // do not use log table if possible, it may be huge and is expensive to join with other tables
1353     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1354                                               d.timestart, d.timeend, d.userid AS duserid,
1355                                               u.firstname, u.lastname, u.email, u.picture
1356                                          FROM {forum_posts} p
1357                                               JOIN {forum_discussions} d ON d.id = p.discussion
1358                                               JOIN {forum} f             ON f.id = d.forum
1359                                               JOIN {user} u              ON u.id = p.userid
1360                                         WHERE p.created > ? AND f.course = ?
1361                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1362          return false;
1363     }
1365     $modinfo =& get_fast_modinfo($course);
1367     $groupmodes = array();
1368     $cms    = array();
1370     $strftimerecent = get_string('strftimerecent');
1372     $printposts = array();
1373     foreach ($posts as $post) {
1374         if (!isset($modinfo->instances['forum'][$post->forum])) {
1375             // not visible
1376             continue;
1377         }
1378         $cm = $modinfo->instances['forum'][$post->forum];
1379         if (!$cm->uservisible) {
1380             continue;
1381         }
1382         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1384         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1385             continue;
1386         }
1388         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1389           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1390             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1391                 continue;
1392             }
1393         }
1395         $groupmode = groups_get_activity_groupmode($cm, $course);
1397         if ($groupmode) {
1398             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1399                 // oki (Open discussions have groupid -1)
1400             } else {
1401                 // separate mode
1402                 if (isguestuser()) {
1403                     // shortcut
1404                     continue;
1405                 }
1407                 if (is_null($modinfo->groups)) {
1408                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1409                 }
1411                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1412                     continue;
1413                 }
1414             }
1415         }
1417         $printposts[] = $post;
1418     }
1419     unset($posts);
1421     if (!$printposts) {
1422         return false;
1423     }
1425     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1426     echo "\n<ul class='unlist'>\n";
1428     foreach ($printposts as $post) {
1429         $subjectclass = empty($post->parent) ? ' bold' : '';
1431         echo '<li><div class="head">'.
1432                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1433                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1434              '</div>';
1435         echo '<div class="info'.$subjectclass.'">';
1436         if (empty($post->parent)) {
1437             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1438         } else {
1439             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1440         }
1441         $post->subject = break_up_long_words(format_string($post->subject, true));
1442         echo $post->subject;
1443         echo "</a>\"</div></li>\n";
1444     }
1446     echo "</ul>\n";
1448     return true;
1451 /**
1452  * Return grade for given user or all users.
1453  *
1454  * @global object
1455  * @global object
1456  * @param object $forum
1457  * @param int $userid optional user id, 0 means all users
1458  * @return array array of grades, false if none
1459  */
1460 function forum_get_user_grades($forum, $userid = 0) {
1461     global $CFG;
1463     require_once($CFG->dirroot.'/rating/lib.php');
1465     $ratingoptions = new stdClass;
1466     $ratingoptions->component = 'mod_forum';
1467     $ratingoptions->ratingarea = 'post';
1469     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1470     $ratingoptions->modulename = 'forum';
1471     $ratingoptions->moduleid   = $forum->id;
1472     $ratingoptions->userid = $userid;
1473     $ratingoptions->aggregationmethod = $forum->assessed;
1474     $ratingoptions->scaleid = $forum->scale;
1475     $ratingoptions->itemtable = 'forum_posts';
1476     $ratingoptions->itemtableusercolumn = 'userid';
1478     $rm = new rating_manager();
1479     return $rm->get_user_grades($ratingoptions);
1482 /**
1483  * Update activity grades
1484  *
1485  * @global object
1486  * @global object
1487  * @param object $forum
1488  * @param int $userid specific user only, 0 means all
1489  * @param boolean $nullifnone return null if grade does not exist
1490  * @return void
1491  */
1492 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1493     global $CFG, $DB;
1494     require_once($CFG->libdir.'/gradelib.php');
1496     if (!$forum->assessed) {
1497         forum_grade_item_update($forum);
1499     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1500         forum_grade_item_update($forum, $grades);
1502     } else if ($userid and $nullifnone) {
1503         $grade = new stdClass();
1504         $grade->userid   = $userid;
1505         $grade->rawgrade = NULL;
1506         forum_grade_item_update($forum, $grade);
1508     } else {
1509         forum_grade_item_update($forum);
1510     }
1513 /**
1514  * Update all grades in gradebook.
1515  * @global object
1516  */
1517 function forum_upgrade_grades() {
1518     global $DB;
1520     $sql = "SELECT COUNT('x')
1521               FROM {forum} f, {course_modules} cm, {modules} m
1522              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1523     $count = $DB->count_records_sql($sql);
1525     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1526               FROM {forum} f, {course_modules} cm, {modules} m
1527              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1528     $rs = $DB->get_recordset_sql($sql);
1529     if ($rs->valid()) {
1530         $pbar = new progress_bar('forumupgradegrades', 500, true);
1531         $i=0;
1532         foreach ($rs as $forum) {
1533             $i++;
1534             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1535             forum_update_grades($forum, 0, false);
1536             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1537         }
1538     }
1539     $rs->close();
1542 /**
1543  * Create/update grade item for given forum
1544  *
1545  * @global object
1546  * @uses GRADE_TYPE_NONE
1547  * @uses GRADE_TYPE_VALUE
1548  * @uses GRADE_TYPE_SCALE
1549  * @param object $forum object with extra cmidnumber
1550  * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
1551  * @return int 0 if ok
1552  */
1553 function forum_grade_item_update($forum, $grades=NULL) {
1554     global $CFG;
1555     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1556         require_once($CFG->libdir.'/gradelib.php');
1557     }
1559     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1561     if (!$forum->assessed or $forum->scale == 0) {
1562         $params['gradetype'] = GRADE_TYPE_NONE;
1564     } else if ($forum->scale > 0) {
1565         $params['gradetype'] = GRADE_TYPE_VALUE;
1566         $params['grademax']  = $forum->scale;
1567         $params['grademin']  = 0;
1569     } else if ($forum->scale < 0) {
1570         $params['gradetype'] = GRADE_TYPE_SCALE;
1571         $params['scaleid']   = -$forum->scale;
1572     }
1574     if ($grades  === 'reset') {
1575         $params['reset'] = true;
1576         $grades = NULL;
1577     }
1579     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1582 /**
1583  * Delete grade item for given forum
1584  *
1585  * @global object
1586  * @param object $forum object
1587  * @return object grade_item
1588  */
1589 function forum_grade_item_delete($forum) {
1590     global $CFG;
1591     require_once($CFG->libdir.'/gradelib.php');
1593     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1597 /**
1598  * Returns the users with data in one forum
1599  * (users with records in forum_subscriptions, forum_posts, students)
1600  *
1601  * @todo: deprecated - to be deleted in 2.2
1602  *
1603  * @param int $forumid
1604  * @return mixed array or false if none
1605  */
1606 function forum_get_participants($forumid) {
1608     global $CFG, $DB;
1610     $params = array('forumid' => $forumid);
1612     //Get students from forum_subscriptions
1613     $sql = "SELECT DISTINCT u.id, u.id
1614               FROM {user} u,
1615                    {forum_subscriptions} s
1616              WHERE s.forum = :forumid AND
1617                    u.id = s.userid";
1618     $st_subscriptions = $DB->get_records_sql($sql, $params);
1620     //Get students from forum_posts
1621     $sql = "SELECT DISTINCT u.id, u.id
1622               FROM {user} u,
1623                    {forum_discussions} d,
1624                    {forum_posts} p
1625               WHERE d.forum = :forumid AND
1626                     p.discussion = d.id AND
1627                     u.id = p.userid";
1628     $st_posts = $DB->get_records_sql($sql, $params);
1630     //Get students from the ratings table
1631     $sql = "SELECT DISTINCT r.userid, r.userid AS id
1632               FROM {forum_discussions} d
1633               JOIN {forum_posts} p ON p.discussion = d.id
1634               JOIN {rating} r on r.itemid = p.id
1635              WHERE d.forum = :forumid AND
1636                    r.component = 'mod_forum' AND
1637                    r.ratingarea = 'post'";
1638     $st_ratings = $DB->get_records_sql($sql, $params);
1640     //Add st_posts to st_subscriptions
1641     if ($st_posts) {
1642         foreach ($st_posts as $st_post) {
1643             $st_subscriptions[$st_post->id] = $st_post;
1644         }
1645     }
1646     //Add st_ratings to st_subscriptions
1647     if ($st_ratings) {
1648         foreach ($st_ratings as $st_rating) {
1649             $st_subscriptions[$st_rating->id] = $st_rating;
1650         }
1651     }
1652     //Return st_subscriptions array (it contains an array of unique users)
1653     return ($st_subscriptions);
1656 /**
1657  * This function returns if a scale is being used by one forum
1658  *
1659  * @global object
1660  * @param int $forumid
1661  * @param int $scaleid negative number
1662  * @return bool
1663  */
1664 function forum_scale_used ($forumid,$scaleid) {
1665     global $DB;
1666     $return = false;
1668     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1670     if (!empty($rec) && !empty($scaleid)) {
1671         $return = true;
1672     }
1674     return $return;
1677 /**
1678  * Checks if scale is being used by any instance of forum
1679  *
1680  * This is used to find out if scale used anywhere
1681  *
1682  * @global object
1683  * @param $scaleid int
1684  * @return boolean True if the scale is used by any forum
1685  */
1686 function forum_scale_used_anywhere($scaleid) {
1687     global $DB;
1688     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1689         return true;
1690     } else {
1691         return false;
1692     }
1695 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1697 /**
1698  * Gets a post with all info ready for forum_print_post
1699  * Most of these joins are just to get the forum id
1700  *
1701  * @global object
1702  * @global object
1703  * @param int $postid
1704  * @return mixed array of posts or false
1705  */
1706 function forum_get_post_full($postid) {
1707     global $CFG, $DB;
1709     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1710                              FROM {forum_posts} p
1711                                   JOIN {forum_discussions} d ON p.discussion = d.id
1712                                   LEFT JOIN {user} u ON p.userid = u.id
1713                             WHERE p.id = ?", array($postid));
1716 /**
1717  * Gets posts with all info ready for forum_print_post
1718  * We pass forumid in because we always know it so no need to make a
1719  * complicated join to find it out.
1720  *
1721  * @global object
1722  * @global object
1723  * @return mixed array of posts or false
1724  */
1725 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1726     global $CFG, $DB;
1728     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1729                               FROM {forum_posts} p
1730                          LEFT JOIN {user} u ON p.userid = u.id
1731                              WHERE p.discussion = ?
1732                                AND p.parent > 0 $sort", array($discussion));
1735 /**
1736  * Gets all posts in discussion including top parent.
1737  *
1738  * @global object
1739  * @global object
1740  * @global object
1741  * @param int $discussionid
1742  * @param string $sort
1743  * @param bool $tracking does user track the forum?
1744  * @return array of posts
1745  */
1746 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1747     global $CFG, $DB, $USER;
1749     $tr_sel  = "";
1750     $tr_join = "";
1751     $params = array();
1753     if ($tracking) {
1754         $now = time();
1755         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1756         $tr_sel  = ", fr.id AS postread";
1757         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1758         $params[] = $USER->id;
1759     }
1761     $params[] = $discussionid;
1762     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1763                                      FROM {forum_posts} p
1764                                           LEFT JOIN {user} u ON p.userid = u.id
1765                                           $tr_join
1766                                     WHERE p.discussion = ?
1767                                  ORDER BY $sort", $params)) {
1768         return array();
1769     }
1771     foreach ($posts as $pid=>$p) {
1772         if ($tracking) {
1773             if (forum_tp_is_post_old($p)) {
1774                  $posts[$pid]->postread = true;
1775             }
1776         }
1777         if (!$p->parent) {
1778             continue;
1779         }
1780         if (!isset($posts[$p->parent])) {
1781             continue; // parent does not exist??
1782         }
1783         if (!isset($posts[$p->parent]->children)) {
1784             $posts[$p->parent]->children = array();
1785         }
1786         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1787     }
1789     return $posts;
1792 /**
1793  * Gets posts with all info ready for forum_print_post
1794  * We pass forumid in because we always know it so no need to make a
1795  * complicated join to find it out.
1796  *
1797  * @global object
1798  * @global object
1799  * @param int $parent
1800  * @param int $forumid
1801  * @return array
1802  */
1803 function forum_get_child_posts($parent, $forumid) {
1804     global $CFG, $DB;
1806     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1807                               FROM {forum_posts} p
1808                          LEFT JOIN {user} u ON p.userid = u.id
1809                              WHERE p.parent = ?
1810                           ORDER BY p.created ASC", array($parent));
1813 /**
1814  * An array of forum objects that the user is allowed to read/search through.
1815  *
1816  * @global object
1817  * @global object
1818  * @global object
1819  * @param int $userid
1820  * @param int $courseid if 0, we look for forums throughout the whole site.
1821  * @return array of forum objects, or false if no matches
1822  *         Forum objects have the following attributes:
1823  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1824  *         viewhiddentimedposts
1825  */
1826 function forum_get_readable_forums($userid, $courseid=0) {
1828     global $CFG, $DB, $USER;
1829     require_once($CFG->dirroot.'/course/lib.php');
1831     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1832         print_error('notinstalled', 'forum');
1833     }
1835     if ($courseid) {
1836         $courses = $DB->get_records('course', array('id' => $courseid));
1837     } else {
1838         // If no course is specified, then the user can see SITE + his courses.
1839         $courses1 = $DB->get_records('course', array('id' => SITEID));
1840         $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
1841         $courses = array_merge($courses1, $courses2);
1842     }
1843     if (!$courses) {
1844         return array();
1845     }
1847     $readableforums = array();
1849     foreach ($courses as $course) {
1851         $modinfo =& get_fast_modinfo($course);
1852         if (is_null($modinfo->groups)) {
1853             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1854         }
1856         if (empty($modinfo->instances['forum'])) {
1857             // hmm, no forums?
1858             continue;
1859         }
1861         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1863         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1864             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1865                 continue;
1866             }
1867             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1868             $forum = $courseforums[$forumid];
1869             $forum->context = $context;
1870             $forum->cm = $cm;
1872             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1873                 continue;
1874             }
1876          /// group access
1877             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1878                 if (is_null($modinfo->groups)) {
1879                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1880                 }
1881                 if (isset($modinfo->groups[$cm->groupingid])) {
1882                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1883                     $forum->onlygroups[] = -1;
1884                 } else {
1885                     $forum->onlygroups = array(-1);
1886                 }
1887             }
1889         /// hidden timed discussions
1890             $forum->viewhiddentimedposts = true;
1891             if (!empty($CFG->forum_enabletimedposts)) {
1892                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1893                     $forum->viewhiddentimedposts = false;
1894                 }
1895             }
1897         /// qanda access
1898             if ($forum->type == 'qanda'
1899                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1901                 // We need to check whether the user has posted in the qanda forum.
1902                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1903                                                     // the user is allowed to see in this forum.
1904                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1905                     foreach ($discussionspostedin as $d) {
1906                         $forum->onlydiscussions[] = $d->id;
1907                     }
1908                 }
1909             }
1911             $readableforums[$forum->id] = $forum;
1912         }
1914         unset($modinfo);
1916     } // End foreach $courses
1918     return $readableforums;
1921 /**
1922  * Returns a list of posts found using an array of search terms.
1923  *
1924  * @global object
1925  * @global object
1926  * @global object
1927  * @param array $searchterms array of search terms, e.g. word +word -word
1928  * @param int $courseid if 0, we search through the whole site
1929  * @param int $limitfrom
1930  * @param int $limitnum
1931  * @param int &$totalcount
1932  * @param string $extrasql
1933  * @return array|bool Array of posts found or false
1934  */
1935 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1936                             &$totalcount, $extrasql='') {
1937     global $CFG, $DB, $USER;
1938     require_once($CFG->libdir.'/searchlib.php');
1940     $forums = forum_get_readable_forums($USER->id, $courseid);
1942     if (count($forums) == 0) {
1943         $totalcount = 0;
1944         return false;
1945     }
1947     $now = round(time(), -2); // db friendly
1949     $fullaccess = array();
1950     $where = array();
1951     $params = array();
1953     foreach ($forums as $forumid => $forum) {
1954         $select = array();
1956         if (!$forum->viewhiddentimedposts) {
1957             $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
1958             $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
1959         }
1961         $cm = $forum->cm;
1962         $context = $forum->context;
1964         if ($forum->type == 'qanda'
1965             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1966             if (!empty($forum->onlydiscussions)) {
1967                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
1968                 $params = array_merge($params, $discussionid_params);
1969                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
1970             } else {
1971                 $select[] = "p.parent = 0";
1972             }
1973         }
1975         if (!empty($forum->onlygroups)) {
1976             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
1977             $params = array_merge($params, $groupid_params);
1978             $select[] = "d.groupid $groupid_sql";
1979         }
1981         if ($select) {
1982             $selects = implode(" AND ", $select);
1983             $where[] = "(d.forum = :forum{$forumid} AND $selects)";
1984             $params['forum'.$forumid] = $forumid;
1985         } else {
1986             $fullaccess[] = $forumid;
1987         }
1988     }
1990     if ($fullaccess) {
1991         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
1992         $params = array_merge($params, $fullid_params);
1993         $where[] = "(d.forum $fullid_sql)";
1994     }
1996     $selectdiscussion = "(".implode(" OR ", $where).")";
1998     $messagesearch = '';
1999     $searchstring = '';
2001     // Need to concat these back together for parser to work.
2002     foreach($searchterms as $searchterm){
2003         if ($searchstring != '') {
2004             $searchstring .= ' ';
2005         }
2006         $searchstring .= $searchterm;
2007     }
2009     // We need to allow quoted strings for the search. The quotes *should* be stripped
2010     // by the parser, but this should be examined carefully for security implications.
2011     $searchstring = str_replace("\\\"","\"",$searchstring);
2012     $parser = new search_parser();
2013     $lexer = new search_lexer($parser);
2015     if ($lexer->parse($searchstring)) {
2016         $parsearray = $parser->get_parsed_array();
2017     // Experimental feature under 1.8! MDL-8830
2018     // Use alternative text searches if defined
2019     // This feature only works under mysql until properly implemented for other DBs
2020     // Requires manual creation of text index for forum_posts before enabling it:
2021     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2022     // Experimental feature under 1.8! MDL-8830
2023         if (!empty($CFG->forum_usetextsearches)) {
2024             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2025                                                  'p.userid', 'u.id', 'u.firstname',
2026                                                  'u.lastname', 'p.modified', 'd.forum');
2027         } else {
2028             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2029                                                  'p.userid', 'u.id', 'u.firstname',
2030                                                  'u.lastname', 'p.modified', 'd.forum');
2031         }
2032         $params = array_merge($params, $msparams);
2033     }
2035     $fromsql = "{forum_posts} p,
2036                   {forum_discussions} d,
2037                   {user} u";
2039     $selectsql = " $messagesearch
2040                AND p.discussion = d.id
2041                AND p.userid = u.id
2042                AND $selectdiscussion
2043                    $extrasql";
2045     $countsql = "SELECT COUNT(*)
2046                    FROM $fromsql
2047                   WHERE $selectsql";
2049     $searchsql = "SELECT p.*,
2050                          d.forum,
2051                          u.firstname,
2052                          u.lastname,
2053                          u.email,
2054                          u.picture,
2055                          u.imagealt,
2056                          u.email
2057                     FROM $fromsql
2058                    WHERE $selectsql
2059                 ORDER BY p.modified DESC";
2061     $totalcount = $DB->count_records_sql($countsql, $params);
2063     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2066 /**
2067  * Returns a list of ratings for a particular post - sorted.
2068  *
2069  * TODO: Check if this function is actually used anywhere.
2070  * Up until the fix for MDL-27471 this function wasn't even returning.
2071  *
2072  * @param stdClass $context
2073  * @param int $postid
2074  * @param string $sort
2075  * @return array Array of ratings or false
2076  */
2077 function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
2078     $options = new stdClass;
2079     $options->context = $context;
2080     $options->component = 'mod_forum';
2081     $options->ratingarea = 'post';
2082     $options->itemid = $postid;
2083     $options->sort = "ORDER BY $sort";
2085     $rm = new rating_manager();
2086     return $rm->get_all_ratings_for_item($options);
2089 /**
2090  * Returns a list of all new posts that have not been mailed yet
2091  *
2092  * @param int $starttime posts created after this time
2093  * @param int $endtime posts created before this
2094  * @param int $now used for timed discussions only
2095  * @return array
2096  */
2097 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2098     global $CFG, $DB;
2100     $params = array($starttime, $endtime);
2101     if (!empty($CFG->forum_enabletimedposts)) {
2102         if (empty($now)) {
2103             $now = time();
2104         }
2105         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2106         $params[] = $now;
2107         $params[] = $now;
2108     } else {
2109         $timedsql = "";
2110     }
2112     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2113                               FROM {forum_posts} p
2114                                    JOIN {forum_discussions} d ON d.id = p.discussion
2115                              WHERE p.mailed = 0
2116                                    AND p.created >= ?
2117                                    AND (p.created < ? OR p.mailnow = 1)
2118                                    $timedsql
2119                           ORDER BY p.modified ASC", $params);
2122 /**
2123  * Marks posts before a certain time as being mailed already
2124  *
2125  * @global object
2126  * @global object
2127  * @param int $endtime
2128  * @param int $now Defaults to time()
2129  * @return bool
2130  */
2131 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2132     global $CFG, $DB;
2133     if (empty($now)) {
2134         $now = time();
2135     }
2137     if (empty($CFG->forum_enabletimedposts)) {
2138         return $DB->execute("UPDATE {forum_posts}
2139                                SET mailed = '1'
2140                              WHERE (created < ? OR mailnow = 1)
2141                                    AND mailed = 0", array($endtime));
2143     } else {
2144         return $DB->execute("UPDATE {forum_posts}
2145                                SET mailed = '1'
2146                              WHERE discussion NOT IN (SELECT d.id
2147                                                         FROM {forum_discussions} d
2148                                                        WHERE d.timestart > ?)
2149                                    AND (created < ? OR mailnow = 1)
2150                                    AND mailed = 0", array($now, $endtime));
2151     }
2154 /**
2155  * Get all the posts for a user in a forum suitable for forum_print_post
2156  *
2157  * @global object
2158  * @global object
2159  * @uses CONTEXT_MODULE
2160  * @return array
2161  */
2162 function forum_get_user_posts($forumid, $userid) {
2163     global $CFG, $DB;
2165     $timedsql = "";
2166     $params = array($forumid, $userid);
2168     if (!empty($CFG->forum_enabletimedposts)) {
2169         $cm = get_coursemodule_from_instance('forum', $forumid);
2170         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2171             $now = time();
2172             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2173             $params[] = $now;
2174             $params[] = $now;
2175         }
2176     }
2178     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2179                               FROM {forum} f
2180                                    JOIN {forum_discussions} d ON d.forum = f.id
2181                                    JOIN {forum_posts} p       ON p.discussion = d.id
2182                                    JOIN {user} u              ON u.id = p.userid
2183                              WHERE f.id = ?
2184                                    AND p.userid = ?
2185                                    $timedsql
2186                           ORDER BY p.modified ASC", $params);
2189 /**
2190  * Get all the discussions user participated in
2191  *
2192  * @global object
2193  * @global object
2194  * @uses CONTEXT_MODULE
2195  * @param int $forumid
2196  * @param int $userid
2197  * @return array Array or false
2198  */
2199 function forum_get_user_involved_discussions($forumid, $userid) {
2200     global $CFG, $DB;
2202     $timedsql = "";
2203     $params = array($forumid, $userid);
2204     if (!empty($CFG->forum_enabletimedposts)) {
2205         $cm = get_coursemodule_from_instance('forum', $forumid);
2206         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2207             $now = time();
2208             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2209             $params[] = $now;
2210             $params[] = $now;
2211         }
2212     }
2214     return $DB->get_records_sql("SELECT DISTINCT d.*
2215                               FROM {forum} f
2216                                    JOIN {forum_discussions} d ON d.forum = f.id
2217                                    JOIN {forum_posts} p       ON p.discussion = d.id
2218                              WHERE f.id = ?
2219                                    AND p.userid = ?
2220                                    $timedsql", $params);
2223 /**
2224  * Get all the posts for a user in a forum suitable for forum_print_post
2225  *
2226  * @global object
2227  * @global object
2228  * @param int $forumid
2229  * @param int $userid
2230  * @return array of counts or false
2231  */
2232 function forum_count_user_posts($forumid, $userid) {
2233     global $CFG, $DB;
2235     $timedsql = "";
2236     $params = array($forumid, $userid);
2237     if (!empty($CFG->forum_enabletimedposts)) {
2238         $cm = get_coursemodule_from_instance('forum', $forumid);
2239         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2240             $now = time();
2241             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2242             $params[] = $now;
2243             $params[] = $now;
2244         }
2245     }
2247     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2248                              FROM {forum} f
2249                                   JOIN {forum_discussions} d ON d.forum = f.id
2250                                   JOIN {forum_posts} p       ON p.discussion = d.id
2251                                   JOIN {user} u              ON u.id = p.userid
2252                             WHERE f.id = ?
2253                                   AND p.userid = ?
2254                                   $timedsql", $params);
2257 /**
2258  * Given a log entry, return the forum post details for it.
2259  *
2260  * @global object
2261  * @global object
2262  * @param object $log
2263  * @return array|null
2264  */
2265 function forum_get_post_from_log($log) {
2266     global $CFG, $DB;
2268     if ($log->action == "add post") {
2270         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2271                                            u.firstname, u.lastname, u.email, u.picture
2272                                  FROM {forum_discussions} d,
2273                                       {forum_posts} p,
2274                                       {forum} f,
2275                                       {user} u
2276                                 WHERE p.id = ?
2277                                   AND d.id = p.discussion
2278                                   AND p.userid = u.id
2279                                   AND u.deleted <> '1'
2280                                   AND f.id = d.forum", array($log->info));
2283     } else if ($log->action == "add discussion") {
2285         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2286                                            u.firstname, u.lastname, u.email, u.picture
2287                                  FROM {forum_discussions} d,
2288                                       {forum_posts} p,
2289                                       {forum} f,
2290                                       {user} u
2291                                 WHERE d.id = ?
2292                                   AND d.firstpost = p.id
2293                                   AND p.userid = u.id
2294                                   AND u.deleted <> '1'
2295                                   AND f.id = d.forum", array($log->info));
2296     }
2297     return NULL;
2300 /**
2301  * Given a discussion id, return the first post from the discussion
2302  *
2303  * @global object
2304  * @global object
2305  * @param int $dicsussionid
2306  * @return array
2307  */
2308 function forum_get_firstpost_from_discussion($discussionid) {
2309     global $CFG, $DB;
2311     return $DB->get_record_sql("SELECT p.*
2312                              FROM {forum_discussions} d,
2313                                   {forum_posts} p
2314                             WHERE d.id = ?
2315                               AND d.firstpost = p.id ", array($discussionid));
2318 /**
2319  * Returns an array of counts of replies to each discussion
2320  *
2321  * @global object
2322  * @global object
2323  * @param int $forumid
2324  * @param string $forumsort
2325  * @param int $limit
2326  * @param int $page
2327  * @param int $perpage
2328  * @return array
2329  */
2330 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2331     global $CFG, $DB;
2333     if ($limit > 0) {
2334         $limitfrom = 0;
2335         $limitnum  = $limit;
2336     } else if ($page != -1) {
2337         $limitfrom = $page*$perpage;
2338         $limitnum  = $perpage;
2339     } else {
2340         $limitfrom = 0;
2341         $limitnum  = 0;
2342     }
2344     if ($forumsort == "") {
2345         $orderby = "";
2346         $groupby = "";
2348     } else {
2349         $orderby = "ORDER BY $forumsort";
2350         $groupby = ", ".strtolower($forumsort);
2351         $groupby = str_replace('desc', '', $groupby);
2352         $groupby = str_replace('asc', '', $groupby);
2353     }
2355     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2356         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2357                   FROM {forum_posts} p
2358                        JOIN {forum_discussions} d ON p.discussion = d.id
2359                  WHERE p.parent > 0 AND d.forum = ?
2360               GROUP BY p.discussion";
2361         return $DB->get_records_sql($sql, array($forumid));
2363     } else {
2364         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2365                   FROM {forum_posts} p
2366                        JOIN {forum_discussions} d ON p.discussion = d.id
2367                  WHERE d.forum = ?
2368               GROUP BY p.discussion $groupby
2369               $orderby";
2370         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2371     }
2374 /**
2375  * @global object
2376  * @global object
2377  * @global object
2378  * @staticvar array $cache
2379  * @param object $forum
2380  * @param object $cm
2381  * @param object $course
2382  * @return mixed
2383  */
2384 function forum_count_discussions($forum, $cm, $course) {
2385     global $CFG, $DB, $USER;
2387     static $cache = array();
2389     $now = round(time(), -2); // db cache friendliness
2391     $params = array($course->id);
2393     if (!isset($cache[$course->id])) {
2394         if (!empty($CFG->forum_enabletimedposts)) {
2395             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2396             $params[] = $now;
2397             $params[] = $now;
2398         } else {
2399             $timedsql = "";
2400         }
2402         $sql = "SELECT f.id, COUNT(d.id) as dcount
2403                   FROM {forum} f
2404                        JOIN {forum_discussions} d ON d.forum = f.id
2405                  WHERE f.course = ?
2406                        $timedsql
2407               GROUP BY f.id";
2409         if ($counts = $DB->get_records_sql($sql, $params)) {
2410             foreach ($counts as $count) {
2411                 $counts[$count->id] = $count->dcount;
2412             }
2413             $cache[$course->id] = $counts;
2414         } else {
2415             $cache[$course->id] = array();
2416         }
2417     }
2419     if (empty($cache[$course->id][$forum->id])) {
2420         return 0;
2421     }
2423     $groupmode = groups_get_activity_groupmode($cm, $course);
2425     if ($groupmode != SEPARATEGROUPS) {
2426         return $cache[$course->id][$forum->id];
2427     }
2429     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2430         return $cache[$course->id][$forum->id];
2431     }
2433     require_once($CFG->dirroot.'/course/lib.php');
2435     $modinfo =& get_fast_modinfo($course);
2436     if (is_null($modinfo->groups)) {
2437         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2438     }
2440     if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2441         $mygroups = $modinfo->groups[$cm->groupingid];
2442     } else {
2443         $mygroups = false; // Will be set below
2444     }
2446     // add all groups posts
2447     if (empty($mygroups)) {
2448         $mygroups = array(-1=>-1);
2449     } else {
2450         $mygroups[-1] = -1;
2451     }
2453     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2454     $params[] = $forum->id;
2456     if (!empty($CFG->forum_enabletimedposts)) {
2457         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2458         $params[] = $now;
2459         $params[] = $now;
2460     } else {
2461         $timedsql = "";
2462     }
2464     $sql = "SELECT COUNT(d.id)
2465               FROM {forum_discussions} d
2466              WHERE d.groupid $mygroups_sql AND d.forum = ?
2467                    $timedsql";
2469     return $DB->get_field_sql($sql, $params);
2472 /**
2473  * How many posts by other users are unrated by a given user in the given discussion?
2474  *
2475  * TODO: Is this function still used anywhere?
2476  *
2477  * @param int $discussionid
2478  * @param int $userid
2479  * @return mixed
2480  */
2481 function forum_count_unrated_posts($discussionid, $userid) {
2482     global $CFG, $DB;
2484     $sql = "SELECT COUNT(*) as num
2485               FROM {forum_posts}
2486              WHERE parent > 0
2487                AND discussion = :discussionid
2488                AND userid <> :userid";
2489     $params = array('discussionid' => $discussionid, 'userid' => $userid);
2490     $posts = $DB->get_record_sql($sql, $params);
2491     if ($posts) {
2492         $sql = "SELECT count(*) as num
2493                   FROM {forum_posts} p,
2494                        {rating} r
2495                  WHERE p.discussion = :discussionid AND
2496                        p.id = r.itemid AND
2497                        r.userid = userid AND
2498                        r.component = 'mod_forum' AND
2499                        r.ratingarea = 'post'";
2500         $rated = $DB->get_record_sql($sql, $params);
2501         if ($rated) {
2502             if ($posts->num > $rated->num) {
2503                 return $posts->num - $rated->num;
2504             } else {
2505                 return 0;    // Just in case there was a counting error
2506             }
2507         } else {
2508             return $posts->num;
2509         }
2510     } else {
2511         return 0;
2512     }
2515 /**
2516  * Get all discussions in a forum
2517  *
2518  * @global object
2519  * @global object
2520  * @global object
2521  * @uses CONTEXT_MODULE
2522  * @uses VISIBLEGROUPS
2523  * @param object $cm
2524  * @param string $forumsort
2525  * @param bool $fullpost
2526  * @param int $unused
2527  * @param int $limit
2528  * @param bool $userlastmodified
2529  * @param int $page
2530  * @param int $perpage
2531  * @return array
2532  */
2533 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2534     global $CFG, $DB, $USER;
2536     $timelimit = '';
2538     $now = round(time(), -2);
2539     $params = array($cm->instance);
2541     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2543     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2544         return array();
2545     }
2547     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2549         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2550             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2551             $params[] = $now;
2552             $params[] = $now;
2553             if (isloggedin()) {
2554                 $timelimit .= " OR d.userid = ?";
2555                 $params[] = $USER->id;
2556             }
2557             $timelimit .= ")";
2558         }
2559     }
2561     if ($limit > 0) {
2562         $limitfrom = 0;
2563         $limitnum  = $limit;
2564     } else if ($page != -1) {
2565         $limitfrom = $page*$perpage;
2566         $limitnum  = $perpage;
2567     } else {
2568         $limitfrom = 0;
2569         $limitnum  = 0;
2570     }
2572     $groupmode    = groups_get_activity_groupmode($cm);
2573     $currentgroup = groups_get_activity_group($cm);
2575     if ($groupmode) {
2576         if (empty($modcontext)) {
2577             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2578         }
2580         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2581             if ($currentgroup) {
2582                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2583                 $params[] = $currentgroup;
2584             } else {
2585                 $groupselect = "";
2586             }
2588         } else {
2589             //seprate groups without access all
2590             if ($currentgroup) {
2591                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2592                 $params[] = $currentgroup;
2593             } else {
2594                 $groupselect = "AND d.groupid = -1";
2595             }
2596         }
2597     } else {
2598         $groupselect = "";
2599     }
2602     if (empty($forumsort)) {
2603         $forumsort = "d.timemodified DESC";
2604     }
2605     if (empty($fullpost)) {
2606         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2607     } else {
2608         $postdata = "p.*";
2609     }
2611     if (empty($userlastmodified)) {  // We don't need to know this
2612         $umfields = "";
2613         $umtable  = "";
2614     } else {
2615         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2616         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2617     }
2619     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2620                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2621               FROM {forum_discussions} d
2622                    JOIN {forum_posts} p ON p.discussion = d.id
2623                    JOIN {user} u ON p.userid = u.id
2624                    $umtable
2625              WHERE d.forum = ? AND p.parent = 0
2626                    $timelimit $groupselect
2627           ORDER BY $forumsort";
2628     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2631 /**
2632  *
2633  * @global object
2634  * @global object
2635  * @global object
2636  * @uses CONTEXT_MODULE
2637  * @uses VISIBLEGROUPS
2638  * @param object $cm
2639  * @return array
2640  */
2641 function forum_get_discussions_unread($cm) {
2642     global $CFG, $DB, $USER;
2644     $now = round(time(), -2);
2645     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2647     $params = array();
2648     $groupmode    = groups_get_activity_groupmode($cm);
2649     $currentgroup = groups_get_activity_group($cm);
2651     if ($groupmode) {
2652         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2654         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2655             if ($currentgroup) {
2656                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2657                 $params['currentgroup'] = $currentgroup;
2658             } else {
2659                 $groupselect = "";
2660             }
2662         } else {
2663             //separate groups without access all
2664             if ($currentgroup) {
2665                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2666                 $params['currentgroup'] = $currentgroup;
2667             } else {
2668                 $groupselect = "AND d.groupid = -1";
2669             }
2670         }
2671     } else {
2672         $groupselect = "";
2673     }
2675     if (!empty($CFG->forum_enabletimedposts)) {
2676         $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2677         $params['now1'] = $now;
2678         $params['now2'] = $now;
2679     } else {
2680         $timedsql = "";
2681     }
2683     $sql = "SELECT d.id, COUNT(p.id) AS unread
2684               FROM {forum_discussions} d
2685                    JOIN {forum_posts} p     ON p.discussion = d.id
2686                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2687              WHERE d.forum = {$cm->instance}
2688                    AND p.modified >= :cutoffdate AND r.id is NULL
2689                    $groupselect
2690                    $timedsql
2691           GROUP BY d.id";
2692     $params['cutoffdate'] = $cutoffdate;
2694     if ($unreads = $DB->get_records_sql($sql, $params)) {
2695         foreach ($unreads as $unread) {
2696             $unreads[$unread->id] = $unread->unread;
2697         }
2698         return $unreads;
2699     } else {
2700         return array();
2701     }
2704 /**
2705  * @global object
2706  * @global object
2707  * @global object
2708  * @uses CONEXT_MODULE
2709  * @uses VISIBLEGROUPS
2710  * @param object $cm
2711  * @return array
2712  */
2713 function forum_get_discussions_count($cm) {
2714     global $CFG, $DB, $USER;
2716     $now = round(time(), -2);
2717     $params = array($cm->instance);
2718     $groupmode    = groups_get_activity_groupmode($cm);
2719     $currentgroup = groups_get_activity_group($cm);
2721     if ($groupmode) {
2722         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2724         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2725             if ($currentgroup) {
2726                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2727                 $params[] = $currentgroup;
2728             } else {
2729                 $groupselect = "";
2730             }
2732         } else {
2733             //seprate groups without access all
2734             if ($currentgroup) {
2735                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2736                 $params[] = $currentgroup;
2737             } else {
2738                 $groupselect = "AND d.groupid = -1";
2739             }
2740         }
2741     } else {
2742         $groupselect = "";
2743     }
2745     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2747     $timelimit = "";
2749     if (!empty($CFG->forum_enabletimedposts)) {
2751         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2753         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2754             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2755             $params[] = $now;
2756             $params[] = $now;
2757             if (isloggedin()) {
2758                 $timelimit .= " OR d.userid = ?";
2759                 $params[] = $USER->id;
2760             }
2761             $timelimit .= ")";
2762         }
2763     }
2765     $sql = "SELECT COUNT(d.id)
2766               FROM {forum_discussions} d
2767                    JOIN {forum_posts} p ON p.discussion = d.id
2768              WHERE d.forum = ? AND p.parent = 0
2769                    $groupselect $timelimit";
2771     return $DB->get_field_sql($sql, $params);
2775 /**
2776  * Get all discussions started by a particular user in a course (or group)
2777  * This function no longer used ...
2778  *
2779  * @todo Remove this function if no longer used
2780  * @global object
2781  * @global object
2782  * @param int $courseid
2783  * @param int $userid
2784  * @param int $groupid
2785  * @return array
2786  */
2787 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2788     global $CFG, $DB;
2789     $params = array($courseid, $userid);
2790     if ($groupid) {
2791         $groupselect = " AND d.groupid = ? ";
2792         $params[] = $groupid;
2793     } else  {
2794         $groupselect = "";
2795     }
2797     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2798                                    f.type as forumtype, f.name as forumname, f.id as forumid
2799                               FROM {forum_discussions} d,
2800                                    {forum_posts} p,
2801                                    {user} u,
2802                                    {forum} f
2803                              WHERE d.course = ?
2804                                AND p.discussion = d.id
2805                                AND p.parent = 0
2806                                AND p.userid = u.id
2807                                AND u.id = ?
2808                                AND d.forum = f.id $groupselect
2809                           ORDER BY p.created DESC", $params);
2812 /**
2813  * Get the list of potential subscribers to a forum.
2814  *
2815  * @param object $forumcontext the forum context.
2816  * @param integer $groupid the id of a group, or 0 for all groups.
2817  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2818  * @param string $sort sort order. As for get_users_by_capability.
2819  * @return array list of users.
2820  */
2821 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2822     global $DB;
2824     // only active enrolled users or everybody on the frontpage
2825     list($esql, $params) = get_enrolled_sql($forumcontext, '', $groupid, true);
2827     $sql = "SELECT $fields
2828               FROM {user} u
2829               JOIN ($esql) je ON je.id = u.id";
2830     if ($sort) {
2831         $sql = "$sql ORDER BY $sort";
2832     } else {
2833         $sql = "$sql ORDER BY u.lastname ASC, u.firstname ASC";
2834     }
2836     return $DB->get_records_sql($sql, $params);
2839 /**
2840  * Returns list of user objects that are subscribed to this forum
2841  *
2842  * @global object
2843  * @global object
2844  * @param object $course the course
2845  * @param forum $forum the forum
2846  * @param integer $groupid group id, or 0 for all.
2847  * @param object $context the forum context, to save re-fetching it where possible.
2848  * @param string $fields requested user fields (with "u." table prefix)
2849  * @return array list of users.
2850  */
2851 function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
2852     global $CFG, $DB;
2854     if (empty($fields)) {
2855         $fields ="u.id,
2856                   u.username,
2857                   u.firstname,
2858                   u.lastname,
2859                   u.maildisplay,
2860                   u.mailformat,
2861                   u.maildigest,
2862                   u.imagealt,
2863                   u.email,
2864                   u.emailstop,
2865                   u.city,
2866                   u.country,
2867                   u.lastaccess,
2868                   u.lastlogin,
2869                   u.picture,
2870                   u.timezone,
2871                   u.theme,
2872                   u.lang,
2873                   u.trackforums,
2874                   u.mnethostid";
2875     }
2877     if (empty($context)) {
2878         $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2879         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2880     }
2882     if (forum_is_forcesubscribed($forum)) {
2883         $results = forum_get_potential_subscribers($context, $groupid, $fields, "u.email ASC");
2885     } else {
2886         // only active enrolled users or everybody on the frontpage
2887         list($esql, $params) = get_enrolled_sql($context, '', $groupid, true);
2888         $params['forumid'] = $forum->id;
2889         $results = $DB->get_records_sql("SELECT $fields
2890                                            FROM {user} u
2891                                            JOIN ($esql) je ON je.id = u.id
2892                                            JOIN {forum_subscriptions} s ON s.userid = u.id
2893                                           WHERE s.forum = :forumid
2894                                        ORDER BY u.email ASC", $params);
2895     }
2897     // Guest user should never be subscribed to a forum.
2898     unset($results[$CFG->siteguest]);
2900     return $results;
2905 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2908 /**
2909  * @global object
2910  * @global object
2911  * @param int $courseid
2912  * @param string $type
2913  */
2914 function forum_get_course_forum($courseid, $type) {
2915 // How to set up special 1-per-course forums
2916     global $CFG, $DB, $OUTPUT;
2918     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2919         // There should always only be ONE, but with the right combination of
2920         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2921         foreach ($forums as $forum) {
2922             return $forum;   // ie the first one
2923         }
2924     }
2926     // Doesn't exist, so create one now.
2927     $forum = new stdClass();
2928     $forum->course = $courseid;
2929     $forum->type = "$type";
2930     switch ($forum->type) {
2931         case "news":
2932             $forum->name  = get_string("namenews", "forum");
2933             $forum->intro = get_string("intronews", "forum");
2934             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2935             $forum->assessed = 0;
2936             if ($courseid == SITEID) {
2937                 $forum->name  = get_string("sitenews");
2938                 $forum->forcesubscribe = 0;
2939             }
2940             break;
2941         case "social":
2942             $forum->name  = get_string("namesocial", "forum");
2943             $forum->intro = get_string("introsocial", "forum");
2944             $forum->assessed = 0;
2945             $forum->forcesubscribe = 0;
2946             break;
2947         case "blog":
2948             $forum->name = get_string('blogforum', 'forum');
2949             $forum->intro = get_string('introblog', 'forum');
2950             $forum->assessed = 0;
2951             $forum->forcesubscribe = 0;
2952             break;
2953         default:
2954             echo $OUTPUT->notification("That forum type doesn't exist!");
2955             return false;
2956             break;
2957     }
2959     $forum->timemodified = time();
2960     $forum->id = $DB->insert_record("forum", $forum);
2962     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2963         echo $OUTPUT->notification("Could not find forum module!!");
2964         return false;
2965     }
2966     $mod = new stdClass();
2967     $mod->course = $courseid;
2968     $mod->module = $module->id;
2969     $mod->instance = $forum->id;
2970     $mod->section = 0;
2971     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
2972         echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
2973         return false;
2974     }
2975     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
2976         echo $OUTPUT->notification("Could not add the new course module to that section");
2977         return false;
2978     }
2979     $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule));
2981     include_once("$CFG->dirroot/course/lib.php");
2982     rebuild_course_cache($courseid);
2984     return $DB->get_record("forum", array("id" => "$forum->id"));
2988 /**
2989  * Given the data about a posting, builds up the HTML to display it and
2990  * returns the HTML in a string.  This is designed for sending via HTML email.
2991  *
2992  * @global object
2993  * @param object $course
2994  * @param object $cm
2995  * @param object $forum
2996  * @param object $discussion
2997  * @param object $post
2998  * @param object $userform
2999  * @param object $userto
3000  * @param bool $ownpost
3001  * @param bool $reply
3002  * @param bool $link
3003  * @param bool $rate
3004  * @param string $footer
3005  * @return string
3006  */
3007 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
3008                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
3010     global $CFG, $OUTPUT;
3012     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3014     if (!isset($userto->viewfullnames[$forum->id])) {
3015         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3016     } else {
3017         $viewfullnames = $userto->viewfullnames[$forum->id];
3018     }
3020     // add absolute file links
3021     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3023     // format the post body
3024     $options = new stdClass();
3025     $options->para = true;
3026     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3028     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3030     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3031     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3032     $output .= '</td>';
3034     if ($post->parent) {
3035         $output .= '<td class="topic">';
3036     } else {
3037         $output .= '<td class="topic starter">';
3038     }
3039     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3041     $fullname = fullname($userfrom, $viewfullnames);
3042     $by = new stdClass();
3043     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3044     $by->date = userdate($post->modified, '', $userto->timezone);
3045     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3047     $output .= '</td></tr>';
3049     $output .= '<tr><td class="left side" valign="top">';
3051     if (isset($userfrom->groups)) {
3052         $groups = $userfrom->groups[$forum->id];
3053     } else {
3054         $groups = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3055     }
3057     if ($groups) {
3058         $output .= print_group_picture($groups, $course->id, false, true, true);
3059     } else {
3060         $output .= '&nbsp;';
3061     }
3063     $output .= '</td><td class="content">';
3065     $attachments = forum_print_attachments($post, $cm, 'html');
3066     if ($attachments !== '') {
3067         $output .= '<div class="attachments">';
3068         $output .= $attachments;
3069         $output .= '</div>';
3070     }
3072     $output .= $formattedtext;
3074 // Commands
3075     $commands = array();
3077     if ($post->parent) {
3078         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3079                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3080     }
3082     if ($reply) {
3083         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3084                       get_string('reply', 'forum').'</a>';
3085     }
3087     $output .= '<div class="commands">';
3088     $output .= implode(' | ', $commands);
3089     $output .= '</div>';
3091 // Context link to post if required
3092     if ($link) {
3093         $output .= '<div class="link">';
3094         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3095                      get_string('postincontext', 'forum').'</a>';
3096         $output .= '</div>';
3097     }
3099     if ($footer) {
3100         $output .= '<div class="footer">'.$footer.'</div>';
3101     }
3102     $output .= '</td></tr></table>'."\n\n";
3104     return $output;
3107 /**
3108  * Print a forum post
3109  *
3110  * @global object
3111  * @global object
3112  * @uses FORUM_MODE_THREADED
3113  * @uses PORTFOLIO_FORMAT_PLAINHTML
3114  * @uses PORTFOLIO_FORMAT_FILE
3115  * @uses PORTFOLIO_FORMAT_RICHHTML
3116  * @uses PORTFOLIO_ADD_TEXT_LINK
3117  * @uses CONTEXT_MODULE
3118  * @param object $post The post to print.
3119  * @param object $discussion
3120  * @param object $forum
3121  * @param object $cm
3122  * @param object $course
3123  * @param boolean $ownpost Whether this post belongs to the current user.
3124  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3125  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3126  * @param string $footer Extra stuff to print after the message.
3127  * @param string $highlight Space-separated list of terms to highlight.
3128  * @param int $post_read true, false or -99. If we already know whether this user
3129  *          has read this post, pass that in, otherwise, pass in -99, and this
3130  *          function will work it out.
3131  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3132  *          the current user can't see this post, if this argument is true
3133  *          (the default) then print a dummy 'you can't see this post' post.
3134  *          If false, don't output anything at all.
3135  * @param bool|null $istracked
3136  * @return void
3137  */
3138 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3139                           $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3140     global $USER, $CFG, $OUTPUT;
3142     require_once($CFG->libdir . '/filelib.php');
3144     // String cache
3145     static $str;
3147     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3149     $post->course = $course->id;
3150     $post->forum  = $forum->id;
3151     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3153     // caching
3154     if (!isset($cm->cache)) {
3155         $cm->cache = new stdClass;
3156     }
3158     if (!isset($cm->cache->caps)) {
3159         $cm->cache->caps = array();
3160         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3161         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3162         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3163         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3164         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3165         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3166         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3167         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3168         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3169     }
3171     if (!isset($cm->uservisible)) {
3172         $cm->uservisible = coursemodule_visible_for_user($cm);
3173     }
3175     if ($istracked && is_null($postisread)) {
3176         $postisread = forum_tp_is_post_read($USER->id, $post);
3177     }
3179     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3180         $output = '';
3181         if (!$dummyifcantsee) {
3182             if ($return) {
3183                 return $output;
3184             }
3185             echo $output;
3186             return;
3187         }
3188         $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3189         $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'));
3190         $output .= html_writer::start_tag('div', array('class'=>'row header'));
3191         $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3192         if ($post->parent) {
3193             $output .= html_writer::start_tag('div', array('class'=>'topic'));
3194         } else {
3195             $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3196         }
3197         $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class'=>'subject')); // Subject
3198         $output .= html_writer::tag('div', get_string('forumauthorhidden','forum'), array('class'=>'author')); // author
3199         $output .= html_writer::end_tag('div');
3200         $output .= html_writer::end_tag('div'); // row
3201         $output .= html_writer::start_tag('div', array('class'=>'row'));
3202         $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3203         $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3204         $output .= html_writer::end_tag('div'); // row
3205         $output .= html_writer::end_tag('div'); // forumpost
3207         if ($return) {
3208             return $output;
3209         }
3210         echo $output;
3211         return;
3212     }
3214     if (empty($str)) {
3215         $str = new stdClass;
3216         $str->edit         = get_string('edit', 'forum');
3217         $str->delete       = get_string('delete', 'forum');
3218         $str->reply        = get_string('reply', 'forum');
3219         $str->parent       = get_string('parent', 'forum');
3220         $str->pruneheading = get_string('pruneheading', 'forum');
3221         $str->prune        = get_string('prune', 'forum');
3222         $str->displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3223         $str->markread     = get_string('markread', 'forum');
3224         $str->markunread   = get_string('markunread', 'forum');
3225     }
3227     $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3229     // Build an object that represents the posting user
3230     $postuser = new stdClass;
3231     $postuser->id        = $post->userid;
3232     $postuser->firstname = $post->firstname;
3233     $postuser->lastname  = $post->lastname;
3234     $postuser->imagealt  = $post->imagealt;
3235     $postuser->picture   = $post->picture;
3236     $postuser->email     = $post->email;
3237     // Some handy things for later on
3238     $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3239     $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3241     // Prepare the groups the posting user belongs to
3242     if (isset($cm->cache->usersgroups)) {
3243         $groups = array();
3244         if (isset($cm->cache->usersgroups[$post->userid])) {
3245             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3246                 $groups[$gid] = $cm->cache->groups[$gid];
3247             }
3248         }
3249     } else {
3250         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3251     }
3253     // Prepare the attachements for the post, files then images
3254     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3256     // Determine if we need to shorten this post
3257     $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3260     // Prepare an array of commands
3261     $commands = array();
3263     // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3264     // Don't display the mark read / unread controls in this case.
3265     if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3266         $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3267         $text = $str->markunread;
3268         if (!$postisread) {
3269             $url->param('mark', 'read');
3270             $text = $str->markread;
3271         }
3272         if ($str->displaymode == FORUM_MODE_THREADED) {
3273             $url->param('parent', $post->parent);
3274         } else {
3275             $url->set_anchor('p'.$post->id);
3276         }
3277         $commands[] = array('url'=>$url, 'text'=>$text);
3278     }
3280     // Zoom in to the parent specifically
3281     if ($post->parent) {
3282         $url = new moodle_url($discussionlink);
3283         if ($str->displaymode == FORUM_MODE_THREADED) {
3284             $url->param('parent', $post->parent);
3285         } else {
3286             $url->set_anchor('p'.$post->parent);
3287         }
3288         $commands[] = array('url'=>$url, 'text'=>$str->parent);
3289     }
3291     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3292     $age = time() - $post->created;
3293     if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3294         $age = 0;
3295     }
3296     if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3297         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3298     }
3300     if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3301         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3302     }
3304     if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3305         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3306     }
3308     if ($reply) {
3309         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('reply'=>$post->id)), 'text'=>$str->reply);
3310     }
3312     if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
3313         $p = array('postid' => $post->id);
3314         require_once($CFG->libdir.'/portfoliolib.php');
3315         $button = new portfolio_add_button();
3316         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php');
3317         if (empty($attachments)) {
3318             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3319         } else {
3320             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3321         }
3323         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3324         if (!empty($porfoliohtml)) {
3325             $commands[] = $porfoliohtml;
3326         }
3327     }
3328     // Finished building commands
3331     // Begin output
3333     $output  = '';
3335     if ($istracked) {
3336         if ($postisread) {
3337             $forumpostclass = ' read';
3338         } else {
3339             $forumpostclass = ' unread';
3340             $output .= html_writer::tag('a', '', array('name'=>'unread'));
3341         }
3342     } else {
3343         // ignore trackign status if not tracked or tracked param missing
3344         $forumpostclass = '';
3345     }
3347     $topicclass = '';
3348     if (empty($post->parent)) {
3349         $topicclass = ' firstpost starter';
3350     }
3352     $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3353     $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass));
3354     $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3355     $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3356     $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3357     $output .= html_writer::end_tag('div');
3360     $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3362     $postsubject = $post->subject;
3363     if (empty($post->subjectnoformat)) {
3364         $postsubject = format_string($postsubject);
3365     }
3366     $output .= html_writer::tag('div', $postsubject, array('class'=>'subject'));
3368     $by = new stdClass();
3369     $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3370     $by->date = userdate($post->modified);
3371     $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author'));
3373     $output .= html_writer::end_tag('div'); //topic
3374     $output .= html_writer::end_tag('div'); //row
3376     $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3377     $output .= html_writer::start_tag('div', array('class'=>'left'));
3379     $groupoutput = '';
3380     if ($groups) {
3381         $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3382     }
3383     if (empty($groupoutput)) {
3384         $groupoutput = '&nbsp;';
3385     }
3386     $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3388     $output .= html_writer::end_tag('div'); //left side
3389     $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3390     $output .= html_writer::start_tag('div', array('class'=>'content'));
3391     if (!empty($attachments)) {
3392         $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3393     }
3395     $options = new stdClass;
3396     $options->para    = false;
3397     $options->trusted = $post->messagetrust;
3398     $options->context = $modcontext;
3399     if ($shortenpost) {
3400         // Prepare shortened version
3401         $postclass    = 'shortenedpost';
3402         $postcontent  = format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3403         $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
3404         $postcontent .= html_writer::tag('span', '('.get_string('numwords', 'moodle', count_words(strip_tags($post->message))).')...', array('class'=>'post-word-count'));
3405     } else {
3406         // Prepare whole post
3407         $postclass    = 'fullpost';
3408         $postcontent  = format_text($post->message, $post->messageformat, $options, $course->id);
3409         if (!empty($highlight)) {
3410             $postcontent = highlight($highlight, $postcontent);
3411         }
3412         $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
3413     }
3414     // Output the post content
3415     $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
3416     $output .= html_writer::end_tag('div'); // Content
3417     $output .= html_writer::end_tag('div'); // Content mask
3418     $output .= html_writer::end_tag('div'); // Row
3420     $output .= html_writer::start_tag('div', array('class'=>'row side'));
3421     $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
3422     $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
3424     // Output ratings
3425     if (!empty($post->rating)) {
3426         $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
3427     }
3429     // Output the commands
3430     $commandhtml = array();
3431     foreach ($commands as $command) {
3432         if (is_array($command)) {
3433             $commandhtml[] = html_writer::link($command['url'], $command['text']);
3434         } else {
3435             $commandhtml[] = $command;
3436         }
3437     }
3438     $output .= html_writer::tag('div', implode(' | ', $commandhtml), array('class'=>'commands'));
3440     // Output link to post if required
3441     if ($link) {
3442         if ($post->replies == 1) {
3443             $replystring = get_string('repliesone', 'forum', $post->replies);
3444         } else {
3445             $replystring = get_string('repliesmany', 'forum', $post->replies);
3446         }
3448         $output .= html_writer::start_tag('div', array('class'=>'link'));
3449         $output .= html_writer::link($discussionlink, get_string('discussthistopic', 'forum'));
3450         $output .= '&nbsp;('.$replystring.')';
3451         $output .= html_writer::end_tag('div'); // link
3452     }
3454     // Output footer if required
3455     if ($footer) {
3456         $output .= html_writer::tag('div', $footer, array('class'=>'footer'));
3457     }
3459     // Close remaining open divs
3460     $output .= html_writer::end_tag('div'); // content
3461     $output .= html_writer::end_tag('div'); // row
3462     $output .= html_writer::end_tag('div'); // forumpost
3464     // Mark the forum post as read if required
3465     if ($istracked && !$CFG->forum_usermarksread && !$postisread) {
3466         forum_tp_mark_post_read($USER->id, $post, $forum->id);
3467     }
3469     if ($return) {
3470         return $output;
3471     }
3472     echo $output;
3473     return;
3476 /**
3477  * Return rating related permissions
3478  *
3479  * @param string $options the context id
3480  * @return array an associative array of the user's rating permissions
3481  */
3482 function forum_rating_permissions($contextid, $component, $ratingarea) {
3483     $context = get_context_instance_by_id($contextid, MUST_EXIST);
3484     if ($component != 'mod_forum' || $ratingarea != 'post') {
3485         // We don't know about this component/ratingarea so just return null to get the
3486         // default restrictive permissions.
3487         return null;
3488     }
3489     return array(
3490         'view'    => has_capability('mod/forum:viewrating', $context),
3491         'viewany' => has_capability('mod/forum:viewanyrating', $context),
3492         'viewall' => has_capability('mod/forum:viewallratings', $context),
3493         'rate'    => has_capability('mod/forum:rate', $context)
3494     );
3497 /**
3498  * Validates a submitted rating
3499  * @param array $params submitted data
3500  *            context => object the context in which the rated items exists [required]
3501  *            component => The component for this module - should always be mod_forum [required]
3502  *            ratingarea => object the context in which the rated items exists [required]
3503  *            itemid => int the ID of the object being rated [required]
3504  *            scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
3505  *            rating => int the submitted rating [required]
3506  *            rateduserid => int the id of the user whose items have been rated. NOT the user who submitted the ratings. 0 to update all. [required]
3507  *            aggregation => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [required]
3508  * @return boolean true if the rating is valid. Will throw rating_exception if not
3509  */
3510 function forum_rating_validate($params) {
3511     global $DB, $USER;
3513     // Check the component is mod_forum
3514     if ($params['component'] != 'mod_forum') {
3515         throw new rating_exception('invalidcomponent');
3516     }
3518     // Check the ratingarea is post (the only rating area in forum)
3519     if ($params['ratingarea'] != 'post') {
3520         throw new rating_exception('invalidratingarea');
3521     }
3523     // Check the rateduserid is not the current user .. you can't rate your own posts
3524     if ($params['rateduserid'] == $USER->id) {
3525         throw new rating_exception('nopermissiontorate');
3526     }
3528     // Fetch all the related records ... we need to do this anyway to call forum_user_can_see_post
3529     $post = $DB->get_record('forum_posts', array('id' => $params['itemid'], 'userid' => $params['rateduserid']), '*', MUST_EXIST);
3530     $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion), '*', MUST_EXIST);
3531     $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
3532     $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
3533     $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id , false, MUST_EXIST);
3534     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
3536     // Make sure the context provided is the context of the forum
3537     if ($context->id != $params['context']->id) {
3538         throw new rating_exception('invalidcontext');
3539     }
3541     if ($forum->scale != $params['scaleid']) {
3542         //the scale being submitted doesnt match the one in the database
3543         throw new rating_exception('invalidscaleid');
3544     }
3546     // check the item we're rating was created in the assessable time window
3547     if (!empty($forum->assesstimestart) && !empty($forum->assesstimefinish)) {
3548         if ($post->created < $forum->assesstimestart || $post->created > $forum->assesstimefinish) {
3549             throw new rating_exception('notavailable');
3550         }
3551     }
3553     //check that the submitted rating is valid for the scale
3555     // lower limit
3556     if ($params['rating'] < 0  && $params['rating'] != RATING_UNSET_RATING) {
3557         throw new rating_exception('invalidnum');
3558     }
3560     // upper limit
3561     if ($forum->scale < 0) {
3562         //its a custom scale
3563         $scalerecord = $DB->get_record('scale', array('id' => -$forum->scale));
3564         if ($scalerecord) {
3565             $scalearray = explode(',', $scalerecord->scale);
3566             if ($params['rating'] > count($scalearray)) {
3567                 throw new rating_exception('invalidnum');
3568             }
3569         } else {
3570             throw new rating_exception('invalidscaleid');
3571         }
3572     } else if ($params['rating'] > $forum->scale) {
3573         //if its numeric and submitted rating is above maximum
3574         throw new rating_exception('invalidnum');
3575     }
3577     // Make sure groups allow this user to see the item they're rating
3578     if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
3579         if (!groups_group_exists($discussion->groupid)) { // Can't find group
3580             throw new rating_exception('cannotfindgroup');//something is wrong
3581         }
3583         if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
3584             // do not allow rating of posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
3585             throw new rating_exception('notmemberofgroup');
3586         }
3587     }
3589     // perform some final capability checks
3590     if (!forum_user_can_see_post($forum, $discussion, $post, $USER, $cm)) {
3591         throw new rating_exception('nopermissiontorate');
3592     }
3594     return true;
3598 /**
3599  * This function prints the overview of a discussion in the forum listing.
3600  * It needs some discussion information and some post information, these
3601  * happen to be combined for efficiency in the $post parameter by the function
3602  * that calls this one: forum_print_latest_discussions()
3603  *
3604  * @global object
3605  * @global object
3606  * @param object $post The post object (passed by reference for speed).
3607  * @param object $forum The forum object.
3608  * @param int $group Current group.
3609  * @param string $datestring Format to use for the dates.
3610  * @param boolean $cantrack Is tracking enabled for this forum.
3611  * @param boolean $forumtracked Is the user tracking this forum.
3612  * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
3613  */
3614 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
3615                                         $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) {
3617     global $USER, $CFG, $OUTPUT;
3619     static $rowcount;
3620     static $strmarkalldread;
3622     if (empty($modcontext)) {
3623         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3624             print_error('invalidcoursemodule');
3625         }
3626         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3627     }
3629     if (!isset($rowcount)) {
3630         $rowcount = 0;
3631         $strmarkalldread = get_string('markalldread', 'forum');
3632     } else {
3633         $rowcount = ($rowcount + 1) % 2;
3634     }
3636     $post->subject = format_string($post->subject,true);
3638     echo "\n\n";
3639     echo '<tr class="discussion r'.$rowcount.'">';
3641     // Topic
3642     echo '<td class="topic starter">';
3643     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.$post->subject.'</a>';
3644     echo "</td>\n";
3646     // Picture
3647     $postuser = new stdClass();
3648     $postuser->id = $post->userid;
3649     $postuser->firstname = $post->firstname;
3650     $postuser->lastname = $post->lastname;
3651     $postuser->imagealt = $post->imagealt;
3652     $postuser->picture = $post->picture;
3653     $postuser->email = $post->email;
3655     echo '<td class="picture">';
3656     echo $OUTPUT->user_picture($postuser, array('courseid'=>$forum->course));
3657     echo "</td>\n";
3659     // User name
3660     $fullname = fullname($post, has_capability('moodle/site:viewfullnames', $modcontext));
3661     echo '<td class="author">';
3662     echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->userid.'&amp;course='.$forum->course.'">'.$fullname.'</a>';
3663     echo "</td>\n";
3665     // Group picture
3666     if ($group !== -1) {  // Groups are active - group is a group data object or NULL
3667         echo '<td class="picture group">';
3668         if (!empty($group->picture) and empty($group->hidepicture)) {
3669             print_group_picture($group, $forum->course, false, false, true);
3670         } else if (isset($group->id)) {
3671             if($canviewparticipants) {
3672                 echo '<a href="'.$CFG->wwwroot.'/user/index.php?id='.$forum->course.'&amp;group='.$group->id.'">'.$group->name.'</a>';
3673             } else {
3674                 echo $group->name;
3675             }
3676         }
3677         echo "</td>\n";
3678     }
3680     if (has_capability('mod/forum:viewdiscussion', $modcontext)) {   // Show the column with replies
3681         echo '<td class="replies">';
3682         echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3683         echo $post->replies.'</a>';
3684         echo "</td>\n";
3686         if ($cantrack) {
3687             echo '<td class="replies">';
3688             if ($forumtracked) {
3689                 if ($post->unread > 0) {
3690                     echo '<span class="unread">';
3691                     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#unread">';
3692                     echo $post->unread;
3693                     echo '</a>';
3694                     echo '<a title="'.$strmarkalldread.'" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
3695                          $forum->id.'&amp;d='.$post->discussion.'&amp;mark=read&amp;returnpage=view.php">' .
3696                          '<img src="'.$OUTPUT->pix_url('t/clear') . '" class="iconsmall" alt="'.$strmarkalldread.'" /></a>';
3697                     echo '</span>';
3698                 } else {
3699                     echo '<span class="read">';
3700                     echo $post->unread;
3701                     echo '</span>';
3702                 }
3703             } else {
3704                 echo '<span class="read">';
3705                 echo '-';
3706                 echo '</span>';
3707             }
3708             echo "</td>\n";
3709         }
3710     }
3712     echo '<td class="lastpost">';
3713     $usedate = (empty($post->timemodified)) ? $post->modified : $post->timemodified;  // Just in case
3714     $parenturl = (empty($post->lastpostid)) ? '' : '&amp;parent='.$post->lastpostid;
3715     $usermodified = new stdClass();
3716     $usermodified->id        = $post->usermodified;
3717     $usermodified->firstname = $post->umfirstname;
3718     $usermodified->lastname  = $post->umlastname;
3719     echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->usermodified.'&amp;course='.$forum->course.'">'.
3720          fullname($usermodified).'</a><br />';
3721     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.$parenturl.'">'.
3722           userdate($usedate, $datestring).'</a>';
3723     echo "</td>\n";
3725     echo "</tr>\n\n";
3730 /**
3731  * Given a post object that we already know has a long message
3732  * this function truncates the message nicely to the first
3733  * sane place between $CFG->forum_longpost and $CFG->forum_shortpost
3734  *
3735  * @global object
3736  * @param string $message
3737  * @return string
3738  */
3739 function forum_shorten_post($message) {
3741    global $CFG;
3743    $i = 0;
3744    $tag = false;
3745    $length = strlen($message);
3746    $count = 0;
3747    $stopzone = false;
3748    $truncate = 0;
3750    for ($i=0; $i<$length; $i++) {
3751        $char = $message[$i];
3753        switch ($char) {
3754            case "<":
3755                $tag = true;
3756                break;
3757            case ">":
3758                $tag = false;
3759                break;
3760            default:
3761                if (!$tag) {
3762                    if ($stopzone) {
3763                        if ($char == ".") {
3764                            $truncate = $i+1;
3765                            break 2;
3766                        }
3767                    }
3768                    $count++;
3769                }
3770                break;
3771        }
3772        if (!$stopzone) {
3773            if ($count > $CFG->forum_shortpost) {
3774                $stopzone = true;
3775            }
3776        }
3777    }
3779    if (!$truncate) {
3780        $truncate = $i;
3781    }
3783    return substr($message, 0, $truncate);
3786 /**
3787  * Print the drop down that allows the user to select how they want to have
3788  * the discussion displayed.
3789  *
3790  * @param int $id forum id if $forumtype is 'single',
3791  *              discussion id for any other forum type
3792  * @param mixed $mode forum layout mode
3793  * @param string $forumtype optional
3794  */
3795 function forum_print_mode_form($id, $mode, $forumtype='') {
3796     global $OUTPUT;
3797     if ($forumtype == 'single') {
3798         $select = new single_select(new moodle_url("/mod/forum/view.php", array('f'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode");
3799         $select->class = "forummode";
3800     } else {
3801         $select = new single_select(new moodle_url("/mod/forum/discuss.php", array('d'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode");
3802     }
3803     echo $OUTPUT->render($select);
3806 /**
3807  * @global object
3808  * @param object $course
3809  * @param string $search
3810  * @return string
3811  */
3812 function forum_search_form($course, $search='') {
3813     global $CFG, $OUTPUT;
3815     $output  = '<div class="forumsearch">';
3816     $output .= '<form action="'.$CFG->wwwroot.'/mod/forum/search.php" style="display:inline">';
3817     $output .= '<fieldset class="invisiblefieldset">';
3818     $output .= $OUTPUT->help_icon('search');
3819     $output .= '<label class="accesshide" for="search" >'.get_string('search', 'forum').'</label>';
3820     $output .= '<input id="search" name="search" type="text" size="18" value="'.s($search, true).'" alt="search" />';
3821     $output .= '<label class="accesshide" for="searchforums" >'.get_string('searchforums', 'forum').'</label>';
3822     $output .= '<input id="searchforums" value="'.get_string('searchforums', 'forum').'" type="submit" />';
3823     $output .= '<input name="id" type="hidden" value="'.$course->id.'" />';
3824     $output .= '</fieldset>';
3825     $output .= '</form>';
3826     $output .= '</div>';
3828     return $output;
3832 /**
3833  * @global object
3834  * @global object
3835  */
3836 function forum_set_return() {
3837     global $CFG, $SESSION;
3839     if (! isset($SESSION->fromdiscussion)) {
3840         if (!empty($_SERVER['HTTP_REFERER'])) {
3841             $referer = $_SERVER['HTTP_REFERER'];
3842         } else {
3843             $referer = "";
3844         }
3845         // If the referer is NOT a login screen then save it.
3846         if (! strncasecmp("$CFG->wwwroot/login", $referer, 300)) {
3847             $SESSION->fromdiscussion = $_SERVER["HTTP_REFERER"];
3848         }
3849     }
3853 /**
3854  * @global object
3855  * @param string $default
3856  * @return string
3857  */
3858 function forum_go_back_to($default) {
3859     global $SESSION;
3861     if (!empty($SESSION->fromdiscussion)) {
3862         $returnto = $SESSION->fromdiscussion;
3863         unset($SESSION->fromdiscussion);
3864         return $returnto;
3865     } else {
3866         return $default;
3867     }
3870 /**
3871  * Given a discussion object that is being moved to $forumto,
3872  * this function checks all posts in that discussion
3873  * for attachments, and if any are found, these are
3874  * moved to the new forum directory.
3875  *
3876  * @global object
3877  * @param object $discussion
3878  * @param int $forumfrom source forum id
3879  * @param int $forumto target forum id
3880  * @return bool success
3881  */
3882 function forum_move_attachments($discussion, $forumfrom, $forumto) {
3883     global $DB;
3885     $fs = get_file_storage();
3887     $newcm = get_coursemodule_from_instance('forum', $forumto);
3888     $oldcm = get_coursemodule_from_instance('forum', $forumfrom);
3890     $newcontext = get_context_instance(CONTEXT_MODULE, $newcm->id);
3891     $oldcontext = get_context_instance(CONTEXT_MODULE, $oldcm->id);
3893     // loop through all posts, better not use attachment flag ;-)
3894     if ($posts = $DB->get_records('forum_posts', array('discussion'=>$discussion->id), '', 'id, attachment')) {
3895         foreach ($posts as $post) {
3896             $fs->move_area_files_to_new_context($oldcontext->id,
3897                     $newcontext->id, 'mod_forum', 'post', $post->id);
3898             $attachmentsmoved = $fs->move_area_files_to_new_context($oldcontext->id,
3899                     $newcontext->id, 'mod_forum', 'attachment', $post->id);
3900             if ($attachmentsmoved > 0 && $post->attachment != '1') {
3901                 // Weird - let's fix it
3902                 $post->attachment = '1';
3903                 $DB->update_record('forum_posts', $post);
3904             } else if ($attachmentsmoved == 0 && $post->attachment != '') {
3905                 // Weird - let's fix it
3906                 $post->attachment = '';
3907                 $DB->update_record('forum_posts', $post);
3908             }
3909         }
3910     }
3912     return true;
3915 /**
3916  * Returns attachments as formated text/html optionally with separate images
3917  *
3918  * @global object
3919  * @global object
3920  * @global object
3921  * @param object $post
3922  * @param object $cm
3923  * @param string $type html/text/separateimages
3924  * @return mixed string or array of (html text withouth images and image HTML)
3925  */
3926 function forum_print_attachments($post, $cm, $type) {
3927     global $CFG, $DB, $USER, $OUTPUT;
3929     if (empty($post->attachment)) {
3930         return $type !== 'separateimages' ? '' : array('', '');
3931     }
3933     if (!in_array($type, array('separateimages', 'html', 'text'))) {
3934         return $type !== 'separateimages' ? '' : array('', '');
3935     }
3937     if (!$context = get_context_instance(CONTEXT_MODULE, $cm->id)) {
3938         return $type !== 'separateimages' ? '' : array('', '');
3939     }
3940     $strattachment = get_string('attachment', 'forum');
3942     $fs = get_file_storage();
3944     $imagereturn = '';
3945     $output = '';
3947     $canexport = !empty($CFG->enableportfolios) && (has_capability('mod/forum:exportpost', $context) || ($post->userid == $USER->id && has_capability('mod/forum:exportownpost', $context)));
3949     if ($canexport) {
3950         require_once($CFG->libdir.'/portfoliolib.php');
3951     }
3953     $files = $fs->get_area_files($context->id, 'mod_forum', 'attachment', $post->id, "timemodified", false);
3954     if ($files) {
3955         if ($canexport) {
3956             $button = new portfolio_add_button();
3957         }
3958         foreach ($files as $file) {
3959             $filename = $file->get_filename();
3960             $mimetype = $file->get_mimetype();
3961             $iconimage = '<img src="'.$OUTPUT->pix_url(file_mimetype_icon($mimetype)).'" class="icon" alt="'.$mimetype.'" />';
3962             $path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$context->id.'/mod_forum/attachment/'.$post->id.'/'.$filename);
3964             if ($type == 'html') {
3965                 $output .= "<a href=\"$path\">$iconimage</a> ";
3966                 $output .= "<a href=\"$path\">".s($filename)."</a>";
3967                 if ($canexport) {
3968                     $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php');
3969                     $button->set_format_by_file($file);
3970                     $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
3971                 }
3972                 $output .= "<br />";
3974             } else if ($type == 'text') {
3975                 $output .= "$strattachment ".s($filename).":\n$path\n";
3977             } else { //'returnimages'
3978                 if (in_array($mimetype, array('image/gif', 'image/jpeg', 'image/png'))) {
3979                     // Image attachments don't get printed as links
3980                     $imagereturn .= "<br /><img src=\"$path\" alt=\"\" />";
3981                     if ($canexport) {
3982                         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php');
3983                         $button->set_format_by_file($file);
3984                         $imagereturn .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
3985                     }
3986                 } else {
3987                     $output .= "<a href=\"$path\">$iconimage</a> ";
3988                     $output .= format_text("<a href=\"$path\">".s($filename)."</a>", FORMAT_HTML, array('context'=>$context));
3989                     if ($canexport) {
3990                         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php');
3991                         $button->set_format_by_file($file);
3992                         $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
3993                     }
3994                     $output .= '<br />';
3995                 }
3996             }
3997         }
3998     }
4000     if ($type !== 'separateimages') {
4001         return $output;
4003     } else {
4004         return array($output, $imagereturn);
4005     }
4008 /**
4009  * Lists all browsable file areas
4010  *
4011  * @param object $course
4012  * @param object $cm
4013  * @param object $context
4014  * @return array
4015  */
4016 function forum_get_file_areas($course, $cm, $context) {
4017     $areas = array();
4018     return $areas;
4021 /**
4022  * File browsing support for forum module.
4023  *
4024  * @param object $browser
4025  * @param object $areas
4026  * @param object $course
4027  * @param object $cm
4028  * @param object $context
4029  * @param string $filearea
4030  * @param int $itemid
4031  * @param string $filepath
4032  * @param string $filename
4033  * @return object file_info instance or null if not found
4034  */
4035 function forum_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
4036     global $CFG, $DB;
4038     if ($context->contextlevel != CONTEXT_MODULE) {
4039         return null;
4040     }
4042     $fileareas = array('attachment', 'post');
4043     if (!in_array($filearea, $fileareas)) {
4044         return null;
4045     }
4047     if (!$post = $DB->get_record('forum_posts', array('id' => $itemid))) {
4048         return null;
4049     }
4051     if (!$discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion))) {
4052         return null;
4053     }
4055     if (!$forum = $DB->get_record('forum', array('id' => $cm->instance))) {
4056         return null;
4057     }
4059     $fs = get_file_storage();
4060     $filepath = is_null($filepath) ? '/' : $filepath;
4061     $filename = is_null($filename) ? '.' : $filename;
4062     if (!($storedfile = $fs->get_file($context->id, 'mod_forum', $filearea, $itemid, $filepath, $filename))) {
4063         return null;
4064     }
4066     // Make sure groups allow this user to see this file
4067     if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
4068         if (!groups_group_exists($discussion->groupid)) { // Can't find group
4069             return null;                           // Be safe and don't send it to anyone
4070         }
4072         if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
4073             // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
4074             return null;
4075         }
4076     }
4078     // Make sure we're allowed to see it...
4079     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
4080         return null;
4081     }
4083     $urlbase = $CFG->wwwroot.'/pluginfile.php';
4084     return new file_info_stored($browser, $context, $storedfile, $urlbase, $filearea, $itemid, true, true, false);
4087 /**
4088  * Serves the forum attachments. Implements needed access control ;-)
4089  *
4090  * @param object $course
4091  * @param object $cm
4092  * @param object $context
4093  * @param string $filearea
4094  * @param array $args
4095  * @param bool $forcedownload
4096  * @return bool false if file not found, does not return if found - justsend the file
4097  */
4098 function forum_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) {
4099     global $CFG, $DB;
4101     if ($context->contextlevel != CONTEXT_MODULE) {
4102         return false;
4103     }
4105     require_course_login($course, true, $cm);
4107     $fileareas = array('attachment', 'post');
4108     if (!in_array($filearea, $fileareas)) {
4109         return false;
4110     }
4112     $postid = (int)array_shift($args);
4114     if (!$post = $DB->get_record('forum_posts', array('id'=>$postid))) {
4115         return false;
4116     }
4118     if (!$discussion = $DB->get_record('forum_discussions', array('id'=>$post->discussion))) {
4119         return false;
4120     }
4122     if (!$forum = $DB->get_record('forum', array('id'=>$cm->instance))) {
4123         return false;
4124     }
4126     $fs = get_file_storage();
4127     $relativepath = implode('/', $args);
4128     $fullpath = "/$context->id/mod_forum/$filearea/$postid/$relativepath";
4129     if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
4130         return false;
4131     }
4133     // Make sure groups allow this user to see this file
4134     if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
4135         if (!groups_group_exists($discussion->groupid)) { // Can't find group
4136             return false;                           // Be safe and don't send it to anyone
4137         }
4139         if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
4140             // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS