Merge branch 'MDL-26440_uid' of git://github.com/andyjdavis/moodle
[moodle.git] / mod / forum / lib.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * @package mod-forum
20  * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
21  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22  */
24 /** Include required files */
25 require_once($CFG->libdir.'/filelib.php');
26 require_once($CFG->libdir.'/eventslib.php');
27 require_once($CFG->dirroot.'/user/selector/lib.php');
29 /// CONSTANTS ///////////////////////////////////////////////////////////
31 define('FORUM_MODE_FLATOLDEST', 1);
32 define('FORUM_MODE_FLATNEWEST', -1);
33 define('FORUM_MODE_THREADED', 2);
34 define('FORUM_MODE_NESTED', 3);
36 define('FORUM_CHOOSESUBSCRIBE', 0);
37 define('FORUM_FORCESUBSCRIBE', 1);
38 define('FORUM_INITIALSUBSCRIBE', 2);
39 define('FORUM_DISALLOWSUBSCRIBE',3);
41 define('FORUM_TRACKING_OFF', 0);
42 define('FORUM_TRACKING_OPTIONAL', 1);
43 define('FORUM_TRACKING_ON', 2);
45 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
47 /**
48  * Given an object containing all the necessary data,
49  * (defined by the form in mod_form.php) this function
50  * will create a new instance and return the id number
51  * of the new instance.
52  *
53  * @global object
54  * @global object
55  * @param object $forum add forum instance (with magic quotes)
56  * @return int intance id
57  */
58 function forum_add_instance($forum, $mform) {
59     global $CFG, $DB;
61     $forum->timemodified = time();
63     if (empty($forum->assessed)) {
64         $forum->assessed = 0;
65     }
67     if (empty($forum->ratingtime) or empty($forum->assessed)) {
68         $forum->assesstimestart  = 0;
69         $forum->assesstimefinish = 0;
70     }
72     $forum->id = $DB->insert_record('forum', $forum);
73     $modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule);
75     if ($forum->type == 'single') {  // Create related discussion.
76         $discussion = new stdClass();
77         $discussion->course        = $forum->course;
78         $discussion->forum         = $forum->id;
79         $discussion->name          = $forum->name;
80         $discussion->assessed      = $forum->assessed;
81         $discussion->message       = $forum->intro;
82         $discussion->messageformat = $forum->introformat;
83         $discussion->messagetrust  = trusttext_trusted(get_context_instance(CONTEXT_COURSE, $forum->course));
84         $discussion->mailnow       = false;
85         $discussion->groupid       = -1;
87         $message = '';
89         $discussion->id = forum_add_discussion($discussion, null, $message);
91         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
92             // ugly hack - we need to copy the files somehow
93             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
94             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
96             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
97             $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
98         }
99     }
101     if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
102     /// all users should be subscribed initially
103     /// Note: forum_get_potential_subscribers should take the forum context,
104     /// but that does not exist yet, becuase the forum is only half build at this
105     /// stage. However, because the forum is brand new, we know that there are
106     /// no role assignments or overrides in the forum context, so using the
107     /// course context gives the same list of users.
108         $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
109         foreach ($users as $user) {
110             forum_subscribe($user->id, $forum->id);
111         }
112     }
114     forum_grade_item_update($forum);
116     return $forum->id;
120 /**
121  * Given an object containing all the necessary data,
122  * (defined by the form in mod_form.php) this function
123  * will update an existing instance with new data.
124  *
125  * @global object
126  * @param object $forum forum instance (with magic quotes)
127  * @return bool success
128  */
129 function forum_update_instance($forum, $mform) {
130     global $DB, $OUTPUT, $USER;
132     $forum->timemodified = time();
133     $forum->id           = $forum->instance;
135     if (empty($forum->assessed)) {
136         $forum->assessed = 0;
137     }
139     if (empty($forum->ratingtime) or empty($forum->assessed)) {
140         $forum->assesstimestart  = 0;
141         $forum->assesstimefinish = 0;
142     }
144     $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
146     // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
147     // if  scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
148     // for count and sum aggregation types the grade we check to make sure they do not exceed the scale (i.e. max score) when calculating the grade
149     if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
150         forum_update_grades($forum); // recalculate grades for the forum
151     }
153     if ($forum->type == 'single') {  // Update related discussion and post.
154         if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
155             if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC')) {
156                 echo $OUTPUT->notification('Warning! There is more than one discussion in this forum - using the most recent');
157                 $discussion = array_pop($discussions);
158             } else {
159                 // try to recover by creating initial discussion - MDL-16262
160                 $discussion = new stdClass();
161                 $discussion->course          = $forum->course;
162                 $discussion->forum           = $forum->id;
163                 $discussion->name            = $forum->name;
164                 $discussion->assessed        = $forum->assessed;
165                 $discussion->message         = $forum->intro;
166                 $discussion->messageformat   = $forum->introformat;
167                 $discussion->messagetrust    = true;
168                 $discussion->mailnow         = false;
169                 $discussion->groupid         = -1;
171                 $message = '';
173                 forum_add_discussion($discussion, null, $message);
175                 if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
176                     print_error('cannotadd', 'forum');
177                 }
178             }
179         }
180         if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
181             print_error('cannotfindfirstpost', 'forum');
182         }
184         $cm         = get_coursemodule_from_instance('forum', $forum->id);
185         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
187         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
188             // ugly hack - we need to copy the files somehow
189             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
190             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
192             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
193         }
195         $post->subject       = $forum->name;
196         $post->message       = $forum->intro;
197         $post->messageformat = $forum->introformat;
198         $post->messagetrust  = trusttext_trusted($modcontext);
199         $post->modified      = $forum->timemodified;
200         $post->userid        = $USER->id;    // MDL-18599, so that current teacher can take ownership of activities
202         $DB->update_record('forum_posts', $post);
203         $discussion->name = $forum->name;
204         $DB->update_record('forum_discussions', $discussion);
205     }
207     $DB->update_record('forum', $forum);
209     forum_grade_item_update($forum);
211     return true;
215 /**
216  * Given an ID of an instance of this module,
217  * this function will permanently delete the instance
218  * and any data that depends on it.
219  *
220  * @global object
221  * @param int $id forum instance id
222  * @return bool success
223  */
224 function forum_delete_instance($id) {
225     global $DB;
227     if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
228         return false;
229     }
230     if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
231         return false;
232     }
233     if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
234         return false;
235     }
237     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
239     // now get rid of all files
240     $fs = get_file_storage();
241     $fs->delete_area_files($context->id);
243     $result = true;
245     if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
246         foreach ($discussions as $discussion) {
247             if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
248                 $result = false;
249             }
250         }
251     }
253     if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
254         $result = false;
255     }
257     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
259     if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
260         $result = false;
261     }
263     forum_grade_item_delete($forum);
265     return $result;
269 /**
270  * Indicates API features that the forum supports.
271  *
272  * @uses FEATURE_GROUPS
273  * @uses FEATURE_GROUPINGS
274  * @uses FEATURE_GROUPMEMBERSONLY
275  * @uses FEATURE_MOD_INTRO
276  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
277  * @uses FEATURE_COMPLETION_HAS_RULES
278  * @uses FEATURE_GRADE_HAS_GRADE
279  * @uses FEATURE_GRADE_OUTCOMES
280  * @param string $feature
281  * @return mixed True if yes (some features may use other values)
282  */
283 function forum_supports($feature) {
284     switch($feature) {
285         case FEATURE_GROUPS:                  return true;
286         case FEATURE_GROUPINGS:               return true;
287         case FEATURE_GROUPMEMBERSONLY:        return true;
288         case FEATURE_MOD_INTRO:               return true;
289         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
290         case FEATURE_COMPLETION_HAS_RULES:    return true;
291         case FEATURE_GRADE_HAS_GRADE:         return true;
292         case FEATURE_GRADE_OUTCOMES:          return true;
293         case FEATURE_RATE:                    return true;
294         case FEATURE_BACKUP_MOODLE2:          return true;
296         default: return null;
297     }
301 /**
302  * Obtains the automatic completion state for this forum based on any conditions
303  * in forum settings.
304  *
305  * @global object
306  * @global object
307  * @param object $course Course
308  * @param object $cm Course-module
309  * @param int $userid User ID
310  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
311  * @return bool True if completed, false if not. (If no conditions, then return
312  *   value depends on comparison type)
313  */
314 function forum_get_completion_state($course,$cm,$userid,$type) {
315     global $CFG,$DB;
317     // Get forum details
318     if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
319         throw new Exception("Can't find forum {$cm->instance}");
320     }
322     $result=$type; // Default return value
324     $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
325     $postcountsql="
326 SELECT
327     COUNT(1)
328 FROM
329     {forum_posts} fp
330     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
331 WHERE
332     fp.userid=:userid AND fd.forum=:forumid";
334     if ($forum->completiondiscussions) {
335         $value = $forum->completiondiscussions <=
336                  $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
337         if ($type == COMPLETION_AND) {
338             $result = $result && $value;
339         } else {
340             $result = $result || $value;
341         }
342     }
343     if ($forum->completionreplies) {
344         $value = $forum->completionreplies <=
345                  $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
346         if ($type==COMPLETION_AND) {
347             $result = $result && $value;
348         } else {
349             $result = $result || $value;
350         }
351     }
352     if ($forum->completionposts) {
353         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
354         if ($type == COMPLETION_AND) {
355             $result = $result && $value;
356         } else {
357             $result = $result || $value;
358         }
359     }
361     return $result;
365 /**
366  * Function to be run periodically according to the moodle cron
367  * Finds all posts that have yet to be mailed out, and mails them
368  * out to all subscribers
369  *
370  * @global object
371  * @global object
372  * @global object
373  * @uses CONTEXT_MODULE
374  * @uses CONTEXT_COURSE
375  * @uses SITEID
376  * @uses FORMAT_PLAIN
377  * @return void
378  */
379 function forum_cron() {
380     global $CFG, $USER, $DB;
382     $site = get_site();
384     // all users that are subscribed to any post that needs sending
385     $users = array();
387     // status arrays
388     $mailcount  = array();
389     $errorcount = array();
391     // caches
392     $discussions     = array();
393     $forums          = array();
394     $courses         = array();
395     $coursemodules   = array();
396     $subscribedusers = array();
399     // Posts older than 2 days will not be mailed.  This is to avoid the problem where
400     // cron has not been running for a long time, and then suddenly people are flooded
401     // with mail from the past few weeks or months
402     $timenow   = time();
403     $endtime   = $timenow - $CFG->maxeditingtime;
404     $starttime = $endtime - 48 * 3600;   // Two days earlier
406     if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
407         // Mark them all now as being mailed.  It's unlikely but possible there
408         // might be an error later so that a post is NOT actually mailed out,
409         // but since mail isn't crucial, we can accept this risk.  Doing it now
410         // prevents the risk of duplicated mails, which is a worse problem.
412         if (!forum_mark_old_posts_as_mailed($endtime)) {
413             mtrace('Errors occurred while trying to mark some posts as being mailed.');
414             return false;  // Don't continue trying to mail them, in case we are in a cron loop
415         }
417         // checking post validity, and adding users to loop through later
418         foreach ($posts as $pid => $post) {
420             $discussionid = $post->discussion;
421             if (!isset($discussions[$discussionid])) {
422                 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
423                     $discussions[$discussionid] = $discussion;
424                 } else {
425                     mtrace('Could not find discussion '.$discussionid);
426                     unset($posts[$pid]);
427                     continue;
428                 }
429             }
430             $forumid = $discussions[$discussionid]->forum;
431             if (!isset($forums[$forumid])) {
432                 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
433                     $forums[$forumid] = $forum;
434                 } else {
435                     mtrace('Could not find forum '.$forumid);
436                     unset($posts[$pid]);
437                     continue;
438                 }
439             }
440             $courseid = $forums[$forumid]->course;
441             if (!isset($courses[$courseid])) {
442                 if ($course = $DB->get_record('course', array('id' => $courseid))) {
443                     $courses[$courseid] = $course;
444                 } else {
445                     mtrace('Could not find course '.$courseid);
446                     unset($posts[$pid]);
447                     continue;
448                 }
449             }
450             if (!isset($coursemodules[$forumid])) {
451                 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
452                     $coursemodules[$forumid] = $cm;
453                 } else {
454                     mtrace('Could not find course module for forum '.$forumid);
455                     unset($posts[$pid]);
456                     continue;
457                 }
458             }
461             // caching subscribed users of each forum
462             if (!isset($subscribedusers[$forumid])) {
463                 $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
464                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext, "u.*")) {
465                     foreach ($subusers as $postuser) {
466                         unset($postuser->description); // not necessary
467                         // this user is subscribed to this forum
468                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
469                         // this user is a user we have to process later
470                         $users[$postuser->id] = $postuser;
471                     }
472                     unset($subusers); // release memory
473                 }
474             }
476             $mailcount[$pid] = 0;
477             $errorcount[$pid] = 0;
478         }
479     }
481     if ($users && $posts) {
483         $urlinfo = parse_url($CFG->wwwroot);
484         $hostname = $urlinfo['host'];
486         foreach ($users as $userto) {
488             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
490             // set this so that the capabilities are cached, and environment matches receiving user
491             cron_setup_user($userto);
493             mtrace('Processing user '.$userto->id);
495             // init caches
496             $userto->viewfullnames = array();
497             $userto->canpost       = array();
498             $userto->markposts     = array();
499             $userto->enrolledin    = array();
501             // reset the caches
502             foreach ($coursemodules as $forumid=>$unused) {
503                 $coursemodules[$forumid]->cache       = new stdClass();
504                 $coursemodules[$forumid]->cache->caps = array();
505                 unset($coursemodules[$forumid]->uservisible);
506             }
508             foreach ($posts as $pid => $post) {
510                 // Set up the environment for the post, discussion, forum, course
511                 $discussion = $discussions[$post->discussion];
512                 $forum      = $forums[$discussion->forum];
513                 $course     = $courses[$forum->course];
514                 $cm         =& $coursemodules[$forum->id];
516                 // Do some checks  to see if we can bail out now
517                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
518                     continue; // user does not subscribe to this forum
519                 }
521                 // Verify user is enrollend in course - if not do not send any email
522                 if (!isset($userto->enrolledin[$course->id])) {
523                     $userto->enrolledin[$course->id] = is_enrolled(get_context_instance(CONTEXT_COURSE, $course->id));
524                 }
525                 if (!$userto->enrolledin[$course->id]) {
526                     // oops - this user should not receive anything from this course
527                     continue;
528                 }
530                 // Get info about the sending user
531                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
532                     $userfrom = $users[$post->userid];
533                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
534                     unset($userfrom->description); // not necessary
535                     $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
536                 } else {
537                     mtrace('Could not find user '.$post->userid);
538                     continue;
539                 }
541                 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
543                 // setup global $COURSE properly - needed for roles and languages
544                 cron_setup_user($userto, $course);
546                 // Fill caches
547                 if (!isset($userto->viewfullnames[$forum->id])) {
548                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
549                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
550                 }
551                 if (!isset($userto->canpost[$discussion->id])) {
552                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
553                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
554                 }
555                 if (!isset($userfrom->groups[$forum->id])) {
556                     if (!isset($userfrom->groups)) {
557                         $userfrom->groups = array();
558                         $users[$userfrom->id]->groups = array();
559                     }
560                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
561                     $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
562                 }
564                 // Make sure groups allow this user to see this email
565                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
566                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
567                         continue;                           // Be safe and don't send it to anyone
568                     }
570                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
571                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
572                         continue;
573                     }
574                 }
576                 // Make sure we're allowed to see it...
577                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
578                     mtrace('user '.$userto->id. ' can not see '.$post->id);
579                     continue;
580                 }
582                 // OK so we need to send the email.
584                 // Does the user want this post in a digest?  If so postpone it for now.
585                 if ($userto->maildigest > 0) {
586                     // This user wants the mails to be in digest form
587                     $queue = new stdClass();
588                     $queue->userid       = $userto->id;
589                     $queue->discussionid = $discussion->id;
590                     $queue->postid       = $post->id;
591                     $queue->timemodified = $post->created;
592                     $DB->insert_record('forum_queue', $queue);
593                     continue;
594                 }
597                 // Prepare to actually send the post now, and build up the content
599                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
601                 $userfrom->customheaders = array (  // Headers to make emails easier to track
602                            'Precedence: Bulk',
603                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
604                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
605                            'Message-ID: <moodlepost'.$post->id.'@'.$hostname.'>',
606                            'X-Course-Id: '.$course->id,
607                            'X-Course-Name: '.format_string($course->fullname, true)
608                 );
610                 if ($post->parent) {  // This post is a reply, so add headers for threading (see MDL-22551)
611                     $userfrom->customheaders[] = 'In-Reply-To: <moodlepost'.$post->parent.'@'.$hostname.'>';
612                     $userfrom->customheaders[] = 'References: <moodlepost'.$post->parent.'@'.$hostname.'>';
613                 }
615                 $postsubject = "$course->shortname: ".format_string($post->subject,true);
616                 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
617                 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
619                 // Send the post now!
621                 mtrace('Sending ', '');
623                 $eventdata = new stdClass();
624                 $eventdata->component        = 'mod_forum';
625                 $eventdata->name             = 'posts';
626                 $eventdata->userfrom         = $userfrom;
627                 $eventdata->userto           = $userto;
628                 $eventdata->subject          = $postsubject;
629                 $eventdata->fullmessage      = $posttext;
630                 $eventdata->fullmessageformat = FORMAT_PLAIN;
631                 $eventdata->fullmessagehtml  = $posthtml;
632                 $eventdata->notification = 1;
634                 $smallmessagestrings = new stdClass();
635                 $smallmessagestrings->user = fullname($userfrom);
636                 $smallmessagestrings->forumname = "{$course->shortname}: ".format_string($forum->name,true).": ".$discussion->name;
637                 $smallmessagestrings->message = $post->message;
638                 //make sure strings are in message recipients language
639                 $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
641                 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
642                 $eventdata->contexturlname = $discussion->name;
644                 $mailresult = message_send($eventdata);
645                 if (!$mailresult){
646                     mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
647                          " ($userto->email) .. not trying again.");
648                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
649                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
650                     $errorcount[$post->id]++;
651                 } else {
652                     $mailcount[$post->id]++;
654                 // Mark post as read if forum_usermarksread is set off
655                     if (!$CFG->forum_usermarksread) {
656                         $userto->markposts[$post->id] = $post->id;
657                     }
658                 }
660                 mtrace('post '.$post->id. ': '.$post->subject);
661             }
663             // mark processed posts as read
664             forum_tp_mark_posts_read($userto, $userto->markposts);
665         }
666     }
668     if ($posts) {
669         foreach ($posts as $post) {
670             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
671             if ($errorcount[$post->id]) {
672                 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
673             }
674         }
675     }
677     // release some memory
678     unset($subscribedusers);
679     unset($mailcount);
680     unset($errorcount);
682     cron_setup_user();
684     $sitetimezone = $CFG->timezone;
686     // Now see if there are any digest mails waiting to be sent, and if we should send them
688     mtrace('Starting digest processing...');
690     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
692     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
693         set_config('digestmailtimelast', 0);
694     }
696     $timenow = time();
697     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
699     // Delete any really old ones (normally there shouldn't be any)
700     $weekago = $timenow - (7 * 24 * 3600);
701     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
702     mtrace ('Cleaned old digest records');
704     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
706         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
708         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
710         if ($digestposts_rs->valid()) {
712             // We have work to do
713             $usermailcount = 0;
715             //caches - reuse the those filled before too
716             $discussionposts = array();
717             $userdiscussions = array();
719             foreach ($digestposts_rs as $digestpost) {
720                 if (!isset($users[$digestpost->userid])) {
721                     if ($user = $DB->get_record('user', array('id' => $digestpost->userid))) {
722                         $users[$digestpost->userid] = $user;
723                     } else {
724                         continue;
725                     }
726                 }
727                 $postuser = $users[$digestpost->userid];
729                 if (!isset($posts[$digestpost->postid])) {
730                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
731                         $posts[$digestpost->postid] = $post;
732                     } else {
733                         continue;
734                     }
735                 }
736                 $discussionid = $digestpost->discussionid;
737                 if (!isset($discussions[$discussionid])) {
738                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
739                         $discussions[$discussionid] = $discussion;
740                     } else {
741                         continue;
742                     }
743                 }
744                 $forumid = $discussions[$discussionid]->forum;
745                 if (!isset($forums[$forumid])) {
746                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
747                         $forums[$forumid] = $forum;
748                     } else {
749                         continue;
750                     }
751                 }
753                 $courseid = $forums[$forumid]->course;
754                 if (!isset($courses[$courseid])) {
755                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
756                         $courses[$courseid] = $course;
757                     } else {
758                         continue;
759                     }
760                 }
762                 if (!isset($coursemodules[$forumid])) {
763                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
764                         $coursemodules[$forumid] = $cm;
765                     } else {
766                         continue;
767                     }
768                 }
769                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
770                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
771             }
772             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
774             // Data collected, start sending out emails to each user
775             foreach ($userdiscussions as $userid => $thesediscussions) {
777                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
779                 cron_setup_user();
781                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
783                 // First of all delete all the queue entries for this user
784                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
785                 $userto = $users[$userid];
787                 // Override the language and timezone of the "current" user, so that
788                 // mail is customised for the receiver.
789                 cron_setup_user($userto);
791                 // init caches
792                 $userto->viewfullnames = array();
793                 $userto->canpost       = array();
794                 $userto->markposts     = array();
796                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
798                 $headerdata = new stdClass();
799                 $headerdata->sitename = format_string($site->fullname, true);
800                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
802                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
803                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
805                 $posthtml = "<head>";
806 /*                foreach ($CFG->stylesheets as $stylesheet) {
807                     //TODO: MDL-21120
808                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
809                 }*/
810                 $posthtml .= "</head>\n<body id=\"email\">\n";
811                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
813                 foreach ($thesediscussions as $discussionid) {
815                     @set_time_limit(120);   // to be reset for each post
817                     $discussion = $discussions[$discussionid];
818                     $forum      = $forums[$discussion->forum];
819                     $course     = $courses[$forum->course];
820                     $cm         = $coursemodules[$forum->id];
822                     //override language
823                     cron_setup_user($userto, $course);
825                     // Fill caches
826                     if (!isset($userto->viewfullnames[$forum->id])) {
827                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
828                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
829                     }
830                     if (!isset($userto->canpost[$discussion->id])) {
831                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
832                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
833                     }
835                     $strforums      = get_string('forums', 'forum');
836                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
837                     $canreply       = $userto->canpost[$discussion->id];
839                     $posttext .= "\n \n";
840                     $posttext .= '=====================================================================';
841                     $posttext .= "\n \n";
842                     $posttext .= "$course->shortname -> $strforums -> ".format_string($forum->name,true);
843                     if ($discussion->name != $forum->name) {
844                         $posttext  .= " -> ".format_string($discussion->name,true);
845                     }
846                     $posttext .= "\n";
848                     $posthtml .= "<p><font face=\"sans-serif\">".
849                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> -> ".
850                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
851                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
852                     if ($discussion->name == $forum->name) {
853                         $posthtml .= "</font></p>";
854                     } else {
855                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
856                     }
857                     $posthtml .= '<p>';
859                     $postsarray = $discussionposts[$discussionid];
860                     sort($postsarray);
862                     foreach ($postsarray as $postid) {
863                         $post = $posts[$postid];
865                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
866                             $userfrom = $users[$post->userid];
867                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
868                             $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
869                         } else {
870                             mtrace('Could not find user '.$post->userid);
871                             continue;
872                         }
874                         if (!isset($userfrom->groups[$forum->id])) {
875                             if (!isset($userfrom->groups)) {
876                                 $userfrom->groups = array();
877                                 $users[$userfrom->id]->groups = array();
878                             }
879                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
880                             $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
881                         }
883                         $userfrom->customheaders = array ("Precedence: Bulk");
885                         if ($userto->maildigest == 2) {
886                             // Subjects only
887                             $by = new stdClass();
888                             $by->name = fullname($userfrom);
889                             $by->date = userdate($post->modified);
890                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
891                             $posttext .= "\n---------------------------------------------------------------------";
893                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
894                             $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>';
896                         } else {
897                             // The full treatment
898                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
899                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
901                         // Create an array of postid's for this user to mark as read.
902                             if (!$CFG->forum_usermarksread) {
903                                 $userto->markposts[$post->id] = $post->id;
904                             }
905                         }
906                     }
907                     if ($canunsubscribe) {
908                         $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>";
909                     } else {
910                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
911                     }
912                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
913                 }
914                 $posthtml .= '</body>';
916                 if (empty($userto->mailformat) || $userto->mailformat != 1) {
917                     // This user DOESN'T want to receive HTML
918                     $posthtml = '';
919                 }
921                 $attachment = $attachname='';
922                 $usetrueaddress = true;
923                 //directly email forum digests rather than sending them via messaging
924                 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname, $usetrueaddress, $CFG->forum_replytouser);
926                 if (!$mailresult) {
927                     mtrace("ERROR!");
928                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
929                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
930                 } else {
931                     mtrace("success.");
932                     $usermailcount++;
934                     // Mark post as read if forum_usermarksread is set off
935                     forum_tp_mark_posts_read($userto, $userto->markposts);
936                 }
937             }
938         }
939     /// We have finishied all digest emails, update $CFG->digestmailtimelast
940         set_config('digestmailtimelast', $timenow);
941     }
943     cron_setup_user();
945     if (!empty($usermailcount)) {
946         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
947     }
949     if (!empty($CFG->forum_lastreadclean)) {
950         $timenow = time();
951         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
952             set_config('forum_lastreadclean', $timenow);
953             mtrace('Removing old forum read tracking info...');
954             forum_tp_clean_read_records();
955         }
956     } else {
957         set_config('forum_lastreadclean', time());
958     }
961     return true;
964 /**
965  * Builds and returns the body of the email notification in plain text.
966  *
967  * @global object
968  * @global object
969  * @uses CONTEXT_MODULE
970  * @param object $course
971  * @param object $cm
972  * @param object $forum
973  * @param object $discussion
974  * @param object $post
975  * @param object $userfrom
976  * @param object $userto
977  * @param boolean $bare
978  * @return string The email body in plain text format.
979  */
980 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
981     global $CFG, $USER;
983     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
985     if (!isset($userto->viewfullnames[$forum->id])) {
986         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
987     } else {
988         $viewfullnames = $userto->viewfullnames[$forum->id];
989     }
991     if (!isset($userto->canpost[$discussion->id])) {
992         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
993     } else {
994         $canreply = $userto->canpost[$discussion->id];
995     }
997     $by = New stdClass;
998     $by->name = fullname($userfrom, $viewfullnames);
999     $by->date = userdate($post->modified, "", $userto->timezone);
1001     $strbynameondate = get_string('bynameondate', 'forum', $by);
1003     $strforums = get_string('forums', 'forum');
1005     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1007     $posttext = '';
1009     if (!$bare) {
1010         $posttext  = "$course->shortname -> $strforums -> ".format_string($forum->name,true);
1012         if ($discussion->name != $forum->name) {
1013             $posttext  .= " -> ".format_string($discussion->name,true);
1014         }
1015     }
1017     // add absolute file links
1018     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
1020     $posttext .= "\n---------------------------------------------------------------------\n";
1021     $posttext .= format_string($post->subject,true);
1022     if ($bare) {
1023         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1024     }
1025     $posttext .= "\n".$strbynameondate."\n";
1026     $posttext .= "---------------------------------------------------------------------\n";
1027     $posttext .= format_text_email($post->message, $post->messageformat);
1028     $posttext .= "\n\n";
1029     $posttext .= forum_print_attachments($post, $cm, "text");
1031     if (!$bare && $canreply) {
1032         $posttext .= "---------------------------------------------------------------------\n";
1033         $posttext .= get_string("postmailinfo", "forum", $course->shortname)."\n";
1034         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1035     }
1036     if (!$bare && $canunsubscribe) {
1037         $posttext .= "\n---------------------------------------------------------------------\n";
1038         $posttext .= get_string("unsubscribe", "forum");
1039         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1040     }
1042     return $posttext;
1045 /**
1046  * Builds and returns the body of the email notification in html format.
1047  *
1048  * @global object
1049  * @param object $course
1050  * @param object $cm
1051  * @param object $forum
1052  * @param object $discussion
1053  * @param object $post
1054  * @param object $userfrom
1055  * @param object $userto
1056  * @return string The email text in HTML format
1057  */
1058 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1059     global $CFG;
1061     if ($userto->mailformat != 1) {  // Needs to be HTML
1062         return '';
1063     }
1065     if (!isset($userto->canpost[$discussion->id])) {
1066         $canreply = forum_user_can_post($forum, $discussion, $userto);
1067     } else {
1068         $canreply = $userto->canpost[$discussion->id];
1069     }
1071     $strforums = get_string('forums', 'forum');
1072     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1074     $posthtml = '<head>';
1075 /*    foreach ($CFG->stylesheets as $stylesheet) {
1076         //TODO: MDL-21120
1077         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1078     }*/
1079     $posthtml .= '</head>';
1080     $posthtml .= "\n<body id=\"email\">\n\n";
1082     $posthtml .= '<div class="navbar">'.
1083     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$course->shortname.'</a> &raquo; '.
1084     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1085     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1086     if ($discussion->name == $forum->name) {
1087         $posthtml .= '</div>';
1088     } else {
1089         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1090                      format_string($discussion->name,true).'</a></div>';
1091     }
1092     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1094     if ($canunsubscribe) {
1095         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1096                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1097                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1098     }
1100     $posthtml .= '</body>';
1102     return $posthtml;
1106 /**
1107  *
1108  * @param object $course
1109  * @param object $user
1110  * @param object $mod TODO this is not used in this function, refactor
1111  * @param object $forum
1112  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1113  */
1114 function forum_user_outline($course, $user, $mod, $forum) {
1115     global $CFG;
1116     require_once("$CFG->libdir/gradelib.php");
1117     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1118     if (empty($grades->items[0]->grades)) {
1119         $grade = false;
1120     } else {
1121         $grade = reset($grades->items[0]->grades);
1122     }
1124     $count = forum_count_user_posts($forum->id, $user->id);
1126     if ($count && $count->postcount > 0) {
1127         $result = new stdClass();
1128         $result->info = get_string("numposts", "forum", $count->postcount);
1129         $result->time = $count->lastpost;
1130         if ($grade) {
1131             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1132         }
1133         return $result;
1134     } else if ($grade) {
1135         $result = new stdClass();
1136         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1137         $result->time = $grade->dategraded;
1138         return $result;
1139     }
1140     return NULL;
1144 /**
1145  * @global object
1146  * @global object
1147  * @param object $coure
1148  * @param object $user
1149  * @param object $mod
1150  * @param object $forum
1151  */
1152 function forum_user_complete($course, $user, $mod, $forum) {
1153     global $CFG,$USER, $OUTPUT;
1154     require_once("$CFG->libdir/gradelib.php");
1156     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1157     if (!empty($grades->items[0]->grades)) {
1158         $grade = reset($grades->items[0]->grades);
1159         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1160         if ($grade->str_feedback) {
1161             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1162         }
1163     }
1165     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1167         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1168             print_error('invalidcoursemodule');
1169         }
1170         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1172         foreach ($posts as $post) {
1173             if (!isset($discussions[$post->discussion])) {
1174                 continue;
1175             }
1176             $discussion = $discussions[$post->discussion];
1178             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1179         }
1180     } else {
1181         echo "<p>".get_string("noposts", "forum")."</p>";
1182     }
1190 /**
1191  * @global object
1192  * @global object
1193  * @global object
1194  * @param array $courses
1195  * @param array $htmlarray
1196  */
1197 function forum_print_overview($courses,&$htmlarray) {
1198     global $USER, $CFG, $DB, $SESSION;
1200     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1201         return array();
1202     }
1204     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1205         return;
1206     }
1209     // get all forum logs in ONE query (much better!)
1210     $params = array();
1211     $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {log} l "
1212         ." JOIN {course_modules} cm ON cm.id = cmid "
1213         ." WHERE (";
1214     foreach ($courses as $course) {
1215         $sql .= '(l.course = ? AND l.time > ?) OR ';
1216         $params[] = $course->id;
1217         $params[] = $course->lastaccess;
1218     }
1219     $sql = substr($sql,0,-3); // take off the last OR
1221     $sql .= ") AND l.module = 'forum' AND action = 'add post' "
1222         ." AND userid != ? GROUP BY cmid,l.course,instance";
1224     $params[] = $USER->id;
1226     if (!$new = $DB->get_records_sql($sql, $params)) {
1227         $new = array(); // avoid warnings
1228     }
1230     // also get all forum tracking stuff ONCE.
1231     $trackingforums = array();
1232     foreach ($forums as $forum) {
1233         if (forum_tp_can_track_forums($forum)) {
1234             $trackingforums[$forum->id] = $forum;
1235         }
1236     }
1238     if (count($trackingforums) > 0) {
1239         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1240         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1241             ' FROM {forum_posts} p '.
1242             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1243             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1244         $params = array($USER->id);
1246         foreach ($trackingforums as $track) {
1247             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1248             $params[] = $track->id;
1249             if (isset($SESSION->currentgroup[$track->course])) {
1250                 $groupid =  $SESSION->currentgroup[$track->course];
1251             } else {
1252                 $groupid = groups_get_all_groups($track->course, $USER->id);
1253                 if (is_array($groupid)) {
1254                     $groupid = array_shift(array_keys($groupid));
1255                     $SESSION->currentgroup[$track->course] = $groupid;
1256                 } else {
1257                     $groupid = 0;
1258                 }
1259             }
1260             $params[] = $groupid;
1261         }
1262         $sql = substr($sql,0,-3); // take off the last OR
1263         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1264         $params[] = $cutoffdate;
1266         if (!$unread = $DB->get_records_sql($sql, $params)) {
1267             $unread = array();
1268         }
1269     } else {
1270         $unread = array();
1271     }
1273     if (empty($unread) and empty($new)) {
1274         return;
1275     }
1277     $strforum = get_string('modulename','forum');
1278     $strnumunread = get_string('overviewnumunread','forum');
1279     $strnumpostssince = get_string('overviewnumpostssince','forum');
1281     foreach ($forums as $forum) {
1282         $str = '';
1283         $count = 0;
1284         $thisunread = 0;
1285         $showunread = false;
1286         // either we have something from logs, or trackposts, or nothing.
1287         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1288             $count = $new[$forum->id]->count;
1289         }
1290         if (array_key_exists($forum->id,$unread)) {
1291             $thisunread = $unread[$forum->id]->count;
1292             $showunread = true;
1293         }
1294         if ($count > 0 || $thisunread > 0) {
1295             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1296                 $forum->name.'</a></div>';
1297             $str .= '<div class="info">';
1298             $str .= $count.' '.$strnumpostssince;
1299             if (!empty($showunread)) {
1300                 $str .= '<br />'.$thisunread .' '.$strnumunread;
1301             }
1302             $str .= '</div></div>';
1303         }
1304         if (!empty($str)) {
1305             if (!array_key_exists($forum->course,$htmlarray)) {
1306                 $htmlarray[$forum->course] = array();
1307             }
1308             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1309                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1310             }
1311             $htmlarray[$forum->course]['forum'] .= $str;
1312         }
1313     }
1316 /**
1317  * Given a course and a date, prints a summary of all the new
1318  * messages posted in the course since that date
1319  *
1320  * @global object
1321  * @global object
1322  * @global object
1323  * @uses CONTEXT_MODULE
1324  * @uses VISIBLEGROUPS
1325  * @param object $course
1326  * @param bool $viewfullnames capability
1327  * @param int $timestart
1328  * @return bool success
1329  */
1330 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1331     global $CFG, $USER, $DB, $OUTPUT;
1333     // do not use log table if possible, it may be huge and is expensive to join with other tables
1335     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1336                                               d.timestart, d.timeend, d.userid AS duserid,
1337                                               u.firstname, u.lastname, u.email, u.picture
1338                                          FROM {forum_posts} p
1339                                               JOIN {forum_discussions} d ON d.id = p.discussion
1340                                               JOIN {forum} f             ON f.id = d.forum
1341                                               JOIN {user} u              ON u.id = p.userid
1342                                         WHERE p.created > ? AND f.course = ?
1343                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1344          return false;
1345     }
1347     $modinfo =& get_fast_modinfo($course);
1349     $groupmodes = array();
1350     $cms    = array();
1352     $strftimerecent = get_string('strftimerecent');
1354     $printposts = array();
1355     foreach ($posts as $post) {
1356         if (!isset($modinfo->instances['forum'][$post->forum])) {
1357             // not visible
1358             continue;
1359         }
1360         $cm = $modinfo->instances['forum'][$post->forum];
1361         if (!$cm->uservisible) {
1362             continue;
1363         }
1364         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1366         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1367             continue;
1368         }
1370         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1371           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1372             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1373                 continue;
1374             }
1375         }
1377         $groupmode = groups_get_activity_groupmode($cm, $course);
1379         if ($groupmode) {
1380             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1381                 // oki (Open discussions have groupid -1)
1382             } else {
1383                 // separate mode
1384                 if (isguestuser()) {
1385                     // shortcut
1386                     continue;
1387                 }
1389                 if (is_null($modinfo->groups)) {
1390                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1391                 }
1393                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1394                     continue;
1395                 }
1396             }
1397         }
1399         $printposts[] = $post;
1400     }
1401     unset($posts);
1403     if (!$printposts) {
1404         return false;
1405     }
1407     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1408     echo "\n<ul class='unlist'>\n";
1410     foreach ($printposts as $post) {
1411         $subjectclass = empty($post->parent) ? ' bold' : '';
1413         echo '<li><div class="head">'.
1414                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1415                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1416              '</div>';
1417         echo '<div class="info'.$subjectclass.'">';
1418         if (empty($post->parent)) {
1419             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1420         } else {
1421             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1422         }
1423         $post->subject = break_up_long_words(format_string($post->subject, true));
1424         echo $post->subject;
1425         echo "</a>\"</div></li>\n";
1426     }
1428     echo "</ul>\n";
1430     return true;
1433 /**
1434  * Return grade for given user or all users.
1435  *
1436  * @global object
1437  * @global object
1438  * @param object $forum
1439  * @param int $userid optional user id, 0 means all users
1440  * @return array array of grades, false if none
1441  */
1442 function forum_get_user_grades($forum, $userid=0) {
1443     global $CFG;
1445     require_once($CFG->dirroot.'/rating/lib.php');
1446     $rm = new rating_manager();
1448     $ratingoptions = new stdclass();
1450     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1451     $ratingoptions->modulename = 'forum';
1452     $ratingoptions->moduleid   = $forum->id;
1453     //$ratingoptions->cmidnumber = $forum->cmidnumber;
1455     $ratingoptions->userid = $userid;
1456     $ratingoptions->aggregationmethod = $forum->assessed;
1457     $ratingoptions->scaleid = $forum->scale;
1458     $ratingoptions->itemtable = 'forum_posts';
1459     $ratingoptions->itemtableusercolumn = 'userid';
1461     return $rm->get_user_grades($ratingoptions);
1464 /**
1465  * Update activity grades
1466  *
1467  * @global object
1468  * @global object
1469  * @param object $forum
1470  * @param int $userid specific user only, 0 means all
1471  * @param boolean $nullifnone return null if grade does not exist
1472  * @return void
1473  */
1474 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1475     global $CFG, $DB;
1476     require_once($CFG->libdir.'/gradelib.php');
1478     if (!$forum->assessed) {
1479         forum_grade_item_update($forum);
1481     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1482         forum_grade_item_update($forum, $grades);
1484     } else if ($userid and $nullifnone) {
1485         $grade = new stdClass();
1486         $grade->userid   = $userid;
1487         $grade->rawgrade = NULL;
1488         forum_grade_item_update($forum, $grade);
1490     } else {
1491         forum_grade_item_update($forum);
1492     }
1495 /**
1496  * Update all grades in gradebook.
1497  * @global object
1498  */
1499 function forum_upgrade_grades() {
1500     global $DB;
1502     $sql = "SELECT COUNT('x')
1503               FROM {forum} f, {course_modules} cm, {modules} m
1504              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1505     $count = $DB->count_records_sql($sql);
1507     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1508               FROM {forum} f, {course_modules} cm, {modules} m
1509              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1510     $rs = $DB->get_recordset_sql($sql);
1511     if ($rs->valid()) {
1512         $pbar = new progress_bar('forumupgradegrades', 500, true);
1513         $i=0;
1514         foreach ($rs as $forum) {
1515             $i++;
1516             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1517             forum_update_grades($forum, 0, false);
1518             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1519         }
1520     }
1521     $rs->close();
1524 /**
1525  * Create/update grade item for given forum
1526  *
1527  * @global object
1528  * @uses GRADE_TYPE_NONE
1529  * @uses GRADE_TYPE_VALUE
1530  * @uses GRADE_TYPE_SCALE
1531  * @param object $forum object with extra cmidnumber
1532  * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
1533  * @return int 0 if ok
1534  */
1535 function forum_grade_item_update($forum, $grades=NULL) {
1536     global $CFG;
1537     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1538         require_once($CFG->libdir.'/gradelib.php');
1539     }
1541     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1543     if (!$forum->assessed or $forum->scale == 0) {
1544         $params['gradetype'] = GRADE_TYPE_NONE;
1546     } else if ($forum->scale > 0) {
1547         $params['gradetype'] = GRADE_TYPE_VALUE;
1548         $params['grademax']  = $forum->scale;
1549         $params['grademin']  = 0;
1551     } else if ($forum->scale < 0) {
1552         $params['gradetype'] = GRADE_TYPE_SCALE;
1553         $params['scaleid']   = -$forum->scale;
1554     }
1556     if ($grades  === 'reset') {
1557         $params['reset'] = true;
1558         $grades = NULL;
1559     }
1561     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1564 /**
1565  * Delete grade item for given forum
1566  *
1567  * @global object
1568  * @param object $forum object
1569  * @return object grade_item
1570  */
1571 function forum_grade_item_delete($forum) {
1572     global $CFG;
1573     require_once($CFG->libdir.'/gradelib.php');
1575     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1579 /**
1580  * Returns the users with data in one forum
1581  * (users with records in forum_subscriptions, forum_posts, students)
1582  *
1583  * @global object
1584  * @global object
1585  * @param int $forumid
1586  * @return mixed array or false if none
1587  */
1588 function forum_get_participants($forumid) {
1590     global $CFG, $DB;
1592     //Get students from forum_subscriptions
1593     $st_subscriptions = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1594                                          FROM {user} u,
1595                                               {forum_subscriptions} s
1596                                          WHERE s.forum = ? AND
1597                                                u.id = s.userid", array($forumid));
1598     //Get students from forum_posts
1599     $st_posts = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1600                                  FROM {user} u,
1601                                       {forum_discussions} d,
1602                                       {forum_posts} p
1603                                  WHERE d.forum = ? AND
1604                                        p.discussion = d.id AND
1605                                        u.id = p.userid", array($forumid));
1607     //Get students from the ratings table
1608     $st_ratings = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1609                                    FROM {user} u,
1610                                         {forum_discussions} d,
1611                                         {forum_posts} p,
1612                                         {ratings} r
1613                                    WHERE d.forum = ? AND
1614                                          p.discussion = d.id AND
1615                                          r.post = p.id AND
1616                                          u.id = r.userid", array($forumid));
1618     //Add st_posts to st_subscriptions
1619     if ($st_posts) {
1620         foreach ($st_posts as $st_post) {
1621             $st_subscriptions[$st_post->id] = $st_post;
1622         }
1623     }
1624     //Add st_ratings to st_subscriptions
1625     if ($st_ratings) {
1626         foreach ($st_ratings as $st_rating) {
1627             $st_subscriptions[$st_rating->id] = $st_rating;
1628         }
1629     }
1630     //Return st_subscriptions array (it contains an array of unique users)
1631     return ($st_subscriptions);
1634 /**
1635  * This function returns if a scale is being used by one forum
1636  *
1637  * @global object
1638  * @param int $forumid
1639  * @param int $scaleid negative number
1640  * @return bool
1641  */
1642 function forum_scale_used ($forumid,$scaleid) {
1643     global $DB;
1644     $return = false;
1646     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1648     if (!empty($rec) && !empty($scaleid)) {
1649         $return = true;
1650     }
1652     return $return;
1655 /**
1656  * Checks if scale is being used by any instance of forum
1657  *
1658  * This is used to find out if scale used anywhere
1659  *
1660  * @global object
1661  * @param $scaleid int
1662  * @return boolean True if the scale is used by any forum
1663  */
1664 function forum_scale_used_anywhere($scaleid) {
1665     global $DB;
1666     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1667         return true;
1668     } else {
1669         return false;
1670     }
1673 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1675 /**
1676  * Gets a post with all info ready for forum_print_post
1677  * Most of these joins are just to get the forum id
1678  *
1679  * @global object
1680  * @global object
1681  * @param int $postid
1682  * @return mixed array of posts or false
1683  */
1684 function forum_get_post_full($postid) {
1685     global $CFG, $DB;
1687     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1688                              FROM {forum_posts} p
1689                                   JOIN {forum_discussions} d ON p.discussion = d.id
1690                                   LEFT JOIN {user} u ON p.userid = u.id
1691                             WHERE p.id = ?", array($postid));
1694 /**
1695  * Gets posts with all info ready for forum_print_post
1696  * We pass forumid in because we always know it so no need to make a
1697  * complicated join to find it out.
1698  *
1699  * @global object
1700  * @global object
1701  * @return mixed array of posts or false
1702  */
1703 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1704     global $CFG, $DB;
1706     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1707                               FROM {forum_posts} p
1708                          LEFT JOIN {user} u ON p.userid = u.id
1709                              WHERE p.discussion = ?
1710                                AND p.parent > 0 $sort", array($discussion));
1713 /**
1714  * Gets all posts in discussion including top parent.
1715  *
1716  * @global object
1717  * @global object
1718  * @global object
1719  * @param int $discussionid
1720  * @param string $sort
1721  * @param bool $tracking does user track the forum?
1722  * @return array of posts
1723  */
1724 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1725     global $CFG, $DB, $USER;
1727     $tr_sel  = "";
1728     $tr_join = "";
1729     $params = array();
1731     if ($tracking) {
1732         $now = time();
1733         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1734         $tr_sel  = ", fr.id AS postread";
1735         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1736         $params[] = $USER->id;
1737     }
1739     $params[] = $discussionid;
1740     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1741                                      FROM {forum_posts} p
1742                                           LEFT JOIN {user} u ON p.userid = u.id
1743                                           $tr_join
1744                                     WHERE p.discussion = ?
1745                                  ORDER BY $sort", $params)) {
1746         return array();
1747     }
1749     foreach ($posts as $pid=>$p) {
1750         if ($tracking) {
1751             if (forum_tp_is_post_old($p)) {
1752                  $posts[$pid]->postread = true;
1753             }
1754         }
1755         if (!$p->parent) {
1756             continue;
1757         }
1758         if (!isset($posts[$p->parent])) {
1759             continue; // parent does not exist??
1760         }
1761         if (!isset($posts[$p->parent]->children)) {
1762             $posts[$p->parent]->children = array();
1763         }
1764         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1765     }
1767     return $posts;
1770 /**
1771  * Gets posts with all info ready for forum_print_post
1772  * We pass forumid in because we always know it so no need to make a
1773  * complicated join to find it out.
1774  *
1775  * @global object
1776  * @global object
1777  * @param int $parent
1778  * @param int $forumid
1779  * @return array
1780  */
1781 function forum_get_child_posts($parent, $forumid) {
1782     global $CFG, $DB;
1784     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1785                               FROM {forum_posts} p
1786                          LEFT JOIN {user} u ON p.userid = u.id
1787                              WHERE p.parent = ?
1788                           ORDER BY p.created ASC", array($parent));
1791 /**
1792  * An array of forum objects that the user is allowed to read/search through.
1793  *
1794  * @global object
1795  * @global object
1796  * @global object
1797  * @param int $userid
1798  * @param int $courseid if 0, we look for forums throughout the whole site.
1799  * @return array of forum objects, or false if no matches
1800  *         Forum objects have the following attributes:
1801  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1802  *         viewhiddentimedposts
1803  */
1804 function forum_get_readable_forums($userid, $courseid=0) {
1806     global $CFG, $DB, $USER;
1807     require_once($CFG->dirroot.'/course/lib.php');
1809     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1810         print_error('notinstalled', 'forum');
1811     }
1813     if ($courseid) {
1814         $courses = $DB->get_records('course', array('id' => $courseid));
1815     } else {
1816         // If no course is specified, then the user can see SITE + his courses.
1817         $courses1 = $DB->get_records('course', array('id' => SITEID));
1818         $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
1819         $courses = array_merge($courses1, $courses2);
1820     }
1821     if (!$courses) {
1822         return array();
1823     }
1825     $readableforums = array();
1827     foreach ($courses as $course) {
1829         $modinfo =& get_fast_modinfo($course);
1830         if (is_null($modinfo->groups)) {
1831             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1832         }
1834         if (empty($modinfo->instances['forum'])) {
1835             // hmm, no forums?
1836             continue;
1837         }
1839         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1841         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1842             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1843                 continue;
1844             }
1845             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1846             $forum = $courseforums[$forumid];
1847             $forum->context = $context;
1848             $forum->cm = $cm;
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{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
1936             $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
1937         }
1939         $cm = $forum->cm;
1940         $context = $forum->context;
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, 'qanda'.$forumid.'_0000');
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, 'grps'.$forumid.'_0000');
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{$forumid} AND $selects)";
1962             $params['forum'.$forumid] = $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  * @param string $fields requested user fields (with "u." table prefix)
2807  * @return array list of users.
2808  */
2809 function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
2810     global $CFG, $DB;
2811     $params = array($forum->id);
2813     if ($groupid) {
2814         $grouptables = ", {groups_members} gm ";
2815         $groupselect = "AND gm.groupid = ? AND u.id = gm.userid";
2816         $params[] = $groupid;
2817     } else  {
2818         $grouptables = '';
2819         $groupselect = '';
2820     }
2822     if (empty($fields)) {
2823         $fields ="u.id,
2824                   u.username,
2825                   u.firstname,
2826                   u.lastname,
2827                   u.maildisplay,
2828                   u.mailformat,
2829                   u.maildigest,
2830                   u.imagealt,
2831                   u.email,
2832                   u.city,
2833                   u.country,
2834                   u.lastaccess,
2835                   u.lastlogin,
2836                   u.picture,
2837                   u.timezone,
2838                   u.theme,
2839                   u.lang,
2840                   u.trackforums,
2841                   u.mnethostid";
2842     }
2844     if (forum_is_forcesubscribed($forum)) {
2845         if (empty($context)) {
2846             $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2847             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2848         }
2849         $sort = "u.email ASC";
2850         $results = forum_get_potential_subscribers($context, $groupid, $fields, $sort);
2851     } else {
2852         $results = $DB->get_records_sql("SELECT $fields
2853                               FROM {user} u,
2854                                    {forum_subscriptions} s $grouptables
2855                              WHERE s.forum = ?
2856                                AND s.userid = u.id
2857                                AND u.deleted = 0  $groupselect
2858                           ORDER BY u.email ASC", $params);
2859     }
2861     static $guestid = null;
2863     if (is_null($guestid)) {
2864         if ($guest = guest_user()) {
2865             $guestid = $guest->id;
2866         } else {
2867             $guestid = 0;
2868         }
2869     }
2871     // Guest user should never be subscribed to a forum.
2872     unset($results[$guestid]);
2874     return $results;
2879 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2882 /**
2883  * @global object
2884  * @global object
2885  * @param int $courseid
2886  * @param string $type
2887  */
2888 function forum_get_course_forum($courseid, $type) {
2889 // How to set up special 1-per-course forums
2890     global $CFG, $DB, $OUTPUT;
2892     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2893         // There should always only be ONE, but with the right combination of
2894         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2895         foreach ($forums as $forum) {
2896             return $forum;   // ie the first one
2897         }
2898     }
2900     // Doesn't exist, so create one now.
2901     $forum->course = $courseid;
2902     $forum->type = "$type";
2903     switch ($forum->type) {
2904         case "news":
2905             $forum->name  = get_string("namenews", "forum");
2906             $forum->intro = get_string("intronews", "forum");
2907             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2908             $forum->assessed = 0;
2909             if ($courseid == SITEID) {
2910                 $forum->name  = get_string("sitenews");
2911                 $forum->forcesubscribe = 0;
2912             }
2913             break;
2914         case "social":
2915             $forum->name  = get_string("namesocial", "forum");
2916             $forum->intro = get_string("introsocial", "forum");
2917             $forum->assessed = 0;
2918             $forum->forcesubscribe = 0;
2919             break;
2920         case "blog":
2921             $forum->name = get_string('blogforum', 'forum');
2922             $forum->intro = get_string('introblog', 'forum');
2923             $forum->assessed = 0;
2924             $forum->forcesubscribe = 0;
2925             break;
2926         default:
2927             echo $OUTPUT->notification("That forum type doesn't exist!");
2928             return false;
2929             break;
2930     }
2932     $forum->timemodified = time();
2933     $forum->id = $DB->insert_record("forum", $forum);
2935     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2936         echo $OUTPUT->notification("Could not find forum module!!");
2937         return false;
2938     }
2939     $mod = new stdClass();
2940     $mod->course = $courseid;
2941     $mod->module = $module->id;
2942     $mod->instance = $forum->id;
2943     $mod->section = 0;
2944     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
2945         echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
2946         return false;
2947     }
2948     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
2949         echo $OUTPUT->notification("Could not add the new course module to that section");
2950         return false;
2951     }
2952     $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule));
2954     include_once("$CFG->dirroot/course/lib.php");
2955     rebuild_course_cache($courseid);
2957     return $DB->get_record("forum", array("id" => "$forum->id"));
2961 /**
2962  * Given the data about a posting, builds up the HTML to display it and
2963  * returns the HTML in a string.  This is designed for sending via HTML email.
2964  *
2965  * @global object
2966  * @param object $course
2967  * @param object $cm
2968  * @param object $forum
2969  * @param object $discussion
2970  * @param object $post
2971  * @param object $userform
2972  * @param object $userto
2973  * @param bool $ownpost
2974  * @param bool $reply
2975  * @param bool $link
2976  * @param bool $rate
2977  * @param string $footer
2978  * @return string
2979  */
2980 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
2981                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
2983     global $CFG, $OUTPUT;
2985     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2987     if (!isset($userto->viewfullnames[$forum->id])) {
2988         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
2989     } else {
2990         $viewfullnames = $userto->viewfullnames[$forum->id];
2991     }
2993     // add absolute file links
2994     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
2996     // format the post body
2997     $options = new stdClass();
2998     $options->para = true;
2999     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3001     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3003     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3004     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3005     $output .= '</td>';
3007     if ($post->parent) {
3008         $output .= '<td class="topic">';
3009     } else {
3010         $output .= '<td class="topic starter">';
3011     }
3012     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3014     $fullname = fullname($userfrom, $viewfullnames);
3015     $by = new stdClass();
3016     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3017     $by->date = userdate($post->modified, '', $userto->timezone);
3018     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3020     $output .= '</td></tr>';
3022     $output .= '<tr><td class="left side" valign="top">';
3024     if (isset($userfrom->groups)) {
3025         $groups = $userfrom->groups[$forum->id];
3026     } else {
3027         $groups = 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="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3113     global $USER, $CFG, $OUTPUT;
3115     require_once($CFG->libdir . '/filelib.php');
3117     // String cache
3118     static $str;
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 ($istracked && is_null($postisread)) {
3149         $postisread = forum_tp_is_post_read($USER->id, $post);
3150     }
3152     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3153         $output = '';
3154         if (!$dummyifcantsee) {
3155             if ($return) {
3156                 return $output;
3157             }
3158             echo $output;
3159             return;
3160         }
3161         $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3162         $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'));
3163         $output .= html_writer::start_tag('div', array('class'=>'row header'));
3164         $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3165         if ($post->parent) {
3166             $output .= html_writer::start_tag('div', array('class'=>'topic'));
3167         } else {
3168             $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3169         }
3170         $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class'=>'subject')); // Subject
3171         $output .= html_writer::tag('div', get_string('forumauthorhidden','forum'), array('class'=>'author')); // author
3172         $output .= html_writer::end_tag('div');
3173         $output .= html_writer::end_tag('div'); // row
3174         $output .= html_writer::start_tag('div', array('class'=>'row'));
3175         $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3176         $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3177         $output .= html_writer::end_tag('div'); // row
3178         $output .= html_writer::end_tag('div'); // forumpost
3180         if ($return) {
3181             return $output;
3182         }
3183         echo $output;
3184         return;
3185     }
3187     if (empty($str)) {
3188         $str = new stdClass;
3189         $str->edit         = get_string('edit', 'forum');
3190         $str->delete       = get_string('delete', 'forum');
3191         $str->reply        = get_string('reply', 'forum');
3192         $str->parent       = get_string('parent', 'forum');
3193         $str->pruneheading = get_string('pruneheading', 'forum');
3194         $str->prune        = get_string('prune', 'forum');
3195         $str->displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3196         $str->markread     = get_string('markread', 'forum');
3197         $str->markunread   = get_string('markunread', 'forum');
3198     }
3200     $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3202     // Build an object that represents the posting user
3203     $postuser = new stdClass;
3204     $postuser->id        = $post->userid;
3205     $postuser->firstname = $post->firstname;
3206     $postuser->lastname  = $post->lastname;
3207     $postuser->imagealt  = $post->imagealt;
3208     $postuser->picture   = $post->picture;
3209     $postuser->email     = $post->email;
3210     // Some handy things for later on
3211     $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3212     $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3214     // Prepare the groups the posting user belongs to
3215     if (isset($cm->cache->usersgroups)) {
3216         $groups = array();
3217         if (isset($cm->cache->usersgroups[$post->userid])) {
3218             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3219                 $groups[$gid] = $cm->cache->groups[$gid];
3220             }
3221         }
3222     } else {
3223         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3224     }
3226     // Prepare the attachements for the post, files then images
3227     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3229     // Determine if we need to shorten this post
3230     $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3233     // Prepare an array of commands
3234     $commands = array();
3236     // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3237     // Don't display the mark read / unread controls in this case.
3238     if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3239         $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3240         $text = $str->markunread;
3241         if (!$postisread) {
3242             $url->param('mark', 'read');
3243             $text = $str->markread;
3244         }
3245         if ($str->displaymode == FORUM_MODE_THREADED) {
3246             $url->param('parent', $post->parent);
3247         } else {
3248             $url->set_anchor('p'.$post->id);
3249         }
3250         $commands[] = array('url'=>$url, 'text'=>$text);
3251     }
3253     // Zoom in to the parent specifically
3254     if ($post->parent) {
3255         $url = new moodle_url($discussionlink);
3256         if ($str->displaymode == FORUM_MODE_THREADED) {
3257             $url->param('parent', $post->parent);
3258         } else {
3259             $url->set_anchor('p'.$post->parent);
3260         }
3261         $commands[] = array('url'=>$url, 'text'=>$str->parent);
3262     }
3264     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3265     $age = time() - $post->created;
3266     if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3267         $age = 0;
3268     }
3269     if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3270         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3271     }
3273     if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3274         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3275     }
3277     if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3278         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3279     }
3281     if ($reply) {
3282         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('reply'=>$post->id)), 'text'=>$str->reply);
3283     }
3285     if ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost'])) {
3286         $p = array('postid' => $post->id);
3287         require_once($CFG->libdir.'/portfoliolib.php');
3288         $button = new portfolio_add_button();
3289         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php');
3290         if (empty($attachments)) {
3291             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3292         } else {
3293             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3294         }
3296         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3297         if (!empty($porfoliohtml)) {
3298             $commands[] = $porfoliohtml;
3299         }
3300     }
3301     // Finished building commands
3304     // Begin output
3306     $output  = '';
3308     if ($istracked) {
3309         if ($postisread) {
3310             $forumpostclass = ' read';
3311         } else {
3312             $forumpostclass = ' unread';
3313             $output .= html_writer::tag('a', '', array('name'=>'unread'));
3314         }
3315     } else {
3316         // ignore trackign status if not tracked or tracked param missing
3317         $forumpostclass = '';
3318     }
3320     $topicclass = '';
3321     if (empty($post->parent)) {
3322         $topicclass = ' firstpost starter';
3323     }
3325     $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3326     $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass));
3327     $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3328     $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3329     $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3330     $output .= html_writer::end_tag('div');
3333     $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3335     $postsubject = $post->subject;
3336     if (empty($post->subjectnoformat)) {
3337         $postsubject = format_string($postsubject);
3338     }
3339     $output .= html_writer::tag('div', $postsubject, array('class'=>'subject'));
3341     $by = new stdClass();
3342     $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3343     $by->date = userdate($post->modified);
3344     $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author'));
3346     $output .= html_writer::end_tag('div'); //topic
3347     $output .= html_writer::end_tag('div'); //row
3349     $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3350     $output .= html_writer::start_tag('div', array('class'=>'left'));
3352     $groupoutput = '';
3353     if ($groups) {
3354         $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3355     }
3356     if (empty($groupoutput)) {
3357         $groupoutput = '&nbsp;';
3358     }
3359     $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3361     $output .= html_writer::end_tag('div'); //left side
3362     $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3363     $output .= html_writer::start_tag('div', array('class'=>'content'));
3364     if (!empty($attachments)) {
3365         $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3366     }
3368     $options = new stdClass;
3369     $options->para    = false;
3370     $options->trusted = $post->messagetrust;
3371     $options->context = $modcontext;
3372     if ($shortenpost) {
3373         // Prepare shortened version
3374         $postclass    = 'shortenedpost';
3375         $postcontent  = format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3376         $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
3377         $postcontent .= html_writer::tag('span', '('.get_string('numwords', 'moodle', count_words(strip_tags($post->message))).')...', array('class'=>'post-word-count'));
3378     } else {
3379         // Prepare whole post
3380         $postclass    = 'fullpost';
3381         $postcontent  = format_text($post->message, $post->messageformat, $options, $course->id);
3382         if (!empty($highlight)) {
3383             $postcontent = highlight($highlight, $postcontent);
3384         }
3385         $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
3386     }
3387     // Output the post content
3388     $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
3389     $output .= html_writer::end_tag('div'); // Content
3390     $output .= html_writer::end_tag('div'); // Content mask
3391     $output .= html_writer::end_tag('div'); // Row
3393     $output .= html_writer::start_tag('div', array('class'=>'row side'));
3394     $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
3395     $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
3397     // Output ratings
3398     if (!empty($post->rating)) {
3399         $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
3400     }
3402     // Output the commands
3403     $commandhtml = array();
3404     foreach ($commands as $command) {
3405         if (is_array($command)) {
3406             $commandhtml[] = html_writer::link($command['url'], $command['text']);
3407         } else {
3408             $commandhtml[] = $command;
3409         }
3410     }
3411     $output .= html_writer::tag('div', implode(' | ', $commandhtml), array('class'=>'commands'));
3413     // Output link to post if required
3414     if ($link) {
3415         if ($post->replies == 1) {
3416             $replystring = get_string('repliesone', 'forum', $post->replies);
3417         } else {
3418             $replystring = get_string('repliesmany', 'forum', $post->replies);
3419         }
3421         $output .= html_writer::start_tag('div', array('class'=>'link'));
3422         $output .= html_writer::link($discussionlink, get_string('discussthistopic', 'forum'));
3423         $output .= '&nbsp;('.$replystring.')';
3424         $output .= html_writer::end_tag('div'); // link
3425     }
3427     // Output footer if required
3428     if ($footer) {
3429         $output .= html_writer::tag('div', $footer, array('class'=>'footer'));
3430     }
3432     // Close remaining open divs
3433     $output .= html_writer::end_tag('div'); // content
3434     $output .= html_writer::end_tag('div'); // row
3435     $output .= html_writer::end_tag('div'); // forumpost
3437     // Mark the forum post as read if required
3438     if ($istracked && !$CFG->forum_usermarksread && !$postisread) {
3439         forum_tp_mark_post_read($USER->id, $post, $forum->id);
3440     }
3442     if ($return) {
3443         return $output;
3444     }
3445     echo $output;
3446     return;
3449 /**
3450  * Return rating related permissions
3451  * @param string $options the context id
3452  * @return array an associative array of the user's rating permissions
3453  */
3454 function forum_rating_permissions($contextid) {
3455     $context = get_context_instance_by_id($contextid);
3457     if (!$context) {
3458         print_error('invalidcontext');
3459         return null;
3460     } else {
3461         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));
3462     }
3465 /**
3466  * Returns the names of the table and columns necessary to check items for ratings
3467  * @return array an array containing the item table, item id and user id columns
3468  */
3469 function forum_rating_item_check_info() {
3470     return array('forum_posts','id','userid');
3474 /**
3475  * This function prints the overview of a discussion in the forum listing.
3476  * It needs some discussion information and some post information, these
3477  * happen to be combined for efficiency in the $post parameter by the function
3478  * that calls this one: forum_print_latest_discussions()
3479  *
3480  * @global object
3481  * @global object
3482  * @param object $post The post object (passed by reference for speed).
3483  * @param object $forum The forum object.
3484  * @param int $group Current group.
3485  * @param string $datestring Format to use for the dates.
3486  * @param boolean $cantrack Is tracking enabled for this forum.
3487  * @param boolean $forumtracked Is the user tracking this forum.
3488  * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
3489  */
3490 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
3491                                         $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) {
3493     global $USER, $CFG, $OUTPUT;
3495     static $rowcount;
3496     static $strmarkalldread;
3498     if (empty($modcontext)) {
3499         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3500             print_error('invalidcoursemodule');
3501         }