MDL-24698 hopefully fixed all completion_info problems
[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)) {
465                     foreach ($subusers as $postuser) {
466                         // this user is subscribed to this forum
467                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
468                         // this user is a user we have to process later
469                         $users[$postuser->id] = $postuser;
470                     }
471                     unset($subusers); // release memory
472                 }
473             }
475             $mailcount[$pid] = 0;
476             $errorcount[$pid] = 0;
477         }
478     }
480     if ($users && $posts) {
482         $urlinfo = parse_url($CFG->wwwroot);
483         $hostname = $urlinfo['host'];
485         foreach ($users as $userto) {
487             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
489             // set this so that the capabilities are cached, and environment matches receiving user
490             cron_setup_user($userto);
492             mtrace('Processing user '.$userto->id);
494             // init caches
495             $userto->viewfullnames = array();
496             $userto->canpost       = array();
497             $userto->markposts     = array();
498             $userto->enrolledin    = array();
500             // reset the caches
501             foreach ($coursemodules as $forumid=>$unused) {
502                 $coursemodules[$forumid]->cache       = new stdClass();
503                 $coursemodules[$forumid]->cache->caps = array();
504                 unset($coursemodules[$forumid]->uservisible);
505             }
507             foreach ($posts as $pid => $post) {
509                 // Set up the environment for the post, discussion, forum, course
510                 $discussion = $discussions[$post->discussion];
511                 $forum      = $forums[$discussion->forum];
512                 $course     = $courses[$forum->course];
513                 $cm         =& $coursemodules[$forum->id];
515                 // Do some checks  to see if we can bail out now
516                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
517                     continue; // user does not subscribe to this forum
518                 }
520                 // Verify user is enrollend in course - if not do not send any email
521                 if (!isset($userto->enrolledin[$course->id])) {
522                     $userto->enrolledin[$course->id] = is_enrolled(get_context_instance(CONTEXT_COURSE, $course->id));
523                 }
524                 if (!$userto->enrolledin[$course->id]) {
525                     // oops - this user should not receive anything from this course
526                     continue;
527                 }
529                 // Get info about the sending user
530                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
531                     $userfrom = $users[$post->userid];
532                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
533                     $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
534                 } else {
535                     mtrace('Could not find user '.$post->userid);
536                     continue;
537                 }
539                 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
541                 // setup global $COURSE properly - needed for roles and languages
542                 cron_setup_user($userto, $course);
544                 // Fill caches
545                 if (!isset($userto->viewfullnames[$forum->id])) {
546                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
547                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
548                 }
549                 if (!isset($userto->canpost[$discussion->id])) {
550                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
551                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
552                 }
553                 if (!isset($userfrom->groups[$forum->id])) {
554                     if (!isset($userfrom->groups)) {
555                         $userfrom->groups = array();
556                         $users[$userfrom->id]->groups = array();
557                     }
558                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
559                     $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
560                 }
562                 // Make sure groups allow this user to see this email
563                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
564                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
565                         continue;                           // Be safe and don't send it to anyone
566                     }
568                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
569                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
570                         continue;
571                     }
572                 }
574                 // Make sure we're allowed to see it...
575                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
576                     mtrace('user '.$userto->id. ' can not see '.$post->id);
577                     continue;
578                 }
580                 // OK so we need to send the email.
582                 // Does the user want this post in a digest?  If so postpone it for now.
583                 if ($userto->maildigest > 0) {
584                     // This user wants the mails to be in digest form
585                     $queue = new stdClass();
586                     $queue->userid       = $userto->id;
587                     $queue->discussionid = $discussion->id;
588                     $queue->postid       = $post->id;
589                     $queue->timemodified = $post->created;
590                     $DB->insert_record('forum_queue', $queue);
591                     continue;
592                 }
595                 // Prepare to actually send the post now, and build up the content
597                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
599                 $userfrom->customheaders = array (  // Headers to make emails easier to track
600                            'Precedence: Bulk',
601                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
602                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
603                            'Message-ID: <moodlepost'.$post->id.'@'.$hostname.'>',
604                            'X-Course-Id: '.$course->id,
605                            'X-Course-Name: '.format_string($course->fullname, true)
606                 );
608                 if ($post->parent) {  // This post is a reply, so add headers for threading (see MDL-22551)
609                     $userfrom->customheaders[] = 'In-Reply-To: <moodlepost'.$post->parent.'@'.$hostname.'>';
610                     $userfrom->customheaders[] = 'References: <moodlepost'.$post->parent.'@'.$hostname.'>';
611                 }
613                 $postsubject = "$course->shortname: ".format_string($post->subject,true);
614                 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
615                 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
617                 // Send the post now!
619                 mtrace('Sending ', '');
621                 $eventdata = new stdClass();
622                 $eventdata->component        = 'mod_forum';
623                 $eventdata->name             = 'posts';
624                 $eventdata->userfrom         = $userfrom;
625                 $eventdata->userto           = $userto;
626                 $eventdata->subject          = $postsubject;
627                 $eventdata->fullmessage      = $posttext;
628                 $eventdata->fullmessageformat = FORMAT_PLAIN;
629                 $eventdata->fullmessagehtml  = $posthtml;
630                 $eventdata->notification = 1;
632                 $smallmessagestrings = new stdClass();
633                 $smallmessagestrings->user = fullname($userfrom);
634                 $smallmessagestrings->forumname = "{$course->shortname}: ".format_string($forum->name,true).": ".$discussion->name;
635                 $smallmessagestrings->message = $post->message;
636                 //make sure strings are in message recipients language
637                 $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
639                 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
640                 $eventdata->contexturlname = $discussion->name;
642                 $mailresult = message_send($eventdata);
643                 if (!$mailresult){
644                     mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
645                          " ($userto->email) .. not trying again.");
646                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
647                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
648                     $errorcount[$post->id]++;
649                 } else {
650                     $mailcount[$post->id]++;
652                 // Mark post as read if forum_usermarksread is set off
653                     if (!$CFG->forum_usermarksread) {
654                         $userto->markposts[$post->id] = $post->id;
655                     }
656                 }
658                 mtrace('post '.$post->id. ': '.$post->subject);
659             }
661             // mark processed posts as read
662             forum_tp_mark_posts_read($userto, $userto->markposts);
663         }
664     }
666     if ($posts) {
667         foreach ($posts as $post) {
668             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
669             if ($errorcount[$post->id]) {
670                 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
671             }
672         }
673     }
675     // release some memory
676     unset($subscribedusers);
677     unset($mailcount);
678     unset($errorcount);
680     cron_setup_user();
682     $sitetimezone = $CFG->timezone;
684     // Now see if there are any digest mails waiting to be sent, and if we should send them
686     mtrace('Starting digest processing...');
688     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
690     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
691         set_config('digestmailtimelast', 0);
692     }
694     $timenow = time();
695     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
697     // Delete any really old ones (normally there shouldn't be any)
698     $weekago = $timenow - (7 * 24 * 3600);
699     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
700     mtrace ('Cleaned old digest records');
702     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
704         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
706         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
708         if ($digestposts_rs->valid()) {
710             // We have work to do
711             $usermailcount = 0;
713             //caches - reuse the those filled before too
714             $discussionposts = array();
715             $userdiscussions = array();
717             foreach ($digestposts_rs as $digestpost) {
718                 if (!isset($users[$digestpost->userid])) {
719                     if ($user = $DB->get_record('user', array('id' => $digestpost->userid))) {
720                         $users[$digestpost->userid] = $user;
721                     } else {
722                         continue;
723                     }
724                 }
725                 $postuser = $users[$digestpost->userid];
727                 if (!isset($posts[$digestpost->postid])) {
728                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
729                         $posts[$digestpost->postid] = $post;
730                     } else {
731                         continue;
732                     }
733                 }
734                 $discussionid = $digestpost->discussionid;
735                 if (!isset($discussions[$discussionid])) {
736                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
737                         $discussions[$discussionid] = $discussion;
738                     } else {
739                         continue;
740                     }
741                 }
742                 $forumid = $discussions[$discussionid]->forum;
743                 if (!isset($forums[$forumid])) {
744                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
745                         $forums[$forumid] = $forum;
746                     } else {
747                         continue;
748                     }
749                 }
751                 $courseid = $forums[$forumid]->course;
752                 if (!isset($courses[$courseid])) {
753                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
754                         $courses[$courseid] = $course;
755                     } else {
756                         continue;
757                     }
758                 }
760                 if (!isset($coursemodules[$forumid])) {
761                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
762                         $coursemodules[$forumid] = $cm;
763                     } else {
764                         continue;
765                     }
766                 }
767                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
768                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
769             }
770             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
772             // Data collected, start sending out emails to each user
773             foreach ($userdiscussions as $userid => $thesediscussions) {
775                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
777                 cron_setup_user();
779                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
781                 // First of all delete all the queue entries for this user
782                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
783                 $userto = $users[$userid];
785                 // Override the language and timezone of the "current" user, so that
786                 // mail is customised for the receiver.
787                 cron_setup_user($userto);
789                 // init caches
790                 $userto->viewfullnames = array();
791                 $userto->canpost       = array();
792                 $userto->markposts     = array();
794                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
796                 $headerdata = new stdClass();
797                 $headerdata->sitename = format_string($site->fullname, true);
798                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
800                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
801                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
803                 $posthtml = "<head>";
804 /*                foreach ($CFG->stylesheets as $stylesheet) {
805                     //TODO: MDL-21120
806                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
807                 }*/
808                 $posthtml .= "</head>\n<body id=\"email\">\n";
809                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
811                 foreach ($thesediscussions as $discussionid) {
813                     @set_time_limit(120);   // to be reset for each post
815                     $discussion = $discussions[$discussionid];
816                     $forum      = $forums[$discussion->forum];
817                     $course     = $courses[$forum->course];
818                     $cm         = $coursemodules[$forum->id];
820                     //override language
821                     cron_setup_user($userto, $course);
823                     // Fill caches
824                     if (!isset($userto->viewfullnames[$forum->id])) {
825                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
826                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
827                     }
828                     if (!isset($userto->canpost[$discussion->id])) {
829                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
830                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
831                     }
833                     $strforums      = get_string('forums', 'forum');
834                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
835                     $canreply       = $userto->canpost[$discussion->id];
837                     $posttext .= "\n \n";
838                     $posttext .= '=====================================================================';
839                     $posttext .= "\n \n";
840                     $posttext .= "$course->shortname -> $strforums -> ".format_string($forum->name,true);
841                     if ($discussion->name != $forum->name) {
842                         $posttext  .= " -> ".format_string($discussion->name,true);
843                     }
844                     $posttext .= "\n";
846                     $posthtml .= "<p><font face=\"sans-serif\">".
847                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> -> ".
848                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
849                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
850                     if ($discussion->name == $forum->name) {
851                         $posthtml .= "</font></p>";
852                     } else {
853                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
854                     }
855                     $posthtml .= '<p>';
857                     $postsarray = $discussionposts[$discussionid];
858                     sort($postsarray);
860                     foreach ($postsarray as $postid) {
861                         $post = $posts[$postid];
863                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
864                             $userfrom = $users[$post->userid];
865                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
866                             $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
867                         } else {
868                             mtrace('Could not find user '.$post->userid);
869                             continue;
870                         }
872                         if (!isset($userfrom->groups[$forum->id])) {
873                             if (!isset($userfrom->groups)) {
874                                 $userfrom->groups = array();
875                                 $users[$userfrom->id]->groups = array();
876                             }
877                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
878                             $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
879                         }
881                         $userfrom->customheaders = array ("Precedence: Bulk");
883                         if ($userto->maildigest == 2) {
884                             // Subjects only
885                             $by = new stdClass();
886                             $by->name = fullname($userfrom);
887                             $by->date = userdate($post->modified);
888                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
889                             $posttext .= "\n---------------------------------------------------------------------";
891                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
892                             $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>';
894                         } else {
895                             // The full treatment
896                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
897                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
899                         // Create an array of postid's for this user to mark as read.
900                             if (!$CFG->forum_usermarksread) {
901                                 $userto->markposts[$post->id] = $post->id;
902                             }
903                         }
904                     }
905                     if ($canunsubscribe) {
906                         $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>";
907                     } else {
908                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
909                     }
910                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
911                 }
912                 $posthtml .= '</body>';
914                 if (empty($userto->mailformat) || $userto->mailformat != 1) {
915                     // This user DOESN'T want to receive HTML
916                     $posthtml = '';
917                 }
919                 $attachment = $attachname='';
920                 $usetrueaddress = true;
921                 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname, $usetrueaddress, $CFG->forum_replytouser);
923                 if (!$mailresult) {
924                     mtrace("ERROR!");
925                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
926                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
927                 } else {
928                     mtrace("success.");
929                     $usermailcount++;
931                     // Mark post as read if forum_usermarksread is set off
932                     forum_tp_mark_posts_read($userto, $userto->markposts);
933                 }
934             }
935         }
936     /// We have finishied all digest emails, update $CFG->digestmailtimelast
937         set_config('digestmailtimelast', $timenow);
938     }
940     cron_setup_user();
942     if (!empty($usermailcount)) {
943         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
944     }
946     if (!empty($CFG->forum_lastreadclean)) {
947         $timenow = time();
948         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
949             set_config('forum_lastreadclean', $timenow);
950             mtrace('Removing old forum read tracking info...');
951             forum_tp_clean_read_records();
952         }
953     } else {
954         set_config('forum_lastreadclean', time());
955     }
958     return true;
961 /**
962  * Builds and returns the body of the email notification in plain text.
963  *
964  * @global object
965  * @global object
966  * @uses CONTEXT_MODULE
967  * @param object $course
968  * @param object $cm
969  * @param object $forum
970  * @param object $discussion
971  * @param object $post
972  * @param object $userfrom
973  * @param object $userto
974  * @param boolean $bare
975  * @return string The email body in plain text format.
976  */
977 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
978     global $CFG, $USER;
980     if (!isset($userto->viewfullnames[$forum->id])) {
981         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
982         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
983     } else {
984         $viewfullnames = $userto->viewfullnames[$forum->id];
985     }
987     if (!isset($userto->canpost[$discussion->id])) {
988         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
989         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
990     } else {
991         $canreply = $userto->canpost[$discussion->id];
992     }
994     $by = New stdClass;
995     $by->name = fullname($userfrom, $viewfullnames);
996     $by->date = userdate($post->modified, "", $userto->timezone);
998     $strbynameondate = get_string('bynameondate', 'forum', $by);
1000     $strforums = get_string('forums', 'forum');
1002     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1004     $posttext = '';
1006     if (!$bare) {
1007         $posttext  = "$course->shortname -> $strforums -> ".format_string($forum->name,true);
1009         if ($discussion->name != $forum->name) {
1010             $posttext  .= " -> ".format_string($discussion->name,true);
1011         }
1012     }
1014     $posttext .= "\n---------------------------------------------------------------------\n";
1015     $posttext .= format_string($post->subject,true);
1016     if ($bare) {
1017         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1018     }
1019     $posttext .= "\n".$strbynameondate."\n";
1020     $posttext .= "---------------------------------------------------------------------\n";
1021     $posttext .= format_text_email($post->message, $post->messageformat);
1022     $posttext .= "\n\n";
1023     $posttext .= forum_print_attachments($post, $cm, "text");
1025     if (!$bare && $canreply) {
1026         $posttext .= "---------------------------------------------------------------------\n";
1027         $posttext .= get_string("postmailinfo", "forum", $course->shortname)."\n";
1028         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1029     }
1030     if (!$bare && $canunsubscribe) {
1031         $posttext .= "\n---------------------------------------------------------------------\n";
1032         $posttext .= get_string("unsubscribe", "forum");
1033         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1034     }
1036     return $posttext;
1039 /**
1040  * Builds and returns the body of the email notification in html format.
1041  *
1042  * @global object
1043  * @param object $course
1044  * @param object $cm
1045  * @param object $forum
1046  * @param object $discussion
1047  * @param object $post
1048  * @param object $userfrom
1049  * @param object $userto
1050  * @return string The email text in HTML format
1051  */
1052 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1053     global $CFG;
1055     if ($userto->mailformat != 1) {  // Needs to be HTML
1056         return '';
1057     }
1059     if (!isset($userto->canpost[$discussion->id])) {
1060         $canreply = forum_user_can_post($forum, $discussion, $userto);
1061     } else {
1062         $canreply = $userto->canpost[$discussion->id];
1063     }
1065     $strforums = get_string('forums', 'forum');
1066     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1068     $posthtml = '<head>';
1069 /*    foreach ($CFG->stylesheets as $stylesheet) {
1070         //TODO: MDL-21120
1071         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1072     }*/
1073     $posthtml .= '</head>';
1074     $posthtml .= "\n<body id=\"email\">\n\n";
1076     $posthtml .= '<div class="navbar">'.
1077     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$course->shortname.'</a> &raquo; '.
1078     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1079     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1080     if ($discussion->name == $forum->name) {
1081         $posthtml .= '</div>';
1082     } else {
1083         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1084                      format_string($discussion->name,true).'</a></div>';
1085     }
1086     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1088     if ($canunsubscribe) {
1089         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1090                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1091                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1092     }
1094     $posthtml .= '</body>';
1096     return $posthtml;
1100 /**
1101  *
1102  * @param object $course
1103  * @param object $user
1104  * @param object $mod TODO this is not used in this function, refactor
1105  * @param object $forum
1106  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1107  */
1108 function forum_user_outline($course, $user, $mod, $forum) {
1109     global $CFG;
1110     require_once("$CFG->libdir/gradelib.php");
1111     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1112     if (empty($grades->items[0]->grades)) {
1113         $grade = false;
1114     } else {
1115         $grade = reset($grades->items[0]->grades);
1116     }
1118     $count = forum_count_user_posts($forum->id, $user->id);
1120     if ($count && $count->postcount > 0) {
1121         $result = new stdClass();
1122         $result->info = get_string("numposts", "forum", $count->postcount);
1123         $result->time = $count->lastpost;
1124         if ($grade) {
1125             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1126         }
1127         return $result;
1128     } else if ($grade) {
1129         $result = new stdClass();
1130         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1131         $result->time = $grade->dategraded;
1132         return $result;
1133     }
1134     return NULL;
1138 /**
1139  * @global object
1140  * @global object
1141  * @param object $coure
1142  * @param object $user
1143  * @param object $mod
1144  * @param object $forum
1145  */
1146 function forum_user_complete($course, $user, $mod, $forum) {
1147     global $CFG,$USER, $OUTPUT;
1148     require_once("$CFG->libdir/gradelib.php");
1150     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1151     if (!empty($grades->items[0]->grades)) {
1152         $grade = reset($grades->items[0]->grades);
1153         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1154         if ($grade->str_feedback) {
1155             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1156         }
1157     }
1159     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1161         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1162             print_error('invalidcoursemodule');
1163         }
1164         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1166         foreach ($posts as $post) {
1167             if (!isset($discussions[$post->discussion])) {
1168                 continue;
1169             }
1170             $discussion = $discussions[$post->discussion];
1172             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1173         }
1174     } else {
1175         echo "<p>".get_string("noposts", "forum")."</p>";
1176     }
1184 /**
1185  * @global object
1186  * @global object
1187  * @global object
1188  * @param array $courses
1189  * @param array $htmlarray
1190  */
1191 function forum_print_overview($courses,&$htmlarray) {
1192     global $USER, $CFG, $DB, $SESSION;
1194     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1195         return array();
1196     }
1198     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1199         return;
1200     }
1203     // get all forum logs in ONE query (much better!)
1204     $params = array();
1205     $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {log} l "
1206         ." JOIN {course_modules} cm ON cm.id = cmid "
1207         ." WHERE (";
1208     foreach ($courses as $course) {
1209         $sql .= '(l.course = ? AND l.time > ?) OR ';
1210         $params[] = $course->id;
1211         $params[] = $course->lastaccess;
1212     }
1213     $sql = substr($sql,0,-3); // take off the last OR
1215     $sql .= ") AND l.module = 'forum' AND action = 'add post' "
1216         ." AND userid != ? GROUP BY cmid,l.course,instance";
1218     $params[] = $USER->id;
1220     if (!$new = $DB->get_records_sql($sql, $params)) {
1221         $new = array(); // avoid warnings
1222     }
1224     // also get all forum tracking stuff ONCE.
1225     $trackingforums = array();
1226     foreach ($forums as $forum) {
1227         if (forum_tp_can_track_forums($forum)) {
1228             $trackingforums[$forum->id] = $forum;
1229         }
1230     }
1232     if (count($trackingforums) > 0) {
1233         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1234         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1235             ' FROM {forum_posts} p '.
1236             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1237             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1238         $params = array($USER->id);
1240         foreach ($trackingforums as $track) {
1241             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1242             $params[] = $track->id;
1243             if (isset($SESSION->currentgroup[$track->course])) {
1244                 $groupid =  $SESSION->currentgroup[$track->course];
1245             } else {
1246                 $groupid = groups_get_all_groups($track->course, $USER->id);
1247                 if (is_array($groupid)) {
1248                     $groupid = array_shift(array_keys($groupid));
1249                     $SESSION->currentgroup[$track->course] = $groupid;
1250                 } else {
1251                     $groupid = 0;
1252                 }
1253             }
1254             $params[] = $groupid;
1255         }
1256         $sql = substr($sql,0,-3); // take off the last OR
1257         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1258         $params[] = $cutoffdate;
1260         if (!$unread = $DB->get_records_sql($sql, $params)) {
1261             $unread = array();
1262         }
1263     } else {
1264         $unread = array();
1265     }
1267     if (empty($unread) and empty($new)) {
1268         return;
1269     }
1271     $strforum = get_string('modulename','forum');
1272     $strnumunread = get_string('overviewnumunread','forum');
1273     $strnumpostssince = get_string('overviewnumpostssince','forum');
1275     foreach ($forums as $forum) {
1276         $str = '';
1277         $count = 0;
1278         $thisunread = 0;
1279         $showunread = false;
1280         // either we have something from logs, or trackposts, or nothing.
1281         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1282             $count = $new[$forum->id]->count;
1283         }
1284         if (array_key_exists($forum->id,$unread)) {
1285             $thisunread = $unread[$forum->id]->count;
1286             $showunread = true;
1287         }
1288         if ($count > 0 || $thisunread > 0) {
1289             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1290                 $forum->name.'</a></div>';
1291             $str .= '<div class="info">';
1292             $str .= $count.' '.$strnumpostssince;
1293             if (!empty($showunread)) {
1294                 $str .= '<br />'.$thisunread .' '.$strnumunread;
1295             }
1296             $str .= '</div></div>';
1297         }
1298         if (!empty($str)) {
1299             if (!array_key_exists($forum->course,$htmlarray)) {
1300                 $htmlarray[$forum->course] = array();
1301             }
1302             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1303                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1304             }
1305             $htmlarray[$forum->course]['forum'] .= $str;
1306         }
1307     }
1310 /**
1311  * Given a course and a date, prints a summary of all the new
1312  * messages posted in the course since that date
1313  *
1314  * @global object
1315  * @global object
1316  * @global object
1317  * @uses CONTEXT_MODULE
1318  * @uses VISIBLEGROUPS
1319  * @param object $course
1320  * @param bool $viewfullnames capability
1321  * @param int $timestart
1322  * @return bool success
1323  */
1324 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1325     global $CFG, $USER, $DB, $OUTPUT;
1327     // do not use log table if possible, it may be huge and is expensive to join with other tables
1329     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1330                                               d.timestart, d.timeend, d.userid AS duserid,
1331                                               u.firstname, u.lastname, u.email, u.picture
1332                                          FROM {forum_posts} p
1333                                               JOIN {forum_discussions} d ON d.id = p.discussion
1334                                               JOIN {forum} f             ON f.id = d.forum
1335                                               JOIN {user} u              ON u.id = p.userid
1336                                         WHERE p.created > ? AND f.course = ?
1337                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1338          return false;
1339     }
1341     $modinfo =& get_fast_modinfo($course);
1343     $groupmodes = array();
1344     $cms    = array();
1346     $strftimerecent = get_string('strftimerecent');
1348     $printposts = array();
1349     foreach ($posts as $post) {
1350         if (!isset($modinfo->instances['forum'][$post->forum])) {
1351             // not visible
1352             continue;
1353         }
1354         $cm = $modinfo->instances['forum'][$post->forum];
1355         if (!$cm->uservisible) {
1356             continue;
1357         }
1358         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1360         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1361             continue;
1362         }
1364         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1365           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1366             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1367                 continue;
1368             }
1369         }
1371         $groupmode = groups_get_activity_groupmode($cm, $course);
1373         if ($groupmode) {
1374             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1375                 // oki (Open discussions have groupid -1)
1376             } else {
1377                 // separate mode
1378                 if (isguestuser()) {
1379                     // shortcut
1380                     continue;
1381                 }
1383                 if (is_null($modinfo->groups)) {
1384                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1385                 }
1387                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1388                     continue;
1389                 }
1390             }
1391         }
1393         $printposts[] = $post;
1394     }
1395     unset($posts);
1397     if (!$printposts) {
1398         return false;
1399     }
1401     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1402     echo "\n<ul class='unlist'>\n";
1404     foreach ($printposts as $post) {
1405         $subjectclass = empty($post->parent) ? ' bold' : '';
1407         echo '<li><div class="head">'.
1408                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1409                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1410              '</div>';
1411         echo '<div class="info'.$subjectclass.'">';
1412         if (empty($post->parent)) {
1413             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1414         } else {
1415             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1416         }
1417         $post->subject = break_up_long_words(format_string($post->subject, true));
1418         echo $post->subject;
1419         echo "</a>\"</div></li>\n";
1420     }
1422     echo "</ul>\n";
1424     return true;
1427 /**
1428  * Return grade for given user or all users.
1429  *
1430  * @global object
1431  * @global object
1432  * @param object $forum
1433  * @param int $userid optional user id, 0 means all users
1434  * @return array array of grades, false if none
1435  */
1436 function forum_get_user_grades($forum, $userid=0) {
1437     global $CFG;
1439     require_once($CFG->dirroot.'/rating/lib.php');
1440     $rm = new rating_manager();
1442     $ratingoptions = new stdclass();
1444     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1445     $ratingoptions->modulename = 'forum';
1446     $ratingoptions->moduleid   = $forum->id;
1447     //$ratingoptions->cmidnumber = $forum->cmidnumber;
1449     $ratingoptions->userid = $userid;
1450     $ratingoptions->aggregationmethod = $forum->assessed;
1451     $ratingoptions->scaleid = $forum->scale;
1452     $ratingoptions->itemtable = 'forum_posts';
1453     $ratingoptions->itemtableusercolumn = 'userid';
1455     return $rm->get_user_grades($ratingoptions);
1458 /**
1459  * Update activity grades
1460  *
1461  * @global object
1462  * @global object
1463  * @param object $forum
1464  * @param int $userid specific user only, 0 means all
1465  * @param boolean $nullifnone return null if grade does not exist
1466  * @return void
1467  */
1468 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1469     global $CFG, $DB;
1470     require_once($CFG->libdir.'/gradelib.php');
1472     if (!$forum->assessed) {
1473         forum_grade_item_update($forum);
1475     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1476         forum_grade_item_update($forum, $grades);
1478     } else if ($userid and $nullifnone) {
1479         $grade = new stdClass();
1480         $grade->userid   = $userid;
1481         $grade->rawgrade = NULL;
1482         forum_grade_item_update($forum, $grade);
1484     } else {
1485         forum_grade_item_update($forum);
1486     }
1489 /**
1490  * Update all grades in gradebook.
1491  * @global object
1492  */
1493 function forum_upgrade_grades() {
1494     global $DB;
1496     $sql = "SELECT COUNT('x')
1497               FROM {forum} f, {course_modules} cm, {modules} m
1498              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1499     $count = $DB->count_records_sql($sql);
1501     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1502               FROM {forum} f, {course_modules} cm, {modules} m
1503              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1504     if ($rs = $DB->get_recordset_sql($sql)) {
1505         $pbar = new progress_bar('forumupgradegrades', 500, true);
1506         $i=0;
1507         foreach ($rs as $forum) {
1508             $i++;
1509             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1510             forum_update_grades($forum, 0, false);
1511             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1512         }
1513         $rs->close();
1514     }
1517 /**
1518  * Create/update grade item for given forum
1519  *
1520  * @global object
1521  * @uses GRADE_TYPE_NONE
1522  * @uses GRADE_TYPE_VALUE
1523  * @uses GRADE_TYPE_SCALE
1524  * @param object $forum object with extra cmidnumber
1525  * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
1526  * @return int 0 if ok
1527  */
1528 function forum_grade_item_update($forum, $grades=NULL) {
1529     global $CFG;
1530     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1531         require_once($CFG->libdir.'/gradelib.php');
1532     }
1534     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1536     if (!$forum->assessed or $forum->scale == 0) {
1537         $params['gradetype'] = GRADE_TYPE_NONE;
1539     } else if ($forum->scale > 0) {
1540         $params['gradetype'] = GRADE_TYPE_VALUE;
1541         $params['grademax']  = $forum->scale;
1542         $params['grademin']  = 0;
1544     } else if ($forum->scale < 0) {
1545         $params['gradetype'] = GRADE_TYPE_SCALE;
1546         $params['scaleid']   = -$forum->scale;
1547     }
1549     if ($grades  === 'reset') {
1550         $params['reset'] = true;
1551         $grades = NULL;
1552     }
1554     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1557 /**
1558  * Delete grade item for given forum
1559  *
1560  * @global object
1561  * @param object $forum object
1562  * @return object grade_item
1563  */
1564 function forum_grade_item_delete($forum) {
1565     global $CFG;
1566     require_once($CFG->libdir.'/gradelib.php');
1568     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1572 /**
1573  * Returns the users with data in one forum
1574  * (users with records in forum_subscriptions, forum_posts, students)
1575  *
1576  * @global object
1577  * @global object
1578  * @param int $forumid
1579  * @return mixed array or false if none
1580  */
1581 function forum_get_participants($forumid) {
1583     global $CFG, $DB;
1585     //Get students from forum_subscriptions
1586     $st_subscriptions = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1587                                          FROM {user} u,
1588                                               {forum_subscriptions} s
1589                                          WHERE s.forum = ? AND
1590                                                u.id = s.userid", array($forumid));
1591     //Get students from forum_posts
1592     $st_posts = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1593                                  FROM {user} u,
1594                                       {forum_discussions} d,
1595                                       {forum_posts} p
1596                                  WHERE d.forum = ? AND
1597                                        p.discussion = d.id AND
1598                                        u.id = p.userid", array($forumid));
1600     //Get students from the ratings table
1601     $st_ratings = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1602                                    FROM {user} u,
1603                                         {forum_discussions} d,
1604                                         {forum_posts} p,
1605                                         {ratings} r
1606                                    WHERE d.forum = ? AND
1607                                          p.discussion = d.id AND
1608                                          r.post = p.id AND
1609                                          u.id = r.userid", array($forumid));
1611     //Add st_posts to st_subscriptions
1612     if ($st_posts) {
1613         foreach ($st_posts as $st_post) {
1614             $st_subscriptions[$st_post->id] = $st_post;
1615         }
1616     }
1617     //Add st_ratings to st_subscriptions
1618     if ($st_ratings) {
1619         foreach ($st_ratings as $st_rating) {
1620             $st_subscriptions[$st_rating->id] = $st_rating;
1621         }
1622     }
1623     //Return st_subscriptions array (it contains an array of unique users)
1624     return ($st_subscriptions);
1627 /**
1628  * This function returns if a scale is being used by one forum
1629  *
1630  * @global object
1631  * @param int $forumid
1632  * @param int $scaleid negative number
1633  * @return bool
1634  */
1635 function forum_scale_used ($forumid,$scaleid) {
1636     global $DB;
1637     $return = false;
1639     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1641     if (!empty($rec) && !empty($scaleid)) {
1642         $return = true;
1643     }
1645     return $return;
1648 /**
1649  * Checks if scale is being used by any instance of forum
1650  *
1651  * This is used to find out if scale used anywhere
1652  *
1653  * @global object
1654  * @param $scaleid int
1655  * @return boolean True if the scale is used by any forum
1656  */
1657 function forum_scale_used_anywhere($scaleid) {
1658     global $DB;
1659     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1660         return true;
1661     } else {
1662         return false;
1663     }
1666 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1668 /**
1669  * Gets a post with all info ready for forum_print_post
1670  * Most of these joins are just to get the forum id
1671  *
1672  * @global object
1673  * @global object
1674  * @param int $postid
1675  * @return mixed array of posts or false
1676  */
1677 function forum_get_post_full($postid) {
1678     global $CFG, $DB;
1680     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1681                              FROM {forum_posts} p
1682                                   JOIN {forum_discussions} d ON p.discussion = d.id
1683                                   LEFT JOIN {user} u ON p.userid = u.id
1684                             WHERE p.id = ?", array($postid));
1687 /**
1688  * Gets posts with all info ready for forum_print_post
1689  * We pass forumid in because we always know it so no need to make a
1690  * complicated join to find it out.
1691  *
1692  * @global object
1693  * @global object
1694  * @return mixed array of posts or false
1695  */
1696 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1697     global $CFG, $DB;
1699     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1700                               FROM {forum_posts} p
1701                          LEFT JOIN {user} u ON p.userid = u.id
1702                              WHERE p.discussion = ?
1703                                AND p.parent > 0 $sort", array($discussion));
1706 /**
1707  * Gets all posts in discussion including top parent.
1708  *
1709  * @global object
1710  * @global object
1711  * @global object
1712  * @param int $discussionid
1713  * @param string $sort
1714  * @param bool $tracking does user track the forum?
1715  * @return array of posts
1716  */
1717 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1718     global $CFG, $DB, $USER;
1720     $tr_sel  = "";
1721     $tr_join = "";
1722     $params = array();
1724     if ($tracking) {
1725         $now = time();
1726         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1727         $tr_sel  = ", fr.id AS postread";
1728         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1729         $params[] = $USER->id;
1730     }
1732     $params[] = $discussionid;
1733     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1734                                      FROM {forum_posts} p
1735                                           LEFT JOIN {user} u ON p.userid = u.id
1736                                           $tr_join
1737                                     WHERE p.discussion = ?
1738                                  ORDER BY $sort", $params)) {
1739         return array();
1740     }
1742     foreach ($posts as $pid=>$p) {
1743         if ($tracking) {
1744             if (forum_tp_is_post_old($p)) {
1745                  $posts[$pid]->postread = true;
1746             }
1747         }
1748         if (!$p->parent) {
1749             continue;
1750         }
1751         if (!isset($posts[$p->parent])) {
1752             continue; // parent does not exist??
1753         }
1754         if (!isset($posts[$p->parent]->children)) {
1755             $posts[$p->parent]->children = array();
1756         }
1757         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1758     }
1760     return $posts;
1763 /**
1764  * Gets posts with all info ready for forum_print_post
1765  * We pass forumid in because we always know it so no need to make a
1766  * complicated join to find it out.
1767  *
1768  * @global object
1769  * @global object
1770  * @param int $parent
1771  * @param int $forumid
1772  * @return array
1773  */
1774 function forum_get_child_posts($parent, $forumid) {
1775     global $CFG, $DB;
1777     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1778                               FROM {forum_posts} p
1779                          LEFT JOIN {user} u ON p.userid = u.id
1780                              WHERE p.parent = ?
1781                           ORDER BY p.created ASC", array($parent));
1784 /**
1785  * An array of forum objects that the user is allowed to read/search through.
1786  *
1787  * @global object
1788  * @global object
1789  * @global object
1790  * @param int $userid
1791  * @param int $courseid if 0, we look for forums throughout the whole site.
1792  * @return array of forum objects, or false if no matches
1793  *         Forum objects have the following attributes:
1794  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1795  *         viewhiddentimedposts
1796  */
1797 function forum_get_readable_forums($userid, $courseid=0) {
1799     global $CFG, $DB, $USER;
1800     require_once($CFG->dirroot.'/course/lib.php');
1802     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1803         print_error('notinstalled', 'forum');
1804     }
1806     if ($courseid) {
1807         $courses = $DB->get_records('course', array('id' => $courseid));
1808     } else {
1809         // If no course is specified, then the user can see SITE + his courses.
1810         $courses1 = $DB->get_records('course', array('id' => SITEID));
1811         $courses2 = enrol_get_users_courses($userid, true);
1812         $courses = array_merge($courses1, $courses2);
1813     }
1814     if (!$courses) {
1815         return array();
1816     }
1818     $readableforums = array();
1820     foreach ($courses as $course) {
1822         $modinfo =& get_fast_modinfo($course);
1823         if (is_null($modinfo->groups)) {
1824             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1825         }
1827         if (empty($modinfo->instances['forum'])) {
1828             // hmm, no forums?
1829             continue;
1830         }
1832         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1834         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1835             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1836                 continue;
1837             }
1838             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1839             $forum = $courseforums[$forumid];
1840             $forum->context = $context;
1841             $forum->cm = $cm;
1843             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1844                 continue;
1845             }
1847          /// group access
1848             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1849                 if (is_null($modinfo->groups)) {
1850                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1851                 }
1852                 if (isset($modinfo->groups[$cm->groupingid])) {
1853                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1854                     $forum->onlygroups[] = -1;
1855                 } else {
1856                     $forum->onlygroups = array(-1);
1857                 }
1858             }
1860         /// hidden timed discussions
1861             $forum->viewhiddentimedposts = true;
1862             if (!empty($CFG->forum_enabletimedposts)) {
1863                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1864                     $forum->viewhiddentimedposts = false;
1865                 }
1866             }
1868         /// qanda access
1869             if ($forum->type == 'qanda'
1870                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1872                 // We need to check whether the user has posted in the qanda forum.
1873                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1874                                                     // the user is allowed to see in this forum.
1875                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1876                     foreach ($discussionspostedin as $d) {
1877                         $forum->onlydiscussions[] = $d->id;
1878                     }
1879                 }
1880             }
1882             $readableforums[$forum->id] = $forum;
1883         }
1885         unset($modinfo);
1887     } // End foreach $courses
1889     return $readableforums;
1892 /**
1893  * Returns a list of posts found using an array of search terms.
1894  *
1895  * @global object
1896  * @global object
1897  * @global object
1898  * @param array $searchterms array of search terms, e.g. word +word -word
1899  * @param int $courseid if 0, we search through the whole site
1900  * @param int $limitfrom
1901  * @param int $limitnum
1902  * @param int &$totalcount
1903  * @param string $extrasql
1904  * @return array|bool Array of posts found or false
1905  */
1906 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1907                             &$totalcount, $extrasql='') {
1908     global $CFG, $DB, $USER;
1909     require_once($CFG->libdir.'/searchlib.php');
1911     $forums = forum_get_readable_forums($USER->id, $courseid);
1913     if (count($forums) == 0) {
1914         $totalcount = 0;
1915         return false;
1916     }
1918     $now = round(time(), -2); // db friendly
1920     $fullaccess = array();
1921     $where = array();
1922     $params = array();
1924     foreach ($forums as $forumid => $forum) {
1925         $select = array();
1927         if (!$forum->viewhiddentimedposts) {
1928             $select[] = "(d.userid = :userid OR (d.timestart < : AND (d.timeend = 0 OR d.timeend > :timeend)))";
1929             $params = array('userid'=>$USER->id, 'timestart'=>$now, 'timeend'=>$now);
1930         }
1932         $cm = $forum->cm;
1933         $context = $forum->context;
1935         if ($forum->type == 'qanda'
1936             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1937             if (!empty($forum->onlydiscussions)) {
1938                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda0');
1939                 $params = array_merge($params, $discussionid_params);
1940                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
1941             } else {
1942                 $select[] = "p.parent = 0";
1943             }
1944         }
1946         if (!empty($forum->onlygroups)) {
1947             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps0');
1948             $params = array_merge($params, $groupid_params);
1949             $select[] = "d.groupid $groupid_sql";
1950         }
1952         if ($select) {
1953             $selects = implode(" AND ", $select);
1954             $where[] = "(d.forum = :forum AND $selects)";
1955             $params['forum'] = $forumid;
1956         } else {
1957             $fullaccess[] = $forumid;
1958         }
1959     }
1961     if ($fullaccess) {
1962         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula0');
1963         $params = array_merge($params, $fullid_params);
1964         $where[] = "(d.forum $fullid_sql)";
1965     }
1967     $selectdiscussion = "(".implode(" OR ", $where).")";
1969     $messagesearch = '';
1970     $searchstring = '';
1972     // Need to concat these back together for parser to work.
1973     foreach($searchterms as $searchterm){
1974         if ($searchstring != '') {
1975             $searchstring .= ' ';
1976         }
1977         $searchstring .= $searchterm;
1978     }
1980     // We need to allow quoted strings for the search. The quotes *should* be stripped
1981     // by the parser, but this should be examined carefully for security implications.
1982     $searchstring = str_replace("\\\"","\"",$searchstring);
1983     $parser = new search_parser();
1984     $lexer = new search_lexer($parser);
1986     if ($lexer->parse($searchstring)) {
1987         $parsearray = $parser->get_parsed_array();
1988     // Experimental feature under 1.8! MDL-8830
1989     // Use alternative text searches if defined
1990     // This feature only works under mysql until properly implemented for other DBs
1991     // Requires manual creation of text index for forum_posts before enabling it:
1992     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
1993     // Experimental feature under 1.8! MDL-8830
1994         if (!empty($CFG->forum_usetextsearches)) {
1995             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
1996                                                  'p.userid', 'u.id', 'u.firstname',
1997                                                  'u.lastname', 'p.modified', 'd.forum');
1998         } else {
1999             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2000                                                  'p.userid', 'u.id', 'u.firstname',
2001                                                  'u.lastname', 'p.modified', 'd.forum');
2002         }
2003         $params = array_merge($params, $msparams);
2004     }
2006     $fromsql = "{forum_posts} p,
2007                   {forum_discussions} d,
2008                   {user} u";
2010     $selectsql = " $messagesearch
2011                AND p.discussion = d.id
2012                AND p.userid = u.id
2013                AND $selectdiscussion
2014                    $extrasql";
2016     $countsql = "SELECT COUNT(*)
2017                    FROM $fromsql
2018                   WHERE $selectsql";
2020     $searchsql = "SELECT p.*,
2021                          d.forum,
2022                          u.firstname,
2023                          u.lastname,
2024                          u.email,
2025                          u.picture,
2026                          u.imagealt,
2027                          u.email
2028                     FROM $fromsql
2029                    WHERE $selectsql
2030                 ORDER BY p.modified DESC";
2032     $totalcount = $DB->count_records_sql($countsql, $params);
2034     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2037 /**
2038  * Returns a list of ratings for a particular post - sorted.
2039  *
2040  * @global object
2041  * @global object
2042  * @param int $postid
2043  * @param string $sort
2044  * @return array Array of ratings or false
2045  */
2046 function forum_get_ratings($context, $postid, $sort="u.firstname ASC") {
2047     global $PAGE;
2049     $options = new stdclass();
2050     $options->context = $PAGE->context;
2051     $options->itemid = $postid;
2052     $options->sort = "ORDER BY $sort";
2054     $rm = new rating_manager();
2055     $rm->get_all_ratings_for_item($options);
2058 /**
2059  * Returns a list of all new posts that have not been mailed yet
2060  *
2061  * @global object
2062  * @global object
2063  * @param int $starttime posts created after this time
2064  * @param int $endtime posts created before this
2065  * @param int $now used for timed discussions only
2066  * @return array
2067  */
2068 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2069     global $CFG, $DB;
2071     $params = array($starttime, $endtime);
2072     if (!empty($CFG->forum_enabletimedposts)) {
2073         if (empty($now)) {
2074             $now = time();
2075         }
2076         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2077         $params[] = $now;
2078         $params[] = $now;
2079     } else {
2080         $timedsql = "";
2081     }
2083     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2084                               FROM {forum_posts} p
2085                                    JOIN {forum_discussions} d ON d.id = p.discussion
2086                              WHERE p.mailed = 0
2087                                    AND p.created >= ?
2088                                    AND (p.created < ? OR p.mailnow = 1)
2089                                    $timedsql
2090                           ORDER BY p.modified ASC", $params);
2093 /**
2094  * Marks posts before a certain time as being mailed already
2095  *
2096  * @global object
2097  * @global object
2098  * @param int $endtime
2099  * @param int $now Defaults to time()
2100  * @return bool
2101  */
2102 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2103     global $CFG, $DB;
2104     if (empty($now)) {
2105         $now = time();
2106     }
2108     if (empty($CFG->forum_enabletimedposts)) {
2109         return $DB->execute("UPDATE {forum_posts}
2110                                SET mailed = '1'
2111                              WHERE (created < ? OR mailnow = 1)
2112                                    AND mailed = 0", array($endtime));
2114     } else {
2115         return $DB->execute("UPDATE {forum_posts}
2116                                SET mailed = '1'
2117                              WHERE discussion NOT IN (SELECT d.id
2118                                                         FROM {forum_discussions} d
2119                                                        WHERE d.timestart > ?)
2120                                    AND (created < ? OR mailnow = 1)
2121                                    AND mailed = 0", array($now, $endtime));
2122     }
2125 /**
2126  * Get all the posts for a user in a forum suitable for forum_print_post
2127  *
2128  * @global object
2129  * @global object
2130  * @uses CONTEXT_MODULE
2131  * @return array
2132  */
2133 function forum_get_user_posts($forumid, $userid) {
2134     global $CFG, $DB;
2136     $timedsql = "";
2137     $params = array($forumid, $userid);
2139     if (!empty($CFG->forum_enabletimedposts)) {
2140         $cm = get_coursemodule_from_instance('forum', $forumid);
2141         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2142             $now = time();
2143             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2144             $params[] = $now;
2145             $params[] = $now;
2146         }
2147     }
2149     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2150                               FROM {forum} f
2151                                    JOIN {forum_discussions} d ON d.forum = f.id
2152                                    JOIN {forum_posts} p       ON p.discussion = d.id
2153                                    JOIN {user} u              ON u.id = p.userid
2154                              WHERE f.id = ?
2155                                    AND p.userid = ?
2156                                    $timedsql
2157                           ORDER BY p.modified ASC", $params);
2160 /**
2161  * Get all the discussions user participated in
2162  *
2163  * @global object
2164  * @global object
2165  * @uses CONTEXT_MODULE
2166  * @param int $forumid
2167  * @param int $userid
2168  * @return array Array or false
2169  */
2170 function forum_get_user_involved_discussions($forumid, $userid) {
2171     global $CFG, $DB;
2173     $timedsql = "";
2174     $params = array($forumid, $userid);
2175     if (!empty($CFG->forum_enabletimedposts)) {
2176         $cm = get_coursemodule_from_instance('forum', $forumid);
2177         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2178             $now = time();
2179             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2180             $params[] = $now;
2181             $params[] = $now;
2182         }
2183     }
2185     return $DB->get_records_sql("SELECT DISTINCT d.*
2186                               FROM {forum} f
2187                                    JOIN {forum_discussions} d ON d.forum = f.id
2188                                    JOIN {forum_posts} p       ON p.discussion = d.id
2189                              WHERE f.id = ?
2190                                    AND p.userid = ?
2191                                    $timedsql", $params);
2194 /**
2195  * Get all the posts for a user in a forum suitable for forum_print_post
2196  *
2197  * @global object
2198  * @global object
2199  * @param int $forumid
2200  * @param int $userid
2201  * @return array of counts or false
2202  */
2203 function forum_count_user_posts($forumid, $userid) {
2204     global $CFG, $DB;
2206     $timedsql = "";
2207     $params = array($forumid, $userid);
2208     if (!empty($CFG->forum_enabletimedposts)) {
2209         $cm = get_coursemodule_from_instance('forum', $forumid);
2210         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2211             $now = time();
2212             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2213             $params[] = $now;
2214             $params[] = $now;
2215         }
2216     }
2218     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2219                              FROM {forum} f
2220                                   JOIN {forum_discussions} d ON d.forum = f.id
2221                                   JOIN {forum_posts} p       ON p.discussion = d.id
2222                                   JOIN {user} u              ON u.id = p.userid
2223                             WHERE f.id = ?
2224                                   AND p.userid = ?
2225                                   $timedsql", $params);
2228 /**
2229  * Given a log entry, return the forum post details for it.
2230  *
2231  * @global object
2232  * @global object
2233  * @param object $log
2234  * @return array|null
2235  */
2236 function forum_get_post_from_log($log) {
2237     global $CFG, $DB;
2239     if ($log->action == "add post") {
2241         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2242                                            u.firstname, u.lastname, u.email, u.picture
2243                                  FROM {forum_discussions} d,
2244                                       {forum_posts} p,
2245                                       {forum} f,
2246                                       {user} u
2247                                 WHERE p.id = ?
2248                                   AND d.id = p.discussion
2249                                   AND p.userid = u.id
2250                                   AND u.deleted <> '1'
2251                                   AND f.id = d.forum", array($log->info));
2254     } else if ($log->action == "add discussion") {
2256         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2257                                            u.firstname, u.lastname, u.email, u.picture
2258                                  FROM {forum_discussions} d,
2259                                       {forum_posts} p,
2260                                       {forum} f,
2261                                       {user} u
2262                                 WHERE d.id = ?
2263                                   AND d.firstpost = p.id
2264                                   AND p.userid = u.id
2265                                   AND u.deleted <> '1'
2266                                   AND f.id = d.forum", array($log->info));
2267     }
2268     return NULL;
2271 /**
2272  * Given a discussion id, return the first post from the discussion
2273  *
2274  * @global object
2275  * @global object
2276  * @param int $dicsussionid
2277  * @return array
2278  */
2279 function forum_get_firstpost_from_discussion($discussionid) {
2280     global $CFG, $DB;
2282     return $DB->get_record_sql("SELECT p.*
2283                              FROM {forum_discussions} d,
2284                                   {forum_posts} p
2285                             WHERE d.id = ?
2286                               AND d.firstpost = p.id ", array($discussionid));
2289 /**
2290  * Returns an array of counts of replies to each discussion
2291  *
2292  * @global object
2293  * @global object
2294  * @param int $forumid
2295  * @param string $forumsort
2296  * @param int $limit
2297  * @param int $page
2298  * @param int $perpage
2299  * @return array
2300  */
2301 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2302     global $CFG, $DB;
2304     if ($limit > 0) {
2305         $limitfrom = 0;
2306         $limitnum  = $limit;
2307     } else if ($page != -1) {
2308         $limitfrom = $page*$perpage;
2309         $limitnum  = $perpage;
2310     } else {
2311         $limitfrom = 0;
2312         $limitnum  = 0;
2313     }
2315     if ($forumsort == "") {
2316         $orderby = "";
2317         $groupby = "";
2319     } else {
2320         $orderby = "ORDER BY $forumsort";
2321         $groupby = ", ".strtolower($forumsort);
2322         $groupby = str_replace('desc', '', $groupby);
2323         $groupby = str_replace('asc', '', $groupby);
2324     }
2326     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2327         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2328                   FROM {forum_posts} p
2329                        JOIN {forum_discussions} d ON p.discussion = d.id
2330                  WHERE p.parent > 0 AND d.forum = ?
2331               GROUP BY p.discussion";
2332         return $DB->get_records_sql($sql, array($forumid));
2334     } else {
2335         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2336                   FROM {forum_posts} p
2337                        JOIN {forum_discussions} d ON p.discussion = d.id
2338                  WHERE d.forum = ?
2339               GROUP BY p.discussion $groupby
2340               $orderby";
2341         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2342     }
2345 /**
2346  * @global object
2347  * @global object
2348  * @global object
2349  * @staticvar array $cache
2350  * @param object $forum
2351  * @param object $cm
2352  * @param object $course
2353  * @return mixed
2354  */
2355 function forum_count_discussions($forum, $cm, $course) {
2356     global $CFG, $DB, $USER;
2358     static $cache = array();
2360     $now = round(time(), -2); // db cache friendliness
2362     $params = array($course->id);
2364     if (!isset($cache[$course->id])) {
2365         if (!empty($CFG->forum_enabletimedposts)) {
2366             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2367             $params[] = $now;
2368             $params[] = $now;
2369         } else {
2370             $timedsql = "";
2371         }
2373         $sql = "SELECT f.id, COUNT(d.id) as dcount
2374                   FROM {forum} f
2375                        JOIN {forum_discussions} d ON d.forum = f.id
2376                  WHERE f.course = ?
2377                        $timedsql
2378               GROUP BY f.id";
2380         if ($counts = $DB->get_records_sql($sql, $params)) {
2381             foreach ($counts as $count) {
2382                 $counts[$count->id] = $count->dcount;
2383             }
2384             $cache[$course->id] = $counts;
2385         } else {
2386             $cache[$course->id] = array();
2387         }
2388     }
2390     if (empty($cache[$course->id][$forum->id])) {
2391         return 0;
2392     }
2394     $groupmode = groups_get_activity_groupmode($cm, $course);
2396     if ($groupmode != SEPARATEGROUPS) {
2397         return $cache[$course->id][$forum->id];
2398     }
2400     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2401         return $cache[$course->id][$forum->id];
2402     }
2404     require_once($CFG->dirroot.'/course/lib.php');
2406     $modinfo =& get_fast_modinfo($course);
2407     if (is_null($modinfo->groups)) {
2408         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2409     }
2411     if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2412         $mygroups = $modinfo->groups[$cm->groupingid];
2413     } else {
2414         $mygroups = false; // Will be set below
2415     }
2417     // add all groups posts
2418     if (empty($mygroups)) {
2419         $mygroups = array(-1=>-1);
2420     } else {
2421         $mygroups[-1] = -1;
2422     }
2424     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2425     $params[] = $forum->id;
2427     if (!empty($CFG->forum_enabletimedposts)) {
2428         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2429         $params[] = $now;
2430         $params[] = $now;
2431     } else {
2432         $timedsql = "";
2433     }
2435     $sql = "SELECT COUNT(d.id)
2436               FROM {forum_discussions} d
2437              WHERE d.groupid $mygroups_sql AND d.forum = ?
2438                    $timedsql";
2440     return $DB->get_field_sql($sql, $params);
2443 /**
2444  * How many posts by other users are unrated by a given user in the given discussion?
2445  *
2446  * @global object
2447  * @global object
2448  * @param int $discussionid
2449  * @param int $userid
2450  * @return mixed
2451  */
2452 function forum_count_unrated_posts($discussionid, $userid) {
2453     global $CFG, $DB;
2454     if ($posts = $DB->get_record_sql("SELECT count(*) as num
2455                                    FROM {forum_posts}
2456                                   WHERE parent > 0
2457                                     AND discussion = ?
2458                                     AND userid <> ? ", array($discussionid, $userid))) {
2460         if ($rated = $DB->get_record_sql("SELECT count(*) as num
2461                                        FROM {forum_posts} p,
2462                                             {rating} r
2463                                       WHERE p.discussion = ?
2464                                         AND p.id = r.itemid
2465                                         AND r.userid = ?", array($discussionid, $userid))) {
2466             $difference = $posts->num - $rated->num;
2467             if ($difference > 0) {
2468                 return $difference;
2469             } else {
2470                 return 0;    // Just in case there was a counting error
2471             }
2472         } else {
2473             return $posts->num;
2474         }
2475     } else {
2476         return 0;
2477     }
2480 /**
2481  * Get all discussions in a forum
2482  *
2483  * @global object
2484  * @global object
2485  * @global object
2486  * @uses CONTEXT_MODULE
2487  * @uses VISIBLEGROUPS
2488  * @param object $cm
2489  * @param string $forumsort
2490  * @param bool $fullpost
2491  * @param int $unused
2492  * @param int $limit
2493  * @param bool $userlastmodified
2494  * @param int $page
2495  * @param int $perpage
2496  * @return array
2497  */
2498 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2499     global $CFG, $DB, $USER;
2501     $timelimit = '';
2503     $now = round(time(), -2);
2504     $params = array($cm->instance);
2506     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2508     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2509         return array();
2510     }
2512     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2514         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2515             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2516             $params[] = $now;
2517             $params[] = $now;
2518             if (isloggedin()) {
2519                 $timelimit .= " OR d.userid = ?";
2520                 $params[] = $USER->id;
2521             }
2522             $timelimit .= ")";
2523         }
2524     }
2526     if ($limit > 0) {
2527         $limitfrom = 0;
2528         $limitnum  = $limit;
2529     } else if ($page != -1) {
2530         $limitfrom = $page*$perpage;
2531         $limitnum  = $perpage;
2532     } else {
2533         $limitfrom = 0;
2534         $limitnum  = 0;
2535     }
2537     $groupmode    = groups_get_activity_groupmode($cm);
2538     $currentgroup = groups_get_activity_group($cm);
2540     if ($groupmode) {
2541         if (empty($modcontext)) {
2542             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2543         }
2545         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2546             if ($currentgroup) {
2547                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2548                 $params[] = $currentgroup;
2549             } else {
2550                 $groupselect = "";
2551             }
2553         } else {
2554             //seprate groups without access all
2555             if ($currentgroup) {
2556                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2557                 $params[] = $currentgroup;
2558             } else {
2559                 $groupselect = "AND d.groupid = -1";
2560             }
2561         }
2562     } else {
2563         $groupselect = "";
2564     }
2567     if (empty($forumsort)) {
2568         $forumsort = "d.timemodified DESC";
2569     }
2570     if (empty($fullpost)) {
2571         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2572     } else {
2573         $postdata = "p.*";
2574     }
2576     if (empty($userlastmodified)) {  // We don't need to know this
2577         $umfields = "";
2578         $umtable  = "";
2579     } else {
2580         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2581         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2582     }
2584     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2585                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2586               FROM {forum_discussions} d
2587                    JOIN {forum_posts} p ON p.discussion = d.id
2588                    JOIN {user} u ON p.userid = u.id
2589                    $umtable
2590              WHERE d.forum = ? AND p.parent = 0
2591                    $timelimit $groupselect
2592           ORDER BY $forumsort";
2593     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2596 /**
2597  *
2598  * @global object
2599  * @global object
2600  * @global object
2601  * @uses CONTEXT_MODULE
2602  * @uses VISIBLEGROUPS
2603  * @param object $cm
2604  * @return array
2605  */
2606 function forum_get_discussions_unread($cm) {
2607     global $CFG, $DB, $USER;
2609     $now = round(time(), -2);
2610     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2612     $params = array();
2613     $groupmode    = groups_get_activity_groupmode($cm);
2614     $currentgroup = groups_get_activity_group($cm);
2616     if ($groupmode) {
2617         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2619         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2620             if ($currentgroup) {
2621                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2622                 $params['currentgroup'] = $currentgroup;
2623             } else {
2624                 $groupselect = "";
2625             }
2627         } else {
2628             //separate groups without access all
2629             if ($currentgroup) {
2630                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2631                 $params['currentgroup'] = $currentgroup;
2632             } else {
2633                 $groupselect = "AND d.groupid = -1";
2634             }
2635         }
2636     } else {
2637         $groupselect = "";
2638     }
2640     if (!empty($CFG->forum_enabletimedposts)) {
2641         $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2642         $params['now1'] = $now;
2643         $params['now2'] = $now;
2644     } else {
2645         $timedsql = "";
2646     }
2648     $sql = "SELECT d.id, COUNT(p.id) AS unread
2649               FROM {forum_discussions} d
2650                    JOIN {forum_posts} p     ON p.discussion = d.id
2651                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2652              WHERE d.forum = {$cm->instance}
2653                    AND p.modified >= :cutoffdate AND r.id is NULL
2654                    $groupselect
2655                    $timedsql
2656           GROUP BY d.id";
2657     $params['cutoffdate'] = $cutoffdate;
2659     if ($unreads = $DB->get_records_sql($sql, $params)) {
2660         foreach ($unreads as $unread) {
2661             $unreads[$unread->id] = $unread->unread;
2662         }
2663         return $unreads;
2664     } else {
2665         return array();
2666     }
2669 /**
2670  * @global object
2671  * @global object
2672  * @global object
2673  * @uses CONEXT_MODULE
2674  * @uses VISIBLEGROUPS
2675  * @param object $cm
2676  * @return array
2677  */
2678 function forum_get_discussions_count($cm) {
2679     global $CFG, $DB, $USER;
2681     $now = round(time(), -2);
2682     $params = array($cm->instance);
2683     $groupmode    = groups_get_activity_groupmode($cm);
2684     $currentgroup = groups_get_activity_group($cm);
2686     if ($groupmode) {
2687         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2689         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2690             if ($currentgroup) {
2691                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2692                 $params[] = $currentgroup;
2693             } else {
2694                 $groupselect = "";
2695             }
2697         } else {
2698             //seprate groups without access all
2699             if ($currentgroup) {
2700                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2701                 $params[] = $currentgroup;
2702             } else {
2703                 $groupselect = "AND d.groupid = -1";
2704             }
2705         }
2706     } else {
2707         $groupselect = "";
2708     }
2710     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2712     $timelimit = "";
2714     if (!empty($CFG->forum_enabletimedposts)) {
2716         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2718         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2719             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2720             $params[] = $now;
2721             $params[] = $now;
2722             if (isloggedin()) {
2723                 $timelimit .= " OR d.userid = ?";
2724                 $params[] = $USER->id;
2725             }
2726             $timelimit .= ")";
2727         }
2728     }
2730     $sql = "SELECT COUNT(d.id)
2731               FROM {forum_discussions} d
2732                    JOIN {forum_posts} p ON p.discussion = d.id
2733              WHERE d.forum = ? AND p.parent = 0
2734                    $groupselect $timelimit";
2736     return $DB->get_field_sql($sql, $params);
2740 /**
2741  * Get all discussions started by a particular user in a course (or group)
2742  * This function no longer used ...
2743  *
2744  * @todo Remove this function if no longer used
2745  * @global object
2746  * @global object
2747  * @param int $courseid
2748  * @param int $userid
2749  * @param int $groupid
2750  * @return array
2751  */
2752 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2753     global $CFG, $DB;
2754     $params = array($courseid, $userid);
2755     if ($groupid) {
2756         $groupselect = " AND d.groupid = ? ";
2757         $params[] = $groupid;
2758     } else  {
2759         $groupselect = "";
2760     }
2762     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2763                                    f.type as forumtype, f.name as forumname, f.id as forumid
2764                               FROM {forum_discussions} d,
2765                                    {forum_posts} p,
2766                                    {user} u,
2767                                    {forum} f
2768                              WHERE d.course = ?
2769                                AND p.discussion = d.id
2770                                AND p.parent = 0
2771                                AND p.userid = u.id
2772                                AND u.id = ?
2773                                AND d.forum = f.id $groupselect
2774                           ORDER BY p.created DESC", $params);
2777 /**
2778  * Get the list of potential subscribers to a forum.
2779  *
2780  * @param object $forumcontext the forum context.
2781  * @param integer $groupid the id of a group, or 0 for all groups.
2782  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2783  * @param string $sort sort order. As for get_users_by_capability.
2784  * @return array list of users.
2785  */
2786 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2787     return get_users_by_capability($forumcontext, 'mod/forum:initialsubscriptions', $fields, $sort, '', '', $groupid, '', false, true);
2790 /**
2791  * Returns list of user objects that are subscribed to this forum
2792  *
2793  * @global object
2794  * @global object
2795  * @param object $course the course
2796  * @param forum $forum the forum
2797  * @param integer $groupid group id, or 0 for all.
2798  * @param object $context the forum context, to save re-fetching it where possible.
2799  * @return array list of users.
2800  */
2801 function forum_subscribed_users($course, $forum, $groupid=0, $context = NULL) {
2802     global $CFG, $DB;
2803     $params = array($forum->id);
2805     if ($groupid) {
2806         $grouptables = ", {groups_members} gm ";
2807         $groupselect = "AND gm.groupid = ? AND u.id = gm.userid";
2808         $params[] = $groupid;
2809     } else  {
2810         $grouptables = '';
2811         $groupselect = '';
2812     }
2814     $fields ="u.id,
2815               u.username,
2816               u.firstname,
2817               u.lastname,
2818               u.maildisplay,
2819               u.mailformat,
2820               u.maildigest,
2821               u.imagealt,
2822               u.email,
2823               u.city,
2824               u.country,
2825               u.lastaccess,
2826               u.lastlogin,
2827               u.picture,
2828               u.timezone,
2829               u.theme,
2830               u.lang,
2831               u.trackforums,
2832               u.mnethostid";
2834     if (forum_is_forcesubscribed($forum)) {
2835         if (empty($context)) {
2836             $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2837             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2838         }
2839         $sort = "u.email ASC";
2840         $results = forum_get_potential_subscribers($context, $groupid, $fields, $sort);
2841     } else {
2842         $results = $DB->get_records_sql("SELECT $fields
2843                               FROM {user} u,
2844                                    {forum_subscriptions} s $grouptables
2845                              WHERE s.forum = ?
2846                                AND s.userid = u.id
2847                                AND u.deleted = 0  $groupselect
2848                           ORDER BY u.email ASC", $params);
2849     }
2851     static $guestid = null;
2853     if (is_null($guestid)) {
2854         if ($guest = guest_user()) {
2855             $guestid = $guest->id;
2856         } else {
2857             $guestid = 0;
2858         }
2859     }
2861     // Guest user should never be subscribed to a forum.
2862     unset($results[$guestid]);
2864     return $results;
2869 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2872 /**
2873  * @global object
2874  * @global object
2875  * @param int $courseid
2876  * @param string $type
2877  */
2878 function forum_get_course_forum($courseid, $type) {
2879 // How to set up special 1-per-course forums
2880     global $CFG, $DB, $OUTPUT;
2882     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2883         // There should always only be ONE, but with the right combination of
2884         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2885         foreach ($forums as $forum) {
2886             return $forum;   // ie the first one
2887         }
2888     }
2890     // Doesn't exist, so create one now.
2891     $forum->course = $courseid;
2892     $forum->type = "$type";
2893     switch ($forum->type) {
2894         case "news":
2895             $forum->name  = get_string("namenews", "forum");
2896             $forum->intro = get_string("intronews", "forum");
2897             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2898             $forum->assessed = 0;
2899             if ($courseid == SITEID) {
2900                 $forum->name  = get_string("sitenews");
2901                 $forum->forcesubscribe = 0;
2902             }
2903             break;
2904         case "social":
2905             $forum->name  = get_string("namesocial", "forum");
2906             $forum->intro = get_string("introsocial", "forum");
2907             $forum->assessed = 0;
2908             $forum->forcesubscribe = 0;
2909             break;
2910         case "blog":
2911             $forum->name = get_string('blogforum', 'forum');
2912             $forum->intro = get_string('introblog', 'forum');
2913             $forum->assessed = 0;
2914             $forum->forcesubscribe = 0;
2915             break;
2916         default:
2917             echo $OUTPUT->notification("That forum type doesn't exist!");
2918             return false;
2919             break;
2920     }
2922     $forum->timemodified = time();
2923     $forum->id = $DB->insert_record("forum", $forum);
2925     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2926         echo $OUTPUT->notification("Could not find forum module!!");
2927         return false;
2928     }
2929     $mod = new stdClass();
2930     $mod->course = $courseid;
2931     $mod->module = $module->id;
2932     $mod->instance = $forum->id;
2933     $mod->section = 0;
2934     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
2935         echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
2936         return false;
2937     }
2938     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
2939         echo $OUTPUT->notification("Could not add the new course module to that section");
2940         return false;
2941     }
2942     $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule));
2944     include_once("$CFG->dirroot/course/lib.php");
2945     rebuild_course_cache($courseid);
2947     return $DB->get_record("forum", array("id" => "$forum->id"));
2951 /**
2952  * Given the data about a posting, builds up the HTML to display it and
2953  * returns the HTML in a string.  This is designed for sending via HTML email.
2954  *
2955  * @global object
2956  * @param object $course
2957  * @param object $cm
2958  * @param object $forum
2959  * @param object $discussion
2960  * @param object $post
2961  * @param object $userform
2962  * @param object $userto
2963  * @param bool $ownpost
2964  * @param bool $reply
2965  * @param bool $link
2966  * @param bool $rate
2967  * @param string $footer
2968  * @return string
2969  */
2970 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
2971                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
2973     global $CFG, $OUTPUT;
2975     if (!isset($userto->viewfullnames[$forum->id])) {
2976         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
2977             print_error('invalidcoursemodule');
2978         }
2979         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2980         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
2981     } else {
2982         $viewfullnames = $userto->viewfullnames[$forum->id];
2983     }
2985     // format the post body
2986     $options = new stdClass();
2987     $options->para = true;
2988     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
2990     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
2992     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
2993     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
2994     $output .= '</td>';
2996     if ($post->parent) {
2997         $output .= '<td class="topic">';
2998     } else {
2999         $output .= '<td class="topic starter">';
3000     }
3001     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3003     $fullname = fullname($userfrom, $viewfullnames);
3004     $by = new stdClass();
3005     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3006     $by->date = userdate($post->modified, '', $userto->timezone);
3007     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3009     $output .= '</td></tr>';
3011     $output .= '<tr><td class="left side" valign="top">';
3013     if (isset($userfrom->groups)) {
3014         $groups = $userfrom->groups[$forum->id];
3015     } else {
3016         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
3017             print_error('invalidcoursemodule');
3018         }
3019         $group = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3020     }
3022     if ($groups) {
3023         $output .= print_group_picture($groups, $course->id, false, true, true);
3024     } else {
3025         $output .= '&nbsp;';
3026     }
3028     $output .= '</td><td class="content">';
3030     $attachments = forum_print_attachments($post, $cm, 'html');
3031     if ($attachments !== '') {
3032         $output .= '<div class="attachments">';
3033         $output .= $attachments;
3034         $output .= '</div>';
3035     }
3037     $output .= $formattedtext;
3039 // Commands
3040     $commands = array();
3042     if ($post->parent) {
3043         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3044                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3045     }
3047     if ($reply) {
3048         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3049                       get_string('reply', 'forum').'</a>';
3050     }
3052     $output .= '<div class="commands">';
3053     $output .= implode(' | ', $commands);
3054     $output .= '</div>';
3056 // Context link to post if required
3057     if ($link) {
3058         $output .= '<div class="link">';
3059         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3060                      get_string('postincontext', 'forum').'</a>';
3061         $output .= '</div>';
3062     }
3064     if ($footer) {
3065         $output .= '<div class="footer">'.$footer.'</div>';
3066     }
3067     $output .= '</td></tr></table>'."\n\n";
3069     return $output;
3072 /**
3073  * Print a forum post
3074  *
3075  * @global object
3076  * @global object
3077  * @uses FORUM_MODE_THREADED
3078  * @uses PORTFOLIO_FORMAT_PLAINHTML
3079  * @uses PORTFOLIO_FORMAT_FILE
3080  * @uses PORTFOLIO_FORMAT_RICHHTML
3081  * @uses PORTFOLIO_ADD_TEXT_LINK
3082  * @uses CONTEXT_MODULE
3083  * @param object $post The post to print.
3084  * @param object $discussion
3085  * @param object $forum
3086  * @param object $cm
3087  * @param object $course
3088  * @param boolean $ownpost Whether this post belongs to the current user.
3089  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3090  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3091  * @param string $footer Extra stuff to print after the message.
3092  * @param string $highlight Space-separated list of terms to highlight.
3093  * @param int $post_read true, false or -99. If we already know whether this user
3094  *          has read this post, pass that in, otherwise, pass in -99, and this
3095  *          function will work it out.
3096  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3097  *          the current user can't see this post, if this argument is true
3098  *          (the default) then print a dummy 'you can't see this post' post.
3099  *          If false, don't output anything at all.
3100  * @param bool|null $istracked
3101  * @return void
3102  */
3103 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3104                           $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3105     global $USER, $CFG, $OUTPUT;
3107     // String cache
3108     static $str;
3110     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3112     $post->course = $course->id;
3113     $post->forum  = $forum->id;
3114     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3116     // caching
3117     if (!isset($cm->cache)) {
3118         $cm->cache = new stdClass;
3119     }
3121     if (!isset($cm->cache->caps)) {
3122         $cm->cache->caps = array();
3123         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3124         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3125         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3126         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3127         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3128         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3129         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3130         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3131         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3132     }
3134     if (!isset($cm->uservisible)) {
3135         $cm->uservisible = coursemodule_visible_for_user($cm);
3136     }
3138     if ($istracked && is_null($postisread)) {
3139         $postisread = forum_tp_is_post_read($USER->id, $post);
3140     }
3142     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3143         $output = '';
3144         if (!$dummyifcantsee) {
3145             if ($return) {
3146                 return $output;
3147             }
3148             echo $output;
3149             return;
3150         }
3151         $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3152         $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix '.$forumpostclass));
3153         $output .= html_writer::start_tag('div', array('class'=>'row header'));
3154         $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3155         if ($post->parent) {
3156             $output .= html_writer::start_tag('div', array('class'=>'topic'));
3157         } else {
3158             $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3159         }
3160         $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class'=>'subject')); // Subject
3161         $output .= html_writer::tag('div', get_string('forumauthorhidden','forum'), array('class'=>'author')); // author
3162         $output .= html_writer::end_tag('div'); // row
3163         $output .= html_writer::start_tag('div', array('class'=>'row'));
3164         $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3165         $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3166         $output .= html_writer::end_tag('div'); // row
3167         $output .= html_writer::end_tag('div'); // forumpost
3169         if ($return) {
3170             return $output;
3171         }
3172         echo $output;
3173         return;
3174     }
3176     if (empty($str)) {
3177         $str = new stdClass;
3178         $str->edit         = get_string('edit', 'forum');
3179         $str->delete       = get_string('delete', 'forum');
3180         $str->reply        = get_string('reply', 'forum');
3181         $str->parent       = get_string('parent', 'forum');
3182         $str->pruneheading = get_string('pruneheading', 'forum');
3183         $str->prune        = get_string('prune', 'forum');
3184         $str->displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3185         $str->markread     = get_string('markread', 'forum');
3186         $str->markunread   = get_string('markunread', 'forum');
3187     }
3189     $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3191     // Build an object that represents the posting user
3192     $postuser = new stdClass;
3193     $postuser->id        = $post->userid;
3194     $postuser->firstname = $post->firstname;
3195     $postuser->lastname  = $post->lastname;
3196     $postuser->imagealt  = $post->imagealt;
3197     $postuser->picture   = $post->picture;
3198     $postuser->email     = $post->email;
3199     // Some handy things for later on
3200     $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3201     $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3203     // Prepare the groups the posting user belongs to
3204     if (isset($cm->cache->usersgroups)) {
3205         $groups = array();
3206         if (isset($cm->cache->usersgroups[$post->userid])) {
3207             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3208                 $groups[$gid] = $cm->cache->groups[$gid];
3209             }
3210         }
3211     } else {
3212         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3213     }
3215     // Prepare the attachements for the post, files then images
3216     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3218     // Determine if we need to shorten this post
3219     $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3222     // Prepare an array of commands
3223     $commands = array();
3225     // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3226     // Don't display the mark read / unread controls in this case.
3227     if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3228         $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3229         $text = $str->markunread;
3230         if (!$postisread) {
3231             $url->param('mark', 'read');
3232             $text = $str->markread;
3233         }
3234         if ($str->displaymode == FORUM_MODE_THREADED) {
3235             $url->param('parent', $post->parent);
3236         } else {
3237             $url->set_anchor('p'.$post->id);
3238         }
3239         $commands[] = array('url'=>$url, 'text'=>$text);
3240     }
3242     // Zoom in to the parent specifically
3243     if ($post->parent) {
3244         $url = new moodle_url($discussionlink);
3245         if ($str->displaymode == FORUM_MODE_THREADED) {
3246             $url->param('parent', $post->parent);
3247         } else {
3248             $url->set_anchor('p'.$post->id);
3249         }
3250         $commands[] = array('url'=>$url, 'text'=>$str->parent);
3251     }
3253     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3254     $age = time() - $post->created;
3255     if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3256         $age = 0;
3257     }
3258     if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3259         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3260     }
3262     if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3263         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3264     }
3266     if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3267         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3268     }
3270     if ($reply) {
3271         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('reply'=>$post->id)), 'text'=>$str->reply);
3272     }
3274     if ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost'])) {
3275         $p = array('postid' => $post->id);
3276         require_once($CFG->libdir.'/portfoliolib.php');
3277         $button = new portfolio_add_button();
3278         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php');
3279         if (empty($attachments)) {
3280             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3281         } else {
3282             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3283         }
3285         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3286         if (!empty($porfoliohtml)) {
3287             $commands[] = $porfoliohtml;
3288         }
3289     }
3290     // Finished building commands
3293     // Begin output
3295     $output  = '';
3297     if ($istracked) {
3298         if ($postisread) {
3299             $forumpostclass = ' read';
3300         } else {
3301             $forumpostclass = ' unread';
3302             $output .= html_writer::tag('a', '', array('name'=>'unread'));
3303         }
3304     } else {
3305         // ignore trackign status if not tracked or tracked param missing
3306         $forumpostclass = '';
3307     }
3309     $topicclass = '';
3310     if (empty($post->parent)) {
3311         $topicclass = ' firstpost starter';
3312     }
3314     $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3315     $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass));
3316     $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3317     $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3318     $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3319     $output .= html_writer::end_tag('div');
3322     $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3324     $postsubject = $post->subject;
3325     if (empty($post->subjectnoformat)) {
3326         $postsubject = format_string($postsubject);
3327     }
3328     $output .= html_writer::tag('div', $postsubject, array('class'=>'subject'));
3330     $by = new stdClass();
3331     $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3332     $by->date = userdate($post->modified);
3333     $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author'));
3335     $output .= html_writer::end_tag('div'); //topic
3336     $output .= html_writer::end_tag('div'); //row
3338     $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3339     $output .= html_writer::start_tag('div', array('class'=>'left'));
3341     $groupoutput = '';
3342     if ($groups) {
3343         $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3344     }
3345     if (empty($groupoutput)) {
3346         $groupoutput = '&nbsp;';
3347     }
3348     $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3350     $output .= html_writer::end_tag('div'); //left side
3351     $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3352     $output .= html_writer::start_tag('div', array('class'=>'content'));
3353     if (!empty($attachments)) {
3354         $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3355     }
3357     $options = new stdClass;
3358     $options->para    = false;
3359     $options->trusted = $post->messagetrust;
3360     $options->context = $modcontext;
3361     if ($shortenpost) {
3362         // Prepare shortened version
3363         $postclass    = 'shortenedpost';
3364         $postcontent  = format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3365         $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
3366         $postcontent .= html_writer::tag('span', '('.get_string('numwords', 'moodle', count_words(strip_tags($post->message))).')...', array('class'=>'post-word-count'));
3367     } else {
3368         // Prepare whole post
3369         $postclass    = 'fullpost';
3370         $postcontent  = format_text($post->message, $post->messageformat, $options, $course->id);
3371         if (!empty($highlight)) {
3372             $postcontent = highlight($highlight, $postcontent);
3373         }
3374         $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
3375     }
3376     // Output the post content
3377     $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
3378     $output .= html_writer::end_tag('div'); // Content
3379     $output .= html_writer::end_tag('div'); // Content mask
3380     $output .= html_writer::end_tag('div'); // Row
3382     $output .= html_writer::start_tag('div', array('class'=>'row side'));
3383     $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
3384     $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
3386     // Output ratings
3387     if (!empty($post->rating)) {
3388         $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
3389     }
3391     // Output the commands
3392     $commandhtml = array();
3393     foreach ($commands as $command) {
3394         if (is_array($command)) {
3395             $commandhtml[] = html_writer::link($command['url'], $command['text']);
3396         } else {
3397             $commandhtml[] = $command;
3398         }
3399     }
3400     $output .= html_writer::tag('div', implode(' | ', $commandhtml), array('class'=>'commands'));
3402     // Output link to post if required
3403     if ($link) {
3404         if ($post->replies == 1) {
3405             $replystring = get_string('repliesone', 'forum', $post->replies);
3406         } else {
3407             $replystring = get_string('repliesmany', 'forum', $post->replies);
3408         }
3410         $output .= html_writer::start_tag('div', array('class'=>'link'));
3411         $output .= html_writer::link($discussionlink, get_string('discussthistopic', 'forum'));
3412         $output .= '&nbsp;('.$replystring.')';
3413         $output .= html_writer::end_tag('div'); // link
3414     }
3416     // Output footer if required
3417     if ($footer) {
3418         $output .= html_writer::tag('div', $footer, array('class'=>'footer'));
3419     }
3421     // Close remaining open divs
3422     $output .= html_writer::end_tag('div'); // content
3423     $output .= html_writer::end_tag('div'); // row
3424     $output .= html_writer::end_tag('div'); // forumpost
3426     // Mark the forum post as read if required
3427     if ($istracked && !$CFG->forum_usermarksread && !$postisread) {
3428         forum_tp_mark_post_read($USER->id, $post, $forum->id);
3429     }
3431     if ($return) {
3432         return $output;
3433     }
3434     echo $output;
3435     return;
3438 /**
3439  * Return rating related permissions
3440  * @param string $options the context id
3441  * @return array an associative array of the user's rating permissions
3442  */
3443 function forum_rating_permissions($contextid) {
3444     $context = get_context_instance_by_id($contextid);
3446     if (!$context) {
3447         print_error('invalidcontext');
3448         return null;
3449     } else {
3450         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));
3451     }
3454 /**
3455  * Returns the names of the table and columns necessary to check items for ratings
3456  * @return array an array containing the item table, item id and user id columns
3457  */
3458 function forum_rating_item_check_info() {
3459     return array('forum_posts','id','userid');
3463 /**
3464  * This function prints the overview of a discussion in the forum listing.
3465  * It needs some discussion information and some post information, these
3466  * happen to be combined for efficiency in the $post parameter by the function
3467  * that calls this one: forum_print_latest_discussions()
3468  *
3469  * @global object
3470  * @global object
3471  * @param object $post The post object (passed by reference for speed).
3472  * @param object $forum The forum object.
3473  * @param int $group Current group.
3474  * @param string $datestring Format to use for the dates.
3475  * @param boolean $cantrack Is tracking enabled for this forum.
3476  * @param boolean $forumtracked Is the user tracking this forum.
3477  * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
3478  */
3479 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
3480                                         $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) {
3482     global $USER, $CFG, $OUTPUT;
3484     static $rowcount;
3485     static $strmarkalldread;
3487     if (empty($modcontext)) {
3488         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3489             print_error('invalidcoursemodule');
3490         }
3491         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3492     }
3494     if (!isset($rowcount)) {
3495         $rowcount = 0;
3496         $strmarkalldread = get_string('markalldread', 'forum');
3497     } else {
3498         $rowcount = ($rowcount + 1) % 2;
3499     }
3501     $post->subject = format_string($post->subject,true);
3503     echo "\n\n";
3504     echo '<tr class="discussion r'.$rowcount.'">';