MDL-24955 improved add discussion logic for users that are not enrolled yet
[moodle.git] / mod / forum / lib.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * @package mod-forum
20  * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
21  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22  */
24 /** Include required files */
25 require_once($CFG->libdir.'/filelib.php');
26 require_once($CFG->libdir.'/eventslib.php');
27 require_once($CFG->libdir . '/completionlib.php');
28 require_once($CFG->dirroot.'/user/selector/lib.php');
30 /// CONSTANTS ///////////////////////////////////////////////////////////
32 define('FORUM_MODE_FLATOLDEST', 1);
33 define('FORUM_MODE_FLATNEWEST', -1);
34 define('FORUM_MODE_THREADED', 2);
35 define('FORUM_MODE_NESTED', 3);
37 define('FORUM_CHOOSESUBSCRIBE', 0);
38 define('FORUM_FORCESUBSCRIBE', 1);
39 define('FORUM_INITIALSUBSCRIBE', 2);
40 define('FORUM_DISALLOWSUBSCRIBE',3);
42 define('FORUM_TRACKING_OFF', 0);
43 define('FORUM_TRACKING_OPTIONAL', 1);
44 define('FORUM_TRACKING_ON', 2);
46 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
48 /**
49  * Given an object containing all the necessary data,
50  * (defined by the form in mod_form.php) this function
51  * will create a new instance and return the id number
52  * of the new instance.
53  *
54  * @global object
55  * @global object
56  * @param object $forum add forum instance (with magic quotes)
57  * @return int intance id
58  */
59 function forum_add_instance($forum, $mform) {
60     global $CFG, $DB;
62     $forum->timemodified = time();
64     if (empty($forum->assessed)) {
65         $forum->assessed = 0;
66     }
68     if (empty($forum->ratingtime) or empty($forum->assessed)) {
69         $forum->assesstimestart  = 0;
70         $forum->assesstimefinish = 0;
71     }
73     $forum->id = $DB->insert_record('forum', $forum);
74     $modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule);
76     if ($forum->type == 'single') {  // Create related discussion.
77         $discussion = new stdClass();
78         $discussion->course        = $forum->course;
79         $discussion->forum         = $forum->id;
80         $discussion->name          = $forum->name;
81         $discussion->assessed      = $forum->assessed;
82         $discussion->message       = $forum->intro;
83         $discussion->messageformat = $forum->introformat;
84         $discussion->messagetrust  = trusttext_trusted(get_context_instance(CONTEXT_COURSE, $forum->course));
85         $discussion->mailnow       = false;
86         $discussion->groupid       = -1;
88         $message = '';
90         $discussion->id = forum_add_discussion($discussion, null, $message);
92         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
93             // ugly hack - we need to copy the files somehow
94             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
95             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
97             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
98             $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
99         }
100     }
102     if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
103     /// all users should be subscribed initially
104     /// Note: forum_get_potential_subscribers should take the forum context,
105     /// but that does not exist yet, becuase the forum is only half build at this
106     /// stage. However, because the forum is brand new, we know that there are
107     /// no role assignments or overrides in the forum context, so using the
108     /// course context gives the same list of users.
109         $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
110         foreach ($users as $user) {
111             forum_subscribe($user->id, $forum->id);
112         }
113     }
115     forum_grade_item_update($forum);
117     return $forum->id;
121 /**
122  * Given an object containing all the necessary data,
123  * (defined by the form in mod_form.php) this function
124  * will update an existing instance with new data.
125  *
126  * @global object
127  * @param object $forum forum instance (with magic quotes)
128  * @return bool success
129  */
130 function forum_update_instance($forum, $mform) {
131     global $DB, $OUTPUT, $USER;
133     $forum->timemodified = time();
134     $forum->id           = $forum->instance;
136     if (empty($forum->assessed)) {
137         $forum->assessed = 0;
138     }
140     if (empty($forum->ratingtime) or empty($forum->assessed)) {
141         $forum->assesstimestart  = 0;
142         $forum->assesstimefinish = 0;
143     }
145     $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
147     // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
148     // if  scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
149     // for count and sum aggregation types the grade we check to make sure they do not exceed the scale (i.e. max score) when calculating the grade
150     if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
151         forum_update_grades($forum); // recalculate grades for the forum
152     }
154     if ($forum->type == 'single') {  // Update related discussion and post.
155         if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
156             if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC')) {
157                 echo $OUTPUT->notification('Warning! There is more than one discussion in this forum - using the most recent');
158                 $discussion = array_pop($discussions);
159             } else {
160                 // try to recover by creating initial discussion - MDL-16262
161                 $discussion = new stdClass();
162                 $discussion->course          = $forum->course;
163                 $discussion->forum           = $forum->id;
164                 $discussion->name            = $forum->name;
165                 $discussion->assessed        = $forum->assessed;
166                 $discussion->message         = $forum->intro;
167                 $discussion->messageformat   = $forum->introformat;
168                 $discussion->messagetrust    = true;
169                 $discussion->mailnow         = false;
170                 $discussion->groupid         = -1;
172                 $message = '';
174                 forum_add_discussion($discussion, null, $message);
176                 if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
177                     print_error('cannotadd', 'forum');
178                 }
179             }
180         }
181         if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
182             print_error('cannotfindfirstpost', 'forum');
183         }
185         $cm         = get_coursemodule_from_instance('forum', $forum->id);
186         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
188         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
189             // ugly hack - we need to copy the files somehow
190             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
191             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
193             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
194         }
196         $post->subject       = $forum->name;
197         $post->message       = $forum->intro;
198         $post->messageformat = $forum->introformat;
199         $post->messagetrust  = trusttext_trusted($modcontext);
200         $post->modified      = $forum->timemodified;
201         $post->userid        = $USER->id;    // MDL-18599, so that current teacher can take ownership of activities
203         $DB->update_record('forum_posts', $post);
204         $discussion->name = $forum->name;
205         $DB->update_record('forum_discussions', $discussion);
206     }
208     $DB->update_record('forum', $forum);
210     forum_grade_item_update($forum);
212     return true;
216 /**
217  * Given an ID of an instance of this module,
218  * this function will permanently delete the instance
219  * and any data that depends on it.
220  *
221  * @global object
222  * @param int $id forum instance id
223  * @return bool success
224  */
225 function forum_delete_instance($id) {
226     global $DB;
228     if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
229         return false;
230     }
231     if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
232         return false;
233     }
234     if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
235         return false;
236     }
238     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
240     // now get rid of all files
241     $fs = get_file_storage();
242     $fs->delete_area_files($context->id);
244     $result = true;
246     if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
247         foreach ($discussions as $discussion) {
248             if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
249                 $result = false;
250             }
251         }
252     }
254     if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
255         $result = false;
256     }
258     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
260     if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
261         $result = false;
262     }
264     forum_grade_item_delete($forum);
266     return $result;
270 /**
271  * Indicates API features that the forum supports.
272  *
273  * @uses FEATURE_GROUPS
274  * @uses FEATURE_GROUPINGS
275  * @uses FEATURE_GROUPMEMBERSONLY
276  * @uses FEATURE_MOD_INTRO
277  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
278  * @uses FEATURE_COMPLETION_HAS_RULES
279  * @uses FEATURE_GRADE_HAS_GRADE
280  * @uses FEATURE_GRADE_OUTCOMES
281  * @param string $feature
282  * @return mixed True if yes (some features may use other values)
283  */
284 function forum_supports($feature) {
285     switch($feature) {
286         case FEATURE_GROUPS:                  return true;
287         case FEATURE_GROUPINGS:               return true;
288         case FEATURE_GROUPMEMBERSONLY:        return true;
289         case FEATURE_MOD_INTRO:               return true;
290         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
291         case FEATURE_COMPLETION_HAS_RULES:    return true;
292         case FEATURE_GRADE_HAS_GRADE:         return true;
293         case FEATURE_GRADE_OUTCOMES:          return true;
294         case FEATURE_RATE:                    return true;
295         case FEATURE_BACKUP_MOODLE2:          return true;
297         default: return null;
298     }
302 /**
303  * Obtains the automatic completion state for this forum based on any conditions
304  * in forum settings.
305  *
306  * @global object
307  * @global object
308  * @param object $course Course
309  * @param object $cm Course-module
310  * @param int $userid User ID
311  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
312  * @return bool True if completed, false if not. (If no conditions, then return
313  *   value depends on comparison type)
314  */
315 function forum_get_completion_state($course,$cm,$userid,$type) {
316     global $CFG,$DB;
318     // Get forum details
319     if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
320         throw new Exception("Can't find forum {$cm->instance}");
321     }
323     $result=$type; // Default return value
325     $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
326     $postcountsql="
327 SELECT
328     COUNT(1)
329 FROM
330     {forum_posts} fp
331     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
332 WHERE
333     fp.userid=:userid AND fd.forum=:forumid";
335     if ($forum->completiondiscussions) {
336         $value = $forum->completiondiscussions <=
337                  $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
338         if ($type == COMPLETION_AND) {
339             $result = $result && $value;
340         } else {
341             $result = $result || $value;
342         }
343     }
344     if ($forum->completionreplies) {
345         $value = $forum->completionreplies <=
346                  $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
347         if ($type==COMPLETION_AND) {
348             $result = $result && $value;
349         } else {
350             $result = $result || $value;
351         }
352     }
353     if ($forum->completionposts) {
354         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
355         if ($type == COMPLETION_AND) {
356             $result = $result && $value;
357         } else {
358             $result = $result || $value;
359         }
360     }
362     return $result;
366 /**
367  * Function to be run periodically according to the moodle cron
368  * Finds all posts that have yet to be mailed out, and mails them
369  * out to all subscribers
370  *
371  * @global object
372  * @global object
373  * @global object
374  * @uses CONTEXT_MODULE
375  * @uses CONTEXT_COURSE
376  * @uses SITEID
377  * @uses FORMAT_PLAIN
378  * @return void
379  */
380 function forum_cron() {
381     global $CFG, $USER, $DB;
383     $site = get_site();
385     // all users that are subscribed to any post that needs sending
386     $users = array();
388     // status arrays
389     $mailcount  = array();
390     $errorcount = array();
392     // caches
393     $discussions     = array();
394     $forums          = array();
395     $courses         = array();
396     $coursemodules   = array();
397     $subscribedusers = array();
400     // Posts older than 2 days will not be mailed.  This is to avoid the problem where
401     // cron has not been running for a long time, and then suddenly people are flooded
402     // with mail from the past few weeks or months
403     $timenow   = time();
404     $endtime   = $timenow - $CFG->maxeditingtime;
405     $starttime = $endtime - 48 * 3600;   // Two days earlier
407     if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
408         // Mark them all now as being mailed.  It's unlikely but possible there
409         // might be an error later so that a post is NOT actually mailed out,
410         // but since mail isn't crucial, we can accept this risk.  Doing it now
411         // prevents the risk of duplicated mails, which is a worse problem.
413         if (!forum_mark_old_posts_as_mailed($endtime)) {
414             mtrace('Errors occurred while trying to mark some posts as being mailed.');
415             return false;  // Don't continue trying to mail them, in case we are in a cron loop
416         }
418         // checking post validity, and adding users to loop through later
419         foreach ($posts as $pid => $post) {
421             $discussionid = $post->discussion;
422             if (!isset($discussions[$discussionid])) {
423                 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
424                     $discussions[$discussionid] = $discussion;
425                 } else {
426                     mtrace('Could not find discussion '.$discussionid);
427                     unset($posts[$pid]);
428                     continue;
429                 }
430             }
431             $forumid = $discussions[$discussionid]->forum;
432             if (!isset($forums[$forumid])) {
433                 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
434                     $forums[$forumid] = $forum;
435                 } else {
436                     mtrace('Could not find forum '.$forumid);
437                     unset($posts[$pid]);
438                     continue;
439                 }
440             }
441             $courseid = $forums[$forumid]->course;
442             if (!isset($courses[$courseid])) {
443                 if ($course = $DB->get_record('course', array('id' => $courseid))) {
444                     $courses[$courseid] = $course;
445                 } else {
446                     mtrace('Could not find course '.$courseid);
447                     unset($posts[$pid]);
448                     continue;
449                 }
450             }
451             if (!isset($coursemodules[$forumid])) {
452                 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
453                     $coursemodules[$forumid] = $cm;
454                 } else {
455                     mtrace('Could not find course module for forum '.$forumid);
456                     unset($posts[$pid]);
457                     continue;
458                 }
459             }
462             // caching subscribed users of each forum
463             if (!isset($subscribedusers[$forumid])) {
464                 $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
465                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext)) {
466                     foreach ($subusers as $postuser) {
467                         // this user is subscribed to this forum
468                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
469                         // this user is a user we have to process later
470                         $users[$postuser->id] = $postuser;
471                     }
472                     unset($subusers); // release memory
473                 }
474             }
476             $mailcount[$pid] = 0;
477             $errorcount[$pid] = 0;
478         }
479     }
481     if ($users && $posts) {
483         $urlinfo = parse_url($CFG->wwwroot);
484         $hostname = $urlinfo['host'];
486         foreach ($users as $userto) {
488             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
490             // set this so that the capabilities are cached, and environment matches receiving user
491             cron_setup_user($userto);
493             mtrace('Processing user '.$userto->id);
495             // init caches
496             $userto->viewfullnames = array();
497             $userto->canpost       = array();
498             $userto->markposts     = array();
499             $userto->enrolledin    = array();
501             // reset the caches
502             foreach ($coursemodules as $forumid=>$unused) {
503                 $coursemodules[$forumid]->cache       = new stdClass();
504                 $coursemodules[$forumid]->cache->caps = array();
505                 unset($coursemodules[$forumid]->uservisible);
506             }
508             foreach ($posts as $pid => $post) {
510                 // Set up the environment for the post, discussion, forum, course
511                 $discussion = $discussions[$post->discussion];
512                 $forum      = $forums[$discussion->forum];
513                 $course     = $courses[$forum->course];
514                 $cm         =& $coursemodules[$forum->id];
516                 // Do some checks  to see if we can bail out now
517                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
518                     continue; // user does not subscribe to this forum
519                 }
521                 // Verify user is enrollend in course - if not do not send any email
522                 if (!isset($userto->enrolledin[$course->id])) {
523                     $userto->enrolledin[$course->id] = is_enrolled(get_context_instance(CONTEXT_COURSE, $course->id));
524                 }
525                 if (!$userto->enrolledin[$course->id]) {
526                     // oops - this user should not receive anything from this course
527                     continue;
528                 }
530                 // Get info about the sending user
531                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
532                     $userfrom = $users[$post->userid];
533                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
534                     $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
535                 } else {
536                     mtrace('Could not find user '.$post->userid);
537                     continue;
538                 }
540                 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
542                 // setup global $COURSE properly - needed for roles and languages
543                 cron_setup_user($userto, $course);
545                 // Fill caches
546                 if (!isset($userto->viewfullnames[$forum->id])) {
547                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
548                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
549                 }
550                 if (!isset($userto->canpost[$discussion->id])) {
551                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
552                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
553                 }
554                 if (!isset($userfrom->groups[$forum->id])) {
555                     if (!isset($userfrom->groups)) {
556                         $userfrom->groups = array();
557                         $users[$userfrom->id]->groups = array();
558                     }
559                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
560                     $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
561                 }
563                 // Make sure groups allow this user to see this email
564                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
565                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
566                         continue;                           // Be safe and don't send it to anyone
567                     }
569                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
570                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
571                         continue;
572                     }
573                 }
575                 // Make sure we're allowed to see it...
576                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
577                     mtrace('user '.$userto->id. ' can not see '.$post->id);
578                     continue;
579                 }
581                 // OK so we need to send the email.
583                 // Does the user want this post in a digest?  If so postpone it for now.
584                 if ($userto->maildigest > 0) {
585                     // This user wants the mails to be in digest form
586                     $queue = new stdClass();
587                     $queue->userid       = $userto->id;
588                     $queue->discussionid = $discussion->id;
589                     $queue->postid       = $post->id;
590                     $queue->timemodified = $post->created;
591                     $DB->insert_record('forum_queue', $queue);
592                     continue;
593                 }
596                 // Prepare to actually send the post now, and build up the content
598                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
600                 $userfrom->customheaders = array (  // Headers to make emails easier to track
601                            'Precedence: Bulk',
602                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
603                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
604                            'Message-ID: <moodlepost'.$post->id.'@'.$hostname.'>',
605                            'X-Course-Id: '.$course->id,
606                            'X-Course-Name: '.format_string($course->fullname, true)
607                 );
609                 if ($post->parent) {  // This post is a reply, so add headers for threading (see MDL-22551)
610                     $userfrom->customheaders[] = 'In-Reply-To: <moodlepost'.$post->parent.'@'.$hostname.'>';
611                     $userfrom->customheaders[] = 'References: <moodlepost'.$post->parent.'@'.$hostname.'>';
612                 }
614                 $postsubject = "$course->shortname: ".format_string($post->subject,true);
615                 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
616                 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
618                 // Send the post now!
620                 mtrace('Sending ', '');
622                 $eventdata = new stdClass();
623                 $eventdata->component        = 'mod_forum';
624                 $eventdata->name             = 'posts';
625                 $eventdata->userfrom         = $userfrom;
626                 $eventdata->userto           = $userto;
627                 $eventdata->subject          = $postsubject;
628                 $eventdata->fullmessage      = $posttext;
629                 $eventdata->fullmessageformat = FORMAT_PLAIN;
630                 $eventdata->fullmessagehtml  = $posthtml;
631                 $eventdata->notification = 1;
633                 $smallmessagestrings = new stdClass();
634                 $smallmessagestrings->user = fullname($userfrom);
635                 $smallmessagestrings->forumname = "{$course->shortname}: ".format_string($forum->name,true).": ".$discussion->name;
636                 $smallmessagestrings->message = $post->message;
637                 //make sure strings are in message recipients language
638                 $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
640                 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
641                 $eventdata->contexturlname = $discussion->name;
643                 $mailresult = message_send($eventdata);
644                 if (!$mailresult){
645                     mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
646                          " ($userto->email) .. not trying again.");
647                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
648                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
649                     $errorcount[$post->id]++;
650                 } else {
651                     $mailcount[$post->id]++;
653                 // Mark post as read if forum_usermarksread is set off
654                     if (!$CFG->forum_usermarksread) {
655                         $userto->markposts[$post->id] = $post->id;
656                     }
657                 }
659                 mtrace('post '.$post->id. ': '.$post->subject);
660             }
662             // mark processed posts as read
663             forum_tp_mark_posts_read($userto, $userto->markposts);
664         }
665     }
667     if ($posts) {
668         foreach ($posts as $post) {
669             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
670             if ($errorcount[$post->id]) {
671                 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
672             }
673         }
674     }
676     // release some memory
677     unset($subscribedusers);
678     unset($mailcount);
679     unset($errorcount);
681     cron_setup_user();
683     $sitetimezone = $CFG->timezone;
685     // Now see if there are any digest mails waiting to be sent, and if we should send them
687     mtrace('Starting digest processing...');
689     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
691     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
692         set_config('digestmailtimelast', 0);
693     }
695     $timenow = time();
696     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
698     // Delete any really old ones (normally there shouldn't be any)
699     $weekago = $timenow - (7 * 24 * 3600);
700     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
701     mtrace ('Cleaned old digest records');
703     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
705         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
707         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
709         if ($digestposts_rs->valid()) {
711             // We have work to do
712             $usermailcount = 0;
714             //caches - reuse the those filled before too
715             $discussionposts = array();
716             $userdiscussions = array();
718             foreach ($digestposts_rs as $digestpost) {
719                 if (!isset($users[$digestpost->userid])) {
720                     if ($user = $DB->get_record('user', array('id' => $digestpost->userid))) {
721                         $users[$digestpost->userid] = $user;
722                     } else {
723                         continue;
724                     }
725                 }
726                 $postuser = $users[$digestpost->userid];
728                 if (!isset($posts[$digestpost->postid])) {
729                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
730                         $posts[$digestpost->postid] = $post;
731                     } else {
732                         continue;
733                     }
734                 }
735                 $discussionid = $digestpost->discussionid;
736                 if (!isset($discussions[$discussionid])) {
737                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
738                         $discussions[$discussionid] = $discussion;
739                     } else {
740                         continue;
741                     }
742                 }
743                 $forumid = $discussions[$discussionid]->forum;
744                 if (!isset($forums[$forumid])) {
745                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
746                         $forums[$forumid] = $forum;
747                     } else {
748                         continue;
749                     }
750                 }
752                 $courseid = $forums[$forumid]->course;
753                 if (!isset($courses[$courseid])) {
754                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
755                         $courses[$courseid] = $course;
756                     } else {
757                         continue;
758                     }
759                 }
761                 if (!isset($coursemodules[$forumid])) {
762                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
763                         $coursemodules[$forumid] = $cm;
764                     } else {
765                         continue;
766                     }
767                 }
768                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
769                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
770             }
771             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
773             // Data collected, start sending out emails to each user
774             foreach ($userdiscussions as $userid => $thesediscussions) {
776                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
778                 cron_setup_user();
780                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
782                 // First of all delete all the queue entries for this user
783                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
784                 $userto = $users[$userid];
786                 // Override the language and timezone of the "current" user, so that
787                 // mail is customised for the receiver.
788                 cron_setup_user($userto);
790                 // init caches
791                 $userto->viewfullnames = array();
792                 $userto->canpost       = array();
793                 $userto->markposts     = array();
795                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
797                 $headerdata = new stdClass();
798                 $headerdata->sitename = format_string($site->fullname, true);
799                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
801                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
802                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
804                 $posthtml = "<head>";
805 /*                foreach ($CFG->stylesheets as $stylesheet) {
806                     //TODO: MDL-21120
807                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
808                 }*/
809                 $posthtml .= "</head>\n<body id=\"email\">\n";
810                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
812                 foreach ($thesediscussions as $discussionid) {
814                     @set_time_limit(120);   // to be reset for each post
816                     $discussion = $discussions[$discussionid];
817                     $forum      = $forums[$discussion->forum];
818                     $course     = $courses[$forum->course];
819                     $cm         = $coursemodules[$forum->id];
821                     //override language
822                     cron_setup_user($userto, $course);
824                     // Fill caches
825                     if (!isset($userto->viewfullnames[$forum->id])) {
826                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
827                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
828                     }
829                     if (!isset($userto->canpost[$discussion->id])) {
830                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
831                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
832                     }
834                     $strforums      = get_string('forums', 'forum');
835                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
836                     $canreply       = $userto->canpost[$discussion->id];
838                     $posttext .= "\n \n";
839                     $posttext .= '=====================================================================';
840                     $posttext .= "\n \n";
841                     $posttext .= "$course->shortname -> $strforums -> ".format_string($forum->name,true);
842                     if ($discussion->name != $forum->name) {
843                         $posttext  .= " -> ".format_string($discussion->name,true);
844                     }
845                     $posttext .= "\n";
847                     $posthtml .= "<p><font face=\"sans-serif\">".
848                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> -> ".
849                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
850                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
851                     if ($discussion->name == $forum->name) {
852                         $posthtml .= "</font></p>";
853                     } else {
854                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
855                     }
856                     $posthtml .= '<p>';
858                     $postsarray = $discussionposts[$discussionid];
859                     sort($postsarray);
861                     foreach ($postsarray as $postid) {
862                         $post = $posts[$postid];
864                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
865                             $userfrom = $users[$post->userid];
866                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
867                             $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
868                         } else {
869                             mtrace('Could not find user '.$post->userid);
870                             continue;
871                         }
873                         if (!isset($userfrom->groups[$forum->id])) {
874                             if (!isset($userfrom->groups)) {
875                                 $userfrom->groups = array();
876                                 $users[$userfrom->id]->groups = array();
877                             }
878                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
879                             $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
880                         }
882                         $userfrom->customheaders = array ("Precedence: Bulk");
884                         if ($userto->maildigest == 2) {
885                             // Subjects only
886                             $by = new stdClass();
887                             $by->name = fullname($userfrom);
888                             $by->date = userdate($post->modified);
889                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
890                             $posttext .= "\n---------------------------------------------------------------------";
892                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
893                             $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>';
895                         } else {
896                             // The full treatment
897                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
898                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
900                         // Create an array of postid's for this user to mark as read.
901                             if (!$CFG->forum_usermarksread) {
902                                 $userto->markposts[$post->id] = $post->id;
903                             }
904                         }
905                     }
906                     if ($canunsubscribe) {
907                         $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>";
908                     } else {
909                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
910                     }
911                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
912                 }
913                 $posthtml .= '</body>';
915                 if (empty($userto->mailformat) || $userto->mailformat != 1) {
916                     // This user DOESN'T want to receive HTML
917                     $posthtml = '';
918                 }
920                 $attachment = $attachname='';
921                 $usetrueaddress = true;
922                 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname, $usetrueaddress, $CFG->forum_replytouser);
924                 if (!$mailresult) {
925                     mtrace("ERROR!");
926                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
927                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
928                 } else {
929                     mtrace("success.");
930                     $usermailcount++;
932                     // Mark post as read if forum_usermarksread is set off
933                     forum_tp_mark_posts_read($userto, $userto->markposts);
934                 }
935             }
936         }
937     /// We have finishied all digest emails, update $CFG->digestmailtimelast
938         set_config('digestmailtimelast', $timenow);
939     }
941     cron_setup_user();
943     if (!empty($usermailcount)) {
944         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
945     }
947     if (!empty($CFG->forum_lastreadclean)) {
948         $timenow = time();
949         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
950             set_config('forum_lastreadclean', $timenow);
951             mtrace('Removing old forum read tracking info...');
952             forum_tp_clean_read_records();
953         }
954     } else {
955         set_config('forum_lastreadclean', time());
956     }
959     return true;
962 /**
963  * Builds and returns the body of the email notification in plain text.
964  *
965  * @global object
966  * @global object
967  * @uses CONTEXT_MODULE
968  * @param object $course
969  * @param object $cm
970  * @param object $forum
971  * @param object $discussion
972  * @param object $post
973  * @param object $userfrom
974  * @param object $userto
975  * @param boolean $bare
976  * @return string The email body in plain text format.
977  */
978 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
979     global $CFG, $USER;
981     if (!isset($userto->viewfullnames[$forum->id])) {
982         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
983         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
984     } else {
985         $viewfullnames = $userto->viewfullnames[$forum->id];
986     }
988     if (!isset($userto->canpost[$discussion->id])) {
989         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
990         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
991     } else {
992         $canreply = $userto->canpost[$discussion->id];
993     }
995     $by = New stdClass;
996     $by->name = fullname($userfrom, $viewfullnames);
997     $by->date = userdate($post->modified, "", $userto->timezone);
999     $strbynameondate = get_string('bynameondate', 'forum', $by);
1001     $strforums = get_string('forums', 'forum');
1003     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1005     $posttext = '';
1007     if (!$bare) {
1008         $posttext  = "$course->shortname -> $strforums -> ".format_string($forum->name,true);
1010         if ($discussion->name != $forum->name) {
1011             $posttext  .= " -> ".format_string($discussion->name,true);
1012         }
1013     }
1015     $posttext .= "\n---------------------------------------------------------------------\n";
1016     $posttext .= format_string($post->subject,true);
1017     if ($bare) {
1018         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1019     }
1020     $posttext .= "\n".$strbynameondate."\n";
1021     $posttext .= "---------------------------------------------------------------------\n";
1022     $posttext .= format_text_email($post->message, $post->messageformat);
1023     $posttext .= "\n\n";
1024     $posttext .= forum_print_attachments($post, $cm, "text");
1026     if (!$bare && $canreply) {
1027         $posttext .= "---------------------------------------------------------------------\n";
1028         $posttext .= get_string("postmailinfo", "forum", $course->shortname)."\n";
1029         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1030     }
1031     if (!$bare && $canunsubscribe) {
1032         $posttext .= "\n---------------------------------------------------------------------\n";
1033         $posttext .= get_string("unsubscribe", "forum");
1034         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1035     }
1037     return $posttext;
1040 /**
1041  * Builds and returns the body of the email notification in html format.
1042  *
1043  * @global object
1044  * @param object $course
1045  * @param object $cm
1046  * @param object $forum
1047  * @param object $discussion
1048  * @param object $post
1049  * @param object $userfrom
1050  * @param object $userto
1051  * @return string The email text in HTML format
1052  */
1053 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1054     global $CFG;
1056     if ($userto->mailformat != 1) {  // Needs to be HTML
1057         return '';
1058     }
1060     if (!isset($userto->canpost[$discussion->id])) {
1061         $canreply = forum_user_can_post($forum, $discussion, $userto);
1062     } else {
1063         $canreply = $userto->canpost[$discussion->id];
1064     }
1066     $strforums = get_string('forums', 'forum');
1067     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1069     $posthtml = '<head>';
1070 /*    foreach ($CFG->stylesheets as $stylesheet) {
1071         //TODO: MDL-21120
1072         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1073     }*/
1074     $posthtml .= '</head>';
1075     $posthtml .= "\n<body id=\"email\">\n\n";
1077     $posthtml .= '<div class="navbar">'.
1078     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$course->shortname.'</a> &raquo; '.
1079     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1080     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1081     if ($discussion->name == $forum->name) {
1082         $posthtml .= '</div>';
1083     } else {
1084         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1085                      format_string($discussion->name,true).'</a></div>';
1086     }
1087     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1089     if ($canunsubscribe) {
1090         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1091                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1092                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1093     }
1095     $posthtml .= '</body>';
1097     return $posthtml;
1101 /**
1102  *
1103  * @param object $course
1104  * @param object $user
1105  * @param object $mod TODO this is not used in this function, refactor
1106  * @param object $forum
1107  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1108  */
1109 function forum_user_outline($course, $user, $mod, $forum) {
1110     global $CFG;
1111     require_once("$CFG->libdir/gradelib.php");
1112     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1113     if (empty($grades->items[0]->grades)) {
1114         $grade = false;
1115     } else {
1116         $grade = reset($grades->items[0]->grades);
1117     }
1119     $count = forum_count_user_posts($forum->id, $user->id);
1121     if ($count && $count->postcount > 0) {
1122         $result = new stdClass();
1123         $result->info = get_string("numposts", "forum", $count->postcount);
1124         $result->time = $count->lastpost;
1125         if ($grade) {
1126             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1127         }
1128         return $result;
1129     } else if ($grade) {
1130         $result = new stdClass();
1131         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1132         $result->time = $grade->dategraded;
1133         return $result;
1134     }
1135     return NULL;
1139 /**
1140  * @global object
1141  * @global object
1142  * @param object $coure
1143  * @param object $user
1144  * @param object $mod
1145  * @param object $forum
1146  */
1147 function forum_user_complete($course, $user, $mod, $forum) {
1148     global $CFG,$USER, $OUTPUT;
1149     require_once("$CFG->libdir/gradelib.php");
1151     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1152     if (!empty($grades->items[0]->grades)) {
1153         $grade = reset($grades->items[0]->grades);
1154         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1155         if ($grade->str_feedback) {
1156             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1157         }
1158     }
1160     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1162         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1163             print_error('invalidcoursemodule');
1164         }
1165         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1167         foreach ($posts as $post) {
1168             if (!isset($discussions[$post->discussion])) {
1169                 continue;
1170             }
1171             $discussion = $discussions[$post->discussion];
1173             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1174         }
1175     } else {
1176         echo "<p>".get_string("noposts", "forum")."</p>";
1177     }
1185 /**
1186  * @global object
1187  * @global object
1188  * @global object
1189  * @param array $courses
1190  * @param array $htmlarray
1191  */
1192 function forum_print_overview($courses,&$htmlarray) {
1193     global $USER, $CFG, $DB, $SESSION;
1195     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1196         return array();
1197     }
1199     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1200         return;
1201     }
1204     // get all forum logs in ONE query (much better!)
1205     $params = array();
1206     $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {log} l "
1207         ." JOIN {course_modules} cm ON cm.id = cmid "
1208         ." WHERE (";
1209     foreach ($courses as $course) {
1210         $sql .= '(l.course = ? AND l.time > ?) OR ';
1211         $params[] = $course->id;
1212         $params[] = $course->lastaccess;
1213     }
1214     $sql = substr($sql,0,-3); // take off the last OR
1216     $sql .= ") AND l.module = 'forum' AND action = 'add post' "
1217         ." AND userid != ? GROUP BY cmid,l.course,instance";
1219     $params[] = $USER->id;
1221     if (!$new = $DB->get_records_sql($sql, $params)) {
1222         $new = array(); // avoid warnings
1223     }
1225     // also get all forum tracking stuff ONCE.
1226     $trackingforums = array();
1227     foreach ($forums as $forum) {
1228         if (forum_tp_can_track_forums($forum)) {
1229             $trackingforums[$forum->id] = $forum;
1230         }
1231     }
1233     if (count($trackingforums) > 0) {
1234         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1235         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1236             ' FROM {forum_posts} p '.
1237             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1238             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1239         $params = array($USER->id);
1241         foreach ($trackingforums as $track) {
1242             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1243             $params[] = $track->id;
1244             if (isset($SESSION->currentgroup[$track->course])) {
1245                 $groupid =  $SESSION->currentgroup[$track->course];
1246             } else {
1247                 $groupid = groups_get_all_groups($track->course, $USER->id);
1248                 if (is_array($groupid)) {
1249                     $groupid = array_shift(array_keys($groupid));
1250                     $SESSION->currentgroup[$track->course] = $groupid;
1251                 } else {
1252                     $groupid = 0;
1253                 }
1254             }
1255             $params[] = $groupid;
1256         }
1257         $sql = substr($sql,0,-3); // take off the last OR
1258         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1259         $params[] = $cutoffdate;
1261         if (!$unread = $DB->get_records_sql($sql, $params)) {
1262             $unread = array();
1263         }
1264     } else {
1265         $unread = array();
1266     }
1268     if (empty($unread) and empty($new)) {
1269         return;
1270     }
1272     $strforum = get_string('modulename','forum');
1273     $strnumunread = get_string('overviewnumunread','forum');
1274     $strnumpostssince = get_string('overviewnumpostssince','forum');
1276     foreach ($forums as $forum) {
1277         $str = '';
1278         $count = 0;
1279         $thisunread = 0;
1280         $showunread = false;
1281         // either we have something from logs, or trackposts, or nothing.
1282         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1283             $count = $new[$forum->id]->count;
1284         }
1285         if (array_key_exists($forum->id,$unread)) {
1286             $thisunread = $unread[$forum->id]->count;
1287             $showunread = true;
1288         }
1289         if ($count > 0 || $thisunread > 0) {
1290             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1291                 $forum->name.'</a></div>';
1292             $str .= '<div class="info">';
1293             $str .= $count.' '.$strnumpostssince;
1294             if (!empty($showunread)) {
1295                 $str .= '<br />'.$thisunread .' '.$strnumunread;
1296             }
1297             $str .= '</div></div>';
1298         }
1299         if (!empty($str)) {
1300             if (!array_key_exists($forum->course,$htmlarray)) {
1301                 $htmlarray[$forum->course] = array();
1302             }
1303             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1304                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1305             }
1306             $htmlarray[$forum->course]['forum'] .= $str;
1307         }
1308     }
1311 /**
1312  * Given a course and a date, prints a summary of all the new
1313  * messages posted in the course since that date
1314  *
1315  * @global object
1316  * @global object
1317  * @global object
1318  * @uses CONTEXT_MODULE
1319  * @uses VISIBLEGROUPS
1320  * @param object $course
1321  * @param bool $viewfullnames capability
1322  * @param int $timestart
1323  * @return bool success
1324  */
1325 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1326     global $CFG, $USER, $DB, $OUTPUT;
1328     // do not use log table if possible, it may be huge and is expensive to join with other tables
1330     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1331                                               d.timestart, d.timeend, d.userid AS duserid,
1332                                               u.firstname, u.lastname, u.email, u.picture
1333                                          FROM {forum_posts} p
1334                                               JOIN {forum_discussions} d ON d.id = p.discussion
1335                                               JOIN {forum} f             ON f.id = d.forum
1336                                               JOIN {user} u              ON u.id = p.userid
1337                                         WHERE p.created > ? AND f.course = ?
1338                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1339          return false;
1340     }
1342     $modinfo =& get_fast_modinfo($course);
1344     $groupmodes = array();
1345     $cms    = array();
1347     $strftimerecent = get_string('strftimerecent');
1349     $printposts = array();
1350     foreach ($posts as $post) {
1351         if (!isset($modinfo->instances['forum'][$post->forum])) {
1352             // not visible
1353             continue;
1354         }
1355         $cm = $modinfo->instances['forum'][$post->forum];
1356         if (!$cm->uservisible) {
1357             continue;
1358         }
1359         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1361         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1362             continue;
1363         }
1365         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1366           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1367             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1368                 continue;
1369             }
1370         }
1372         $groupmode = groups_get_activity_groupmode($cm, $course);
1374         if ($groupmode) {
1375             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1376                 // oki (Open discussions have groupid -1)
1377             } else {
1378                 // separate mode
1379                 if (isguestuser()) {
1380                     // shortcut
1381                     continue;
1382                 }
1384                 if (is_null($modinfo->groups)) {
1385                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1386                 }
1388                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1389                     continue;
1390                 }
1391             }
1392         }
1394         $printposts[] = $post;
1395     }
1396     unset($posts);
1398     if (!$printposts) {
1399         return false;
1400     }
1402     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1403     echo "\n<ul class='unlist'>\n";
1405     foreach ($printposts as $post) {
1406         $subjectclass = empty($post->parent) ? ' bold' : '';
1408         echo '<li><div class="head">'.
1409                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1410                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1411              '</div>';
1412         echo '<div class="info'.$subjectclass.'">';
1413         if (empty($post->parent)) {
1414             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1415         } else {
1416             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1417         }
1418         $post->subject = break_up_long_words(format_string($post->subject, true));
1419         echo $post->subject;
1420         echo "</a>\"</div></li>\n";
1421     }
1423     echo "</ul>\n";
1425     return true;
1428 /**
1429  * Return grade for given user or all users.
1430  *
1431  * @global object
1432  * @global object
1433  * @param object $forum
1434  * @param int $userid optional user id, 0 means all users
1435  * @return array array of grades, false if none
1436  */
1437 function forum_get_user_grades($forum, $userid=0) {
1438     global $CFG;
1440     require_once($CFG->dirroot.'/rating/lib.php');
1441     $rm = new rating_manager();
1443     $ratingoptions = new stdclass();
1445     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1446     $ratingoptions->modulename = 'forum';
1447     $ratingoptions->moduleid   = $forum->id;
1448     //$ratingoptions->cmidnumber = $forum->cmidnumber;
1450     $ratingoptions->userid = $userid;
1451     $ratingoptions->aggregationmethod = $forum->assessed;
1452     $ratingoptions->scaleid = $forum->scale;
1453     $ratingoptions->itemtable = 'forum_posts';
1454     $ratingoptions->itemtableusercolumn = 'userid';
1456     return $rm->get_user_grades($ratingoptions);
1459 /**
1460  * Update activity grades
1461  *
1462  * @global object
1463  * @global object
1464  * @param object $forum
1465  * @param int $userid specific user only, 0 means all
1466  * @param boolean $nullifnone return null if grade does not exist
1467  * @return void
1468  */
1469 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1470     global $CFG, $DB;
1471     require_once($CFG->libdir.'/gradelib.php');
1473     if (!$forum->assessed) {
1474         forum_grade_item_update($forum);
1476     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1477         forum_grade_item_update($forum, $grades);
1479     } else if ($userid and $nullifnone) {
1480         $grade = new stdClass();
1481         $grade->userid   = $userid;
1482         $grade->rawgrade = NULL;
1483         forum_grade_item_update($forum, $grade);
1485     } else {
1486         forum_grade_item_update($forum);
1487     }
1490 /**
1491  * Update all grades in gradebook.
1492  * @global object
1493  */
1494 function forum_upgrade_grades() {
1495     global $DB;
1497     $sql = "SELECT COUNT('x')
1498               FROM {forum} f, {course_modules} cm, {modules} m
1499              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1500     $count = $DB->count_records_sql($sql);
1502     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1503               FROM {forum} f, {course_modules} cm, {modules} m
1504              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1505     if ($rs = $DB->get_recordset_sql($sql)) {
1506         $pbar = new progress_bar('forumupgradegrades', 500, true);
1507         $i=0;
1508         foreach ($rs as $forum) {
1509             $i++;
1510             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1511             forum_update_grades($forum, 0, false);
1512             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1513         }
1514         $rs->close();
1515     }
1518 /**
1519  * Create/update grade item for given forum
1520  *
1521  * @global object
1522  * @uses GRADE_TYPE_NONE
1523  * @uses GRADE_TYPE_VALUE
1524  * @uses GRADE_TYPE_SCALE
1525  * @param object $forum object with extra cmidnumber
1526  * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
1527  * @return int 0 if ok
1528  */
1529 function forum_grade_item_update($forum, $grades=NULL) {
1530     global $CFG;
1531     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1532         require_once($CFG->libdir.'/gradelib.php');
1533     }
1535     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1537     if (!$forum->assessed or $forum->scale == 0) {
1538         $params['gradetype'] = GRADE_TYPE_NONE;
1540     } else if ($forum->scale > 0) {
1541         $params['gradetype'] = GRADE_TYPE_VALUE;
1542         $params['grademax']  = $forum->scale;
1543         $params['grademin']  = 0;
1545     } else if ($forum->scale < 0) {
1546         $params['gradetype'] = GRADE_TYPE_SCALE;
1547         $params['scaleid']   = -$forum->scale;
1548     }
1550     if ($grades  === 'reset') {
1551         $params['reset'] = true;
1552         $grades = NULL;
1553     }
1555     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1558 /**
1559  * Delete grade item for given forum
1560  *
1561  * @global object
1562  * @param object $forum object
1563  * @return object grade_item
1564  */
1565 function forum_grade_item_delete($forum) {
1566     global $CFG;
1567     require_once($CFG->libdir.'/gradelib.php');
1569     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1573 /**
1574  * Returns the users with data in one forum
1575  * (users with records in forum_subscriptions, forum_posts, students)
1576  *
1577  * @global object
1578  * @global object
1579  * @param int $forumid
1580  * @return mixed array or false if none
1581  */
1582 function forum_get_participants($forumid) {
1584     global $CFG, $DB;
1586     //Get students from forum_subscriptions
1587     $st_subscriptions = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1588                                          FROM {user} u,
1589                                               {forum_subscriptions} s
1590                                          WHERE s.forum = ? AND
1591                                                u.id = s.userid", array($forumid));
1592     //Get students from forum_posts
1593     $st_posts = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1594                                  FROM {user} u,
1595                                       {forum_discussions} d,
1596                                       {forum_posts} p
1597                                  WHERE d.forum = ? AND
1598                                        p.discussion = d.id AND
1599                                        u.id = p.userid", array($forumid));
1601     //Get students from the ratings table
1602     $st_ratings = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1603                                    FROM {user} u,
1604                                         {forum_discussions} d,
1605                                         {forum_posts} p,
1606                                         {ratings} r
1607                                    WHERE d.forum = ? AND
1608                                          p.discussion = d.id AND
1609                                          r.post = p.id AND
1610                                          u.id = r.userid", array($forumid));
1612     //Add st_posts to st_subscriptions
1613     if ($st_posts) {
1614         foreach ($st_posts as $st_post) {
1615             $st_subscriptions[$st_post->id] = $st_post;
1616         }
1617     }
1618     //Add st_ratings to st_subscriptions
1619     if ($st_ratings) {
1620         foreach ($st_ratings as $st_rating) {
1621             $st_subscriptions[$st_rating->id] = $st_rating;
1622         }
1623     }
1624     //Return st_subscriptions array (it contains an array of unique users)
1625     return ($st_subscriptions);
1628 /**
1629  * This function returns if a scale is being used by one forum
1630  *
1631  * @global object
1632  * @param int $forumid
1633  * @param int $scaleid negative number
1634  * @return bool
1635  */
1636 function forum_scale_used ($forumid,$scaleid) {
1637     global $DB;
1638     $return = false;
1640     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1642     if (!empty($rec) && !empty($scaleid)) {
1643         $return = true;
1644     }
1646     return $return;
1649 /**
1650  * Checks if scale is being used by any instance of forum
1651  *
1652  * This is used to find out if scale used anywhere
1653  *
1654  * @global object
1655  * @param $scaleid int
1656  * @return boolean True if the scale is used by any forum
1657  */
1658 function forum_scale_used_anywhere($scaleid) {
1659     global $DB;
1660     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1661         return true;
1662     } else {
1663         return false;
1664     }
1667 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1669 /**
1670  * Gets a post with all info ready for forum_print_post
1671  * Most of these joins are just to get the forum id
1672  *
1673  * @global object
1674  * @global object
1675  * @param int $postid
1676  * @return mixed array of posts or false
1677  */
1678 function forum_get_post_full($postid) {
1679     global $CFG, $DB;
1681     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1682                              FROM {forum_posts} p
1683                                   JOIN {forum_discussions} d ON p.discussion = d.id
1684                                   LEFT JOIN {user} u ON p.userid = u.id
1685                             WHERE p.id = ?", array($postid));
1688 /**
1689  * Gets posts with all info ready for forum_print_post
1690  * We pass forumid in because we always know it so no need to make a
1691  * complicated join to find it out.
1692  *
1693  * @global object
1694  * @global object
1695  * @return mixed array of posts or false
1696  */
1697 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1698     global $CFG, $DB;
1700     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1701                               FROM {forum_posts} p
1702                          LEFT JOIN {user} u ON p.userid = u.id
1703                              WHERE p.discussion = ?
1704                                AND p.parent > 0 $sort", array($discussion));
1707 /**
1708  * Gets all posts in discussion including top parent.
1709  *
1710  * @global object
1711  * @global object
1712  * @global object
1713  * @param int $discussionid
1714  * @param string $sort
1715  * @param bool $tracking does user track the forum?
1716  * @return array of posts
1717  */
1718 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1719     global $CFG, $DB, $USER;
1721     $tr_sel  = "";
1722     $tr_join = "";
1723     $params = array();
1725     if ($tracking) {
1726         $now = time();
1727         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1728         $tr_sel  = ", fr.id AS postread";
1729         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1730         $params[] = $USER->id;
1731     }
1733     $params[] = $discussionid;
1734     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1735                                      FROM {forum_posts} p
1736                                           LEFT JOIN {user} u ON p.userid = u.id
1737                                           $tr_join
1738                                     WHERE p.discussion = ?
1739                                  ORDER BY $sort", $params)) {
1740         return array();
1741     }
1743     foreach ($posts as $pid=>$p) {
1744         if ($tracking) {
1745             if (forum_tp_is_post_old($p)) {
1746                  $posts[$pid]->postread = true;
1747             }
1748         }
1749         if (!$p->parent) {
1750             continue;
1751         }
1752         if (!isset($posts[$p->parent])) {
1753             continue; // parent does not exist??
1754         }
1755         if (!isset($posts[$p->parent]->children)) {
1756             $posts[$p->parent]->children = array();
1757         }
1758         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1759     }
1761     return $posts;
1764 /**
1765  * Gets posts with all info ready for forum_print_post
1766  * We pass forumid in because we always know it so no need to make a
1767  * complicated join to find it out.
1768  *
1769  * @global object
1770  * @global object
1771  * @param int $parent
1772  * @param int $forumid
1773  * @return array
1774  */
1775 function forum_get_child_posts($parent, $forumid) {
1776     global $CFG, $DB;
1778     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1779                               FROM {forum_posts} p
1780                          LEFT JOIN {user} u ON p.userid = u.id
1781                              WHERE p.parent = ?
1782                           ORDER BY p.created ASC", array($parent));
1785 /**
1786  * An array of forum objects that the user is allowed to read/search through.
1787  *
1788  * @global object
1789  * @global object
1790  * @global object
1791  * @param int $userid
1792  * @param int $courseid if 0, we look for forums throughout the whole site.
1793  * @return array of forum objects, or false if no matches
1794  *         Forum objects have the following attributes:
1795  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1796  *         viewhiddentimedposts
1797  */
1798 function forum_get_readable_forums($userid, $courseid=0) {
1800     global $CFG, $DB, $USER;
1801     require_once($CFG->dirroot.'/course/lib.php');
1803     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1804         print_error('notinstalled', 'forum');
1805     }
1807     if ($courseid) {
1808         $courses = $DB->get_records('course', array('id' => $courseid));
1809     } else {
1810         // If no course is specified, then the user can see SITE + his courses.
1811         $courses1 = $DB->get_records('course', array('id' => SITEID));
1812         $courses2 = enrol_get_users_courses($userid, true);
1813         $courses = array_merge($courses1, $courses2);
1814     }
1815     if (!$courses) {
1816         return array();
1817     }
1819     $readableforums = array();
1821     foreach ($courses as $course) {
1823         $modinfo =& get_fast_modinfo($course);
1824         if (is_null($modinfo->groups)) {
1825             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1826         }
1828         if (empty($modinfo->instances['forum'])) {
1829             // hmm, no forums?
1830             continue;
1831         }
1833         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1835         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1836             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1837                 continue;
1838             }
1839             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1840             $forum = $courseforums[$forumid];
1841             $forum->context = $context;
1842             $forum->cm = $cm;
1844             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1845                 continue;
1846             }
1848          /// group access
1849             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1850                 if (is_null($modinfo->groups)) {
1851                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1852                 }
1853                 if (isset($modinfo->groups[$cm->groupingid])) {
1854                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1855                     $forum->onlygroups[] = -1;
1856                 } else {
1857                     $forum->onlygroups = array(-1);
1858                 }
1859             }
1861         /// hidden timed discussions
1862             $forum->viewhiddentimedposts = true;
1863             if (!empty($CFG->forum_enabletimedposts)) {
1864                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1865                     $forum->viewhiddentimedposts = false;
1866                 }
1867             }
1869         /// qanda access
1870             if ($forum->type == 'qanda'
1871                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1873                 // We need to check whether the user has posted in the qanda forum.
1874                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1875                                                     // the user is allowed to see in this forum.
1876                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1877                     foreach ($discussionspostedin as $d) {
1878                         $forum->onlydiscussions[] = $d->id;
1879                     }
1880                 }
1881             }
1883             $readableforums[$forum->id] = $forum;
1884         }
1886         unset($modinfo);
1888     } // End foreach $courses
1890     return $readableforums;
1893 /**
1894  * Returns a list of posts found using an array of search terms.
1895  *
1896  * @global object
1897  * @global object
1898  * @global object
1899  * @param array $searchterms array of search terms, e.g. word +word -word
1900  * @param int $courseid if 0, we search through the whole site
1901  * @param int $limitfrom
1902  * @param int $limitnum
1903  * @param int &$totalcount
1904  * @param string $extrasql
1905  * @return array|bool Array of posts found or false
1906  */
1907 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1908                             &$totalcount, $extrasql='') {
1909     global $CFG, $DB, $USER;
1910     require_once($CFG->libdir.'/searchlib.php');
1912     $forums = forum_get_readable_forums($USER->id, $courseid);
1914     if (count($forums) == 0) {
1915         $totalcount = 0;
1916         return false;
1917     }
1919     $now = round(time(), -2); // db friendly
1921     $fullaccess = array();
1922     $where = array();
1923     $params = array();
1925     foreach ($forums as $forumid => $forum) {
1926         $select = array();
1928         if (!$forum->viewhiddentimedposts) {
1929             $select[] = "(d.userid = :userid OR (d.timestart < : AND (d.timeend = 0 OR d.timeend > :timeend)))";
1930             $params = array('userid'=>$USER->id, 'timestart'=>$now, 'timeend'=>$now);
1931         }
1933         $cm = $forum->cm;
1934         $context = $forum->context;
1936         if ($forum->type == 'qanda'
1937             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1938             if (!empty($forum->onlydiscussions)) {
1939                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda0');
1940                 $params = array_merge($params, $discussionid_params);
1941                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
1942             } else {
1943                 $select[] = "p.parent = 0";
1944             }
1945         }
1947         if (!empty($forum->onlygroups)) {
1948             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps0');
1949             $params = array_merge($params, $groupid_params);
1950             $select[] = "d.groupid $groupid_sql";
1951         }
1953         if ($select) {
1954             $selects = implode(" AND ", $select);
1955             $where[] = "(d.forum = :forum AND $selects)";
1956             $params['forum'] = $forumid;
1957         } else {
1958             $fullaccess[] = $forumid;
1959         }
1960     }
1962     if ($fullaccess) {
1963         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula0');
1964         $params = array_merge($params, $fullid_params);
1965         $where[] = "(d.forum $fullid_sql)";
1966     }
1968     $selectdiscussion = "(".implode(" OR ", $where).")";
1970     $messagesearch = '';
1971     $searchstring = '';
1973     // Need to concat these back together for parser to work.
1974     foreach($searchterms as $searchterm){
1975         if ($searchstring != '') {
1976             $searchstring .= ' ';
1977         }
1978         $searchstring .= $searchterm;
1979     }
1981     // We need to allow quoted strings for the search. The quotes *should* be stripped
1982     // by the parser, but this should be examined carefully for security implications.
1983     $searchstring = str_replace("\\\"","\"",$searchstring);
1984     $parser = new search_parser();
1985     $lexer = new search_lexer($parser);
1987     if ($lexer->parse($searchstring)) {
1988         $parsearray = $parser->get_parsed_array();
1989     // Experimental feature under 1.8! MDL-8830
1990     // Use alternative text searches if defined
1991     // This feature only works under mysql until properly implemented for other DBs
1992     // Requires manual creation of text index for forum_posts before enabling it:
1993     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
1994     // Experimental feature under 1.8! MDL-8830
1995         if (!empty($CFG->forum_usetextsearches)) {
1996             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
1997                                                  'p.userid', 'u.id', 'u.firstname',
1998                                                  'u.lastname', 'p.modified', 'd.forum');
1999         } else {
2000             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2001                                                  'p.userid', 'u.id', 'u.firstname',
2002                                                  'u.lastname', 'p.modified', 'd.forum');
2003         }
2004         $params = array_merge($params, $msparams);
2005     }
2007     $fromsql = "{forum_posts} p,
2008                   {forum_discussions} d,
2009                   {user} u";
2011     $selectsql = " $messagesearch
2012                AND p.discussion = d.id
2013                AND p.userid = u.id
2014                AND $selectdiscussion
2015                    $extrasql";
2017     $countsql = "SELECT COUNT(*)
2018                    FROM $fromsql
2019                   WHERE $selectsql";
2021     $searchsql = "SELECT p.*,
2022                          d.forum,
2023                          u.firstname,
2024                          u.lastname,
2025                          u.email,
2026                          u.picture,
2027                          u.imagealt,
2028                          u.email
2029                     FROM $fromsql
2030                    WHERE $selectsql
2031                 ORDER BY p.modified DESC";
2033     $totalcount = $DB->count_records_sql($countsql, $params);
2035     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2038 /**
2039  * Returns a list of ratings for a particular post - sorted.
2040  *
2041  * @global object
2042  * @global object
2043  * @param int $postid
2044  * @param string $sort
2045  * @return array Array of ratings or false
2046  */
2047 function forum_get_ratings($context, $postid, $sort="u.firstname ASC") {
2048     global $PAGE;
2050     $options = new stdclass();
2051     $options->context = $PAGE->context;
2052     $options->itemid = $postid;
2053     $options->sort = "ORDER BY $sort";
2055     $rm = new rating_manager();
2056     $rm->get_all_ratings_for_item($options);
2059 /**
2060  * Returns a list of all new posts that have not been mailed yet
2061  *
2062  * @global object
2063  * @global object
2064  * @param int $starttime posts created after this time
2065  * @param int $endtime posts created before this
2066  * @param int $now used for timed discussions only
2067  * @return array
2068  */
2069 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2070     global $CFG, $DB;
2072     $params = array($starttime, $endtime);
2073     if (!empty($CFG->forum_enabletimedposts)) {
2074         if (empty($now)) {
2075             $now = time();
2076         }
2077         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2078         $params[] = $now;
2079         $params[] = $now;
2080     } else {
2081         $timedsql = "";
2082     }
2084     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2085                               FROM {forum_posts} p
2086                                    JOIN {forum_discussions} d ON d.id = p.discussion
2087                              WHERE p.mailed = 0
2088                                    AND p.created >= ?
2089                                    AND (p.created < ? OR p.mailnow = 1)
2090                                    $timedsql
2091                           ORDER BY p.modified ASC", $params);
2094 /**
2095  * Marks posts before a certain time as being mailed already
2096  *
2097  * @global object
2098  * @global object
2099  * @param int $endtime
2100  * @param int $now Defaults to time()
2101  * @return bool
2102  */
2103 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2104     global $CFG, $DB;
2105     if (empty($now)) {
2106         $now = time();
2107     }
2109     if (empty($CFG->forum_enabletimedposts)) {
2110         return $DB->execute("UPDATE {forum_posts}
2111                                SET mailed = '1'
2112                              WHERE (created < ? OR mailnow = 1)
2113                                    AND mailed = 0", array($endtime));
2115     } else {
2116         return $DB->execute("UPDATE {forum_posts}
2117                                SET mailed = '1'
2118                              WHERE discussion NOT IN (SELECT d.id
2119                                                         FROM {forum_discussions} d
2120                                                        WHERE d.timestart > ?)
2121                                    AND (created < ? OR mailnow = 1)
2122                                    AND mailed = 0", array($now, $endtime));
2123     }
2126 /**
2127  * Get all the posts for a user in a forum suitable for forum_print_post
2128  *
2129  * @global object
2130  * @global object
2131  * @uses CONTEXT_MODULE
2132  * @return array
2133  */
2134 function forum_get_user_posts($forumid, $userid) {
2135     global $CFG, $DB;
2137     $timedsql = "";
2138     $params = array($forumid, $userid);
2140     if (!empty($CFG->forum_enabletimedposts)) {
2141         $cm = get_coursemodule_from_instance('forum', $forumid);
2142         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2143             $now = time();
2144             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2145             $params[] = $now;
2146             $params[] = $now;
2147         }
2148     }
2150     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2151                               FROM {forum} f
2152                                    JOIN {forum_discussions} d ON d.forum = f.id
2153                                    JOIN {forum_posts} p       ON p.discussion = d.id
2154                                    JOIN {user} u              ON u.id = p.userid
2155                              WHERE f.id = ?
2156                                    AND p.userid = ?
2157                                    $timedsql
2158                           ORDER BY p.modified ASC", $params);
2161 /**
2162  * Get all the discussions user participated in
2163  *
2164  * @global object
2165  * @global object
2166  * @uses CONTEXT_MODULE
2167  * @param int $forumid
2168  * @param int $userid
2169  * @return array Array or false
2170  */
2171 function forum_get_user_involved_discussions($forumid, $userid) {
2172     global $CFG, $DB;
2174     $timedsql = "";
2175     $params = array($forumid, $userid);
2176     if (!empty($CFG->forum_enabletimedposts)) {
2177         $cm = get_coursemodule_from_instance('forum', $forumid);
2178         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2179             $now = time();
2180             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2181             $params[] = $now;
2182             $params[] = $now;
2183         }
2184     }
2186     return $DB->get_records_sql("SELECT DISTINCT d.*
2187                               FROM {forum} f
2188                                    JOIN {forum_discussions} d ON d.forum = f.id
2189                                    JOIN {forum_posts} p       ON p.discussion = d.id
2190                              WHERE f.id = ?
2191                                    AND p.userid = ?
2192                                    $timedsql", $params);
2195 /**
2196  * Get all the posts for a user in a forum suitable for forum_print_post
2197  *
2198  * @global object
2199  * @global object
2200  * @param int $forumid
2201  * @param int $userid
2202  * @return array of counts or false
2203  */
2204 function forum_count_user_posts($forumid, $userid) {
2205     global $CFG, $DB;
2207     $timedsql = "";
2208     $params = array($forumid, $userid);
2209     if (!empty($CFG->forum_enabletimedposts)) {
2210         $cm = get_coursemodule_from_instance('forum', $forumid);
2211         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2212             $now = time();
2213             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2214             $params[] = $now;
2215             $params[] = $now;
2216         }
2217     }
2219     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2220                              FROM {forum} f
2221                                   JOIN {forum_discussions} d ON d.forum = f.id
2222                                   JOIN {forum_posts} p       ON p.discussion = d.id
2223                                   JOIN {user} u              ON u.id = p.userid
2224                             WHERE f.id = ?
2225                                   AND p.userid = ?
2226                                   $timedsql", $params);
2229 /**
2230  * Given a log entry, return the forum post details for it.
2231  *
2232  * @global object
2233  * @global object
2234  * @param object $log
2235  * @return array|null
2236  */
2237 function forum_get_post_from_log($log) {
2238     global $CFG, $DB;
2240     if ($log->action == "add post") {
2242         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2243                                            u.firstname, u.lastname, u.email, u.picture
2244                                  FROM {forum_discussions} d,
2245                                       {forum_posts} p,
2246                                       {forum} f,
2247                                       {user} u
2248                                 WHERE p.id = ?
2249                                   AND d.id = p.discussion
2250                                   AND p.userid = u.id
2251                                   AND u.deleted <> '1'
2252                                   AND f.id = d.forum", array($log->info));
2255     } else if ($log->action == "add discussion") {
2257         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2258                                            u.firstname, u.lastname, u.email, u.picture
2259                                  FROM {forum_discussions} d,
2260                                       {forum_posts} p,
2261                                       {forum} f,
2262                                       {user} u
2263                                 WHERE d.id = ?
2264                                   AND d.firstpost = p.id
2265                                   AND p.userid = u.id
2266                                   AND u.deleted <> '1'
2267                                   AND f.id = d.forum", array($log->info));
2268     }
2269     return NULL;
2272 /**
2273  * Given a discussion id, return the first post from the discussion
2274  *
2275  * @global object
2276  * @global object
2277  * @param int $dicsussionid
2278  * @return array
2279  */
2280 function forum_get_firstpost_from_discussion($discussionid) {
2281     global $CFG, $DB;
2283     return $DB->get_record_sql("SELECT p.*
2284                              FROM {forum_discussions} d,
2285                                   {forum_posts} p
2286                             WHERE d.id = ?
2287                               AND d.firstpost = p.id ", array($discussionid));
2290 /**
2291  * Returns an array of counts of replies to each discussion
2292  *
2293  * @global object
2294  * @global object
2295  * @param int $forumid
2296  * @param string $forumsort
2297  * @param int $limit
2298  * @param int $page
2299  * @param int $perpage
2300  * @return array
2301  */
2302 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2303     global $CFG, $DB;
2305     if ($limit > 0) {
2306         $limitfrom = 0;
2307         $limitnum  = $limit;
2308     } else if ($page != -1) {
2309         $limitfrom = $page*$perpage;
2310         $limitnum  = $perpage;
2311     } else {
2312         $limitfrom = 0;
2313         $limitnum  = 0;
2314     }
2316     if ($forumsort == "") {
2317         $orderby = "";
2318         $groupby = "";
2320     } else {
2321         $orderby = "ORDER BY $forumsort";
2322         $groupby = ", ".strtolower($forumsort);
2323         $groupby = str_replace('desc', '', $groupby);
2324         $groupby = str_replace('asc', '', $groupby);
2325     }
2327     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2328         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2329                   FROM {forum_posts} p
2330                        JOIN {forum_discussions} d ON p.discussion = d.id
2331                  WHERE p.parent > 0 AND d.forum = ?
2332               GROUP BY p.discussion";
2333         return $DB->get_records_sql($sql, array($forumid));
2335     } else {
2336         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2337                   FROM {forum_posts} p
2338                        JOIN {forum_discussions} d ON p.discussion = d.id
2339                  WHERE d.forum = ?
2340               GROUP BY p.discussion $groupby
2341               $orderby";
2342         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2343     }
2346 /**
2347  * @global object
2348  * @global object
2349  * @global object
2350  * @staticvar array $cache
2351  * @param object $forum
2352  * @param object $cm
2353  * @param object $course
2354  * @return mixed
2355  */
2356 function forum_count_discussions($forum, $cm, $course) {
2357     global $CFG, $DB, $USER;
2359     static $cache = array();
2361     $now = round(time(), -2); // db cache friendliness
2363     $params = array($course->id);
2365     if (!isset($cache[$course->id])) {
2366         if (!empty($CFG->forum_enabletimedposts)) {
2367             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2368             $params[] = $now;
2369             $params[] = $now;
2370         } else {
2371             $timedsql = "";
2372         }
2374         $sql = "SELECT f.id, COUNT(d.id) as dcount
2375                   FROM {forum} f
2376                        JOIN {forum_discussions} d ON d.forum = f.id
2377                  WHERE f.course = ?
2378                        $timedsql
2379               GROUP BY f.id";
2381         if ($counts = $DB->get_records_sql($sql, $params)) {
2382             foreach ($counts as $count) {
2383                 $counts[$count->id] = $count->dcount;
2384             }
2385             $cache[$course->id] = $counts;
2386         } else {
2387             $cache[$course->id] = array();
2388         }
2389     }
2391     if (empty($cache[$course->id][$forum->id])) {
2392         return 0;
2393     }
2395     $groupmode = groups_get_activity_groupmode($cm, $course);
2397     if ($groupmode != SEPARATEGROUPS) {
2398         return $cache[$course->id][$forum->id];
2399     }
2401     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2402         return $cache[$course->id][$forum->id];
2403     }
2405     require_once($CFG->dirroot.'/course/lib.php');
2407     $modinfo =& get_fast_modinfo($course);
2408     if (is_null($modinfo->groups)) {
2409         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2410     }
2412     if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2413         $mygroups = $modinfo->groups[$cm->groupingid];
2414     } else {
2415         $mygroups = false; // Will be set below
2416     }
2418     // add all groups posts
2419     if (empty($mygroups)) {
2420         $mygroups = array(-1=>-1);
2421     } else {
2422         $mygroups[-1] = -1;
2423     }
2425     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2426     $params[] = $forum->id;
2428     if (!empty($CFG->forum_enabletimedposts)) {
2429         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2430         $params[] = $now;
2431         $params[] = $now;
2432     } else {
2433         $timedsql = "";
2434     }
2436     $sql = "SELECT COUNT(d.id)
2437               FROM {forum_discussions} d
2438              WHERE d.groupid $mygroups_sql AND d.forum = ?
2439                    $timedsql";
2441     return $DB->get_field_sql($sql, $params);
2444 /**
2445  * How many posts by other users are unrated by a given user in the given discussion?
2446  *
2447  * @global object
2448  * @global object
2449  * @param int $discussionid
2450  * @param int $userid
2451  * @return mixed
2452  */
2453 function forum_count_unrated_posts($discussionid, $userid) {
2454     global $CFG, $DB;
2455     if ($posts = $DB->get_record_sql("SELECT count(*) as num
2456                                    FROM {forum_posts}
2457                                   WHERE parent > 0
2458                                     AND discussion = ?
2459                                     AND userid <> ? ", array($discussionid, $userid))) {
2461         if ($rated = $DB->get_record_sql("SELECT count(*) as num
2462                                        FROM {forum_posts} p,
2463                                             {rating} r
2464                                       WHERE p.discussion = ?
2465                                         AND p.id = r.itemid
2466                                         AND r.userid = ?", array($discussionid, $userid))) {
2467             $difference = $posts->num - $rated->num;
2468             if ($difference > 0) {
2469                 return $difference;
2470             } else {
2471                 return 0;    // Just in case there was a counting error
2472             }
2473         } else {
2474             return $posts->num;
2475         }
2476     } else {
2477         return 0;
2478     }
2481 /**
2482  * Get all discussions in a forum
2483  *
2484  * @global object
2485  * @global object
2486  * @global object
2487  * @uses CONTEXT_MODULE
2488  * @uses VISIBLEGROUPS
2489  * @param object $cm
2490  * @param string $forumsort
2491  * @param bool $fullpost
2492  * @param int $unused
2493  * @param int $limit
2494  * @param bool $userlastmodified
2495  * @param int $page
2496  * @param int $perpage
2497  * @return array
2498  */
2499 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2500     global $CFG, $DB, $USER;
2502     $timelimit = '';
2504     $now = round(time(), -2);
2505     $params = array($cm->instance);
2507     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2509     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2510         return array();
2511     }
2513     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2515         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2516             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2517             $params[] = $now;
2518             $params[] = $now;
2519             if (isloggedin()) {
2520                 $timelimit .= " OR d.userid = ?";
2521                 $params[] = $USER->id;
2522             }
2523             $timelimit .= ")";
2524         }
2525     }
2527     if ($limit > 0) {
2528         $limitfrom = 0;
2529         $limitnum  = $limit;
2530     } else if ($page != -1) {
2531         $limitfrom = $page*$perpage;
2532         $limitnum  = $perpage;
2533     } else {
2534         $limitfrom = 0;
2535         $limitnum  = 0;
2536     }
2538     $groupmode    = groups_get_activity_groupmode($cm);
2539     $currentgroup = groups_get_activity_group($cm);
2541     if ($groupmode) {
2542         if (empty($modcontext)) {
2543             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2544         }
2546         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2547             if ($currentgroup) {
2548                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2549                 $params[] = $currentgroup;
2550             } else {
2551                 $groupselect = "";
2552             }
2554         } else {
2555             //seprate groups without access all
2556             if ($currentgroup) {
2557                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2558                 $params[] = $currentgroup;
2559             } else {
2560                 $groupselect = "AND d.groupid = -1";
2561             }
2562         }
2563     } else {
2564         $groupselect = "";
2565     }
2568     if (empty($forumsort)) {
2569         $forumsort = "d.timemodified DESC";
2570     }
2571     if (empty($fullpost)) {
2572         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2573     } else {
2574         $postdata = "p.*";
2575     }
2577     if (empty($userlastmodified)) {  // We don't need to know this
2578         $umfields = "";
2579         $umtable  = "";
2580     } else {
2581         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2582         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2583     }
2585     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2586                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2587               FROM {forum_discussions} d
2588                    JOIN {forum_posts} p ON p.discussion = d.id
2589                    JOIN {user} u ON p.userid = u.id
2590                    $umtable
2591              WHERE d.forum = ? AND p.parent = 0
2592                    $timelimit $groupselect
2593           ORDER BY $forumsort";
2594     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2597 /**
2598  *
2599  * @global object
2600  * @global object
2601  * @global object
2602  * @uses CONTEXT_MODULE
2603  * @uses VISIBLEGROUPS
2604  * @param object $cm
2605  * @return array
2606  */
2607 function forum_get_discussions_unread($cm) {
2608     global $CFG, $DB, $USER;
2610     $now = round(time(), -2);
2611     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2613     $params = array();
2614     $groupmode    = groups_get_activity_groupmode($cm);
2615     $currentgroup = groups_get_activity_group($cm);
2617     if ($groupmode) {
2618         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2620         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2621             if ($currentgroup) {
2622                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2623                 $params['currentgroup'] = $currentgroup;
2624             } else {
2625                 $groupselect = "";
2626             }
2628         } else {
2629             //separate groups without access all
2630             if ($currentgroup) {
2631                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2632                 $params['currentgroup'] = $currentgroup;
2633             } else {
2634                 $groupselect = "AND d.groupid = -1";
2635             }
2636         }
2637     } else {
2638         $groupselect = "";
2639     }
2641     if (!empty($CFG->forum_enabletimedposts)) {
2642         $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2643         $params['now1'] = $now;
2644         $params['now2'] = $now;
2645     } else {
2646         $timedsql = "";
2647     }
2649     $sql = "SELECT d.id, COUNT(p.id) AS unread
2650               FROM {forum_discussions} d
2651                    JOIN {forum_posts} p     ON p.discussion = d.id
2652                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2653              WHERE d.forum = {$cm->instance}
2654                    AND p.modified >= :cutoffdate AND r.id is NULL
2655                    $groupselect
2656                    $timedsql
2657           GROUP BY d.id";
2658     $params['cutoffdate'] = $cutoffdate;
2660     if ($unreads = $DB->get_records_sql($sql, $params)) {
2661         foreach ($unreads as $unread) {
2662             $unreads[$unread->id] = $unread->unread;
2663         }
2664         return $unreads;
2665     } else {
2666         return array();
2667     }
2670 /**
2671  * @global object
2672  * @global object
2673  * @global object
2674  * @uses CONEXT_MODULE
2675  * @uses VISIBLEGROUPS
2676  * @param object $cm
2677  * @return array
2678  */
2679 function forum_get_discussions_count($cm) {
2680     global $CFG, $DB, $USER;
2682     $now = round(time(), -2);
2683     $params = array($cm->instance);
2684     $groupmode    = groups_get_activity_groupmode($cm);
2685     $currentgroup = groups_get_activity_group($cm);
2687     if ($groupmode) {
2688         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2690         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2691             if ($currentgroup) {
2692                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2693                 $params[] = $currentgroup;
2694             } else {
2695                 $groupselect = "";
2696             }
2698         } else {
2699             //seprate groups without access all
2700             if ($currentgroup) {
2701                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2702                 $params[] = $currentgroup;
2703             } else {
2704                 $groupselect = "AND d.groupid = -1";
2705             }
2706         }
2707     } else {
2708         $groupselect = "";
2709     }
2711     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2713     $timelimit = "";
2715     if (!empty($CFG->forum_enabletimedposts)) {
2717         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2719         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2720             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2721             $params[] = $now;
2722             $params[] = $now;
2723             if (isloggedin()) {
2724                 $timelimit .= " OR d.userid = ?";
2725                 $params[] = $USER->id;
2726             }
2727             $timelimit .= ")";
2728         }
2729     }
2731     $sql = "SELECT COUNT(d.id)
2732               FROM {forum_discussions} d
2733                    JOIN {forum_posts} p ON p.discussion = d.id
2734              WHERE d.forum = ? AND p.parent = 0
2735                    $groupselect $timelimit";
2737     return $DB->get_field_sql($sql, $params);
2741 /**
2742  * Get all discussions started by a particular user in a course (or group)
2743  * This function no longer used ...
2744  *
2745  * @todo Remove this function if no longer used
2746  * @global object
2747  * @global object
2748  * @param int $courseid
2749  * @param int $userid
2750  * @param int $groupid
2751  * @return array
2752  */
2753 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2754     global $CFG, $DB;
2755     $params = array($courseid, $userid);
2756     if ($groupid) {
2757         $groupselect = " AND d.groupid = ? ";
2758         $params[] = $groupid;
2759     } else  {
2760         $groupselect = "";
2761     }
2763     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2764                                    f.type as forumtype, f.name as forumname, f.id as forumid
2765                               FROM {forum_discussions} d,
2766                                    {forum_posts} p,
2767                                    {user} u,
2768                                    {forum} f
2769                              WHERE d.course = ?
2770                                AND p.discussion = d.id
2771                                AND p.parent = 0
2772                                AND p.userid = u.id
2773                                AND u.id = ?
2774                                AND d.forum = f.id $groupselect
2775                           ORDER BY p.created DESC", $params);
2778 /**
2779  * Get the list of potential subscribers to a forum.
2780  *
2781  * @param object $forumcontext the forum context.
2782  * @param integer $groupid the id of a group, or 0 for all groups.
2783  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2784  * @param string $sort sort order. As for get_users_by_capability.
2785  * @return array list of users.
2786  */
2787 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2788     return get_users_by_capability($forumcontext, 'mod/forum:initialsubscriptions', $fields, $sort, '', '', $groupid, '', false, true);
2791 /**
2792  * Returns list of user objects that are subscribed to this forum
2793  *
2794  * @global object
2795  * @global object
2796  * @param object $course the course
2797  * @param forum $forum the forum
2798  * @param integer $groupid group id, or 0 for all.
2799  * @param object $context the forum context, to save re-fetching it where possible.
2800  * @return array list of users.
2801  */
2802 function forum_subscribed_users($course, $forum, $groupid=0, $context = NULL) {
2803     global $CFG, $DB;
2804     $params = array($forum->id);
2806     if ($groupid) {
2807         $grouptables = ", {groups_members} gm ";
2808         $groupselect = "AND gm.groupid = ? AND u.id = gm.userid";
2809         $params[] = $groupid;
2810     } else  {
2811         $grouptables = '';
2812         $groupselect = '';
2813     }
2815     $fields ="u.id,
2816               u.username,
2817               u.firstname,
2818               u.lastname,
2819               u.maildisplay,
2820               u.mailformat,
2821               u.maildigest,
2822               u.imagealt,
2823               u.email,
2824               u.city,
2825               u.country,
2826               u.lastaccess,
2827               u.lastlogin,
2828               u.picture,
2829               u.timezone,
2830               u.theme,
2831               u.lang,
2832               u.trackforums,
2833               u.mnethostid";
2835     if (forum_is_forcesubscribed($forum)) {
2836         if (empty($context)) {
2837             $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2838             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2839         }
2840         $sort = "u.email ASC";
2841         $results = forum_get_potential_subscribers($context, $groupid, $fields, $sort);
2842     } else {
2843         $results = $DB->get_records_sql("SELECT $fields
2844                               FROM {user} u,
2845                                    {forum_subscriptions} s $grouptables
2846                              WHERE s.forum = ?
2847                                AND s.userid = u.id
2848                                AND u.deleted = 0  $groupselect
2849                           ORDER BY u.email ASC", $params);
2850     }
2852     static $guestid = null;
2854     if (is_null($guestid)) {
2855         if ($guest = guest_user()) {
2856             $guestid = $guest->id;
2857         } else {
2858             $guestid = 0;
2859         }
2860     }
2862     // Guest user should never be subscribed to a forum.
2863     unset($results[$guestid]);
2865     return $results;
2870 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2873 /**
2874  * @global object
2875  * @global object
2876  * @param int $courseid
2877  * @param string $type
2878  */
2879 function forum_get_course_forum($courseid, $type) {
2880 // How to set up special 1-per-course forums
2881     global $CFG, $DB, $OUTPUT;
2883     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2884         // There should always only be ONE, but with the right combination of
2885         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2886         foreach ($forums as $forum) {
2887             return $forum;   // ie the first one
2888         }
2889     }
2891     // Doesn't exist, so create one now.
2892     $forum->course = $courseid;
2893     $forum->type = "$type";
2894     switch ($forum->type) {
2895         case "news":
2896             $forum->name  = get_string("namenews", "forum");
2897             $forum->intro = get_string("intronews", "forum");
2898             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2899             $forum->assessed = 0;
2900             if ($courseid == SITEID) {
2901                 $forum->name  = get_string("sitenews");
2902                 $forum->forcesubscribe = 0;
2903             }
2904             break;
2905         case "social":
2906             $forum->name  = get_string("namesocial", "forum");
2907             $forum->intro = get_string("introsocial", "forum");
2908             $forum->assessed = 0;
2909             $forum->forcesubscribe = 0;
2910             break;
2911         case "blog":
2912             $forum->name = get_string('blogforum', 'forum');
2913             $forum->intro = get_string('introblog', 'forum');
2914             $forum->assessed = 0;
2915             $forum->forcesubscribe = 0;
2916             break;
2917         default:
2918             echo $OUTPUT->notification("That forum type doesn't exist!");
2919             return false;
2920             break;
2921     }
2923     $forum->timemodified = time();
2924     $forum->id = $DB->insert_record("forum", $forum);
2926     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2927         echo $OUTPUT->notification("Could not find forum module!!");
2928         return false;
2929     }
2930     $mod = new stdClass();
2931     $mod->course = $courseid;
2932     $mod->module = $module->id;
2933     $mod->instance = $forum->id;
2934     $mod->section = 0;
2935     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
2936         echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
2937         return false;
2938     }
2939     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
2940         echo $OUTPUT->notification("Could not add the new course module to that section");
2941         return false;
2942     }
2943     $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule));
2945     include_once("$CFG->dirroot/course/lib.php");
2946     rebuild_course_cache($courseid);
2948     return $DB->get_record("forum", array("id" => "$forum->id"));
2952 /**
2953  * Given the data about a posting, builds up the HTML to display it and
2954  * returns the HTML in a string.  This is designed for sending via HTML email.
2955  *
2956  * @global object
2957  * @param object $course
2958  * @param object $cm
2959  * @param object $forum
2960  * @param object $discussion
2961  * @param object $post
2962  * @param object $userform
2963  * @param object $userto
2964  * @param bool $ownpost
2965  * @param bool $reply
2966  * @param bool $link
2967  * @param bool $rate
2968  * @param string $footer
2969  * @return string
2970  */
2971 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
2972                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
2974     global $CFG, $OUTPUT;
2976     if (!isset($userto->viewfullnames[$forum->id])) {
2977         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
2978             print_error('invalidcoursemodule');
2979         }
2980         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2981         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
2982     } else {
2983         $viewfullnames = $userto->viewfullnames[$forum->id];
2984     }
2986     // format the post body
2987     $options = new stdClass();
2988     $options->para = true;
2989     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
2991     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
2993     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
2994     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
2995     $output .= '</td>';
2997     if ($post->parent) {
2998         $output .= '<td class="topic">';
2999     } else {
3000         $output .= '<td class="topic starter">';
3001     }
3002     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3004     $fullname = fullname($userfrom, $viewfullnames);
3005     $by = new stdClass();
3006     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3007     $by->date = userdate($post->modified, '', $userto->timezone);
3008     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3010     $output .= '</td></tr>';
3012     $output .= '<tr><td class="left side" valign="top">';
3014     if (isset($userfrom->groups)) {
3015         $groups = $userfrom->groups[$forum->id];
3016     } else {
3017         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
3018             print_error('invalidcoursemodule');
3019         }
3020         $group = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3021     }
3023     if ($groups) {
3024         $output .= print_group_picture($groups, $course->id, false, true, true);
3025     } else {
3026         $output .= '&nbsp;';
3027     }
3029     $output .= '</td><td class="content">';
3031     $attachments = forum_print_attachments($post, $cm, 'html');
3032     if ($attachments !== '') {
3033         $output .= '<div class="attachments">';
3034         $output .= $attachments;
3035         $output .= '</div>';
3036     }
3038     $output .= $formattedtext;
3040 // Commands
3041     $commands = array();
3043     if ($post->parent) {
3044         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3045                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3046     }
3048     if ($reply) {
3049         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3050                       get_string('reply', 'forum').'</a>';
3051     }
3053     $output .= '<div class="commands">';
3054     $output .= implode(' | ', $commands);
3055     $output .= '</div>';
3057 // Context link to post if required
3058     if ($link) {
3059         $output .= '<div class="link">';
3060         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3061                      get_string('postincontext', 'forum').'</a>';
3062         $output .= '</div>';
3063     }
3065     if ($footer) {
3066         $output .= '<div class="footer">'.$footer.'</div>';
3067     }
3068     $output .= '</td></tr></table>'."\n\n";
3070     return $output;
3073 /**
3074  * Print a forum post
3075  *
3076  * @global object
3077  * @global object
3078  * @uses FORUM_MODE_THREADED
3079  * @uses PORTFOLIO_FORMAT_PLAINHTML
3080  * @uses PORTFOLIO_FORMAT_FILE
3081  * @uses PORTFOLIO_FORMAT_RICHHTML
3082  * @uses PORTFOLIO_ADD_TEXT_LINK
3083  * @uses CONTEXT_MODULE
3084  * @param object $post The post to print.
3085  * @param object $discussion
3086  * @param object $forum
3087  * @param object $cm
3088  * @param object $course
3089  * @param boolean $ownpost Whether this post belongs to the current user.
3090  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3091  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3092  * @param string $footer Extra stuff to print after the message.
3093  * @param string $highlight Space-separated list of terms to highlight.
3094  * @param int $post_read true, false or -99. If we already know whether this user
3095  *          has read this post, pass that in, otherwise, pass in -99, and this
3096  *          function will work it out.
3097  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3098  *          the current user can't see this post, if this argument is true
3099  *          (the default) then print a dummy 'you can't see this post' post.
3100  *          If false, don't output anything at all.
3101  * @param bool|null $istracked
3102  * @return void
3103  */
3104 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3105                           $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3106     global $USER, $CFG, $OUTPUT;
3108     // String cache
3109     static $str;
3111     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3113     $post->course = $course->id;
3114     $post->forum  = $forum->id;
3115     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3117     // caching
3118     if (!isset($cm->cache)) {
3119         $cm->cache = new stdClass;
3120     }
3122     if (!isset($cm->cache->caps)) {
3123         $cm->cache->caps = array();
3124         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3125         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3126         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3127         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3128         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3129         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3130         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3131         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3132         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3133     }
3135     if (!isset($cm->uservisible)) {
3136         $cm->uservisible = coursemodule_visible_for_user($cm);
3137     }
3139     if ($istracked && is_null($postisread)) {
3140         $postisread = forum_tp_is_post_read($USER->id, $post);
3141     }
3143     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3144         $output = '';
3145         if (!$dummyifcantsee) {
3146             if ($return) {
3147                 return $output;
3148             }
3149             echo $output;
3150             return;
3151         }
3152         $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3153         $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix '.$forumpostclass));
3154         $output .= html_writer::start_tag('div', array('class'=>'row header'));
3155         $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3156         if ($post->parent) {
3157             $output .= html_writer::start_tag('div', array('class'=>'topic'));
3158         } else {
3159             $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3160         }
3161         $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class'=>'subject')); // Subject
3162         $output .= html_writer::tag('div', get_string('forumauthorhidden','forum'), array('class'=>'author')); // author
3163         $output .= html_writer::end_tag('div'); // row
3164         $output .= html_writer::start_tag('div', array('class'=>'row'));
3165         $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3166         $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3167         $output .= html_writer::end_tag('div'); // row
3168         $output .= html_writer::end_tag('div'); // forumpost
3170         if ($return) {
3171             return $output;
3172         }
3173         echo $output;
3174         return;
3175     }
3177     if (empty($str)) {
3178         $str = new stdClass;
3179         $str->edit         = get_string('edit', 'forum');
3180         $str->delete       = get_string('delete', 'forum');
3181         $str->reply        = get_string('reply', 'forum');
3182         $str->parent       = get_string('parent', 'forum');
3183         $str->pruneheading = get_string('pruneheading', 'forum');
3184         $str->prune        = get_string('prune', 'forum');
3185         $str->displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3186         $str->markread     = get_string('markread', 'forum');
3187         $str->markunread   = get_string('markunread', 'forum');
3188     }
3190     $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3192     // Build an object that represents the posting user
3193     $postuser = new stdClass;
3194     $postuser->id        = $post->userid;
3195     $postuser->firstname = $post->firstname;
3196     $postuser->lastname  = $post->lastname;
3197     $postuser->imagealt  = $post->imagealt;
3198     $postuser->picture   = $post->picture;
3199     $postuser->email     = $post->email;
3200     // Some handy things for later on
3201     $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3202     $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3204     // Prepare the groups the posting user belongs to
3205     if (isset($cm->cache->usersgroups)) {
3206         $groups = array();
3207         if (isset($cm->cache->usersgroups[$post->userid])) {
3208             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3209                 $groups[$gid] = $cm->cache->groups[$gid];
3210             }
3211         }
3212     } else {
3213         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3214     }
3216     // Prepare the attachements for the post, files then images
3217     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3219     // Determine if we need to shorten this post
3220     $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3223     // Prepare an array of commands
3224     $commands = array();
3226     // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3227     // Don't display the mark read / unread controls in this case.
3228     if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3229         $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3230         $text = $str->markunread;
3231         if (!$postisread) {
3232             $url->param('mark', 'read');
3233             $text = $str->markread;
3234         }
3235         if ($str->displaymode == FORUM_MODE_THREADED) {
3236             $url->param('parent', $post->parent);
3237         } else {
3238             $url->set_anchor('p'.$post->id);
3239         }
3240         $commands[] = array('url'=>$url, 'text'=>$text);
3241     }
3243     // Zoom in to the parent specifically
3244     if ($post->parent) {
3245         $url = new moodle_url($discussionlink);
3246         if ($str->displaymode == FORUM_MODE_THREADED) {
3247             $url->param('parent', $post->parent);
3248         } else {
3249             $url->set_anchor('p'.$post->id);
3250         }
3251         $commands[] = array('url'=>$url, 'text'=>$str->parent);
3252     }
3254     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3255     $age = time() - $post->created;
3256     if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3257         $age = 0;
3258     }
3259     if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3260         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3261     }
3263     if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3264         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3265     }
3267     if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3268         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3269     }
3271     if ($reply) {
3272         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('reply'=>$post->id)), 'text'=>$str->reply);
3273     }
3275     if ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost'])) {
3276         $p = array('postid' => $post->id);
3277         require_once($CFG->libdir.'/portfoliolib.php');
3278         $button = new portfolio_add_button();
3279         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php');
3280         if (empty($attachments)) {
3281             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3282         } else {
3283             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3284         }
3286         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3287         if (!empty($porfoliohtml)) {
3288             $commands[] = $porfoliohtml;
3289         }
3290     }
3291     // Finished building commands
3294     // Begin output
3296     $output  = '';
3298     if ($istracked) {
3299         if ($postisread) {
3300             $forumpostclass = ' read';
3301         } else {
3302             $forumpostclass = ' unread';
3303             $output .= html_writer::tag('a', '', array('name'=>'unread'));
3304         }
3305     } else {
3306         // ignore trackign status if not tracked or tracked param missing
3307         $forumpostclass = '';
3308     }
3310     $topicclass = '';
3311     if (empty($post->parent)) {
3312         $topicclass = ' firstpost starter';
3313     }
3315     $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3316     $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass));
3317     $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3318     $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3319     $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3320     $output .= html_writer::end_tag('div');
3323     $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3325     $postsubject = $post->subject;
3326     if (empty($post->subjectnoformat)) {
3327         $postsubject = format_string($postsubject);
3328     }
3329     $output .= html_writer::tag('div', $postsubject, array('class'=>'subject'));
3331     $by = new stdClass();
3332     $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3333     $by->date = userdate($post->modified);
3334     $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author'));
3336     $output .= html_writer::end_tag('div'); //topic
3337     $output .= html_writer::end_tag('div'); //row
3339     $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3340     $output .= html_writer::start_tag('div', array('class'=>'left'));
3342     $groupoutput = '';
3343     if ($groups) {
3344         $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3345     }
3346     if (empty($groupoutput)) {
3347         $groupoutput = '&nbsp;';
3348     }
3349     $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3351     $output .= html_writer::end_tag('div'); //left side
3352     $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3353     $output .= html_writer::start_tag('div', array('class'=>'content'));
3354     if (!empty($attachments)) {
3355         $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3356     }
3358     $options = new stdClass;
3359     $options->para    = false;
3360     $options->trusted = $post->messagetrust;
3361     $options->context = $modcontext;
3362     if ($shortenpost) {
3363         // Prepare shortened version
3364         $postclass    = 'shortenedpost';
3365         $postcontent  = format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3366         $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
3367         $postcontent .= html_writer::tag('span', '('.get_string('numwords', 'moodle', count_words(strip_tags($post->message))).')...', array('class'=>'post-word-count'));
3368     } else {
3369         // Prepare whole post
3370         $postclass    = 'fullpost';
3371         $postcontent  = format_text($post->message, $post->messageformat, $options, $course->id);
3372         if (!empty($highlight)) {
3373             $postcontent = highlight($highlight, $postcontent);
3374         }
3375         $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
3376     }
3377     // Output the post content
3378     $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
3379     $output .= html_writer::end_tag('div'); // Content
3380     $output .= html_writer::end_tag('div'); // Content mask
3381     $output .= html_writer::end_tag('div'); // Row
3383     $output .= html_writer::start_tag('div', array('class'=>'row side'));
3384     $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
3385     $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
3387     // Output ratings
3388     if (!empty($post->rating)) {
3389         $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
3390     }
3392     // Output the commands
3393     $commandhtml = array();
3394     foreach ($commands as $command) {
3395         if (is_array($command)) {
3396             $commandhtml[] = html_writer::link($command['url'], $command['text']);
3397         } else {
3398             $commandhtml[] = $command;
3399         }
3400     }
3401     $output .= html_writer::tag('div', implode(' | ', $commandhtml), array('class'=>'commands'));
3403     // Output link to post if required
3404     if ($link) {
3405         if ($post->replies == 1) {
3406             $replystring = get_string('repliesone', 'forum', $post->replies);
3407         } else {
3408             $replystring = get_string('repliesmany', 'forum', $post->replies);
3409         }
3411         $output .= html_writer::start_tag('div', array('class'=>'link'));
3412         $output .= html_writer::link($discussionlink, get_string('discussthistopic', 'forum'));
3413         $output .= '&nbsp;('.$replystring.')';
3414         $output .= html_writer::end_tag('div'); // link
3415     }
3417     // Output footer if required
3418     if ($footer) {
3419         $output .= html_writer::tag('div', $footer, array('class'=>'footer'));
3420     }
3422     // Close remaining open divs
3423     $output .= html_writer::end_tag('div'); // content
3424     $output .= html_writer::end_tag('div'); // row
3425     $output .= html_writer::end_tag('div'); // forumpost
3427     // Mark the forum post as read if required
3428     if ($istracked && !$CFG->forum_usermarksread && !$postisread) {
3429         forum_tp_mark_post_read($USER->id, $post, $forum->id);
3430     }
3432     if ($return) {
3433         return $output;
3434     }
3435     echo $output;
3436     return;
3439 /**
3440  * Return rating related permissions
3441  * @param string $options the context id
3442  * @return array an associative array of the user's rating permissions
3443  */
3444 function forum_rating_permissions($contextid) {
3445     $context = get_context_instance_by_id($contextid);
3447     if (!$context) {
3448         print_error('invalidcontext');
3449         return null;
3450     } else {
3451         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));
3452     }
3455 /**
3456  * Returns the names of the table and columns necessary to check items for ratings
3457  * @return array an array containing the item table, item id and user id columns
3458  */
3459 function forum_rating_item_check_info() {
3460     return array('forum_posts','id','userid');
3464 /**
3465  * This function prints the overview of a discussion in the forum listing.
3466  * It needs some discussion information and some post information, these
3467  * happen to be combined for efficiency in the $post parameter by the function
3468  * that calls this one: forum_print_latest_discussions()
3469  *
3470  * @global object
3471  * @global object
3472  * @param object $post The post object (passed by reference for speed).
3473  * @param object $forum The forum object.
3474  * @param int $group Current group.
3475  * @param string $datestring Format to use for the dates.
3476  * @param boolean $cantrack Is tracking enabled for this forum.
3477  * @param boolean $forumtracked Is the user tracking this forum.
3478  * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
3479  */
3480 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
3481                                         $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) {
3483     global $USER, $CFG, $OUTPUT;
3485     static $rowcount;
3486     static $strmarkalldread;
3488     if (empty($modcontext)) {
3489         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3490             print_error('invalidcoursemodule');
3491         }
3492         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3493     }
3495     if (!isset($rowcount)) {
3496         $rowcount = 0;
3497         $strmarkalldread = get_string('markalldread', 'forum');
3498     } else {
3499         $rowcount = ($rowcount + 1) % 2;
3500     }
3502     $post->subject = format_string($post->subject,true);
3504     echo "\n\n";