general MDL-26440 removed references to uid in SQL queries as its a reserved word...
[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     if (!isset($userto->viewfullnames[$forum->id])) {
984         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
985         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
986     } else {
987         $viewfullnames = $userto->viewfullnames[$forum->id];
988     }
990     if (!isset($userto->canpost[$discussion->id])) {
991         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->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     $posttext .= "\n---------------------------------------------------------------------\n";
1018     $posttext .= format_string($post->subject,true);
1019     if ($bare) {
1020         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1021     }
1022     $posttext .= "\n".$strbynameondate."\n";
1023     $posttext .= "---------------------------------------------------------------------\n";
1024     $posttext .= format_text_email($post->message, $post->messageformat);
1025     $posttext .= "\n\n";
1026     $posttext .= forum_print_attachments($post, $cm, "text");
1028     if (!$bare && $canreply) {
1029         $posttext .= "---------------------------------------------------------------------\n";
1030         $posttext .= get_string("postmailinfo", "forum", $course->shortname)."\n";
1031         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1032     }
1033     if (!$bare && $canunsubscribe) {
1034         $posttext .= "\n---------------------------------------------------------------------\n";
1035         $posttext .= get_string("unsubscribe", "forum");
1036         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1037     }
1039     return $posttext;
1042 /**
1043  * Builds and returns the body of the email notification in html format.
1044  *
1045  * @global object
1046  * @param object $course
1047  * @param object $cm
1048  * @param object $forum
1049  * @param object $discussion
1050  * @param object $post
1051  * @param object $userfrom
1052  * @param object $userto
1053  * @return string The email text in HTML format
1054  */
1055 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1056     global $CFG;
1058     if ($userto->mailformat != 1) {  // Needs to be HTML
1059         return '';
1060     }
1062     if (!isset($userto->canpost[$discussion->id])) {
1063         $canreply = forum_user_can_post($forum, $discussion, $userto);
1064     } else {
1065         $canreply = $userto->canpost[$discussion->id];
1066     }
1068     $strforums = get_string('forums', 'forum');
1069     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1071     $posthtml = '<head>';
1072 /*    foreach ($CFG->stylesheets as $stylesheet) {
1073         //TODO: MDL-21120
1074         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1075     }*/
1076     $posthtml .= '</head>';
1077     $posthtml .= "\n<body id=\"email\">\n\n";
1079     $posthtml .= '<div class="navbar">'.
1080     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$course->shortname.'</a> &raquo; '.
1081     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1082     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1083     if ($discussion->name == $forum->name) {
1084         $posthtml .= '</div>';
1085     } else {
1086         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1087                      format_string($discussion->name,true).'</a></div>';
1088     }
1089     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1091     if ($canunsubscribe) {
1092         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1093                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1094                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1095     }
1097     $posthtml .= '</body>';
1099     return $posthtml;
1103 /**
1104  *
1105  * @param object $course
1106  * @param object $user
1107  * @param object $mod TODO this is not used in this function, refactor
1108  * @param object $forum
1109  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1110  */
1111 function forum_user_outline($course, $user, $mod, $forum) {
1112     global $CFG;
1113     require_once("$CFG->libdir/gradelib.php");
1114     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1115     if (empty($grades->items[0]->grades)) {
1116         $grade = false;
1117     } else {
1118         $grade = reset($grades->items[0]->grades);
1119     }
1121     $count = forum_count_user_posts($forum->id, $user->id);
1123     if ($count && $count->postcount > 0) {
1124         $result = new stdClass();
1125         $result->info = get_string("numposts", "forum", $count->postcount);
1126         $result->time = $count->lastpost;
1127         if ($grade) {
1128             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1129         }
1130         return $result;
1131     } else if ($grade) {
1132         $result = new stdClass();
1133         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1134         $result->time = $grade->dategraded;
1135         return $result;
1136     }
1137     return NULL;
1141 /**
1142  * @global object
1143  * @global object
1144  * @param object $coure
1145  * @param object $user
1146  * @param object $mod
1147  * @param object $forum
1148  */
1149 function forum_user_complete($course, $user, $mod, $forum) {
1150     global $CFG,$USER, $OUTPUT;
1151     require_once("$CFG->libdir/gradelib.php");
1153     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1154     if (!empty($grades->items[0]->grades)) {
1155         $grade = reset($grades->items[0]->grades);
1156         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1157         if ($grade->str_feedback) {
1158             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1159         }
1160     }
1162     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1164         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1165             print_error('invalidcoursemodule');
1166         }
1167         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1169         foreach ($posts as $post) {
1170             if (!isset($discussions[$post->discussion])) {
1171                 continue;
1172             }
1173             $discussion = $discussions[$post->discussion];
1175             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1176         }
1177     } else {
1178         echo "<p>".get_string("noposts", "forum")."</p>";
1179     }
1187 /**
1188  * @global object
1189  * @global object
1190  * @global object
1191  * @param array $courses
1192  * @param array $htmlarray
1193  */
1194 function forum_print_overview($courses,&$htmlarray) {
1195     global $USER, $CFG, $DB, $SESSION;
1197     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1198         return array();
1199     }
1201     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1202         return;
1203     }
1206     // get all forum logs in ONE query (much better!)
1207     $params = array();
1208     $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {log} l "
1209         ." JOIN {course_modules} cm ON cm.id = cmid "
1210         ." WHERE (";
1211     foreach ($courses as $course) {
1212         $sql .= '(l.course = ? AND l.time > ?) OR ';
1213         $params[] = $course->id;
1214         $params[] = $course->lastaccess;
1215     }
1216     $sql = substr($sql,0,-3); // take off the last OR
1218     $sql .= ") AND l.module = 'forum' AND action = 'add post' "
1219         ." AND userid != ? GROUP BY cmid,l.course,instance";
1221     $params[] = $USER->id;
1223     if (!$new = $DB->get_records_sql($sql, $params)) {
1224         $new = array(); // avoid warnings
1225     }
1227     // also get all forum tracking stuff ONCE.
1228     $trackingforums = array();
1229     foreach ($forums as $forum) {
1230         if (forum_tp_can_track_forums($forum)) {
1231             $trackingforums[$forum->id] = $forum;
1232         }
1233     }
1235     if (count($trackingforums) > 0) {
1236         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1237         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1238             ' FROM {forum_posts} p '.
1239             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1240             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1241         $params = array($USER->id);
1243         foreach ($trackingforums as $track) {
1244             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1245             $params[] = $track->id;
1246             if (isset($SESSION->currentgroup[$track->course])) {
1247                 $groupid =  $SESSION->currentgroup[$track->course];
1248             } else {
1249                 $groupid = groups_get_all_groups($track->course, $USER->id);
1250                 if (is_array($groupid)) {
1251                     $groupid = array_shift(array_keys($groupid));
1252                     $SESSION->currentgroup[$track->course] = $groupid;
1253                 } else {
1254                     $groupid = 0;
1255                 }
1256             }
1257             $params[] = $groupid;
1258         }
1259         $sql = substr($sql,0,-3); // take off the last OR
1260         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1261         $params[] = $cutoffdate;
1263         if (!$unread = $DB->get_records_sql($sql, $params)) {
1264             $unread = array();
1265         }
1266     } else {
1267         $unread = array();
1268     }
1270     if (empty($unread) and empty($new)) {
1271         return;
1272     }
1274     $strforum = get_string('modulename','forum');
1275     $strnumunread = get_string('overviewnumunread','forum');
1276     $strnumpostssince = get_string('overviewnumpostssince','forum');
1278     foreach ($forums as $forum) {
1279         $str = '';
1280         $count = 0;
1281         $thisunread = 0;
1282         $showunread = false;
1283         // either we have something from logs, or trackposts, or nothing.
1284         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1285             $count = $new[$forum->id]->count;
1286         }
1287         if (array_key_exists($forum->id,$unread)) {
1288             $thisunread = $unread[$forum->id]->count;
1289             $showunread = true;
1290         }
1291         if ($count > 0 || $thisunread > 0) {
1292             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1293                 $forum->name.'</a></div>';
1294             $str .= '<div class="info">';
1295             $str .= $count.' '.$strnumpostssince;
1296             if (!empty($showunread)) {
1297                 $str .= '<br />'.$thisunread .' '.$strnumunread;
1298             }
1299             $str .= '</div></div>';
1300         }
1301         if (!empty($str)) {
1302             if (!array_key_exists($forum->course,$htmlarray)) {
1303                 $htmlarray[$forum->course] = array();
1304             }
1305             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1306                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1307             }
1308             $htmlarray[$forum->course]['forum'] .= $str;
1309         }
1310     }
1313 /**
1314  * Given a course and a date, prints a summary of all the new
1315  * messages posted in the course since that date
1316  *
1317  * @global object
1318  * @global object
1319  * @global object
1320  * @uses CONTEXT_MODULE
1321  * @uses VISIBLEGROUPS
1322  * @param object $course
1323  * @param bool $viewfullnames capability
1324  * @param int $timestart
1325  * @return bool success
1326  */
1327 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1328     global $CFG, $USER, $DB, $OUTPUT;
1330     // do not use log table if possible, it may be huge and is expensive to join with other tables
1332     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1333                                               d.timestart, d.timeend, d.userid AS duserid,
1334                                               u.firstname, u.lastname, u.email, u.picture
1335                                          FROM {forum_posts} p
1336                                               JOIN {forum_discussions} d ON d.id = p.discussion
1337                                               JOIN {forum} f             ON f.id = d.forum
1338                                               JOIN {user} u              ON u.id = p.userid
1339                                         WHERE p.created > ? AND f.course = ?
1340                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1341          return false;
1342     }
1344     $modinfo =& get_fast_modinfo($course);
1346     $groupmodes = array();
1347     $cms    = array();
1349     $strftimerecent = get_string('strftimerecent');
1351     $printposts = array();
1352     foreach ($posts as $post) {
1353         if (!isset($modinfo->instances['forum'][$post->forum])) {
1354             // not visible
1355             continue;
1356         }
1357         $cm = $modinfo->instances['forum'][$post->forum];
1358         if (!$cm->uservisible) {
1359             continue;
1360         }
1361         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1363         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1364             continue;
1365         }
1367         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1368           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1369             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1370                 continue;
1371             }
1372         }
1374         $groupmode = groups_get_activity_groupmode($cm, $course);
1376         if ($groupmode) {
1377             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1378                 // oki (Open discussions have groupid -1)
1379             } else {
1380                 // separate mode
1381                 if (isguestuser()) {
1382                     // shortcut
1383                     continue;
1384                 }
1386                 if (is_null($modinfo->groups)) {
1387                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1388                 }
1390                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1391                     continue;
1392                 }
1393             }
1394         }
1396         $printposts[] = $post;
1397     }
1398     unset($posts);
1400     if (!$printposts) {
1401         return false;
1402     }
1404     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1405     echo "\n<ul class='unlist'>\n";
1407     foreach ($printposts as $post) {
1408         $subjectclass = empty($post->parent) ? ' bold' : '';
1410         echo '<li><div class="head">'.
1411                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1412                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1413              '</div>';
1414         echo '<div class="info'.$subjectclass.'">';
1415         if (empty($post->parent)) {
1416             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1417         } else {
1418             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1419         }
1420         $post->subject = break_up_long_words(format_string($post->subject, true));
1421         echo $post->subject;
1422         echo "</a>\"</div></li>\n";
1423     }
1425     echo "</ul>\n";
1427     return true;
1430 /**
1431  * Return grade for given user or all users.
1432  *
1433  * @global object
1434  * @global object
1435  * @param object $forum
1436  * @param int $userid optional user id, 0 means all users
1437  * @return array array of grades, false if none
1438  */
1439 function forum_get_user_grades($forum, $userid=0) {
1440     global $CFG;
1442     require_once($CFG->dirroot.'/rating/lib.php');
1443     $rm = new rating_manager();
1445     $ratingoptions = new stdclass();
1447     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1448     $ratingoptions->modulename = 'forum';
1449     $ratingoptions->moduleid   = $forum->id;
1450     //$ratingoptions->cmidnumber = $forum->cmidnumber;
1452     $ratingoptions->userid = $userid;
1453     $ratingoptions->aggregationmethod = $forum->assessed;
1454     $ratingoptions->scaleid = $forum->scale;
1455     $ratingoptions->itemtable = 'forum_posts';
1456     $ratingoptions->itemtableusercolumn = 'userid';
1458     return $rm->get_user_grades($ratingoptions);
1461 /**
1462  * Update activity grades
1463  *
1464  * @global object
1465  * @global object
1466  * @param object $forum
1467  * @param int $userid specific user only, 0 means all
1468  * @param boolean $nullifnone return null if grade does not exist
1469  * @return void
1470  */
1471 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1472     global $CFG, $DB;
1473     require_once($CFG->libdir.'/gradelib.php');
1475     if (!$forum->assessed) {
1476         forum_grade_item_update($forum);
1478     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1479         forum_grade_item_update($forum, $grades);
1481     } else if ($userid and $nullifnone) {
1482         $grade = new stdClass();
1483         $grade->userid   = $userid;
1484         $grade->rawgrade = NULL;
1485         forum_grade_item_update($forum, $grade);
1487     } else {
1488         forum_grade_item_update($forum);
1489     }
1492 /**
1493  * Update all grades in gradebook.
1494  * @global object
1495  */
1496 function forum_upgrade_grades() {
1497     global $DB;
1499     $sql = "SELECT COUNT('x')
1500               FROM {forum} f, {course_modules} cm, {modules} m
1501              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1502     $count = $DB->count_records_sql($sql);
1504     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1505               FROM {forum} f, {course_modules} cm, {modules} m
1506              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1507     $rs = $DB->get_recordset_sql($sql);
1508     if ($rs->valid()) {
1509         $pbar = new progress_bar('forumupgradegrades', 500, true);
1510         $i=0;
1511         foreach ($rs as $forum) {
1512             $i++;
1513             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1514             forum_update_grades($forum, 0, false);
1515             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1516         }
1517     }
1518     $rs->close();
1521 /**
1522  * Create/update grade item for given forum
1523  *
1524  * @global object
1525  * @uses GRADE_TYPE_NONE
1526  * @uses GRADE_TYPE_VALUE
1527  * @uses GRADE_TYPE_SCALE
1528  * @param object $forum object with extra cmidnumber
1529  * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
1530  * @return int 0 if ok
1531  */
1532 function forum_grade_item_update($forum, $grades=NULL) {
1533     global $CFG;
1534     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1535         require_once($CFG->libdir.'/gradelib.php');
1536     }
1538     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1540     if (!$forum->assessed or $forum->scale == 0) {
1541         $params['gradetype'] = GRADE_TYPE_NONE;
1543     } else if ($forum->scale > 0) {
1544         $params['gradetype'] = GRADE_TYPE_VALUE;
1545         $params['grademax']  = $forum->scale;
1546         $params['grademin']  = 0;
1548     } else if ($forum->scale < 0) {
1549         $params['gradetype'] = GRADE_TYPE_SCALE;
1550         $params['scaleid']   = -$forum->scale;
1551     }
1553     if ($grades  === 'reset') {
1554         $params['reset'] = true;
1555         $grades = NULL;
1556     }
1558     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1561 /**
1562  * Delete grade item for given forum
1563  *
1564  * @global object
1565  * @param object $forum object
1566  * @return object grade_item
1567  */
1568 function forum_grade_item_delete($forum) {
1569     global $CFG;
1570     require_once($CFG->libdir.'/gradelib.php');
1572     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1576 /**
1577  * Returns the users with data in one forum
1578  * (users with records in forum_subscriptions, forum_posts, students)
1579  *
1580  * @global object
1581  * @global object
1582  * @param int $forumid
1583  * @return mixed array or false if none
1584  */
1585 function forum_get_participants($forumid) {
1587     global $CFG, $DB;
1589     //Get students from forum_subscriptions
1590     $st_subscriptions = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1591                                          FROM {user} u,
1592                                               {forum_subscriptions} s
1593                                          WHERE s.forum = ? AND
1594                                                u.id = s.userid", array($forumid));
1595     //Get students from forum_posts
1596     $st_posts = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1597                                  FROM {user} u,
1598                                       {forum_discussions} d,
1599                                       {forum_posts} p
1600                                  WHERE d.forum = ? AND
1601                                        p.discussion = d.id AND
1602                                        u.id = p.userid", array($forumid));
1604     //Get students from the ratings table
1605     $st_ratings = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1606                                    FROM {user} u,
1607                                         {forum_discussions} d,
1608                                         {forum_posts} p,
1609                                         {ratings} r
1610                                    WHERE d.forum = ? AND
1611                                          p.discussion = d.id AND
1612                                          r.post = p.id AND
1613                                          u.id = r.userid", array($forumid));
1615     //Add st_posts to st_subscriptions
1616     if ($st_posts) {
1617         foreach ($st_posts as $st_post) {
1618             $st_subscriptions[$st_post->id] = $st_post;
1619         }
1620     }
1621     //Add st_ratings to st_subscriptions
1622     if ($st_ratings) {
1623         foreach ($st_ratings as $st_rating) {
1624             $st_subscriptions[$st_rating->id] = $st_rating;
1625         }
1626     }
1627     //Return st_subscriptions array (it contains an array of unique users)
1628     return ($st_subscriptions);
1631 /**
1632  * This function returns if a scale is being used by one forum
1633  *
1634  * @global object
1635  * @param int $forumid
1636  * @param int $scaleid negative number
1637  * @return bool
1638  */
1639 function forum_scale_used ($forumid,$scaleid) {
1640     global $DB;
1641     $return = false;
1643     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1645     if (!empty($rec) && !empty($scaleid)) {
1646         $return = true;
1647     }
1649     return $return;
1652 /**
1653  * Checks if scale is being used by any instance of forum
1654  *
1655  * This is used to find out if scale used anywhere
1656  *
1657  * @global object
1658  * @param $scaleid int
1659  * @return boolean True if the scale is used by any forum
1660  */
1661 function forum_scale_used_anywhere($scaleid) {
1662     global $DB;
1663     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1664         return true;
1665     } else {
1666         return false;
1667     }
1670 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1672 /**
1673  * Gets a post with all info ready for forum_print_post
1674  * Most of these joins are just to get the forum id
1675  *
1676  * @global object
1677  * @global object
1678  * @param int $postid
1679  * @return mixed array of posts or false
1680  */
1681 function forum_get_post_full($postid) {
1682     global $CFG, $DB;
1684     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1685                              FROM {forum_posts} p
1686                                   JOIN {forum_discussions} d ON p.discussion = d.id
1687                                   LEFT JOIN {user} u ON p.userid = u.id
1688                             WHERE p.id = ?", array($postid));
1691 /**
1692  * Gets posts with all info ready for forum_print_post
1693  * We pass forumid in because we always know it so no need to make a
1694  * complicated join to find it out.
1695  *
1696  * @global object
1697  * @global object
1698  * @return mixed array of posts or false
1699  */
1700 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1701     global $CFG, $DB;
1703     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1704                               FROM {forum_posts} p
1705                          LEFT JOIN {user} u ON p.userid = u.id
1706                              WHERE p.discussion = ?
1707                                AND p.parent > 0 $sort", array($discussion));
1710 /**
1711  * Gets all posts in discussion including top parent.
1712  *
1713  * @global object
1714  * @global object
1715  * @global object
1716  * @param int $discussionid
1717  * @param string $sort
1718  * @param bool $tracking does user track the forum?
1719  * @return array of posts
1720  */
1721 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1722     global $CFG, $DB, $USER;
1724     $tr_sel  = "";
1725     $tr_join = "";
1726     $params = array();
1728     if ($tracking) {
1729         $now = time();
1730         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1731         $tr_sel  = ", fr.id AS postread";
1732         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1733         $params[] = $USER->id;
1734     }
1736     $params[] = $discussionid;
1737     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1738                                      FROM {forum_posts} p
1739                                           LEFT JOIN {user} u ON p.userid = u.id
1740                                           $tr_join
1741                                     WHERE p.discussion = ?
1742                                  ORDER BY $sort", $params)) {
1743         return array();
1744     }
1746     foreach ($posts as $pid=>$p) {
1747         if ($tracking) {
1748             if (forum_tp_is_post_old($p)) {
1749                  $posts[$pid]->postread = true;
1750             }
1751         }
1752         if (!$p->parent) {
1753             continue;
1754         }
1755         if (!isset($posts[$p->parent])) {
1756             continue; // parent does not exist??
1757         }
1758         if (!isset($posts[$p->parent]->children)) {
1759             $posts[$p->parent]->children = array();
1760         }
1761         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1762     }
1764     return $posts;
1767 /**
1768  * Gets posts with all info ready for forum_print_post
1769  * We pass forumid in because we always know it so no need to make a
1770  * complicated join to find it out.
1771  *
1772  * @global object
1773  * @global object
1774  * @param int $parent
1775  * @param int $forumid
1776  * @return array
1777  */
1778 function forum_get_child_posts($parent, $forumid) {
1779     global $CFG, $DB;
1781     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1782                               FROM {forum_posts} p
1783                          LEFT JOIN {user} u ON p.userid = u.id
1784                              WHERE p.parent = ?
1785                           ORDER BY p.created ASC", array($parent));
1788 /**
1789  * An array of forum objects that the user is allowed to read/search through.
1790  *
1791  * @global object
1792  * @global object
1793  * @global object
1794  * @param int $userid
1795  * @param int $courseid if 0, we look for forums throughout the whole site.
1796  * @return array of forum objects, or false if no matches
1797  *         Forum objects have the following attributes:
1798  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1799  *         viewhiddentimedposts
1800  */
1801 function forum_get_readable_forums($userid, $courseid=0) {
1803     global $CFG, $DB, $USER;
1804     require_once($CFG->dirroot.'/course/lib.php');
1806     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1807         print_error('notinstalled', 'forum');
1808     }
1810     if ($courseid) {
1811         $courses = $DB->get_records('course', array('id' => $courseid));
1812     } else {
1813         // If no course is specified, then the user can see SITE + his courses.
1814         $courses1 = $DB->get_records('course', array('id' => SITEID));
1815         $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
1816         $courses = array_merge($courses1, $courses2);
1817     }
1818     if (!$courses) {
1819         return array();
1820     }
1822     $readableforums = array();
1824     foreach ($courses as $course) {
1826         $modinfo =& get_fast_modinfo($course);
1827         if (is_null($modinfo->groups)) {
1828             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1829         }
1831         if (empty($modinfo->instances['forum'])) {
1832             // hmm, no forums?
1833             continue;
1834         }
1836         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1838         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1839             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1840                 continue;
1841             }
1842             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1843             $forum = $courseforums[$forumid];
1844             $forum->context = $context;
1845             $forum->cm = $cm;
1847             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1848                 continue;
1849             }
1851          /// group access
1852             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1853                 if (is_null($modinfo->groups)) {
1854                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1855                 }
1856                 if (isset($modinfo->groups[$cm->groupingid])) {
1857                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1858                     $forum->onlygroups[] = -1;
1859                 } else {
1860                     $forum->onlygroups = array(-1);
1861                 }
1862             }
1864         /// hidden timed discussions
1865             $forum->viewhiddentimedposts = true;
1866             if (!empty($CFG->forum_enabletimedposts)) {
1867                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1868                     $forum->viewhiddentimedposts = false;
1869                 }
1870             }
1872         /// qanda access
1873             if ($forum->type == 'qanda'
1874                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1876                 // We need to check whether the user has posted in the qanda forum.
1877                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1878                                                     // the user is allowed to see in this forum.
1879                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1880                     foreach ($discussionspostedin as $d) {
1881                         $forum->onlydiscussions[] = $d->id;
1882                     }
1883                 }
1884             }
1886             $readableforums[$forum->id] = $forum;
1887         }
1889         unset($modinfo);
1891     } // End foreach $courses
1893     return $readableforums;
1896 /**
1897  * Returns a list of posts found using an array of search terms.
1898  *
1899  * @global object
1900  * @global object
1901  * @global object
1902  * @param array $searchterms array of search terms, e.g. word +word -word
1903  * @param int $courseid if 0, we search through the whole site
1904  * @param int $limitfrom
1905  * @param int $limitnum
1906  * @param int &$totalcount
1907  * @param string $extrasql
1908  * @return array|bool Array of posts found or false
1909  */
1910 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1911                             &$totalcount, $extrasql='') {
1912     global $CFG, $DB, $USER;
1913     require_once($CFG->libdir.'/searchlib.php');
1915     $forums = forum_get_readable_forums($USER->id, $courseid);
1917     if (count($forums) == 0) {
1918         $totalcount = 0;
1919         return false;
1920     }
1922     $now = round(time(), -2); // db friendly
1924     $fullaccess = array();
1925     $where = array();
1926     $params = array();
1928     foreach ($forums as $forumid => $forum) {
1929         $select = array();
1931         if (!$forum->viewhiddentimedposts) {
1932             $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
1933             $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
1934         }
1936         $cm = $forum->cm;
1937         $context = $forum->context;
1939         if ($forum->type == 'qanda'
1940             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1941             if (!empty($forum->onlydiscussions)) {
1942                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_0000');
1943                 $params = array_merge($params, $discussionid_params);
1944                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
1945             } else {
1946                 $select[] = "p.parent = 0";
1947             }
1948         }
1950         if (!empty($forum->onlygroups)) {
1951             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_0000');
1952             $params = array_merge($params, $groupid_params);
1953             $select[] = "d.groupid $groupid_sql";
1954         }
1956         if ($select) {
1957             $selects = implode(" AND ", $select);
1958             $where[] = "(d.forum = :forum{$forumid} AND $selects)";
1959             $params['forum'.$forumid] = $forumid;
1960         } else {
1961             $fullaccess[] = $forumid;
1962         }
1963     }
1965     if ($fullaccess) {
1966         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula0');
1967         $params = array_merge($params, $fullid_params);
1968         $where[] = "(d.forum $fullid_sql)";
1969     }
1971     $selectdiscussion = "(".implode(" OR ", $where).")";
1973     $messagesearch = '';
1974     $searchstring = '';
1976     // Need to concat these back together for parser to work.
1977     foreach($searchterms as $searchterm){
1978         if ($searchstring != '') {
1979             $searchstring .= ' ';
1980         }
1981         $searchstring .= $searchterm;
1982     }
1984     // We need to allow quoted strings for the search. The quotes *should* be stripped
1985     // by the parser, but this should be examined carefully for security implications.
1986     $searchstring = str_replace("\\\"","\"",$searchstring);
1987     $parser = new search_parser();
1988     $lexer = new search_lexer($parser);
1990     if ($lexer->parse($searchstring)) {
1991         $parsearray = $parser->get_parsed_array();
1992     // Experimental feature under 1.8! MDL-8830
1993     // Use alternative text searches if defined
1994     // This feature only works under mysql until properly implemented for other DBs
1995     // Requires manual creation of text index for forum_posts before enabling it:
1996     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
1997     // Experimental feature under 1.8! MDL-8830
1998         if (!empty($CFG->forum_usetextsearches)) {
1999             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2000                                                  'p.userid', 'u.id', 'u.firstname',
2001                                                  'u.lastname', 'p.modified', 'd.forum');
2002         } else {
2003             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2004                                                  'p.userid', 'u.id', 'u.firstname',
2005                                                  'u.lastname', 'p.modified', 'd.forum');
2006         }
2007         $params = array_merge($params, $msparams);
2008     }
2010     $fromsql = "{forum_posts} p,
2011                   {forum_discussions} d,
2012                   {user} u";
2014     $selectsql = " $messagesearch
2015                AND p.discussion = d.id
2016                AND p.userid = u.id
2017                AND $selectdiscussion
2018                    $extrasql";
2020     $countsql = "SELECT COUNT(*)
2021                    FROM $fromsql
2022                   WHERE $selectsql";
2024     $searchsql = "SELECT p.*,
2025                          d.forum,
2026                          u.firstname,
2027                          u.lastname,
2028                          u.email,
2029                          u.picture,
2030                          u.imagealt,
2031                          u.email
2032                     FROM $fromsql
2033                    WHERE $selectsql
2034                 ORDER BY p.modified DESC";
2036     $totalcount = $DB->count_records_sql($countsql, $params);
2038     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2041 /**
2042  * Returns a list of ratings for a particular post - sorted.
2043  *
2044  * @global object
2045  * @global object
2046  * @param int $postid
2047  * @param string $sort
2048  * @return array Array of ratings or false
2049  */
2050 function forum_get_ratings($context, $postid, $sort="u.firstname ASC") {
2051     global $PAGE;
2053     $options = new stdclass();
2054     $options->context = $PAGE->context;
2055     $options->itemid = $postid;
2056     $options->sort = "ORDER BY $sort";
2058     $rm = new rating_manager();
2059     $rm->get_all_ratings_for_item($options);
2062 /**
2063  * Returns a list of all new posts that have not been mailed yet
2064  *
2065  * @global object
2066  * @global object
2067  * @param int $starttime posts created after this time
2068  * @param int $endtime posts created before this
2069  * @param int $now used for timed discussions only
2070  * @return array
2071  */
2072 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2073     global $CFG, $DB;
2075     $params = array($starttime, $endtime);
2076     if (!empty($CFG->forum_enabletimedposts)) {
2077         if (empty($now)) {
2078             $now = time();
2079         }
2080         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2081         $params[] = $now;
2082         $params[] = $now;
2083     } else {
2084         $timedsql = "";
2085     }
2087     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2088                               FROM {forum_posts} p
2089                                    JOIN {forum_discussions} d ON d.id = p.discussion
2090                              WHERE p.mailed = 0
2091                                    AND p.created >= ?
2092                                    AND (p.created < ? OR p.mailnow = 1)
2093                                    $timedsql
2094                           ORDER BY p.modified ASC", $params);
2097 /**
2098  * Marks posts before a certain time as being mailed already
2099  *
2100  * @global object
2101  * @global object
2102  * @param int $endtime
2103  * @param int $now Defaults to time()
2104  * @return bool
2105  */
2106 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2107     global $CFG, $DB;
2108     if (empty($now)) {
2109         $now = time();
2110     }
2112     if (empty($CFG->forum_enabletimedposts)) {
2113         return $DB->execute("UPDATE {forum_posts}
2114                                SET mailed = '1'
2115                              WHERE (created < ? OR mailnow = 1)
2116                                    AND mailed = 0", array($endtime));
2118     } else {
2119         return $DB->execute("UPDATE {forum_posts}
2120                                SET mailed = '1'
2121                              WHERE discussion NOT IN (SELECT d.id
2122                                                         FROM {forum_discussions} d
2123                                                        WHERE d.timestart > ?)
2124                                    AND (created < ? OR mailnow = 1)
2125                                    AND mailed = 0", array($now, $endtime));
2126     }
2129 /**
2130  * Get all the posts for a user in a forum suitable for forum_print_post
2131  *
2132  * @global object
2133  * @global object
2134  * @uses CONTEXT_MODULE
2135  * @return array
2136  */
2137 function forum_get_user_posts($forumid, $userid) {
2138     global $CFG, $DB;
2140     $timedsql = "";
2141     $params = array($forumid, $userid);
2143     if (!empty($CFG->forum_enabletimedposts)) {
2144         $cm = get_coursemodule_from_instance('forum', $forumid);
2145         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2146             $now = time();
2147             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2148             $params[] = $now;
2149             $params[] = $now;
2150         }
2151     }
2153     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2154                               FROM {forum} f
2155                                    JOIN {forum_discussions} d ON d.forum = f.id
2156                                    JOIN {forum_posts} p       ON p.discussion = d.id
2157                                    JOIN {user} u              ON u.id = p.userid
2158                              WHERE f.id = ?
2159                                    AND p.userid = ?
2160                                    $timedsql
2161                           ORDER BY p.modified ASC", $params);
2164 /**
2165  * Get all the discussions user participated in
2166  *
2167  * @global object
2168  * @global object
2169  * @uses CONTEXT_MODULE
2170  * @param int $forumid
2171  * @param int $userid
2172  * @return array Array or false
2173  */
2174 function forum_get_user_involved_discussions($forumid, $userid) {
2175     global $CFG, $DB;
2177     $timedsql = "";
2178     $params = array($forumid, $userid);
2179     if (!empty($CFG->forum_enabletimedposts)) {
2180         $cm = get_coursemodule_from_instance('forum', $forumid);
2181         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2182             $now = time();
2183             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2184             $params[] = $now;
2185             $params[] = $now;
2186         }
2187     }
2189     return $DB->get_records_sql("SELECT DISTINCT d.*
2190                               FROM {forum} f
2191                                    JOIN {forum_discussions} d ON d.forum = f.id
2192                                    JOIN {forum_posts} p       ON p.discussion = d.id
2193                              WHERE f.id = ?
2194                                    AND p.userid = ?
2195                                    $timedsql", $params);
2198 /**
2199  * Get all the posts for a user in a forum suitable for forum_print_post
2200  *
2201  * @global object
2202  * @global object
2203  * @param int $forumid
2204  * @param int $userid
2205  * @return array of counts or false
2206  */
2207 function forum_count_user_posts($forumid, $userid) {
2208     global $CFG, $DB;
2210     $timedsql = "";
2211     $params = array($forumid, $userid);
2212     if (!empty($CFG->forum_enabletimedposts)) {
2213         $cm = get_coursemodule_from_instance('forum', $forumid);
2214         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2215             $now = time();
2216             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2217             $params[] = $now;
2218             $params[] = $now;
2219         }
2220     }
2222     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2223                              FROM {forum} f
2224                                   JOIN {forum_discussions} d ON d.forum = f.id
2225                                   JOIN {forum_posts} p       ON p.discussion = d.id
2226                                   JOIN {user} u              ON u.id = p.userid
2227                             WHERE f.id = ?
2228                                   AND p.userid = ?
2229                                   $timedsql", $params);
2232 /**
2233  * Given a log entry, return the forum post details for it.
2234  *
2235  * @global object
2236  * @global object
2237  * @param object $log
2238  * @return array|null
2239  */
2240 function forum_get_post_from_log($log) {
2241     global $CFG, $DB;
2243     if ($log->action == "add post") {
2245         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2246                                            u.firstname, u.lastname, u.email, u.picture
2247                                  FROM {forum_discussions} d,
2248                                       {forum_posts} p,
2249                                       {forum} f,
2250                                       {user} u
2251                                 WHERE p.id = ?
2252                                   AND d.id = p.discussion
2253                                   AND p.userid = u.id
2254                                   AND u.deleted <> '1'
2255                                   AND f.id = d.forum", array($log->info));
2258     } else if ($log->action == "add discussion") {
2260         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2261                                            u.firstname, u.lastname, u.email, u.picture
2262                                  FROM {forum_discussions} d,
2263                                       {forum_posts} p,
2264                                       {forum} f,
2265                                       {user} u
2266                                 WHERE d.id = ?
2267                                   AND d.firstpost = p.id
2268                                   AND p.userid = u.id
2269                                   AND u.deleted <> '1'
2270                                   AND f.id = d.forum", array($log->info));
2271     }
2272     return NULL;
2275 /**
2276  * Given a discussion id, return the first post from the discussion
2277  *
2278  * @global object
2279  * @global object
2280  * @param int $dicsussionid
2281  * @return array
2282  */
2283 function forum_get_firstpost_from_discussion($discussionid) {
2284     global $CFG, $DB;
2286     return $DB->get_record_sql("SELECT p.*
2287                              FROM {forum_discussions} d,
2288                                   {forum_posts} p
2289                             WHERE d.id = ?
2290                               AND d.firstpost = p.id ", array($discussionid));
2293 /**
2294  * Returns an array of counts of replies to each discussion
2295  *
2296  * @global object
2297  * @global object
2298  * @param int $forumid
2299  * @param string $forumsort
2300  * @param int $limit
2301  * @param int $page
2302  * @param int $perpage
2303  * @return array
2304  */
2305 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2306     global $CFG, $DB;
2308     if ($limit > 0) {
2309         $limitfrom = 0;
2310         $limitnum  = $limit;
2311     } else if ($page != -1) {
2312         $limitfrom = $page*$perpage;
2313         $limitnum  = $perpage;
2314     } else {
2315         $limitfrom = 0;
2316         $limitnum  = 0;
2317     }
2319     if ($forumsort == "") {
2320         $orderby = "";
2321         $groupby = "";
2323     } else {
2324         $orderby = "ORDER BY $forumsort";
2325         $groupby = ", ".strtolower($forumsort);
2326         $groupby = str_replace('desc', '', $groupby);
2327         $groupby = str_replace('asc', '', $groupby);
2328     }
2330     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2331         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2332                   FROM {forum_posts} p
2333                        JOIN {forum_discussions} d ON p.discussion = d.id
2334                  WHERE p.parent > 0 AND d.forum = ?
2335               GROUP BY p.discussion";
2336         return $DB->get_records_sql($sql, array($forumid));
2338     } else {
2339         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2340                   FROM {forum_posts} p
2341                        JOIN {forum_discussions} d ON p.discussion = d.id
2342                  WHERE d.forum = ?
2343               GROUP BY p.discussion $groupby
2344               $orderby";
2345         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2346     }
2349 /**
2350  * @global object
2351  * @global object
2352  * @global object
2353  * @staticvar array $cache
2354  * @param object $forum
2355  * @param object $cm
2356  * @param object $course
2357  * @return mixed
2358  */
2359 function forum_count_discussions($forum, $cm, $course) {
2360     global $CFG, $DB, $USER;
2362     static $cache = array();
2364     $now = round(time(), -2); // db cache friendliness
2366     $params = array($course->id);
2368     if (!isset($cache[$course->id])) {
2369         if (!empty($CFG->forum_enabletimedposts)) {
2370             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2371             $params[] = $now;
2372             $params[] = $now;
2373         } else {
2374             $timedsql = "";
2375         }
2377         $sql = "SELECT f.id, COUNT(d.id) as dcount
2378                   FROM {forum} f
2379                        JOIN {forum_discussions} d ON d.forum = f.id
2380                  WHERE f.course = ?
2381                        $timedsql
2382               GROUP BY f.id";
2384         if ($counts = $DB->get_records_sql($sql, $params)) {
2385             foreach ($counts as $count) {
2386                 $counts[$count->id] = $count->dcount;
2387             }
2388             $cache[$course->id] = $counts;
2389         } else {
2390             $cache[$course->id] = array();
2391         }
2392     }
2394     if (empty($cache[$course->id][$forum->id])) {
2395         return 0;
2396     }
2398     $groupmode = groups_get_activity_groupmode($cm, $course);
2400     if ($groupmode != SEPARATEGROUPS) {
2401         return $cache[$course->id][$forum->id];
2402     }
2404     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2405         return $cache[$course->id][$forum->id];
2406     }
2408     require_once($CFG->dirroot.'/course/lib.php');
2410     $modinfo =& get_fast_modinfo($course);
2411     if (is_null($modinfo->groups)) {
2412         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2413     }
2415     if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2416         $mygroups = $modinfo->groups[$cm->groupingid];
2417     } else {
2418         $mygroups = false; // Will be set below
2419     }
2421     // add all groups posts
2422     if (empty($mygroups)) {
2423         $mygroups = array(-1=>-1);
2424     } else {
2425         $mygroups[-1] = -1;
2426     }
2428     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2429     $params[] = $forum->id;
2431     if (!empty($CFG->forum_enabletimedposts)) {
2432         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2433         $params[] = $now;
2434         $params[] = $now;
2435     } else {
2436         $timedsql = "";
2437     }
2439     $sql = "SELECT COUNT(d.id)
2440               FROM {forum_discussions} d
2441              WHERE d.groupid $mygroups_sql AND d.forum = ?
2442                    $timedsql";
2444     return $DB->get_field_sql($sql, $params);
2447 /**
2448  * How many posts by other users are unrated by a given user in the given discussion?
2449  *
2450  * @global object
2451  * @global object
2452  * @param int $discussionid
2453  * @param int $userid
2454  * @return mixed
2455  */
2456 function forum_count_unrated_posts($discussionid, $userid) {
2457     global $CFG, $DB;
2458     if ($posts = $DB->get_record_sql("SELECT count(*) as num
2459                                    FROM {forum_posts}
2460                                   WHERE parent > 0
2461                                     AND discussion = ?
2462                                     AND userid <> ? ", array($discussionid, $userid))) {
2464         if ($rated = $DB->get_record_sql("SELECT count(*) as num
2465                                        FROM {forum_posts} p,
2466                                             {rating} r
2467                                       WHERE p.discussion = ?
2468                                         AND p.id = r.itemid
2469                                         AND r.userid = ?", array($discussionid, $userid))) {
2470             $difference = $posts->num - $rated->num;
2471             if ($difference > 0) {
2472                 return $difference;
2473             } else {
2474                 return 0;    // Just in case there was a counting error
2475             }
2476         } else {
2477             return $posts->num;
2478         }
2479     } else {
2480         return 0;
2481     }
2484 /**
2485  * Get all discussions in a forum
2486  *
2487  * @global object
2488  * @global object
2489  * @global object
2490  * @uses CONTEXT_MODULE
2491  * @uses VISIBLEGROUPS
2492  * @param object $cm
2493  * @param string $forumsort
2494  * @param bool $fullpost
2495  * @param int $unused
2496  * @param int $limit
2497  * @param bool $userlastmodified
2498  * @param int $page
2499  * @param int $perpage
2500  * @return array
2501  */
2502 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2503     global $CFG, $DB, $USER;
2505     $timelimit = '';
2507     $now = round(time(), -2);
2508     $params = array($cm->instance);
2510     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2512     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2513         return array();
2514     }
2516     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2518         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2519             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2520             $params[] = $now;
2521             $params[] = $now;
2522             if (isloggedin()) {
2523                 $timelimit .= " OR d.userid = ?";
2524                 $params[] = $USER->id;
2525             }
2526             $timelimit .= ")";
2527         }
2528     }
2530     if ($limit > 0) {
2531         $limitfrom = 0;
2532         $limitnum  = $limit;
2533     } else if ($page != -1) {
2534         $limitfrom = $page*$perpage;
2535         $limitnum  = $perpage;
2536     } else {
2537         $limitfrom = 0;
2538         $limitnum  = 0;
2539     }
2541     $groupmode    = groups_get_activity_groupmode($cm);
2542     $currentgroup = groups_get_activity_group($cm);
2544     if ($groupmode) {
2545         if (empty($modcontext)) {
2546             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2547         }
2549         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2550             if ($currentgroup) {
2551                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2552                 $params[] = $currentgroup;
2553             } else {
2554                 $groupselect = "";
2555             }
2557         } else {
2558             //seprate groups without access all
2559             if ($currentgroup) {
2560                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2561                 $params[] = $currentgroup;
2562             } else {
2563                 $groupselect = "AND d.groupid = -1";
2564             }
2565         }
2566     } else {
2567         $groupselect = "";
2568     }
2571     if (empty($forumsort)) {
2572         $forumsort = "d.timemodified DESC";
2573     }
2574     if (empty($fullpost)) {
2575         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2576     } else {
2577         $postdata = "p.*";
2578     }
2580     if (empty($userlastmodified)) {  // We don't need to know this
2581         $umfields = "";
2582         $umtable  = "";
2583     } else {
2584         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2585         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2586     }
2588     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2589                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2590               FROM {forum_discussions} d
2591                    JOIN {forum_posts} p ON p.discussion = d.id
2592                    JOIN {user} u ON p.userid = u.id
2593                    $umtable
2594              WHERE d.forum = ? AND p.parent = 0
2595                    $timelimit $groupselect
2596           ORDER BY $forumsort";
2597     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2600 /**
2601  *
2602  * @global object
2603  * @global object
2604  * @global object
2605  * @uses CONTEXT_MODULE
2606  * @uses VISIBLEGROUPS
2607  * @param object $cm
2608  * @return array
2609  */
2610 function forum_get_discussions_unread($cm) {
2611     global $CFG, $DB, $USER;
2613     $now = round(time(), -2);
2614     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2616     $params = array();
2617     $groupmode    = groups_get_activity_groupmode($cm);
2618     $currentgroup = groups_get_activity_group($cm);
2620     if ($groupmode) {
2621         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2623         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2624             if ($currentgroup) {
2625                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2626                 $params['currentgroup'] = $currentgroup;
2627             } else {
2628                 $groupselect = "";
2629             }
2631         } else {
2632             //separate groups without access all
2633             if ($currentgroup) {
2634                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2635                 $params['currentgroup'] = $currentgroup;
2636             } else {
2637                 $groupselect = "AND d.groupid = -1";
2638             }
2639         }
2640     } else {
2641         $groupselect = "";
2642     }
2644     if (!empty($CFG->forum_enabletimedposts)) {
2645         $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2646         $params['now1'] = $now;
2647         $params['now2'] = $now;
2648     } else {
2649         $timedsql = "";
2650     }
2652     $sql = "SELECT d.id, COUNT(p.id) AS unread
2653               FROM {forum_discussions} d
2654                    JOIN {forum_posts} p     ON p.discussion = d.id
2655                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2656              WHERE d.forum = {$cm->instance}
2657                    AND p.modified >= :cutoffdate AND r.id is NULL
2658                    $groupselect
2659                    $timedsql
2660           GROUP BY d.id";
2661     $params['cutoffdate'] = $cutoffdate;
2663     if ($unreads = $DB->get_records_sql($sql, $params)) {
2664         foreach ($unreads as $unread) {
2665             $unreads[$unread->id] = $unread->unread;
2666         }
2667         return $unreads;
2668     } else {
2669         return array();
2670     }
2673 /**
2674  * @global object
2675  * @global object
2676  * @global object
2677  * @uses CONEXT_MODULE
2678  * @uses VISIBLEGROUPS
2679  * @param object $cm
2680  * @return array
2681  */
2682 function forum_get_discussions_count($cm) {
2683     global $CFG, $DB, $USER;
2685     $now = round(time(), -2);
2686     $params = array($cm->instance);
2687     $groupmode    = groups_get_activity_groupmode($cm);
2688     $currentgroup = groups_get_activity_group($cm);
2690     if ($groupmode) {
2691         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2693         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2694             if ($currentgroup) {
2695                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2696                 $params[] = $currentgroup;
2697             } else {
2698                 $groupselect = "";
2699             }
2701         } else {
2702             //seprate groups without access all
2703             if ($currentgroup) {
2704                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2705                 $params[] = $currentgroup;
2706             } else {
2707                 $groupselect = "AND d.groupid = -1";
2708             }
2709         }
2710     } else {
2711         $groupselect = "";
2712     }
2714     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2716     $timelimit = "";
2718     if (!empty($CFG->forum_enabletimedposts)) {
2720         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2722         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2723             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2724             $params[] = $now;
2725             $params[] = $now;
2726             if (isloggedin()) {
2727                 $timelimit .= " OR d.userid = ?";
2728                 $params[] = $USER->id;
2729             }
2730             $timelimit .= ")";
2731         }
2732     }
2734     $sql = "SELECT COUNT(d.id)
2735               FROM {forum_discussions} d
2736                    JOIN {forum_posts} p ON p.discussion = d.id
2737              WHERE d.forum = ? AND p.parent = 0
2738                    $groupselect $timelimit";
2740     return $DB->get_field_sql($sql, $params);
2744 /**
2745  * Get all discussions started by a particular user in a course (or group)
2746  * This function no longer used ...
2747  *
2748  * @todo Remove this function if no longer used
2749  * @global object
2750  * @global object
2751  * @param int $courseid
2752  * @param int $userid
2753  * @param int $groupid
2754  * @return array
2755  */
2756 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2757     global $CFG, $DB;
2758     $params = array($courseid, $userid);
2759     if ($groupid) {
2760         $groupselect = " AND d.groupid = ? ";
2761         $params[] = $groupid;
2762     } else  {
2763         $groupselect = "";
2764     }
2766     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2767                                    f.type as forumtype, f.name as forumname, f.id as forumid
2768                               FROM {forum_discussions} d,
2769                                    {forum_posts} p,
2770                                    {user} u,
2771                                    {forum} f
2772                              WHERE d.course = ?
2773                                AND p.discussion = d.id
2774                                AND p.parent = 0
2775                                AND p.userid = u.id
2776                                AND u.id = ?
2777                                AND d.forum = f.id $groupselect
2778                           ORDER BY p.created DESC", $params);
2781 /**
2782  * Get the list of potential subscribers to a forum.
2783  *
2784  * @param object $forumcontext the forum context.
2785  * @param integer $groupid the id of a group, or 0 for all groups.
2786  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2787  * @param string $sort sort order. As for get_users_by_capability.
2788  * @return array list of users.
2789  */
2790 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2791     return get_users_by_capability($forumcontext, 'mod/forum:initialsubscriptions', $fields, $sort, '', '', $groupid, '', false, true);
2794 /**
2795  * Returns list of user objects that are subscribed to this forum
2796  *
2797  * @global object
2798  * @global object
2799  * @param object $course the course
2800  * @param forum $forum the forum
2801  * @param integer $groupid group id, or 0 for all.
2802  * @param object $context the forum context, to save re-fetching it where possible.
2803  * @param string $fields requested user fields (with "u." table prefix)
2804  * @return array list of users.
2805  */
2806 function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
2807     global $CFG, $DB;
2808     $params = array($forum->id);
2810     if ($groupid) {
2811         $grouptables = ", {groups_members} gm ";
2812         $groupselect = "AND gm.groupid = ? AND u.id = gm.userid";
2813         $params[] = $groupid;
2814     } else  {
2815         $grouptables = '';
2816         $groupselect = '';
2817     }
2819     if (empty($fields)) {
2820         $fields ="u.id,
2821                   u.username,
2822                   u.firstname,
2823                   u.lastname,
2824                   u.maildisplay,
2825                   u.mailformat,
2826                   u.maildigest,
2827                   u.imagealt,
2828                   u.email,
2829                   u.city,
2830                   u.country,
2831                   u.lastaccess,
2832                   u.lastlogin,
2833                   u.picture,
2834                   u.timezone,
2835                   u.theme,
2836                   u.lang,
2837                   u.trackforums,
2838                   u.mnethostid";
2839     }
2841     if (forum_is_forcesubscribed($forum)) {
2842         if (empty($context)) {
2843             $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2844             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2845         }
2846         $sort = "u.email ASC";
2847         $results = forum_get_potential_subscribers($context, $groupid, $fields, $sort);
2848     } else {
2849         $results = $DB->get_records_sql("SELECT $fields
2850                               FROM {user} u,
2851                                    {forum_subscriptions} s $grouptables
2852                              WHERE s.forum = ?
2853                                AND s.userid = u.id
2854                                AND u.deleted = 0  $groupselect
2855                           ORDER BY u.email ASC", $params);
2856     }
2858     static $guestid = null;
2860     if (is_null($guestid)) {
2861         if ($guest = guest_user()) {
2862             $guestid = $guest->id;
2863         } else {
2864             $guestid = 0;
2865         }
2866     }
2868     // Guest user should never be subscribed to a forum.
2869     unset($results[$guestid]);
2871     return $results;
2876 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2879 /**
2880  * @global object
2881  * @global object
2882  * @param int $courseid
2883  * @param string $type
2884  */
2885 function forum_get_course_forum($courseid, $type) {
2886 // How to set up special 1-per-course forums
2887     global $CFG, $DB, $OUTPUT;
2889     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2890         // There should always only be ONE, but with the right combination of
2891         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2892         foreach ($forums as $forum) {
2893             return $forum;   // ie the first one
2894         }
2895     }
2897     // Doesn't exist, so create one now.
2898     $forum->course = $courseid;
2899     $forum->type = "$type";
2900     switch ($forum->type) {
2901         case "news":
2902             $forum->name  = get_string("namenews", "forum");
2903             $forum->intro = get_string("intronews", "forum");
2904             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2905             $forum->assessed = 0;
2906             if ($courseid == SITEID) {
2907                 $forum->name  = get_string("sitenews");
2908                 $forum->forcesubscribe = 0;
2909             }
2910             break;
2911         case "social":
2912             $forum->name  = get_string("namesocial", "forum");
2913             $forum->intro = get_string("introsocial", "forum");
2914             $forum->assessed = 0;
2915             $forum->forcesubscribe = 0;
2916             break;
2917         case "blog":
2918             $forum->name = get_string('blogforum', 'forum');
2919             $forum->intro = get_string('introblog', 'forum');
2920             $forum->assessed = 0;
2921             $forum->forcesubscribe = 0;
2922             break;
2923         default:
2924             echo $OUTPUT->notification("That forum type doesn't exist!");
2925             return false;
2926             break;
2927     }
2929     $forum->timemodified = time();
2930     $forum->id = $DB->insert_record("forum", $forum);
2932     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2933         echo $OUTPUT->notification("Could not find forum module!!");
2934         return false;
2935     }
2936     $mod = new stdClass();
2937     $mod->course = $courseid;
2938     $mod->module = $module->id;
2939     $mod->instance = $forum->id;
2940     $mod->section = 0;
2941     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
2942         echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
2943         return false;
2944     }
2945     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
2946         echo $OUTPUT->notification("Could not add the new course module to that section");
2947         return false;
2948     }
2949     $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule));
2951     include_once("$CFG->dirroot/course/lib.php");
2952     rebuild_course_cache($courseid);
2954     return $DB->get_record("forum", array("id" => "$forum->id"));
2958 /**
2959  * Given the data about a posting, builds up the HTML to display it and
2960  * returns the HTML in a string.  This is designed for sending via HTML email.
2961  *
2962  * @global object
2963  * @param object $course
2964  * @param object $cm
2965  * @param object $forum
2966  * @param object $discussion
2967  * @param object $post
2968  * @param object $userform
2969  * @param object $userto
2970  * @param bool $ownpost
2971  * @param bool $reply
2972  * @param bool $link
2973  * @param bool $rate
2974  * @param string $footer
2975  * @return string
2976  */
2977 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
2978                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
2980     global $CFG, $OUTPUT;
2982     if (!isset($userto->viewfullnames[$forum->id])) {
2983         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
2984             print_error('invalidcoursemodule');
2985         }
2986         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2987         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
2988     } else {
2989         $viewfullnames = $userto->viewfullnames[$forum->id];
2990     }
2992     // format the post body
2993     $options = new stdClass();
2994     $options->para = true;
2995     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
2997     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
2999     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3000     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3001     $output .= '</td>';
3003     if ($post->parent) {
3004         $output .= '<td class="topic">';
3005     } else {
3006         $output .= '<td class="topic starter">';
3007     }
3008     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3010     $fullname = fullname($userfrom, $viewfullnames);
3011     $by = new stdClass();
3012     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3013     $by->date = userdate($post->modified, '', $userto->timezone);
3014     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3016     $output .= '</td></tr>';
3018     $output .= '<tr><td class="left side" valign="top">';
3020     if (isset($userfrom->groups)) {
3021         $groups = $userfrom->groups[$forum->id];
3022     } else {
3023         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
3024             print_error('invalidcoursemodule');
3025         }
3026         $group = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3027     }
3029     if ($groups) {
3030         $output .= print_group_picture($groups, $course->id, false, true, true);
3031     } else {
3032         $output .= '&nbsp;';
3033     }
3035     $output .= '</td><td class="content">';
3037     $attachments = forum_print_attachments($post, $cm, 'html');
3038     if ($attachments !== '') {
3039         $output .= '<div class="attachments">';
3040         $output .= $attachments;
3041         $output .= '</div>';
3042     }
3044     $output .= $formattedtext;
3046 // Commands
3047     $commands = array();
3049     if ($post->parent) {
3050         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3051                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3052     }
3054     if ($reply) {
3055         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3056                       get_string('reply', 'forum').'</a>';
3057     }
3059     $output .= '<div class="commands">';
3060     $output .= implode(' | ', $commands);
3061     $output .= '</div>';
3063 // Context link to post if required
3064     if ($link) {
3065         $output .= '<div class="link">';
3066         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3067                      get_string('postincontext', 'forum').'</a>';
3068         $output .= '</div>';
3069     }
3071     if ($footer) {
3072         $output .= '<div class="footer">'.$footer.'</div>';
3073     }
3074     $output .= '</td></tr></table>'."\n\n";
3076     return $output;
3079 /**
3080  * Print a forum post
3081  *
3082  * @global object
3083  * @global object
3084  * @uses FORUM_MODE_THREADED
3085  * @uses PORTFOLIO_FORMAT_PLAINHTML
3086  * @uses PORTFOLIO_FORMAT_FILE
3087  * @uses PORTFOLIO_FORMAT_RICHHTML
3088  * @uses PORTFOLIO_ADD_TEXT_LINK
3089  * @uses CONTEXT_MODULE
3090  * @param object $post The post to print.
3091  * @param object $discussion
3092  * @param object $forum
3093  * @param object $cm
3094  * @param object $course
3095  * @param boolean $ownpost Whether this post belongs to the current user.
3096  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3097  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3098  * @param string $footer Extra stuff to print after the message.
3099  * @param string $highlight Space-separated list of terms to highlight.
3100  * @param int $post_read true, false or -99. If we already know whether this user
3101  *          has read this post, pass that in, otherwise, pass in -99, and this
3102  *          function will work it out.
3103  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3104  *          the current user can't see this post, if this argument is true
3105  *          (the default) then print a dummy 'you can't see this post' post.
3106  *          If false, don't output anything at all.
3107  * @param bool|null $istracked
3108  * @return void
3109  */
3110 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3111                           $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3112     global $USER, $CFG, $OUTPUT;
3114     require_once($CFG->libdir . '/filelib.php');
3116     // String cache
3117     static $str;
3119     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3121     $post->course = $course->id;
3122     $post->forum  = $forum->id;
3123     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3125     // caching
3126     if (!isset($cm->cache)) {
3127         $cm->cache = new stdClass;
3128     }
3130     if (!isset($cm->cache->caps)) {
3131         $cm->cache->caps = array();
3132         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3133         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3134         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3135         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3136         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3137         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3138         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3139         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3140         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3141     }
3143     if (!isset($cm->uservisible)) {
3144         $cm->uservisible = coursemodule_visible_for_user($cm);
3145     }
3147     if ($istracked && is_null($postisread)) {
3148         $postisread = forum_tp_is_post_read($USER->id, $post);
3149     }
3151     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3152         $output = '';
3153         if (!$dummyifcantsee) {
3154             if ($return) {
3155                 return $output;
3156             }
3157             echo $output;
3158             return;
3159         }
3160         $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3161         $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'));
3162         $output .= html_writer::start_tag('div', array('class'=>'row header'));
3163         $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3164         if ($post->parent) {
3165             $output .= html_writer::start_tag('div', array('class'=>'topic'));
3166         } else {
3167             $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3168         }
3169         $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class'=>'subject')); // Subject
3170         $output .= html_writer::tag('div', get_string('forumauthorhidden','forum'), array('class'=>'author')); // author
3171         $output .= html_writer::end_tag('div');
3172         $output .= html_writer::end_tag('div'); // row
3173         $output .= html_writer::start_tag('div', array('class'=>'row'));
3174         $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3175         $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3176         $output .= html_writer::end_tag('div'); // row
3177         $output .= html_writer::end_tag('div'); // forumpost
3179         if ($return) {
3180             return $output;
3181         }
3182         echo $output;
3183         return;
3184     }
3186     if (empty($str)) {
3187         $str = new stdClass;
3188         $str->edit         = get_string('edit', 'forum');
3189         $str->delete       = get_string('delete', 'forum');
3190         $str->reply        = get_string('reply', 'forum');
3191         $str->parent       = get_string('parent', 'forum');
3192         $str->pruneheading = get_string('pruneheading', 'forum');
3193         $str->prune        = get_string('prune', 'forum');
3194         $str->displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3195         $str->markread     = get_string('markread', 'forum');
3196         $str->markunread   = get_string('markunread', 'forum');
3197     }
3199     $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3201     // Build an object that represents the posting user
3202     $postuser = new stdClass;
3203     $postuser->id        = $post->userid;
3204     $postuser->firstname = $post->firstname;
3205     $postuser->lastname  = $post->lastname;
3206     $postuser->imagealt  = $post->imagealt;
3207     $postuser->picture   = $post->picture;
3208     $postuser->email     = $post->email;
3209     // Some handy things for later on
3210     $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3211     $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3213     // Prepare the groups the posting user belongs to
3214     if (isset($cm->cache->usersgroups)) {
3215         $groups = array();
3216         if (isset($cm->cache->usersgroups[$post->userid])) {
3217             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3218                 $groups[$gid] = $cm->cache->groups[$gid];
3219             }
3220         }
3221     } else {
3222         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3223     }
3225     // Prepare the attachements for the post, files then images
3226     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3228     // Determine if we need to shorten this post
3229     $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3232     // Prepare an array of commands
3233     $commands = array();
3235     // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3236     // Don't display the mark read / unread controls in this case.
3237     if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3238         $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3239         $text = $str->markunread;
3240         if (!$postisread) {
3241             $url->param('mark', 'read');
3242             $text = $str->markread;
3243         }
3244         if ($str->displaymode == FORUM_MODE_THREADED) {
3245             $url->param('parent', $post->parent);
3246         } else {
3247             $url->set_anchor('p'.$post->id);
3248         }
3249         $commands[] = array('url'=>$url, 'text'=>$text);
3250     }
3252     // Zoom in to the parent specifically
3253     if ($post->parent) {
3254         $url = new moodle_url($discussionlink);
3255         if ($str->displaymode == FORUM_MODE_THREADED) {
3256             $url->param('parent', $post->parent);
3257         } else {
3258             $url->set_anchor('p'.$post->parent);
3259         }
3260         $commands[] = array('url'=>$url, 'text'=>$str->parent);
3261     }
3263     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3264     $age = time() - $post->created;
3265     if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3266         $age = 0;
3267     }
3268     if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3269         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3270     }
3272     if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3273         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3274     }
3276     if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3277         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3278     }
3280     if ($reply) {
3281         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('reply'=>$post->id)), 'text'=>$str->reply);
3282     }
3284     if ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost'])) {
3285         $p = array('postid' => $post->id);
3286         require_once($CFG->libdir.'/portfoliolib.php');
3287         $button = new portfolio_add_button();
3288         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php');
3289         if (empty($attachments)) {
3290             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3291         } else {
3292             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3293         }
3295         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3296         if (!empty($porfoliohtml)) {
3297             $commands[] = $porfoliohtml;
3298         }
3299     }
3300     // Finished building commands
3303     // Begin output
3305     $output  = '';
3307     if ($istracked) {
3308         if ($postisread) {
3309             $forumpostclass = ' read';
3310         } else {
3311             $forumpostclass = ' unread';
3312             $output .= html_writer::tag('a', '', array('name'=>'unread'));
3313         }
3314     } else {
3315         // ignore trackign status if not tracked or tracked param missing
3316         $forumpostclass = '';
3317     }
3319     $topicclass = '';
3320     if (empty($post->parent)) {
3321         $topicclass = ' firstpost starter';
3322     }
3324     $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3325     $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass));
3326     $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3327     $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3328     $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3329     $output .= html_writer::end_tag('div');
3332     $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3334     $postsubject = $post->subject;
3335     if (empty($post->subjectnoformat)) {
3336         $postsubject = format_string($postsubject);
3337     }
3338     $output .= html_writer::tag('div', $postsubject, array('class'=>'subject'));
3340     $by = new stdClass();
3341     $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3342     $by->date = userdate($post->modified);
3343     $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author'));
3345     $output .= html_writer::end_tag('div'); //topic
3346     $output .= html_writer::end_tag('div'); //row
3348     $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3349     $output .= html_writer::start_tag('div', array('class'=>'left'));
3351     $groupoutput = '';
3352     if ($groups) {
3353         $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3354     }
3355     if (empty($groupoutput)) {
3356         $groupoutput = '&nbsp;';
3357     }
3358     $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3360     $output .= html_writer::end_tag('div'); //left side
3361     $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3362     $output .= html_writer::start_tag('div', array('class'=>'content'));
3363     if (!empty($attachments)) {
3364         $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3365     }
3367     $options = new stdClass;
3368     $options->para    = false;
3369     $options->trusted = $post->messagetrust;
3370     $options->context = $modcontext;
3371     if ($shortenpost) {
3372         // Prepare shortened version
3373         $postclass    = 'shortenedpost';
3374         $postcontent  = format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3375         $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
3376         $postcontent .= html_writer::tag('span', '('.get_string('numwords', 'moodle', count_words(strip_tags($post->message))).')...', array('class'=>'post-word-count'));
3377     } else {
3378         // Prepare whole post
3379         $postclass    = 'fullpost';
3380         $postcontent  = format_text($post->message, $post->messageformat, $options, $course->id);
3381         if (!empty($highlight)) {
3382             $postcontent = highlight($highlight, $postcontent);
3383         }
3384         $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
3385     }
3386     // Output the post content
3387     $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
3388     $output .= html_writer::end_tag('div'); // Content
3389     $output .= html_writer::end_tag('div'); // Content mask
3390     $output .= html_writer::end_tag('div'); // Row
3392     $output .= html_writer::start_tag('div', array('class'=>'row side'));
3393     $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
3394     $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
3396     // Output ratings
3397     if (!empty($post->rating)) {
3398         $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
3399     }
3401     // Output the commands
3402     $commandhtml = array();
3403     foreach ($commands as $command) {
3404         if (is_array($command)) {
3405             $commandhtml[] = html_writer::link($command['url'], $command['text']);
3406         } else {
3407             $commandhtml[] = $command;
3408         }
3409     }
3410     $output .= html_writer::tag('div', implode(' | ', $commandhtml), array('class'=>'commands'));
3412     // Output link to post if required
3413     if ($link) {
3414         if ($post->replies == 1) {
3415             $replystring = get_string('repliesone', 'forum', $post->replies);
3416         } else {
3417             $replystring = get_string('repliesmany', 'forum', $post->replies);
3418         }
3420         $output .= html_writer::start_tag('div', array('class'=>'link'));
3421         $output .= html_writer::link($discussionlink, get_string('discussthistopic', 'forum'));
3422         $output .= '&nbsp;('.$replystring.')';
3423         $output .= html_writer::end_tag('div'); // link
3424     }
3426     // Output footer if required
3427     if ($footer) {
3428         $output .= html_writer::tag('div', $footer, array('class'=>'footer'));
3429     }
3431     // Close remaining open divs
3432     $output .= html_writer::end_tag('div'); // content
3433     $output .= html_writer::end_tag('div'); // row
3434     $output .= html_writer::end_tag('div'); // forumpost
3436     // Mark the forum post as read if required
3437     if ($istracked && !$CFG->forum_usermarksread && !$postisread) {
3438         forum_tp_mark_post_read($USER->id, $post, $forum->id);
3439     }
3441     if ($return) {
3442         return $output;
3443     }
3444     echo $output;
3445     return;
3448 /**
3449  * Return rating related permissions
3450  * @param string $options the context id
3451  * @return array an associative array of the user's rating permissions
3452  */
3453 function forum_rating_permissions($contextid) {
3454     $context = get_context_instance_by_id($contextid);
3456     if (!$context) {
3457         print_error('invalidcontext');
3458         return null;
3459     } else {
3460         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));
3461     }
3464 /**
3465  * Returns the names of the table and columns necessary to check items for ratings
3466  * @return array an array containing the item table, item id and user id columns
3467  */
3468 function forum_rating_item_check_info() {
3469     return array('forum_posts','id','userid');
3473 /**
3474  * This function prints the overview of a discussion in the forum listing.
3475  * It needs some discussion information and some post information, these
3476  * happen to be combined for efficiency in the $post parameter by the function
3477  * that calls this one: forum_print_latest_discussions()
3478  *
3479  * @global object
3480  * @global object
3481  * @param object $post The post object (passed by reference for speed).
3482  * @param object $forum The forum object.
3483  * @param int $group Current group.
3484  * @param string $datestring Format to use for the dates.
3485  * @param boolean $cantrack Is tracking enabled for this forum.
3486  * @param boolean $forumtracked Is the user tracking this forum.
3487  * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
3488  */
3489 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
3490                                         $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) {
3492     global $USER, $CFG, $OUTPUT;
3494     static $rowcount;
3495     static $strmarkalldread;
3497     if (empty($modcontext)) {
3498         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3499             print_error('invalidcoursemodule');
3500         }