NOBUG removing forums navigation customisations
[moodle.git] / mod / forum / lib.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * @package mod-forum
20  * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
21  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22  */
24 /** Include required files */
25 require_once($CFG->libdir.'/filelib.php');
26 require_once($CFG->libdir.'/eventslib.php');
27 require_once($CFG->libdir . '/completionlib.php');
28 require_once($CFG->dirroot.'/user/selector/lib.php');
30 /// CONSTANTS ///////////////////////////////////////////////////////////
32 define('FORUM_MODE_FLATOLDEST', 1);
33 define('FORUM_MODE_FLATNEWEST', -1);
34 define('FORUM_MODE_THREADED', 2);
35 define('FORUM_MODE_NESTED', 3);
37 define('FORUM_CHOOSESUBSCRIBE', 0);
38 define('FORUM_FORCESUBSCRIBE', 1);
39 define('FORUM_INITIALSUBSCRIBE', 2);
40 define('FORUM_DISALLOWSUBSCRIBE',3);
42 define('FORUM_TRACKING_OFF', 0);
43 define('FORUM_TRACKING_OPTIONAL', 1);
44 define('FORUM_TRACKING_ON', 2);
46 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
48 /**
49  * Given an object containing all the necessary data,
50  * (defined by the form in mod_form.php) this function
51  * will create a new instance and return the id number
52  * of the new instance.
53  *
54  * @global object
55  * @global object
56  * @param object $forum add forum instance (with magic quotes)
57  * @return int intance id
58  */
59 function forum_add_instance($forum, $mform) {
60     global $CFG, $DB;
62     $forum->timemodified = time();
64     if (empty($forum->assessed)) {
65         $forum->assessed = 0;
66     }
68     if (empty($forum->ratingtime) or empty($forum->assessed)) {
69         $forum->assesstimestart  = 0;
70         $forum->assesstimefinish = 0;
71     }
73     $forum->id = $DB->insert_record('forum', $forum);
74     $modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule);
76     if ($forum->type == 'single') {  // Create related discussion.
77         $discussion = new 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         if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
156             if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC')) {
157                 echo $OUTPUT->notification('Warning! There is more than one discussion in this forum - using the most recent');
158                 $discussion = array_pop($discussions);
159             } else {
160                 // try to recover by creating initial discussion - MDL-16262
161                 $discussion = new stdClass();
162                 $discussion->course          = $forum->course;
163                 $discussion->forum           = $forum->id;
164                 $discussion->name            = $forum->name;
165                 $discussion->assessed        = $forum->assessed;
166                 $discussion->message         = $forum->intro;
167                 $discussion->messageformat   = $forum->introformat;
168                 $discussion->messagetrust    = true;
169                 $discussion->mailnow         = false;
170                 $discussion->groupid         = -1;
172                 $message = '';
174                 forum_add_discussion($discussion, null, $message);
176                 if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
177                     print_error('cannotadd', 'forum');
178                 }
179             }
180         }
181         if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
182             print_error('cannotfindfirstpost', 'forum');
183         }
185         $cm         = get_coursemodule_from_instance('forum', $forum->id);
186         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
188         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
189             // ugly hack - we need to copy the files somehow
190             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
191             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
193             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
194         }
196         $post->subject       = $forum->name;
197         $post->message       = $forum->intro;
198         $post->messageformat = $forum->introformat;
199         $post->messagetrust  = trusttext_trusted($modcontext);
200         $post->modified      = $forum->timemodified;
201         $post->userid        = $USER->id;    // MDL-18599, so that current teacher can take ownership of activities
203         $DB->update_record('forum_posts', $post);
204         $discussion->name = $forum->name;
205         $DB->update_record('forum_discussions', $discussion);
206     }
208     $DB->update_record('forum', $forum);
210     forum_grade_item_update($forum);
212     return true;
216 /**
217  * Given an ID of an instance of this module,
218  * this function will permanently delete the instance
219  * and any data that depends on it.
220  *
221  * @global object
222  * @param int $id forum instance id
223  * @return bool success
224  */
225 function forum_delete_instance($id) {
226     global $DB;
228     if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
229         return false;
230     }
231     if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
232         return false;
233     }
234     if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
235         return false;
236     }
238     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
240     // now get rid of all files
241     $fs = get_file_storage();
242     $fs->delete_area_files($context->id);
244     $result = true;
246     if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
247         foreach ($discussions as $discussion) {
248             if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
249                 $result = false;
250             }
251         }
252     }
254     if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
255         $result = false;
256     }
258     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
260     if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
261         $result = false;
262     }
264     forum_grade_item_delete($forum);
266     return $result;
270 /**
271  * Indicates API features that the forum supports.
272  *
273  * @uses FEATURE_GROUPS
274  * @uses FEATURE_GROUPINGS
275  * @uses FEATURE_GROUPMEMBERSONLY
276  * @uses FEATURE_MOD_INTRO
277  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
278  * @uses FEATURE_COMPLETION_HAS_RULES
279  * @uses FEATURE_GRADE_HAS_GRADE
280  * @uses FEATURE_GRADE_OUTCOMES
281  * @param string $feature
282  * @return mixed True if yes (some features may use other values)
283  */
284 function forum_supports($feature) {
285     switch($feature) {
286         case FEATURE_GROUPS:                  return true;
287         case FEATURE_GROUPINGS:               return true;
288         case FEATURE_GROUPMEMBERSONLY:        return true;
289         case FEATURE_MOD_INTRO:               return true;
290         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
291         case FEATURE_COMPLETION_HAS_RULES:    return true;
292         case FEATURE_GRADE_HAS_GRADE:         return true;
293         case FEATURE_GRADE_OUTCOMES:          return true;
294         case FEATURE_RATE:                    return true;
295         case FEATURE_BACKUP_MOODLE2:          return true;
297         default: return null;
298     }
302 /**
303  * Obtains the automatic completion state for this forum based on any conditions
304  * in forum settings.
305  *
306  * @global object
307  * @global object
308  * @param object $course Course
309  * @param object $cm Course-module
310  * @param int $userid User ID
311  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
312  * @return bool True if completed, false if not. (If no conditions, then return
313  *   value depends on comparison type)
314  */
315 function forum_get_completion_state($course,$cm,$userid,$type) {
316     global $CFG,$DB;
318     // Get forum details
319     if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
320         throw new Exception("Can't find forum {$cm->instance}");
321     }
323     $result=$type; // Default return value
325     $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
326     $postcountsql="
327 SELECT
328     COUNT(1)
329 FROM
330     {forum_posts} fp
331     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
332 WHERE
333     fp.userid=:userid AND fd.forum=:forumid";
335     if ($forum->completiondiscussions) {
336         $value = $forum->completiondiscussions <=
337                  $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
338         if ($type == COMPLETION_AND) {
339             $result = $result && $value;
340         } else {
341             $result = $result || $value;
342         }
343     }
344     if ($forum->completionreplies) {
345         $value = $forum->completionreplies <=
346                  $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
347         if ($type==COMPLETION_AND) {
348             $result = $result && $value;
349         } else {
350             $result = $result || $value;
351         }
352     }
353     if ($forum->completionposts) {
354         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
355         if ($type == COMPLETION_AND) {
356             $result = $result && $value;
357         } else {
358             $result = $result || $value;
359         }
360     }
362     return $result;
366 /**
367  * Function to be run periodically according to the moodle cron
368  * Finds all posts that have yet to be mailed out, and mails them
369  * out to all subscribers
370  *
371  * @global object
372  * @global object
373  * @global object
374  * @uses CONTEXT_MODULE
375  * @uses CONTEXT_COURSE
376  * @uses SITEID
377  * @uses FORMAT_PLAIN
378  * @return void
379  */
380 function forum_cron() {
381     global $CFG, $USER, $DB;
383     $site = get_site();
385     // all users that are subscribed to any post that needs sending
386     $users = array();
388     // status arrays
389     $mailcount  = array();
390     $errorcount = array();
392     // caches
393     $discussions     = array();
394     $forums          = array();
395     $courses         = array();
396     $coursemodules   = array();
397     $subscribedusers = array();
400     // Posts older than 2 days will not be mailed.  This is to avoid the problem where
401     // cron has not been running for a long time, and then suddenly people are flooded
402     // with mail from the past few weeks or months
403     $timenow   = time();
404     $endtime   = $timenow - $CFG->maxeditingtime;
405     $starttime = $endtime - 48 * 3600;   // Two days earlier
407     if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
408         // Mark them all now as being mailed.  It's unlikely but possible there
409         // might be an error later so that a post is NOT actually mailed out,
410         // but since mail isn't crucial, we can accept this risk.  Doing it now
411         // prevents the risk of duplicated mails, which is a worse problem.
413         if (!forum_mark_old_posts_as_mailed($endtime)) {
414             mtrace('Errors occurred while trying to mark some posts as being mailed.');
415             return false;  // Don't continue trying to mail them, in case we are in a cron loop
416         }
418         // checking post validity, and adding users to loop through later
419         foreach ($posts as $pid => $post) {
421             $discussionid = $post->discussion;
422             if (!isset($discussions[$discussionid])) {
423                 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
424                     $discussions[$discussionid] = $discussion;
425                 } else {
426                     mtrace('Could not find discussion '.$discussionid);
427                     unset($posts[$pid]);
428                     continue;
429                 }
430             }
431             $forumid = $discussions[$discussionid]->forum;
432             if (!isset($forums[$forumid])) {
433                 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
434                     $forums[$forumid] = $forum;
435                 } else {
436                     mtrace('Could not find forum '.$forumid);
437                     unset($posts[$pid]);
438                     continue;
439                 }
440             }
441             $courseid = $forums[$forumid]->course;
442             if (!isset($courses[$courseid])) {
443                 if ($course = $DB->get_record('course', array('id' => $courseid))) {
444                     $courses[$courseid] = $course;
445                 } else {
446                     mtrace('Could not find course '.$courseid);
447                     unset($posts[$pid]);
448                     continue;
449                 }
450             }
451             if (!isset($coursemodules[$forumid])) {
452                 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
453                     $coursemodules[$forumid] = $cm;
454                 } else {
455                     mtrace('Could not course module for forum '.$forumid);
456                     unset($posts[$pid]);
457                     continue;
458                 }
459             }
462             // caching subscribed users of each forum
463             if (!isset($subscribedusers[$forumid])) {
464                 $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
465                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext)) {
466                     foreach ($subusers as $postuser) {
467                         // do not try to mail users with stopped email
468                         if ($postuser->emailstop) {
469                             if (!empty($CFG->forum_logblocked)) {
470                                 add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
471                             }
472                             continue;
473                         }
474                         // this user is subscribed to this forum
475                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
476                         // this user is a user we have to process later
477                         $users[$postuser->id] = $postuser;
478                     }
479                     unset($subusers); // release memory
480                 }
481             }
483             $mailcount[$pid] = 0;
484             $errorcount[$pid] = 0;
485         }
486     }
488     if ($users && $posts) {
490         $urlinfo = parse_url($CFG->wwwroot);
491         $hostname = $urlinfo['host'];
493         foreach ($users as $userto) {
495             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
497             // set this so that the capabilities are cached, and environment matches receiving user
498             cron_setup_user($userto);
500             mtrace('Processing user '.$userto->id);
502             // init caches
503             $userto->viewfullnames = array();
504             $userto->canpost       = array();
505             $userto->markposts     = array();
506             $userto->enrolledin    = array();
508             // reset the caches
509             foreach ($coursemodules as $forumid=>$unused) {
510                 $coursemodules[$forumid]->cache       = new stdClass();
511                 $coursemodules[$forumid]->cache->caps = array();
512                 unset($coursemodules[$forumid]->uservisible);
513             }
515             foreach ($posts as $pid => $post) {
517                 // Set up the environment for the post, discussion, forum, course
518                 $discussion = $discussions[$post->discussion];
519                 $forum      = $forums[$discussion->forum];
520                 $course     = $courses[$forum->course];
521                 $cm         =& $coursemodules[$forum->id];
523                 // Do some checks  to see if we can bail out now
524                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
525                     continue; // user does not subscribe to this forum
526                 }
528                 // Verify user is enrollend in course - if not do not send any email
529                 if (!isset($userto->enrolledin[$course->id])) {
530                     $userto->enrolledin[$course->id] = is_enrolled(get_context_instance(CONTEXT_COURSE, $course->id));
531                 }
532                 if (!$userto->enrolledin[$course->id]) {
533                     // oops - this user should not receive anything from this course
534                     continue;
535                 }
537                 // Get info about the sending user
538                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
539                     $userfrom = $users[$post->userid];
540                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
541                     $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
542                 } else {
543                     mtrace('Could not find user '.$post->userid);
544                     continue;
545                 }
547                 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
549                 // setup global $COURSE properly - needed for roles and languages
550                 cron_setup_user($userto, $course);
552                 // Fill caches
553                 if (!isset($userto->viewfullnames[$forum->id])) {
554                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
555                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
556                 }
557                 if (!isset($userto->canpost[$discussion->id])) {
558                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
559                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
560                 }
561                 if (!isset($userfrom->groups[$forum->id])) {
562                     if (!isset($userfrom->groups)) {
563                         $userfrom->groups = array();
564                         $users[$userfrom->id]->groups = array();
565                     }
566                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
567                     $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
568                 }
570                 // Make sure groups allow this user to see this email
571                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
572                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
573                         continue;                           // Be safe and don't send it to anyone
574                     }
576                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
577                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
578                         continue;
579                     }
580                 }
582                 // Make sure we're allowed to see it...
583                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
584                     mtrace('user '.$userto->id. ' can not see '.$post->id);
585                     continue;
586                 }
588                 // OK so we need to send the email.
590                 // Does the user want this post in a digest?  If so postpone it for now.
591                 if ($userto->maildigest > 0) {
592                     // This user wants the mails to be in digest form
593                     $queue = new stdClass();
594                     $queue->userid       = $userto->id;
595                     $queue->discussionid = $discussion->id;
596                     $queue->postid       = $post->id;
597                     $queue->timemodified = $post->created;
598                     $DB->insert_record('forum_queue', $queue);
599                     continue;
600                 }
603                 // Prepare to actually send the post now, and build up the content
605                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
607                 $userfrom->customheaders = array (  // Headers to make emails easier to track
608                            'Precedence: Bulk',
609                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
610                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
611                            'Message-ID: <moodlepost'.$post->id.'@'.$hostname.'>',
612                            'X-Course-Id: '.$course->id,
613                            'X-Course-Name: '.format_string($course->fullname, true)
614                 );
616                 if ($post->parent) {  // This post is a reply, so add headers for threading (see MDL-22551)
617                     $userfrom->customheaders[] = 'In-Reply-To: <moodlepost'.$post->parent.'@'.$hostname.'>';
618                     $userfrom->customheaders[] = 'References: <moodlepost'.$post->parent.'@'.$hostname.'>';
619                 }
621                 $postsubject = "$course->shortname: ".format_string($post->subject,true);
622                 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
623                 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
625                 // Send the post now!
627                 mtrace('Sending ', '');
629                 $eventdata = new stdClass();
630                 $eventdata->component        = 'mod_forum';
631                 $eventdata->name             = 'posts';
632                 $eventdata->userfrom         = $userfrom;
633                 $eventdata->userto           = $userto;
634                 $eventdata->subject          = $postsubject;
635                 $eventdata->fullmessage      = $posttext;
636                 $eventdata->fullmessageformat = FORMAT_PLAIN;
637                 $eventdata->fullmessagehtml  = $posthtml;
638                 $eventdata->smallmessage     = '';
640                 $mailresult = message_send($eventdata);
641                 if (!$mailresult){
642                     mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
643                          " ($userto->email) .. not trying again.");
644                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
645                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
646                     $errorcount[$post->id]++;
647                 } else if ($mailresult === 'emailstop') {
648                     // should not be reached anymore - see check above
649                     mtrace("Error: mod/forum/lib.php forum_cron(): received 'emailstop' while sending out mail for id $post->id to user $userto->id ($userto->email)");
650                 } else {
651                     $mailcount[$post->id]++;
653                 // Mark post as read if forum_usermarksread is set off
654                     if (!$CFG->forum_usermarksread) {
655                         $userto->markposts[$post->id] = $post->id;
656                     }
657                 }
659                 mtrace('post '.$post->id. ': '.$post->subject);
660             }
662             // mark processed posts as read
663             forum_tp_mark_posts_read($userto, $userto->markposts);
664         }
665     }
667     if ($posts) {
668         foreach ($posts as $post) {
669             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
670             if ($errorcount[$post->id]) {
671                 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
672             }
673         }
674     }
676     // release some memory
677     unset($subscribedusers);
678     unset($mailcount);
679     unset($errorcount);
681     cron_setup_user();
683     $sitetimezone = $CFG->timezone;
685     // Now see if there are any digest mails waiting to be sent, and if we should send them
687     mtrace('Starting digest processing...');
689     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
691     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
692         set_config('digestmailtimelast', 0);
693     }
695     $timenow = time();
696     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
698     // Delete any really old ones (normally there shouldn't be any)
699     $weekago = $timenow - (7 * 24 * 3600);
700     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
701     mtrace ('Cleaned old digest records');
703     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
705         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
707         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
709         if ($digestposts_rs->valid()) {
711             // We have work to do
712             $usermailcount = 0;
714             //caches - reuse the those filled before too
715             $discussionposts = array();
716             $userdiscussions = array();
718             foreach ($digestposts_rs as $digestpost) {
719                 if (!isset($users[$digestpost->userid])) {
720                     if ($user = $DB->get_record('user', array('id' => $digestpost->userid))) {
721                         $users[$digestpost->userid] = $user;
722                     } else {
723                         continue;
724                     }
725                 }
726                 $postuser = $users[$digestpost->userid];
727                 if ($postuser->emailstop) {
728                     if (!empty($CFG->forum_logblocked)) {
729                         add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
730                     }
731                     continue;
732                 }
734                 if (!isset($posts[$digestpost->postid])) {
735                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
736                         $posts[$digestpost->postid] = $post;
737                     } else {
738                         continue;
739                     }
740                 }
741                 $discussionid = $digestpost->discussionid;
742                 if (!isset($discussions[$discussionid])) {
743                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
744                         $discussions[$discussionid] = $discussion;
745                     } else {
746                         continue;
747                     }
748                 }
749                 $forumid = $discussions[$discussionid]->forum;
750                 if (!isset($forums[$forumid])) {
751                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
752                         $forums[$forumid] = $forum;
753                     } else {
754                         continue;
755                     }
756                 }
758                 $courseid = $forums[$forumid]->course;
759                 if (!isset($courses[$courseid])) {
760                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
761                         $courses[$courseid] = $course;
762                     } else {
763                         continue;
764                     }
765                 }
767                 if (!isset($coursemodules[$forumid])) {
768                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
769                         $coursemodules[$forumid] = $cm;
770                     } else {
771                         continue;
772                     }
773                 }
774                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
775                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
776             }
777             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
779             // Data collected, start sending out emails to each user
780             foreach ($userdiscussions as $userid => $thesediscussions) {
782                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
784                 cron_setup_user();
786                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
788                 // First of all delete all the queue entries for this user
789                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
790                 $userto = $users[$userid];
792                 // Override the language and timezone of the "current" user, so that
793                 // mail is customised for the receiver.
794                 cron_setup_user($userto);
796                 // init caches
797                 $userto->viewfullnames = array();
798                 $userto->canpost       = array();
799                 $userto->markposts     = array();
801                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
803                 $headerdata = new stdClass();
804                 $headerdata->sitename = format_string($site->fullname, true);
805                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
807                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
808                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
810                 $posthtml = "<head>";
811 /*                foreach ($CFG->stylesheets as $stylesheet) {
812                     //TODO: MDL-21120
813                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
814                 }*/
815                 $posthtml .= "</head>\n<body id=\"email\">\n";
816                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
818                 foreach ($thesediscussions as $discussionid) {
820                     @set_time_limit(120);   // to be reset for each post
822                     $discussion = $discussions[$discussionid];
823                     $forum      = $forums[$discussion->forum];
824                     $course     = $courses[$forum->course];
825                     $cm         = $coursemodules[$forum->id];
827                     //override language
828                     cron_setup_user($userto, $course);
830                     // Fill caches
831                     if (!isset($userto->viewfullnames[$forum->id])) {
832                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
833                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
834                     }
835                     if (!isset($userto->canpost[$discussion->id])) {
836                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
837                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
838                     }
840                     $strforums      = get_string('forums', 'forum');
841                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
842                     $canreply       = $userto->canpost[$discussion->id];
844                     $posttext .= "\n \n";
845                     $posttext .= '=====================================================================';
846                     $posttext .= "\n \n";
847                     $posttext .= "$course->shortname -> $strforums -> ".format_string($forum->name,true);
848                     if ($discussion->name != $forum->name) {
849                         $posttext  .= " -> ".format_string($discussion->name,true);
850                     }
851                     $posttext .= "\n";
853                     $posthtml .= "<p><font face=\"sans-serif\">".
854                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> -> ".
855                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
856                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
857                     if ($discussion->name == $forum->name) {
858                         $posthtml .= "</font></p>";
859                     } else {
860                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
861                     }
862                     $posthtml .= '<p>';
864                     $postsarray = $discussionposts[$discussionid];
865                     sort($postsarray);
867                     foreach ($postsarray as $postid) {
868                         $post = $posts[$postid];
870                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
871                             $userfrom = $users[$post->userid];
872                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
873                             $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
874                         } else {
875                             mtrace('Could not find user '.$post->userid);
876                             continue;
877                         }
879                         if (!isset($userfrom->groups[$forum->id])) {
880                             if (!isset($userfrom->groups)) {
881                                 $userfrom->groups = array();
882                                 $users[$userfrom->id]->groups = array();
883                             }
884                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
885                             $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
886                         }
888                         $userfrom->customheaders = array ("Precedence: Bulk");
890                         if ($userto->maildigest == 2) {
891                             // Subjects only
892                             $by = new stdClass();
893                             $by->name = fullname($userfrom);
894                             $by->date = userdate($post->modified);
895                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
896                             $posttext .= "\n---------------------------------------------------------------------";
898                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
899                             $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>';
901                         } else {
902                             // The full treatment
903                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
904                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
906                         // Create an array of postid's for this user to mark as read.
907                             if (!$CFG->forum_usermarksread) {
908                                 $userto->markposts[$post->id] = $post->id;
909                             }
910                         }
911                     }
912                     if ($canunsubscribe) {
913                         $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>";
914                     } else {
915                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
916                     }
917                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
918                 }
919                 $posthtml .= '</body>';
921                 if (empty($userto->mailformat) || $userto->mailformat != 1) {
922                     // This user DOESN'T want to receive HTML
923                     $posthtml = '';
924                 }
926                 $attachment = $attachname='';
927                 $usetrueaddress = true;
928                 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname, $usetrueaddress, $CFG->forum_replytouser);
930                 if (!$mailresult) {
931                     mtrace("ERROR!");
932                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
933                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
934                 } else if ($mailresult === 'emailstop') {
935                     // should not happen anymore - see check above
936                 } else {
937                     mtrace("success.");
938                     $usermailcount++;
940                     // Mark post as read if forum_usermarksread is set off
941                     forum_tp_mark_posts_read($userto, $userto->markposts);
942                 }
943             }
944         }
945     /// We have finishied all digest emails, update $CFG->digestmailtimelast
946         set_config('digestmailtimelast', $timenow);
947     }
949     cron_setup_user();
951     if (!empty($usermailcount)) {
952         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
953     }
955     if (!empty($CFG->forum_lastreadclean)) {
956         $timenow = time();
957         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
958             set_config('forum_lastreadclean', $timenow);
959             mtrace('Removing old forum read tracking info...');
960             forum_tp_clean_read_records();
961         }
962     } else {
963         set_config('forum_lastreadclean', time());
964     }
967     return true;
970 /**
971  * Builds and returns the body of the email notification in plain text.
972  *
973  * @global object
974  * @global object
975  * @uses CONTEXT_MODULE
976  * @param object $course
977  * @param object $cm
978  * @param object $forum
979  * @param object $discussion
980  * @param object $post
981  * @param object $userfrom
982  * @param object $userto
983  * @param boolean $bare
984  * @return string The email body in plain text format.
985  */
986 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
987     global $CFG, $USER;
989     if (!isset($userto->viewfullnames[$forum->id])) {
990         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
991         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
992     } else {
993         $viewfullnames = $userto->viewfullnames[$forum->id];
994     }
996     if (!isset($userto->canpost[$discussion->id])) {
997         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
998         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
999     } else {
1000         $canreply = $userto->canpost[$discussion->id];
1001     }
1003     $by = New stdClass;
1004     $by->name = fullname($userfrom, $viewfullnames);
1005     $by->date = userdate($post->modified, "", $userto->timezone);
1007     $strbynameondate = get_string('bynameondate', 'forum', $by);
1009     $strforums = get_string('forums', 'forum');
1011     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1013     $posttext = '';
1015     if (!$bare) {
1016         $posttext  = "$course->shortname -> $strforums -> ".format_string($forum->name,true);
1018         if ($discussion->name != $forum->name) {
1019             $posttext  .= " -> ".format_string($discussion->name,true);
1020         }
1021     }
1023     $posttext .= "\n---------------------------------------------------------------------\n";
1024     $posttext .= format_string($post->subject,true);
1025     if ($bare) {
1026         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1027     }
1028     $posttext .= "\n".$strbynameondate."\n";
1029     $posttext .= "---------------------------------------------------------------------\n";
1030     $posttext .= format_text_email($post->message, $post->messageformat);
1031     $posttext .= "\n\n";
1032     $posttext .= forum_print_attachments($post, $cm, "text");
1034     if (!$bare && $canreply) {
1035         $posttext .= "---------------------------------------------------------------------\n";
1036         $posttext .= get_string("postmailinfo", "forum", $course->shortname)."\n";
1037         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1038     }
1039     if (!$bare && $canunsubscribe) {
1040         $posttext .= "\n---------------------------------------------------------------------\n";
1041         $posttext .= get_string("unsubscribe", "forum");
1042         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1043     }
1045     return $posttext;
1048 /**
1049  * Builds and returns the body of the email notification in html format.
1050  *
1051  * @global object
1052  * @param object $course
1053  * @param object $cm
1054  * @param object $forum
1055  * @param object $discussion
1056  * @param object $post
1057  * @param object $userfrom
1058  * @param object $userto
1059  * @return string The email text in HTML format
1060  */
1061 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1062     global $CFG;
1064     if ($userto->mailformat != 1) {  // Needs to be HTML
1065         return '';
1066     }
1068     if (!isset($userto->canpost[$discussion->id])) {
1069         $canreply = forum_user_can_post($forum, $discussion, $userto);
1070     } else {
1071         $canreply = $userto->canpost[$discussion->id];
1072     }
1074     $strforums = get_string('forums', 'forum');
1075     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1077     $posthtml = '<head>';
1078 /*    foreach ($CFG->stylesheets as $stylesheet) {
1079         //TODO: MDL-21120
1080         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1081     }*/
1082     $posthtml .= '</head>';
1083     $posthtml .= "\n<body id=\"email\">\n\n";
1085     $posthtml .= '<div class="navbar">'.
1086     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$course->shortname.'</a> &raquo; '.
1087     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1088     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1089     if ($discussion->name == $forum->name) {
1090         $posthtml .= '</div>';
1091     } else {
1092         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1093                      format_string($discussion->name,true).'</a></div>';
1094     }
1095     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1097     if ($canunsubscribe) {
1098         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1099                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1100                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1101     }
1103     $posthtml .= '</body>';
1105     return $posthtml;
1109 /**
1110  *
1111  * @param object $course
1112  * @param object $user
1113  * @param object $mod TODO this is not used in this function, refactor
1114  * @param object $forum
1115  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1116  */
1117 function forum_user_outline($course, $user, $mod, $forum) {
1118     global $CFG;
1119     require_once("$CFG->libdir/gradelib.php");
1120     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1121     if (empty($grades->items[0]->grades)) {
1122         $grade = false;
1123     } else {
1124         $grade = reset($grades->items[0]->grades);
1125     }
1127     $count = forum_count_user_posts($forum->id, $user->id);
1129     if ($count && $count->postcount > 0) {
1130         $result = new stdClass();
1131         $result->info = get_string("numposts", "forum", $count->postcount);
1132         $result->time = $count->lastpost;
1133         if ($grade) {
1134             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1135         }
1136         return $result;
1137     } else if ($grade) {
1138         $result = new stdClass();
1139         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1140         $result->time = $grade->dategraded;
1141         return $result;
1142     }
1143     return NULL;
1147 /**
1148  * @global object
1149  * @global object
1150  * @param object $coure
1151  * @param object $user
1152  * @param object $mod
1153  * @param object $forum
1154  */
1155 function forum_user_complete($course, $user, $mod, $forum) {
1156     global $CFG,$USER, $OUTPUT;
1157     require_once("$CFG->libdir/gradelib.php");
1159     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1160     if (!empty($grades->items[0]->grades)) {
1161         $grade = reset($grades->items[0]->grades);
1162         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1163         if ($grade->str_feedback) {
1164             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1165         }
1166     }
1168     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1170         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1171             print_error('invalidcoursemodule');
1172         }
1173         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1175         foreach ($posts as $post) {
1176             if (!isset($discussions[$post->discussion])) {
1177                 continue;
1178             }
1179             $discussion = $discussions[$post->discussion];
1181             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1182         }
1183     } else {
1184         echo "<p>".get_string("noposts", "forum")."</p>";
1185     }
1193 /**
1194  * @global object
1195  * @global object
1196  * @global object
1197  * @param array $courses
1198  * @param array $htmlarray
1199  */
1200 function forum_print_overview($courses,&$htmlarray) {
1201     global $USER, $CFG, $DB, $SESSION;
1203     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1204         return array();
1205     }
1207     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1208         return;
1209     }
1212     // get all forum logs in ONE query (much better!)
1213     $params = array();
1214     $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {log} l "
1215         ." JOIN {course_modules} cm ON cm.id = cmid "
1216         ." WHERE (";
1217     foreach ($courses as $course) {
1218         $sql .= '(l.course = ? AND l.time > ?) OR ';
1219         $params[] = $course->id;
1220         $params[] = $course->lastaccess;
1221     }
1222     $sql = substr($sql,0,-3); // take off the last OR
1224     $sql .= ") AND l.module = 'forum' AND action = 'add post' "
1225         ." AND userid != ? GROUP BY cmid,l.course,instance";
1227     $params[] = $USER->id;
1229     if (!$new = $DB->get_records_sql($sql, $params)) {
1230         $new = array(); // avoid warnings
1231     }
1233     // also get all forum tracking stuff ONCE.
1234     $trackingforums = array();
1235     foreach ($forums as $forum) {
1236         if (forum_tp_can_track_forums($forum)) {
1237             $trackingforums[$forum->id] = $forum;
1238         }
1239     }
1241     if (count($trackingforums) > 0) {
1242         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1243         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1244             ' FROM {forum_posts} p '.
1245             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1246             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1247         $params = array($USER->id);
1249         foreach ($trackingforums as $track) {
1250             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1251             $params[] = $track->id;
1252             if (isset($SESSION->currentgroup[$track->course])) {
1253                 $groupid =  $SESSION->currentgroup[$track->course];
1254             } else {
1255                 $groupid = groups_get_all_groups($track->course, $USER->id);
1256                 if (is_array($groupid)) {
1257                     $groupid = array_shift(array_keys($groupid));
1258                     $SESSION->currentgroup[$track->course] = $groupid;
1259                 } else {
1260                     $groupid = 0;
1261                 }
1262             }
1263             $params[] = $groupid;
1264         }
1265         $sql = substr($sql,0,-3); // take off the last OR
1266         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1267         $params[] = $cutoffdate;
1269         if (!$unread = $DB->get_records_sql($sql, $params)) {
1270             $unread = array();
1271         }
1272     } else {
1273         $unread = array();
1274     }
1276     if (empty($unread) and empty($new)) {
1277         return;
1278     }
1280     $strforum = get_string('modulename','forum');
1281     $strnumunread = get_string('overviewnumunread','forum');
1282     $strnumpostssince = get_string('overviewnumpostssince','forum');
1284     foreach ($forums as $forum) {
1285         $str = '';
1286         $count = 0;
1287         $thisunread = 0;
1288         $showunread = false;
1289         // either we have something from logs, or trackposts, or nothing.
1290         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1291             $count = $new[$forum->id]->count;
1292         }
1293         if (array_key_exists($forum->id,$unread)) {
1294             $thisunread = $unread[$forum->id]->count;
1295             $showunread = true;
1296         }
1297         if ($count > 0 || $thisunread > 0) {
1298             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1299                 $forum->name.'</a></div>';
1300             $str .= '<div class="info">';
1301             $str .= $count.' '.$strnumpostssince;
1302             if (!empty($showunread)) {
1303                 $str .= '<br />'.$thisunread .' '.$strnumunread;
1304             }
1305             $str .= '</div></div>';
1306         }
1307         if (!empty($str)) {
1308             if (!array_key_exists($forum->course,$htmlarray)) {
1309                 $htmlarray[$forum->course] = array();
1310             }
1311             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1312                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1313             }
1314             $htmlarray[$forum->course]['forum'] .= $str;
1315         }
1316     }
1319 /**
1320  * Given a course and a date, prints a summary of all the new
1321  * messages posted in the course since that date
1322  *
1323  * @global object
1324  * @global object
1325  * @global object
1326  * @uses CONTEXT_MODULE
1327  * @uses VISIBLEGROUPS
1328  * @param object $course
1329  * @param bool $viewfullnames capability
1330  * @param int $timestart
1331  * @return bool success
1332  */
1333 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1334     global $CFG, $USER, $DB, $OUTPUT;
1336     // do not use log table if possible, it may be huge and is expensive to join with other tables
1338     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1339                                               d.timestart, d.timeend, d.userid AS duserid,
1340                                               u.firstname, u.lastname, u.email, u.picture
1341                                          FROM {forum_posts} p
1342                                               JOIN {forum_discussions} d ON d.id = p.discussion
1343                                               JOIN {forum} f             ON f.id = d.forum
1344                                               JOIN {user} u              ON u.id = p.userid
1345                                         WHERE p.created > ? AND f.course = ?
1346                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1347          return false;
1348     }
1350     $modinfo =& get_fast_modinfo($course);
1352     $groupmodes = array();
1353     $cms    = array();
1355     $strftimerecent = get_string('strftimerecent');
1357     $printposts = array();
1358     foreach ($posts as $post) {
1359         if (!isset($modinfo->instances['forum'][$post->forum])) {
1360             // not visible
1361             continue;
1362         }
1363         $cm = $modinfo->instances['forum'][$post->forum];
1364         if (!$cm->uservisible) {
1365             continue;
1366         }
1367         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1369         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1370             continue;
1371         }
1373         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1374           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1375             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1376                 continue;
1377             }
1378         }
1380         $groupmode = groups_get_activity_groupmode($cm, $course);
1382         if ($groupmode) {
1383             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1384                 // oki (Open discussions have groupid -1)
1385             } else {
1386                 // separate mode
1387                 if (isguestuser()) {
1388                     // shortcut
1389                     continue;
1390                 }
1392                 if (is_null($modinfo->groups)) {
1393                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1394                 }
1396                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1397                     continue;
1398                 }
1399             }
1400         }
1402         $printposts[] = $post;
1403     }
1404     unset($posts);
1406     if (!$printposts) {
1407         return false;
1408     }
1410     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1411     echo "\n<ul class='unlist'>\n";
1413     foreach ($printposts as $post) {
1414         $subjectclass = empty($post->parent) ? ' bold' : '';
1416         echo '<li><div class="head">'.
1417                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1418                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1419              '</div>';
1420         echo '<div class="info'.$subjectclass.'">';
1421         if (empty($post->parent)) {
1422             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1423         } else {
1424             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1425         }
1426         $post->subject = break_up_long_words(format_string($post->subject, true));
1427         echo $post->subject;
1428         echo "</a>\"</div></li>\n";
1429     }
1431     echo "</ul>\n";
1433     return true;
1436 /**
1437  * Return grade for given user or all users.
1438  *
1439  * @global object
1440  * @global object
1441  * @param object $forum
1442  * @param int $userid optional user id, 0 means all users
1443  * @return array array of grades, false if none
1444  */
1445 function forum_get_user_grades($forum, $userid=0) {
1446     global $CFG;
1448     require_once($CFG->dirroot.'/rating/lib.php');
1449     $rm = new rating_manager();
1451     $ratingoptions = new stdclass();
1453     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1454     $ratingoptions->modulename = 'forum';
1455     $ratingoptions->moduleid   = $forum->id;
1456     //$ratingoptions->cmidnumber = $forum->cmidnumber;
1458     $ratingoptions->userid = $userid;
1459     $ratingoptions->aggregationmethod = $forum->assessed;
1460     $ratingoptions->scaleid = $forum->scale;
1461     $ratingoptions->itemtable = 'forum_posts';
1462     $ratingoptions->itemtableusercolumn = 'userid';
1464     return $rm->get_user_grades($ratingoptions);
1467 /**
1468  * Update activity grades
1469  *
1470  * @global object
1471  * @global object
1472  * @param object $forum
1473  * @param int $userid specific user only, 0 means all
1474  * @param boolean $nullifnone return null if grade does not exist
1475  * @return void
1476  */
1477 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1478     global $CFG, $DB;
1479     require_once($CFG->libdir.'/gradelib.php');
1481     if (!$forum->assessed) {
1482         forum_grade_item_update($forum);
1484     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1485         forum_grade_item_update($forum, $grades);
1487     } else if ($userid and $nullifnone) {
1488         $grade = new stdClass();
1489         $grade->userid   = $userid;
1490         $grade->rawgrade = NULL;
1491         forum_grade_item_update($forum, $grade);
1493     } else {
1494         forum_grade_item_update($forum);
1495     }
1498 /**
1499  * Update all grades in gradebook.
1500  * @global object
1501  */
1502 function forum_upgrade_grades() {
1503     global $DB;
1505     $sql = "SELECT COUNT('x')
1506               FROM {forum} f, {course_modules} cm, {modules} m
1507              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1508     $count = $DB->count_records_sql($sql);
1510     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1511               FROM {forum} f, {course_modules} cm, {modules} m
1512              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1513     if ($rs = $DB->get_recordset_sql($sql)) {
1514         $pbar = new progress_bar('forumupgradegrades', 500, true);
1515         $i=0;
1516         foreach ($rs as $forum) {
1517             $i++;
1518             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1519             forum_update_grades($forum, 0, false);
1520             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1521         }
1522         $rs->close();
1523     }
1526 /**
1527  * Create/update grade item for given forum
1528  *
1529  * @global object
1530  * @uses GRADE_TYPE_NONE
1531  * @uses GRADE_TYPE_VALUE
1532  * @uses GRADE_TYPE_SCALE
1533  * @param object $forum object with extra cmidnumber
1534  * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
1535  * @return int 0 if ok
1536  */
1537 function forum_grade_item_update($forum, $grades=NULL) {
1538     global $CFG;
1539     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1540         require_once($CFG->libdir.'/gradelib.php');
1541     }
1543     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1545     if (!$forum->assessed or $forum->scale == 0) {
1546         $params['gradetype'] = GRADE_TYPE_NONE;
1548     } else if ($forum->scale > 0) {
1549         $params['gradetype'] = GRADE_TYPE_VALUE;
1550         $params['grademax']  = $forum->scale;
1551         $params['grademin']  = 0;
1553     } else if ($forum->scale < 0) {
1554         $params['gradetype'] = GRADE_TYPE_SCALE;
1555         $params['scaleid']   = -$forum->scale;
1556     }
1558     if ($grades  === 'reset') {
1559         $params['reset'] = true;
1560         $grades = NULL;
1561     }
1563     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1566 /**
1567  * Delete grade item for given forum
1568  *
1569  * @global object
1570  * @param object $forum object
1571  * @return object grade_item
1572  */
1573 function forum_grade_item_delete($forum) {
1574     global $CFG;
1575     require_once($CFG->libdir.'/gradelib.php');
1577     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1581 /**
1582  * Returns the users with data in one forum
1583  * (users with records in forum_subscriptions, forum_posts, students)
1584  *
1585  * @global object
1586  * @global object
1587  * @param int $forumid
1588  * @return mixed array or false if none
1589  */
1590 function forum_get_participants($forumid) {
1592     global $CFG, $DB;
1594     //Get students from forum_subscriptions
1595     $st_subscriptions = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1596                                          FROM {user} u,
1597                                               {forum_subscriptions} s
1598                                          WHERE s.forum = ? AND
1599                                                u.id = s.userid", array($forumid));
1600     //Get students from forum_posts
1601     $st_posts = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1602                                  FROM {user} u,
1603                                       {forum_discussions} d,
1604                                       {forum_posts} p
1605                                  WHERE d.forum = ? AND
1606                                        p.discussion = d.id AND
1607                                        u.id = p.userid", array($forumid));
1609     //Get students from the ratings table
1610     $st_ratings = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1611                                    FROM {user} u,
1612                                         {forum_discussions} d,
1613                                         {forum_posts} p,
1614                                         {ratings} r
1615                                    WHERE d.forum = ? AND
1616                                          p.discussion = d.id AND
1617                                          r.post = p.id AND
1618                                          u.id = r.userid", array($forumid));
1620     //Add st_posts to st_subscriptions
1621     if ($st_posts) {
1622         foreach ($st_posts as $st_post) {
1623             $st_subscriptions[$st_post->id] = $st_post;
1624         }
1625     }
1626     //Add st_ratings to st_subscriptions
1627     if ($st_ratings) {
1628         foreach ($st_ratings as $st_rating) {
1629             $st_subscriptions[$st_rating->id] = $st_rating;
1630         }
1631     }
1632     //Return st_subscriptions array (it contains an array of unique users)
1633     return ($st_subscriptions);
1636 /**
1637  * This function returns if a scale is being used by one forum
1638  *
1639  * @global object
1640  * @param int $forumid
1641  * @param int $scaleid negative number
1642  * @return bool
1643  */
1644 function forum_scale_used ($forumid,$scaleid) {
1645     global $DB;
1646     $return = false;
1648     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1650     if (!empty($rec) && !empty($scaleid)) {
1651         $return = true;
1652     }
1654     return $return;
1657 /**
1658  * Checks if scale is being used by any instance of forum
1659  *
1660  * This is used to find out if scale used anywhere
1661  *
1662  * @global object
1663  * @param $scaleid int
1664  * @return boolean True if the scale is used by any forum
1665  */
1666 function forum_scale_used_anywhere($scaleid) {
1667     global $DB;
1668     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1669         return true;
1670     } else {
1671         return false;
1672     }
1675 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1677 /**
1678  * Gets a post with all info ready for forum_print_post
1679  * Most of these joins are just to get the forum id
1680  *
1681  * @global object
1682  * @global object
1683  * @param int $postid
1684  * @return mixed array of posts or false
1685  */
1686 function forum_get_post_full($postid) {
1687     global $CFG, $DB;
1689     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1690                              FROM {forum_posts} p
1691                                   JOIN {forum_discussions} d ON p.discussion = d.id
1692                                   LEFT JOIN {user} u ON p.userid = u.id
1693                             WHERE p.id = ?", array($postid));
1696 /**
1697  * Gets posts with all info ready for forum_print_post
1698  * We pass forumid in because we always know it so no need to make a
1699  * complicated join to find it out.
1700  *
1701  * @global object
1702  * @global object
1703  * @return mixed array of posts or false
1704  */
1705 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1706     global $CFG, $DB;
1708     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1709                               FROM {forum_posts} p
1710                          LEFT JOIN {user} u ON p.userid = u.id
1711                              WHERE p.discussion = ?
1712                                AND p.parent > 0 $sort", array($discussion));
1715 /**
1716  * Gets all posts in discussion including top parent.
1717  *
1718  * @global object
1719  * @global object
1720  * @global object
1721  * @param int $discussionid
1722  * @param string $sort
1723  * @param bool $tracking does user track the forum?
1724  * @return array of posts
1725  */
1726 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1727     global $CFG, $DB, $USER;
1729     $tr_sel  = "";
1730     $tr_join = "";
1731     $params = array();
1733     if ($tracking) {
1734         $now = time();
1735         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1736         $tr_sel  = ", fr.id AS postread";
1737         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1738         $params[] = $USER->id;
1739     }
1741     $params[] = $discussionid;
1742     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1743                                      FROM {forum_posts} p
1744                                           LEFT JOIN {user} u ON p.userid = u.id
1745                                           $tr_join
1746                                     WHERE p.discussion = ?
1747                                  ORDER BY $sort", $params)) {
1748         return array();
1749     }
1751     foreach ($posts as $pid=>$p) {
1752         if ($tracking) {
1753             if (forum_tp_is_post_old($p)) {
1754                  $posts[$pid]->postread = true;
1755             }
1756         }
1757         if (!$p->parent) {
1758             continue;
1759         }
1760         if (!isset($posts[$p->parent])) {
1761             continue; // parent does not exist??
1762         }
1763         if (!isset($posts[$p->parent]->children)) {
1764             $posts[$p->parent]->children = array();
1765         }
1766         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1767     }
1769     return $posts;
1772 /**
1773  * Gets posts with all info ready for forum_print_post
1774  * We pass forumid in because we always know it so no need to make a
1775  * complicated join to find it out.
1776  *
1777  * @global object
1778  * @global object
1779  * @param int $parent
1780  * @param int $forumid
1781  * @return array
1782  */
1783 function forum_get_child_posts($parent, $forumid) {
1784     global $CFG, $DB;
1786     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1787                               FROM {forum_posts} p
1788                          LEFT JOIN {user} u ON p.userid = u.id
1789                              WHERE p.parent = ?
1790                           ORDER BY p.created ASC", array($parent));
1793 /**
1794  * An array of forum objects that the user is allowed to read/search through.
1795  *
1796  * @global object
1797  * @global object
1798  * @global object
1799  * @param int $userid
1800  * @param int $courseid if 0, we look for forums throughout the whole site.
1801  * @return array of forum objects, or false if no matches
1802  *         Forum objects have the following attributes:
1803  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1804  *         viewhiddentimedposts
1805  */
1806 function forum_get_readable_forums($userid, $courseid=0) {
1808     global $CFG, $DB, $USER;
1809     require_once($CFG->dirroot.'/course/lib.php');
1811     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1812         print_error('notinstalled', 'forum');
1813     }
1815     if ($courseid) {
1816         $courses = $DB->get_records('course', array('id' => $courseid));
1817     } else {
1818         // If no course is specified, then the user can see SITE + his courses.
1819         $courses1 = $DB->get_records('course', array('id' => SITEID));
1820         $courses2 = enrol_get_users_courses($userid, true);
1821         $courses = array_merge($courses1, $courses2);
1822     }
1823     if (!$courses) {
1824         return array();
1825     }
1827     $readableforums = array();
1829     foreach ($courses as $course) {
1831         $modinfo =& get_fast_modinfo($course);
1832         if (is_null($modinfo->groups)) {
1833             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1834         }
1836         if (empty($modinfo->instances['forum'])) {
1837             // hmm, no forums?
1838             continue;
1839         }
1841         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1843         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1844             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1845                 continue;
1846             }
1847             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1848             $forum = $courseforums[$forumid];
1850             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1851                 continue;
1852             }
1854          /// group access
1855             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1856                 if (is_null($modinfo->groups)) {
1857                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1858                 }
1859                 if (isset($modinfo->groups[$cm->groupingid])) {
1860                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1861                     $forum->onlygroups[] = -1;
1862                 } else {
1863                     $forum->onlygroups = array(-1);
1864                 }
1865             }
1867         /// hidden timed discussions
1868             $forum->viewhiddentimedposts = true;
1869             if (!empty($CFG->forum_enabletimedposts)) {
1870                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1871                     $forum->viewhiddentimedposts = false;
1872                 }
1873             }
1875         /// qanda access
1876             if ($forum->type == 'qanda'
1877                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1879                 // We need to check whether the user has posted in the qanda forum.
1880                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1881                                                     // the user is allowed to see in this forum.
1882                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1883                     foreach ($discussionspostedin as $d) {
1884                         $forum->onlydiscussions[] = $d->id;
1885                     }
1886                 }
1887             }
1889             $readableforums[$forum->id] = $forum;
1890         }
1892         unset($modinfo);
1894     } // End foreach $courses
1896     return $readableforums;
1899 /**
1900  * Returns a list of posts found using an array of search terms.
1901  *
1902  * @global object
1903  * @global object
1904  * @global object
1905  * @param array $searchterms array of search terms, e.g. word +word -word
1906  * @param int $courseid if 0, we search through the whole site
1907  * @param int $limitfrom
1908  * @param int $limitnum
1909  * @param int &$totalcount
1910  * @param string $extrasql
1911  * @return array|bool Array of posts found or false
1912  */
1913 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1914                             &$totalcount, $extrasql='') {
1915     global $CFG, $DB, $USER;
1916     require_once($CFG->libdir.'/searchlib.php');
1918     $forums = forum_get_readable_forums($USER->id, $courseid);
1920     if (count($forums) == 0) {
1921         $totalcount = 0;
1922         return false;
1923     }
1925     $now = round(time(), -2); // db friendly
1927     $fullaccess = array();
1928     $where = array();
1929     $params = array();
1931     foreach ($forums as $forumid => $forum) {
1932         $select = array();
1934         if (!$forum->viewhiddentimedposts) {
1935             $select[] = "(d.userid = :userid OR (d.timestart < : AND (d.timeend = 0 OR d.timeend > :timeend)))";
1936             $params = array('userid'=>$USER->id, 'timestart'=>$now, 'timeend'=>$now);
1937         }
1939         $cm = get_coursemodule_from_instance('forum', $forumid);
1940         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1942         if ($forum->type == 'qanda'
1943             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1944             if (!empty($forum->onlydiscussions)) {
1945                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda0');
1946                 $params = array_merge($params, $discussionid_params);
1947                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
1948             } else {
1949                 $select[] = "p.parent = 0";
1950             }
1951         }
1953         if (!empty($forum->onlygroups)) {
1954             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps0');
1955             $params = array_merge($params, $groupid_params);
1956             $select[] = "d.groupid $groupid_sql";
1957         }
1959         if ($select) {
1960             $selects = implode(" AND ", $select);
1961             $where[] = "(d.forum = :forum AND $selects)";
1962             $params['forum'] = $forumid;
1963         } else {
1964             $fullaccess[] = $forumid;
1965         }
1966     }
1968     if ($fullaccess) {
1969         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula0');
1970         $params = array_merge($params, $fullid_params);
1971         $where[] = "(d.forum $fullid_sql)";
1972     }
1974     $selectdiscussion = "(".implode(" OR ", $where).")";
1976     $messagesearch = '';
1977     $searchstring = '';
1979     // Need to concat these back together for parser to work.
1980     foreach($searchterms as $searchterm){
1981         if ($searchstring != '') {
1982             $searchstring .= ' ';
1983         }
1984         $searchstring .= $searchterm;
1985     }
1987     // We need to allow quoted strings for the search. The quotes *should* be stripped
1988     // by the parser, but this should be examined carefully for security implications.
1989     $searchstring = str_replace("\\\"","\"",$searchstring);
1990     $parser = new search_parser();
1991     $lexer = new search_lexer($parser);
1993     if ($lexer->parse($searchstring)) {
1994         $parsearray = $parser->get_parsed_array();
1995     // Experimental feature under 1.8! MDL-8830
1996     // Use alternative text searches if defined
1997     // This feature only works under mysql until properly implemented for other DBs
1998     // Requires manual creation of text index for forum_posts before enabling it:
1999     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2000     // Experimental feature under 1.8! MDL-8830
2001         if (!empty($CFG->forum_usetextsearches)) {
2002             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2003                                                  'p.userid', 'u.id', 'u.firstname',
2004                                                  'u.lastname', 'p.modified', 'd.forum');
2005         } else {
2006             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2007                                                  'p.userid', 'u.id', 'u.firstname',
2008                                                  'u.lastname', 'p.modified', 'd.forum');
2009         }
2010         $params = array_merge($params, $msparams);
2011     }
2013     $fromsql = "{forum_posts} p,
2014                   {forum_discussions} d,
2015                   {user} u";
2017     $selectsql = " $messagesearch
2018                AND p.discussion = d.id
2019                AND p.userid = u.id
2020                AND $selectdiscussion
2021                    $extrasql";
2023     $countsql = "SELECT COUNT(*)
2024                    FROM $fromsql
2025                   WHERE $selectsql";
2027     $searchsql = "SELECT p.*,
2028                          d.forum,
2029                          u.firstname,
2030                          u.lastname,
2031                          u.email,
2032                          u.picture,
2033                          u.imagealt,
2034                          u.email
2035                     FROM $fromsql
2036                    WHERE $selectsql
2037                 ORDER BY p.modified DESC";
2039     $totalcount = $DB->count_records_sql($countsql, $params);
2041     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2044 /**
2045  * Returns a list of ratings for a particular post - sorted.
2046  *
2047  * @global object
2048  * @global object
2049  * @param int $postid
2050  * @param string $sort
2051  * @return array Array of ratings or false
2052  */
2053 function forum_get_ratings($context, $postid, $sort="u.firstname ASC") {
2054     global $PAGE;
2056     $options = new stdclass();
2057     $options->context = $PAGE->context;
2058     $options->itemid = $postid;
2059     $options->sort = "ORDER BY $sort";
2061     $rm = new rating_manager();
2062     $rm->get_all_ratings_for_item($options);
2065 /**
2066  * Returns a list of all new posts that have not been mailed yet
2067  *
2068  * @global object
2069  * @global object
2070  * @param int $starttime posts created after this time
2071  * @param int $endtime posts created before this
2072  * @param int $now used for timed discussions only
2073  * @return array
2074  */
2075 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2076     global $CFG, $DB;
2078     $params = array($starttime, $endtime);
2079     if (!empty($CFG->forum_enabletimedposts)) {
2080         if (empty($now)) {
2081             $now = time();
2082         }
2083         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2084         $params[] = $now;
2085         $params[] = $now;
2086     } else {
2087         $timedsql = "";
2088     }
2090     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2091                               FROM {forum_posts} p
2092                                    JOIN {forum_discussions} d ON d.id = p.discussion
2093                              WHERE p.mailed = 0
2094                                    AND p.created >= ?
2095                                    AND (p.created < ? OR p.mailnow = 1)
2096                                    $timedsql
2097                           ORDER BY p.modified ASC", $params);
2100 /**
2101  * Marks posts before a certain time as being mailed already
2102  *
2103  * @global object
2104  * @global object
2105  * @param int $endtime
2106  * @param int $now Defaults to time()
2107  * @return bool
2108  */
2109 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2110     global $CFG, $DB;
2111     if (empty($now)) {
2112         $now = time();
2113     }
2115     if (empty($CFG->forum_enabletimedposts)) {
2116         return $DB->execute("UPDATE {forum_posts}
2117                                SET mailed = '1'
2118                              WHERE (created < ? OR mailnow = 1)
2119                                    AND mailed = 0", array($endtime));
2121     } else {
2122         return $DB->execute("UPDATE {forum_posts}
2123                                SET mailed = '1'
2124                              WHERE discussion NOT IN (SELECT d.id
2125                                                         FROM {forum_discussions} d
2126                                                        WHERE d.timestart > ?)
2127                                    AND (created < ? OR mailnow = 1)
2128                                    AND mailed = 0", array($now, $endtime));
2129     }
2132 /**
2133  * Get all the posts for a user in a forum suitable for forum_print_post
2134  *
2135  * @global object
2136  * @global object
2137  * @uses CONTEXT_MODULE
2138  * @return array
2139  */
2140 function forum_get_user_posts($forumid, $userid) {
2141     global $CFG, $DB;
2143     $timedsql = "";
2144     $params = array($forumid, $userid);
2146     if (!empty($CFG->forum_enabletimedposts)) {
2147         $cm = get_coursemodule_from_instance('forum', $forumid);
2148         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2149             $now = time();
2150             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2151             $params[] = $now;
2152             $params[] = $now;
2153         }
2154     }
2156     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2157                               FROM {forum} f
2158                                    JOIN {forum_discussions} d ON d.forum = f.id
2159                                    JOIN {forum_posts} p       ON p.discussion = d.id
2160                                    JOIN {user} u              ON u.id = p.userid
2161                              WHERE f.id = ?
2162                                    AND p.userid = ?
2163                                    $timedsql
2164                           ORDER BY p.modified ASC", $params);
2167 /**
2168  * Get all the discussions user participated in
2169  *
2170  * @global object
2171  * @global object
2172  * @uses CONTEXT_MODULE
2173  * @param int $forumid
2174  * @param int $userid
2175  * @return array Array or false
2176  */
2177 function forum_get_user_involved_discussions($forumid, $userid) {
2178     global $CFG, $DB;
2180     $timedsql = "";
2181     $params = array($forumid, $userid);
2182     if (!empty($CFG->forum_enabletimedposts)) {
2183         $cm = get_coursemodule_from_instance('forum', $forumid);
2184         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2185             $now = time();
2186             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2187             $params[] = $now;
2188             $params[] = $now;
2189         }
2190     }
2192     return $DB->get_records_sql("SELECT DISTINCT d.*
2193                               FROM {forum} f
2194                                    JOIN {forum_discussions} d ON d.forum = f.id
2195                                    JOIN {forum_posts} p       ON p.discussion = d.id
2196                              WHERE f.id = ?
2197                                    AND p.userid = ?
2198                                    $timedsql", $params);
2201 /**
2202  * Get all the posts for a user in a forum suitable for forum_print_post
2203  *
2204  * @global object
2205  * @global object
2206  * @param int $forumid
2207  * @param int $userid
2208  * @return array of counts or false
2209  */
2210 function forum_count_user_posts($forumid, $userid) {
2211     global $CFG, $DB;
2213     $timedsql = "";
2214     $params = array($forumid, $userid);
2215     if (!empty($CFG->forum_enabletimedposts)) {
2216         $cm = get_coursemodule_from_instance('forum', $forumid);
2217         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2218             $now = time();
2219             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2220             $params[] = $now;
2221             $params[] = $now;
2222         }
2223     }
2225     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2226                              FROM {forum} f
2227                                   JOIN {forum_discussions} d ON d.forum = f.id
2228                                   JOIN {forum_posts} p       ON p.discussion = d.id
2229                                   JOIN {user} u              ON u.id = p.userid
2230                             WHERE f.id = ?
2231                                   AND p.userid = ?
2232                                   $timedsql", $params);
2235 /**
2236  * Given a log entry, return the forum post details for it.
2237  *
2238  * @global object
2239  * @global object
2240  * @param object $log
2241  * @return array|null
2242  */
2243 function forum_get_post_from_log($log) {
2244     global $CFG, $DB;
2246     if ($log->action == "add post") {
2248         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2249                                            u.firstname, u.lastname, u.email, u.picture
2250                                  FROM {forum_discussions} d,
2251                                       {forum_posts} p,
2252                                       {forum} f,
2253                                       {user} u
2254                                 WHERE p.id = ?
2255                                   AND d.id = p.discussion
2256                                   AND p.userid = u.id
2257                                   AND u.deleted <> '1'
2258                                   AND f.id = d.forum", array($log->info));
2261     } else if ($log->action == "add discussion") {
2263         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2264                                            u.firstname, u.lastname, u.email, u.picture
2265                                  FROM {forum_discussions} d,
2266                                       {forum_posts} p,
2267                                       {forum} f,
2268                                       {user} u
2269                                 WHERE d.id = ?
2270                                   AND d.firstpost = p.id
2271                                   AND p.userid = u.id
2272                                   AND u.deleted <> '1'
2273                                   AND f.id = d.forum", array($log->info));
2274     }
2275     return NULL;
2278 /**
2279  * Given a discussion id, return the first post from the discussion
2280  *
2281  * @global object
2282  * @global object
2283  * @param int $dicsussionid
2284  * @return array
2285  */
2286 function forum_get_firstpost_from_discussion($discussionid) {
2287     global $CFG, $DB;
2289     return $DB->get_record_sql("SELECT p.*
2290                              FROM {forum_discussions} d,
2291                                   {forum_posts} p
2292                             WHERE d.id = ?
2293                               AND d.firstpost = p.id ", array($discussionid));
2296 /**
2297  * Returns an array of counts of replies to each discussion
2298  *
2299  * @global object
2300  * @global object
2301  * @param int $forumid
2302  * @param string $forumsort
2303  * @param int $limit
2304  * @param int $page
2305  * @param int $perpage
2306  * @return array
2307  */
2308 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2309     global $CFG, $DB;
2311     if ($limit > 0) {
2312         $limitfrom = 0;
2313         $limitnum  = $limit;
2314     } else if ($page != -1) {
2315         $limitfrom = $page*$perpage;
2316         $limitnum  = $perpage;
2317     } else {
2318         $limitfrom = 0;
2319         $limitnum  = 0;
2320     }
2322     if ($forumsort == "") {
2323         $orderby = "";
2324         $groupby = "";
2326     } else {
2327         $orderby = "ORDER BY $forumsort";
2328         $groupby = ", ".strtolower($forumsort);
2329         $groupby = str_replace('desc', '', $groupby);
2330         $groupby = str_replace('asc', '', $groupby);
2331     }
2333     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2334         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2335                   FROM {forum_posts} p
2336                        JOIN {forum_discussions} d ON p.discussion = d.id
2337                  WHERE p.parent > 0 AND d.forum = ?
2338               GROUP BY p.discussion";
2339         return $DB->get_records_sql($sql, array($forumid));
2341     } else {
2342         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2343                   FROM {forum_posts} p
2344                        JOIN {forum_discussions} d ON p.discussion = d.id
2345                  WHERE d.forum = ?
2346               GROUP BY p.discussion $groupby
2347               $orderby";
2348         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2349     }
2352 /**
2353  * @global object
2354  * @global object
2355  * @global object
2356  * @staticvar array $cache
2357  * @param object $forum
2358  * @param object $cm
2359  * @param object $course
2360  * @return mixed
2361  */
2362 function forum_count_discussions($forum, $cm, $course) {
2363     global $CFG, $DB, $USER;
2365     static $cache = array();
2367     $now = round(time(), -2); // db cache friendliness
2369     $params = array($course->id);
2371     if (!isset($cache[$course->id])) {
2372         if (!empty($CFG->forum_enabletimedposts)) {
2373             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2374             $params[] = $now;
2375             $params[] = $now;
2376         } else {
2377             $timedsql = "";
2378         }
2380         $sql = "SELECT f.id, COUNT(d.id) as dcount
2381                   FROM {forum} f
2382                        JOIN {forum_discussions} d ON d.forum = f.id
2383                  WHERE f.course = ?
2384                        $timedsql
2385               GROUP BY f.id";
2387         if ($counts = $DB->get_records_sql($sql, $params)) {
2388             foreach ($counts as $count) {
2389                 $counts[$count->id] = $count->dcount;
2390             }
2391             $cache[$course->id] = $counts;
2392         } else {
2393             $cache[$course->id] = array();
2394         }
2395     }
2397     if (empty($cache[$course->id][$forum->id])) {
2398         return 0;
2399     }
2401     $groupmode = groups_get_activity_groupmode($cm, $course);
2403     if ($groupmode != SEPARATEGROUPS) {
2404         return $cache[$course->id][$forum->id];
2405     }
2407     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2408         return $cache[$course->id][$forum->id];
2409     }
2411     require_once($CFG->dirroot.'/course/lib.php');
2413     $modinfo =& get_fast_modinfo($course);
2414     if (is_null($modinfo->groups)) {
2415         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2416     }
2418     if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2419         $mygroups = $modinfo->groups[$cm->groupingid];
2420     } else {
2421         $mygroups = false; // Will be set below
2422     }
2424     // add all groups posts
2425     if (empty($mygroups)) {
2426         $mygroups = array(-1=>-1);
2427     } else {
2428         $mygroups[-1] = -1;
2429     }
2431     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2432     $params[] = $forum->id;
2434     if (!empty($CFG->forum_enabletimedposts)) {
2435         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2436         $params[] = $now;
2437         $params[] = $now;
2438     } else {
2439         $timedsql = "";
2440     }
2442     $sql = "SELECT COUNT(d.id)
2443               FROM {forum_discussions} d
2444              WHERE d.groupid $mygroups_sql AND d.forum = ?
2445                    $timedsql";
2447     return $DB->get_field_sql($sql, $params);
2450 /**
2451  * How many posts by other users are unrated by a given user in the given discussion?
2452  *
2453  * @global object
2454  * @global object
2455  * @param int $discussionid
2456  * @param int $userid
2457  * @return mixed
2458  */
2459 function forum_count_unrated_posts($discussionid, $userid) {
2460     global $CFG, $DB;
2461     if ($posts = $DB->get_record_sql("SELECT count(*) as num
2462                                    FROM {forum_posts}
2463                                   WHERE parent > 0
2464                                     AND discussion = ?
2465                                     AND userid <> ? ", array($discussionid, $userid))) {
2467         if ($rated = $DB->get_record_sql("SELECT count(*) as num
2468                                        FROM {forum_posts} p,
2469                                             {rating} r
2470                                       WHERE p.discussion = ?
2471                                         AND p.id = r.itemid
2472                                         AND r.userid = ?", array($discussionid, $userid))) {
2473             $difference = $posts->num - $rated->num;
2474             if ($difference > 0) {
2475                 return $difference;
2476             } else {
2477                 return 0;    // Just in case there was a counting error
2478             }
2479         } else {
2480             return $posts->num;
2481         }
2482     } else {
2483         return 0;
2484     }
2487 /**
2488  * Get all discussions in a forum
2489  *
2490  * @global object
2491  * @global object
2492  * @global object
2493  * @uses CONTEXT_MODULE
2494  * @uses VISIBLEGROUPS
2495  * @param object $cm
2496  * @param string $forumsort
2497  * @param bool $fullpost
2498  * @param int $unused
2499  * @param int $limit
2500  * @param bool $userlastmodified
2501  * @param int $page
2502  * @param int $perpage
2503  * @return array
2504  */
2505 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2506     global $CFG, $DB, $USER;
2508     $timelimit = '';
2510     $now = round(time(), -2);
2511     $params = array($cm->instance);
2513     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2515     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2516         return array();
2517     }
2519     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2521         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2522             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2523             $params[] = $now;
2524             $params[] = $now;
2525             if (isloggedin()) {
2526                 $timelimit .= " OR d.userid = ?";
2527                 $params[] = $USER->id;
2528             }
2529             $timelimit .= ")";
2530         }
2531     }
2533     if ($limit > 0) {
2534         $limitfrom = 0;
2535         $limitnum  = $limit;
2536     } else if ($page != -1) {
2537         $limitfrom = $page*$perpage;
2538         $limitnum  = $perpage;
2539     } else {
2540         $limitfrom = 0;
2541         $limitnum  = 0;
2542     }
2544     $groupmode    = groups_get_activity_groupmode($cm);
2545     $currentgroup = groups_get_activity_group($cm);
2547     if ($groupmode) {
2548         if (empty($modcontext)) {
2549             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2550         }
2552         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2553             if ($currentgroup) {
2554                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2555                 $params[] = $currentgroup;
2556             } else {
2557                 $groupselect = "";
2558             }
2560         } else {
2561             //seprate groups without access all
2562             if ($currentgroup) {
2563                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2564                 $params[] = $currentgroup;
2565             } else {
2566                 $groupselect = "AND d.groupid = -1";
2567             }
2568         }
2569     } else {
2570         $groupselect = "";
2571     }
2574     if (empty($forumsort)) {
2575         $forumsort = "d.timemodified DESC";
2576     }
2577     if (empty($fullpost)) {
2578         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2579     } else {
2580         $postdata = "p.*";
2581     }
2583     if (empty($userlastmodified)) {  // We don't need to know this
2584         $umfields = "";
2585         $umtable  = "";
2586     } else {
2587         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2588         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2589     }
2591     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2592                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2593               FROM {forum_discussions} d
2594                    JOIN {forum_posts} p ON p.discussion = d.id
2595                    JOIN {user} u ON p.userid = u.id
2596                    $umtable
2597              WHERE d.forum = ? AND p.parent = 0
2598                    $timelimit $groupselect
2599           ORDER BY $forumsort";
2600     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2603 /**
2604  *
2605  * @global object
2606  * @global object
2607  * @global object
2608  * @uses CONTEXT_MODULE
2609  * @uses VISIBLEGROUPS
2610  * @param object $cm
2611  * @return array
2612  */
2613 function forum_get_discussions_unread($cm) {
2614     global $CFG, $DB, $USER;
2616     $now = round(time(), -2);
2617     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2619     $params = array();
2620     $groupmode    = groups_get_activity_groupmode($cm);
2621     $currentgroup = groups_get_activity_group($cm);
2623     if ($groupmode) {
2624         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2626         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2627             if ($currentgroup) {
2628                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2629                 $params['currentgroup'] = $currentgroup;
2630             } else {
2631                 $groupselect = "";
2632             }
2634         } else {
2635             //separate groups without access all
2636             if ($currentgroup) {
2637                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2638                 $params['currentgroup'] = $currentgroup;
2639             } else {
2640                 $groupselect = "AND d.groupid = -1";
2641             }
2642         }
2643     } else {
2644         $groupselect = "";
2645     }
2647     if (!empty($CFG->forum_enabletimedposts)) {
2648         $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2649         $params['now1'] = $now;
2650         $params['now2'] = $now;
2651     } else {
2652         $timedsql = "";
2653     }
2655     $sql = "SELECT d.id, COUNT(p.id) AS unread
2656               FROM {forum_discussions} d
2657                    JOIN {forum_posts} p     ON p.discussion = d.id
2658                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2659              WHERE d.forum = {$cm->instance}
2660                    AND p.modified >= :cutoffdate AND r.id is NULL
2661                    $groupselect
2662                    $timedsql
2663           GROUP BY d.id";
2664     $params['cutoffdate'] = $cutoffdate;
2666     if ($unreads = $DB->get_records_sql($sql, $params)) {
2667         foreach ($unreads as $unread) {
2668             $unreads[$unread->id] = $unread->unread;
2669         }
2670         return $unreads;
2671     } else {
2672         return array();
2673     }
2676 /**
2677  * @global object
2678  * @global object
2679  * @global object
2680  * @uses CONEXT_MODULE
2681  * @uses VISIBLEGROUPS
2682  * @param object $cm
2683  * @return array
2684  */
2685 function forum_get_discussions_count($cm) {
2686     global $CFG, $DB, $USER;
2688     $now = round(time(), -2);
2689     $params = array($cm->instance);
2690     $groupmode    = groups_get_activity_groupmode($cm);
2691     $currentgroup = groups_get_activity_group($cm);
2693     if ($groupmode) {
2694         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2696         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2697             if ($currentgroup) {
2698                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2699                 $params[] = $currentgroup;
2700             } else {
2701                 $groupselect = "";
2702             }
2704         } else {
2705             //seprate groups without access all
2706             if ($currentgroup) {
2707                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2708                 $params[] = $currentgroup;
2709             } else {
2710                 $groupselect = "AND d.groupid = -1";
2711             }
2712         }
2713     } else {
2714         $groupselect = "";
2715     }
2717     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2719     $timelimit = "";
2721     if (!empty($CFG->forum_enabletimedposts)) {
2723         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2725         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2726             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2727             $params[] = $now;
2728             $params[] = $now;
2729             if (isloggedin()) {
2730                 $timelimit .= " OR d.userid = ?";
2731                 $params[] = $USER->id;
2732             }
2733             $timelimit .= ")";
2734         }
2735     }
2737     $sql = "SELECT COUNT(d.id)
2738               FROM {forum_discussions} d
2739                    JOIN {forum_posts} p ON p.discussion = d.id
2740              WHERE d.forum = ? AND p.parent = 0
2741                    $groupselect $timelimit";
2743     return $DB->get_field_sql($sql, $params);
2747 /**
2748  * Get all discussions started by a particular user in a course (or group)
2749  * This function no longer used ...
2750  *
2751  * @todo Remove this function if no longer used
2752  * @global object
2753  * @global object
2754  * @param int $courseid
2755  * @param int $userid
2756  * @param int $groupid
2757  * @return array
2758  */
2759 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2760     global $CFG, $DB;
2761     $params = array($courseid, $userid);
2762     if ($groupid) {
2763         $groupselect = " AND d.groupid = ? ";
2764         $params[] = $groupid;
2765     } else  {
2766         $groupselect = "";
2767     }
2769     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2770                                    f.type as forumtype, f.name as forumname, f.id as forumid
2771                               FROM {forum_discussions} d,
2772                                    {forum_posts} p,
2773                                    {user} u,
2774                                    {forum} f
2775                              WHERE d.course = ?
2776                                AND p.discussion = d.id
2777                                AND p.parent = 0
2778                                AND p.userid = u.id
2779                                AND u.id = ?
2780                                AND d.forum = f.id $groupselect
2781                           ORDER BY p.created DESC", $params);
2784 /**
2785  * Get the list of potential subscribers to a forum.
2786  *
2787  * @param object $forumcontext the forum context.
2788  * @param integer $groupid the id of a group, or 0 for all groups.
2789  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2790  * @param string $sort sort order. As for get_users_by_capability.
2791  * @return array list of users.
2792  */
2793 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2794     return get_users_by_capability($forumcontext, 'mod/forum:initialsubscriptions', $fields, $sort, '', '', $groupid, '', false, true);
2797 /**
2798  * Returns list of user objects that are subscribed to this forum
2799  *
2800  * @global object
2801  * @global object
2802  * @param object $course the course
2803  * @param forum $forum the forum
2804  * @param integer $groupid group id, or 0 for all.
2805  * @param object $context the forum context, to save re-fetching it where possible.
2806  * @return array list of users.
2807  */
2808 function forum_subscribed_users($course, $forum, $groupid=0, $context = NULL) {
2809     global $CFG, $DB;
2810     $params = array($forum->id);
2812     if ($groupid) {
2813         $grouptables = ", {groups_members} gm ";
2814         $groupselect = "AND gm.groupid = ? AND u.id = gm.userid";
2815         $params[] = $groupid;
2816     } else  {
2817         $grouptables = '';
2818         $groupselect = '';
2819     }
2821     $fields ="u.id,
2822               u.username,
2823               u.firstname,
2824               u.lastname,
2825               u.maildisplay,
2826               u.mailformat,
2827               u.maildigest,
2828               u.emailstop,
2829               u.imagealt,
2830               u.email,
2831               u.city,
2832               u.country,
2833               u.lastaccess,
2834               u.lastlogin,
2835               u.picture,
2836               u.timezone,
2837               u.theme,
2838               u.lang,
2839               u.trackforums,
2840               u.mnethostid";
2842     if (forum_is_forcesubscribed($forum)) {
2843         if (empty($context)) {
2844             $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2845             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2846         }
2847         $sort = "u.email ASC";
2848         $results = forum_get_potential_subscribers($context, $groupid, $fields, $sort);
2849     } else {
2850         $results = $DB->get_records_sql("SELECT $fields
2851                               FROM {user} u,
2852                                    {forum_subscriptions} s $grouptables
2853                              WHERE s.forum = ?
2854                                AND s.userid = u.id
2855                                AND u.deleted = 0  $groupselect
2856                           ORDER BY u.email ASC", $params);
2857     }
2859     static $guestid = null;
2861     if (is_null($guestid)) {
2862         if ($guest = guest_user()) {
2863             $guestid = $guest->id;
2864         } else {
2865             $guestid = 0;
2866         }
2867     }
2869     // Guest user should never be subscribed to a forum.
2870     unset($results[$guestid]);
2872     return $results;
2877 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2880 /**
2881  * @global object
2882  * @global object
2883  * @param int $courseid
2884  * @param string $type
2885  */
2886 function forum_get_course_forum($courseid, $type) {
2887 // How to set up special 1-per-course forums
2888     global $CFG, $DB, $OUTPUT;
2890     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2891         // There should always only be ONE, but with the right combination of
2892         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2893         foreach ($forums as $forum) {
2894             return $forum;   // ie the first one
2895         }
2896     }
2898     // Doesn't exist, so create one now.
2899     $forum->course = $courseid;
2900     $forum->type = "$type";
2901     switch ($forum->type) {
2902         case "news":
2903             $forum->name  = get_string("namenews", "forum");
2904             $forum->intro = get_string("intronews", "forum");
2905             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2906             $forum->assessed = 0;
2907             if ($courseid == SITEID) {
2908                 $forum->name  = get_string("sitenews");
2909                 $forum->forcesubscribe = 0;
2910             }
2911             break;
2912         case "social":
2913             $forum->name  = get_string("namesocial", "forum");
2914             $forum->intro = get_string("introsocial", "forum");
2915             $forum->assessed = 0;
2916             $forum->forcesubscribe = 0;
2917             break;
2918         case "blog":
2919             $forum->name = get_string('blogforum', 'forum');
2920             $forum->intro = get_string('introblog', 'forum');
2921             $forum->assessed = 0;
2922             $forum->forcesubscribe = 0;
2923             break;
2924         default:
2925             echo $OUTPUT->notification("That forum type doesn't exist!");
2926             return false;
2927             break;
2928     }
2930     $forum->timemodified = time();
2931     $forum->id = $DB->insert_record("forum", $forum);
2933     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2934         echo $OUTPUT->notification("Could not find forum module!!");
2935         return false;
2936     }
2937     $mod = new stdClass();
2938     $mod->course = $courseid;
2939     $mod->module = $module->id;
2940     $mod->instance = $forum->id;
2941     $mod->section = 0;
2942     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
2943         echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
2944         return false;
2945     }
2946     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
2947         echo $OUTPUT->notification("Could not add the new course module to that section");
2948         return false;
2949     }
2950     $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule));
2952     include_once("$CFG->dirroot/course/lib.php");
2953     rebuild_course_cache($courseid);
2955     return $DB->get_record("forum", array("id" => "$forum->id"));
2959 /**
2960  * Given the data about a posting, builds up the HTML to display it and
2961  * returns the HTML in a string.  This is designed for sending via HTML email.
2962  *
2963  * @global object
2964  * @param object $course
2965  * @param object $cm
2966  * @param object $forum
2967  * @param object $discussion
2968  * @param object $post
2969  * @param object $userform
2970  * @param object $userto
2971  * @param bool $ownpost
2972  * @param bool $reply
2973  * @param bool $link
2974  * @param bool $rate
2975  * @param string $footer
2976  * @return string
2977  */
2978 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
2979                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
2981     global $CFG, $OUTPUT;
2983     if (!isset($userto->viewfullnames[$forum->id])) {
2984         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
2985             print_error('invalidcoursemodule');
2986         }
2987         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2988         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
2989     } else {
2990         $viewfullnames = $userto->viewfullnames[$forum->id];
2991     }
2993     // format the post body
2994     $options = new stdClass();
2995     $options->para = true;
2996     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
2998     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3000     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3001     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3002     $output .= '</td>';
3004     if ($post->parent) {
3005         $output .= '<td class="topic">';
3006     } else {
3007         $output .= '<td class="topic starter">';
3008     }
3009     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3011     $fullname = fullname($userfrom, $viewfullnames);
3012     $by = new stdClass();
3013     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3014     $by->date = userdate($post->modified, '', $userto->timezone);
3015     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3017     $output .= '</td></tr>';
3019     $output .= '<tr><td class="left side" valign="top">';
3021     if (isset($userfrom->groups)) {
3022         $groups = $userfrom->groups[$forum->id];
3023     } else {
3024         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
3025             print_error('invalidcoursemodule');
3026         }
3027         $group = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3028     }
3030     if ($groups) {
3031         $output .= print_group_picture($groups, $course->id, false, true, true);
3032     } else {
3033         $output .= '&nbsp;';
3034     }
3036     $output .= '</td><td class="content">';
3038     $attachments = forum_print_attachments($post, $cm, 'html');
3039     if ($attachments !== '') {
3040         $output .= '<div class="attachments">';
3041         $output .= $attachments;
3042         $output .= '</div>';
3043     }
3045     $output .= $formattedtext;
3047 // Commands
3048     $commands = array();
3050     if ($post->parent) {
3051         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3052                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3053     }
3055     if ($reply) {
3056         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3057                       get_string('reply', 'forum').'</a>';
3058     }
3060     $output .= '<div class="commands">';
3061     $output .= implode(' | ', $commands);
3062     $output .= '</div>';
3064 // Context link to post if required
3065     if ($link) {
3066         $output .= '<div class="link">';
3067         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3068                      get_string('postincontext', 'forum').'</a>';
3069         $output .= '</div>';
3070     }
3072     if ($footer) {
3073         $output .= '<div class="footer">'.$footer.'</div>';
3074     }
3075     $output .= '</td></tr></table>'."\n\n";
3077     return $output;
3080 /**
3081  * Print a forum post
3082  *
3083  * @global object
3084  * @global object
3085  * @uses FORUM_MODE_THREADED
3086  * @uses PORTFOLIO_FORMAT_PLAINHTML
3087  * @uses PORTFOLIO_FORMAT_FILE
3088  * @uses PORTFOLIO_FORMAT_RICHHTML
3089  * @uses PORTFOLIO_ADD_TEXT_LINK
3090  * @uses CONTEXT_MODULE
3091  * @param object $post The post to print.
3092  * @param object $discussion
3093  * @param object $forum
3094  * @param object $cm
3095  * @param object $course
3096  * @param boolean $ownpost Whether this post belongs to the current user.
3097  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3098  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3099  * @param string $footer Extra stuff to print after the message.
3100  * @param string $highlight Space-separated list of terms to highlight.
3101  * @param int $post_read true, false or -99. If we already know whether this user
3102  *          has read this post, pass that in, otherwise, pass in -99, and this
3103  *          function will work it out.
3104  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3105  *          the current user can't see this post, if this argument is true
3106  *          (the default) then print a dummy 'you can't see this post' post.
3107  *          If false, don't output anything at all.
3108  * @param bool|null $istracked
3109  * @return void
3110  */
3111 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3112                           $footer="", $highlight="", $post_read=null, $dummyifcantsee=true, $istracked=null) {
3114     global $USER, $CFG, $OUTPUT;
3116     static $stredit, $strdelete, $strreply, $strparent, $strprune;
3117     static $strpruneheading, $displaymode;
3118     static $strmarkread, $strmarkunread;
3120     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3122     $post->course = $course->id;
3123     $post->forum  = $forum->id;
3124     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3126     // caching
3127     if (!isset($cm->cache)) {
3128         $cm->cache = new stdClass();
3129     }
3131     if (!isset($cm->cache->caps)) {
3132         $cm->cache->caps = array();
3133         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3134         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3135         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3136         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3137         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3138         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3139         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3140         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3141         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3142     }
3144     if (!isset($cm->uservisible)) {
3145         $cm->uservisible = coursemodule_visible_for_user($cm);
3146     }
3148     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3149         if (!$dummyifcantsee) {
3150             return;
3151         }
3152         echo '<a id="p'.$post->id.'"></a>';
3153         echo '<table cellspacing="0" class="forumpost">';
3154         echo '<tr class="header"><td class="picture left">';
3155         //        print_user_picture($post->userid, $courseid, $post->picture);
3156         echo '</td>';
3157         if ($post->parent) {
3158             echo '<td class="topic">';
3159         } else {
3160             echo '<td class="topic starter">';
3161         }
3162         echo '<div class="subject">'.get_string('forumsubjecthidden','forum').'</div>';
3163         echo '<div class="author">';
3164         print_string('forumauthorhidden','forum');
3165         echo '</div></td></tr>';
3167         echo '<tr><td class="left side">';
3168         echo '&nbsp;';
3170         // Actual content
3172         echo '</td><td class="content">'."\n";
3173         echo get_string('forumbodyhidden','forum');
3174         echo '</td></tr></table>';
3175         return;
3176     }
3178     if (empty($stredit)) {
3179         $stredit         = get_string('edit', 'forum');
3180         $strdelete       = get_string('delete', 'forum');
3181         $strreply        = get_string('reply', 'forum');
3182         $strparent       = get_string('parent', 'forum');
3183         $strpruneheading = get_string('pruneheading', 'forum');
3184         $strprune        = get_string('prune', 'forum');
3185         $displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3186         $strmarkread     = get_string('markread', 'forum');
3187         $strmarkunread   = get_string('markunread', 'forum');
3189     }
3191     $read_style = '';
3192     // ignore trackign status if not tracked or tracked param missing
3193     if ($istracked) {
3194         if (is_null($post_read)) {
3195             debugging('fetching post_read info');
3196             $post_read = forum_tp_is_post_read($USER->id, $post);
3197         }
3199         if ($post_read) {
3200             $read_style = ' read';
3201         } else {
3202             $read_style = ' unread';
3203             echo '<a name="unread"></a>';
3204         }
3205     }
3207     echo '<a id="p'.$post->id.'"></a>';
3208     echo '<table cellspacing="0" class="forumpost'.$read_style.'">';
3210     // Picture
3211     $postuser = new stdClass();
3212     $postuser->id        = $post->userid;
3213     $postuser->firstname = $post->firstname;
3214     $postuser->lastname  = $post->lastname;
3215     $postuser->imagealt  = $post->imagealt;
3216     $postuser->picture   = $post->picture;
3217     $postuser->email     = $post->email;
3219     echo '<tr class="header"><td class="picture left">';
3220     echo $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3221     echo '</td>';
3223     if ($post->parent) {
3224         echo '<td class="topic">';
3225     } else {
3226         echo '<td class="topic starter">';
3227     }
3229     if (!empty($post->subjectnoformat)) {
3230         echo '<div class="subject">'.$post->subject.'</div>';
3231     } else {
3232         echo '<div class="subject">'.format_string($post->subject).'</div>';
3233     }
3235     echo '<div class="author">';
3236     $fullname = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3237     $by = new stdClass();
3238     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.
3239                 $post->userid.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3240     $by->date = userdate($post->modified);
3241     print_string('bynameondate', 'forum', $by);
3242     echo '</div></td></tr>';
3244     echo '<tr><td class="left side">';
3245     if (isset($cm->cache->usersgroups)) {
3246         $groups = array();
3247         if (isset($cm->cache->usersgroups[$post->userid])) {
3248             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3249                 $groups[$gid] = $cm->cache->groups[$gid];
3250             }
3251         }
3252     } else {
3253         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3254     }
3256     if ($groups) {
3257         print_group_picture($groups, $course->id, false, false, true);
3258     } else {
3259         echo '&nbsp;';
3260     }
3262 // Actual content
3264     echo '</td><td class="content">'."\n";
3266     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3268     if ($attachments !== '') {
3269         echo '<div class="attachments">';
3270         echo $attachments;
3271         echo '</div>';
3272     }
3274     $options = new stdClass();
3275     $options->para    = false;
3276     $options->trusted = $post->messagetrust;
3277     if ($link and (strlen(strip_tags($post->message)) > $CFG->forum_longpost)) {
3278         // Print shortened version
3279         echo format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3280         $numwords = count_words(strip_tags($post->message));
3281         echo '<div class="posting shortenedpost"><a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3282         echo get_string('readtherest', 'forum');
3283         echo '</a> ('.get_string('numwords', '', $numwords).')...</div>';
3284     } else {
3285         // Print whole message
3286         echo '<div class="posting fullpost">';
3287         if ($highlight) {
3288             echo highlight($highlight, format_text($post->message, $post->messageformat, $options, $course->id));
3289         } else {
3290             echo format_text($post->message, $post->messageformat, $options, $course->id);
3291         }
3292         echo '</div>';
3293         echo $attachedimages;
3294     }
3297 // Commands
3299     $commands = array();
3301     if ($istracked) {
3302         // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3303         // Don't display the mark read / unread controls in this case.
3304         if ($CFG->forum_usermarksread and isloggedin()) {
3305             if ($post_read) {
3306                 $mcmd = '&amp;mark=unread&amp;postid='.$post->id;
3307                 $mtxt = $strmarkunread;
3308             } else {
3309                 $mcmd = '&amp;mark=read&amp;postid='.$post->id;
3310                 $mtxt = $strmarkread;
3311             }
3312             if ($displaymode == FORUM_MODE_THREADED) {
3313                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3314                               $post->discussion.'&amp;parent='.$post->id.$mcmd.'">'.$mtxt.'</a>';
3315             } else {
3316                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3317                               $post->discussion.$mcmd.'#p'.$post->id.'">'.$mtxt.'</a>';
3318             }
3319         }
3320     }
3322     if ($post->parent) {  // Zoom in to the parent specifically
3323         if ($displaymode == FORUM_MODE_THREADED) {
3324             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3325                       $post->discussion.'&amp;parent='.$post->parent.'">'.$strparent.'</a>';
3326         } else {
3327             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3328                       $post->discussion.'#p'.$post->parent.'">'.$strparent.'</a>';
3329         }
3330     }
3332     $age = time() - $post->created;
3333     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3334     if (!$post->parent and $forum->type == 'news' and $discussion->timestart > time()) {
3335         $age = 0;
3336     }
3337     $editanypost = $cm->cache->caps['mod/forum:editanypost'];
3339     if ($ownpost or $editanypost) {
3340         if (($age < $CFG->maxeditingtime) or $editanypost) {
3341             $commands[] =  '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?edit='.$post->id.'">'.$stredit.'</a>';
3342         }
3343     }
3345     if ($cm->cache->caps['mod/forum:splitdiscussions']
3346                 && $post->parent && $forum->type != 'single') {
3348         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?prune='.$post->id.
3349                       '" title="'.$strpruneheading.'">'.$strprune.'</a>';
3350     }
3352     if (($ownpost and $age < $CFG->maxeditingtime
3353                 and $cm->cache->caps['mod/forum:deleteownpost'])
3354                 or $cm->cache->caps['mod/forum:deleteanypost']) {
3355         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?delete='.$post->id.'">'.$strdelete.'</a>';
3356     }
3358     if ($reply) {
3359         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.$strreply.'</a>';
3360     }
3362     if ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost'])) {
3363         $p = array(
3364             'postid' => $post->id,
3365         );
3366         require_once($CFG->libdir.'/portfoliolib.php');
3367         $button = new portfolio_add_button();
3368         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php');
3369         if (empty($attachments)) {
3370             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3371         } else {
3372             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3373         }
3375         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3376         if (!empty($porfoliohtml)) {
3377             $commands[] = $porfoliohtml;
3378         }
3379     }
3381     echo '<div class="commands">';
3382     echo implode(' | ', $commands);
3383     echo '</div>';
3386 // Ratings
3387     if (!empty($post->rating)) {
3388         echo $OUTPUT->render($post->rating);
3389     }
3391 // Link to post if required
3393     if ($link) {
3394         echo '<div class="link">';
3395         if ($post->replies == 1) {
3396             $replystring = get_string('repliesone', 'forum', $post->replies);
3397         } else {
3398             $replystring = get_string('repliesmany', 'forum', $post->replies);
3399         }
3400         echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.
3401              get_string('discussthistopic', 'forum').'</a>&nbsp;('.$replystring.')';
3402         echo '</div>';
3403     }
3405     if ($footer) {
3406         echo '<div class="footer">'.$footer.'</div>';
3407     }
3408     echo '</td></tr></table>'."\n\n";
3410     if ($istracked && !$CFG->forum_usermarksread && !$post_read) {
3411         forum_tp_mark_post_read($USER->id, $post, $forum->id);
3412     }
3415 /**
3416  * Return rating related permissions
3417  * @param string $options the context id
3418  * @return array an associative array of the user's rating permissions
3419  */
3420 function forum_rating_permissions($contextid) {
3421     $context = get_context_instance_by_id($contextid);
3423     if (!$context) {
3424         print_error('invalidcontext');
3425         return null;
3426     } else {
3427         return array('view'=>has_capability('mod/forum:viewrating',$context), 'viewany'=>has_capability('mod/forum:viewanyrating',$context), 'viewall'=>has_capability('mod/forum:viewallratings',$context), 'rate'=>has_capability('mod/forum:rate',$context));
3428     }
3431 /**
3432  * Returns the names of the table and columns necessary to check items for ratings
3433  * @return array an array containing the item table, item id and user id columns
3434  */
3435 function forum_rating_item_check_info() {
3436     return array('forum_posts','id','userid');
3440 /**
3441  * This function prints the overview of a discussion in the forum listing.
3442  * It needs some discussion information and some post information, these
3443  * happen to be combined for efficiency in the $post parameter by the function
3444  * that calls this one: forum_print_latest_discussions()
3445  *
3446  * @global object
3447  * @global object
3448  * @param object $post The post object (passed by reference for speed).
3449  * @param object $forum The forum object.
3450  * @param int $group Current group.
3451  * @param string $datestring Format to use for the dates.
3452  * @param boolean $cantrack Is tracking enabled for this forum.
3453  * @param boolean $forumtracked Is the user tracking this forum.
3454  * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
3455  */
3456 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
3457                                         $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) {
3459     global $USER, $CFG, $OUTPUT;
3461     static $rowcount;
3462     static $strmarkalldread;
3464     if (empty($modcontext)) {
3465         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3466             print_error('invalidcoursemodule');
3467         }
3468         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3469     }
3471     if (!isset($rowcount)) {
3472         $rowcount = 0;
3473         $strmarkalldread = get_string('markalldread', 'forum');
3474     } else {
3475         $rowcount = ($rowcount + 1) % 2;
3476     }
3478     $post->subject = format_string($post->subject,true);
3480     echo "\n\n";
3481     echo '<tr class="discussion r'.$rowcount.'">';
3483     // Topic
3484     echo '<td class="topic starter">';
3485     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.$post->subject.'</a>';
3486     echo "</td>\n";
3488     // Picture
3489     $postuser = new stdClass();
3490     $postuser->id = $post->userid;
3491     $postuser->firstname = $post->firstname;
3492     $postuser->lastname = $post->lastname;
3493     $postuser->imagealt = $post->imagealt;
3494     $postuser->picture = $post->picture;
3495     $postuser->email = $post->email;
3497     echo '<td class="picture">';
3498     echo $OUTPUT->user_picture($postuser, array('courseid'=>$forum->course));
3499     echo "</td>\n";
3501     // User name
3502     $fullname = fullname($post, has_capability('moodle/site:viewfullnames', $modcontext));
3503     echo '<td class="author">';
3504     echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$p