c778e0a528d64119774796999e22a1423b70969c
[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  * @param stdClass $forum add forum instance
56  * @param mod_forum_mod_form $mform
57  * @return int intance id
58  */
59 function forum_add_instance($forum, $mform = null) {
60     global $CFG, $DB;
62     $forum->timemodified = time();
64     if (empty($forum->assessed)) {
65         $forum->assessed = 0;
66     }
68     if (empty($forum->ratingtime) or empty($forum->assessed)) {
69         $forum->assesstimestart  = 0;
70         $forum->assesstimefinish = 0;
71     }
73     $forum->id = $DB->insert_record('forum', $forum);
74     $modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule);
76     if ($forum->type == 'single') {  // Create related discussion.
77         $discussion = new stdClass();
78         $discussion->course        = $forum->course;
79         $discussion->forum         = $forum->id;
80         $discussion->name          = $forum->name;
81         $discussion->assessed      = $forum->assessed;
82         $discussion->message       = $forum->intro;
83         $discussion->messageformat = $forum->introformat;
84         $discussion->messagetrust  = trusttext_trusted(get_context_instance(CONTEXT_COURSE, $forum->course));
85         $discussion->mailnow       = false;
86         $discussion->groupid       = -1;
88         $message = '';
90         $discussion->id = forum_add_discussion($discussion, null, $message);
92         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
93             // ugly hack - we need to copy the files somehow
94             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
95             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
97             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
98             $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
99         }
100     }
102     if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
103     /// all users should be subscribed initially
104     /// Note: forum_get_potential_subscribers should take the forum context,
105     /// but that does not exist yet, becuase the forum is only half build at this
106     /// stage. However, because the forum is brand new, we know that there are
107     /// no role assignments or overrides in the forum context, so using the
108     /// course context gives the same list of users.
109         $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
110         foreach ($users as $user) {
111             forum_subscribe($user->id, $forum->id);
112         }
113     }
115     forum_grade_item_update($forum);
117     return $forum->id;
121 /**
122  * Given an object containing all the necessary data,
123  * (defined by the form in mod_form.php) this function
124  * will update an existing instance with new data.
125  *
126  * @global object
127  * @param object $forum forum instance (with magic quotes)
128  * @return bool success
129  */
130 function forum_update_instance($forum, $mform) {
131     global $DB, $OUTPUT, $USER;
133     $forum->timemodified = time();
134     $forum->id           = $forum->instance;
136     if (empty($forum->assessed)) {
137         $forum->assessed = 0;
138     }
140     if (empty($forum->ratingtime) or empty($forum->assessed)) {
141         $forum->assesstimestart  = 0;
142         $forum->assesstimefinish = 0;
143     }
145     $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
147     // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
148     // if  scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
149     // for count and sum aggregation types the grade we check to make sure they do not exceed the scale (i.e. max score) when calculating the grade
150     if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
151         forum_update_grades($forum); // recalculate grades for the forum
152     }
154     if ($forum->type == 'single') {  // Update related discussion and post.
155         $discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC');
156         if (!empty($discussions)) {
157             if (count($discussions) > 1) {
158                 echo $OUTPUT->notification(get_string('warnformorepost', 'forum'));
159             }
160             $discussion = array_pop($discussions);
161         } else {
162             // try to recover by creating initial discussion - MDL-16262
163             $discussion = new stdClass();
164             $discussion->course          = $forum->course;
165             $discussion->forum           = $forum->id;
166             $discussion->name            = $forum->name;
167             $discussion->assessed        = $forum->assessed;
168             $discussion->message         = $forum->intro;
169             $discussion->messageformat   = $forum->introformat;
170             $discussion->messagetrust    = true;
171             $discussion->mailnow         = false;
172             $discussion->groupid         = -1;
174             $message = '';
176             forum_add_discussion($discussion, null, $message);
178             if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
179                 print_error('cannotadd', 'forum');
180             }
181         }
182         if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
183             print_error('cannotfindfirstpost', 'forum');
184         }
186         $cm         = get_coursemodule_from_instance('forum', $forum->id);
187         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
189         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
190             // ugly hack - we need to copy the files somehow
191             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
192             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
194             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
195         }
197         $post->subject       = $forum->name;
198         $post->message       = $forum->intro;
199         $post->messageformat = $forum->introformat;
200         $post->messagetrust  = trusttext_trusted($modcontext);
201         $post->modified      = $forum->timemodified;
202         $post->userid        = $USER->id;    // MDL-18599, so that current teacher can take ownership of activities
204         $DB->update_record('forum_posts', $post);
205         $discussion->name = $forum->name;
206         $DB->update_record('forum_discussions', $discussion);
207     }
209     $DB->update_record('forum', $forum);
211     forum_grade_item_update($forum);
213     return true;
217 /**
218  * Given an ID of an instance of this module,
219  * this function will permanently delete the instance
220  * and any data that depends on it.
221  *
222  * @global object
223  * @param int $id forum instance id
224  * @return bool success
225  */
226 function forum_delete_instance($id) {
227     global $DB;
229     if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
230         return false;
231     }
232     if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
233         return false;
234     }
235     if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
236         return false;
237     }
239     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
241     // now get rid of all files
242     $fs = get_file_storage();
243     $fs->delete_area_files($context->id);
245     $result = true;
247     if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
248         foreach ($discussions as $discussion) {
249             if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
250                 $result = false;
251             }
252         }
253     }
255     if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
256         $result = false;
257     }
259     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
261     if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
262         $result = false;
263     }
265     forum_grade_item_delete($forum);
267     return $result;
271 /**
272  * Indicates API features that the forum supports.
273  *
274  * @uses FEATURE_GROUPS
275  * @uses FEATURE_GROUPINGS
276  * @uses FEATURE_GROUPMEMBERSONLY
277  * @uses FEATURE_MOD_INTRO
278  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
279  * @uses FEATURE_COMPLETION_HAS_RULES
280  * @uses FEATURE_GRADE_HAS_GRADE
281  * @uses FEATURE_GRADE_OUTCOMES
282  * @param string $feature
283  * @return mixed True if yes (some features may use other values)
284  */
285 function forum_supports($feature) {
286     switch($feature) {
287         case FEATURE_GROUPS:                  return true;
288         case FEATURE_GROUPINGS:               return true;
289         case FEATURE_GROUPMEMBERSONLY:        return true;
290         case FEATURE_MOD_INTRO:               return true;
291         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
292         case FEATURE_COMPLETION_HAS_RULES:    return true;
293         case FEATURE_GRADE_HAS_GRADE:         return true;
294         case FEATURE_GRADE_OUTCOMES:          return true;
295         case FEATURE_RATE:                    return true;
296         case FEATURE_BACKUP_MOODLE2:          return true;
297         case FEATURE_SHOW_DESCRIPTION:        return true;
299         default: return null;
300     }
304 /**
305  * Obtains the automatic completion state for this forum based on any conditions
306  * in forum settings.
307  *
308  * @global object
309  * @global object
310  * @param object $course Course
311  * @param object $cm Course-module
312  * @param int $userid User ID
313  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
314  * @return bool True if completed, false if not. (If no conditions, then return
315  *   value depends on comparison type)
316  */
317 function forum_get_completion_state($course,$cm,$userid,$type) {
318     global $CFG,$DB;
320     // Get forum details
321     if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
322         throw new Exception("Can't find forum {$cm->instance}");
323     }
325     $result=$type; // Default return value
327     $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
328     $postcountsql="
329 SELECT
330     COUNT(1)
331 FROM
332     {forum_posts} fp
333     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
334 WHERE
335     fp.userid=:userid AND fd.forum=:forumid";
337     if ($forum->completiondiscussions) {
338         $value = $forum->completiondiscussions <=
339                  $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
340         if ($type == COMPLETION_AND) {
341             $result = $result && $value;
342         } else {
343             $result = $result || $value;
344         }
345     }
346     if ($forum->completionreplies) {
347         $value = $forum->completionreplies <=
348                  $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
349         if ($type==COMPLETION_AND) {
350             $result = $result && $value;
351         } else {
352             $result = $result || $value;
353         }
354     }
355     if ($forum->completionposts) {
356         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
357         if ($type == COMPLETION_AND) {
358             $result = $result && $value;
359         } else {
360             $result = $result || $value;
361         }
362     }
364     return $result;
367 /**
368  * Create a message-id string to use in the custom headers of forum notification emails
369  *
370  * message-id is used by email clients to identify emails and to nest conversations
371  *
372  * @param int $postid The ID of the forum post we are notifying the user about
373  * @param int $usertoid The ID of the user being notified
374  * @param string $hostname The server's hostname
375  * @return string A unique message-id
376  */
377 function forum_get_email_message_id($postid, $usertoid, $hostname) {
378     return '<'.hash('sha256',$postid.'to'.$usertoid.'@'.$hostname).'>';
381 /**
382  * Function to be run periodically according to the moodle cron
383  * Finds all posts that have yet to be mailed out, and mails them
384  * out to all subscribers
385  *
386  * @global object
387  * @global object
388  * @global object
389  * @uses CONTEXT_MODULE
390  * @uses CONTEXT_COURSE
391  * @uses SITEID
392  * @uses FORMAT_PLAIN
393  * @return void
394  */
395 function forum_cron() {
396     global $CFG, $USER, $DB;
398     $site = get_site();
400     // all users that are subscribed to any post that needs sending
401     $users = array();
403     // status arrays
404     $mailcount  = array();
405     $errorcount = array();
407     // caches
408     $discussions     = array();
409     $forums          = array();
410     $courses         = array();
411     $coursemodules   = array();
412     $subscribedusers = array();
415     // Posts older than 2 days will not be mailed.  This is to avoid the problem where
416     // cron has not been running for a long time, and then suddenly people are flooded
417     // with mail from the past few weeks or months
418     $timenow   = time();
419     $endtime   = $timenow - $CFG->maxeditingtime;
420     $starttime = $endtime - 48 * 3600;   // Two days earlier
422     if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
423         // Mark them all now as being mailed.  It's unlikely but possible there
424         // might be an error later so that a post is NOT actually mailed out,
425         // but since mail isn't crucial, we can accept this risk.  Doing it now
426         // prevents the risk of duplicated mails, which is a worse problem.
428         if (!forum_mark_old_posts_as_mailed($endtime)) {
429             mtrace('Errors occurred while trying to mark some posts as being mailed.');
430             return false;  // Don't continue trying to mail them, in case we are in a cron loop
431         }
433         // checking post validity, and adding users to loop through later
434         foreach ($posts as $pid => $post) {
436             $discussionid = $post->discussion;
437             if (!isset($discussions[$discussionid])) {
438                 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
439                     $discussions[$discussionid] = $discussion;
440                 } else {
441                     mtrace('Could not find discussion '.$discussionid);
442                     unset($posts[$pid]);
443                     continue;
444                 }
445             }
446             $forumid = $discussions[$discussionid]->forum;
447             if (!isset($forums[$forumid])) {
448                 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
449                     $forums[$forumid] = $forum;
450                 } else {
451                     mtrace('Could not find forum '.$forumid);
452                     unset($posts[$pid]);
453                     continue;
454                 }
455             }
456             $courseid = $forums[$forumid]->course;
457             if (!isset($courses[$courseid])) {
458                 if ($course = $DB->get_record('course', array('id' => $courseid))) {
459                     $courses[$courseid] = $course;
460                 } else {
461                     mtrace('Could not find course '.$courseid);
462                     unset($posts[$pid]);
463                     continue;
464                 }
465             }
466             if (!isset($coursemodules[$forumid])) {
467                 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
468                     $coursemodules[$forumid] = $cm;
469                 } else {
470                     mtrace('Could not find course module for forum '.$forumid);
471                     unset($posts[$pid]);
472                     continue;
473                 }
474             }
477             // caching subscribed users of each forum
478             if (!isset($subscribedusers[$forumid])) {
479                 $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
480                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext, "u.*")) {
481                     foreach ($subusers as $postuser) {
482                         unset($postuser->description); // not necessary
483                         // this user is subscribed to this forum
484                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
485                         // this user is a user we have to process later
486                         $users[$postuser->id] = $postuser;
487                     }
488                     unset($subusers); // release memory
489                 }
490             }
492             $mailcount[$pid] = 0;
493             $errorcount[$pid] = 0;
494         }
495     }
497     if ($users && $posts) {
499         $urlinfo = parse_url($CFG->wwwroot);
500         $hostname = $urlinfo['host'];
502         foreach ($users as $userto) {
504             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
506             // set this so that the capabilities are cached, and environment matches receiving user
507             cron_setup_user($userto);
509             mtrace('Processing user '.$userto->id);
511             // init caches
512             $userto->viewfullnames = array();
513             $userto->canpost       = array();
514             $userto->markposts     = array();
516             // reset the caches
517             foreach ($coursemodules as $forumid=>$unused) {
518                 $coursemodules[$forumid]->cache       = new stdClass();
519                 $coursemodules[$forumid]->cache->caps = array();
520                 unset($coursemodules[$forumid]->uservisible);
521             }
523             foreach ($posts as $pid => $post) {
525                 // Set up the environment for the post, discussion, forum, course
526                 $discussion = $discussions[$post->discussion];
527                 $forum      = $forums[$discussion->forum];
528                 $course     = $courses[$forum->course];
529                 $cm         =& $coursemodules[$forum->id];
531                 // Do some checks  to see if we can bail out now
532                 // Only active enrolled users are in the list of subscribers
533                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
534                     continue; // user does not subscribe to this forum
535                 }
537                 // Don't send email if the forum is Q&A and the user has not posted
538                 // Initial topics are still mailed
539                 if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id) && $pid != $discussion->firstpost) {
540                     mtrace('Did not email '.$userto->id.' because user has not posted in discussion');
541                     continue;
542                 }
544                 // Get info about the sending user
545                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
546                     $userfrom = $users[$post->userid];
547                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
548                     unset($userfrom->description); // not necessary
549                     $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
550                 } else {
551                     mtrace('Could not find user '.$post->userid);
552                     continue;
553                 }
555                 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
557                 // setup global $COURSE properly - needed for roles and languages
558                 cron_setup_user($userto, $course);
560                 // Fill caches
561                 if (!isset($userto->viewfullnames[$forum->id])) {
562                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
563                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
564                 }
565                 if (!isset($userto->canpost[$discussion->id])) {
566                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
567                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
568                 }
569                 if (!isset($userfrom->groups[$forum->id])) {
570                     if (!isset($userfrom->groups)) {
571                         $userfrom->groups = array();
572                         $users[$userfrom->id]->groups = array();
573                     }
574                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
575                     $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
576                 }
578                 // Make sure groups allow this user to see this email
579                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
580                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
581                         continue;                           // Be safe and don't send it to anyone
582                     }
584                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
585                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
586                         continue;
587                     }
588                 }
590                 // Make sure we're allowed to see it...
591                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
592                     mtrace('user '.$userto->id. ' can not see '.$post->id);
593                     continue;
594                 }
596                 // OK so we need to send the email.
598                 // Does the user want this post in a digest?  If so postpone it for now.
599                 if ($userto->maildigest > 0) {
600                     // This user wants the mails to be in digest form
601                     $queue = new stdClass();
602                     $queue->userid       = $userto->id;
603                     $queue->discussionid = $discussion->id;
604                     $queue->postid       = $post->id;
605                     $queue->timemodified = $post->created;
606                     $DB->insert_record('forum_queue', $queue);
607                     continue;
608                 }
611                 // Prepare to actually send the post now, and build up the content
613                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
615                 $userfrom->customheaders = array (  // Headers to make emails easier to track
616                            'Precedence: Bulk',
617                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
618                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
619                            'Message-ID: '.forum_get_email_message_id($post->id, $userto->id, $hostname),
620                            'X-Course-Id: '.$course->id,
621                            'X-Course-Name: '.format_string($course->fullname, true)
622                 );
624                 if ($post->parent) {  // This post is a reply, so add headers for threading (see MDL-22551)
625                     $userfrom->customheaders[] = 'In-Reply-To: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
626                     $userfrom->customheaders[] = 'References: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
627                 }
629                 $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
631                 $postsubject = html_to_text("$shortname: ".format_string($post->subject, true));
632                 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
633                 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
635                 // Send the post now!
637                 mtrace('Sending ', '');
639                 $eventdata = new stdClass();
640                 $eventdata->component        = 'mod_forum';
641                 $eventdata->name             = 'posts';
642                 $eventdata->userfrom         = $userfrom;
643                 $eventdata->userto           = $userto;
644                 $eventdata->subject          = $postsubject;
645                 $eventdata->fullmessage      = $posttext;
646                 $eventdata->fullmessageformat = FORMAT_PLAIN;
647                 $eventdata->fullmessagehtml  = $posthtml;
648                 $eventdata->notification = 1;
650                 $smallmessagestrings = new stdClass();
651                 $smallmessagestrings->user = fullname($userfrom);
652                 $smallmessagestrings->forumname = "$shortname: ".format_string($forum->name,true).": ".$discussion->name;
653                 $smallmessagestrings->message = $post->message;
654                 //make sure strings are in message recipients language
655                 $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
657                 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
658                 $eventdata->contexturlname = $discussion->name;
660                 $mailresult = message_send($eventdata);
661                 if (!$mailresult){
662                     mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
663                          " ($userto->email) .. not trying again.");
664                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
665                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
666                     $errorcount[$post->id]++;
667                 } else {
668                     $mailcount[$post->id]++;
670                 // Mark post as read if forum_usermarksread is set off
671                     if (!$CFG->forum_usermarksread) {
672                         $userto->markposts[$post->id] = $post->id;
673                     }
674                 }
676                 mtrace('post '.$post->id. ': '.$post->subject);
677             }
679             // mark processed posts as read
680             forum_tp_mark_posts_read($userto, $userto->markposts);
681         }
682     }
684     if ($posts) {
685         foreach ($posts as $post) {
686             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
687             if ($errorcount[$post->id]) {
688                 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
689             }
690         }
691     }
693     // release some memory
694     unset($subscribedusers);
695     unset($mailcount);
696     unset($errorcount);
698     cron_setup_user();
700     $sitetimezone = $CFG->timezone;
702     // Now see if there are any digest mails waiting to be sent, and if we should send them
704     mtrace('Starting digest processing...');
706     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
708     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
709         set_config('digestmailtimelast', 0);
710     }
712     $timenow = time();
713     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
715     // Delete any really old ones (normally there shouldn't be any)
716     $weekago = $timenow - (7 * 24 * 3600);
717     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
718     mtrace ('Cleaned old digest records');
720     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
722         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
724         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
726         if ($digestposts_rs->valid()) {
728             // We have work to do
729             $usermailcount = 0;
731             //caches - reuse the those filled before too
732             $discussionposts = array();
733             $userdiscussions = array();
735             foreach ($digestposts_rs as $digestpost) {
736                 if (!isset($users[$digestpost->userid])) {
737                     if ($user = $DB->get_record('user', array('id' => $digestpost->userid))) {
738                         $users[$digestpost->userid] = $user;
739                     } else {
740                         continue;
741                     }
742                 }
743                 $postuser = $users[$digestpost->userid];
745                 if (!isset($posts[$digestpost->postid])) {
746                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
747                         $posts[$digestpost->postid] = $post;
748                     } else {
749                         continue;
750                     }
751                 }
752                 $discussionid = $digestpost->discussionid;
753                 if (!isset($discussions[$discussionid])) {
754                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
755                         $discussions[$discussionid] = $discussion;
756                     } else {
757                         continue;
758                     }
759                 }
760                 $forumid = $discussions[$discussionid]->forum;
761                 if (!isset($forums[$forumid])) {
762                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
763                         $forums[$forumid] = $forum;
764                     } else {
765                         continue;
766                     }
767                 }
769                 $courseid = $forums[$forumid]->course;
770                 if (!isset($courses[$courseid])) {
771                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
772                         $courses[$courseid] = $course;
773                     } else {
774                         continue;
775                     }
776                 }
778                 if (!isset($coursemodules[$forumid])) {
779                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
780                         $coursemodules[$forumid] = $cm;
781                     } else {
782                         continue;
783                     }
784                 }
785                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
786                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
787             }
788             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
790             // Data collected, start sending out emails to each user
791             foreach ($userdiscussions as $userid => $thesediscussions) {
793                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
795                 cron_setup_user();
797                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
799                 // First of all delete all the queue entries for this user
800                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
801                 $userto = $users[$userid];
803                 // Override the language and timezone of the "current" user, so that
804                 // mail is customised for the receiver.
805                 cron_setup_user($userto);
807                 // init caches
808                 $userto->viewfullnames = array();
809                 $userto->canpost       = array();
810                 $userto->markposts     = array();
812                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
814                 $headerdata = new stdClass();
815                 $headerdata->sitename = format_string($site->fullname, true);
816                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
818                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
819                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
821                 $posthtml = "<head>";
822 /*                foreach ($CFG->stylesheets as $stylesheet) {
823                     //TODO: MDL-21120
824                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
825                 }*/
826                 $posthtml .= "</head>\n<body id=\"email\">\n";
827                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
829                 foreach ($thesediscussions as $discussionid) {
831                     @set_time_limit(120);   // to be reset for each post
833                     $discussion = $discussions[$discussionid];
834                     $forum      = $forums[$discussion->forum];
835                     $course     = $courses[$forum->course];
836                     $cm         = $coursemodules[$forum->id];
838                     //override language
839                     cron_setup_user($userto, $course);
841                     // Fill caches
842                     if (!isset($userto->viewfullnames[$forum->id])) {
843                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
844                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
845                     }
846                     if (!isset($userto->canpost[$discussion->id])) {
847                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
848                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
849                     }
851                     $strforums      = get_string('forums', 'forum');
852                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
853                     $canreply       = $userto->canpost[$discussion->id];
854                     $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
856                     $posttext .= "\n \n";
857                     $posttext .= '=====================================================================';
858                     $posttext .= "\n \n";
859                     $posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
860                     if ($discussion->name != $forum->name) {
861                         $posttext  .= " -> ".format_string($discussion->name,true);
862                     }
863                     $posttext .= "\n";
865                     $posthtml .= "<p><font face=\"sans-serif\">".
866                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
867                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
868                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
869                     if ($discussion->name == $forum->name) {
870                         $posthtml .= "</font></p>";
871                     } else {
872                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
873                     }
874                     $posthtml .= '<p>';
876                     $postsarray = $discussionposts[$discussionid];
877                     sort($postsarray);
879                     foreach ($postsarray as $postid) {
880                         $post = $posts[$postid];
882                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
883                             $userfrom = $users[$post->userid];
884                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
885                             $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
886                         } else {
887                             mtrace('Could not find user '.$post->userid);
888                             continue;
889                         }
891                         if (!isset($userfrom->groups[$forum->id])) {
892                             if (!isset($userfrom->groups)) {
893                                 $userfrom->groups = array();
894                                 $users[$userfrom->id]->groups = array();
895                             }
896                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
897                             $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
898                         }
900                         $userfrom->customheaders = array ("Precedence: Bulk");
902                         if ($userto->maildigest == 2) {
903                             // Subjects only
904                             $by = new stdClass();
905                             $by->name = fullname($userfrom);
906                             $by->date = userdate($post->modified);
907                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
908                             $posttext .= "\n---------------------------------------------------------------------";
910                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
911                             $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>';
913                         } else {
914                             // The full treatment
915                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
916                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
918                         // Create an array of postid's for this user to mark as read.
919                             if (!$CFG->forum_usermarksread) {
920                                 $userto->markposts[$post->id] = $post->id;
921                             }
922                         }
923                     }
924                     if ($canunsubscribe) {
925                         $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>";
926                     } else {
927                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
928                     }
929                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
930                 }
931                 $posthtml .= '</body>';
933                 if (empty($userto->mailformat) || $userto->mailformat != 1) {
934                     // This user DOESN'T want to receive HTML
935                     $posthtml = '';
936                 }
938                 $attachment = $attachname='';
939                 $usetrueaddress = true;
940                 //directly email forum digests rather than sending them via messaging
941                 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname, $usetrueaddress, $CFG->forum_replytouser);
943                 if (!$mailresult) {
944                     mtrace("ERROR!");
945                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
946                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
947                 } else {
948                     mtrace("success.");
949                     $usermailcount++;
951                     // Mark post as read if forum_usermarksread is set off
952                     forum_tp_mark_posts_read($userto, $userto->markposts);
953                 }
954             }
955         }
956     /// We have finishied all digest emails, update $CFG->digestmailtimelast
957         set_config('digestmailtimelast', $timenow);
958     }
960     cron_setup_user();
962     if (!empty($usermailcount)) {
963         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
964     }
966     if (!empty($CFG->forum_lastreadclean)) {
967         $timenow = time();
968         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
969             set_config('forum_lastreadclean', $timenow);
970             mtrace('Removing old forum read tracking info...');
971             forum_tp_clean_read_records();
972         }
973     } else {
974         set_config('forum_lastreadclean', time());
975     }
978     return true;
981 /**
982  * Builds and returns the body of the email notification in plain text.
983  *
984  * @global object
985  * @global object
986  * @uses CONTEXT_MODULE
987  * @param object $course
988  * @param object $cm
989  * @param object $forum
990  * @param object $discussion
991  * @param object $post
992  * @param object $userfrom
993  * @param object $userto
994  * @param boolean $bare
995  * @return string The email body in plain text format.
996  */
997 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
998     global $CFG, $USER;
1000     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
1002     if (!isset($userto->viewfullnames[$forum->id])) {
1003         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
1004     } else {
1005         $viewfullnames = $userto->viewfullnames[$forum->id];
1006     }
1008     if (!isset($userto->canpost[$discussion->id])) {
1009         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
1010     } else {
1011         $canreply = $userto->canpost[$discussion->id];
1012     }
1014     $by = New stdClass;
1015     $by->name = fullname($userfrom, $viewfullnames);
1016     $by->date = userdate($post->modified, "", $userto->timezone);
1018     $strbynameondate = get_string('bynameondate', 'forum', $by);
1020     $strforums = get_string('forums', 'forum');
1022     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1024     $posttext = '';
1026     if (!$bare) {
1027         $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
1028         $posttext  = "$shortname -> $strforums -> ".format_string($forum->name,true);
1030         if ($discussion->name != $forum->name) {
1031             $posttext  .= " -> ".format_string($discussion->name,true);
1032         }
1033     }
1035     // add absolute file links
1036     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
1038     $posttext .= "\n---------------------------------------------------------------------\n";
1039     $posttext .= format_string($post->subject,true);
1040     if ($bare) {
1041         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1042     }
1043     $posttext .= "\n".$strbynameondate."\n";
1044     $posttext .= "---------------------------------------------------------------------\n";
1045     $posttext .= format_text_email($post->message, $post->messageformat);
1046     $posttext .= "\n\n";
1047     $posttext .= forum_print_attachments($post, $cm, "text");
1049     if (!$bare && $canreply) {
1050         $posttext .= "---------------------------------------------------------------------\n";
1051         $posttext .= get_string("postmailinfo", "forum", $shortname)."\n";
1052         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1053     }
1054     if (!$bare && $canunsubscribe) {
1055         $posttext .= "\n---------------------------------------------------------------------\n";
1056         $posttext .= get_string("unsubscribe", "forum");
1057         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1058     }
1060     return $posttext;
1063 /**
1064  * Builds and returns the body of the email notification in html format.
1065  *
1066  * @global object
1067  * @param object $course
1068  * @param object $cm
1069  * @param object $forum
1070  * @param object $discussion
1071  * @param object $post
1072  * @param object $userfrom
1073  * @param object $userto
1074  * @return string The email text in HTML format
1075  */
1076 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1077     global $CFG;
1079     if ($userto->mailformat != 1) {  // Needs to be HTML
1080         return '';
1081     }
1083     if (!isset($userto->canpost[$discussion->id])) {
1084         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course);
1085     } else {
1086         $canreply = $userto->canpost[$discussion->id];
1087     }
1089     $strforums = get_string('forums', 'forum');
1090     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1091     $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
1093     $posthtml = '<head>';
1094 /*    foreach ($CFG->stylesheets as $stylesheet) {
1095         //TODO: MDL-21120
1096         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1097     }*/
1098     $posthtml .= '</head>';
1099     $posthtml .= "\n<body id=\"email\">\n\n";
1101     $posthtml .= '<div class="navbar">'.
1102     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$shortname.'</a> &raquo; '.
1103     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1104     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1105     if ($discussion->name == $forum->name) {
1106         $posthtml .= '</div>';
1107     } else {
1108         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1109                      format_string($discussion->name,true).'</a></div>';
1110     }
1111     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1113     if ($canunsubscribe) {
1114         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1115                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1116                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1117     }
1119     $posthtml .= '</body>';
1121     return $posthtml;
1125 /**
1126  *
1127  * @param object $course
1128  * @param object $user
1129  * @param object $mod TODO this is not used in this function, refactor
1130  * @param object $forum
1131  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1132  */
1133 function forum_user_outline($course, $user, $mod, $forum) {
1134     global $CFG;
1135     require_once("$CFG->libdir/gradelib.php");
1136     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1137     if (empty($grades->items[0]->grades)) {
1138         $grade = false;
1139     } else {
1140         $grade = reset($grades->items[0]->grades);
1141     }
1143     $count = forum_count_user_posts($forum->id, $user->id);
1145     if ($count && $count->postcount > 0) {
1146         $result = new stdClass();
1147         $result->info = get_string("numposts", "forum", $count->postcount);
1148         $result->time = $count->lastpost;
1149         if ($grade) {
1150             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1151         }
1152         return $result;
1153     } else if ($grade) {
1154         $result = new stdClass();
1155         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1157         //datesubmitted == time created. dategraded == time modified or time overridden
1158         //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
1159         //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
1160         if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1161             $result->time = $grade->dategraded;
1162         } else {
1163             $result->time = $grade->datesubmitted;
1164         }
1166         return $result;
1167     }
1168     return NULL;
1172 /**
1173  * @global object
1174  * @global object
1175  * @param object $coure
1176  * @param object $user
1177  * @param object $mod
1178  * @param object $forum
1179  */
1180 function forum_user_complete($course, $user, $mod, $forum) {
1181     global $CFG,$USER, $OUTPUT;
1182     require_once("$CFG->libdir/gradelib.php");
1184     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1185     if (!empty($grades->items[0]->grades)) {
1186         $grade = reset($grades->items[0]->grades);
1187         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1188         if ($grade->str_feedback) {
1189             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1190         }
1191     }
1193     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1195         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1196             print_error('invalidcoursemodule');
1197         }
1198         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1200         foreach ($posts as $post) {
1201             if (!isset($discussions[$post->discussion])) {
1202                 continue;
1203             }
1204             $discussion = $discussions[$post->discussion];
1206             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1207         }
1208     } else {
1209         echo "<p>".get_string("noposts", "forum")."</p>";
1210     }
1218 /**
1219  * @global object
1220  * @global object
1221  * @global object
1222  * @param array $courses
1223  * @param array $htmlarray
1224  */
1225 function forum_print_overview($courses,&$htmlarray) {
1226     global $USER, $CFG, $DB, $SESSION;
1228     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1229         return array();
1230     }
1232     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1233         return;
1234     }
1237     // get all forum logs in ONE query (much better!)
1238     $params = array();
1239     $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {log} l "
1240         ." JOIN {course_modules} cm ON cm.id = cmid "
1241         ." WHERE (";
1242     foreach ($courses as $course) {
1243         $sql .= '(l.course = ? AND l.time > ?) OR ';
1244         $params[] = $course->id;
1245         $params[] = $course->lastaccess;
1246     }
1247     $sql = substr($sql,0,-3); // take off the last OR
1249     $sql .= ") AND l.module = 'forum' AND action = 'add post' "
1250         ." AND userid != ? GROUP BY cmid,l.course,instance";
1252     $params[] = $USER->id;
1254     if (!$new = $DB->get_records_sql($sql, $params)) {
1255         $new = array(); // avoid warnings
1256     }
1258     // also get all forum tracking stuff ONCE.
1259     $trackingforums = array();
1260     foreach ($forums as $forum) {
1261         if (forum_tp_can_track_forums($forum)) {
1262             $trackingforums[$forum->id] = $forum;
1263         }
1264     }
1266     if (count($trackingforums) > 0) {
1267         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1268         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1269             ' FROM {forum_posts} p '.
1270             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1271             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1272         $params = array($USER->id);
1274         foreach ($trackingforums as $track) {
1275             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1276             $params[] = $track->id;
1277             if (isset($SESSION->currentgroup[$track->course])) {
1278                 $groupid =  $SESSION->currentgroup[$track->course];
1279             } else {
1280                 // get first groupid
1281                 $groupids = groups_get_all_groups($track->course, $USER->id);
1282                 if ($groupids) {
1283                     reset($groupids);
1284                     $groupid = key($groupids);
1285                     $SESSION->currentgroup[$track->course] = $groupid;
1286                 } else {
1287                     $groupid = 0;
1288                 }
1289                 unset($groupids);
1290             }
1291             $params[] = $groupid;
1292         }
1293         $sql = substr($sql,0,-3); // take off the last OR
1294         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1295         $params[] = $cutoffdate;
1297         if (!$unread = $DB->get_records_sql($sql, $params)) {
1298             $unread = array();
1299         }
1300     } else {
1301         $unread = array();
1302     }
1304     if (empty($unread) and empty($new)) {
1305         return;
1306     }
1308     $strforum = get_string('modulename','forum');
1310     foreach ($forums as $forum) {
1311         $str = '';
1312         $count = 0;
1313         $thisunread = 0;
1314         $showunread = false;
1315         // either we have something from logs, or trackposts, or nothing.
1316         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1317             $count = $new[$forum->id]->count;
1318         }
1319         if (array_key_exists($forum->id,$unread)) {
1320             $thisunread = $unread[$forum->id]->count;
1321             $showunread = true;
1322         }
1323         if ($count > 0 || $thisunread > 0) {
1324             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1325                 $forum->name.'</a></div>';
1326             $str .= '<div class="info"><span class="postsincelogin">';
1327             $str .= get_string('overviewnumpostssince', 'forum', $count)."</span>";
1328             if (!empty($showunread)) {
1329                 $str .= '<div class="unreadposts">'.get_string('overviewnumunread', 'forum', $thisunread).'</div>';
1330             }
1331             $str .= '</div></div>';
1332         }
1333         if (!empty($str)) {
1334             if (!array_key_exists($forum->course,$htmlarray)) {
1335                 $htmlarray[$forum->course] = array();
1336             }
1337             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1338                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1339             }
1340             $htmlarray[$forum->course]['forum'] .= $str;
1341         }
1342     }
1345 /**
1346  * Given a course and a date, prints a summary of all the new
1347  * messages posted in the course since that date
1348  *
1349  * @global object
1350  * @global object
1351  * @global object
1352  * @uses CONTEXT_MODULE
1353  * @uses VISIBLEGROUPS
1354  * @param object $course
1355  * @param bool $viewfullnames capability
1356  * @param int $timestart
1357  * @return bool success
1358  */
1359 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1360     global $CFG, $USER, $DB, $OUTPUT;
1362     // do not use log table if possible, it may be huge and is expensive to join with other tables
1364     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1365                                               d.timestart, d.timeend, d.userid AS duserid,
1366                                               u.firstname, u.lastname, u.email, u.picture
1367                                          FROM {forum_posts} p
1368                                               JOIN {forum_discussions} d ON d.id = p.discussion
1369                                               JOIN {forum} f             ON f.id = d.forum
1370                                               JOIN {user} u              ON u.id = p.userid
1371                                         WHERE p.created > ? AND f.course = ?
1372                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1373          return false;
1374     }
1376     $modinfo = get_fast_modinfo($course);
1378     $groupmodes = array();
1379     $cms    = array();
1381     $strftimerecent = get_string('strftimerecent');
1383     $printposts = array();
1384     foreach ($posts as $post) {
1385         if (!isset($modinfo->instances['forum'][$post->forum])) {
1386             // not visible
1387             continue;
1388         }
1389         $cm = $modinfo->instances['forum'][$post->forum];
1390         if (!$cm->uservisible) {
1391             continue;
1392         }
1393         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1395         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1396             continue;
1397         }
1399         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1400           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1401             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1402                 continue;
1403             }
1404         }
1406         $groupmode = groups_get_activity_groupmode($cm, $course);
1408         if ($groupmode) {
1409             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1410                 // oki (Open discussions have groupid -1)
1411             } else {
1412                 // separate mode
1413                 if (isguestuser()) {
1414                     // shortcut
1415                     continue;
1416                 }
1418                 if (is_null($modinfo->groups)) {
1419                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1420                 }
1422                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1423                     continue;
1424                 }
1425             }
1426         }
1428         $printposts[] = $post;
1429     }
1430     unset($posts);
1432     if (!$printposts) {
1433         return false;
1434     }
1436     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1437     echo "\n<ul class='unlist'>\n";
1439     foreach ($printposts as $post) {
1440         $subjectclass = empty($post->parent) ? ' bold' : '';
1442         echo '<li><div class="head">'.
1443                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1444                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1445              '</div>';
1446         echo '<div class="info'.$subjectclass.'">';
1447         if (empty($post->parent)) {
1448             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1449         } else {
1450             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1451         }
1452         $post->subject = break_up_long_words(format_string($post->subject, true));
1453         echo $post->subject;
1454         echo "</a>\"</div></li>\n";
1455     }
1457     echo "</ul>\n";
1459     return true;
1462 /**
1463  * Return grade for given user or all users.
1464  *
1465  * @global object
1466  * @global object
1467  * @param object $forum
1468  * @param int $userid optional user id, 0 means all users
1469  * @return array array of grades, false if none
1470  */
1471 function forum_get_user_grades($forum, $userid = 0) {
1472     global $CFG;
1474     require_once($CFG->dirroot.'/rating/lib.php');
1476     $ratingoptions = new stdClass;
1477     $ratingoptions->component = 'mod_forum';
1478     $ratingoptions->ratingarea = 'post';
1480     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1481     $ratingoptions->modulename = 'forum';
1482     $ratingoptions->moduleid   = $forum->id;
1483     $ratingoptions->userid = $userid;
1484     $ratingoptions->aggregationmethod = $forum->assessed;
1485     $ratingoptions->scaleid = $forum->scale;
1486     $ratingoptions->itemtable = 'forum_posts';
1487     $ratingoptions->itemtableusercolumn = 'userid';
1489     $rm = new rating_manager();
1490     return $rm->get_user_grades($ratingoptions);
1493 /**
1494  * Update activity grades
1495  *
1496  * @category grade
1497  * @param object $forum
1498  * @param int $userid specific user only, 0 means all
1499  * @param boolean $nullifnone return null if grade does not exist
1500  * @return void
1501  */
1502 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1503     global $CFG, $DB;
1504     require_once($CFG->libdir.'/gradelib.php');
1506     if (!$forum->assessed) {
1507         forum_grade_item_update($forum);
1509     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1510         forum_grade_item_update($forum, $grades);
1512     } else if ($userid and $nullifnone) {
1513         $grade = new stdClass();
1514         $grade->userid   = $userid;
1515         $grade->rawgrade = NULL;
1516         forum_grade_item_update($forum, $grade);
1518     } else {
1519         forum_grade_item_update($forum);
1520     }
1523 /**
1524  * Update all grades in gradebook.
1525  * @global object
1526  */
1527 function forum_upgrade_grades() {
1528     global $DB;
1530     $sql = "SELECT COUNT('x')
1531               FROM {forum} f, {course_modules} cm, {modules} m
1532              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1533     $count = $DB->count_records_sql($sql);
1535     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1536               FROM {forum} f, {course_modules} cm, {modules} m
1537              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1538     $rs = $DB->get_recordset_sql($sql);
1539     if ($rs->valid()) {
1540         $pbar = new progress_bar('forumupgradegrades', 500, true);
1541         $i=0;
1542         foreach ($rs as $forum) {
1543             $i++;
1544             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1545             forum_update_grades($forum, 0, false);
1546             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1547         }
1548     }
1549     $rs->close();
1552 /**
1553  * Create/update grade item for given forum
1554  *
1555  * @category grade
1556  * @uses GRADE_TYPE_NONE
1557  * @uses GRADE_TYPE_VALUE
1558  * @uses GRADE_TYPE_SCALE
1559  * @param stdClass $forum Forum object with extra cmidnumber
1560  * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
1561  * @return int 0 if ok
1562  */
1563 function forum_grade_item_update($forum, $grades=NULL) {
1564     global $CFG;
1565     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1566         require_once($CFG->libdir.'/gradelib.php');
1567     }
1569     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1571     if (!$forum->assessed or $forum->scale == 0) {
1572         $params['gradetype'] = GRADE_TYPE_NONE;
1574     } else if ($forum->scale > 0) {
1575         $params['gradetype'] = GRADE_TYPE_VALUE;
1576         $params['grademax']  = $forum->scale;
1577         $params['grademin']  = 0;
1579     } else if ($forum->scale < 0) {
1580         $params['gradetype'] = GRADE_TYPE_SCALE;
1581         $params['scaleid']   = -$forum->scale;
1582     }
1584     if ($grades  === 'reset') {
1585         $params['reset'] = true;
1586         $grades = NULL;
1587     }
1589     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1592 /**
1593  * Delete grade item for given forum
1594  *
1595  * @category grade
1596  * @param stdClass $forum Forum object
1597  * @return grade_item
1598  */
1599 function forum_grade_item_delete($forum) {
1600     global $CFG;
1601     require_once($CFG->libdir.'/gradelib.php');
1603     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1607 /**
1608  * This function returns if a scale is being used by one forum
1609  *
1610  * @global object
1611  * @param int $forumid
1612  * @param int $scaleid negative number
1613  * @return bool
1614  */
1615 function forum_scale_used ($forumid,$scaleid) {
1616     global $DB;
1617     $return = false;
1619     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1621     if (!empty($rec) && !empty($scaleid)) {
1622         $return = true;
1623     }
1625     return $return;
1628 /**
1629  * Checks if scale is being used by any instance of forum
1630  *
1631  * This is used to find out if scale used anywhere
1632  *
1633  * @global object
1634  * @param $scaleid int
1635  * @return boolean True if the scale is used by any forum
1636  */
1637 function forum_scale_used_anywhere($scaleid) {
1638     global $DB;
1639     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1640         return true;
1641     } else {
1642         return false;
1643     }
1646 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1648 /**
1649  * Gets a post with all info ready for forum_print_post
1650  * Most of these joins are just to get the forum id
1651  *
1652  * @global object
1653  * @global object
1654  * @param int $postid
1655  * @return mixed array of posts or false
1656  */
1657 function forum_get_post_full($postid) {
1658     global $CFG, $DB;
1660     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1661                              FROM {forum_posts} p
1662                                   JOIN {forum_discussions} d ON p.discussion = d.id
1663                                   LEFT JOIN {user} u ON p.userid = u.id
1664                             WHERE p.id = ?", array($postid));
1667 /**
1668  * Gets posts with all info ready for forum_print_post
1669  * We pass forumid in because we always know it so no need to make a
1670  * complicated join to find it out.
1671  *
1672  * @global object
1673  * @global object
1674  * @return mixed array of posts or false
1675  */
1676 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1677     global $CFG, $DB;
1679     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1680                               FROM {forum_posts} p
1681                          LEFT JOIN {user} u ON p.userid = u.id
1682                              WHERE p.discussion = ?
1683                                AND p.parent > 0 $sort", array($discussion));
1686 /**
1687  * Gets all posts in discussion including top parent.
1688  *
1689  * @global object
1690  * @global object
1691  * @global object
1692  * @param int $discussionid
1693  * @param string $sort
1694  * @param bool $tracking does user track the forum?
1695  * @return array of posts
1696  */
1697 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1698     global $CFG, $DB, $USER;
1700     $tr_sel  = "";
1701     $tr_join = "";
1702     $params = array();
1704     if ($tracking) {
1705         $now = time();
1706         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1707         $tr_sel  = ", fr.id AS postread";
1708         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1709         $params[] = $USER->id;
1710     }
1712     $params[] = $discussionid;
1713     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1714                                      FROM {forum_posts} p
1715                                           LEFT JOIN {user} u ON p.userid = u.id
1716                                           $tr_join
1717                                     WHERE p.discussion = ?
1718                                  ORDER BY $sort", $params)) {
1719         return array();
1720     }
1722     foreach ($posts as $pid=>$p) {
1723         if ($tracking) {
1724             if (forum_tp_is_post_old($p)) {
1725                  $posts[$pid]->postread = true;
1726             }
1727         }
1728         if (!$p->parent) {
1729             continue;
1730         }
1731         if (!isset($posts[$p->parent])) {
1732             continue; // parent does not exist??
1733         }
1734         if (!isset($posts[$p->parent]->children)) {
1735             $posts[$p->parent]->children = array();
1736         }
1737         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1738     }
1740     return $posts;
1743 /**
1744  * Gets posts with all info ready for forum_print_post
1745  * We pass forumid in because we always know it so no need to make a
1746  * complicated join to find it out.
1747  *
1748  * @global object
1749  * @global object
1750  * @param int $parent
1751  * @param int $forumid
1752  * @return array
1753  */
1754 function forum_get_child_posts($parent, $forumid) {
1755     global $CFG, $DB;
1757     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1758                               FROM {forum_posts} p
1759                          LEFT JOIN {user} u ON p.userid = u.id
1760                              WHERE p.parent = ?
1761                           ORDER BY p.created ASC", array($parent));
1764 /**
1765  * An array of forum objects that the user is allowed to read/search through.
1766  *
1767  * @global object
1768  * @global object
1769  * @global object
1770  * @param int $userid
1771  * @param int $courseid if 0, we look for forums throughout the whole site.
1772  * @return array of forum objects, or false if no matches
1773  *         Forum objects have the following attributes:
1774  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1775  *         viewhiddentimedposts
1776  */
1777 function forum_get_readable_forums($userid, $courseid=0) {
1779     global $CFG, $DB, $USER;
1780     require_once($CFG->dirroot.'/course/lib.php');
1782     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1783         print_error('notinstalled', 'forum');
1784     }
1786     if ($courseid) {
1787         $courses = $DB->get_records('course', array('id' => $courseid));
1788     } else {
1789         // If no course is specified, then the user can see SITE + his courses.
1790         $courses1 = $DB->get_records('course', array('id' => SITEID));
1791         $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
1792         $courses = array_merge($courses1, $courses2);
1793     }
1794     if (!$courses) {
1795         return array();
1796     }
1798     $readableforums = array();
1800     foreach ($courses as $course) {
1802         $modinfo = get_fast_modinfo($course);
1803         if (is_null($modinfo->groups)) {
1804             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1805         }
1807         if (empty($modinfo->instances['forum'])) {
1808             // hmm, no forums?
1809             continue;
1810         }
1812         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1814         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1815             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1816                 continue;
1817             }
1818             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1819             $forum = $courseforums[$forumid];
1820             $forum->context = $context;
1821             $forum->cm = $cm;
1823             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1824                 continue;
1825             }
1827          /// group access
1828             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1829                 if (is_null($modinfo->groups)) {
1830                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1831                 }
1832                 if (isset($modinfo->groups[$cm->groupingid])) {
1833                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1834                     $forum->onlygroups[] = -1;
1835                 } else {
1836                     $forum->onlygroups = array(-1);
1837                 }
1838             }
1840         /// hidden timed discussions
1841             $forum->viewhiddentimedposts = true;
1842             if (!empty($CFG->forum_enabletimedposts)) {
1843                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1844                     $forum->viewhiddentimedposts = false;
1845                 }
1846             }
1848         /// qanda access
1849             if ($forum->type == 'qanda'
1850                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1852                 // We need to check whether the user has posted in the qanda forum.
1853                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1854                                                     // the user is allowed to see in this forum.
1855                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1856                     foreach ($discussionspostedin as $d) {
1857                         $forum->onlydiscussions[] = $d->id;
1858                     }
1859                 }
1860             }
1862             $readableforums[$forum->id] = $forum;
1863         }
1865         unset($modinfo);
1867     } // End foreach $courses
1869     return $readableforums;
1872 /**
1873  * Returns a list of posts found using an array of search terms.
1874  *
1875  * @global object
1876  * @global object
1877  * @global object
1878  * @param array $searchterms array of search terms, e.g. word +word -word
1879  * @param int $courseid if 0, we search through the whole site
1880  * @param int $limitfrom
1881  * @param int $limitnum
1882  * @param int &$totalcount
1883  * @param string $extrasql
1884  * @return array|bool Array of posts found or false
1885  */
1886 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1887                             &$totalcount, $extrasql='') {
1888     global $CFG, $DB, $USER;
1889     require_once($CFG->libdir.'/searchlib.php');
1891     $forums = forum_get_readable_forums($USER->id, $courseid);
1893     if (count($forums) == 0) {
1894         $totalcount = 0;
1895         return false;
1896     }
1898     $now = round(time(), -2); // db friendly
1900     $fullaccess = array();
1901     $where = array();
1902     $params = array();
1904     foreach ($forums as $forumid => $forum) {
1905         $select = array();
1907         if (!$forum->viewhiddentimedposts) {
1908             $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
1909             $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
1910         }
1912         $cm = $forum->cm;
1913         $context = $forum->context;
1915         if ($forum->type == 'qanda'
1916             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1917             if (!empty($forum->onlydiscussions)) {
1918                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
1919                 $params = array_merge($params, $discussionid_params);
1920                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
1921             } else {
1922                 $select[] = "p.parent = 0";
1923             }
1924         }
1926         if (!empty($forum->onlygroups)) {
1927             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
1928             $params = array_merge($params, $groupid_params);
1929             $select[] = "d.groupid $groupid_sql";
1930         }
1932         if ($select) {
1933             $selects = implode(" AND ", $select);
1934             $where[] = "(d.forum = :forum{$forumid} AND $selects)";
1935             $params['forum'.$forumid] = $forumid;
1936         } else {
1937             $fullaccess[] = $forumid;
1938         }
1939     }
1941     if ($fullaccess) {
1942         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
1943         $params = array_merge($params, $fullid_params);
1944         $where[] = "(d.forum $fullid_sql)";
1945     }
1947     $selectdiscussion = "(".implode(" OR ", $where).")";
1949     $messagesearch = '';
1950     $searchstring = '';
1952     // Need to concat these back together for parser to work.
1953     foreach($searchterms as $searchterm){
1954         if ($searchstring != '') {
1955             $searchstring .= ' ';
1956         }
1957         $searchstring .= $searchterm;
1958     }
1960     // We need to allow quoted strings for the search. The quotes *should* be stripped
1961     // by the parser, but this should be examined carefully for security implications.
1962     $searchstring = str_replace("\\\"","\"",$searchstring);
1963     $parser = new search_parser();
1964     $lexer = new search_lexer($parser);
1966     if ($lexer->parse($searchstring)) {
1967         $parsearray = $parser->get_parsed_array();
1968     // Experimental feature under 1.8! MDL-8830
1969     // Use alternative text searches if defined
1970     // This feature only works under mysql until properly implemented for other DBs
1971     // Requires manual creation of text index for forum_posts before enabling it:
1972     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
1973     // Experimental feature under 1.8! MDL-8830
1974         if (!empty($CFG->forum_usetextsearches)) {
1975             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
1976                                                  'p.userid', 'u.id', 'u.firstname',
1977                                                  'u.lastname', 'p.modified', 'd.forum');
1978         } else {
1979             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
1980                                                  'p.userid', 'u.id', 'u.firstname',
1981                                                  'u.lastname', 'p.modified', 'd.forum');
1982         }
1983         $params = array_merge($params, $msparams);
1984     }
1986     $fromsql = "{forum_posts} p,
1987                   {forum_discussions} d,
1988                   {user} u";
1990     $selectsql = " $messagesearch
1991                AND p.discussion = d.id
1992                AND p.userid = u.id
1993                AND $selectdiscussion
1994                    $extrasql";
1996     $countsql = "SELECT COUNT(*)
1997                    FROM $fromsql
1998                   WHERE $selectsql";
2000     $searchsql = "SELECT p.*,
2001                          d.forum,
2002                          u.firstname,
2003                          u.lastname,
2004                          u.email,
2005                          u.picture,
2006                          u.imagealt,
2007                          u.email
2008                     FROM $fromsql
2009                    WHERE $selectsql
2010                 ORDER BY p.modified DESC";
2012     $totalcount = $DB->count_records_sql($countsql, $params);
2014     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2017 /**
2018  * Returns a list of ratings for a particular post - sorted.
2019  *
2020  * TODO: Check if this function is actually used anywhere.
2021  * Up until the fix for MDL-27471 this function wasn't even returning.
2022  *
2023  * @param stdClass $context
2024  * @param int $postid
2025  * @param string $sort
2026  * @return array Array of ratings or false
2027  */
2028 function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
2029     $options = new stdClass;
2030     $options->context = $context;
2031     $options->component = 'mod_forum';
2032     $options->ratingarea = 'post';
2033     $options->itemid = $postid;
2034     $options->sort = "ORDER BY $sort";
2036     $rm = new rating_manager();
2037     return $rm->get_all_ratings_for_item($options);
2040 /**
2041  * Returns a list of all new posts that have not been mailed yet
2042  *
2043  * @param int $starttime posts created after this time
2044  * @param int $endtime posts created before this
2045  * @param int $now used for timed discussions only
2046  * @return array
2047  */
2048 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2049     global $CFG, $DB;
2051     $params = array($starttime, $endtime);
2052     if (!empty($CFG->forum_enabletimedposts)) {
2053         if (empty($now)) {
2054             $now = time();
2055         }
2056         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2057         $params[] = $now;
2058         $params[] = $now;
2059     } else {
2060         $timedsql = "";
2061     }
2063     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2064                               FROM {forum_posts} p
2065                                    JOIN {forum_discussions} d ON d.id = p.discussion
2066                              WHERE p.mailed = 0
2067                                    AND p.created >= ?
2068                                    AND (p.created < ? OR p.mailnow = 1)
2069                                    $timedsql
2070                           ORDER BY p.modified ASC", $params);
2073 /**
2074  * Marks posts before a certain time as being mailed already
2075  *
2076  * @global object
2077  * @global object
2078  * @param int $endtime
2079  * @param int $now Defaults to time()
2080  * @return bool
2081  */
2082 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2083     global $CFG, $DB;
2084     if (empty($now)) {
2085         $now = time();
2086     }
2088     if (empty($CFG->forum_enabletimedposts)) {
2089         return $DB->execute("UPDATE {forum_posts}
2090                                SET mailed = '1'
2091                              WHERE (created < ? OR mailnow = 1)
2092                                    AND mailed = 0", array($endtime));
2094     } else {
2095         return $DB->execute("UPDATE {forum_posts}
2096                                SET mailed = '1'
2097                              WHERE discussion NOT IN (SELECT d.id
2098                                                         FROM {forum_discussions} d
2099                                                        WHERE d.timestart > ?)
2100                                    AND (created < ? OR mailnow = 1)
2101                                    AND mailed = 0", array($now, $endtime));
2102     }
2105 /**
2106  * Get all the posts for a user in a forum suitable for forum_print_post
2107  *
2108  * @global object
2109  * @global object
2110  * @uses CONTEXT_MODULE
2111  * @return array
2112  */
2113 function forum_get_user_posts($forumid, $userid) {
2114     global $CFG, $DB;
2116     $timedsql = "";
2117     $params = array($forumid, $userid);
2119     if (!empty($CFG->forum_enabletimedposts)) {
2120         $cm = get_coursemodule_from_instance('forum', $forumid);
2121         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2122             $now = time();
2123             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2124             $params[] = $now;
2125             $params[] = $now;
2126         }
2127     }
2129     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2130                               FROM {forum} f
2131                                    JOIN {forum_discussions} d ON d.forum = f.id
2132                                    JOIN {forum_posts} p       ON p.discussion = d.id
2133                                    JOIN {user} u              ON u.id = p.userid
2134                              WHERE f.id = ?
2135                                    AND p.userid = ?
2136                                    $timedsql
2137                           ORDER BY p.modified ASC", $params);
2140 /**
2141  * Get all the discussions user participated in
2142  *
2143  * @global object
2144  * @global object
2145  * @uses CONTEXT_MODULE
2146  * @param int $forumid
2147  * @param int $userid
2148  * @return array Array or false
2149  */
2150 function forum_get_user_involved_discussions($forumid, $userid) {
2151     global $CFG, $DB;
2153     $timedsql = "";
2154     $params = array($forumid, $userid);
2155     if (!empty($CFG->forum_enabletimedposts)) {
2156         $cm = get_coursemodule_from_instance('forum', $forumid);
2157         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2158             $now = time();
2159             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2160             $params[] = $now;
2161             $params[] = $now;
2162         }
2163     }
2165     return $DB->get_records_sql("SELECT DISTINCT d.*
2166                               FROM {forum} f
2167                                    JOIN {forum_discussions} d ON d.forum = f.id
2168                                    JOIN {forum_posts} p       ON p.discussion = d.id
2169                              WHERE f.id = ?
2170                                    AND p.userid = ?
2171                                    $timedsql", $params);
2174 /**
2175  * Get all the posts for a user in a forum suitable for forum_print_post
2176  *
2177  * @global object
2178  * @global object
2179  * @param int $forumid
2180  * @param int $userid
2181  * @return array of counts or false
2182  */
2183 function forum_count_user_posts($forumid, $userid) {
2184     global $CFG, $DB;
2186     $timedsql = "";
2187     $params = array($forumid, $userid);
2188     if (!empty($CFG->forum_enabletimedposts)) {
2189         $cm = get_coursemodule_from_instance('forum', $forumid);
2190         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2191             $now = time();
2192             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2193             $params[] = $now;
2194             $params[] = $now;
2195         }
2196     }
2198     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2199                              FROM {forum} f
2200                                   JOIN {forum_discussions} d ON d.forum = f.id
2201                                   JOIN {forum_posts} p       ON p.discussion = d.id
2202                                   JOIN {user} u              ON u.id = p.userid
2203                             WHERE f.id = ?
2204                                   AND p.userid = ?
2205                                   $timedsql", $params);
2208 /**
2209  * Given a log entry, return the forum post details for it.
2210  *
2211  * @global object
2212  * @global object
2213  * @param object $log
2214  * @return array|null
2215  */
2216 function forum_get_post_from_log($log) {
2217     global $CFG, $DB;
2219     if ($log->action == "add post") {
2221         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2222                                            u.firstname, u.lastname, u.email, u.picture
2223                                  FROM {forum_discussions} d,
2224                                       {forum_posts} p,
2225                                       {forum} f,
2226                                       {user} u
2227                                 WHERE p.id = ?
2228                                   AND d.id = p.discussion
2229                                   AND p.userid = u.id
2230                                   AND u.deleted <> '1'
2231                                   AND f.id = d.forum", array($log->info));
2234     } else if ($log->action == "add discussion") {
2236         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2237                                            u.firstname, u.lastname, u.email, u.picture
2238                                  FROM {forum_discussions} d,
2239                                       {forum_posts} p,
2240                                       {forum} f,
2241                                       {user} u
2242                                 WHERE d.id = ?
2243                                   AND d.firstpost = p.id
2244                                   AND p.userid = u.id
2245                                   AND u.deleted <> '1'
2246                                   AND f.id = d.forum", array($log->info));
2247     }
2248     return NULL;
2251 /**
2252  * Given a discussion id, return the first post from the discussion
2253  *
2254  * @global object
2255  * @global object
2256  * @param int $dicsussionid
2257  * @return array
2258  */
2259 function forum_get_firstpost_from_discussion($discussionid) {
2260     global $CFG, $DB;
2262     return $DB->get_record_sql("SELECT p.*
2263                              FROM {forum_discussions} d,
2264                                   {forum_posts} p
2265                             WHERE d.id = ?
2266                               AND d.firstpost = p.id ", array($discussionid));
2269 /**
2270  * Returns an array of counts of replies to each discussion
2271  *
2272  * @global object
2273  * @global object
2274  * @param int $forumid
2275  * @param string $forumsort
2276  * @param int $limit
2277  * @param int $page
2278  * @param int $perpage
2279  * @return array
2280  */
2281 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2282     global $CFG, $DB;
2284     if ($limit > 0) {
2285         $limitfrom = 0;
2286         $limitnum  = $limit;
2287     } else if ($page != -1) {
2288         $limitfrom = $page*$perpage;
2289         $limitnum  = $perpage;
2290     } else {
2291         $limitfrom = 0;
2292         $limitnum  = 0;
2293     }
2295     if ($forumsort == "") {
2296         $orderby = "";
2297         $groupby = "";
2299     } else {
2300         $orderby = "ORDER BY $forumsort";
2301         $groupby = ", ".strtolower($forumsort);
2302         $groupby = str_replace('desc', '', $groupby);
2303         $groupby = str_replace('asc', '', $groupby);
2304     }
2306     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2307         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2308                   FROM {forum_posts} p
2309                        JOIN {forum_discussions} d ON p.discussion = d.id
2310                  WHERE p.parent > 0 AND d.forum = ?
2311               GROUP BY p.discussion";
2312         return $DB->get_records_sql($sql, array($forumid));
2314     } else {
2315         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2316                   FROM {forum_posts} p
2317                        JOIN {forum_discussions} d ON p.discussion = d.id
2318                  WHERE d.forum = ?
2319               GROUP BY p.discussion $groupby
2320               $orderby";
2321         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2322     }
2325 /**
2326  * @global object
2327  * @global object
2328  * @global object
2329  * @staticvar array $cache
2330  * @param object $forum
2331  * @param object $cm
2332  * @param object $course
2333  * @return mixed
2334  */
2335 function forum_count_discussions($forum, $cm, $course) {
2336     global $CFG, $DB, $USER;
2338     static $cache = array();
2340     $now = round(time(), -2); // db cache friendliness
2342     $params = array($course->id);
2344     if (!isset($cache[$course->id])) {
2345         if (!empty($CFG->forum_enabletimedposts)) {
2346             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2347             $params[] = $now;
2348             $params[] = $now;
2349         } else {
2350             $timedsql = "";
2351         }
2353         $sql = "SELECT f.id, COUNT(d.id) as dcount
2354                   FROM {forum} f
2355                        JOIN {forum_discussions} d ON d.forum = f.id
2356                  WHERE f.course = ?
2357                        $timedsql
2358               GROUP BY f.id";
2360         if ($counts = $DB->get_records_sql($sql, $params)) {
2361             foreach ($counts as $count) {
2362                 $counts[$count->id] = $count->dcount;
2363             }
2364             $cache[$course->id] = $counts;
2365         } else {
2366             $cache[$course->id] = array();
2367         }
2368     }
2370     if (empty($cache[$course->id][$forum->id])) {
2371         return 0;
2372     }
2374     $groupmode = groups_get_activity_groupmode($cm, $course);
2376     if ($groupmode != SEPARATEGROUPS) {
2377         return $cache[$course->id][$forum->id];
2378     }
2380     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2381         return $cache[$course->id][$forum->id];
2382     }
2384     require_once($CFG->dirroot.'/course/lib.php');
2386     $modinfo = get_fast_modinfo($course);
2387     if (is_null($modinfo->groups)) {
2388         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2389     }
2391     if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2392         $mygroups = $modinfo->groups[$cm->groupingid];
2393     } else {
2394         $mygroups = false; // Will be set below
2395     }
2397     // add all groups posts
2398     if (empty($mygroups)) {
2399         $mygroups = array(-1=>-1);
2400     } else {
2401         $mygroups[-1] = -1;
2402     }
2404     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2405     $params[] = $forum->id;
2407     if (!empty($CFG->forum_enabletimedposts)) {
2408         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2409         $params[] = $now;
2410         $params[] = $now;
2411     } else {
2412         $timedsql = "";
2413     }
2415     $sql = "SELECT COUNT(d.id)
2416               FROM {forum_discussions} d
2417              WHERE d.groupid $mygroups_sql AND d.forum = ?
2418                    $timedsql";
2420     return $DB->get_field_sql($sql, $params);
2423 /**
2424  * How many posts by other users are unrated by a given user in the given discussion?
2425  *
2426  * TODO: Is this function still used anywhere?
2427  *
2428  * @param int $discussionid
2429  * @param int $userid
2430  * @return mixed
2431  */
2432 function forum_count_unrated_posts($discussionid, $userid) {
2433     global $CFG, $DB;
2435     $sql = "SELECT COUNT(*) as num
2436               FROM {forum_posts}
2437              WHERE parent > 0
2438                AND discussion = :discussionid
2439                AND userid <> :userid";
2440     $params = array('discussionid' => $discussionid, 'userid' => $userid);
2441     $posts = $DB->get_record_sql($sql, $params);
2442     if ($posts) {
2443         $sql = "SELECT count(*) as num
2444                   FROM {forum_posts} p,
2445                        {rating} r
2446                  WHERE p.discussion = :discussionid AND
2447                        p.id = r.itemid AND
2448                        r.userid = userid AND
2449                        r.component = 'mod_forum' AND
2450                        r.ratingarea = 'post'";
2451         $rated = $DB->get_record_sql($sql, $params);
2452         if ($rated) {
2453             if ($posts->num > $rated->num) {
2454                 return $posts->num - $rated->num;
2455             } else {
2456                 return 0;    // Just in case there was a counting error
2457             }
2458         } else {
2459             return $posts->num;
2460         }
2461     } else {
2462         return 0;
2463     }
2466 /**
2467  * Get all discussions in a forum
2468  *
2469  * @global object
2470  * @global object
2471  * @global object
2472  * @uses CONTEXT_MODULE
2473  * @uses VISIBLEGROUPS
2474  * @param object $cm
2475  * @param string $forumsort
2476  * @param bool $fullpost
2477  * @param int $unused
2478  * @param int $limit
2479  * @param bool $userlastmodified
2480  * @param int $page
2481  * @param int $perpage
2482  * @return array
2483  */
2484 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2485     global $CFG, $DB, $USER;
2487     $timelimit = '';
2489     $now = round(time(), -2);
2490     $params = array($cm->instance);
2492     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2494     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2495         return array();
2496     }
2498     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2500         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2501             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2502             $params[] = $now;
2503             $params[] = $now;
2504             if (isloggedin()) {
2505                 $timelimit .= " OR d.userid = ?";
2506                 $params[] = $USER->id;
2507             }
2508             $timelimit .= ")";
2509         }
2510     }
2512     if ($limit > 0) {
2513         $limitfrom = 0;
2514         $limitnum  = $limit;
2515     } else if ($page != -1) {
2516         $limitfrom = $page*$perpage;
2517         $limitnum  = $perpage;
2518     } else {
2519         $limitfrom = 0;
2520         $limitnum  = 0;
2521     }
2523     $groupmode    = groups_get_activity_groupmode($cm);
2524     $currentgroup = groups_get_activity_group($cm);
2526     if ($groupmode) {
2527         if (empty($modcontext)) {
2528             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2529         }
2531         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2532             if ($currentgroup) {
2533                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2534                 $params[] = $currentgroup;
2535             } else {
2536                 $groupselect = "";
2537             }
2539         } else {
2540             //seprate groups without access all
2541             if ($currentgroup) {
2542                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2543                 $params[] = $currentgroup;
2544             } else {
2545                 $groupselect = "AND d.groupid = -1";
2546             }
2547         }
2548     } else {
2549         $groupselect = "";
2550     }
2553     if (empty($forumsort)) {
2554         $forumsort = "d.timemodified DESC";
2555     }
2556     if (empty($fullpost)) {
2557         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2558     } else {
2559         $postdata = "p.*";
2560     }
2562     if (empty($userlastmodified)) {  // We don't need to know this
2563         $umfields = "";
2564         $umtable  = "";
2565     } else {
2566         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2567         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2568     }
2570     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2571                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2572               FROM {forum_discussions} d
2573                    JOIN {forum_posts} p ON p.discussion = d.id
2574                    JOIN {user} u ON p.userid = u.id
2575                    $umtable
2576              WHERE d.forum = ? AND p.parent = 0
2577                    $timelimit $groupselect
2578           ORDER BY $forumsort";
2579     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2582 /**
2583  *
2584  * @global object
2585  * @global object
2586  * @global object
2587  * @uses CONTEXT_MODULE
2588  * @uses VISIBLEGROUPS
2589  * @param object $cm
2590  * @return array
2591  */
2592 function forum_get_discussions_unread($cm) {
2593     global $CFG, $DB, $USER;
2595     $now = round(time(), -2);
2596     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2598     $params = array();
2599     $groupmode    = groups_get_activity_groupmode($cm);
2600     $currentgroup = groups_get_activity_group($cm);
2602     if ($groupmode) {
2603         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2605         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2606             if ($currentgroup) {
2607                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2608                 $params['currentgroup'] = $currentgroup;
2609             } else {
2610                 $groupselect = "";
2611             }
2613         } else {
2614             //separate groups without access all
2615             if ($currentgroup) {
2616                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2617                 $params['currentgroup'] = $currentgroup;
2618             } else {
2619                 $groupselect = "AND d.groupid = -1";
2620             }
2621         }
2622     } else {
2623         $groupselect = "";
2624     }
2626     if (!empty($CFG->forum_enabletimedposts)) {
2627         $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2628         $params['now1'] = $now;
2629         $params['now2'] = $now;
2630     } else {
2631         $timedsql = "";
2632     }
2634     $sql = "SELECT d.id, COUNT(p.id) AS unread
2635               FROM {forum_discussions} d
2636                    JOIN {forum_posts} p     ON p.discussion = d.id
2637                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2638              WHERE d.forum = {$cm->instance}
2639                    AND p.modified >= :cutoffdate AND r.id is NULL
2640                    $groupselect
2641                    $timedsql
2642           GROUP BY d.id";
2643     $params['cutoffdate'] = $cutoffdate;
2645     if ($unreads = $DB->get_records_sql($sql, $params)) {
2646         foreach ($unreads as $unread) {
2647             $unreads[$unread->id] = $unread->unread;
2648         }
2649         return $unreads;
2650     } else {
2651         return array();
2652     }
2655 /**
2656  * @global object
2657  * @global object
2658  * @global object
2659  * @uses CONEXT_MODULE
2660  * @uses VISIBLEGROUPS
2661  * @param object $cm
2662  * @return array
2663  */
2664 function forum_get_discussions_count($cm) {
2665     global $CFG, $DB, $USER;
2667     $now = round(time(), -2);
2668     $params = array($cm->instance);
2669     $groupmode    = groups_get_activity_groupmode($cm);
2670     $currentgroup = groups_get_activity_group($cm);
2672     if ($groupmode) {
2673         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2675         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2676             if ($currentgroup) {
2677                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2678                 $params[] = $currentgroup;
2679             } else {
2680                 $groupselect = "";
2681             }
2683         } else {
2684             //seprate groups without access all
2685             if ($currentgroup) {
2686                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2687                 $params[] = $currentgroup;
2688             } else {
2689                 $groupselect = "AND d.groupid = -1";
2690             }
2691         }
2692     } else {
2693         $groupselect = "";
2694     }
2696     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2698     $timelimit = "";
2700     if (!empty($CFG->forum_enabletimedposts)) {
2702         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2704         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2705             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2706             $params[] = $now;
2707             $params[] = $now;
2708             if (isloggedin()) {
2709                 $timelimit .= " OR d.userid = ?";
2710                 $params[] = $USER->id;
2711             }
2712             $timelimit .= ")";
2713         }
2714     }
2716     $sql = "SELECT COUNT(d.id)
2717               FROM {forum_discussions} d
2718                    JOIN {forum_posts} p ON p.discussion = d.id
2719              WHERE d.forum = ? AND p.parent = 0
2720                    $groupselect $timelimit";
2722     return $DB->get_field_sql($sql, $params);
2726 /**
2727  * Get all discussions started by a particular user in a course (or group)
2728  * This function no longer used ...
2729  *
2730  * @todo Remove this function if no longer used
2731  * @global object
2732  * @global object
2733  * @param int $courseid
2734  * @param int $userid
2735  * @param int $groupid
2736  * @return array
2737  */
2738 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2739     global $CFG, $DB;
2740     $params = array($courseid, $userid);
2741     if ($groupid) {
2742         $groupselect = " AND d.groupid = ? ";
2743         $params[] = $groupid;
2744     } else  {
2745         $groupselect = "";
2746     }
2748     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2749                                    f.type as forumtype, f.name as forumname, f.id as forumid
2750                               FROM {forum_discussions} d,
2751                                    {forum_posts} p,
2752                                    {user} u,
2753                                    {forum} f
2754                              WHERE d.course = ?
2755                                AND p.discussion = d.id
2756                                AND p.parent = 0
2757                                AND p.userid = u.id
2758                                AND u.id = ?
2759                                AND d.forum = f.id $groupselect
2760                           ORDER BY p.created DESC", $params);
2763 /**
2764  * Get the list of potential subscribers to a forum.
2765  *
2766  * @param object $forumcontext the forum context.
2767  * @param integer $groupid the id of a group, or 0 for all groups.
2768  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2769  * @param string $sort sort order. As for get_users_by_capability.
2770  * @return array list of users.
2771  */
2772 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2773     global $DB;
2775     // only active enrolled users or everybody on the frontpage
2776     list($esql, $params) = get_enrolled_sql($forumcontext, '', $groupid, true);
2778     $sql = "SELECT $fields
2779               FROM {user} u
2780               JOIN ($esql) je ON je.id = u.id";
2781     if ($sort) {
2782         $sql = "$sql ORDER BY $sort";
2783     } else {
2784         $sql = "$sql ORDER BY u.lastname ASC, u.firstname ASC";
2785     }
2787     return $DB->get_records_sql($sql, $params);
2790 /**
2791  * Returns list of user objects that are subscribed to this forum
2792  *
2793  * @global object
2794  * @global object
2795  * @param object $course the course
2796  * @param forum $forum the forum
2797  * @param integer $groupid group id, or 0 for all.
2798  * @param object $context the forum context, to save re-fetching it where possible.
2799  * @param string $fields requested user fields (with "u." table prefix)
2800  * @return array list of users.
2801  */
2802 function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
2803     global $CFG, $DB;
2805     if (empty($fields)) {
2806         $fields ="u.id,
2807                   u.username,
2808                   u.firstname,
2809                   u.lastname,
2810                   u.maildisplay,
2811                   u.mailformat,
2812                   u.maildigest,
2813                   u.imagealt,
2814                   u.email,
2815                   u.emailstop,
2816                   u.city,
2817                   u.country,
2818                   u.lastaccess,
2819                   u.lastlogin,
2820                   u.picture,
2821                   u.timezone,
2822                   u.theme,
2823                   u.lang,
2824                   u.trackforums,
2825                   u.mnethostid";
2826     }
2828     if (empty($context)) {
2829         $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2830         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2831     }
2833     if (forum_is_forcesubscribed($forum)) {
2834         $results = forum_get_potential_subscribers($context, $groupid, $fields, "u.email ASC");
2836     } else {
2837         // only active enrolled users or everybody on the frontpage
2838         list($esql, $params) = get_enrolled_sql($context, '', $groupid, true);
2839         $params['forumid'] = $forum->id;
2840         $results = $DB->get_records_sql("SELECT $fields
2841                                            FROM {user} u
2842                                            JOIN ($esql) je ON je.id = u.id
2843                                            JOIN {forum_subscriptions} s ON s.userid = u.id
2844                                           WHERE s.forum = :forumid
2845                                        ORDER BY u.email ASC", $params);
2846     }
2848     // Guest user should never be subscribed to a forum.
2849     unset($results[$CFG->siteguest]);
2851     return $results;
2856 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2859 /**
2860  * @global object
2861  * @global object
2862  * @param int $courseid
2863  * @param string $type
2864  */
2865 function forum_get_course_forum($courseid, $type) {
2866 // How to set up special 1-per-course forums
2867     global $CFG, $DB, $OUTPUT;
2869     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2870         // There should always only be ONE, but with the right combination of
2871         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2872         foreach ($forums as $forum) {
2873             return $forum;   // ie the first one
2874         }
2875     }
2877     // Doesn't exist, so create one now.
2878     $forum = new stdClass();
2879     $forum->course = $courseid;
2880     $forum->type = "$type";
2881     switch ($forum->type) {
2882         case "news":
2883             $forum->name  = get_string("namenews", "forum");
2884             $forum->intro = get_string("intronews", "forum");
2885             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2886             $forum->assessed = 0;
2887             if ($courseid == SITEID) {
2888                 $forum->name  = get_string("sitenews");
2889                 $forum->forcesubscribe = 0;
2890             }
2891             break;
2892         case "social":
2893             $forum->name  = get_string("namesocial", "forum");
2894             $forum->intro = get_string("introsocial", "forum");
2895             $forum->assessed = 0;
2896             $forum->forcesubscribe = 0;
2897             break;
2898         case "blog":
2899             $forum->name = get_string('blogforum', 'forum');
2900             $forum->intro = get_string('introblog', 'forum');
2901             $forum->assessed = 0;
2902             $forum->forcesubscribe = 0;
2903             break;
2904         default:
2905             echo $OUTPUT->notification("That forum type doesn't exist!");
2906             return false;
2907             break;
2908     }
2910     $forum->timemodified = time();
2911     $forum->id = $DB->insert_record("forum", $forum);
2913     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2914         echo $OUTPUT->notification("Could not find forum module!!");
2915         return false;
2916     }
2917     $mod = new stdClass();
2918     $mod->course = $courseid;
2919     $mod->module = $module->id;
2920     $mod->instance = $forum->id;
2921     $mod->section = 0;
2922     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
2923         echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
2924         return false;
2925     }
2926     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
2927         echo $OUTPUT->notification("Could not add the new course module to that section");
2928         return false;
2929     }
2930     $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule));
2932     include_once("$CFG->dirroot/course/lib.php");
2933     rebuild_course_cache($courseid);
2935     return $DB->get_record("forum", array("id" => "$forum->id"));
2939 /**
2940  * Given the data about a posting, builds up the HTML to display it and
2941  * returns the HTML in a string.  This is designed for sending via HTML email.
2942  *
2943  * @global object
2944  * @param object $course
2945  * @param object $cm
2946  * @param object $forum
2947  * @param object $discussion
2948  * @param object $post
2949  * @param object $userform
2950  * @param object $userto
2951  * @param bool $ownpost
2952  * @param bool $reply
2953  * @param bool $link
2954  * @param bool $rate
2955  * @param string $footer
2956  * @return string
2957  */
2958 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
2959                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
2961     global $CFG, $OUTPUT;
2963     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2965     if (!isset($userto->viewfullnames[$forum->id])) {
2966         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
2967     } else {
2968         $viewfullnames = $userto->viewfullnames[$forum->id];
2969     }
2971     // add absolute file links
2972     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
2974     // format the post body
2975     $options = new stdClass();
2976     $options->para = true;
2977     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
2979     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
2981     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
2982     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
2983     $output .= '</td>';
2985     if ($post->parent) {
2986         $output .= '<td class="topic">';
2987     } else {
2988         $output .= '<td class="topic starter">';
2989     }
2990     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
2992     $fullname = fullname($userfrom, $viewfullnames);
2993     $by = new stdClass();
2994     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
2995     $by->date = userdate($post->modified, '', $userto->timezone);
2996     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
2998     $output .= '</td></tr>';
3000     $output .= '<tr><td class="left side" valign="top">';
3002     if (isset($userfrom->groups)) {
3003         $groups = $userfrom->groups[$forum->id];
3004     } else {
3005         $groups = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3006     }
3008     if ($groups) {
3009         $output .= print_group_picture($groups, $course->id, false, true, true);
3010     } else {
3011         $output .= '&nbsp;';
3012     }
3014     $output .= '</td><td class="content">';
3016     $attachments = forum_print_attachments($post, $cm, 'html');
3017     if ($attachments !== '') {
3018         $output .= '<div class="attachments">';
3019         $output .= $attachments;
3020         $output .= '</div>';
3021     }
3023     $output .= $formattedtext;
3025 // Commands
3026     $commands = array();
3028     if ($post->parent) {
3029         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3030                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3031     }
3033     if ($reply) {
3034         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3035                       get_string('reply', 'forum').'</a>';
3036     }
3038     $output .= '<div class="commands">';
3039     $output .= implode(' | ', $commands);
3040     $output .= '</div>';
3042 // Context link to post if required
3043     if ($link) {
3044         $output .= '<div class="link">';
3045         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3046                      get_string('postincontext', 'forum').'</a>';
3047         $output .= '</div>';
3048     }
3050     if ($footer) {
3051         $output .= '<div class="footer">'.$footer.'</div>';
3052     }
3053     $output .= '</td></tr></table>'."\n\n";
3055     return $output;
3058 /**
3059  * Print a forum post
3060  *
3061  * @global object
3062  * @global object
3063  * @uses FORUM_MODE_THREADED
3064  * @uses PORTFOLIO_FORMAT_PLAINHTML
3065  * @uses PORTFOLIO_FORMAT_FILE
3066  * @uses PORTFOLIO_FORMAT_RICHHTML
3067  * @uses PORTFOLIO_ADD_TEXT_LINK
3068  * @uses CONTEXT_MODULE
3069  * @param object $post The post to print.
3070  * @param object $discussion
3071  * @param object $forum
3072  * @param object $cm
3073  * @param object $course
3074  * @param boolean $ownpost Whether this post belongs to the current user.
3075  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3076  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3077  * @param string $footer Extra stuff to print after the message.
3078  * @param string $highlight Space-separated list of terms to highlight.
3079  * @param int $post_read true, false or -99. If we already know whether this user
3080  *          has read this post, pass that in, otherwise, pass in -99, and this
3081  *          function will work it out.
3082  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3083  *          the current user can't see this post, if this argument is true
3084  *          (the default) then print a dummy 'you can't see this post' post.
3085  *          If false, don't output anything at all.
3086  * @param bool|null $istracked
3087  * @return void
3088  */
3089 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3090                           $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3091     global $USER, $CFG, $OUTPUT;
3093     require_once($CFG->libdir . '/filelib.php');
3095     // String cache
3096     static $str;
3098     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3100     $post->course = $course->id;
3101     $post->forum  = $forum->id;
3102     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3104     // caching
3105     if (!isset($cm->cache)) {
3106         $cm->cache = new stdClass;
3107     }
3109     if (!isset($cm->cache->caps)) {
3110         $cm->cache->caps = array();
3111         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3112         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3113         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3114         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3115         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3116         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3117         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3118         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3119         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3120     }
3122     if (!isset($cm->uservisible)) {
3123         $cm->uservisible = coursemodule_visible_for_user($cm);
3124     }
3126     if ($istracked && is_null($postisread)) {
3127         $postisread = forum_tp_is_post_read($USER->id, $post);
3128     }
3130     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3131         $output = '';
3132         if (!$dummyifcantsee) {
3133             if ($return) {
3134                 return $output;
3135             }
3136             echo $output;
3137             return;
3138         }
3139         $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3140         $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'));
3141         $output .= html_writer::start_tag('div', array('class'=>'row header'));
3142         $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3143         if ($post->parent) {
3144             $output .= html_writer::start_tag('div', array('class'=>'topic'));
3145         } else {
3146             $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3147         }
3148         $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class'=>'subject')); // Subject
3149         $output .= html_writer::tag('div', get_string('forumauthorhidden','forum'), array('class'=>'author')); // author
3150         $output .= html_writer::end_tag('div');
3151         $output .= html_writer::end_tag('div'); // row
3152         $output .= html_writer::start_tag('div', array('class'=>'row'));
3153         $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3154         $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3155         $output .= html_writer::end_tag('div'); // row
3156         $output .= html_writer::end_tag('div'); // forumpost
3158         if ($return) {
3159             return $output;
3160         }
3161         echo $output;
3162         return;
3163     }
3165     if (empty($str)) {
3166         $str = new stdClass;
3167         $str->edit         = get_string('edit', 'forum');
3168         $str->delete       = get_string('delete', 'forum');
3169         $str->reply        = get_string('reply', 'forum');
3170         $str->parent       = get_string('parent', 'forum');
3171         $str->pruneheading = get_string('pruneheading', 'forum');
3172         $str->prune        = get_string('prune', 'forum');
3173         $str->displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3174         $str->markread     = get_string('markread', 'forum');
3175         $str->markunread   = get_string('markunread', 'forum');
3176     }
3178     $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3180     // Build an object that represents the posting user
3181     $postuser = new stdClass;
3182     $postuser->id        = $post->userid;
3183     $postuser->firstname = $post->firstname;
3184     $postuser->lastname  = $post->lastname;
3185     $postuser->imagealt  = $post->imagealt;
3186     $postuser->picture   = $post->picture;
3187     $postuser->email     = $post->email;
3188     // Some handy things for later on
3189     $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3190     $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3192     // Prepare the groups the posting user belongs to
3193     if (isset($cm->cache->usersgroups)) {
3194         $groups = array();
3195         if (isset($cm->cache->usersgroups[$post->userid])) {
3196             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3197                 $groups[$gid] = $cm->cache->groups[$gid];
3198             }
3199         }
3200     } else {
3201         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3202     }
3204     // Prepare the attachements for the post, files then images
3205     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3207     // Determine if we need to shorten this post
3208     $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3211     // Prepare an array of commands
3212     $commands = array();
3214     // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3215     // Don't display the mark read / unread controls in this case.
3216     if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3217         $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3218         $text = $str->markunread;
3219         if (!$postisread) {
3220             $url->param('mark', 'read');
3221             $text = $str->markread;
3222         }
3223         if ($str->displaymode == FORUM_MODE_THREADED) {
3224             $url->param('parent', $post->parent);
3225         } else {
3226             $url->set_anchor('p'.$post->id);
3227         }
3228         $commands[] = array('url'=>$url, 'text'=>$text);
3229     }
3231     // Zoom in to the parent specifically
3232     if ($post->parent) {
3233         $url = new moodle_url($discussionlink);
3234         if ($str->displaymode == FORUM_MODE_THREADED) {
3235             $url->param('parent', $post->parent);
3236         } else {
3237             $url->set_anchor('p'.$post->parent);
3238         }
3239         $commands[] = array('url'=>$url, 'text'=>$str->parent);
3240     }
3242     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3243     $age = time() - $post->created;
3244     if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3245         $age = 0;
3246     }
3247     if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3248         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3249     }
3251     if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3252         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3253     }
3255     if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3256         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3257     }
3259     if ($reply) {
3260         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('reply'=>$post->id)), 'text'=>$str->reply);
3261     }
3263     if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
3264         $p = array('postid' => $post->id);
3265         require_once($CFG->libdir.'/portfoliolib.php');
3266         $button = new portfolio_add_button();
3267         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php');
3268         if (empty($attachments)) {
3269             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3270         } else {
3271             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3272         }
3274         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3275         if (!empty($porfoliohtml)) {
3276             $commands[] = $porfoliohtml;
3277         }
3278     }
3279     // Finished building commands
3282     // Begin output
3284     $output  = '';
3286     if ($istracked) {
3287         if ($postisread) {
3288             $forumpostclass = ' read';
3289         } else {
3290             $forumpostclass = ' unread';
3291             $output .= html_writer::tag('a', '', array('name'=>'unread'));
3292         }
3293     } else {
3294         // ignore trackign status if not tracked or tracked param missing
3295         $forumpostclass = '';
3296     }
3298     $topicclass = '';
3299     if (empty($post->parent)) {
3300         $topicclass = ' firstpost starter';
3301     }
3303     $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3304     $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass));
3305     $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3306     $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3307     $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3308     $output .= html_writer::end_tag('div');
3311     $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3313     $postsubject = $post->subject;
3314     if (empty($post->subjectnoformat)) {
3315         $postsubject = format_string($postsubject);
3316     }
3317     $output .= html_writer::tag('div', $postsubject, array('class'=>'subject'));
3319     $by = new stdClass();
3320     $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3321     $by->date = userdate($post->modified);
3322     $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author'));
3324     $output .= html_writer::end_tag('div'); //topic
3325     $output .= html_writer::end_tag('div'); //row
3327     $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3328     $output .= html_writer::start_tag('div', array('class'=>'left'));
3330     $groupoutput = '';
3331     if ($groups) {
3332         $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3333     }
3334     if (empty($groupoutput)) {
3335         $groupoutput = '&nbsp;';
3336     }
3337     $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3339     $output .= html_writer::end_tag('div'); //left side
3340     $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3341     $output .= html_writer::start_tag('div', array('class'=>'content'));
3342     if (!empty($attachments)) {
3343         $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3344     }
3346     $options = new stdClass;
3347     $options->para    = false;
3348     $options->trusted = $post->messagetrust;
3349     $options->context = $modcontext;
3350     if ($shortenpost) {
3351         // Prepare shortened version
3352         $postclass    = 'shortenedpost';
3353         $postcontent  = format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3354         $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
3355         $postcontent .= html_writer::tag('span', '('.get_string('numwords', 'moodle', count_words(strip_tags($post->message))).')...', array('class'=>'post-word-count'));
3356     } else {
3357         // Prepare whole post
3358         $postclass    = 'fullpost';
3359         $postcontent  = format_text($post->message, $post->messageformat, $options, $course->id);
3360         if (!empty($highlight)) {
3361             $postcontent = highlight($highlight, $postcontent);
3362         }
3363         $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
3364     }
3365     // Output the post content
3366     $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
3367     $output .= html_writer::end_tag('div'); // Content
3368     $output .= html_writer::end_tag('div'); // Content mask
3369     $output .= html_writer::end_tag('div'); // Row
3371     $output .= html_writer::start_tag('div', array('class'=>'row side'));
3372     $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
3373     $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
3375     // Output ratings
3376     if (!empty($post->rating)) {
3377         $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
3378     }
3380     // Output the commands
3381     $commandhtml = array();
3382     foreach ($commands as $command) {
3383         if (is_array($command)) {
3384             $commandhtml[] = html_writer::link($command['url'], $command['text']);
3385         } else {
3386             $commandhtml[] = $command;
3387         }
3388     }
3389     $output .= html_writer::tag('div', implode(' | ', $commandhtml), array('class'=>'commands'));
3391     // Output link to post if required
3392     if ($link) {
3393         if ($post->replies == 1) {
3394             $replystring = get_string('repliesone', 'forum', $post->replies);
3395         } else {
3396             $replystring = get_string('repliesmany', 'forum', $post->replies);
3397         }
3399         $output .= html_writer::start_tag('div', array('class'=>'link'));
3400         $output .= html_writer::link($discussionlink, get_string('discussthistopic', 'forum'));
3401         $output .= '&nbsp;('.$replystring.')';
3402         $output .= html_writer::end_tag('div'); // link
3403     }
3405     // Output footer if required
3406     if ($footer) {
3407         $output .= html_writer::tag('div', $footer, array('class'=>'footer'));
3408     }
3410     // Close remaining open divs
3411     $output .= html_writer::end_tag('div'); // content
3412     $output .= html_writer::end_tag('div'); // row
3413     $output .= html_writer::end_tag('div'); // forumpost
3415     // Mark the forum post as read if required
3416     if ($istracked && !$CFG->forum_usermarksread && !$postisread) {
3417         forum_tp_mark_post_read($USER->id, $post, $forum->id);
3418     }
3420     if ($return) {
3421         return $output;
3422     }
3423     echo $output;
3424     return;
3427 /**
3428  * Return rating related permissions
3429  *
3430  * @param string $options the context id
3431  * @return array an associative array of the user's rating permissions
3432  */
3433 function forum_rating_permissions($contextid, $component, $ratingarea) {
3434     $context = get_context_instance_by_id($contextid, MUST_EXIST);
3435     if ($component != 'mod_forum' || $ratingarea != 'post') {
3436         // We don't know about this component/ratingarea so just return null to get the
3437         // default restrictive permissions.
3438         return null;
3439     }
3440     return array(
3441         'view'    => has_capability('mod/forum:viewrating', $context),
3442         'viewany' => has_capability('mod/forum:viewanyrating', $context),
3443         'viewall' => has_capability('mod/forum:viewallratings', $context),
3444         'rate'    => has_capability('mod/forum:rate', $context)
3445     );
3448 /**
3449  * Validates a submitted rating
3450  * @param array $params submitted data
3451  *            context => object the context in which the rated items exists [required]
3452  *            component => The component for this module - should always be mod_forum [required]
3453  *            ratingarea => object the context in which the rated items exists [required]
3454  *            itemid => int the ID of the object being rated [required]
3455  *            scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
3456  *            rating => int the submitted rating [required]
3457  *            rateduserid => int the id of the user whose items have been rated. NOT the user who submitted the ratings. 0 to update all. [required]
3458  *            aggregation => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [required]
3459  * @return boolean true if the rating is valid. Will throw rating_exception if not
3460  */
3461 function forum_rating_validate($params) {
3462     global $DB, $USER;
3464     // Check the component is mod_forum
3465     if ($params['component'] != 'mod_forum') {
3466         throw new rating_exception('invalidcomponent');
3467     }
3469     // Check the ratingarea is post (the only rating area in forum)
3470     if ($params['ratingarea'] != 'post') {
3471         throw new rating_exception('invalidratingarea');
3472     }
3474     // Check the rateduserid is not the current user .. you can't rate your own posts
3475     if ($params['rateduserid'] == $USER->id) {
3476         throw new rating_exception('nopermissiontorate');
3477     }
3479     // Fetch all the related records ... we need to do this anyway to call forum_user_can_see_post
3480     $post = $DB->get_record('forum_posts', array('id' => $params['itemid'], 'userid' => $params['rateduserid']), '*', MUST_EXIST);
3481     $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion), '*', MUST_EXIST);
3482     $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
3483     $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
3484     $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id , false, MUST_EXIST);
3485     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
3487     // Make sure the context provided is the context of the forum
3488     if ($context->id != $params['context']->id) {
3489         throw new rating_exception('invalidcontext');
3490     }
3492     if ($forum->scale != $params['scaleid']) {
3493         //the scale being submitted doesnt match the one in the database
3494         throw new rating_exception('invalidscaleid');
3495     }
3497     // check the item we're rating was created in the assessable time window
3498     if (!empty($forum->assesstimestart) && !empty($forum->assesstimefinish)) {
3499         if ($post->created < $forum->assesstimestart || $post->created > $forum->assesstimefinish) {
3500             throw new rating_exception('notavailable');
3501         }
3502     }
3504     //check that the submitted rating is valid for the scale
3506     // lower limit