MDL-21432 backup 2.0 - initial commit. Enable forum backup
[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 //todo andrew remove this
47 define('FORUM_UNSET_POST_RATING', -999);
49 //todo andrew and remove these
50 define ('FORUM_AGGREGATE_NONE', 0); //no ratings
51 define ('FORUM_AGGREGATE_AVG', 1);
52 define ('FORUM_AGGREGATE_COUNT', 2);
53 define ('FORUM_AGGREGATE_MAX', 3);
54 define ('FORUM_AGGREGATE_MIN', 4);
55 define ('FORUM_AGGREGATE_SUM', 5);
57 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
59 /**
60  * Given an object containing all the necessary data,
61  * (defined by the form in mod_form.php) this function
62  * will create a new instance and return the id number
63  * of the new instance.
64  *
65  * @global object
66  * @global object
67  * @param object $forum add forum instance (with magic quotes)
68  * @return int intance id
69  */
70 function forum_add_instance($forum) {
71     global $CFG, $DB;
73     $forum->timemodified = time();
75     if (empty($forum->assessed)) {
76         $forum->assessed = 0;
77     }
79     if (empty($forum->ratingtime) or empty($forum->assessed)) {
80         $forum->assesstimestart  = 0;
81         $forum->assesstimefinish = 0;
82     }
84     $forum->id = $DB->insert_record('forum', $forum);
86     if ($forum->type == 'single') {  // Create related discussion.
87         $discussion = new object();
88         $discussion->course        = $forum->course;
89         $discussion->forum         = $forum->id;
90         $discussion->name          = $forum->name;
91         $discussion->intro         = $forum->intro;
92         $discussion->assessed      = $forum->assessed;
93         $discussion->messageformat = $forum->messageformat;
94         $discussion->mailnow       = false;
95         $discussion->groupid       = -1;
97         $message = '';
99         if (! forum_add_discussion($discussion, null, $message)) {
100             print_error('cannotadd', 'forum');
101         }
102     }
104     if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
105     /// all users should be subscribed initially
106     /// Note: forum_get_potential_subscribers should take the forum context,
107     /// but that does not exist yet, becuase the forum is only half build at this
108     /// stage. However, because the forum is brand new, we know that there are
109     /// no role assignments or overrides in the forum context, so using the
110     /// course context gives the same list of users.
111         $users = forum_get_potential_subscribers(get_context_instance(CONTEXT_COURSE, $forum->course), 0, 'u.id, u.email', '');
112         foreach ($users as $user) {
113             forum_subscribe($user->id, $forum->id);
114         }
115     }
117     forum_grade_item_update($forum);
119     return $forum->id;
123 /**
124  * Given an object containing all the necessary data,
125  * (defined by the form in mod_form.php) this function
126  * will update an existing instance with new data.
127  *
128  * @global object
129  * @param object $forum forum instance (with magic quotes)
130  * @return bool success
131  */
132 function forum_update_instance($forum) {
133     global $DB, $OUTPUT, $USER;
135     $forum->timemodified = time();
136     $forum->id           = $forum->instance;
138     if (empty($forum->assessed)) {
139         $forum->assessed = 0;
140     }
142     if (empty($forum->ratingtime) or empty($forum->assessed)) {
143         $forum->assesstimestart  = 0;
144         $forum->assesstimefinish = 0;
145     }
147     $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
149     // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
150     // if  scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
151     // 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
152     if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
153         forum_update_grades($forum); // recalculate grades for the forum
154     }
156     if ($forum->type == 'single') {  // Update related discussion and post.
157         if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
158             if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC')) {
159                 echo $OUTPUT->notification('Warning! There is more than one discussion in this forum - using the most recent');
160                 $discussion = array_pop($discussions);
161             } else {
162                 // try to recover by creating initial discussion - MDL-16262
163                 $discussion = new object();
164                 $discussion->course          = $forum->course;
165                 $discussion->forum           = $forum->id;
166                 $discussion->name            = $forum->name;
167                 $discussion->intro           = $forum->intro;
168                 $discussion->assessed        = $forum->assessed;
169                 $discussion->messageformat   = $forum->messageformat;
170                 $discussion->mailnow         = false;
171                 $discussion->groupid         = -1;
173                 forum_add_discussion($discussion, null, $message);
175                 if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
176                     print_error('cannotadd', 'forum');
177                 }
178             }
179         }
180         if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
181             print_error('cannotfindfirstpost', 'forum');
182         }
184         $post->subject  = $forum->name;
185         $post->message  = $forum->intro;
186         $post->modified = $forum->timemodified;
187         $post->userid   = $USER->id;    // MDL-18599, so that current teacher can take ownership of activities
189         $DB->update_record('forum_posts', $post);
190         $discussion->name = $forum->name;
191         $DB->update_record('forum_discussions', $discussion);
192     }
194     $DB->update_record('forum', $forum);
196     forum_grade_item_update($forum);
198     return true;
202 /**
203  * Given an ID of an instance of this module,
204  * this function will permanently delete the instance
205  * and any data that depends on it.
206  *
207  * @global object
208  * @param int $id forum instance id
209  * @return bool success
210  */
211 function forum_delete_instance($id) {
212     global $DB;
214     if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
215         return false;
216     }
217     if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
218         return false;
219     }
220     if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
221         return false;
222     }
224     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
226     // now get rid of all files
227     $fs = get_file_storage();
228     $fs->delete_area_files($context->id);
230     $result = true;
232     if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
233         foreach ($discussions as $discussion) {
234             if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
235                 $result = false;
236             }
237         }
238     }
240     if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
241         $result = false;
242     }
244     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
246     if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
247         $result = false;
248     }
250     forum_grade_item_delete($forum);
252     return $result;
256 /**
257  * Indicates API features that the forum supports.
258  *
259  * @uses FEATURE_GROUPS
260  * @uses FEATURE_GROUPINGS
261  * @uses FEATURE_GROUPMEMBERSONLY
262  * @uses FEATURE_MOD_INTRO
263  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
264  * @uses FEATURE_COMPLETION_HAS_RULES
265  * @uses FEATURE_GRADE_HAS_GRADE
266  * @uses FEATURE_GRADE_OUTCOMES
267  * @param string $feature
268  * @return mixed True if yes (some features may use other values)
269  */
270 function forum_supports($feature) {
271     switch($feature) {
272         case FEATURE_GROUPS:                  return true;
273         case FEATURE_GROUPINGS:               return true;
274         case FEATURE_GROUPMEMBERSONLY:        return true;
275         case FEATURE_MOD_INTRO:               return true;
276         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
277         case FEATURE_COMPLETION_HAS_RULES:    return true;
278         case FEATURE_GRADE_HAS_GRADE:         return true;
279         case FEATURE_GRADE_OUTCOMES:          return true;
280         case FEATURE_RATE:                    return true;
281         case FEATURE_BACKUP_MOODLE2:          return true;
283         default: return null;
284     }
288 /**
289  * Obtains the automatic completion state for this forum based on any conditions
290  * in forum settings.
291  *
292  * @global object
293  * @global object
294  * @param object $course Course
295  * @param object $cm Course-module
296  * @param int $userid User ID
297  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
298  * @return bool True if completed, false if not. (If no conditions, then return
299  *   value depends on comparison type)
300  */
301 function forum_get_completion_state($course,$cm,$userid,$type) {
302     global $CFG,$DB;
304     // Get forum details
305     if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
306         throw new Exception("Can't find forum {$cm->instance}");
307     }
309     $result=$type; // Default return value
311     $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
312     $postcountsql="
313 SELECT
314     COUNT(1)
315 FROM
316     {forum_posts} fp
317     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
318 WHERE
319     fp.userid=:userid AND fd.forum=:forumid";
321     if ($forum->completiondiscussions) {
322         $value = $forum->completiondiscussions <=
323                  $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
324         if ($type == COMPLETION_AND) {
325             $result = $result && $value;
326         } else {
327             $result = $result || $value;
328         }
329     }
330     if ($forum->completionreplies) {
331         $value = $forum->completionreplies <=
332                  $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
333         if ($type==COMPLETION_AND) {
334             $result = $result && $value;
335         } else {
336             $result = $result || $value;
337         }
338     }
339     if ($forum->completionposts) {
340         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
341         if ($type == COMPLETION_AND) {
342             $result = $result && $value;
343         } else {
344             $result = $result || $value;
345         }
346     }
348     return $result;
352 /**
353  * Function to be run periodically according to the moodle cron
354  * Finds all posts that have yet to be mailed out, and mails them
355  * out to all subscribers
356  *
357  * @global object
358  * @global object
359  * @global object
360  * @uses CONTEXT_MODULE
361  * @uses CONTEXT_COURSE
362  * @uses SITEID
363  * @uses FORMAT_PLAIN
364  * @return void
365  */
366 function forum_cron() {
367     global $CFG, $USER, $DB;
369     $site = get_site();
371     // all users that are subscribed to any post that needs sending
372     $users = array();
374     // status arrays
375     $mailcount  = array();
376     $errorcount = array();
378     // caches
379     $discussions     = array();
380     $forums          = array();
381     $courses         = array();
382     $coursemodules   = array();
383     $subscribedusers = array();
386     // Posts older than 2 days will not be mailed.  This is to avoid the problem where
387     // cron has not been running for a long time, and then suddenly people are flooded
388     // with mail from the past few weeks or months
389     $timenow   = time();
390     $endtime   = $timenow - $CFG->maxeditingtime;
391     $starttime = $endtime - 48 * 3600;   // Two days earlier
393     if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
394         // Mark them all now as being mailed.  It's unlikely but possible there
395         // might be an error later so that a post is NOT actually mailed out,
396         // but since mail isn't crucial, we can accept this risk.  Doing it now
397         // prevents the risk of duplicated mails, which is a worse problem.
399         if (!forum_mark_old_posts_as_mailed($endtime)) {
400             mtrace('Errors occurred while trying to mark some posts as being mailed.');
401             return false;  // Don't continue trying to mail them, in case we are in a cron loop
402         }
404         // checking post validity, and adding users to loop through later
405         foreach ($posts as $pid => $post) {
407             $discussionid = $post->discussion;
408             if (!isset($discussions[$discussionid])) {
409                 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
410                     $discussions[$discussionid] = $discussion;
411                 } else {
412                     mtrace('Could not find discussion '.$discussionid);
413                     unset($posts[$pid]);
414                     continue;
415                 }
416             }
417             $forumid = $discussions[$discussionid]->forum;
418             if (!isset($forums[$forumid])) {
419                 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
420                     $forums[$forumid] = $forum;
421                 } else {
422                     mtrace('Could not find forum '.$forumid);
423                     unset($posts[$pid]);
424                     continue;
425                 }
426             }
427             $courseid = $forums[$forumid]->course;
428             if (!isset($courses[$courseid])) {
429                 if ($course = $DB->get_record('course', array('id' => $courseid))) {
430                     $courses[$courseid] = $course;
431                 } else {
432                     mtrace('Could not find course '.$courseid);
433                     unset($posts[$pid]);
434                     continue;
435                 }
436             }
437             if (!isset($coursemodules[$forumid])) {
438                 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
439                     $coursemodules[$forumid] = $cm;
440                 } else {
441                     mtrace('Could not course module for forum '.$forumid);
442                     unset($posts[$pid]);
443                     continue;
444                 }
445             }
448             // caching subscribed users of each forum
449             if (!isset($subscribedusers[$forumid])) {
450                 $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
451                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext)) {
452                     foreach ($subusers as $postuser) {
453                         // do not try to mail users with stopped email
454                         if ($postuser->emailstop) {
455                             if (!empty($CFG->forum_logblocked)) {
456                                 add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
457                             }
458                             continue;
459                         }
460                         // this user is subscribed to this forum
461                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
462                         // this user is a user we have to process later
463                         $users[$postuser->id] = $postuser;
464                     }
465                     unset($subusers); // release memory
466                 }
467             }
469             $mailcount[$pid] = 0;
470             $errorcount[$pid] = 0;
471         }
472     }
474     if ($users && $posts) {
476         $urlinfo = parse_url($CFG->wwwroot);
477         $hostname = $urlinfo['host'];
479         foreach ($users as $userto) {
481             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
483             // set this so that the capabilities are cached, and environment matches receiving user
484             cron_setup_user($userto);
486             mtrace('Processing user '.$userto->id);
488             // init caches
489             $userto->viewfullnames = array();
490             $userto->canpost       = array();
491             $userto->markposts     = array();
492             $userto->enrolledin    = array();
494             // reset the caches
495             foreach ($coursemodules as $forumid=>$unused) {
496                 $coursemodules[$forumid]->cache       = new object();
497                 $coursemodules[$forumid]->cache->caps = array();
498                 unset($coursemodules[$forumid]->uservisible);
499             }
501             foreach ($posts as $pid => $post) {
503                 // Set up the environment for the post, discussion, forum, course
504                 $discussion = $discussions[$post->discussion];
505                 $forum      = $forums[$discussion->forum];
506                 $course     = $courses[$forum->course];
507                 $cm         =& $coursemodules[$forum->id];
509                 // Do some checks  to see if we can bail out now
510                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
511                     continue; // user does not subscribe to this forum
512                 }
514                 // Verify user is enrollend in course - if not do not send any email
515                 if (!isset($userto->enrolledin[$course->id])) {
516                     $userto->enrolledin[$course->id] = is_enrolled(get_context_instance(CONTEXT_COURSE, $course->id));
517                 }
518                 if (!$userto->enrolledin[$course->id]) {
519                     // oops - this user should not receive anything from this course
520                     continue;
521                 }
523                 // Get info about the sending user
524                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
525                     $userfrom = $users[$post->userid];
526                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
527                     $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
528                 } else {
529                     mtrace('Could not find user '.$post->userid);
530                     continue;
531                 }
533                 // setup global $COURSE properly - needed for roles and languages
534                 cron_setup_user($userto, $course);
536                 // Fill caches
537                 if (!isset($userto->viewfullnames[$forum->id])) {
538                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
539                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
540                 }
541                 if (!isset($userto->canpost[$discussion->id])) {
542                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
543                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
544                 }
545                 if (!isset($userfrom->groups[$forum->id])) {
546                     if (!isset($userfrom->groups)) {
547                         $userfrom->groups = array();
548                         $users[$userfrom->id]->groups = array();
549                     }
550                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
551                     $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
552                 }
554                 // Make sure groups allow this user to see this email
555                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
556                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
557                         continue;                           // Be safe and don't send it to anyone
558                     }
560                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
561                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
562                         continue;
563                     }
564                 }
566                 // Make sure we're allowed to see it...
567                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
568                     mtrace('user '.$userto->id. ' can not see '.$post->id);
569                     continue;
570                 }
572                 // OK so we need to send the email.
574                 // Does the user want this post in a digest?  If so postpone it for now.
575                 if ($userto->maildigest > 0) {
576                     // This user wants the mails to be in digest form
577                     $queue = new object();
578                     $queue->userid       = $userto->id;
579                     $queue->discussionid = $discussion->id;
580                     $queue->postid       = $post->id;
581                     $queue->timemodified = $post->created;
582                     $DB->insert_record('forum_queue', $queue);
583                     continue;
584                 }
587                 // Prepare to actually send the post now, and build up the content
589                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
591                 $userfrom->customheaders = array (  // Headers to make emails easier to track
592                            'Precedence: Bulk',
593                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
594                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
595                            'Message-ID: <moodlepost'.$post->id.'@'.$hostname.'>',
596                            'In-Reply-To: <moodlepost'.$post->parent.'@'.$hostname.'>',
597                            'References: <moodlepost'.$post->parent.'@'.$hostname.'>',
598                            'X-Course-Id: '.$course->id,
599                            'X-Course-Name: '.format_string($course->fullname, true)
600                 );
603                 $postsubject = "$course->shortname: ".format_string($post->subject,true);
604                 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
605                 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
607                 // Send the post now!
609                 mtrace('Sending ', '');
611                 $eventdata = new object();
612                 $eventdata->component        = 'mod/forum';
613                 $eventdata->name             = 'posts';
614                 $eventdata->userfrom         = $userfrom;
615                 $eventdata->userto           = $userto;
616                 $eventdata->subject          = $postsubject;
617                 $eventdata->fullmessage      = $posttext;
618                 $eventdata->fullmessageformat = FORMAT_PLAIN;
619                 $eventdata->fullmessagehtml  = $posthtml;
620                 $eventdata->smallmessage     = '';
621                 if (!message_send($eventdata)){
623                     mtrace("Error: mod/forum/cron.php: Could not send out mail for id $post->id to user $userto->id".
624                          " ($userto->email) .. not trying again.");
625                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
626                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
627                     $errorcount[$post->id]++;
628                 } else if ($mailresult === 'emailstop') {
629                     // should not be reached anymore - see check above
630                 } else {
631                     $mailcount[$post->id]++;
633                 // Mark post as read if forum_usermarksread is set off
634                     if (!$CFG->forum_usermarksread) {
635                         $userto->markposts[$post->id] = $post->id;
636                     }
637                 }
639                 mtrace('post '.$post->id. ': '.$post->subject);
640             }
642             // mark processed posts as read
643             forum_tp_mark_posts_read($userto, $userto->markposts);
644         }
645     }
647     if ($posts) {
648         foreach ($posts as $post) {
649             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
650             if ($errorcount[$post->id]) {
651                 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
652             }
653         }
654     }
656     // release some memory
657     unset($subscribedusers);
658     unset($mailcount);
659     unset($errorcount);
661     cron_setup_user();
663     $sitetimezone = $CFG->timezone;
665     // Now see if there are any digest mails waiting to be sent, and if we should send them
667     mtrace('Starting digest processing...');
669     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
671     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
672         set_config('digestmailtimelast', 0);
673     }
675     $timenow = time();
676     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
678     // Delete any really old ones (normally there shouldn't be any)
679     $weekago = $timenow - (7 * 24 * 3600);
680     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
681     mtrace ('Cleaned old digest records');
683     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
685         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
687         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
689         if ($digestposts_rs->valid()) {
691             // We have work to do
692             $usermailcount = 0;
694             //caches - reuse the those filled before too
695             $discussionposts = array();
696             $userdiscussions = array();
698             foreach ($digestposts_rs as $digestpost) {
699                 if (!isset($users[$digestpost->userid])) {
700                     if ($user = $DB->get_record('user', array('id' => $digestpost->userid))) {
701                         $users[$digestpost->userid] = $user;
702                     } else {
703                         continue;
704                     }
705                 }
706                 $postuser = $users[$digestpost->userid];
707                 if ($postuser->emailstop) {
708                     if (!empty($CFG->forum_logblocked)) {
709                         add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
710                     }
711                     continue;
712                 }
714                 if (!isset($posts[$digestpost->postid])) {
715                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
716                         $posts[$digestpost->postid] = $post;
717                     } else {
718                         continue;
719                     }
720                 }
721                 $discussionid = $digestpost->discussionid;
722                 if (!isset($discussions[$discussionid])) {
723                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
724                         $discussions[$discussionid] = $discussion;
725                     } else {
726                         continue;
727                     }
728                 }
729                 $forumid = $discussions[$discussionid]->forum;
730                 if (!isset($forums[$forumid])) {
731                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
732                         $forums[$forumid] = $forum;
733                     } else {
734                         continue;
735                     }
736                 }
738                 $courseid = $forums[$forumid]->course;
739                 if (!isset($courses[$courseid])) {
740                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
741                         $courses[$courseid] = $course;
742                     } else {
743                         continue;
744                     }
745                 }
747                 if (!isset($coursemodules[$forumid])) {
748                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
749                         $coursemodules[$forumid] = $cm;
750                     } else {
751                         continue;
752                     }
753                 }
754                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
755                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
756             }
757             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
759             // Data collected, start sending out emails to each user
760             foreach ($userdiscussions as $userid => $thesediscussions) {
762                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
764                 cron_setup_user();
766                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
768                 // First of all delete all the queue entries for this user
769                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
770                 $userto = $users[$userid];
772                 // Override the language and timezone of the "current" user, so that
773                 // mail is customised for the receiver.
774                 cron_setup_user($userto);
776                 // init caches
777                 $userto->viewfullnames = array();
778                 $userto->canpost       = array();
779                 $userto->markposts     = array();
781                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
783                 $headerdata = new object();
784                 $headerdata->sitename = format_string($site->fullname, true);
785                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
787                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
788                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
790                 $posthtml = "<head>";
791 /*                foreach ($CFG->stylesheets as $stylesheet) {
792                     //TODO: MDL-21120
793                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
794                 }*/
795                 $posthtml .= "</head>\n<body id=\"email\">\n";
796                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
798                 foreach ($thesediscussions as $discussionid) {
800                     @set_time_limit(120);   // to be reset for each post
802                     $discussion = $discussions[$discussionid];
803                     $forum      = $forums[$discussion->forum];
804                     $course     = $courses[$forum->course];
805                     $cm         = $coursemodules[$forum->id];
807                     //override language
808                     cron_setup_user($userto, $course);
810                     // Fill caches
811                     if (!isset($userto->viewfullnames[$forum->id])) {
812                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
813                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
814                     }
815                     if (!isset($userto->canpost[$discussion->id])) {
816                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
817                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
818                     }
820                     $strforums      = get_string('forums', 'forum');
821                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
822                     $canreply       = $userto->canpost[$discussion->id];
824                     $posttext .= "\n \n";
825                     $posttext .= '=====================================================================';
826                     $posttext .= "\n \n";
827                     $posttext .= "$course->shortname -> $strforums -> ".format_string($forum->name,true);
828                     if ($discussion->name != $forum->name) {
829                         $posttext  .= " -> ".format_string($discussion->name,true);
830                     }
831                     $posttext .= "\n";
833                     $posthtml .= "<p><font face=\"sans-serif\">".
834                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> -> ".
835                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
836                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
837                     if ($discussion->name == $forum->name) {
838                         $posthtml .= "</font></p>";
839                     } else {
840                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
841                     }
842                     $posthtml .= '<p>';
844                     $postsarray = $discussionposts[$discussionid];
845                     sort($postsarray);
847                     foreach ($postsarray as $postid) {
848                         $post = $posts[$postid];
850                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
851                             $userfrom = $users[$post->userid];
852                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
853                             $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
854                         } else {
855                             mtrace('Could not find user '.$post->userid);
856                             continue;
857                         }
859                         if (!isset($userfrom->groups[$forum->id])) {
860                             if (!isset($userfrom->groups)) {
861                                 $userfrom->groups = array();
862                                 $users[$userfrom->id]->groups = array();
863                             }
864                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
865                             $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
866                         }
868                         $userfrom->customheaders = array ("Precedence: Bulk");
870                         if ($userto->maildigest == 2) {
871                             // Subjects only
872                             $by = new object();
873                             $by->name = fullname($userfrom);
874                             $by->date = userdate($post->modified);
875                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
876                             $posttext .= "\n---------------------------------------------------------------------";
878                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
879                             $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>';
881                         } else {
882                             // The full treatment
883                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
884                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
886                         // Create an array of postid's for this user to mark as read.
887                             if (!$CFG->forum_usermarksread) {
888                                 $userto->markposts[$post->id] = $post->id;
889                             }
890                         }
891                     }
892                     if ($canunsubscribe) {
893                         $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>";
894                     } else {
895                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
896                     }
897                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
898                 }
899                 $posthtml .= '</body>';
901                 if ($userto->mailformat != 1) {
902                     // This user DOESN'T want to receive HTML
903                     $posthtml = '';
904                 }
906                 $eventdata = new object();
907                 $eventdata->component        = 'mod/forum';
908                 $eventdata->name             = 'digests';
909                 $eventdata->userfrom         = $site->shortname;
910                 $eventdata->userto           = $userto;
911                 $eventdata->subject          = $postsubject;
912                 $eventdata->fullmessage      = $posttext;
913                 $eventdata->fullmessageformat = FORMAT_PLAIN;
914                 $eventdata->fullmessagehtml  = $posthtml;
915                 $eventdata->smallmessage     = '';
916                 if (!message_send($eventdata)){
917                     mtrace("ERROR!");
918                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
919                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
920                 } else if ($mailresult === 'emailstop') {
921                     // should not happen anymore - see check above
922                 } else {
923                     mtrace("success.");
924                     $usermailcount++;
926                     // Mark post as read if forum_usermarksread is set off
927                     forum_tp_mark_posts_read($userto, $userto->markposts);
928                 }
929             }
930         }
931     /// We have finishied all digest emails, update $CFG->digestmailtimelast
932         set_config('digestmailtimelast', $timenow);
933     }
935     cron_setup_user();
937     if (!empty($usermailcount)) {
938         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
939     }
941     if (!empty($CFG->forum_lastreadclean)) {
942         $timenow = time();
943         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
944             set_config('forum_lastreadclean', $timenow);
945             mtrace('Removing old forum read tracking info...');
946             forum_tp_clean_read_records();
947         }
948     } else {
949         set_config('forum_lastreadclean', time());
950     }
953     return true;
956 /**
957  * Builds and returns the body of the email notification in plain text.
958  *
959  * @global object
960  * @global object
961  * @uses CONTEXT_MODULE
962  * @param object $course
963  * @param object $cm
964  * @param object $forum
965  * @param object $discussion
966  * @param object $post
967  * @param object $userfrom
968  * @param object $userto
969  * @param boolean $bare
970  * @return string The email body in plain text format.
971  */
972 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
973     global $CFG, $USER;
975     if (!isset($userto->viewfullnames[$forum->id])) {
976         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
977         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
978     } else {
979         $viewfullnames = $userto->viewfullnames[$forum->id];
980     }
982     if (!isset($userto->canpost[$discussion->id])) {
983         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
984         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
985     } else {
986         $canreply = $userto->canpost[$discussion->id];
987     }
989     $by = New stdClass;
990     $by->name = fullname($userfrom, $viewfullnames);
991     $by->date = userdate($post->modified, "", $userto->timezone);
993     $strbynameondate = get_string('bynameondate', 'forum', $by);
995     $strforums = get_string('forums', 'forum');
997     $canunsubscribe = ! forum_is_forcesubscribed($forum);
999     $posttext = '';
1001     if (!$bare) {
1002         $posttext  = "$course->shortname -> $strforums -> ".format_string($forum->name,true);
1004         if ($discussion->name != $forum->name) {
1005             $posttext  .= " -> ".format_string($discussion->name,true);
1006         }
1007     }
1009     $posttext .= "\n---------------------------------------------------------------------\n";
1010     $posttext .= format_string($post->subject,true);
1011     if ($bare) {
1012         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1013     }
1014     $posttext .= "\n".$strbynameondate."\n";
1015     $posttext .= "---------------------------------------------------------------------\n";
1016     $posttext .= format_text_email($post->message, $post->messageformat);
1017     $posttext .= "\n\n";
1018     $posttext .= forum_print_attachments($post, $cm, "text");
1020     if (!$bare && $canreply) {
1021         $posttext .= "---------------------------------------------------------------------\n";
1022         $posttext .= get_string("postmailinfo", "forum", $course->shortname)."\n";
1023         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1024     }
1025     if (!$bare && $canunsubscribe) {
1026         $posttext .= "\n---------------------------------------------------------------------\n";
1027         $posttext .= get_string("unsubscribe", "forum");
1028         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1029     }
1031     return $posttext;
1034 /**
1035  * Builds and returns the body of the email notification in html format.
1036  *
1037  * @global object
1038  * @param object $course
1039  * @param object $cm
1040  * @param object $forum
1041  * @param object $discussion
1042  * @param object $post
1043  * @param object $userfrom
1044  * @param object $userto
1045  * @return string The email text in HTML format
1046  */
1047 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1048     global $CFG;
1050     if ($userto->mailformat != 1) {  // Needs to be HTML
1051         return '';
1052     }
1054     if (!isset($userto->canpost[$discussion->id])) {
1055         $canreply = forum_user_can_post($forum, $discussion, $userto);
1056     } else {
1057         $canreply = $userto->canpost[$discussion->id];
1058     }
1060     $strforums = get_string('forums', 'forum');
1061     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1063     $posthtml = '<head>';
1064 /*    foreach ($CFG->stylesheets as $stylesheet) {
1065         //TODO: MDL-21120
1066         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1067     }*/
1068     $posthtml .= '</head>';
1069     $posthtml .= "\n<body id=\"email\">\n\n";
1071     $posthtml .= '<div class="navbar">'.
1072     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$course->shortname.'</a> &raquo; '.
1073     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1074     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1075     if ($discussion->name == $forum->name) {
1076         $posthtml .= '</div>';
1077     } else {
1078         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1079                      format_string($discussion->name,true).'</a></div>';
1080     }
1081     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1083     if ($canunsubscribe) {
1084         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1085                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1086                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1087     }
1089     $posthtml .= '</body>';
1091     return $posthtml;
1095 /**
1096  *
1097  * @param object $course
1098  * @param object $user
1099  * @param object $mod TODO this is not used in this function, refactor
1100  * @param object $forum
1101  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1102  */
1103 function forum_user_outline($course, $user, $mod, $forum) {
1104     global $CFG;
1105     require_once("$CFG->libdir/gradelib.php");
1106     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1107     if (empty($grades->items[0]->grades)) {
1108         $grade = false;
1109     } else {
1110         $grade = reset($grades->items[0]->grades);
1111     }
1113     $count = forum_count_user_posts($forum->id, $user->id);
1115     if ($count && $count->postcount > 0) {
1116         $result = new object();
1117         $result->info = get_string("numposts", "forum", $count->postcount);
1118         $result->time = $count->lastpost;
1119         if ($grade) {
1120             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1121         }
1122         return $result;
1123     } else if ($grade) {
1124         $result = new object();
1125         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1126         $result->time = $grade->dategraded;
1127         return $result;
1128     }
1129     return NULL;
1133 /**
1134  * @global object
1135  * @global object
1136  * @param object $coure
1137  * @param object $user
1138  * @param object $mod
1139  * @param object $forum
1140  */
1141 function forum_user_complete($course, $user, $mod, $forum) {
1142     global $CFG,$USER, $OUTPUT;
1143     require_once("$CFG->libdir/gradelib.php");
1145     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1146     if (!empty($grades->items[0]->grades)) {
1147         $grade = reset($grades->items[0]->grades);
1148         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1149         if ($grade->str_feedback) {
1150             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1151         }
1152     }
1154     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1156         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1157             print_error('invalidcoursemodule');
1158         }
1159         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1161         // preload all user ratings for these discussions - one query only and minimal memory
1162         $cm->cache->ratings = array();
1163         $cm->cache->myratings = array();
1164         if ($postratings = forum_get_all_user_ratings($user->id, $discussions)) {
1165             foreach ($postratings as $pr) {
1166                 if (!isset($cm->cache->ratings[$pr->postid])) {
1167                     $cm->cache->ratings[$pr->postid] = array();
1168                 }
1169                 $cm->cache->ratings[$pr->postid][$pr->id] = $pr->rating;
1171                 if ($pr->userid == $USER->id) {
1172                     $cm->cache->myratings[$pr->postid] = $pr->rating;
1173                 }
1174             }
1175             unset($postratings);
1176         }
1178         foreach ($posts as $post) {
1179             if (!isset($discussions[$post->discussion])) {
1180                 continue;
1181             }
1182             $discussion = $discussions[$post->discussion];
1184             $ratings = null;
1186             if ($forum->assessed) {
1187                 if ($scale = make_grades_menu($forum->scale)) {
1188                     $ratings =new object();
1189                     $ratings->scale = $scale;
1190                     $ratings->assesstimestart = $forum->assesstimestart;
1191                     $ratings->assesstimefinish = $forum->assesstimefinish;
1192                     $ratings->allow = false;
1193                 }
1194             }
1195             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false, $ratings);
1196         }
1197     } else {
1198         echo "<p>".get_string("noposts", "forum")."</p>";
1199     }
1207 /**
1208  * @global object
1209  * @global object
1210  * @global object
1211  * @param array $courses
1212  * @param array $htmlarray
1213  */
1214 function forum_print_overview($courses,&$htmlarray) {
1215     global $USER, $CFG, $DB, $SESSION;
1217     //$LIKE = $DB->sql_ilike();//no longer using like in queries. MDL-20578
1219     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1220         return array();
1221     }
1223     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1224         return;
1225     }
1228     // get all forum logs in ONE query (much better!)
1229     $params = array();
1230     $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {log} l "
1231         ." JOIN {course_modules} cm ON cm.id = cmid "
1232         ." WHERE (";
1233     foreach ($courses as $course) {
1234         $sql .= '(l.course = ? AND l.time > ?) OR ';
1235         $params[] = $course->id;
1236         $params[] = $course->lastaccess;
1237     }
1238     $sql = substr($sql,0,-3); // take off the last OR
1240     $sql .= ") AND l.module = 'forum' AND action = 'add post' "
1241         ." AND userid != ? GROUP BY cmid,l.course,instance";
1243     $params[] = $USER->id;
1245     if (!$new = $DB->get_records_sql($sql, $params)) {
1246         $new = array(); // avoid warnings
1247     }
1249     // also get all forum tracking stuff ONCE.
1250     $trackingforums = array();
1251     foreach ($forums as $forum) {
1252         if (forum_tp_can_track_forums($forum)) {
1253             $trackingforums[$forum->id] = $forum;
1254         }
1255     }
1257     if (count($trackingforums) > 0) {
1258         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1259         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1260             ' FROM {forum_posts} p '.
1261             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1262             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1263         $params = array($USER->id);
1265         foreach ($trackingforums as $track) {
1266             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1267             $params[] = $track->id;
1268             if (isset($SESSION->currentgroup[$track->course])) {
1269                 $groupid =  $SESSION->currentgroup[$track->course];
1270             } else {
1271                 $groupid = groups_get_all_groups($track->course, $USER->id);
1272                 if (is_array($groupid)) {
1273                     $groupid = array_shift(array_keys($groupid));
1274                     $SESSION->currentgroup[$track->course] = $groupid;
1275                 } else {
1276                     $groupid = 0;
1277                 }
1278             }
1279             $params[] = $groupid;
1280         }
1281         $sql = substr($sql,0,-3); // take off the last OR
1282         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1283         $params[] = $cutoffdate;
1285         if (!$unread = $DB->get_records_sql($sql, $params)) {
1286             $unread = array();
1287         }
1288     } else {
1289         $unread = array();
1290     }
1292     if (empty($unread) and empty($new)) {
1293         return;
1294     }
1296     $strforum = get_string('modulename','forum');
1297     $strnumunread = get_string('overviewnumunread','forum');
1298     $strnumpostssince = get_string('overviewnumpostssince','forum');
1300     foreach ($forums as $forum) {
1301         $str = '';
1302         $count = 0;
1303         $thisunread = 0;
1304         $showunread = false;
1305         // either we have something from logs, or trackposts, or nothing.
1306         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1307             $count = $new[$forum->id]->count;
1308         }
1309         if (array_key_exists($forum->id,$unread)) {
1310             $thisunread = $unread[$forum->id]->count;
1311             $showunread = true;
1312         }
1313         if ($count > 0 || $thisunread > 0) {
1314             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1315                 $forum->name.'</a></div>';
1316             $str .= '<div class="info">';
1317             $str .= $count.' '.$strnumpostssince;
1318             if (!empty($showunread)) {
1319                 $str .= '<br />'.$thisunread .' '.$strnumunread;
1320             }
1321             $str .= '</div></div>';
1322         }
1323         if (!empty($str)) {
1324             if (!array_key_exists($forum->course,$htmlarray)) {
1325                 $htmlarray[$forum->course] = array();
1326             }
1327             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1328                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1329             }
1330             $htmlarray[$forum->course]['forum'] .= $str;
1331         }
1332     }
1335 /**
1336  * Given a course and a date, prints a summary of all the new
1337  * messages posted in the course since that date
1338  *
1339  * @global object
1340  * @global object
1341  * @global object
1342  * @uses CONTEXT_MODULE
1343  * @uses VISIBLEGROUPS
1344  * @param object $course
1345  * @param bool $viewfullnames capability
1346  * @param int $timestart
1347  * @return bool success
1348  */
1349 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1350     global $CFG, $USER, $DB, $OUTPUT;
1352     // do not use log table if possible, it may be huge and is expensive to join with other tables
1354     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1355                                               d.timestart, d.timeend, d.userid AS duserid,
1356                                               u.firstname, u.lastname, u.email, u.picture
1357                                          FROM {forum_posts} p
1358                                               JOIN {forum_discussions} d ON d.id = p.discussion
1359                                               JOIN {forum} f             ON f.id = d.forum
1360                                               JOIN {user} u              ON u.id = p.userid
1361                                         WHERE p.created > ? AND f.course = ?
1362                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1363          return false;
1364     }
1366     $modinfo =& get_fast_modinfo($course);
1368     $groupmodes = array();
1369     $cms    = array();
1371     $strftimerecent = get_string('strftimerecent');
1373     $printposts = array();
1374     foreach ($posts as $post) {
1375         if (!isset($modinfo->instances['forum'][$post->forum])) {
1376             // not visible
1377             continue;
1378         }
1379         $cm = $modinfo->instances['forum'][$post->forum];
1380         if (!$cm->uservisible) {
1381             continue;
1382         }
1383         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1385         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1386             continue;
1387         }
1389         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1390           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1391             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1392                 continue;
1393             }
1394         }
1396         $groupmode = groups_get_activity_groupmode($cm, $course);
1398         if ($groupmode) {
1399             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1400                 // oki (Open discussions have groupid -1)
1401             } else {
1402                 // separate mode
1403                 if (isguestuser()) {
1404                     // shortcut
1405                     continue;
1406                 }
1408                 if (is_null($modinfo->groups)) {
1409                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1410                 }
1412                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1413                     continue;
1414                 }
1415             }
1416         }
1418         $printposts[] = $post;
1419     }
1420     unset($posts);
1422     if (!$printposts) {
1423         return false;
1424     }
1426     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1427     echo "\n<ul class='unlist'>\n";
1429     foreach ($printposts as $post) {
1430         $subjectclass = empty($post->parent) ? ' bold' : '';
1432         echo '<li><div class="head">'.
1433                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1434                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1435              '</div>';
1436         echo '<div class="info'.$subjectclass.'">';
1437         if (empty($post->parent)) {
1438             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1439         } else {
1440             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1441         }
1442         $post->subject = break_up_long_words(format_string($post->subject, true));
1443         echo $post->subject;
1444         echo "</a>\"</div></li>\n";
1445     }
1447     echo "</ul>\n";
1449     return true;
1452 /**
1453  * Return grade for given user or all users.
1454  *
1455  * @global object
1456  * @global object
1457  * @param object $forum
1458  * @param int $userid optional user id, 0 means all users
1459  * @return array array of grades, false if none
1460  */
1461  //todo andrew pretty sure I can remove this
1462 function forum_get_user_grades($forum, $userid=0) {
1463     global $CFG, $DB;
1465     $params= array();
1466     if ($userid) {
1467         $params[] = $userid;
1468         $user = "AND u.id = ?";
1469     } else {
1470         $user = "";
1471     }
1473     $params[] = $forum->id;
1475     $aggtype = $forum->assessed;
1476     switch ($aggtype) {
1477         case FORUM_AGGREGATE_COUNT :
1478             $sql = "SELECT u.id, u.id AS userid, COUNT(fr.rating) AS rawgrade
1479                       FROM {user} u, {forum_posts} fp,
1480                            {forum_ratings} fr, {forum_discussions} fd
1481                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1482                            AND fr.userid != u.id AND fd.forum = ?
1483                            $user
1484                   GROUP BY u.id";
1485             break;
1486         case FORUM_AGGREGATE_MAX :
1487             $sql = "SELECT u.id, u.id AS userid, MAX(fr.rating) AS rawgrade
1488                       FROM {user} u, {forum_posts} fp,
1489                            {forum_ratings} fr, {forum_discussions} fd
1490                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1491                            AND fr.userid != u.id AND fd.forum = ?
1492                            $user
1493                   GROUP BY u.id";
1494             break;
1495         case FORUM_AGGREGATE_MIN :
1496             $sql = "SELECT u.id, u.id AS userid, MIN(fr.rating) AS rawgrade
1497                       FROM {user} u, {forum_posts} fp,
1498                            {forum_ratings} fr, {forum_discussions} fd
1499                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1500                            AND fr.userid != u.id AND fd.forum = ?
1501                            $user
1502                   GROUP BY u.id";
1503             break;
1504         case FORUM_AGGREGATE_SUM :
1505             $sql = "SELECT u.id, u.id AS userid, SUM(fr.rating) AS rawgrade
1506                      FROM {user} u, {forum_posts} fp,
1507                           {forum_ratings} fr, {forum_discussions} fd
1508                     WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1509                           AND fr.userid != u.id AND fd.forum = ?
1510                           $user
1511                  GROUP BY u.id";
1512             break;
1513         default : //avg
1514             $sql = "SELECT u.id, u.id AS userid, AVG(fr.rating) AS rawgrade
1515                       FROM {user} u, {forum_posts} fp,
1516                            {forum_ratings} fr, {forum_discussions} fd
1517                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1518                            AND fr.userid != u.id AND fd.forum = ?
1519                            $user
1520                   GROUP BY u.id";
1521             break;
1522     }
1524     if ($results = $DB->get_records_sql($sql, $params)) {
1525         // it could throw off the grading if count and sum returned a rawgrade higher than scale
1526         // so to prevent it we review the results and ensure that rawgrade does not exceed the scale, if it does we set rawgrade = scale (i.e. full credit)
1527         foreach ($results as $rid=>$result) {
1528             if ($forum->scale >= 0) {
1529                 //numeric
1530                 if ($result->rawgrade > $forum->scale) {
1531                     $results[$rid]->rawgrade = $forum->scale;
1532                 }
1533             } else {
1534                 //scales
1535                 if ($scale = $DB->get_record('scale', array('id' => -$forum->scale))) {
1536                     $scale = explode(',', $scale->scale);
1537                     $max = count($scale);
1538                     if ($result->rawgrade > $max) {
1539                         $results[$rid]->rawgrade = $max;
1540                     }
1541                 }
1542             }
1543         }
1544     }
1546     return $results;
1549 /**
1550  * Update activity grades
1551  *
1552  * @global object
1553  * @global object
1554  * @param object $forum
1555  * @param int $userid specific user only, 0 means all
1556  * @param boolean $nullifnone return null if grade does not exist
1557  * @return void
1558  */
1559 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1560     global $CFG, $DB;
1561     require_once($CFG->libdir.'/gradelib.php');
1563     if (!$forum->assessed) {
1564         forum_grade_item_update($forum);
1566     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1567         forum_grade_item_update($forum, $grades);
1569     } else if ($userid and $nullifnone) {
1570         $grade = new object();
1571         $grade->userid   = $userid;
1572         $grade->rawgrade = NULL;
1573         forum_grade_item_update($forum, $grade);
1575     } else {
1576         forum_grade_item_update($forum);
1577     }
1580 /**
1581  * Update all grades in gradebook.
1582  * @global object
1583  */
1584 function forum_upgrade_grades() {
1585     global $DB;
1587     $sql = "SELECT COUNT('x')
1588               FROM {forum} f, {course_modules} cm, {modules} m
1589              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1590     $count = $DB->count_records_sql($sql);
1592     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1593               FROM {forum} f, {course_modules} cm, {modules} m
1594              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1595     if ($rs = $DB->get_recordset_sql($sql)) {
1596         $pbar = new progress_bar('forumupgradegrades', 500, true);
1597         $i=0;
1598         foreach ($rs as $forum) {
1599             $i++;
1600             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1601             forum_update_grades($forum, 0, false);
1602             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1603         }
1604         $rs->close();
1605     }
1608 /**
1609  * Create/update grade item for given forum
1610  *
1611  * @global object
1612  * @uses GRADE_TYPE_NONE
1613  * @uses GRADE_TYPE_VALUE
1614  * @uses GRADE_TYPE_SCALE
1615  * @param object $forum object with extra cmidnumber
1616  * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
1617  * @return int 0 if ok
1618  */
1619 function forum_grade_item_update($forum, $grades=NULL) {
1620     global $CFG;
1621     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1622         require_once($CFG->libdir.'/gradelib.php');
1623     }
1625     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1627     if (!$forum->assessed or $forum->scale == 0) {
1628         $params['gradetype'] = GRADE_TYPE_NONE;
1630     } else if ($forum->scale > 0) {
1631         $params['gradetype'] = GRADE_TYPE_VALUE;
1632         $params['grademax']  = $forum->scale;
1633         $params['grademin']  = 0;
1635     } else if ($forum->scale < 0) {
1636         $params['gradetype'] = GRADE_TYPE_SCALE;
1637         $params['scaleid']   = -$forum->scale;
1638     }
1640     if ($grades  === 'reset') {
1641         $params['reset'] = true;
1642         $grades = NULL;
1643     }
1645     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1648 /**
1649  * Delete grade item for given forum
1650  *
1651  * @global object
1652  * @param object $forum object
1653  * @return object grade_item
1654  */
1655 function forum_grade_item_delete($forum) {
1656     global $CFG;
1657     require_once($CFG->libdir.'/gradelib.php');
1659     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1663 /**
1664  * Returns the users with data in one forum
1665  * (users with records in forum_subscriptions, forum_posts and forum_ratings, students)
1666  *
1667  * @global object
1668  * @global object
1669  * @param int $forumid
1670  * @return mixed array or false if none
1671  */
1672 function forum_get_participants($forumid) {
1674     global $CFG, $DB;
1676     //Get students from forum_subscriptions
1677     $st_subscriptions = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1678                                          FROM {user} u,
1679                                               {forum_subscriptions} s
1680                                          WHERE s.forum = ? AND
1681                                                u.id = s.userid", array($forumid));
1682     //Get students from forum_posts
1683     $st_posts = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1684                                  FROM {user} u,
1685                                       {forum_discussions} d,
1686                                       {forum_posts} p
1687                                  WHERE d.forum = ? AND
1688                                        p.discussion = d.id AND
1689                                        u.id = p.userid", array($forumid));
1691     //Get students from forum_ratings
1692     $st_ratings = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1693                                    FROM {user} u,
1694                                         {forum_discussions} d,
1695                                         {forum_posts} p,
1696                                         {forum_ratings} r
1697                                    WHERE d.forum = ? AND
1698                                          p.discussion = d.id AND
1699                                          r.post = p.id AND
1700                                          u.id = r.userid", array($forumid));
1702     //Add st_posts to st_subscriptions
1703     if ($st_posts) {
1704         foreach ($st_posts as $st_post) {
1705             $st_subscriptions[$st_post->id] = $st_post;
1706         }
1707     }
1708     //Add st_ratings to st_subscriptions
1709     if ($st_ratings) {
1710         foreach ($st_ratings as $st_rating) {
1711             $st_subscriptions[$st_rating->id] = $st_rating;
1712         }
1713     }
1714     //Return st_subscriptions array (it contains an array of unique users)
1715     return ($st_subscriptions);
1718 /**
1719  * This function returns if a scale is being used by one forum
1720  *
1721  * @global object
1722  * @param int $forumid
1723  * @param int $scaleid negative number
1724  * @return bool
1725  */
1726 function forum_scale_used ($forumid,$scaleid) {
1727     global $DB;
1728     $return = false;
1730     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1732     if (!empty($rec) && !empty($scaleid)) {
1733         $return = true;
1734     }
1736     return $return;
1739 /**
1740  * Checks if scale is being used by any instance of forum
1741  *
1742  * This is used to find out if scale used anywhere
1743  *
1744  * @global object
1745  * @param $scaleid int
1746  * @return boolean True if the scale is used by any forum
1747  */
1748 function forum_scale_used_anywhere($scaleid) {
1749     global $DB;
1750     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1751         return true;
1752     } else {
1753         return false;
1754     }
1757 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1759 /**
1760  * Gets a post with all info ready for forum_print_post
1761  * Most of these joins are just to get the forum id
1762  *
1763  * @global object
1764  * @global object
1765  * @param int $postid
1766  * @return mixed array of posts or false
1767  */
1768 function forum_get_post_full($postid) {
1769     global $CFG, $DB;
1771     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1772                              FROM {forum_posts} p
1773                                   JOIN {forum_discussions} d ON p.discussion = d.id
1774                                   LEFT JOIN {user} u ON p.userid = u.id
1775                             WHERE p.id = ?", array($postid));
1778 /**
1779  * Gets posts with all info ready for forum_print_post
1780  * We pass forumid in because we always know it so no need to make a
1781  * complicated join to find it out.
1782  *
1783  * @global object
1784  * @global object
1785  * @return mixed array of posts or false
1786  */
1787 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1788     global $CFG, $DB;
1790     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1791                               FROM {forum_posts} p
1792                          LEFT JOIN {user} u ON p.userid = u.id
1793                              WHERE p.discussion = ?
1794                                AND p.parent > 0 $sort", array($discussion));
1797 /**
1798  * Gets all posts in discussion including top parent.
1799  *
1800  * @global object
1801  * @global object
1802  * @global object
1803  * @param int $discussionid
1804  * @param string $sort
1805  * @param bool $tracking does user track the forum?
1806  * @return array of posts
1807  */
1808 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1809     global $CFG, $DB, $USER;
1811     $tr_sel  = "";
1812     $tr_join = "";
1813     $params = array();
1815     if ($tracking) {
1816         $now = time();
1817         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1818         $tr_sel  = ", fr.id AS postread";
1819         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1820         $params[] = $USER->id;
1821     }
1823     $params[] = $discussionid;
1824     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1825                                      FROM {forum_posts} p
1826                                           LEFT JOIN {user} u ON p.userid = u.id
1827                                           $tr_join
1828                                     WHERE p.discussion = ?
1829                                  ORDER BY $sort", $params)) {
1830         return array();
1831     }
1833     foreach ($posts as $pid=>$p) {
1834         if ($tracking) {
1835             if (forum_tp_is_post_old($p)) {
1836                  $posts[$pid]->postread = true;
1837             }
1838         }
1839         if (!$p->parent) {
1840             continue;
1841         }
1842         if (!isset($posts[$p->parent])) {
1843             continue; // parent does not exist??
1844         }
1845         if (!isset($posts[$p->parent]->children)) {
1846             $posts[$p->parent]->children = array();
1847         }
1848         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1849     }
1851     return $posts;
1854 /**
1855  * Gets posts with all info ready for forum_print_post
1856  * We pass forumid in because we always know it so no need to make a
1857  * complicated join to find it out.
1858  *
1859  * @global object
1860  * @global object
1861  * @param int $parent
1862  * @param int $forumid
1863  * @return array
1864  */
1865 function forum_get_child_posts($parent, $forumid) {
1866     global $CFG, $DB;
1868     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1869                               FROM {forum_posts} p
1870                          LEFT JOIN {user} u ON p.userid = u.id
1871                              WHERE p.parent = ?
1872                           ORDER BY p.created ASC", array($parent));
1875 /**
1876  * An array of forum objects that the user is allowed to read/search through.
1877  *
1878  * @global object
1879  * @global object
1880  * @global object
1881  * @param int $userid
1882  * @param int $courseid if 0, we look for forums throughout the whole site.
1883  * @return array of forum objects, or false if no matches
1884  *         Forum objects have the following attributes:
1885  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1886  *         viewhiddentimedposts
1887  */
1888 function forum_get_readable_forums($userid, $courseid=0) {
1890     global $CFG, $DB, $USER;
1891     require_once($CFG->dirroot.'/course/lib.php');
1893     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1894         print_error('notinstalled', 'forum');
1895     }
1897     if ($courseid) {
1898         $courses = $DB->get_records('course', array('id' => $courseid));
1899     } else {
1900         // If no course is specified, then the user can see SITE + his courses.
1901         // And admins can see all courses, so pass the $doanything flag enabled
1902         $courses1 = $DB->get_records('course', array('id' => SITEID));
1903         $courses2 = get_my_courses($userid, null, null, true);
1904         $courses = array_merge($courses1, $courses2);
1905     }
1906     if (!$courses) {
1907         return array();
1908     }
1910     $readableforums = array();
1912     foreach ($courses as $course) {
1914         $modinfo =& get_fast_modinfo($course);
1915         if (is_null($modinfo->groups)) {
1916             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1917         }
1919         if (empty($modinfo->instances['forum'])) {
1920             // hmm, no forums?
1921             continue;
1922         }
1924         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1926         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1927             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1928                 continue;
1929             }
1930             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1931             $forum = $courseforums[$forumid];
1933             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1934                 continue;
1935             }
1937          /// group access
1938             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1939                 if (is_null($modinfo->groups)) {
1940                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1941                 }
1942                 if (isset($modinfo->groups[$cm->groupingid])) {
1943                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1944                     $forum->onlygroups[] = -1;
1945                 } else {
1946                     $forum->onlygroups = array(-1);
1947                 }
1948             }
1950         /// hidden timed discussions
1951             $forum->viewhiddentimedposts = true;
1952             if (!empty($CFG->forum_enabletimedposts)) {
1953                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1954                     $forum->viewhiddentimedposts = false;
1955                 }
1956             }
1958         /// qanda access
1959             if ($forum->type == 'qanda'
1960                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1962                 // We need to check whether the user has posted in the qanda forum.
1963                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1964                                                     // the user is allowed to see in this forum.
1965                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1966                     foreach ($discussionspostedin as $d) {
1967                         $forum->onlydiscussions[] = $d->id;
1968                     }
1969                 }
1970             }
1972             $readableforums[$forum->id] = $forum;
1973         }
1975         unset($modinfo);
1977     } // End foreach $courses
1979     //print_object($courses);
1980     //print_object($readableforums);
1982     return $readableforums;
1985 /**
1986  * Returns a list of posts found using an array of search terms.
1987  *
1988  * @global object
1989  * @global object
1990  * @global object
1991  * @param array $searchterms array of search terms, e.g. word +word -word
1992  * @param int $courseid if 0, we search through the whole site
1993  * @param int $limitfrom
1994  * @param int $limitnum
1995  * @param int &$totalcount
1996  * @param string $extrasql
1997  * @return array|bool Array of posts found or false
1998  */
1999 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
2000                             &$totalcount, $extrasql='') {
2001     global $CFG, $DB, $USER;
2002     require_once($CFG->libdir.'/searchlib.php');
2004     $forums = forum_get_readable_forums($USER->id, $courseid);
2006     if (count($forums) == 0) {
2007         $totalcount = 0;
2008         return false;
2009     }
2011     $now = round(time(), -2); // db friendly
2013     $fullaccess = array();
2014     $where = array();
2015     $params = array();
2017     foreach ($forums as $forumid => $forum) {
2018         $select = array();
2020         if (!$forum->viewhiddentimedposts) {
2021             $select[] = "(d.userid = :userid OR (d.timestart < : AND (d.timeend = 0 OR d.timeend > :timeend)))";
2022             $params = array('userid'=>$USER->id, 'timestart'=>$now, 'timeend'=>$now);
2023         }
2025         $cm = get_coursemodule_from_instance('forum', $forumid);
2026         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2028         if ($forum->type == 'qanda'
2029             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
2030             if (!empty($forum->onlydiscussions)) {
2031                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda0');
2032                 $params = array_merge($params, $discussionid_params);
2033                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
2034             } else {
2035                 $select[] = "p.parent = 0";
2036             }
2037         }
2039         if (!empty($forum->onlygroups)) {
2040             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps0');
2041             $params = array_merge($params, $groupid_params);
2042             $select[] = "d.groupid $groupid_sql";
2043         }
2045         if ($select) {
2046             $selects = implode(" AND ", $select);
2047             $where[] = "(d.forum = :forum AND $selects)";
2048             $params['forum'] = $forumid;
2049         } else {
2050             $fullaccess[] = $forumid;
2051         }
2052     }
2054     if ($fullaccess) {
2055         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula0');
2056         $params = array_merge($params, $fullid_params);
2057         $where[] = "(d.forum $fullid_sql)";
2058     }
2060     $selectdiscussion = "(".implode(" OR ", $where).")";
2062     $messagesearch = '';
2063     $searchstring = '';
2065     // Need to concat these back together for parser to work.
2066     foreach($searchterms as $searchterm){
2067         if ($searchstring != '') {
2068             $searchstring .= ' ';
2069         }
2070         $searchstring .= $searchterm;
2071     }
2073     // We need to allow quoted strings for the search. The quotes *should* be stripped
2074     // by the parser, but this should be examined carefully for security implications.
2075     $searchstring = str_replace("\\\"","\"",$searchstring);
2076     $parser = new search_parser();
2077     $lexer = new search_lexer($parser);
2079     if ($lexer->parse($searchstring)) {
2080         $parsearray = $parser->get_parsed_array();
2081     // Experimental feature under 1.8! MDL-8830
2082     // Use alternative text searches if defined
2083     // This feature only works under mysql until properly implemented for other DBs
2084     // Requires manual creation of text index for forum_posts before enabling it:
2085     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2086     // Experimental feature under 1.8! MDL-8830
2087         if (!empty($CFG->forum_usetextsearches)) {
2088             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2089                                                  'p.userid', 'u.id', 'u.firstname',
2090                                                  'u.lastname', 'p.modified', 'd.forum');
2091         } else {
2092             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2093                                                  'p.userid', 'u.id', 'u.firstname',
2094                                                  'u.lastname', 'p.modified', 'd.forum');
2095         }
2096         $params = array_merge($params, $msparams);
2097     }
2099     $fromsql = "{forum_posts} p,
2100                   {forum_discussions} d,
2101                   {user} u";
2103     $selectsql = " $messagesearch
2104                AND p.discussion = d.id
2105                AND p.userid = u.id
2106                AND $selectdiscussion
2107                    $extrasql";
2109     $countsql = "SELECT COUNT(*)
2110                    FROM $fromsql
2111                   WHERE $selectsql";
2113     $searchsql = "SELECT p.*,
2114                          d.forum,
2115                          u.firstname,
2116                          u.lastname,
2117                          u.email,
2118                          u.picture,
2119                          u.imagealt
2120                     FROM $fromsql
2121                    WHERE $selectsql
2122                 ORDER BY p.modified DESC";
2124     $totalcount = $DB->count_records_sql($countsql, $params);
2126     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2129 /**
2130  * Returns a list of ratings for all posts in discussion
2131  *
2132  * @global object
2133  * @global object
2134  * @param object $discussion
2135  * @return array of ratings or false
2136  */
2137 function forum_get_all_discussion_ratings($discussion) {
2138     global $CFG, $DB;
2139     return $DB->get_records_sql("SELECT r.id, r.userid, p.id AS postid, r.rating
2140                               FROM {forum_ratings} r,
2141                                    {forum_posts} p
2142                              WHERE r.post = p.id AND p.discussion = ?
2143                              ORDER BY p.id ASC", array($discussion->id));
2146 /**
2147  * Returns a list of ratings for one specific user for all posts in discussion
2148  * @global object
2149  * @global object
2150  * @param object $discussions the discussions for which we return all ratings
2151  * @param int $userid the user for who we return all ratings
2152  * @return array
2153  */
2154 function forum_get_all_user_ratings($userid, $discussions) {
2155     global $CFG, $DB;
2158     foreach ($discussions as $discussion) {
2159      if (!isset($discussionsid)){
2160          $discussionsid = $discussion->id;
2161      }
2162      else {
2163          $discussionsid .= ",".$discussion->id;
2164      }
2165     }
2167     $sql = "SELECT r.id, r.userid, p.id AS postid, r.rating
2168                               FROM {forum_ratings} r,
2169                                    {forum_posts} p
2170                              WHERE r.post = p.id AND p.userid = :userid";
2173     $params = array();
2174     $params['userid'] = $userid;
2175     //postgres compability
2176     if (!isset($discussionsid)) {
2177        $sql .=" AND p.discussion IN (".$discussionsid.")";
2178     }
2179     $sql .=" ORDER BY p.id ASC";
2181     return $DB->get_records_sql($sql, $params);
2186 /**
2187  * Returns a list of ratings for a particular post - sorted.
2188  *
2189  * @global object
2190  * @global object
2191  * @param int $postid
2192  * @param string $sort
2193  * @return array Array of ratings or false
2194  */
2195 function forum_get_ratings($postid, $sort="u.firstname ASC") {
2196     global $CFG, $DB;
2197     return $DB->get_records_sql("SELECT u.*, r.rating, r.time
2198                               FROM {forum_ratings} r,
2199                                    {user} u
2200                              WHERE r.post = ?
2201                                AND r.userid = u.id
2202                              ORDER BY $sort", array($postid));
2205 /**
2206  * Returns a list of all new posts that have not been mailed yet
2207  *
2208  * @global object
2209  * @global object
2210  * @param int $starttime posts created after this time
2211  * @param int $endtime posts created before this
2212  * @param int $now used for timed discussions only
2213  * @return array
2214  */
2215 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2216     global $CFG, $DB;
2218     $params = array($starttime, $endtime);
2219     if (!empty($CFG->forum_enabletimedposts)) {
2220         if (empty($now)) {
2221             $now = time();
2222         }
2223         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2224         $params[] = $now;
2225         $params[] = $now;
2226     } else {
2227         $timedsql = "";
2228     }
2230     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2231                               FROM {forum_posts} p
2232                                    JOIN {forum_discussions} d ON d.id = p.discussion
2233                              WHERE p.mailed = 0
2234                                    AND p.created >= ?
2235                                    AND (p.created < ? OR p.mailnow = 1)
2236                                    $timedsql
2237                           ORDER BY p.modified ASC", $params);
2240 /**
2241  * Marks posts before a certain time as being mailed already
2242  *
2243  * @global object
2244  * @global object
2245  * @param int $endtime
2246  * @param int $now Defaults to time()
2247  * @return bool
2248  */
2249 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2250     global $CFG, $DB;
2251     if (empty($now)) {
2252         $now = time();
2253     }
2255     if (empty($CFG->forum_enabletimedposts)) {
2256         return $DB->execute("UPDATE {forum_posts}
2257                                SET mailed = '1'
2258                              WHERE (created < ? OR mailnow = 1)
2259                                    AND mailed = 0", array($endtime));
2261     } else {
2262         return $DB->execute("UPDATE {forum_posts}
2263                                SET mailed = '1'
2264                              WHERE discussion NOT IN (SELECT d.id
2265                                                         FROM {forum_discussions} d
2266                                                        WHERE d.timestart > ?)
2267                                    AND (created < ? OR mailnow = 1)
2268                                    AND mailed = 0", array($now, $endtime));
2269     }
2272 /**
2273  * Get all the posts for a user in a forum suitable for forum_print_post
2274  *
2275  * @global object
2276  * @global object
2277  * @uses CONTEXT_MODULE
2278  * @return array
2279  */
2280 function forum_get_user_posts($forumid, $userid) {
2281     global $CFG, $DB;
2283     $timedsql = "";
2284     $params = array($forumid, $userid);
2286     if (!empty($CFG->forum_enabletimedposts)) {
2287         $cm = get_coursemodule_from_instance('forum', $forumid);
2288         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2289             $now = time();
2290             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2291             $params[] = $now;
2292             $params[] = $now;
2293         }
2294     }
2296     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2297                               FROM {forum} f
2298                                    JOIN {forum_discussions} d ON d.forum = f.id
2299                                    JOIN {forum_posts} p       ON p.discussion = d.id
2300                                    JOIN {user} u              ON u.id = p.userid
2301                              WHERE f.id = ?
2302                                    AND p.userid = ?
2303                                    $timedsql
2304                           ORDER BY p.modified ASC", $params);
2307 /**
2308  * Get all the discussions user participated in
2309  *
2310  * @global object
2311  * @global object
2312  * @uses CONTEXT_MODULE
2313  * @param int $forumid
2314  * @param int $userid
2315  * @return array Array or false
2316  */
2317 function forum_get_user_involved_discussions($forumid, $userid) {
2318     global $CFG, $DB;
2320     $timedsql = "";
2321     $params = array($forumid, $userid);
2322     if (!empty($CFG->forum_enabletimedposts)) {
2323         $cm = get_coursemodule_from_instance('forum', $forumid);
2324         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2325             $now = time();
2326             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2327             $params[] = $now;
2328             $params[] = $now;
2329         }
2330     }
2332     return $DB->get_records_sql("SELECT DISTINCT d.*
2333                               FROM {forum} f
2334                                    JOIN {forum_discussions} d ON d.forum = f.id
2335                                    JOIN {forum_posts} p       ON p.discussion = d.id
2336                              WHERE f.id = ?
2337                                    AND p.userid = ?
2338                                    $timedsql", $params);
2341 /**
2342  * Get all the posts for a user in a forum suitable for forum_print_post
2343  *
2344  * @global object
2345  * @global object
2346  * @param int $forumid
2347  * @param int $userid
2348  * @return array of counts or false
2349  */
2350 function forum_count_user_posts($forumid, $userid) {
2351     global $CFG, $DB;
2353     $timedsql = "";
2354     $params = array($forumid, $userid);
2355     if (!empty($CFG->forum_enabletimedposts)) {
2356         $cm = get_coursemodule_from_instance('forum', $forumid);
2357         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2358             $now = time();
2359             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2360             $params[] = $now;
2361             $params[] = $now;
2362         }
2363     }
2365     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2366                              FROM {forum} f
2367                                   JOIN {forum_discussions} d ON d.forum = f.id
2368                                   JOIN {forum_posts} p       ON p.discussion = d.id
2369                                   JOIN {user} u              ON u.id = p.userid
2370                             WHERE f.id = ?
2371                                   AND p.userid = ?
2372                                   $timedsql", $params);
2375 /**
2376  * Given a log entry, return the forum post details for it.
2377  *
2378  * @global object
2379  * @global object
2380  * @param object $log
2381  * @return array|null
2382  */
2383 function forum_get_post_from_log($log) {
2384     global $CFG, $DB;
2386     if ($log->action == "add post") {
2388         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2389                                            u.firstname, u.lastname, u.email, u.picture
2390                                  FROM {forum_discussions} d,
2391                                       {forum_posts} p,
2392                                       {forum} f,
2393                                       {user} u
2394                                 WHERE p.id = ?
2395                                   AND d.id = p.discussion
2396                                   AND p.userid = u.id
2397                                   AND u.deleted <> '1'
2398                                   AND f.id = d.forum", array($log->info));
2401     } else if ($log->action == "add discussion") {
2403         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2404                                            u.firstname, u.lastname, u.email, u.picture
2405                                  FROM {forum_discussions} d,
2406                                       {forum_posts} p,
2407                                       {forum} f,
2408                                       {user} u
2409                                 WHERE d.id = ?
2410                                   AND d.firstpost = p.id
2411                                   AND p.userid = u.id
2412                                   AND u.deleted <> '1'
2413                                   AND f.id = d.forum", array($log->info));
2414     }
2415     return NULL;
2418 /**
2419  * Given a discussion id, return the first post from the discussion
2420  *
2421  * @global object
2422  * @global object
2423  * @param int $dicsussionid
2424  * @return array
2425  */
2426 function forum_get_firstpost_from_discussion($discussionid) {
2427     global $CFG, $DB;
2429     return $DB->get_record_sql("SELECT p.*
2430                              FROM {forum_discussions} d,
2431                                   {forum_posts} p
2432                             WHERE d.id = ?
2433                               AND d.firstpost = p.id ", array($discussionid));
2436 /**
2437  * Returns an array of counts of replies to each discussion
2438  *
2439  * @global object
2440  * @global object
2441  * @param int $forumid
2442  * @param string $forumsort
2443  * @param int $limit
2444  * @param int $page
2445  * @param int $perpage
2446  * @return array
2447  */
2448 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2449     global $CFG, $DB;
2451     if ($limit > 0) {
2452         $limitfrom = 0;
2453         $limitnum  = $limit;
2454     } else if ($page != -1) {
2455         $limitfrom = $page*$perpage;
2456         $limitnum  = $perpage;
2457     } else {
2458         $limitfrom = 0;
2459         $limitnum  = 0;
2460     }
2462     if ($forumsort == "") {
2463         $orderby = "";
2464         $groupby = "";
2466     } else {
2467         $orderby = "ORDER BY $forumsort";
2468         $groupby = ", ".strtolower($forumsort);
2469         $groupby = str_replace('desc', '', $groupby);
2470         $groupby = str_replace('asc', '', $groupby);
2471     }
2473     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2474         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2475                   FROM {forum_posts} p
2476                        JOIN {forum_discussions} d ON p.discussion = d.id
2477                  WHERE p.parent > 0 AND d.forum = ?
2478               GROUP BY p.discussion";
2479         return $DB->get_records_sql($sql, array($forumid));
2481     } else {
2482         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2483                   FROM {forum_posts} p
2484                        JOIN {forum_discussions} d ON p.discussion = d.id
2485                  WHERE d.forum = ?
2486               GROUP BY p.discussion $groupby
2487               $orderby";
2488         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2489     }
2492 /**
2493  * @global object
2494  * @global object
2495  * @global object
2496  * @staticvar array $cache
2497  * @param object $forum
2498  * @param object $cm
2499  * @param object $course
2500  * @return mixed
2501  */
2502 function forum_count_discussions($forum, $cm, $course) {
2503     global $CFG, $DB, $USER;
2505     static $cache = array();
2507     $now = round(time(), -2); // db cache friendliness
2509     $params = array($course->id);
2511     if (!isset($cache[$course->id])) {
2512         if (!empty($CFG->forum_enabletimedposts)) {
2513             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2514             $params[] = $now;
2515             $params[] = $now;
2516         } else {
2517             $timedsql = "";
2518         }
2520         $sql = "SELECT f.id, COUNT(d.id) as dcount
2521                   FROM {forum} f
2522                        JOIN {forum_discussions} d ON d.forum = f.id
2523                  WHERE f.course = ?
2524                        $timedsql
2525               GROUP BY f.id";
2527         if ($counts = $DB->get_records_sql($sql, $params)) {
2528             foreach ($counts as $count) {
2529                 $counts[$count->id] = $count->dcount;
2530             }
2531             $cache[$course->id] = $counts;
2532         } else {
2533             $cache[$course->id] = array();
2534         }
2535     }
2537     if (empty($cache[$course->id][$forum->id])) {
2538         return 0;
2539     }
2541     $groupmode = groups_get_activity_groupmode($cm, $course);
2543     if ($groupmode != SEPARATEGROUPS) {
2544         return $cache[$course->id][$forum->id];
2545     }
2547     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2548         return $cache[$course->id][$forum->id];
2549     }
2551     require_once($CFG->dirroot.'/course/lib.php');
2553     $modinfo =& get_fast_modinfo($course);
2554     if (is_null($modinfo->groups)) {
2555         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2556     }
2558     if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2559         $mygroups = $modinfo->groups[$cm->groupingid];
2560     } else {
2561         $mygroups = false; // Will be set below
2562     }
2564     // add all groups posts
2565     if (empty($mygroups)) {
2566         $mygroups = array(-1=>-1);
2567     } else {
2568         $mygroups[-1] = -1;
2569     }
2571     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2572     $params[] = $forum->id;
2574     if (!empty($CFG->forum_enabletimedposts)) {
2575         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2576         $params[] = $now;
2577         $params[] = $now;
2578     } else {
2579         $timedsql = "";
2580     }
2582     $sql = "SELECT COUNT(d.id)
2583               FROM {forum_discussions} d
2584              WHERE d.groupid $mygroups_sql AND d.forum = ?
2585                    $timedsql";
2587     return $DB->get_field_sql($sql, $params);
2590 /**
2591  * How many unrated posts are in the given discussion for a given user?
2592  *
2593  * @global object
2594  * @global object
2595  * @param int $discussionid
2596  * @param int $userid
2597  * @return mixed
2598  */
2599 function forum_count_unrated_posts($discussionid, $userid) {
2600     global $CFG, $DB;
2601     if ($posts = $DB->get_record_sql("SELECT count(*) as num
2602                                    FROM {forum_posts}
2603                                   WHERE parent > 0
2604                                     AND discussion = ?
2605                                     AND userid <> ? ", array($discussionid, $userid))) {
2607         if ($rated = $DB->get_record_sql("SELECT count(*) as num
2608                                        FROM {forum_posts} p,
2609                                             {forum_ratings} r
2610                                       WHERE p.discussion = ?
2611                                         AND p.id = r.post
2612                                         AND r.userid = ?", array($discussionid, $userid))) {
2613             $difference = $posts->num - $rated->num;
2614             if ($difference > 0) {
2615                 return $difference;
2616             } else {
2617                 return 0;    // Just in case there was a counting error
2618             }
2619         } else {
2620             return $posts->num;
2621         }
2622     } else {
2623         return 0;
2624     }
2627 /**
2628  * Get all discussions in a forum
2629  *
2630  * @global object
2631  * @global object
2632  * @global object
2633  * @uses CONTEXT_MODULE
2634  * @uses VISIBLEGROUPS
2635  * @param object $cm
2636  * @param string $forumsort
2637  * @param bool $fullpost
2638  * @param int $unused
2639  * @param int $limit
2640  * @param bool $userlastmodified
2641  * @param int $page
2642  * @param int $perpage
2643  * @return array
2644  */
2645 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2646     global $CFG, $DB, $USER;
2648     $timelimit = '';
2650     $modcontext = null;
2652     $now = round(time(), -2);
2653     $params = array($cm->instance);
2655     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2657     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2658         return array();
2659     }
2661     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2663         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2664             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2665             $params[] = $now;
2666             $params[] = $now;
2667             if (isloggedin()) {
2668                 $timelimit .= " OR d.userid = ?";
2669                 $params[] = $USER->id;
2670             }
2671             $timelimit .= ")";
2672         }
2673     }
2675     if ($limit > 0) {
2676         $limitfrom = 0;
2677         $limitnum  = $limit;
2678     } else if ($page != -1) {
2679         $limitfrom = $page*$perpage;
2680         $limitnum  = $perpage;
2681     } else {
2682         $limitfrom = 0;
2683         $limitnum  = 0;
2684     }
2686     $groupmode    = groups_get_activity_groupmode($cm);
2687     $currentgroup = groups_get_activity_group($cm);
2689     if ($groupmode) {
2690         if (empty($modcontext)) {
2691             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2692         }
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     }
2716     if (empty($forumsort)) {
2717         $forumsort = "d.timemodified DESC";
2718     }
2719     if (empty($fullpost)) {
2720         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2721     } else {
2722         $postdata = "p.*";
2723     }
2725     if (empty($userlastmodified)) {  // We don't need to know this
2726         $umfields = "";
2727         $umtable  = "";
2728     } else {
2729         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2730         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2731     }
2733     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2734                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2735               FROM {forum_discussions} d
2736                    JOIN {forum_posts} p ON p.discussion = d.id
2737                    JOIN {user} u ON p.userid = u.id
2738                    $umtable
2739              WHERE d.forum = ? AND p.parent = 0
2740                    $timelimit $groupselect
2741           ORDER BY $forumsort";
2742     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2745 /**
2746  *
2747  * @global object
2748  * @global object
2749  * @global object
2750  * @uses CONTEXT_MODULE
2751  * @uses VISIBLEGROUPS
2752  * @param object $cm
2753  * @return array
2754  */
2755 function forum_get_discussions_unread($cm) {
2756     global $CFG, $DB, $USER;
2758     $now = round(time(), -2);
2759     $params = array($cutoffdate);
2760     $groupmode    = groups_get_activity_groupmode($cm);
2761     $currentgroup = groups_get_activity_group($cm);
2763     if ($groupmode) {
2764         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2766         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2767             if ($currentgroup) {
2768                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2769                 $params[] = $currentgroup;
2770             } else {
2771                 $groupselect = "";
2772             }
2774         } else {
2775             //seprate groups without access all
2776             if ($currentgroup) {
2777                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2778                 $params[] = $currentgroup;
2779             } else {
2780                 $groupselect = "AND d.groupid = -1";
2781             }
2782         }
2783     } else {
2784         $groupselect = "";
2785     }
2787     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2789     if (!empty($CFG->forum_enabletimedposts)) {
2790         $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2791         $params[] = $now;
2792         $params[] = $now;
2793     } else {
2794         $timedsql = "";
2795     }
2797     $sql = "SELECT d.id, COUNT(p.id) AS unread
2798               FROM {forum_discussions} d
2799                    JOIN {forum_posts} p     ON p.discussion = d.id
2800                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2801              WHERE d.forum = {$cm->instance}
2802                    AND p.modified >= ? AND r.id is NULL
2803                    $groupselect
2804                    $timedsql
2805           GROUP BY d.id";
2806     if ($unreads = $DB->get_records_sql($sql, $params)) {
2807         foreach ($unreads as $unread) {
2808             $unreads[$unread->id] = $unread->unread;
2809         }
2810         return $unreads;
2811     } else {
2812         return array();
2813     }
2816 /**
2817  * @global object
2818  * @global object
2819  * @global object
2820  * @uses CONEXT_MODULE
2821  * @uses VISIBLEGROUPS
2822  * @param object $cm
2823  * @return array
2824  */
2825 function forum_get_discussions_count($cm) {
2826     global $CFG, $DB, $USER;
2828     $now = round(time(), -2);
2829     $params = array($cm->instance);
2830     $groupmode    = groups_get_activity_groupmode($cm);
2831     $currentgroup = groups_get_activity_group($cm);
2833     if ($groupmode) {
2834         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2836         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2837             if ($currentgroup) {
2838                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2839                 $params[] = $currentgroup;
2840             } else {
2841                 $groupselect = "";
2842             }
2844         } else {
2845             //seprate groups without access all
2846             if ($currentgroup) {
2847                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2848                 $params[] = $currentgroup;
2849             } else {
2850                 $groupselect = "AND d.groupid = -1";
2851             }
2852         }
2853     } else {
2854         $groupselect = "";
2855     }
2857     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2859     $timelimit = "";
2861     if (!empty($CFG->forum_enabletimedposts)) {
2863         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2865         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2866             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2867             $params[] = $now;
2868             $params[] = $now;
2869             if (isloggedin()) {
2870                 $timelimit .= " OR d.userid = ?";
2871                 $params[] = $USER->id;
2872             }
2873             $timelimit .= ")";
2874         }
2875     }
2877     $sql = "SELECT COUNT(d.id)
2878               FROM {forum_discussions} d
2879                    JOIN {forum_posts} p ON p.discussion = d.id
2880              WHERE d.forum = ? AND p.parent = 0
2881                    $groupselect $timelimit";
2883     return $DB->get_field_sql($sql, $params);
2887 /**
2888  * Get all discussions started by a particular user in a course (or group)
2889  * This function no longer used ...
2890  *
2891  * @todo Remove this function if no longer used
2892  * @global object
2893  * @global object
2894  * @param int $courseid
2895  * @param int $userid
2896  * @param int $groupid
2897  * @return array
2898  */
2899 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2900     global $CFG, $DB;
2901     $params = array($courseid, $userid);
2902     if ($groupid) {
2903         $groupselect = " AND d.groupid = ? ";
2904         $params[] = $groupid;
2905     } else  {
2906         $groupselect = "";
2907     }
2909     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2910                                    f.type as forumtype, f.name as forumname, f.id as forumid
2911                               FROM {forum_discussions} d,
2912                                    {forum_posts} p,
2913                                    {user} u,
2914                                    {forum} f
2915                              WHERE d.course = ?
2916                                AND p.discussion = d.id
2917                                AND p.parent = 0
2918                                AND p.userid = u.id
2919                                AND u.id = ?
2920                                AND d.forum = f.id $groupselect
2921                           ORDER BY p.created DESC", $params);
2924 /**
2925  * Get the list of potential subscribers to a forum.
2926  *
2927  * @param object $forumcontext the forum context.
2928  * @param integer $groupid the id of a group, or 0 for all groups.
2929  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2930  * @param string $sort sort order. As for get_users_by_capability.
2931  * @return array list of users.
2932  */
2933 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2934     return get_users_by_capability($forumcontext, 'mod/forum:initialsubscriptions', $fields, $sort, '', '', $groupid, '', false, true);
2937 /**
2938  * Returns list of user objects that are subscribed to this forum
2939  *
2940  * @global object
2941  * @global object
2942  * @param object $course the course
2943  * @param forum $forum the forum
2944  * @param integer $groupid group id, or 0 for all.
2945  * @param object $context the forum context, to save re-fetching it where possible.
2946  * @return array list of users.
2947  */
2948 function forum_subscribed_users($course, $forum, $groupid=0, $context = NULL) {
2949     global $CFG, $DB;
2950     $params = array($forum->id);
2952     if ($groupid) {
2953         $grouptables = ", {groups_members} gm ";
2954         $groupselect = "AND gm.groupid = ? AND u.id = gm.userid";
2955         $params[] = $groupid;
2956     } else  {
2957         $grouptables = '';
2958         $groupselect = '';
2959     }
2961     $fields ="u.id,
2962               u.username,
2963               u.firstname,
2964               u.lastname,
2965               u.maildisplay,
2966               u.mailformat,
2967               u.maildigest,
2968               u.emailstop,
2969               u.imagealt,
2970               u.email,
2971               u.city,
2972               u.country,
2973               u.lastaccess,
2974               u.lastlogin,
2975               u.picture,
2976               u.timezone,
2977               u.theme,
2978               u.lang,
2979               u.trackforums,
2980               u.mnethostid";
2982     if (forum_is_forcesubscribed($forum)) {
2983         if (empty($context)) {
2984             $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2985             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2986         }
2987         $sort = "u.email ASC";
2988         $results = forum_get_potential_subscribers($context, $groupid, $fields, $sort);
2989     } else {
2990         $results = $DB->get_records_sql("SELECT $fields
2991                               FROM {user} u,
2992                                    {forum_subscriptions} s $grouptables
2993                              WHERE s.forum = ?
2994                                AND s.userid = u.id
2995                                AND u.deleted = 0  $groupselect
2996                           ORDER BY u.email ASC", $params);
2997     }
2999     static $guestid = null;
3001     if (is_null($guestid)) {
3002         if ($guest = guest_user()) {
3003             $guestid = $guest->id;
3004         } else {
3005             $guestid = 0;
3006         }
3007     }
3009     // Guest user should never be subscribed to a forum.
3010     unset($results[$guestid]);
3012     return $results;
3017 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
3020 /**
3021  * @global object
3022  * @global object
3023  * @param int $courseid
3024  * @param string $type
3025  */
3026 function forum_get_course_forum($courseid, $type) {
3027 // How to set up special 1-per-course forums
3028     global $CFG, $DB, $OUTPUT;
3030     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
3031         // There should always only be ONE, but with the right combination of
3032         // errors there might be more.  In this case, just return the oldest one (lowest ID).
3033         foreach ($forums as $forum) {
3034             return $forum;   // ie the first one
3035         }
3036     }
3038     // Doesn't exist, so create one now.
3039     $forum->course = $courseid;
3040     $forum->type = "$type";
3041     switch ($forum->type) {
3042         case "news":
3043             $forum->name  = get_string("namenews", "forum");
3044             $forum->intro = get_string("intronews", "forum");
3045             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
3046             $forum->assessed = 0;
3047             if ($courseid == SITEID) {
3048                 $forum->name  = get_string("sitenews");
3049                 $forum->forcesubscribe = 0;
3050             }
3051             break;
3052         case "social":
3053             $forum->name  = get_string("namesocial", "forum");
3054             $forum->intro = get_string("introsocial", "forum");
3055             $forum->assessed = 0;
3056             $forum->forcesubscribe = 0;
3057             break;
3058         case "blog":
3059             $forum->name = get_string('blogforum', 'forum');
3060             $forum->intro = get_string('introblog', 'forum');
3061             $forum->assessed = 0;
3062             $forum->forcesubscribe = 0;
3063             break;
3064         default:
3065             echo $OUTPUT->notification("That forum type doesn't exist!");
3066             return false;
3067             break;
3068     }
3070     $forum->timemodified = time();
3071     $forum->id = $DB->insert_record("forum", $forum);
3073     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
3074         echo $OUTPUT->notification("Could not find forum module!!");
3075         return false;
3076     }
3077     $mod = new object();
3078     $mod->course = $courseid;
3079     $mod->module = $module->id;
3080     $mod->instance = $forum->id;
3081     $mod->section = 0;
3082     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
3083         echo $OUTPUT->notification("Could not add a new course module to the course '" . format_string($course->fullname) . "'");
3084         return false;
3085     }
3086     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
3087         echo $OUTPUT->notification("Could not add the new course module to that section");
3088         return false;
3089     }
3090     if (! $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule))) {
3091         echo $OUTPUT->notification("Could not update the course module with the correct section");
3092         return false;
3093     }
3094     include_once("$CFG->dirroot/course/lib.php");
3095     rebuild_course_cache($courseid);
3097     return $DB->get_record("forum", array("id" => "$forum->id"));
3101 /**
3102  * Given the data about a posting, builds up the HTML to display it and
3103  * returns the HTML in a string.  This is designed for sending via HTML email.
3104  *
3105  * @global object
3106  * @param object $course
3107  * @param object $cm
3108  * @param object $forum
3109  * @param object $discussion
3110  * @param object $post
3111  * @param object $userform
3112  * @param object $userto
3113  * @param bool $ownpost
3114  * @param bool $reply
3115  * @param bool $link
3116  * @param bool $rate
3117  * @param string $footer
3118  * @return string
3119  */
3120 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
3121                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
3123     global $CFG, $OUTPUT;
3125     if (!isset($userto->viewfullnames[$forum->id])) {
3126         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
3127             print_error('invalidcoursemodule');
3128         }
3129         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3130         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3131     } else {
3132         $viewfullnames = $userto->viewfullnames[$forum->id];
3133     }
3135     // format the post body
3136     $options = new object();
3137     $options->para = true;
3138     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3140     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3142     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3143     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3144     $output .= '</td>';
3146     if ($post->parent) {
3147         $output .= '<td class="topic">';
3148     } else {
3149         $output .= '<td class="topic starter">';
3150     }
3151     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3153     $fullname = fullname($userfrom, $viewfullnames);
3154     $by = new object();
3155     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3156     $by->date = userdate($post->modified, '', $userto->timezone);
3157     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3159     $output .= '</td></tr>';
3161     $output .= '<tr><td class="left side" valign="top">';
3163     if (isset($userfrom->groups)) {
3164         $groups = $userfrom->groups[$forum->id];
3165     } else {
3166         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
3167             print_error('invalidcoursemodule');
3168         }
3169         $group = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3170     }
3172     if ($groups) {
3173         $output .= print_group_picture($groups, $course->id, false, true, true);
3174     } else {
3175         $output .= '&nbsp;';
3176     }
3178     $output .= '</td><td class="content">';
3180     $attachments = forum_print_attachments($post, $cm, 'html');
3181     if ($attachments !== '') {
3182         $output .= '<div class="attachments">';
3183         $output .= $attachments;
3184         $output .= '</div>';
3185     }
3187     $output .= $formattedtext;
3189 // Commands
3190     $commands = array();
3192     if ($post->parent) {
3193         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3194                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3195     }
3197     if ($reply) {
3198         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3199                       get_string('reply', 'forum').'</a>';
3200     }
3202     $output .= '<div class="commands">';
3203     $output .= implode(' | ', $commands);
3204     $output .= '</div>';
3206 // Context link to post if required
3207     if ($link) {
3208         $output .= '<div class="link">';
3209         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3210                      get_string('postincontext', 'forum').'</a>';
3211         $output .= '</div>';
3212     }
3214     if ($footer) {
3215         $output .= '<div class="footer">'.$footer.'</div>';
3216     }
3217     $output .= '</td></tr></table>'."\n\n";
3219     return $output;
3222 /**
3223  * Print a forum post
3224  *
3225  * @global object
3226  * @global object
3227  * @uses FORUM_MODE_THREADED
3228  * @uses PORTFOLIO_FORMAT_PLAINHTML
3229  * @uses PORTFOLIO_FORMAT_FILE
3230  * @uses PORTFOLIO_FORMAT_RICHHTML
3231  * @uses PORTFOLIO_ADD_TEXT_LINK
3232  * @uses CONTEXT_MODULE
3233  * @param object $post The post to print.
3234  * @param object $discussion
3235  * @param object $forum
3236  * @param object $cm
3237  * @param object $course
3238  * @param boolean $ownpost Whether this post belongs to the current user.
3239  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3240  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3241  * @param object $ratings -- I don't really know --
3242  * @param string $footer Extra stuff to print after the message.
3243  * @param string $highlight Space-separated list of terms to highlight.
3244  * @param int $post_read true, false or -99. If we already know whether this user
3245  *          has read this post, pass that in, otherwise, pass in -99, and this
3246  *          function will work it out.
3247  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3248  *          the current user can't see this post, if this argument is true
3249  *          (the default) then print a dummy 'you can't see this post' post.
3250  *          If false, don't output anything at all.
3251  * @param bool|null $istracked
3252  */
3253 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3254                           $ratings=NULL, $footer="", $highlight="", $post_read=null, $dummyifcantsee=true, $istracked=null) {
3256     global $USER, $CFG, $OUTPUT;
3258     static $stredit, $strdelete, $strreply, $strparent, $strprune;
3259     static $strpruneheading, $displaymode;
3260     static $strmarkread, $strmarkunread;
3262     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3264     $post->course = $course->id;
3265     $post->forum  = $forum->id;
3266     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'forum_post', $post->id);
3268     // caching
3269     if (!isset($cm->cache)) {
3270         $cm->cache = new object();
3271     }
3273     if (!isset($cm->cache->caps)) {
3274         $cm->cache->caps = array();
3275         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3276         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3277         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3278         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3279         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3280         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3281         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3282         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3283         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3284     }
3286     if (!isset($cm->uservisible)) {
3287         $cm->uservisible = coursemodule_visible_for_user($cm);
3288     }
3290     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3291         if (!$dummyifcantsee) {
3292             return;
3293         }
3294         echo '<a id="p'.$post->id.'"></a>';
3295         echo '<table cellspacing="0" class="forumpost">';
3296         echo '<tr class="header"><td class="picture left">';
3297         //        print_user_picture($post->userid, $courseid, $post->picture);
3298         echo '</td>';
3299         if ($post->parent) {
3300             echo '<td class="topic">';
3301         } else {
3302             echo '<td class="topic starter">';
3303         }
3304         echo '<div class="subject">'.get_string('forumsubjecthidden','forum').'</div>';
3305         echo '<div class="author">';
3306         print_string('forumauthorhidden','forum');
3307         echo '</div></td></tr>';
3309         echo '<tr><td class="left side">';
3310         echo '&nbsp;';
3312         // Actual content
3314         echo '</td><td class="content">'."\n";
3315         echo get_string('forumbodyhidden','forum');
3316         echo '</td></tr></table>';
3317         return;
3318     }
3320     if (empty($stredit)) {
3321         $stredit         = get_string('edit', 'forum');
3322         $strdelete       = get_string('delete', 'forum');
3323         $strreply        = get_string('reply', 'forum');
3324         $strparent       = get_string('parent', 'forum');
3325         $strpruneheading = get_string('pruneheading', 'forum');
3326         $strprune        = get_string('prune', 'forum');
3327         $displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3328         $strmarkread     = get_string('markread', 'forum');
3329         $strmarkunread   = get_string('markunread', 'forum');
3331     }
3333     $read_style = '';
3334     // ignore trackign status if not tracked or tracked param missing
3335     if ($istracked) {
3336         if (is_null($post_read)) {
3337             debugging('fetching post_read info');
3338             $post_read = forum_tp_is_post_read($USER->id, $post);
3339         }
3341         if ($post_read) {
3342             $read_style = ' read';
3343         } else {
3344             $read_style = ' unread';
3345             echo '<a name="unread"></a>';
3346         }
3347     }
3349     echo '<a id="p'.$post->id.'"></a>';
3350     echo '<table cellspacing="0" class="forumpost'.$read_style.'">';
3352     // Picture
3353     $postuser = new object();
3354     $postuser->id        = $post->userid;
3355     $postuser->firstname = $post->firstname;
3356     $postuser->lastname  = $post->lastname;
3357     $postuser->imagealt  = $post->imagealt;
3358     $postuser->picture   = $post->picture;
3360     echo '<tr class="header"><td class="picture left">';
3361     echo $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3362     echo '</td>';
3364     if ($post->parent) {
3365         echo '<td class="topic">';
3366     } else {
3367         echo '<td class="topic starter">';
3368     }
3370     if (!empty($post->subjectnoformat)) {
3371         echo '<div class="subject">'.$post->subject.'</div>';
3372     } else {
3373         echo '<div class="subject">'.format_string($post->subject).'</div>';
3374     }
3376     echo '<div class="author">';
3377     $fullname = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3378     $by = new object();
3379     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.
3380                 $post->userid.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3381     $by->date = userdate($post->modified);
3382     print_string('bynameondate', 'forum', $by);
3383     echo '</div></td></tr>';
3385     echo '<tr><td class="left side">';
3386     if (isset($cm->cache->usersgroups)) {
3387         $groups = array();
3388         if (isset($cm->cache->usersgroups[$post->userid])) {
3389             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3390                 $groups[$gid] = $cm->cache->groups[$gid];
3391             }
3392         }
3393     } else {
3394         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3395     }
3397     if ($groups) {
3398         print_group_picture($groups, $course->id, false, false, true);
3399     } else {
3400         echo '&nbsp;';
3401     }
3403 // Actual content
3405     echo '</td><td class="content">'."\n";
3407     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3409     if ($attachments !== '') {
3410         echo '<div class="attachments">';
3411         echo $attachments;
3412         echo '</div>';
3413     }
3415     $options = new object();
3416     $options->para    = false;
3417     $options->trusted = $post->messagetrust;
3418     if ($link and (strlen(strip_tags($post->message)) > $CFG->forum_longpost)) {
3419         // Print shortened version
3420         echo format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3421         $numwords = count_words(strip_tags($post->message));
3422         echo '<div class="posting"><a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3423         echo get_string('readtherest', 'forum');
3424         echo '</a> ('.get_string('numwords', '', $numwords).')...</div>';
3425     } else {
3426         // Print whole message
3427         echo '<div class="posting">';
3428         if ($highlight) {
3429             echo highlight($highlight, format_text($post->message, $post->messageformat, $options, $course->id));
3430         } else {
3431             echo format_text($post->message, $post->messageformat, $options, $course->id);
3432         }
3433         echo '</div>';
3434         echo $attachedimages;
3435     }
3438 // Commands
3440     $commands = array();
3442     if ($istracked) {
3443         // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3444         // Don't display the mark read / unread controls in this case.
3445         if ($CFG->forum_usermarksread and isloggedin()) {
3446             if ($post_read) {
3447                 $mcmd = '&amp;mark=unread&amp;postid='.$post->id;
3448                 $mtxt = $strmarkunread;
3449             } else {
3450                 $mcmd = '&amp;mark=read&amp;postid='.$post->id;
3451                 $mtxt = $strmarkread;
3452             }
3453             if ($displaymode == FORUM_MODE_THREADED) {
3454                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3455                               $post->discussion.'&amp;parent='.$post->id.$mcmd.'">'.$mtxt.'</a>';
3456             } else {
3457                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3458                               $post->discussion.$mcmd.'#p'.$post->id.'">'.$mtxt.'</a>';
3459             }
3460         }
3461     }
3463     if ($post->parent) {  // Zoom in to the parent specifically
3464         if ($displaymode == FORUM_MODE_THREADED) {
3465             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3466                       $post->discussion.'&amp;parent='.$post->parent.'">'.$strparent.'</a>';
3467         } else {
3468             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3469                       $post->discussion.'#p'.$post->parent.'">'.$strparent.'</a>';
3470         }
3471     }
3473     $age = time() - $post->created;
3474     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3475     if (!$post->parent and $forum->type == 'news' and $discussion->timestart > time()) {
3476         $age = 0;
3477     }
3478     $editanypost = $cm->cache->caps['mod/forum:editanypost'];
3480     if ($ownpost or $editanypost) {
3481         if (($age < $CFG->maxeditingtime) or $editanypost) {
3482             $commands[] =  '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?edit='.$post->id.'">'.$stredit.'</a>';
3483         }
3484     }
3486     if ($cm->cache->caps['mod/forum:splitdiscussions']