fixed broken ratings fetching
[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 object();
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 object();
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                 forum_add_discussion($discussion, null, $message);
174                 if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
175                     print_error('cannotadd', 'forum');
176                 }
177             }
178         }
179         if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
180             print_error('cannotfindfirstpost', 'forum');
181         }
183         $cm         = get_coursemodule_from_instance('forum', $forum->id);
184         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
186         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
187             // ugly hack - we need to copy the files somehow
188             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
189             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
191             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
192         }
194         $post->subject       = $forum->name;
195         $post->message       = $forum->intro;
196         $post->messageformat = $forum->introformat;
197         $post->messagetrust  = trusttext_trusted($modcontext);
198         $post->modified      = $forum->timemodified;
199         $post->userid        = $USER->id;    // MDL-18599, so that current teacher can take ownership of activities
201         $DB->update_record('forum_posts', $post);
202         $discussion->name = $forum->name;
203         $DB->update_record('forum_discussions', $discussion);
204     }
206     $DB->update_record('forum', $forum);
208     forum_grade_item_update($forum);
210     return true;
214 /**
215  * Given an ID of an instance of this module,
216  * this function will permanently delete the instance
217  * and any data that depends on it.
218  *
219  * @global object
220  * @param int $id forum instance id
221  * @return bool success
222  */
223 function forum_delete_instance($id) {
224     global $DB;
226     if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
227         return false;
228     }
229     if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
230         return false;
231     }
232     if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
233         return false;
234     }
236     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
238     // now get rid of all files
239     $fs = get_file_storage();
240     $fs->delete_area_files($context->id);
242     $result = true;
244     if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
245         foreach ($discussions as $discussion) {
246             if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
247                 $result = false;
248             }
249         }
250     }
252     if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
253         $result = false;
254     }
256     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
258     if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
259         $result = false;
260     }
262     forum_grade_item_delete($forum);
264     return $result;
268 /**
269  * Indicates API features that the forum supports.
270  *
271  * @uses FEATURE_GROUPS
272  * @uses FEATURE_GROUPINGS
273  * @uses FEATURE_GROUPMEMBERSONLY
274  * @uses FEATURE_MOD_INTRO
275  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
276  * @uses FEATURE_COMPLETION_HAS_RULES
277  * @uses FEATURE_GRADE_HAS_GRADE
278  * @uses FEATURE_GRADE_OUTCOMES
279  * @param string $feature
280  * @return mixed True if yes (some features may use other values)
281  */
282 function forum_supports($feature) {
283     switch($feature) {
284         case FEATURE_GROUPS:                  return true;
285         case FEATURE_GROUPINGS:               return true;
286         case FEATURE_GROUPMEMBERSONLY:        return true;
287         case FEATURE_MOD_INTRO:               return true;
288         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
289         case FEATURE_COMPLETION_HAS_RULES:    return true;
290         case FEATURE_GRADE_HAS_GRADE:         return true;
291         case FEATURE_GRADE_OUTCOMES:          return true;
292         case FEATURE_RATE:                    return true;
293         case FEATURE_BACKUP_MOODLE2:          return true;
295         default: return null;
296     }
300 /**
301  * Obtains the automatic completion state for this forum based on any conditions
302  * in forum settings.
303  *
304  * @global object
305  * @global object
306  * @param object $course Course
307  * @param object $cm Course-module
308  * @param int $userid User ID
309  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
310  * @return bool True if completed, false if not. (If no conditions, then return
311  *   value depends on comparison type)
312  */
313 function forum_get_completion_state($course,$cm,$userid,$type) {
314     global $CFG,$DB;
316     // Get forum details
317     if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
318         throw new Exception("Can't find forum {$cm->instance}");
319     }
321     $result=$type; // Default return value
323     $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
324     $postcountsql="
325 SELECT
326     COUNT(1)
327 FROM
328     {forum_posts} fp
329     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
330 WHERE
331     fp.userid=:userid AND fd.forum=:forumid";
333     if ($forum->completiondiscussions) {
334         $value = $forum->completiondiscussions <=
335                  $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
336         if ($type == COMPLETION_AND) {
337             $result = $result && $value;
338         } else {
339             $result = $result || $value;
340         }
341     }
342     if ($forum->completionreplies) {
343         $value = $forum->completionreplies <=
344                  $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
345         if ($type==COMPLETION_AND) {
346             $result = $result && $value;
347         } else {
348             $result = $result || $value;
349         }
350     }
351     if ($forum->completionposts) {
352         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
353         if ($type == COMPLETION_AND) {
354             $result = $result && $value;
355         } else {
356             $result = $result || $value;
357         }
358     }
360     return $result;
364 /**
365  * Function to be run periodically according to the moodle cron
366  * Finds all posts that have yet to be mailed out, and mails them
367  * out to all subscribers
368  *
369  * @global object
370  * @global object
371  * @global object
372  * @uses CONTEXT_MODULE
373  * @uses CONTEXT_COURSE
374  * @uses SITEID
375  * @uses FORMAT_PLAIN
376  * @return void
377  */
378 function forum_cron() {
379     global $CFG, $USER, $DB;
381     $site = get_site();
383     // all users that are subscribed to any post that needs sending
384     $users = array();
386     // status arrays
387     $mailcount  = array();
388     $errorcount = array();
390     // caches
391     $discussions     = array();
392     $forums          = array();
393     $courses         = array();
394     $coursemodules   = array();
395     $subscribedusers = array();
398     // Posts older than 2 days will not be mailed.  This is to avoid the problem where
399     // cron has not been running for a long time, and then suddenly people are flooded
400     // with mail from the past few weeks or months
401     $timenow   = time();
402     $endtime   = $timenow - $CFG->maxeditingtime;
403     $starttime = $endtime - 48 * 3600;   // Two days earlier
405     if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
406         // Mark them all now as being mailed.  It's unlikely but possible there
407         // might be an error later so that a post is NOT actually mailed out,
408         // but since mail isn't crucial, we can accept this risk.  Doing it now
409         // prevents the risk of duplicated mails, which is a worse problem.
411         if (!forum_mark_old_posts_as_mailed($endtime)) {
412             mtrace('Errors occurred while trying to mark some posts as being mailed.');
413             return false;  // Don't continue trying to mail them, in case we are in a cron loop
414         }
416         // checking post validity, and adding users to loop through later
417         foreach ($posts as $pid => $post) {
419             $discussionid = $post->discussion;
420             if (!isset($discussions[$discussionid])) {
421                 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
422                     $discussions[$discussionid] = $discussion;
423                 } else {
424                     mtrace('Could not find discussion '.$discussionid);
425                     unset($posts[$pid]);
426                     continue;
427                 }
428             }
429             $forumid = $discussions[$discussionid]->forum;
430             if (!isset($forums[$forumid])) {
431                 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
432                     $forums[$forumid] = $forum;
433                 } else {
434                     mtrace('Could not find forum '.$forumid);
435                     unset($posts[$pid]);
436                     continue;
437                 }
438             }
439             $courseid = $forums[$forumid]->course;
440             if (!isset($courses[$courseid])) {
441                 if ($course = $DB->get_record('course', array('id' => $courseid))) {
442                     $courses[$courseid] = $course;
443                 } else {
444                     mtrace('Could not find course '.$courseid);
445                     unset($posts[$pid]);
446                     continue;
447                 }
448             }
449             if (!isset($coursemodules[$forumid])) {
450                 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
451                     $coursemodules[$forumid] = $cm;
452                 } else {
453                     mtrace('Could not course module for forum '.$forumid);
454                     unset($posts[$pid]);
455                     continue;
456                 }
457             }
460             // caching subscribed users of each forum
461             if (!isset($subscribedusers[$forumid])) {
462                 $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
463                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext)) {
464                     foreach ($subusers as $postuser) {
465                         // do not try to mail users with stopped email
466                         if ($postuser->emailstop) {
467                             if (!empty($CFG->forum_logblocked)) {
468                                 add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
469                             }
470                             continue;
471                         }
472                         // this user is subscribed to this forum
473                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
474                         // this user is a user we have to process later
475                         $users[$postuser->id] = $postuser;
476                     }
477                     unset($subusers); // release memory
478                 }
479             }
481             $mailcount[$pid] = 0;
482             $errorcount[$pid] = 0;
483         }
484     }
486     if ($users && $posts) {
488         $urlinfo = parse_url($CFG->wwwroot);
489         $hostname = $urlinfo['host'];
491         foreach ($users as $userto) {
493             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
495             // set this so that the capabilities are cached, and environment matches receiving user
496             cron_setup_user($userto);
498             mtrace('Processing user '.$userto->id);
500             // init caches
501             $userto->viewfullnames = array();
502             $userto->canpost       = array();
503             $userto->markposts     = array();
504             $userto->enrolledin    = array();
506             // reset the caches
507             foreach ($coursemodules as $forumid=>$unused) {
508                 $coursemodules[$forumid]->cache       = new object();
509                 $coursemodules[$forumid]->cache->caps = array();
510                 unset($coursemodules[$forumid]->uservisible);
511             }
513             foreach ($posts as $pid => $post) {
515                 // Set up the environment for the post, discussion, forum, course
516                 $discussion = $discussions[$post->discussion];
517                 $forum      = $forums[$discussion->forum];
518                 $course     = $courses[$forum->course];
519                 $cm         =& $coursemodules[$forum->id];
521                 // Do some checks  to see if we can bail out now
522                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
523                     continue; // user does not subscribe to this forum
524                 }
526                 // Verify user is enrollend in course - if not do not send any email
527                 if (!isset($userto->enrolledin[$course->id])) {
528                     $userto->enrolledin[$course->id] = is_enrolled(get_context_instance(CONTEXT_COURSE, $course->id));
529                 }
530                 if (!$userto->enrolledin[$course->id]) {
531                     // oops - this user should not receive anything from this course
532                     continue;
533                 }
535                 // Get info about the sending user
536                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
537                     $userfrom = $users[$post->userid];
538                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
539                     $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
540                 } else {
541                     mtrace('Could not find user '.$post->userid);
542                     continue;
543                 }
545                 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
547                 // setup global $COURSE properly - needed for roles and languages
548                 cron_setup_user($userto, $course);
550                 // Fill caches
551                 if (!isset($userto->viewfullnames[$forum->id])) {
552                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
553                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
554                 }
555                 if (!isset($userto->canpost[$discussion->id])) {
556                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
557                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
558                 }
559                 if (!isset($userfrom->groups[$forum->id])) {
560                     if (!isset($userfrom->groups)) {
561                         $userfrom->groups = array();
562                         $users[$userfrom->id]->groups = array();
563                     }
564                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
565                     $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
566                 }
568                 // Make sure groups allow this user to see this email
569                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
570                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
571                         continue;                           // Be safe and don't send it to anyone
572                     }
574                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
575                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
576                         continue;
577                     }
578                 }
580                 // Make sure we're allowed to see it...
581                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
582                     mtrace('user '.$userto->id. ' can not see '.$post->id);
583                     continue;
584                 }
586                 // OK so we need to send the email.
588                 // Does the user want this post in a digest?  If so postpone it for now.
589                 if ($userto->maildigest > 0) {
590                     // This user wants the mails to be in digest form
591                     $queue = new object();
592                     $queue->userid       = $userto->id;
593                     $queue->discussionid = $discussion->id;
594                     $queue->postid       = $post->id;
595                     $queue->timemodified = $post->created;
596                     $DB->insert_record('forum_queue', $queue);
597                     continue;
598                 }
601                 // Prepare to actually send the post now, and build up the content
603                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
605                 $userfrom->customheaders = array (  // Headers to make emails easier to track
606                            'Precedence: Bulk',
607                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
608                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
609                            'Message-ID: <moodlepost'.$post->id.'@'.$hostname.'>',
610                            'X-Course-Id: '.$course->id,
611                            'X-Course-Name: '.format_string($course->fullname, true)
612                 );
614                 if ($post->parent) {  // This post is a reply, so add headers for threading (see MDL-22551)
615                     $userfrom->customheaders[] = 'In-Reply-To: <moodlepost'.$post->parent.'@'.$hostname.'>';
616                     $userfrom->customheaders[] = 'References: <moodlepost'.$post->parent.'@'.$hostname.'>';
617                 }
619                 $postsubject = "$course->shortname: ".format_string($post->subject,true);
620                 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
621                 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
623                 // Send the post now!
625                 mtrace('Sending ', '');
627                 $eventdata = new object();
628                 $eventdata->component        = 'mod_forum';
629                 $eventdata->name             = 'posts';
630                 $eventdata->userfrom         = $userfrom;
631                 $eventdata->userto           = $userto;
632                 $eventdata->subject          = $postsubject;
633                 $eventdata->fullmessage      = $posttext;
634                 $eventdata->fullmessageformat = FORMAT_PLAIN;
635                 $eventdata->fullmessagehtml  = $posthtml;
636                 $eventdata->smallmessage     = '';
638                 $mailresult = message_send($eventdata);
639                 if (!$mailresult){
640                     mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
641                          " ($userto->email) .. not trying again.");
642                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
643                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
644                     $errorcount[$post->id]++;
645                 } else if ($mailresult === 'emailstop') {
646                     // should not be reached anymore - see check above
647                     mtrace("Error: mod/forum/lib.php forum_cron(): received 'emailstop' while sending out mail for id $post->id to user $userto->id ($userto->email)");
648                 } else {
649                     $mailcount[$post->id]++;
651                 // Mark post as read if forum_usermarksread is set off
652                     if (!$CFG->forum_usermarksread) {
653                         $userto->markposts[$post->id] = $post->id;
654                     }
655                 }
657                 mtrace('post '.$post->id. ': '.$post->subject);
658             }
660             // mark processed posts as read
661             forum_tp_mark_posts_read($userto, $userto->markposts);
662         }
663     }
665     if ($posts) {
666         foreach ($posts as $post) {
667             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
668             if ($errorcount[$post->id]) {
669                 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
670             }
671         }
672     }
674     // release some memory
675     unset($subscribedusers);
676     unset($mailcount);
677     unset($errorcount);
679     cron_setup_user();
681     $sitetimezone = $CFG->timezone;
683     // Now see if there are any digest mails waiting to be sent, and if we should send them
685     mtrace('Starting digest processing...');
687     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
689     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
690         set_config('digestmailtimelast', 0);
691     }
693     $timenow = time();
694     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
696     // Delete any really old ones (normally there shouldn't be any)
697     $weekago = $timenow - (7 * 24 * 3600);
698     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
699     mtrace ('Cleaned old digest records');
701     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
703         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
705         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
707         if ($digestposts_rs->valid()) {
709             // We have work to do
710             $usermailcount = 0;
712             //caches - reuse the those filled before too
713             $discussionposts = array();
714             $userdiscussions = array();
716             foreach ($digestposts_rs as $digestpost) {
717                 if (!isset($users[$digestpost->userid])) {
718                     if ($user = $DB->get_record('user', array('id' => $digestpost->userid))) {
719                         $users[$digestpost->userid] = $user;
720                     } else {
721                         continue;
722                     }
723                 }
724                 $postuser = $users[$digestpost->userid];
725                 if ($postuser->emailstop) {
726                     if (!empty($CFG->forum_logblocked)) {
727                         add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
728                     }
729                     continue;
730                 }
732                 if (!isset($posts[$digestpost->postid])) {
733                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
734                         $posts[$digestpost->postid] = $post;
735                     } else {
736                         continue;
737                     }
738                 }
739                 $discussionid = $digestpost->discussionid;
740                 if (!isset($discussions[$discussionid])) {
741                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
742                         $discussions[$discussionid] = $discussion;
743                     } else {
744                         continue;
745                     }
746                 }
747                 $forumid = $discussions[$discussionid]->forum;
748                 if (!isset($forums[$forumid])) {
749                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
750                         $forums[$forumid] = $forum;
751                     } else {
752                         continue;
753                     }
754                 }
756                 $courseid = $forums[$forumid]->course;
757                 if (!isset($courses[$courseid])) {
758                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
759                         $courses[$courseid] = $course;
760                     } else {
761                         continue;
762                     }
763                 }
765                 if (!isset($coursemodules[$forumid])) {
766                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
767                         $coursemodules[$forumid] = $cm;
768                     } else {
769                         continue;
770                     }
771                 }
772                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
773                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
774             }
775             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
777             // Data collected, start sending out emails to each user
778             foreach ($userdiscussions as $userid => $thesediscussions) {
780                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
782                 cron_setup_user();
784                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
786                 // First of all delete all the queue entries for this user
787                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
788                 $userto = $users[$userid];
790                 // Override the language and timezone of the "current" user, so that
791                 // mail is customised for the receiver.
792                 cron_setup_user($userto);
794                 // init caches
795                 $userto->viewfullnames = array();
796                 $userto->canpost       = array();
797                 $userto->markposts     = array();
799                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
801                 $headerdata = new object();
802                 $headerdata->sitename = format_string($site->fullname, true);
803                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
805                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
806                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
808                 $posthtml = "<head>";
809 /*                foreach ($CFG->stylesheets as $stylesheet) {
810                     //TODO: MDL-21120
811                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
812                 }*/
813                 $posthtml .= "</head>\n<body id=\"email\">\n";
814                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
816                 foreach ($thesediscussions as $discussionid) {
818                     @set_time_limit(120);   // to be reset for each post
820                     $discussion = $discussions[$discussionid];
821                     $forum      = $forums[$discussion->forum];
822                     $course     = $courses[$forum->course];
823                     $cm         = $coursemodules[$forum->id];
825                     //override language
826                     cron_setup_user($userto, $course);
828                     // Fill caches
829                     if (!isset($userto->viewfullnames[$forum->id])) {
830                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
831                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
832                     }
833                     if (!isset($userto->canpost[$discussion->id])) {
834                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
835                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
836                     }
838                     $strforums      = get_string('forums', 'forum');
839                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
840                     $canreply       = $userto->canpost[$discussion->id];
842                     $posttext .= "\n \n";
843                     $posttext .= '=====================================================================';
844                     $posttext .= "\n \n";
845                     $posttext .= "$course->shortname -> $strforums -> ".format_string($forum->name,true);
846                     if ($discussion->name != $forum->name) {
847                         $posttext  .= " -> ".format_string($discussion->name,true);
848                     }
849                     $posttext .= "\n";
851                     $posthtml .= "<p><font face=\"sans-serif\">".
852                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> -> ".
853                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
854                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
855                     if ($discussion->name == $forum->name) {
856                         $posthtml .= "</font></p>";
857                     } else {
858                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
859                     }
860                     $posthtml .= '<p>';
862                     $postsarray = $discussionposts[$discussionid];
863                     sort($postsarray);
865                     foreach ($postsarray as $postid) {
866                         $post = $posts[$postid];
868                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
869                             $userfrom = $users[$post->userid];
870                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
871                             $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
872                         } else {
873                             mtrace('Could not find user '.$post->userid);
874                             continue;
875                         }
877                         if (!isset($userfrom->groups[$forum->id])) {
878                             if (!isset($userfrom->groups)) {
879                                 $userfrom->groups = array();
880                                 $users[$userfrom->id]->groups = array();
881                             }
882                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
883                             $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
884                         }
886                         $userfrom->customheaders = array ("Precedence: Bulk");
888                         if ($userto->maildigest == 2) {
889                             // Subjects only
890                             $by = new object();
891                             $by->name = fullname($userfrom);
892                             $by->date = userdate($post->modified);
893                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
894                             $posttext .= "\n---------------------------------------------------------------------";
896                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
897                             $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>';
899                         } else {
900                             // The full treatment
901                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
902                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
904                         // Create an array of postid's for this user to mark as read.
905                             if (!$CFG->forum_usermarksread) {
906                                 $userto->markposts[$post->id] = $post->id;
907                             }
908                         }
909                     }
910                     if ($canunsubscribe) {
911                         $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>";
912                     } else {
913                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
914                     }
915                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
916                 }
917                 $posthtml .= '</body>';
919                 if (empty($userto->mailformat) || $userto->mailformat != 1) {
920                     // This user DOESN'T want to receive HTML
921                     $posthtml = '';
922                 }
924                 $attachment = $attachname='';
925                 $usetrueaddress = true;
926                 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname, $usetrueaddress, $CFG->forum_replytouser);
928                 if (!$mailresult) {
929                     mtrace("ERROR!");
930                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
931                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
932                 } else if ($mailresult === 'emailstop') {
933                     // should not happen anymore - see check above
934                 } else {
935                     mtrace("success.");
936                     $usermailcount++;
938                     // Mark post as read if forum_usermarksread is set off
939                     forum_tp_mark_posts_read($userto, $userto->markposts);
940                 }
941             }
942         }
943     /// We have finishied all digest emails, update $CFG->digestmailtimelast
944         set_config('digestmailtimelast', $timenow);
945     }
947     cron_setup_user();
949     if (!empty($usermailcount)) {
950         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
951     }
953     if (!empty($CFG->forum_lastreadclean)) {
954         $timenow = time();
955         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
956             set_config('forum_lastreadclean', $timenow);
957             mtrace('Removing old forum read tracking info...');
958             forum_tp_clean_read_records();
959         }
960     } else {
961         set_config('forum_lastreadclean', time());
962     }
965     return true;
968 /**
969  * Builds and returns the body of the email notification in plain text.
970  *
971  * @global object
972  * @global object
973  * @uses CONTEXT_MODULE
974  * @param object $course
975  * @param object $cm
976  * @param object $forum
977  * @param object $discussion
978  * @param object $post
979  * @param object $userfrom
980  * @param object $userto
981  * @param boolean $bare
982  * @return string The email body in plain text format.
983  */
984 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
985     global $CFG, $USER;
987     if (!isset($userto->viewfullnames[$forum->id])) {
988         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
989         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
990     } else {
991         $viewfullnames = $userto->viewfullnames[$forum->id];
992     }
994     if (!isset($userto->canpost[$discussion->id])) {
995         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
996         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
997     } else {
998         $canreply = $userto->canpost[$discussion->id];
999     }
1001     $by = New stdClass;
1002     $by->name = fullname($userfrom, $viewfullnames);
1003     $by->date = userdate($post->modified, "", $userto->timezone);
1005     $strbynameondate = get_string('bynameondate', 'forum', $by);
1007     $strforums = get_string('forums', 'forum');
1009     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1011     $posttext = '';
1013     if (!$bare) {
1014         $posttext  = "$course->shortname -> $strforums -> ".format_string($forum->name,true);
1016         if ($discussion->name != $forum->name) {
1017             $posttext  .= " -> ".format_string($discussion->name,true);
1018         }
1019     }
1021     $posttext .= "\n---------------------------------------------------------------------\n";
1022     $posttext .= format_string($post->subject,true);
1023     if ($bare) {
1024         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1025     }
1026     $posttext .= "\n".$strbynameondate."\n";
1027     $posttext .= "---------------------------------------------------------------------\n";
1028     $posttext .= format_text_email($post->message, $post->messageformat);
1029     $posttext .= "\n\n";
1030     $posttext .= forum_print_attachments($post, $cm, "text");
1032     if (!$bare && $canreply) {
1033         $posttext .= "---------------------------------------------------------------------\n";
1034         $posttext .= get_string("postmailinfo", "forum", $course->shortname)."\n";
1035         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1036     }
1037     if (!$bare && $canunsubscribe) {
1038         $posttext .= "\n---------------------------------------------------------------------\n";
1039         $posttext .= get_string("unsubscribe", "forum");
1040         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1041     }
1043     return $posttext;
1046 /**
1047  * Builds and returns the body of the email notification in html format.
1048  *
1049  * @global object
1050  * @param object $course
1051  * @param object $cm
1052  * @param object $forum
1053  * @param object $discussion
1054  * @param object $post
1055  * @param object $userfrom
1056  * @param object $userto
1057  * @return string The email text in HTML format
1058  */
1059 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1060     global $CFG;
1062     if ($userto->mailformat != 1) {  // Needs to be HTML
1063         return '';
1064     }
1066     if (!isset($userto->canpost[$discussion->id])) {
1067         $canreply = forum_user_can_post($forum, $discussion, $userto);
1068     } else {
1069         $canreply = $userto->canpost[$discussion->id];
1070     }
1072     $strforums = get_string('forums', 'forum');
1073     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1075     $posthtml = '<head>';
1076 /*    foreach ($CFG->stylesheets as $stylesheet) {
1077         //TODO: MDL-21120
1078         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1079     }*/
1080     $posthtml .= '</head>';
1081     $posthtml .= "\n<body id=\"email\">\n\n";
1083     $posthtml .= '<div class="navbar">'.
1084     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$course->shortname.'</a> &raquo; '.
1085     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1086     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1087     if ($discussion->name == $forum->name) {
1088         $posthtml .= '</div>';
1089     } else {
1090         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1091                      format_string($discussion->name,true).'</a></div>';
1092     }
1093     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1095     if ($canunsubscribe) {
1096         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1097                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1098                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1099     }
1101     $posthtml .= '</body>';
1103     return $posthtml;
1107 /**
1108  *
1109  * @param object $course
1110  * @param object $user
1111  * @param object $mod TODO this is not used in this function, refactor
1112  * @param object $forum
1113  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1114  */
1115 function forum_user_outline($course, $user, $mod, $forum) {
1116     global $CFG;
1117     require_once("$CFG->libdir/gradelib.php");
1118     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1119     if (empty($grades->items[0]->grades)) {
1120         $grade = false;
1121     } else {
1122         $grade = reset($grades->items[0]->grades);
1123     }
1125     $count = forum_count_user_posts($forum->id, $user->id);
1127     if ($count && $count->postcount > 0) {
1128         $result = new object();
1129         $result->info = get_string("numposts", "forum", $count->postcount);
1130         $result->time = $count->lastpost;
1131         if ($grade) {
1132             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1133         }
1134         return $result;
1135     } else if ($grade) {
1136         $result = new object();
1137         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1138         $result->time = $grade->dategraded;
1139         return $result;
1140     }
1141     return NULL;
1145 /**
1146  * @global object
1147  * @global object
1148  * @param object $coure
1149  * @param object $user
1150  * @param object $mod
1151  * @param object $forum
1152  */
1153 function forum_user_complete($course, $user, $mod, $forum) {
1154     global $CFG,$USER, $OUTPUT;
1155     require_once("$CFG->libdir/gradelib.php");
1157     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1158     if (!empty($grades->items[0]->grades)) {
1159         $grade = reset($grades->items[0]->grades);
1160         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1161         if ($grade->str_feedback) {
1162             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1163         }
1164     }
1166     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1168         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1169             print_error('invalidcoursemodule');
1170         }
1171         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1173         foreach ($posts as $post) {
1174             if (!isset($discussions[$post->discussion])) {
1175                 continue;
1176             }
1177             $discussion = $discussions[$post->discussion];
1179             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1180         }
1181     } else {
1182         echo "<p>".get_string("noposts", "forum")."</p>";
1183     }
1191 /**
1192  * @global object
1193  * @global object
1194  * @global object
1195  * @param array $courses
1196  * @param array $htmlarray
1197  */
1198 function forum_print_overview($courses,&$htmlarray) {
1199     global $USER, $CFG, $DB, $SESSION;
1201     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1202         return array();
1203     }
1205     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1206         return;
1207     }
1210     // get all forum logs in ONE query (much better!)
1211     $params = array();
1212     $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {log} l "
1213         ." JOIN {course_modules} cm ON cm.id = cmid "
1214         ." WHERE (";
1215     foreach ($courses as $course) {
1216         $sql .= '(l.course = ? AND l.time > ?) OR ';
1217         $params[] = $course->id;
1218         $params[] = $course->lastaccess;
1219     }
1220     $sql = substr($sql,0,-3); // take off the last OR
1222     $sql .= ") AND l.module = 'forum' AND action = 'add post' "
1223         ." AND userid != ? GROUP BY cmid,l.course,instance";
1225     $params[] = $USER->id;
1227     if (!$new = $DB->get_records_sql($sql, $params)) {
1228         $new = array(); // avoid warnings
1229     }
1231     // also get all forum tracking stuff ONCE.
1232     $trackingforums = array();
1233     foreach ($forums as $forum) {
1234         if (forum_tp_can_track_forums($forum)) {
1235             $trackingforums[$forum->id] = $forum;
1236         }
1237     }
1239     if (count($trackingforums) > 0) {
1240         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1241         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1242             ' FROM {forum_posts} p '.
1243             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1244             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1245         $params = array($USER->id);
1247         foreach ($trackingforums as $track) {
1248             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1249             $params[] = $track->id;
1250             if (isset($SESSION->currentgroup[$track->course])) {
1251                 $groupid =  $SESSION->currentgroup[$track->course];
1252             } else {
1253                 $groupid = groups_get_all_groups($track->course, $USER->id);
1254                 if (is_array($groupid)) {
1255                     $groupid = array_shift(array_keys($groupid));
1256                     $SESSION->currentgroup[$track->course] = $groupid;
1257                 } else {
1258                     $groupid = 0;
1259                 }
1260             }
1261             $params[] = $groupid;
1262         }
1263         $sql = substr($sql,0,-3); // take off the last OR
1264         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1265         $params[] = $cutoffdate;
1267         if (!$unread = $DB->get_records_sql($sql, $params)) {
1268             $unread = array();
1269         }
1270     } else {
1271         $unread = array();
1272     }
1274     if (empty($unread) and empty($new)) {
1275         return;
1276     }
1278     $strforum = get_string('modulename','forum');
1279     $strnumunread = get_string('overviewnumunread','forum');
1280     $strnumpostssince = get_string('overviewnumpostssince','forum');
1282     foreach ($forums as $forum) {
1283         $str = '';
1284         $count = 0;
1285         $thisunread = 0;
1286         $showunread = false;
1287         // either we have something from logs, or trackposts, or nothing.
1288         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1289             $count = $new[$forum->id]->count;
1290         }
1291         if (array_key_exists($forum->id,$unread)) {
1292             $thisunread = $unread[$forum->id]->count;
1293             $showunread = true;
1294         }
1295         if ($count > 0 || $thisunread > 0) {
1296             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1297                 $forum->name.'</a></div>';
1298             $str .= '<div class="info">';
1299             $str .= $count.' '.$strnumpostssince;
1300             if (!empty($showunread)) {
1301                 $str .= '<br />'.$thisunread .' '.$strnumunread;
1302             }
1303             $str .= '</div></div>';
1304         }
1305         if (!empty($str)) {
1306             if (!array_key_exists($forum->course,$htmlarray)) {
1307                 $htmlarray[$forum->course] = array();
1308             }
1309             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1310                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1311             }
1312             $htmlarray[$forum->course]['forum'] .= $str;
1313         }
1314     }
1317 /**
1318  * Given a course and a date, prints a summary of all the new
1319  * messages posted in the course since that date
1320  *
1321  * @global object
1322  * @global object
1323  * @global object
1324  * @uses CONTEXT_MODULE
1325  * @uses VISIBLEGROUPS
1326  * @param object $course
1327  * @param bool $viewfullnames capability
1328  * @param int $timestart
1329  * @return bool success
1330  */
1331 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1332     global $CFG, $USER, $DB, $OUTPUT;
1334     // do not use log table if possible, it may be huge and is expensive to join with other tables
1336     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1337                                               d.timestart, d.timeend, d.userid AS duserid,
1338                                               u.firstname, u.lastname, u.email, u.picture
1339                                          FROM {forum_posts} p
1340                                               JOIN {forum_discussions} d ON d.id = p.discussion
1341                                               JOIN {forum} f             ON f.id = d.forum
1342                                               JOIN {user} u              ON u.id = p.userid
1343                                         WHERE p.created > ? AND f.course = ?
1344                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1345          return false;
1346     }
1348     $modinfo =& get_fast_modinfo($course);
1350     $groupmodes = array();
1351     $cms    = array();
1353     $strftimerecent = get_string('strftimerecent');
1355     $printposts = array();
1356     foreach ($posts as $post) {
1357         if (!isset($modinfo->instances['forum'][$post->forum])) {
1358             // not visible
1359             continue;
1360         }
1361         $cm = $modinfo->instances['forum'][$post->forum];
1362         if (!$cm->uservisible) {
1363             continue;
1364         }
1365         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1367         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1368             continue;
1369         }
1371         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1372           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1373             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1374                 continue;
1375             }
1376         }
1378         $groupmode = groups_get_activity_groupmode($cm, $course);
1380         if ($groupmode) {
1381             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1382                 // oki (Open discussions have groupid -1)
1383             } else {
1384                 // separate mode
1385                 if (isguestuser()) {
1386                     // shortcut
1387                     continue;
1388                 }
1390                 if (is_null($modinfo->groups)) {
1391                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1392                 }
1394                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1395                     continue;
1396                 }
1397             }
1398         }
1400         $printposts[] = $post;
1401     }
1402     unset($posts);
1404     if (!$printposts) {
1405         return false;
1406     }
1408     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1409     echo "\n<ul class='unlist'>\n";
1411     foreach ($printposts as $post) {
1412         $subjectclass = empty($post->parent) ? ' bold' : '';
1414         echo '<li><div class="head">'.
1415                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1416                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1417              '</div>';
1418         echo '<div class="info'.$subjectclass.'">';
1419         if (empty($post->parent)) {
1420             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1421         } else {
1422             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1423         }
1424         $post->subject = break_up_long_words(format_string($post->subject, true));
1425         echo $post->subject;
1426         echo "</a>\"</div></li>\n";
1427     }
1429     echo "</ul>\n";
1431     return true;
1434 /**
1435  * Return grade for given user or all users.
1436  *
1437  * @global object
1438  * @global object
1439  * @param object $forum
1440  * @param int $userid optional user id, 0 means all users
1441  * @return array array of grades, false if none
1442  */
1443 function forum_get_user_grades($forum, $userid=0) {
1444     global $CFG;
1446     require_once($CFG->dirroot.'/rating/lib.php');
1447     $rm = new rating_manager();
1449     $ratingoptions = new stdclass();
1451     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1452     $ratingoptions->modulename = 'forum';
1453     $ratingoptions->moduleid   = $forum->id;
1454     //$ratingoptions->cmidnumber = $forum->cmidnumber;
1456     $ratingoptions->userid = $userid;
1457     $ratingoptions->aggregationmethod = $forum->assessed;
1458     $ratingoptions->scaleid = $forum->scale;
1459     $ratingoptions->itemtable = 'forum_posts';
1460     $ratingoptions->itemtableusercolumn = 'userid';
1462     return $rm->get_user_grades($ratingoptions);
1465 /**
1466  * Update activity grades
1467  *
1468  * @global object
1469  * @global object
1470  * @param object $forum
1471  * @param int $userid specific user only, 0 means all
1472  * @param boolean $nullifnone return null if grade does not exist
1473  * @return void
1474  */
1475 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1476     global $CFG, $DB;
1477     require_once($CFG->libdir.'/gradelib.php');
1479     if (!$forum->assessed) {
1480         forum_grade_item_update($forum);
1482     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1483         forum_grade_item_update($forum, $grades);
1485     } else if ($userid and $nullifnone) {
1486         $grade = new object();
1487         $grade->userid   = $userid;
1488         $grade->rawgrade = NULL;
1489         forum_grade_item_update($forum, $grade);
1491     } else {
1492         forum_grade_item_update($forum);
1493     }
1496 /**
1497  * Update all grades in gradebook.
1498  * @global object
1499  */
1500 function forum_upgrade_grades() {
1501     global $DB;
1503     $sql = "SELECT COUNT('x')
1504               FROM {forum} f, {course_modules} cm, {modules} m
1505              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1506     $count = $DB->count_records_sql($sql);
1508     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1509               FROM {forum} f, {course_modules} cm, {modules} m
1510              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1511     if ($rs = $DB->get_recordset_sql($sql)) {
1512         $pbar = new progress_bar('forumupgradegrades', 500, true);
1513         $i=0;
1514         foreach ($rs as $forum) {
1515             $i++;
1516             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1517             forum_update_grades($forum, 0, false);
1518             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1519         }
1520         $rs->close();
1521     }
1524 /**
1525  * Create/update grade item for given forum
1526  *
1527  * @global object
1528  * @uses GRADE_TYPE_NONE
1529  * @uses GRADE_TYPE_VALUE
1530  * @uses GRADE_TYPE_SCALE
1531  * @param object $forum object with extra cmidnumber
1532  * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
1533  * @return int 0 if ok
1534  */
1535 function forum_grade_item_update($forum, $grades=NULL) {
1536     global $CFG;
1537     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1538         require_once($CFG->libdir.'/gradelib.php');
1539     }
1541     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1543     if (!$forum->assessed or $forum->scale == 0) {
1544         $params['gradetype'] = GRADE_TYPE_NONE;
1546     } else if ($forum->scale > 0) {
1547         $params['gradetype'] = GRADE_TYPE_VALUE;
1548         $params['grademax']  = $forum->scale;
1549         $params['grademin']  = 0;
1551     } else if ($forum->scale < 0) {
1552         $params['gradetype'] = GRADE_TYPE_SCALE;
1553         $params['scaleid']   = -$forum->scale;
1554     }
1556     if ($grades  === 'reset') {
1557         $params['reset'] = true;
1558         $grades = NULL;
1559     }
1561     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1564 /**
1565  * Delete grade item for given forum
1566  *
1567  * @global object
1568  * @param object $forum object
1569  * @return object grade_item
1570  */
1571 function forum_grade_item_delete($forum) {
1572     global $CFG;
1573     require_once($CFG->libdir.'/gradelib.php');
1575     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1579 /**
1580  * Returns the users with data in one forum
1581  * (users with records in forum_subscriptions, forum_posts, students)
1582  *
1583  * @global object
1584  * @global object
1585  * @param int $forumid
1586  * @return mixed array or false if none
1587  */
1588 function forum_get_participants($forumid) {
1590     global $CFG, $DB;
1592     //Get students from forum_subscriptions
1593     $st_subscriptions = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1594                                          FROM {user} u,
1595                                               {forum_subscriptions} s
1596                                          WHERE s.forum = ? AND
1597                                                u.id = s.userid", array($forumid));
1598     //Get students from forum_posts
1599     $st_posts = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1600                                  FROM {user} u,
1601                                       {forum_discussions} d,
1602                                       {forum_posts} p
1603                                  WHERE d.forum = ? AND
1604                                        p.discussion = d.id AND
1605                                        u.id = p.userid", array($forumid));
1607     //Get students from the ratings table
1608     $st_ratings = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1609                                    FROM {user} u,
1610                                         {forum_discussions} d,
1611                                         {forum_posts} p,
1612                                         {ratings} r
1613                                    WHERE d.forum = ? AND
1614                                          p.discussion = d.id AND
1615                                          r.post = p.id AND
1616                                          u.id = r.userid", array($forumid));
1618     //Add st_posts to st_subscriptions
1619     if ($st_posts) {
1620         foreach ($st_posts as $st_post) {
1621             $st_subscriptions[$st_post->id] = $st_post;
1622         }
1623     }
1624     //Add st_ratings to st_subscriptions
1625     if ($st_ratings) {
1626         foreach ($st_ratings as $st_rating) {
1627             $st_subscriptions[$st_rating->id] = $st_rating;
1628         }
1629     }
1630     //Return st_subscriptions array (it contains an array of unique users)
1631     return ($st_subscriptions);
1634 /**
1635  * This function returns if a scale is being used by one forum
1636  *
1637  * @global object
1638  * @param int $forumid
1639  * @param int $scaleid negative number
1640  * @return bool
1641  */
1642 function forum_scale_used ($forumid,$scaleid) {
1643     global $DB;
1644     $return = false;
1646     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1648     if (!empty($rec) && !empty($scaleid)) {
1649         $return = true;
1650     }
1652     return $return;
1655 /**
1656  * Checks if scale is being used by any instance of forum
1657  *
1658  * This is used to find out if scale used anywhere
1659  *
1660  * @global object
1661  * @param $scaleid int
1662  * @return boolean True if the scale is used by any forum
1663  */
1664 function forum_scale_used_anywhere($scaleid) {
1665     global $DB;
1666     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1667         return true;
1668     } else {
1669         return false;
1670     }
1673 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1675 /**
1676  * Gets a post with all info ready for forum_print_post
1677  * Most of these joins are just to get the forum id
1678  *
1679  * @global object
1680  * @global object
1681  * @param int $postid
1682  * @return mixed array of posts or false
1683  */
1684 function forum_get_post_full($postid) {
1685     global $CFG, $DB;
1687     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1688                              FROM {forum_posts} p
1689                                   JOIN {forum_discussions} d ON p.discussion = d.id
1690                                   LEFT JOIN {user} u ON p.userid = u.id
1691                             WHERE p.id = ?", array($postid));
1694 /**
1695  * Gets posts with all info ready for forum_print_post
1696  * We pass forumid in because we always know it so no need to make a
1697  * complicated join to find it out.
1698  *
1699  * @global object
1700  * @global object
1701  * @return mixed array of posts or false
1702  */
1703 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1704     global $CFG, $DB;
1706     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1707                               FROM {forum_posts} p
1708                          LEFT JOIN {user} u ON p.userid = u.id
1709                              WHERE p.discussion = ?
1710                                AND p.parent > 0 $sort", array($discussion));
1713 /**
1714  * Gets all posts in discussion including top parent.
1715  *
1716  * @global object
1717  * @global object
1718  * @global object
1719  * @param int $discussionid
1720  * @param string $sort
1721  * @param bool $tracking does user track the forum?
1722  * @return array of posts
1723  */
1724 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1725     global $CFG, $DB, $USER;
1727     $tr_sel  = "";
1728     $tr_join = "";
1729     $params = array();
1731     if ($tracking) {
1732         $now = time();
1733         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1734         $tr_sel  = ", fr.id AS postread";
1735         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1736         $params[] = $USER->id;
1737     }
1739     $params[] = $discussionid;
1740     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1741                                      FROM {forum_posts} p
1742                                           LEFT JOIN {user} u ON p.userid = u.id
1743                                           $tr_join
1744                                     WHERE p.discussion = ?
1745                                  ORDER BY $sort", $params)) {
1746         return array();
1747     }
1749     foreach ($posts as $pid=>$p) {
1750         if ($tracking) {
1751             if (forum_tp_is_post_old($p)) {
1752                  $posts[$pid]->postread = true;
1753             }
1754         }
1755         if (!$p->parent) {
1756             continue;
1757         }
1758         if (!isset($posts[$p->parent])) {
1759             continue; // parent does not exist??
1760         }
1761         if (!isset($posts[$p->parent]->children)) {
1762             $posts[$p->parent]->children = array();
1763         }
1764         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1765     }
1767     return $posts;
1770 /**
1771  * Gets posts with all info ready for forum_print_post
1772  * We pass forumid in because we always know it so no need to make a
1773  * complicated join to find it out.
1774  *
1775  * @global object
1776  * @global object
1777  * @param int $parent
1778  * @param int $forumid
1779  * @return array
1780  */
1781 function forum_get_child_posts($parent, $forumid) {
1782     global $CFG, $DB;
1784     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1785                               FROM {forum_posts} p
1786                          LEFT JOIN {user} u ON p.userid = u.id
1787                              WHERE p.parent = ?
1788                           ORDER BY p.created ASC", array($parent));
1791 /**
1792  * An array of forum objects that the user is allowed to read/search through.
1793  *
1794  * @global object
1795  * @global object
1796  * @global object
1797  * @param int $userid
1798  * @param int $courseid if 0, we look for forums throughout the whole site.
1799  * @return array of forum objects, or false if no matches
1800  *         Forum objects have the following attributes:
1801  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1802  *         viewhiddentimedposts
1803  */
1804 function forum_get_readable_forums($userid, $courseid=0) {
1806     global $CFG, $DB, $USER;
1807     require_once($CFG->dirroot.'/course/lib.php');
1809     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1810         print_error('notinstalled', 'forum');
1811     }
1813     if ($courseid) {
1814         $courses = $DB->get_records('course', array('id' => $courseid));
1815     } else {
1816         // If no course is specified, then the user can see SITE + his courses.
1817         $courses1 = $DB->get_records('course', array('id' => SITEID));
1818         $courses2 = enrol_get_users_courses($userid, true);
1819         $courses = array_merge($courses1, $courses2);
1820     }
1821     if (!$courses) {
1822         return array();
1823     }
1825     $readableforums = array();
1827     foreach ($courses as $course) {
1829         $modinfo =& get_fast_modinfo($course);
1830         if (is_null($modinfo->groups)) {
1831             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1832         }
1834         if (empty($modinfo->instances['forum'])) {
1835             // hmm, no forums?
1836             continue;
1837         }
1839         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1841         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1842             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1843                 continue;
1844             }
1845             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1846             $forum = $courseforums[$forumid];
1848             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1849                 continue;
1850             }
1852          /// group access
1853             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1854                 if (is_null($modinfo->groups)) {
1855                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1856                 }
1857                 if (isset($modinfo->groups[$cm->groupingid])) {
1858                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1859                     $forum->onlygroups[] = -1;
1860                 } else {
1861                     $forum->onlygroups = array(-1);
1862                 }
1863             }
1865         /// hidden timed discussions
1866             $forum->viewhiddentimedposts = true;
1867             if (!empty($CFG->forum_enabletimedposts)) {
1868                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1869                     $forum->viewhiddentimedposts = false;
1870                 }
1871             }
1873         /// qanda access
1874             if ($forum->type == 'qanda'
1875                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1877                 // We need to check whether the user has posted in the qanda forum.
1878                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1879                                                     // the user is allowed to see in this forum.
1880                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1881                     foreach ($discussionspostedin as $d) {
1882                         $forum->onlydiscussions[] = $d->id;
1883                     }
1884                 }
1885             }
1887             $readableforums[$forum->id] = $forum;
1888         }
1890         unset($modinfo);
1892     } // End foreach $courses
1894     return $readableforums;
1897 /**
1898  * Returns a list of posts found using an array of search terms.
1899  *
1900  * @global object
1901  * @global object
1902  * @global object
1903  * @param array $searchterms array of search terms, e.g. word +word -word
1904  * @param int $courseid if 0, we search through the whole site
1905  * @param int $limitfrom
1906  * @param int $limitnum
1907  * @param int &$totalcount
1908  * @param string $extrasql
1909  * @return array|bool Array of posts found or false
1910  */
1911 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1912                             &$totalcount, $extrasql='') {
1913     global $CFG, $DB, $USER;
1914     require_once($CFG->libdir.'/searchlib.php');
1916     $forums = forum_get_readable_forums($USER->id, $courseid);
1918     if (count($forums) == 0) {
1919         $totalcount = 0;
1920         return false;
1921     }
1923     $now = round(time(), -2); // db friendly
1925     $fullaccess = array();
1926     $where = array();
1927     $params = array();
1929     foreach ($forums as $forumid => $forum) {
1930         $select = array();
1932         if (!$forum->viewhiddentimedposts) {
1933             $select[] = "(d.userid = :userid OR (d.timestart < : AND (d.timeend = 0 OR d.timeend > :timeend)))";
1934             $params = array('userid'=>$USER->id, 'timestart'=>$now, 'timeend'=>$now);
1935         }
1937         $cm = get_coursemodule_from_instance('forum', $forumid);
1938         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1940         if ($forum->type == 'qanda'
1941             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1942             if (!empty($forum->onlydiscussions)) {
1943                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda0');
1944                 $params = array_merge($params, $discussionid_params);
1945                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
1946             } else {
1947                 $select[] = "p.parent = 0";
1948             }
1949         }
1951         if (!empty($forum->onlygroups)) {
1952             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps0');
1953             $params = array_merge($params, $groupid_params);
1954             $select[] = "d.groupid $groupid_sql";
1955         }
1957         if ($select) {
1958             $selects = implode(" AND ", $select);
1959             $where[] = "(d.forum = :forum AND $selects)";
1960             $params['forum'] = $forumid;
1961         } else {
1962             $fullaccess[] = $forumid;
1963         }
1964     }
1966     if ($fullaccess) {
1967         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula0');
1968         $params = array_merge($params, $fullid_params);
1969         $where[] = "(d.forum $fullid_sql)";
1970     }
1972     $selectdiscussion = "(".implode(" OR ", $where).")";
1974     $messagesearch = '';
1975     $searchstring = '';
1977     // Need to concat these back together for parser to work.
1978     foreach($searchterms as $searchterm){
1979         if ($searchstring != '') {
1980             $searchstring .= ' ';
1981         }
1982         $searchstring .= $searchterm;
1983     }
1985     // We need to allow quoted strings for the search. The quotes *should* be stripped
1986     // by the parser, but this should be examined carefully for security implications.
1987     $searchstring = str_replace("\\\"","\"",$searchstring);
1988     $parser = new search_parser();
1989     $lexer = new search_lexer($parser);
1991     if ($lexer->parse($searchstring)) {
1992         $parsearray = $parser->get_parsed_array();
1993     // Experimental feature under 1.8! MDL-8830
1994     // Use alternative text searches if defined
1995     // This feature only works under mysql until properly implemented for other DBs
1996     // Requires manual creation of text index for forum_posts before enabling it:
1997     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
1998     // Experimental feature under 1.8! MDL-8830
1999         if (!empty($CFG->forum_usetextsearches)) {
2000             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2001                                                  'p.userid', 'u.id', 'u.firstname',
2002                                                  'u.lastname', 'p.modified', 'd.forum');
2003         } else {
2004             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2005                                                  'p.userid', 'u.id', 'u.firstname',
2006                                                  'u.lastname', 'p.modified', 'd.forum');
2007         }
2008         $params = array_merge($params, $msparams);
2009     }
2011     $fromsql = "{forum_posts} p,
2012                   {forum_discussions} d,
2013                   {user} u";
2015     $selectsql = " $messagesearch
2016                AND p.discussion = d.id
2017                AND p.userid = u.id
2018                AND $selectdiscussion
2019                    $extrasql";
2021     $countsql = "SELECT COUNT(*)
2022                    FROM $fromsql
2023                   WHERE $selectsql";
2025     $searchsql = "SELECT p.*,
2026                          d.forum,
2027                          u.firstname,
2028                          u.lastname,
2029                          u.email,
2030                          u.picture,
2031                          u.imagealt,
2032                          u.email
2033                     FROM $fromsql
2034                    WHERE $selectsql
2035                 ORDER BY p.modified DESC";
2037     $totalcount = $DB->count_records_sql($countsql, $params);
2039     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2042 /**
2043  * Returns a list of ratings for a particular post - sorted.
2044  *
2045  * @global object
2046  * @global object
2047  * @param int $postid
2048  * @param string $sort
2049  * @return array Array of ratings or false
2050  */
2051 function forum_get_ratings($context, $postid, $sort="u.firstname ASC") {
2052     global $PAGE;
2054     $options = new stdclass();
2055     $options->context = $PAGE->context;
2056     $options->itemid = $postid;
2057     $options->sort = "ORDER BY $sort";
2059     $rm = new rating_manager();
2060     $rm->get_all_ratings_for_item($options);
2063 /**
2064  * Returns a list of all new posts that have not been mailed yet
2065  *
2066  * @global object
2067  * @global object
2068  * @param int $starttime posts created after this time
2069  * @param int $endtime posts created before this
2070  * @param int $now used for timed discussions only
2071  * @return array
2072  */
2073 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2074     global $CFG, $DB;
2076     $params = array($starttime, $endtime);
2077     if (!empty($CFG->forum_enabletimedposts)) {
2078         if (empty($now)) {
2079             $now = time();
2080         }
2081         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2082         $params[] = $now;
2083         $params[] = $now;
2084     } else {
2085         $timedsql = "";
2086     }
2088     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2089                               FROM {forum_posts} p
2090                                    JOIN {forum_discussions} d ON d.id = p.discussion
2091                              WHERE p.mailed = 0
2092                                    AND p.created >= ?
2093                                    AND (p.created < ? OR p.mailnow = 1)
2094                                    $timedsql
2095                           ORDER BY p.modified ASC", $params);
2098 /**
2099  * Marks posts before a certain time as being mailed already
2100  *
2101  * @global object
2102  * @global object
2103  * @param int $endtime
2104  * @param int $now Defaults to time()
2105  * @return bool
2106  */
2107 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2108     global $CFG, $DB;
2109     if (empty($now)) {
2110         $now = time();
2111     }
2113     if (empty($CFG->forum_enabletimedposts)) {
2114         return $DB->execute("UPDATE {forum_posts}
2115                                SET mailed = '1'
2116                              WHERE (created < ? OR mailnow = 1)
2117                                    AND mailed = 0", array($endtime));
2119     } else {
2120         return $DB->execute("UPDATE {forum_posts}
2121                                SET mailed = '1'
2122                              WHERE discussion NOT IN (SELECT d.id
2123                                                         FROM {forum_discussions} d
2124                                                        WHERE d.timestart > ?)
2125                                    AND (created < ? OR mailnow = 1)
2126                                    AND mailed = 0", array($now, $endtime));
2127     }
2130 /**
2131  * Get all the posts for a user in a forum suitable for forum_print_post
2132  *
2133  * @global object
2134  * @global object
2135  * @uses CONTEXT_MODULE
2136  * @return array
2137  */
2138 function forum_get_user_posts($forumid, $userid) {
2139     global $CFG, $DB;
2141     $timedsql = "";
2142     $params = array($forumid, $userid);
2144     if (!empty($CFG->forum_enabletimedposts)) {
2145         $cm = get_coursemodule_from_instance('forum', $forumid);
2146         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2147             $now = time();
2148             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2149             $params[] = $now;
2150             $params[] = $now;
2151         }
2152     }
2154     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2155                               FROM {forum} f
2156                                    JOIN {forum_discussions} d ON d.forum = f.id
2157                                    JOIN {forum_posts} p       ON p.discussion = d.id
2158                                    JOIN {user} u              ON u.id = p.userid
2159                              WHERE f.id = ?
2160                                    AND p.userid = ?
2161                                    $timedsql
2162                           ORDER BY p.modified ASC", $params);
2165 /**
2166  * Get all the discussions user participated in
2167  *
2168  * @global object
2169  * @global object
2170  * @uses CONTEXT_MODULE
2171  * @param int $forumid
2172  * @param int $userid
2173  * @return array Array or false
2174  */
2175 function forum_get_user_involved_discussions($forumid, $userid) {
2176     global $CFG, $DB;
2178     $timedsql = "";
2179     $params = array($forumid, $userid);
2180     if (!empty($CFG->forum_enabletimedposts)) {
2181         $cm = get_coursemodule_from_instance('forum', $forumid);
2182         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2183             $now = time();
2184             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2185             $params[] = $now;
2186             $params[] = $now;
2187         }
2188     }
2190     return $DB->get_records_sql("SELECT DISTINCT d.*
2191                               FROM {forum} f
2192                                    JOIN {forum_discussions} d ON d.forum = f.id
2193                                    JOIN {forum_posts} p       ON p.discussion = d.id
2194                              WHERE f.id = ?
2195                                    AND p.userid = ?
2196                                    $timedsql", $params);
2199 /**
2200  * Get all the posts for a user in a forum suitable for forum_print_post
2201  *
2202  * @global object
2203  * @global object
2204  * @param int $forumid
2205  * @param int $userid
2206  * @return array of counts or false
2207  */
2208 function forum_count_user_posts($forumid, $userid) {
2209     global $CFG, $DB;
2211     $timedsql = "";
2212     $params = array($forumid, $userid);
2213     if (!empty($CFG->forum_enabletimedposts)) {
2214         $cm = get_coursemodule_from_instance('forum', $forumid);
2215         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2216             $now = time();
2217             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2218             $params[] = $now;
2219             $params[] = $now;
2220         }
2221     }
2223     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2224                              FROM {forum} f
2225                                   JOIN {forum_discussions} d ON d.forum = f.id
2226                                   JOIN {forum_posts} p       ON p.discussion = d.id
2227                                   JOIN {user} u              ON u.id = p.userid
2228                             WHERE f.id = ?
2229                                   AND p.userid = ?
2230                                   $timedsql", $params);
2233 /**
2234  * Given a log entry, return the forum post details for it.
2235  *
2236  * @global object
2237  * @global object
2238  * @param object $log
2239  * @return array|null
2240  */
2241 function forum_get_post_from_log($log) {
2242     global $CFG, $DB;
2244     if ($log->action == "add post") {
2246         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2247                                            u.firstname, u.lastname, u.email, u.picture
2248                                  FROM {forum_discussions} d,
2249                                       {forum_posts} p,
2250                                       {forum} f,
2251                                       {user} u
2252                                 WHERE p.id = ?
2253                                   AND d.id = p.discussion
2254                                   AND p.userid = u.id
2255                                   AND u.deleted <> '1'
2256                                   AND f.id = d.forum", array($log->info));
2259     } else if ($log->action == "add discussion") {
2261         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2262                                            u.firstname, u.lastname, u.email, u.picture
2263                                  FROM {forum_discussions} d,
2264                                       {forum_posts} p,
2265                                       {forum} f,
2266                                       {user} u
2267                                 WHERE d.id = ?
2268                                   AND d.firstpost = p.id
2269                                   AND p.userid = u.id
2270                                   AND u.deleted <> '1'
2271                                   AND f.id = d.forum", array($log->info));
2272     }
2273     return NULL;
2276 /**
2277  * Given a discussion id, return the first post from the discussion
2278  *
2279  * @global object
2280  * @global object
2281  * @param int $dicsussionid
2282  * @return array
2283  */
2284 function forum_get_firstpost_from_discussion($discussionid) {
2285     global $CFG, $DB;
2287     return $DB->get_record_sql("SELECT p.*
2288                              FROM {forum_discussions} d,
2289                                   {forum_posts} p
2290                             WHERE d.id = ?
2291                               AND d.firstpost = p.id ", array($discussionid));
2294 /**
2295  * Returns an array of counts of replies to each discussion
2296  *
2297  * @global object
2298  * @global object
2299  * @param int $forumid
2300  * @param string $forumsort
2301  * @param int $limit
2302  * @param int $page
2303  * @param int $perpage
2304  * @return array
2305  */
2306 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2307     global $CFG, $DB;
2309     if ($limit > 0) {
2310         $limitfrom = 0;
2311         $limitnum  = $limit;
2312     } else if ($page != -1) {
2313         $limitfrom = $page*$perpage;
2314         $limitnum  = $perpage;
2315     } else {
2316         $limitfrom = 0;
2317         $limitnum  = 0;
2318     }
2320     if ($forumsort == "") {
2321         $orderby = "";
2322         $groupby = "";
2324     } else {
2325         $orderby = "ORDER BY $forumsort";
2326         $groupby = ", ".strtolower($forumsort);
2327         $groupby = str_replace('desc', '', $groupby);
2328         $groupby = str_replace('asc', '', $groupby);
2329     }
2331     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2332         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2333                   FROM {forum_posts} p
2334                        JOIN {forum_discussions} d ON p.discussion = d.id
2335                  WHERE p.parent > 0 AND d.forum = ?
2336               GROUP BY p.discussion";
2337         return $DB->get_records_sql($sql, array($forumid));
2339     } else {
2340         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2341                   FROM {forum_posts} p
2342                        JOIN {forum_discussions} d ON p.discussion = d.id
2343                  WHERE d.forum = ?
2344               GROUP BY p.discussion $groupby
2345               $orderby";
2346         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2347     }
2350 /**
2351  * @global object
2352  * @global object
2353  * @global object
2354  * @staticvar array $cache
2355  * @param object $forum
2356  * @param object $cm
2357  * @param object $course
2358  * @return mixed
2359  */
2360 function forum_count_discussions($forum, $cm, $course) {
2361     global $CFG, $DB, $USER;
2363     static $cache = array();
2365     $now = round(time(), -2); // db cache friendliness
2367     $params = array($course->id);
2369     if (!isset($cache[$course->id])) {
2370         if (!empty($CFG->forum_enabletimedposts)) {
2371             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2372             $params[] = $now;
2373             $params[] = $now;
2374         } else {
2375             $timedsql = "";
2376         }
2378         $sql = "SELECT f.id, COUNT(d.id) as dcount
2379                   FROM {forum} f
2380                        JOIN {forum_discussions} d ON d.forum = f.id
2381                  WHERE f.course = ?
2382                        $timedsql
2383               GROUP BY f.id";
2385         if ($counts = $DB->get_records_sql($sql, $params)) {
2386             foreach ($counts as $count) {
2387                 $counts[$count->id] = $count->dcount;
2388             }
2389             $cache[$course->id] = $counts;
2390         } else {
2391             $cache[$course->id] = array();
2392         }
2393     }
2395     if (empty($cache[$course->id][$forum->id])) {
2396         return 0;
2397     }
2399     $groupmode = groups_get_activity_groupmode($cm, $course);
2401     if ($groupmode != SEPARATEGROUPS) {
2402         return $cache[$course->id][$forum->id];
2403     }
2405     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2406         return $cache[$course->id][$forum->id];
2407     }
2409     require_once($CFG->dirroot.'/course/lib.php');
2411     $modinfo =& get_fast_modinfo($course);
2412     if (is_null($modinfo->groups)) {
2413         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2414     }
2416     if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2417         $mygroups = $modinfo->groups[$cm->groupingid];
2418     } else {
2419         $mygroups = false; // Will be set below
2420     }
2422     // add all groups posts
2423     if (empty($mygroups)) {
2424         $mygroups = array(-1=>-1);
2425     } else {
2426         $mygroups[-1] = -1;
2427     }
2429     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2430     $params[] = $forum->id;
2432     if (!empty($CFG->forum_enabletimedposts)) {
2433         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2434         $params[] = $now;
2435         $params[] = $now;
2436     } else {
2437         $timedsql = "";
2438     }
2440     $sql = "SELECT COUNT(d.id)
2441               FROM {forum_discussions} d
2442              WHERE d.groupid $mygroups_sql AND d.forum = ?
2443                    $timedsql";
2445     return $DB->get_field_sql($sql, $params);
2448 /**
2449  * How many posts by other users are unrated by a given user in the given discussion?
2450  *
2451  * @global object
2452  * @global object
2453  * @param int $discussionid
2454  * @param int $userid
2455  * @return mixed
2456  */
2457 function forum_count_unrated_posts($discussionid, $userid) {
2458     global $CFG, $DB;
2459     if ($posts = $DB->get_record_sql("SELECT count(*) as num
2460                                    FROM {forum_posts}
2461                                   WHERE parent > 0
2462                                     AND discussion = ?
2463                                     AND userid <> ? ", array($discussionid, $userid))) {
2465         if ($rated = $DB->get_record_sql("SELECT count(*) as num
2466                                        FROM {forum_posts} p,
2467                                             {rating} r
2468                                       WHERE p.discussion = ?
2469                                         AND p.id = r.itemid
2470                                         AND r.userid = ?", array($discussionid, $userid))) {
2471             $difference = $posts->num - $rated->num;
2472             if ($difference > 0) {
2473                 return $difference;
2474             } else {
2475                 return 0;    // Just in case there was a counting error
2476             }
2477         } else {
2478             return $posts->num;
2479         }
2480     } else {
2481         return 0;
2482     }
2485 /**
2486  * Get all discussions in a forum
2487  *
2488  * @global object
2489  * @global object
2490  * @global object
2491  * @uses CONTEXT_MODULE
2492  * @uses VISIBLEGROUPS
2493  * @param object $cm
2494  * @param string $forumsort
2495  * @param bool $fullpost
2496  * @param int $unused
2497  * @param int $limit
2498  * @param bool $userlastmodified
2499  * @param int $page
2500  * @param int $perpage
2501  * @return array
2502  */
2503 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2504     global $CFG, $DB, $USER;
2506     $timelimit = '';
2508     $now = round(time(), -2);
2509     $params = array($cm->instance);
2511     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2513     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2514         return array();
2515     }
2517     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2519         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2520             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2521             $params[] = $now;
2522             $params[] = $now;
2523             if (isloggedin()) {
2524                 $timelimit .= " OR d.userid = ?";
2525                 $params[] = $USER->id;
2526             }
2527             $timelimit .= ")";
2528         }
2529     }
2531     if ($limit > 0) {
2532         $limitfrom = 0;
2533         $limitnum  = $limit;
2534     } else if ($page != -1) {
2535         $limitfrom = $page*$perpage;
2536         $limitnum  = $perpage;
2537     } else {
2538         $limitfrom = 0;
2539         $limitnum  = 0;
2540     }
2542     $groupmode    = groups_get_activity_groupmode($cm);
2543     $currentgroup = groups_get_activity_group($cm);
2545     if ($groupmode) {
2546         if (empty($modcontext)) {
2547             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2548         }
2550         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2551             if ($currentgroup) {
2552                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2553                 $params[] = $currentgroup;
2554             } else {
2555                 $groupselect = "";
2556             }
2558         } else {
2559             //seprate groups without access all
2560             if ($currentgroup) {
2561                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2562                 $params[] = $currentgroup;
2563             } else {
2564                 $groupselect = "AND d.groupid = -1";
2565             }
2566         }
2567     } else {
2568         $groupselect = "";
2569     }
2572     if (empty($forumsort)) {
2573         $forumsort = "d.timemodified DESC";
2574     }
2575     if (empty($fullpost)) {
2576         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2577     } else {
2578         $postdata = "p.*";
2579     }
2581     if (empty($userlastmodified)) {  // We don't need to know this
2582         $umfields = "";
2583         $umtable  = "";
2584     } else {
2585         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2586         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2587     }
2589     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2590                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2591               FROM {forum_discussions} d
2592                    JOIN {forum_posts} p ON p.discussion = d.id
2593                    JOIN {user} u ON p.userid = u.id
2594                    $umtable
2595              WHERE d.forum = ? AND p.parent = 0
2596                    $timelimit $groupselect
2597           ORDER BY $forumsort";
2598     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2601 /**
2602  *
2603  * @global object
2604  * @global object
2605  * @global object
2606  * @uses CONTEXT_MODULE
2607  * @uses VISIBLEGROUPS
2608  * @param object $cm
2609  * @return array
2610  */
2611 function forum_get_discussions_unread($cm) {
2612     global $CFG, $DB, $USER;
2614     $now = round(time(), -2);
2615     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2617     $params = array();
2618     $groupmode    = groups_get_activity_groupmode($cm);
2619     $currentgroup = groups_get_activity_group($cm);
2621     if ($groupmode) {
2622         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2624         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2625             if ($currentgroup) {
2626                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2627                 $params['currentgroup'] = $currentgroup;
2628             } else {
2629                 $groupselect = "";
2630             }
2632         } else {
2633             //separate groups without access all
2634             if ($currentgroup) {
2635                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2636                 $params['currentgroup'] = $currentgroup;
2637             } else {
2638                 $groupselect = "AND d.groupid = -1";
2639             }
2640         }
2641     } else {
2642         $groupselect = "";
2643     }
2645     if (!empty($CFG->forum_enabletimedposts)) {
2646         $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2647         $params['now1'] = $now;
2648         $params['now2'] = $now;
2649     } else {
2650         $timedsql = "";
2651     }
2653     $sql = "SELECT d.id, COUNT(p.id) AS unread
2654               FROM {forum_discussions} d
2655                    JOIN {forum_posts} p     ON p.discussion = d.id
2656                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2657              WHERE d.forum = {$cm->instance}
2658                    AND p.modified >= :cutoffdate AND r.id is NULL
2659                    $groupselect
2660                    $timedsql
2661           GROUP BY d.id";
2662     $params['cutoffdate'] = $cutoffdate;
2664     if ($unreads = $DB->get_records_sql($sql, $params)) {
2665         foreach ($unreads as $unread) {
2666             $unreads[$unread->id] = $unread->unread;
2667         }
2668         return $unreads;
2669     } else {
2670         return array();
2671     }
2674 /**
2675  * @global object
2676  * @global object
2677  * @global object
2678  * @uses CONEXT_MODULE
2679  * @uses VISIBLEGROUPS
2680  * @param object $cm
2681  * @return array
2682  */
2683 function forum_get_discussions_count($cm) {
2684     global $CFG, $DB, $USER;
2686     $now = round(time(), -2);
2687     $params = array($cm->instance);
2688     $groupmode    = groups_get_activity_groupmode($cm);
2689     $currentgroup = groups_get_activity_group($cm);
2691     if ($groupmode) {
2692         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2694         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2695             if ($currentgroup) {
2696                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2697                 $params[] = $currentgroup;
2698             } else {
2699                 $groupselect = "";
2700             }
2702         } else {
2703             //seprate groups without access all
2704             if ($currentgroup) {
2705                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2706                 $params[] = $currentgroup;
2707             } else {
2708                 $groupselect = "AND d.groupid = -1";
2709             }
2710         }
2711     } else {
2712         $groupselect = "";
2713     }
2715     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2717     $timelimit = "";
2719     if (!empty($CFG->forum_enabletimedposts)) {
2721         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2723         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2724             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2725             $params[] = $now;
2726             $params[] = $now;
2727             if (isloggedin()) {
2728                 $timelimit .= " OR d.userid = ?";
2729                 $params[] = $USER->id;
2730             }
2731             $timelimit .= ")";
2732         }
2733     }
2735     $sql = "SELECT COUNT(d.id)
2736               FROM {forum_discussions} d
2737                    JOIN {forum_posts} p ON p.discussion = d.id
2738              WHERE d.forum = ? AND p.parent = 0
2739                    $groupselect $timelimit";
2741     return $DB->get_field_sql($sql, $params);
2745 /**
2746  * Get all discussions started by a particular user in a course (or group)
2747  * This function no longer used ...
2748  *
2749  * @todo Remove this function if no longer used
2750  * @global object
2751  * @global object
2752  * @param int $courseid
2753  * @param int $userid
2754  * @param int $groupid
2755  * @return array
2756  */
2757 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2758     global $CFG, $DB;
2759     $params = array($courseid, $userid);
2760     if ($groupid) {
2761         $groupselect = " AND d.groupid = ? ";
2762         $params[] = $groupid;
2763     } else  {
2764         $groupselect = "";
2765     }
2767     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2768                                    f.type as forumtype, f.name as forumname, f.id as forumid
2769                               FROM {forum_discussions} d,
2770                                    {forum_posts} p,
2771                                    {user} u,
2772                                    {forum} f
2773                              WHERE d.course = ?
2774                                AND p.discussion = d.id
2775                                AND p.parent = 0
2776                                AND p.userid = u.id
2777                                AND u.id = ?
2778                                AND d.forum = f.id $groupselect
2779                           ORDER BY p.created DESC", $params);
2782 /**
2783  * Get the list of potential subscribers to a forum.
2784  *
2785  * @param object $forumcontext the forum context.
2786  * @param integer $groupid the id of a group, or 0 for all groups.
2787  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2788  * @param string $sort sort order. As for get_users_by_capability.
2789  * @return array list of users.
2790  */
2791 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2792     return get_users_by_capability($forumcontext, 'mod/forum:initialsubscriptions', $fields, $sort, '', '', $groupid, '', false, true);
2795 /**
2796  * Returns list of user objects that are subscribed to this forum
2797  *
2798  * @global object
2799  * @global object
2800  * @param object $course the course
2801  * @param forum $forum the forum
2802  * @param integer $groupid group id, or 0 for all.
2803  * @param object $context the forum context, to save re-fetching it where possible.
2804  * @return array list of users.
2805  */
2806 function forum_subscribed_users($course, $forum, $groupid=0, $context = NULL) {
2807     global $CFG, $DB;
2808     $params = array($forum->id);
2810     if ($groupid) {
2811         $grouptables = ", {groups_members} gm ";
2812         $groupselect = "AND gm.groupid = ? AND u.id = gm.userid";
2813         $params[] = $groupid;
2814     } else  {
2815         $grouptables = '';
2816         $groupselect = '';
2817     }
2819     $fields ="u.id,
2820               u.username,
2821               u.firstname,
2822               u.lastname,
2823               u.maildisplay,
2824               u.mailformat,
2825               u.maildigest,
2826               u.emailstop,
2827               u.imagealt,
2828               u.email,
2829               u.city,
2830               u.country,
2831               u.lastaccess,
2832               u.lastlogin,
2833               u.picture,
2834               u.timezone,
2835               u.theme,
2836               u.lang,
2837               u.trackforums,
2838               u.mnethostid";
2840     if (forum_is_forcesubscribed($forum)) {
2841         if (empty($context)) {
2842             $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2843             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2844         }
2845         $sort = "u.email ASC";
2846         $results = forum_get_potential_subscribers($context, $groupid, $fields, $sort);
2847     } else {
2848         $results = $DB->get_records_sql("SELECT $fields
2849                               FROM {user} u,
2850                                    {forum_subscriptions} s $grouptables
2851                              WHERE s.forum = ?
2852                                AND s.userid = u.id
2853                                AND u.deleted = 0  $groupselect
2854                           ORDER BY u.email ASC", $params);
2855     }
2857     static $guestid = null;
2859     if (is_null($guestid)) {
2860         if ($guest = guest_user()) {
2861             $guestid = $guest->id;
2862         } else {
2863             $guestid = 0;
2864         }
2865     }
2867     // Guest user should never be subscribed to a forum.
2868     unset($results[$guestid]);
2870     return $results;
2875 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2878 /**
2879  * @global object
2880  * @global object
2881  * @param int $courseid
2882  * @param string $type
2883  */
2884 function forum_get_course_forum($courseid, $type) {
2885 // How to set up special 1-per-course forums
2886     global $CFG, $DB, $OUTPUT;
2888     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2889         // There should always only be ONE, but with the right combination of
2890         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2891         foreach ($forums as $forum) {
2892             return $forum;   // ie the first one
2893         }
2894     }
2896     // Doesn't exist, so create one now.
2897     $forum->course = $courseid;
2898     $forum->type = "$type";
2899     switch ($forum->type) {
2900         case "news":
2901             $forum->name  = get_string("namenews", "forum");
2902             $forum->intro = get_string("intronews", "forum");
2903             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2904             $forum->assessed = 0;
2905             if ($courseid == SITEID) {
2906                 $forum->name  = get_string("sitenews");
2907                 $forum->forcesubscribe = 0;
2908             }
2909             break;
2910         case "social":
2911             $forum->name  = get_string("namesocial", "forum");
2912             $forum->intro = get_string("introsocial", "forum");
2913             $forum->assessed = 0;
2914             $forum->forcesubscribe = 0;
2915             break;
2916         case "blog":
2917             $forum->name = get_string('blogforum', 'forum');
2918             $forum->intro = get_string('introblog', 'forum');
2919             $forum->assessed = 0;
2920             $forum->forcesubscribe = 0;
2921             break;
2922         default:
2923             echo $OUTPUT->notification("That forum type doesn't exist!");
2924             return false;
2925             break;
2926     }
2928     $forum->timemodified = time();
2929     $forum->id = $DB->insert_record("forum", $forum);
2931     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2932         echo $OUTPUT->notification("Could not find forum module!!");
2933         return false;
2934     }
2935     $mod = new object();
2936     $mod->course = $courseid;
2937     $mod->module = $module->id;
2938     $mod->instance = $forum->id;
2939     $mod->section = 0;
2940     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
2941         echo $OUTPUT->notification("Could not add a new course module to the course '" . format_string($course->fullname) . "'");
2942         return false;
2943     }
2944     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
2945         echo $OUTPUT->notification("Could not add the new course module to that section");
2946         return false;
2947     }
2948     $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule));
2950     include_once("$CFG->dirroot/course/lib.php");
2951     rebuild_course_cache($courseid);
2953     return $DB->get_record("forum", array("id" => "$forum->id"));
2957 /**
2958  * Given the data about a posting, builds up the HTML to display it and
2959  * returns the HTML in a string.  This is designed for sending via HTML email.
2960  *
2961  * @global object
2962  * @param object $course
2963  * @param object $cm
2964  * @param object $forum
2965  * @param object $discussion
2966  * @param object $post
2967  * @param object $userform
2968  * @param object $userto
2969  * @param bool $ownpost
2970  * @param bool $reply
2971  * @param bool $link
2972  * @param bool $rate
2973  * @param string $footer
2974  * @return string
2975  */
2976 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
2977                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
2979     global $CFG, $OUTPUT;
2981     if (!isset($userto->viewfullnames[$forum->id])) {
2982         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
2983             print_error('invalidcoursemodule');
2984         }
2985         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2986         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
2987     } else {
2988         $viewfullnames = $userto->viewfullnames[$forum->id];
2989     }
2991     // format the post body
2992     $options = new object();
2993     $options->para = true;
2994     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
2996     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
2998     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
2999     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3000     $output .= '</td>';
3002     if ($post->parent) {
3003         $output .= '<td class="topic">';
3004     } else {
3005         $output .= '<td class="topic starter">';
3006     }
3007     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3009     $fullname = fullname($userfrom, $viewfullnames);
3010     $by = new object();
3011     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3012     $by->date = userdate($post->modified, '', $userto->timezone);
3013     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3015     $output .= '</td></tr>';
3017     $output .= '<tr><td class="left side" valign="top">';
3019     if (isset($userfrom->groups)) {
3020         $groups = $userfrom->groups[$forum->id];
3021     } else {
3022         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
3023             print_error('invalidcoursemodule');
3024         }
3025         $group = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3026     }
3028     if ($groups) {
3029         $output .= print_group_picture($groups, $course->id, false, true, true);
3030     } else {
3031         $output .= '&nbsp;';
3032     }
3034     $output .= '</td><td class="content">';
3036     $attachments = forum_print_attachments($post, $cm, 'html');
3037     if ($attachments !== '') {
3038         $output .= '<div class="attachments">';
3039         $output .= $attachments;
3040         $output .= '</div>';
3041     }
3043     $output .= $formattedtext;
3045 // Commands
3046     $commands = array();
3048     if ($post->parent) {
3049         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3050                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3051     }
3053     if ($reply) {
3054         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3055                       get_string('reply', 'forum').'</a>';
3056     }
3058     $output .= '<div class="commands">';
3059     $output .= implode(' | ', $commands);
3060     $output .= '</div>';
3062 // Context link to post if required
3063     if ($link) {
3064         $output .= '<div class="link">';
3065         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3066                      get_string('postincontext', 'forum').'</a>';
3067         $output .= '</div>';
3068     }
3070     if ($footer) {
3071         $output .= '<div class="footer">'.$footer.'</div>';
3072     }
3073     $output .= '</td></tr></table>'."\n\n";
3075     return $output;
3078 /**
3079  * Print a forum post
3080  *
3081  * @global object
3082  * @global object
3083  * @uses FORUM_MODE_THREADED
3084  * @uses PORTFOLIO_FORMAT_PLAINHTML
3085  * @uses PORTFOLIO_FORMAT_FILE
3086  * @uses PORTFOLIO_FORMAT_RICHHTML
3087  * @uses PORTFOLIO_ADD_TEXT_LINK
3088  * @uses CONTEXT_MODULE
3089  * @param object $post The post to print.
3090  * @param object $discussion
3091  * @param object $forum
3092  * @param object $cm
3093  * @param object $course
3094  * @param boolean $ownpost Whether this post belongs to the current user.
3095  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3096  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3097  * @param string $footer Extra stuff to print after the message.
3098  * @param string $highlight Space-separated list of terms to highlight.
3099  * @param int $post_read true, false or -99. If we already know whether this user
3100  *          has read this post, pass that in, otherwise, pass in -99, and this
3101  *          function will work it out.
3102  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3103  *          the current user can't see this post, if this argument is true
3104  *          (the default) then print a dummy 'you can't see this post' post.
3105  *          If false, don't output anything at all.
3106  * @param bool|null $istracked
3107  * @return void
3108  */
3109 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3110                           $footer="", $highlight="", $post_read=null, $dummyifcantsee=true, $istracked=null) {
3112     global $USER, $CFG, $OUTPUT;
3114     static $stredit, $strdelete, $strreply, $strparent, $strprune;
3115     static $strpruneheading, $displaymode;
3116     static $strmarkread, $strmarkunread;
3118     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3120     $post->course = $course->id;
3121     $post->forum  = $forum->id;
3122     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3124     // caching
3125     if (!isset($cm->cache)) {
3126         $cm->cache = new object();
3127     }
3129     if (!isset($cm->cache->caps)) {
3130         $cm->cache->caps = array();
3131         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3132         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3133         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3134         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3135         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3136         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3137         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3138         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3139         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3140     }
3142     if (!isset($cm->uservisible)) {
3143         $cm->uservisible = coursemodule_visible_for_user($cm);
3144     }
3146     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3147         if (!$dummyifcantsee) {
3148             return;
3149         }
3150         echo '<a id="p'.$post->id.'"></a>';
3151         echo '<table cellspacing="0" class="forumpost">';
3152         echo '<tr class="header"><td class="picture left">';
3153         //        print_user_picture($post->userid, $courseid, $post->picture);
3154         echo '</td>';
3155         if ($post->parent) {
3156             echo '<td class="topic">';
3157         } else {
3158             echo '<td class="topic starter">';
3159         }
3160         echo '<div class="subject">'.get_string('forumsubjecthidden','forum').'</div>';
3161         echo '<div class="author">';
3162         print_string('forumauthorhidden','forum');
3163         echo '</div></td></tr>';
3165         echo '<tr><td class="left side">';
3166         echo '&nbsp;';
3168         // Actual content
3170         echo '</td><td class="content">'."\n";
3171         echo get_string('forumbodyhidden','forum');
3172         echo '</td></tr></table>';
3173         return;
3174     }
3176     if (empty($stredit)) {
3177         $stredit         = get_string('edit', 'forum');
3178         $strdelete       = get_string('delete', 'forum');
3179         $strreply        = get_string('reply', 'forum');
3180         $strparent       = get_string('parent', 'forum');
3181         $strpruneheading = get_string('pruneheading', 'forum');
3182         $strprune        = get_string('prune', 'forum');
3183         $displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3184         $strmarkread     = get_string('markread', 'forum');
3185         $strmarkunread   = get_string('markunread', 'forum');
3187     }
3189     $read_style = '';
3190     // ignore trackign status if not tracked or tracked param missing
3191     if ($istracked) {
3192         if (is_null($post_read)) {
3193             debugging('fetching post_read info');
3194             $post_read = forum_tp_is_post_read($USER->id, $post);
3195         }
3197         if ($post_read) {
3198             $read_style = ' read';
3199         } else {
3200             $read_style = ' unread';
3201             echo '<a name="unread"></a>';
3202         }
3203     }
3205     echo '<a id="p'.$post->id.'"></a>';
3206     echo '<table cellspacing="0" class="forumpost'.$read_style.'">';
3208     // Picture
3209     $postuser = new object();
3210     $postuser->id        = $post->userid;
3211     $postuser->firstname = $post->firstname;
3212     $postuser->lastname  = $post->lastname;
3213     $postuser->imagealt  = $post->imagealt;
3214     $postuser->picture   = $post->picture;
3215     $postuser->email     = $post->email;
3217     echo '<tr class="header"><td class="picture left">';
3218     echo $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3219     echo '</td>';
3221     if ($post->parent) {
3222         echo '<td class="topic">';
3223     } else {
3224         echo '<td class="topic starter">';
3225     }
3227     if (!empty($post->subjectnoformat)) {
3228         echo '<div class="subject">'.$post->subject.'</div>';
3229     } else {
3230         echo '<div class="subject">'.format_string($post->subject).'</div>';
3231     }
3233     echo '<div class="author">';
3234     $fullname = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3235     $by = new object();
3236     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.
3237                 $post->userid.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3238     $by->date = userdate($post->modified);
3239     print_string('bynameondate', 'forum', $by);
3240     echo '</div></td></tr>';
3242     echo '<tr><td class="left side">';
3243     if (isset($cm->cache->usersgroups)) {
3244         $groups = array();
3245         if (isset($cm->cache->usersgroups[$post->userid])) {
3246             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3247                 $groups[$gid] = $cm->cache->groups[$gid];
3248             }
3249         }
3250     } else {
3251         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3252     }
3254     if ($groups) {
3255         print_group_picture($groups, $course->id, false, false, true);
3256     } else {
3257         echo '&nbsp;';
3258     }
3260 // Actual content
3262     echo '</td><td class="content">'."\n";
3264     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3266     if ($attachments !== '') {
3267         echo '<div class="attachments">';
3268         echo $attachments;
3269         echo '</div>';
3270     }
3272     $options = new object();
3273     $options->para    = false;
3274     $options->trusted = $post->messagetrust;
3275     if ($link and (strlen(strip_tags($post->message)) > $CFG->forum_longpost)) {
3276         // Print shortened version
3277         echo format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3278         $numwords = count_words(strip_tags($post->message));
3279         echo '<div class="posting"><a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3280         echo get_string('readtherest', 'forum');
3281         echo '</a> ('.get_string('numwords', '', $numwords).')...</div>';
3282     } else {
3283         // Print whole message
3284         echo '<div class="posting">';
3285         if ($highlight) {
3286             echo highlight($highlight, format_text($post->message, $post->messageformat, $options, $course->id));
3287         } else {
3288             echo format_text($post->message, $post->messageformat, $options, $course->id);
3289         }
3290         echo '</div>';
3291         echo $attachedimages;
3292     }
3295 // Commands
3297     $commands = array();
3299     if ($istracked) {
3300         // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3301         // Don't display the mark read / unread controls in this case.
3302         if ($CFG->forum_usermarksread and isloggedin()) {
3303             if ($post_read) {
3304                 $mcmd = '&amp;mark=unread&amp;postid='.$post->id;
3305                 $mtxt = $strmarkunread;
3306             } else {
3307                 $mcmd = '&amp;mark=read&amp;postid='.$post->id;
3308                 $mtxt = $strmarkread;
3309             }
3310             if ($displaymode == FORUM_MODE_THREADED) {
3311                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3312                               $post->discussion.'&amp;parent='.$post->id.$mcmd.'">'.$mtxt.'</a>';
3313             } else {
3314                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3315                               $post->discussion.$mcmd.'#p'.$post->id.'">'.$mtxt.'</a>';
3316             }
3317         }
3318     }
3320     if ($post->parent) {  // Zoom in to the parent specifically
3321         if ($displaymode == FORUM_MODE_THREADED) {
3322             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3323                       $post->discussion.'&amp;parent='.$post->parent.'">'.$strparent.'</a>';
3324         } else {
3325             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3326                       $post->discussion.'#p'.$post->parent.'">'.$strparent.'</a>';
3327         }
3328     }
3330     $age = time() - $post->created;
3331     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3332     if (!$post->parent and $forum->type == 'news' and $discussion->timestart > time()) {
3333         $age = 0;
3334     }
3335     $editanypost = $cm->cache->caps['mod/forum:editanypost'];
3337     if ($ownpost or $editanypost) {
3338         if (($age < $CFG->maxeditingtime) or $editanypost) {
3339             $commands[] =  '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?edit='.$post->id.'">'.$stredit.'</a>';
3340         }
3341     }
3343     if ($cm->cache->caps['mod/forum:splitdiscussions']
3344                 && $post->parent && $forum->type != 'single') {
3346         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?prune='.$post->id.
3347                       '" title="'.$strpruneheading.'">'.$strprune.'</a>';
3348     }
3350     if (($ownpost and $age < $CFG->maxeditingtime
3351                 and $cm->cache->caps['mod/forum:deleteownpost'])
3352                 or $cm->cache->caps['mod/forum:deleteanypost']) {
3353         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?delete='.$post->id.'">'.$strdelete.'</a>';
3354     }
3356     if ($reply) {
3357         $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.$strreply.'</a>';
3358     }
3360     if ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost'])) {
3361         $p = array(
3362             'postid' => $post->id,
3363         );
3364         require_once($CFG->libdir.'/portfoliolib.php');
3365         $button = new portfolio_add_button();
3366         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php');
3367         if (empty($attachments)) {
3368             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3369         } else {
3370             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3371         }
3373         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3374         if (!empty($porfoliohtml)) {
3375             $commands[] = $porfoliohtml;
3376         }
3377     }
3379     echo '<div class="commands">';
3380     echo implode(' | ', $commands);
3381     echo '</div>';
3384 // Ratings
3385     if (!empty($post->rating)) {
3386         echo $OUTPUT->render($post->rating);
3387     }
3389 // Link to post if required
3391     if ($link) {
3392         echo '<div class="link">';
3393         if ($post->replies == 1) {
3394             $replystring = get_string('repliesone', 'forum', $post->replies);
3395         } else {
3396             $replystring = get_string('repliesmany', 'forum', $post->replies);
3397         }
3398         echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.
3399              get_string('discussthistopic', 'forum').'</a>&nbsp;('.$replystring.')';
3400         echo '</div>';
3401     }
3403     if ($footer) {
3404         echo '<div class="footer">'.$footer.'</div>';
3405     }
3406     echo '</td></tr></table>'."\n\n";
3408     if ($istracked && !$CFG->forum_usermarksread && !$post_read) {
3409         forum_tp_mark_post_read($USER->id, $post, $forum->id);
3410     }
3413 /**
3414  * Return rating related permissions
3415  * @param string $options the context id
3416  * @return array an associative array of the user's rating permissions
3417  */
3418 function forum_rating_permissions($contextid) {
3419     $context = get_context_instance_by_id($contextid);
3421     if (!$context) {
3422         print_error('invalidcontext');
3423         return null;
3424     } else {
3425         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));
3426     }
3429 /**
3430  * Returns the names of the table and columns necessary to check items for ratings
3431  * @return array an array containing the item table, item id and user id columns
3432  */
3433 function forum_rating_item_check_info() {
3434     return array('forum_posts','id','userid');
3438 /**
3439  * This function prints the overview of a discussion in the forum listing.
3440  * It needs some discussion information and some post information, these
3441  * happen to be combined for efficiency in the $post parameter by the function
3442  * that calls this one: forum_print_latest_discussions()
3443  *
3444  * @global object
3445  * @global object
3446  * @param object $post The post object (passed by reference for speed).
3447  * @param object $forum The forum object.
3448  * @param int $group Current group.
3449  * @param string $datestring Format to use for the dates.
3450  * @param boolean $cantrack Is tracking enabled for this forum.
3451  * @param boolean $forumtracked Is the user tracking this forum.
3452  * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
3453  */
3454 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
3455                                         $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) {
3457     global $USER, $CFG, $OUTPUT;
3459     static $rowcount;
3460     static $strmarkalldread;
3462     if (empty($modcontext)) {
3463         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3464             print_error('invalidcoursemodule');
3465         }
3466         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3467     }
3469     if (!isset($rowcount)) {
3470         $rowcount = 0;
3471         $strmarkalldread = get_string('markalldread', 'forum');
3472     } else {
3473         $rowcount = ($rowcount + 1) % 2;
3474     }
3476     $post->subject = format_string($post->subject,true);
3478     echo "\n\n";
3479     echo '<tr class="discussion r'.$rowcount.'">';
3481     // Topic
3482     echo '<td class="topic starter">';
3483     echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.$post->subject.'</a>';
3484     echo "</td>\n";
3486     // Picture
3487     $postuser = new object;
3488     $postuser->id = $post->userid;
3489     $postuser->firstname = $post->firstname;
3490     $postuser->lastname = $post->lastname;
3491     $postuser->imagealt = $post->imagealt;
3492     $postuser->picture = $post->picture;
3493     $postuser->email = $post->email;
3495     echo '<td class="picture">';
3496     echo $OUTPUT->user_picture($postuser, array('courseid'=>$forum->course));
3497     echo "</td>\n";
3499     // User name
3500     $fullname = fullname($post, has_capability('moodle/site:viewfullnames', $modcontext));
3501     echo '<td class="author">';
3502     echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->userid.'&amp;course='.$forum->course.'">'.$fullname.'</a>';
3503     echo "</td>\n";