5f6c4a83e97f92c9aaee50d08ef3460b540b2dff
[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_FORCESUBSCRIBE', 1);
38 define('FORUM_INITIALSUBSCRIBE', 2);
39 define('FORUM_DISALLOWSUBSCRIBE',3);
41 define('FORUM_TRACKING_OFF', 0);
42 define('FORUM_TRACKING_OPTIONAL', 1);
43 define('FORUM_TRACKING_ON', 2);
45 define('FORUM_UNSET_POST_RATING', -999);
47 define ('FORUM_AGGREGATE_NONE', 0); //no ratings
48 define ('FORUM_AGGREGATE_AVG', 1);
49 define ('FORUM_AGGREGATE_COUNT', 2);
50 define ('FORUM_AGGREGATE_MAX', 3);
51 define ('FORUM_AGGREGATE_MIN', 4);
52 define ('FORUM_AGGREGATE_SUM', 5);
54 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
56 /**
57  * Given an object containing all the necessary data,
58  * (defined by the form in mod_form.php) this function
59  * will create a new instance and return the id number
60  * of the new instance.
61  *
62  * @global object
63  * @global object
64  * @param object $forum add forum instance (with magic quotes)
65  * @return int intance id
66  */
67 function forum_add_instance($forum) {
68     global $CFG, $DB;
70     $forum->timemodified = time();
72     if (empty($forum->assessed)) {
73         $forum->assessed = 0;
74     }
76     if (empty($forum->ratingtime) or empty($forum->assessed)) {
77         $forum->assesstimestart  = 0;
78         $forum->assesstimefinish = 0;
79     }
81     $forum->id = $DB->insert_record('forum', $forum);
83     if ($forum->type == 'single') {  // Create related discussion.
84         $discussion = new object();
85         $discussion->course        = $forum->course;
86         $discussion->forum         = $forum->id;
87         $discussion->name          = $forum->name;
88         $discussion->intro         = $forum->intro;
89         $discussion->assessed      = $forum->assessed;
90         $discussion->messageformat = $forum->messageformat;
91         $discussion->mailnow       = false;
92         $discussion->groupid       = -1;
94         $message = '';
96         if (! forum_add_discussion($discussion, null, $message)) {
97             print_error('cannotadd', 'forum');
98         }
99     }
101     if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
102     /// all users should be subscribed initially
103     /// Note: forum_get_potential_subscribers should take the forum context,
104     /// but that does not exist yet, becuase the forum is only half build at this
105     /// stage. However, because the forum is brand new, we know that there are
106     /// no role assignments or overrides in the forum context, so using the
107     /// course context gives the same list of users.
108         $users = forum_get_potential_subscribers(get_context_instance(CONTEXT_COURSE, $forum->course), 0, 'u.id, u.email', '');
109         foreach ($users as $user) {
110             forum_subscribe($user->id, $forum->id);
111         }
112     }
114     forum_grade_item_update($forum);
116     return $forum->id;
120 /**
121  * Given an object containing all the necessary data,
122  * (defined by the form in mod_form.php) this function
123  * will update an existing instance with new data.
124  *
125  * @global object
126  * @param object $forum forum instance (with magic quotes)
127  * @return bool success
128  */
129 function forum_update_instance($forum) {
130     global $DB, $OUTPUT, $USER;
132     $forum->timemodified = time();
133     $forum->id           = $forum->instance;
135     if (empty($forum->assessed)) {
136         $forum->assessed = 0;
137     }
139     if (empty($forum->ratingtime) or empty($forum->assessed)) {
140         $forum->assesstimestart  = 0;
141         $forum->assesstimefinish = 0;
142     }
144     $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
146     // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
147     // if  scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
148     // for count and sum aggregation types the grade we check to make sure they do not exceed the scale (i.e. max score) when calculating the grade
149     if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
150         forum_update_grades($forum); // recalculate grades for the forum
151     }
153     if ($forum->type == 'single') {  // Update related discussion and post.
154         if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
155             if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC')) {
156                 echo $OUTPUT->notification('Warning! There is more than one discussion in this forum - using the most recent');
157                 $discussion = array_pop($discussions);
158             } else {
159                 // try to recover by creating initial discussion - MDL-16262
160                 $discussion = new object();
161                 $discussion->course          = $forum->course;
162                 $discussion->forum           = $forum->id;
163                 $discussion->name            = $forum->name;
164                 $discussion->intro           = $forum->intro;
165                 $discussion->assessed        = $forum->assessed;
166                 $discussion->messageformat   = $forum->messageformat;
167                 $discussion->mailnow         = false;
168                 $discussion->groupid         = -1;
170                 forum_add_discussion($discussion, null, $message);
172                 if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
173                     print_error('cannotadd', 'forum');
174                 }
175             }
176         }
177         if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
178             print_error('cannotfindfirstpost', 'forum');
179         }
181         $post->subject  = $forum->name;
182         $post->message  = $forum->intro;
183         $post->modified = $forum->timemodified;
184         $post->userid   = $USER->id;    // MDL-18599, so that current teacher can take ownership of activities
186         $DB->update_record('forum_posts', $post);
187         $discussion->name = $forum->name;
188         $DB->update_record('forum_discussions', $discussion);
189     }
191     $DB->update_record('forum', $forum);
193     forum_grade_item_update($forum);
195     return true;
199 /**
200  * Given an ID of an instance of this module,
201  * this function will permanently delete the instance
202  * and any data that depends on it.
203  *
204  * @global object
205  * @param int $id forum instance id
206  * @return bool success
207  */
208 function forum_delete_instance($id) {
209     global $DB;
211     if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
212         return false;
213     }
214     if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
215         return false;
216     }
217     if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
218         return false;
219     }
221     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
223     // now get rid of all files
224     $fs = get_file_storage();
225     $fs->delete_area_files($context->id);
227     $result = true;
229     if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
230         foreach ($discussions as $discussion) {
231             if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
232                 $result = false;
233             }
234         }
235     }
237     if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
238         $result = false;
239     }
241     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
243     if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
244         $result = false;
245     }
247     forum_grade_item_delete($forum);
249     return $result;
253 /**
254  * Indicates API features that the forum supports.
255  *
256  * @uses FEATURE_GROUPS
257  * @uses FEATURE_GROUPINGS
258  * @uses FEATURE_GROUPMEMBERSONLY
259  * @uses FEATURE_MOD_INTRO
260  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
261  * @uses FEATURE_COMPLETION_HAS_RULES
262  * @uses FEATURE_GRADE_HAS_GRADE
263  * @uses FEATURE_GRADE_OUTCOMES
264  * @param string $feature
265  * @return mixed True if yes (some features may use other values)
266  */
267 function forum_supports($feature) {
268     switch($feature) {
269         case FEATURE_GROUPS:                  return true;
270         case FEATURE_GROUPINGS:               return true;
271         case FEATURE_GROUPMEMBERSONLY:        return true;
272         case FEATURE_MOD_INTRO:               return true;
273         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
274         case FEATURE_COMPLETION_HAS_RULES:    return true;
275         case FEATURE_GRADE_HAS_GRADE:         return true;
276         case FEATURE_GRADE_OUTCOMES:          return true;
278         default: return null;
279     }
283 /**
284  * Obtains the automatic completion state for this forum based on any conditions
285  * in forum settings.
286  *
287  * @global object
288  * @global object
289  * @param object $course Course
290  * @param object $cm Course-module
291  * @param int $userid User ID
292  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
293  * @return bool True if completed, false if not. (If no conditions, then return
294  *   value depends on comparison type)
295  */
296 function forum_get_completion_state($course,$cm,$userid,$type) {
297     global $CFG,$DB;
299     // Get forum details
300     if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
301         throw new Exception("Can't find forum {$cm->instance}");
302     }
304     $result=$type; // Default return value
306     $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
307     $postcountsql="
308 SELECT
309     COUNT(1)
310 FROM
311     {forum_posts} fp
312     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
313 WHERE
314     fp.userid=:userid AND fd.forum=:forumid";
316     if ($forum->completiondiscussions) {
317         $value = $forum->completiondiscussions <=
318                  $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
319         if ($type == COMPLETION_AND) {
320             $result = $result && $value;
321         } else {
322             $result = $result || $value;
323         }
324     }
325     if ($forum->completionreplies) {
326         $value = $forum->completionreplies <=
327                  $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
328         if ($type==COMPLETION_AND) {
329             $result = $result && $value;
330         } else {
331             $result = $result || $value;
332         }
333     }
334     if ($forum->completionposts) {
335         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
336         if ($type == COMPLETION_AND) {
337             $result = $result && $value;
338         } else {
339             $result = $result || $value;
340         }
341     }
343     return $result;
347 /**
348  * Function to be run periodically according to the moodle cron
349  * Finds all posts that have yet to be mailed out, and mails them
350  * out to all subscribers
351  *
352  * @global object
353  * @global object
354  * @global object
355  * @uses CONTEXT_MODULE
356  * @uses CONTEXT_COURSE
357  * @uses SITEID
358  * @uses FORMAT_PLAIN
359  * @return void
360  */
361 function forum_cron() {
362     global $CFG, $USER, $DB;
364     $site = get_site();
366     // all users that are subscribed to any post that needs sending
367     $users = array();
369     // status arrays
370     $mailcount  = array();
371     $errorcount = array();
373     // caches
374     $discussions     = array();
375     $forums          = array();
376     $courses         = array();
377     $coursemodules   = array();
378     $subscribedusers = array();
381     // Posts older than 2 days will not be mailed.  This is to avoid the problem where
382     // cron has not been running for a long time, and then suddenly people are flooded
383     // with mail from the past few weeks or months
384     $timenow   = time();
385     $endtime   = $timenow - $CFG->maxeditingtime;
386     $starttime = $endtime - 48 * 3600;   // Two days earlier
388     if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
389         // Mark them all now as being mailed.  It's unlikely but possible there
390         // might be an error later so that a post is NOT actually mailed out,
391         // but since mail isn't crucial, we can accept this risk.  Doing it now
392         // prevents the risk of duplicated mails, which is a worse problem.
394         if (!forum_mark_old_posts_as_mailed($endtime)) {
395             mtrace('Errors occurred while trying to mark some posts as being mailed.');
396             return false;  // Don't continue trying to mail them, in case we are in a cron loop
397         }
399         // checking post validity, and adding users to loop through later
400         foreach ($posts as $pid => $post) {
402             $discussionid = $post->discussion;
403             if (!isset($discussions[$discussionid])) {
404                 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
405                     $discussions[$discussionid] = $discussion;
406                 } else {
407                     mtrace('Could not find discussion '.$discussionid);
408                     unset($posts[$pid]);
409                     continue;
410                 }
411             }
412             $forumid = $discussions[$discussionid]->forum;
413             if (!isset($forums[$forumid])) {
414                 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
415                     $forums[$forumid] = $forum;
416                 } else {
417                     mtrace('Could not find forum '.$forumid);
418                     unset($posts[$pid]);
419                     continue;
420                 }
421             }
422             $courseid = $forums[$forumid]->course;
423             if (!isset($courses[$courseid])) {
424                 if ($course = $DB->get_record('course', array('id' => $courseid))) {
425                     $courses[$courseid] = $course;
426                 } else {
427                     mtrace('Could not find course '.$courseid);
428                     unset($posts[$pid]);
429                     continue;
430                 }
431             }
432             if (!isset($coursemodules[$forumid])) {
433                 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
434                     $coursemodules[$forumid] = $cm;
435                 } else {
436                     mtrace('Could not course module for forum '.$forumid);
437                     unset($posts[$pid]);
438                     continue;
439                 }
440             }
443             // caching subscribed users of each forum
444             if (!isset($subscribedusers[$forumid])) {
445                 $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
446                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext)) {
447                     foreach ($subusers as $postuser) {
448                         // do not try to mail users with stopped email
449                         if ($postuser->emailstop) {
450                             if (!empty($CFG->forum_logblocked)) {
451                                 add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
452                             }
453                             continue;
454                         }
455                         // this user is subscribed to this forum
456                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
457                         // this user is a user we have to process later
458                         $users[$postuser->id] = $postuser;
459                     }
460                     unset($subusers); // release memory
461                 }
462             }
464             $mailcount[$pid] = 0;
465             $errorcount[$pid] = 0;
466         }
467     }
469     if ($users && $posts) {
471         $urlinfo = parse_url($CFG->wwwroot);
472         $hostname = $urlinfo['host'];
474         foreach ($users as $userto) {
476             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
478             // set this so that the capabilities are cached, and environment matches receiving user
479             cron_setup_user($userto);
481             mtrace('Processing user '.$userto->id);
483             // init caches
484             $userto->viewfullnames = array();
485             $userto->canpost       = array();
486             $userto->markposts     = array();
487             $userto->enrolledin    = array();
489             // reset the caches
490             foreach ($coursemodules as $forumid=>$unused) {
491                 $coursemodules[$forumid]->cache       = new object();
492                 $coursemodules[$forumid]->cache->caps = array();
493                 unset($coursemodules[$forumid]->uservisible);
494             }
496             foreach ($posts as $pid => $post) {
498                 // Set up the environment for the post, discussion, forum, course
499                 $discussion = $discussions[$post->discussion];
500                 $forum      = $forums[$discussion->forum];
501                 $course     = $courses[$forum->course];
502                 $cm         =& $coursemodules[$forum->id];
504                 // Do some checks  to see if we can bail out now
505                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
506                     continue; // user does not subscribe to this forum
507                 }
509                 // Verify user is enrollend in course - if not do not send any email
510                 if (!isset($userto->enrolledin[$course->id])) {
511                     $userto->enrolledin[$course->id] = has_capability('moodle/course:view', get_context_instance(CONTEXT_COURSE, $course->id));
512                 }
513                 if (!$userto->enrolledin[$course->id]) {
514                     // oops - this user should not receive anything from this course
515                     continue;
516                 }
518                 // Get info about the sending user
519                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
520                     $userfrom = $users[$post->userid];
521                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
522                     $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
523                 } else {
524                     mtrace('Could not find user '.$post->userid);
525                     continue;
526                 }
528                 // setup global $COURSE properly - needed for roles and languages
529                 cron_setup_user($userto, $course);
531                 // Fill caches
532                 if (!isset($userto->viewfullnames[$forum->id])) {
533                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
534                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
535                 }
536                 if (!isset($userto->canpost[$discussion->id])) {
537                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
538                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
539                 }
540                 if (!isset($userfrom->groups[$forum->id])) {
541                     if (!isset($userfrom->groups)) {
542                         $userfrom->groups = array();
543                         $users[$userfrom->id]->groups = array();
544                     }
545                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
546                     $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
547                 }
549                 // Make sure groups allow this user to see this email
550                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
551                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
552                         continue;                           // Be safe and don't send it to anyone
553                     }
555                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
556                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
557                         continue;
558                     }
559                 }
561                 // Make sure we're allowed to see it...
562                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
563                     mtrace('user '.$userto->id. ' can not see '.$post->id);
564                     continue;
565                 }
567                 // OK so we need to send the email.
569                 // Does the user want this post in a digest?  If so postpone it for now.
570                 if ($userto->maildigest > 0) {
571                     // This user wants the mails to be in digest form
572                     $queue = new object();
573                     $queue->userid       = $userto->id;
574                     $queue->discussionid = $discussion->id;
575                     $queue->postid       = $post->id;
576                     $queue->timemodified = $post->created;
577                     $DB->insert_record('forum_queue', $queue);
578                     continue;
579                 }
582                 // Prepare to actually send the post now, and build up the content
584                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
586                 $userfrom->customheaders = array (  // Headers to make emails easier to track
587                            'Precedence: Bulk',
588                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
589                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
590                            'Message-ID: <moodlepost'.$post->id.'@'.$hostname.'>',
591                            'In-Reply-To: <moodlepost'.$post->parent.'@'.$hostname.'>',
592                            'References: <moodlepost'.$post->parent.'@'.$hostname.'>',
593                            'X-Course-Id: '.$course->id,
594                            'X-Course-Name: '.format_string($course->fullname, true)
595                 );
598                 $postsubject = "$course->shortname: ".format_string($post->subject,true);
599                 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
600                 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
602                 // Send the post now!
604                 mtrace('Sending ', '');
606                 $eventdata = new object();
607                 $eventdata->component        = 'mod/forum';
608                 $eventdata->name             = 'posts';
609                 $eventdata->userfrom         = $userfrom;
610                 $eventdata->userto           = $userto;
611                 $eventdata->subject          = $postsubject;
612                 $eventdata->fullmessage      = $posttext;
613                 $eventdata->fullmessageformat = FORMAT_PLAIN;
614                 $eventdata->fullmessagehtml  = $posthtml;
615                 $eventdata->smallmessage     = '';
616                 if (!message_send($eventdata)){
618                     mtrace("Error: mod/forum/cron.php: Could not send out mail for id $post->id to user $userto->id".
619                          " ($userto->email) .. not trying again.");
620                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
621                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
622                     $errorcount[$post->id]++;
623                 } else if ($mailresult === 'emailstop') {
624                     // should not be reached anymore - see check above
625                 } else {
626                     $mailcount[$post->id]++;
628                 // Mark post as read if forum_usermarksread is set off
629                     if (!$CFG->forum_usermarksread) {
630                         $userto->markposts[$post->id] = $post->id;
631                     }
632                 }
634                 mtrace('post '.$post->id. ': '.$post->subject);
635             }
637             // mark processed posts as read
638             forum_tp_mark_posts_read($userto, $userto->markposts);
639         }
640     }
642     if ($posts) {
643         foreach ($posts as $post) {
644             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
645             if ($errorcount[$post->id]) {
646                 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
647             }
648         }
649     }
651     // release some memory
652     unset($subscribedusers);
653     unset($mailcount);
654     unset($errorcount);
656     cron_setup_user();
658     $sitetimezone = $CFG->timezone;
660     // Now see if there are any digest mails waiting to be sent, and if we should send them
662     mtrace('Starting digest processing...');
664     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
666     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
667         set_config('digestmailtimelast', 0);
668     }
670     $timenow = time();
671     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
673     // Delete any really old ones (normally there shouldn't be any)
674     $weekago = $timenow - (7 * 24 * 3600);
675     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
676     mtrace ('Cleaned old digest records');
678     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
680         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
682         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
684         if ($digestposts_rs->valid()) {
686             // We have work to do
687             $usermailcount = 0;
689             //caches - reuse the those filled before too
690             $discussionposts = array();
691             $userdiscussions = array();
693             foreach ($digestposts_rs as $digestpost) {
694                 if (!isset($users[$digestpost->userid])) {
695                     if ($user = $DB->get_record('user', array('id' => $digestpost->userid))) {
696                         $users[$digestpost->userid] = $user;
697                     } else {
698                         continue;
699                     }
700                 }
701                 $postuser = $users[$digestpost->userid];
702                 if ($postuser->emailstop) {
703                     if (!empty($CFG->forum_logblocked)) {
704                         add_to_log(SITEID, 'forum', 'mail blocked', '', '', 0, $postuser->id);
705                     }
706                     continue;
707                 }
709                 if (!isset($posts[$digestpost->postid])) {
710                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
711                         $posts[$digestpost->postid] = $post;
712                     } else {
713                         continue;
714                     }
715                 }
716                 $discussionid = $digestpost->discussionid;
717                 if (!isset($discussions[$discussionid])) {
718                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
719                         $discussions[$discussionid] = $discussion;
720                     } else {
721                         continue;
722                     }
723                 }
724                 $forumid = $discussions[$discussionid]->forum;
725                 if (!isset($forums[$forumid])) {
726                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
727                         $forums[$forumid] = $forum;
728                     } else {
729                         continue;
730                     }
731                 }
733                 $courseid = $forums[$forumid]->course;
734                 if (!isset($courses[$courseid])) {
735                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
736                         $courses[$courseid] = $course;
737                     } else {
738                         continue;
739                     }
740                 }
742                 if (!isset($coursemodules[$forumid])) {
743                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
744                         $coursemodules[$forumid] = $cm;
745                     } else {
746                         continue;
747                     }
748                 }
749                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
750                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
751             }
752             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
754             // Data collected, start sending out emails to each user
755             foreach ($userdiscussions as $userid => $thesediscussions) {
757                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
759                 cron_setup_user();
761                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
763                 // First of all delete all the queue entries for this user
764                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
765                 $userto = $users[$userid];
767                 // Override the language and timezone of the "current" user, so that
768                 // mail is customised for the receiver.
769                 cron_setup_user($userto);
771                 // init caches
772                 $userto->viewfullnames = array();
773                 $userto->canpost       = array();
774                 $userto->markposts     = array();
776                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
778                 $headerdata = new object();
779                 $headerdata->sitename = format_string($site->fullname, true);
780                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
782                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
783                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
785                 $posthtml = "<head>";
786 /*                foreach ($CFG->stylesheets as $stylesheet) {
787                     //TODO: MDL-21120
788                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
789                 }*/
790                 $posthtml .= "</head>\n<body id=\"email\">\n";
791                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
793                 foreach ($thesediscussions as $discussionid) {
795                     @set_time_limit(120);   // to be reset for each post
797                     $discussion = $discussions[$discussionid];
798                     $forum      = $forums[$discussion->forum];
799                     $course     = $courses[$forum->course];
800                     $cm         = $coursemodules[$forum->id];
802                     //override language
803                     cron_setup_user($userto, $course);
805                     // Fill caches
806                     if (!isset($userto->viewfullnames[$forum->id])) {
807                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
808                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
809                     }
810                     if (!isset($userto->canpost[$discussion->id])) {
811                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
812                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
813                     }
815                     $strforums      = get_string('forums', 'forum');
816                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
817                     $canreply       = $userto->canpost[$discussion->id];
819                     $posttext .= "\n \n";
820                     $posttext .= '=====================================================================';
821                     $posttext .= "\n \n";
822                     $posttext .= "$course->shortname -> $strforums -> ".format_string($forum->name,true);
823                     if ($discussion->name != $forum->name) {
824                         $posttext  .= " -> ".format_string($discussion->name,true);
825                     }
826                     $posttext .= "\n";
828                     $posthtml .= "<p><font face=\"sans-serif\">".
829                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> -> ".
830                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
831                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
832                     if ($discussion->name == $forum->name) {
833                         $posthtml .= "</font></p>";
834                     } else {
835                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
836                     }
837                     $posthtml .= '<p>';
839                     $postsarray = $discussionposts[$discussionid];
840                     sort($postsarray);
842                     foreach ($postsarray as $postid) {
843                         $post = $posts[$postid];
845                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
846                             $userfrom = $users[$post->userid];
847                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
848                             $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
849                         } else {
850                             mtrace('Could not find user '.$post->userid);
851                             continue;
852                         }
854                         if (!isset($userfrom->groups[$forum->id])) {
855                             if (!isset($userfrom->groups)) {
856                                 $userfrom->groups = array();
857                                 $users[$userfrom->id]->groups = array();
858                             }
859                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
860                             $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
861                         }
863                         $userfrom->customheaders = array ("Precedence: Bulk");
865                         if ($userto->maildigest == 2) {
866                             // Subjects only
867                             $by = new object();
868                             $by->name = fullname($userfrom);
869                             $by->date = userdate($post->modified);
870                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
871                             $posttext .= "\n---------------------------------------------------------------------";
873                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
874                             $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>';
876                         } else {
877                             // The full treatment
878                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
879                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
881                         // Create an array of postid's for this user to mark as read.
882                             if (!$CFG->forum_usermarksread) {
883                                 $userto->markposts[$post->id] = $post->id;
884                             }
885                         }
886                     }
887                     if ($canunsubscribe) {
888                         $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>";
889                     } else {
890                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
891                     }
892                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
893                 }
894                 $posthtml .= '</body>';
896                 if ($userto->mailformat != 1) {
897                     // This user DOESN'T want to receive HTML
898                     $posthtml = '';
899                 }
901                 $eventdata = new object();
902                 $eventdata->component        = 'mod/forum';
903                 $eventdata->name             = 'digests';
904                 $eventdata->userfrom         = $site->shortname;
905                 $eventdata->userto           = $userto;
906                 $eventdata->subject          = $postsubject;
907                 $eventdata->fullmessage      = $posttext;
908                 $eventdata->fullmessageformat = FORMAT_PLAIN;
909                 $eventdata->fullmessagehtml  = $posthtml;
910                 $eventdata->smallmessage     = '';
911                 if (!message_send($eventdata)){
912                     mtrace("ERROR!");
913                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
914                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
915                 } else if ($mailresult === 'emailstop') {
916                     // should not happen anymore - see check above
917                 } else {
918                     mtrace("success.");
919                     $usermailcount++;
921                     // Mark post as read if forum_usermarksread is set off
922                     forum_tp_mark_posts_read($userto, $userto->markposts);
923                 }
924             }
925         }
926     /// We have finishied all digest emails, update $CFG->digestmailtimelast
927         set_config('digestmailtimelast', $timenow);
928     }
930     cron_setup_user();
932     if (!empty($usermailcount)) {
933         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
934     }
936     if (!empty($CFG->forum_lastreadclean)) {
937         $timenow = time();
938         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
939             set_config('forum_lastreadclean', $timenow);
940             mtrace('Removing old forum read tracking info...');
941             forum_tp_clean_read_records();
942         }
943     } else {
944         set_config('forum_lastreadclean', time());
945     }
948     return true;
951 /**
952  * Builds and returns the body of the email notification in plain text.
953  *
954  * @global object
955  * @global object
956  * @uses CONTEXT_MODULE
957  * @param object $course
958  * @param object $cm
959  * @param object $forum
960  * @param object $discussion
961  * @param object $post
962  * @param object $userfrom
963  * @param object $userto
964  * @param boolean $bare
965  * @return string The email body in plain text format.
966  */
967 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
968     global $CFG, $USER;
970     if (!isset($userto->viewfullnames[$forum->id])) {
971         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
972         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
973     } else {
974         $viewfullnames = $userto->viewfullnames[$forum->id];
975     }
977     if (!isset($userto->canpost[$discussion->id])) {
978         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
979         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
980     } else {
981         $canreply = $userto->canpost[$discussion->id];
982     }
984     $by = New stdClass;
985     $by->name = fullname($userfrom, $viewfullnames);
986     $by->date = userdate($post->modified, "", $userto->timezone);
988     $strbynameondate = get_string('bynameondate', 'forum', $by);
990     $strforums = get_string('forums', 'forum');
992     $canunsubscribe = ! forum_is_forcesubscribed($forum);
994     $posttext = '';
996     if (!$bare) {
997         $posttext  = "$course->shortname -> $strforums -> ".format_string($forum->name,true);
999         if ($discussion->name != $forum->name) {
1000             $posttext  .= " -> ".format_string($discussion->name,true);
1001         }
1002     }
1004     $posttext .= "\n---------------------------------------------------------------------\n";
1005     $posttext .= format_string($post->subject,true);
1006     if ($bare) {
1007         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1008     }
1009     $posttext .= "\n".$strbynameondate."\n";
1010     $posttext .= "---------------------------------------------------------------------\n";
1011     $posttext .= format_text_email($post->message, $post->messageformat);
1012     $posttext .= "\n\n";
1013     $posttext .= forum_print_attachments($post, $cm, "text");
1015     if (!$bare && $canreply) {
1016         $posttext .= "---------------------------------------------------------------------\n";
1017         $posttext .= get_string("postmailinfo", "forum", $course->shortname)."\n";
1018         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1019     }
1020     if (!$bare && $canunsubscribe) {
1021         $posttext .= "\n---------------------------------------------------------------------\n";
1022         $posttext .= get_string("unsubscribe", "forum");
1023         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1024     }
1026     return $posttext;
1029 /**
1030  * Builds and returns the body of the email notification in html format.
1031  *
1032  * @global object
1033  * @param object $course
1034  * @param object $cm
1035  * @param object $forum
1036  * @param object $discussion
1037  * @param object $post
1038  * @param object $userfrom
1039  * @param object $userto
1040  * @return string The email text in HTML format
1041  */
1042 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1043     global $CFG;
1045     if ($userto->mailformat != 1) {  // Needs to be HTML
1046         return '';
1047     }
1049     if (!isset($userto->canpost[$discussion->id])) {
1050         $canreply = forum_user_can_post($forum, $discussion, $userto);
1051     } else {
1052         $canreply = $userto->canpost[$discussion->id];
1053     }
1055     $strforums = get_string('forums', 'forum');
1056     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1058     $posthtml = '<head>';
1059 /*    foreach ($CFG->stylesheets as $stylesheet) {
1060         //TODO: MDL-21120
1061         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1062     }*/
1063     $posthtml .= '</head>';
1064     $posthtml .= "\n<body id=\"email\">\n\n";
1066     $posthtml .= '<div class="navbar">'.
1067     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$course->shortname.'</a> &raquo; '.
1068     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1069     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1070     if ($discussion->name == $forum->name) {
1071         $posthtml .= '</div>';
1072     } else {
1073         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1074                      format_string($discussion->name,true).'</a></div>';
1075     }
1076     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1078     if ($canunsubscribe) {
1079         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1080                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1081                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1082     }
1084     $posthtml .= '</body>';
1086     return $posthtml;
1090 /**
1091  *
1092  * @param object $course
1093  * @param object $user
1094  * @param object $mod TODO this is not used in this function, refactor
1095  * @param object $forum
1096  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1097  */
1098 function forum_user_outline($course, $user, $mod, $forum) {
1099     global $CFG;
1100     require_once("$CFG->libdir/gradelib.php");
1101     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1102     if (empty($grades->items[0]->grades)) {
1103         $grade = false;
1104     } else {
1105         $grade = reset($grades->items[0]->grades);
1106     }
1108     $count = forum_count_user_posts($forum->id, $user->id);
1110     if ($count && $count->postcount > 0) {
1111         $result = new object();
1112         $result->info = get_string("numposts", "forum", $count->postcount);
1113         $result->time = $count->lastpost;
1114         if ($grade) {
1115             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1116         }
1117         return $result;
1118     } else if ($grade) {
1119         $result = new object();
1120         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1121         $result->time = $grade->dategraded;
1122         return $result;
1123     }
1124     return NULL;
1128 /**
1129  * @global object
1130  * @global object
1131  * @param object $coure
1132  * @param object $user
1133  * @param object $mod
1134  * @param object $forum
1135  */
1136 function forum_user_complete($course, $user, $mod, $forum) {
1137     global $CFG,$USER, $OUTPUT;
1138     require_once("$CFG->libdir/gradelib.php");
1140     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1141     if (!empty($grades->items[0]->grades)) {
1142         $grade = reset($grades->items[0]->grades);
1143         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1144         if ($grade->str_feedback) {
1145             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1146         }
1147     }
1149     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1151         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1152             print_error('invalidcoursemodule');
1153         }
1154         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1156         // preload all user ratings for these discussions - one query only and minimal memory
1157         $cm->cache->ratings = array();
1158         $cm->cache->myratings = array();
1159         if ($postratings = forum_get_all_user_ratings($user->id, $discussions)) {
1160             foreach ($postratings as $pr) {
1161                 if (!isset($cm->cache->ratings[$pr->postid])) {
1162                     $cm->cache->ratings[$pr->postid] = array();
1163                 }
1164                 $cm->cache->ratings[$pr->postid][$pr->id] = $pr->rating;
1166                 if ($pr->userid == $USER->id) {
1167                     $cm->cache->myratings[$pr->postid] = $pr->rating;
1168                 }
1169             }
1170             unset($postratings);
1171         }
1173         foreach ($posts as $post) {
1174             if (!isset($discussions[$post->discussion])) {
1175                 continue;
1176             }
1177             $discussion = $discussions[$post->discussion];
1179             $ratings = null;
1181             if ($forum->assessed) {
1182                 if ($scale = make_grades_menu($forum->scale)) {
1183                     $ratings =new object();
1184                     $ratings->scale = $scale;
1185                     $ratings->assesstimestart = $forum->assesstimestart;
1186                     $ratings->assesstimefinish = $forum->assesstimefinish;
1187                     $ratings->allow = false;
1188                 }
1189             }
1190             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false, $ratings);
1191         }
1192     } else {
1193         echo "<p>".get_string("noposts", "forum")."</p>";
1194     }
1202 /**
1203  * @global object
1204  * @global object
1205  * @global object
1206  * @param array $courses
1207  * @param array $htmlarray
1208  */
1209 function forum_print_overview($courses,&$htmlarray) {
1210     global $USER, $CFG, $DB, $SESSION;
1212     //$LIKE = $DB->sql_ilike();//no longer using like in queries. MDL-20578
1214     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1215         return array();
1216     }
1218     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1219         return;
1220     }
1223     // get all forum logs in ONE query (much better!)
1224     $params = array();
1225     $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {log} l "
1226         ." JOIN {course_modules} cm ON cm.id = cmid "
1227         ." WHERE (";
1228     foreach ($courses as $course) {
1229         $sql .= '(l.course = ? AND l.time > ?) OR ';
1230         $params[] = $course->id;
1231         $params[] = $course->lastaccess;
1232     }
1233     $sql = substr($sql,0,-3); // take off the last OR
1235     $sql .= ") AND l.module = 'forum' AND action = 'add post' "
1236         ." AND userid != ? GROUP BY cmid,l.course,instance";
1238     $params[] = $USER->id;
1240     if (!$new = $DB->get_records_sql($sql, $params)) {
1241         $new = array(); // avoid warnings
1242     }
1244     // also get all forum tracking stuff ONCE.
1245     $trackingforums = array();
1246     foreach ($forums as $forum) {
1247         if (forum_tp_can_track_forums($forum)) {
1248             $trackingforums[$forum->id] = $forum;
1249         }
1250     }
1252     if (count($trackingforums) > 0) {
1253         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1254         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1255             ' FROM {forum_posts} p '.
1256             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1257             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1258         $params = array($USER->id);
1260         foreach ($trackingforums as $track) {
1261             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1262             $params[] = $track->id;
1263             if (isset($SESSION->currentgroup[$track->course])) {
1264                 $groupid =  $SESSION->currentgroup[$track->course];
1265             } else {
1266                 $groupid = groups_get_all_groups($track->course, $USER->id);
1267                 if (is_array($groupid)) {
1268                     $groupid = array_shift(array_keys($groupid));
1269                     $SESSION->currentgroup[$track->course] = $groupid;
1270                 } else {
1271                     $groupid = 0;
1272                 }
1273             }
1274             $params[] = $groupid;
1275         }
1276         $sql = substr($sql,0,-3); // take off the last OR
1277         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1278         $params[] = $cutoffdate;
1280         if (!$unread = $DB->get_records_sql($sql, $params)) {
1281             $unread = array();
1282         }
1283     } else {
1284         $unread = array();
1285     }
1287     if (empty($unread) and empty($new)) {
1288         return;
1289     }
1291     $strforum = get_string('modulename','forum');
1292     $strnumunread = get_string('overviewnumunread','forum');
1293     $strnumpostssince = get_string('overviewnumpostssince','forum');
1295     foreach ($forums as $forum) {
1296         $str = '';
1297         $count = 0;
1298         $thisunread = 0;
1299         $showunread = false;
1300         // either we have something from logs, or trackposts, or nothing.
1301         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1302             $count = $new[$forum->id]->count;
1303         }
1304         if (array_key_exists($forum->id,$unread)) {
1305             $thisunread = $unread[$forum->id]->count;
1306             $showunread = true;
1307         }
1308         if ($count > 0 || $thisunread > 0) {
1309             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1310                 $forum->name.'</a></div>';
1311             $str .= '<div class="info">';
1312             $str .= $count.' '.$strnumpostssince;
1313             if (!empty($showunread)) {
1314                 $str .= '<br />'.$thisunread .' '.$strnumunread;
1315             }
1316             $str .= '</div></div>';
1317         }
1318         if (!empty($str)) {
1319             if (!array_key_exists($forum->course,$htmlarray)) {
1320                 $htmlarray[$forum->course] = array();
1321             }
1322             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1323                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1324             }
1325             $htmlarray[$forum->course]['forum'] .= $str;
1326         }
1327     }
1330 /**
1331  * Given a course and a date, prints a summary of all the new
1332  * messages posted in the course since that date
1333  *
1334  * @global object
1335  * @global object
1336  * @global object
1337  * @uses CONTEXT_MODULE
1338  * @uses VISIBLEGROUPS
1339  * @param object $course
1340  * @param bool $viewfullnames capability
1341  * @param int $timestart
1342  * @return bool success
1343  */
1344 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1345     global $CFG, $USER, $DB, $OUTPUT;
1347     // do not use log table if possible, it may be huge and is expensive to join with other tables
1349     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1350                                               d.timestart, d.timeend, d.userid AS duserid,
1351                                               u.firstname, u.lastname, u.email, u.picture
1352                                          FROM {forum_posts} p
1353                                               JOIN {forum_discussions} d ON d.id = p.discussion
1354                                               JOIN {forum} f             ON f.id = d.forum
1355                                               JOIN {user} u              ON u.id = p.userid
1356                                         WHERE p.created > ? AND f.course = ?
1357                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1358          return false;
1359     }
1361     $modinfo =& get_fast_modinfo($course);
1363     $groupmodes = array();
1364     $cms    = array();
1366     $strftimerecent = get_string('strftimerecent');
1368     $printposts = array();
1369     foreach ($posts as $post) {
1370         if (!isset($modinfo->instances['forum'][$post->forum])) {
1371             // not visible
1372             continue;
1373         }
1374         $cm = $modinfo->instances['forum'][$post->forum];
1375         if (!$cm->uservisible) {
1376             continue;
1377         }
1378         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1380         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1381             continue;
1382         }
1384         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1385           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1386             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1387                 continue;
1388             }
1389         }
1391         $groupmode = groups_get_activity_groupmode($cm, $course);
1393         if ($groupmode) {
1394             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1395                 // oki (Open discussions have groupid -1)
1396             } else {
1397                 // separate mode
1398                 if (isguestuser()) {
1399                     // shortcut
1400                     continue;
1401                 }
1403                 if (is_null($modinfo->groups)) {
1404                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1405                 }
1407                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1408                     continue;
1409                 }
1410             }
1411         }
1413         $printposts[] = $post;
1414     }
1415     unset($posts);
1417     if (!$printposts) {
1418         return false;
1419     }
1421     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1422     echo "\n<ul class='unlist'>\n";
1424     foreach ($printposts as $post) {
1425         $subjectclass = empty($post->parent) ? ' bold' : '';
1427         echo '<li><div class="head">'.
1428                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1429                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1430              '</div>';
1431         echo '<div class="info'.$subjectclass.'">';
1432         if (empty($post->parent)) {
1433             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1434         } else {
1435             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1436         }
1437         $post->subject = break_up_long_words(format_string($post->subject, true));
1438         echo $post->subject;
1439         echo "</a>\"</div></li>\n";
1440     }
1442     echo "</ul>\n";
1444     return true;
1447 /**
1448  * Return grade for given user or all users.
1449  *
1450  * @global object
1451  * @global object
1452  * @param object $forum
1453  * @param int $userid optional user id, 0 means all users
1454  * @return array array of grades, false if none
1455  */
1456 function forum_get_user_grades($forum, $userid=0) {
1457     global $CFG, $DB;
1459     $params= array();
1460     if ($userid) {
1461         $params[] = $userid;
1462         $user = "AND u.id = ?";
1463     } else {
1464         $user = "";
1465     }
1467     $params[] = $forum->id;
1469     $aggtype = $forum->assessed;
1470     switch ($aggtype) {
1471         case FORUM_AGGREGATE_COUNT :
1472             $sql = "SELECT u.id, u.id AS userid, COUNT(fr.rating) AS rawgrade
1473                       FROM {user} u, {forum_posts} fp,
1474                            {forum_ratings} fr, {forum_discussions} fd
1475                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1476                            AND fr.userid != u.id AND fd.forum = ?
1477                            $user
1478                   GROUP BY u.id";
1479             break;
1480         case FORUM_AGGREGATE_MAX :
1481             $sql = "SELECT u.id, u.id AS userid, MAX(fr.rating) AS rawgrade
1482                       FROM {user} u, {forum_posts} fp,
1483                            {forum_ratings} fr, {forum_discussions} fd
1484                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1485                            AND fr.userid != u.id AND fd.forum = ?
1486                            $user
1487                   GROUP BY u.id";
1488             break;
1489         case FORUM_AGGREGATE_MIN :
1490             $sql = "SELECT u.id, u.id AS userid, MIN(fr.rating) AS rawgrade
1491                       FROM {user} u, {forum_posts} fp,
1492                            {forum_ratings} fr, {forum_discussions} fd
1493                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1494                            AND fr.userid != u.id AND fd.forum = ?
1495                            $user
1496                   GROUP BY u.id";
1497             break;
1498         case FORUM_AGGREGATE_SUM :
1499             $sql = "SELECT u.id, u.id AS userid, SUM(fr.rating) AS rawgrade
1500                      FROM {user} u, {forum_posts} fp,
1501                           {forum_ratings} fr, {forum_discussions} fd
1502                     WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1503                           AND fr.userid != u.id AND fd.forum = ?
1504                           $user
1505                  GROUP BY u.id";
1506             break;
1507         default : //avg
1508             $sql = "SELECT u.id, u.id AS userid, AVG(fr.rating) AS rawgrade
1509                       FROM {user} u, {forum_posts} fp,
1510                            {forum_ratings} fr, {forum_discussions} fd
1511                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1512                            AND fr.userid != u.id AND fd.forum = ?
1513                            $user
1514                   GROUP BY u.id";
1515             break;
1516     }
1518     if ($results = $DB->get_records_sql($sql, $params)) {
1519         // it could throw off the grading if count and sum returned a rawgrade higher than scale
1520         // 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)
1521         foreach ($results as $rid=>$result) {
1522             if ($forum->scale >= 0) {
1523                 //numeric
1524                 if ($result->rawgrade > $forum->scale) {
1525                     $results[$rid]->rawgrade = $forum->scale;
1526                 }
1527             } else {
1528                 //scales
1529                 if ($scale = $DB->get_record('scale', array('id' => -$forum->scale))) {
1530                     $scale = explode(',', $scale->scale);
1531                     $max = count($scale);
1532                     if ($result->rawgrade > $max) {
1533                         $results[$rid]->rawgrade = $max;
1534                     }
1535                 }
1536             }
1537         }
1538     }
1540     return $results;
1543 /**
1544  * Update activity grades
1545  *
1546  * @global object
1547  * @global object
1548  * @param object $forum
1549  * @param int $userid specific user only, 0 means all
1550  * @param boolean $nullifnone return null if grade does not exist
1551  * @return void
1552  */
1553 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1554     global $CFG, $DB;
1555     require_once($CFG->libdir.'/gradelib.php');
1557     if (!$forum->assessed) {
1558         forum_grade_item_update($forum);
1560     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1561         forum_grade_item_update($forum, $grades);
1563     } else if ($userid and $nullifnone) {
1564         $grade = new object();
1565         $grade->userid   = $userid;
1566         $grade->rawgrade = NULL;
1567         forum_grade_item_update($forum, $grade);
1569     } else {
1570         forum_grade_item_update($forum);
1571     }
1574 /**
1575  * Update all grades in gradebook.
1576  * @global object
1577  */
1578 function forum_upgrade_grades() {
1579     global $DB;
1581     $sql = "SELECT COUNT('x')
1582               FROM {forum} f, {course_modules} cm, {modules} m
1583              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1584     $count = $DB->count_records_sql($sql);
1586     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1587               FROM {forum} f, {course_modules} cm, {modules} m
1588              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1589     if ($rs = $DB->get_recordset_sql($sql)) {
1590         $pbar = new progress_bar('forumupgradegrades', 500, true);
1591         $i=0;
1592         foreach ($rs as $forum) {
1593             $i++;
1594             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1595             forum_update_grades($forum, 0, false);
1596             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1597         }
1598         $rs->close();
1599     }
1602 /**
1603  * Create/update grade item for given forum
1604  *
1605  * @global object
1606  * @uses GRADE_TYPE_NONE
1607  * @uses GRADE_TYPE_VALUE
1608  * @uses GRADE_TYPE_SCALE
1609  * @param object $forum object with extra cmidnumber
1610  * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
1611  * @return int 0 if ok
1612  */
1613 function forum_grade_item_update($forum, $grades=NULL) {
1614     global $CFG;
1615     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1616         require_once($CFG->libdir.'/gradelib.php');
1617     }
1619     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1621     if (!$forum->assessed or $forum->scale == 0) {
1622         $params['gradetype'] = GRADE_TYPE_NONE;
1624     } else if ($forum->scale > 0) {
1625         $params['gradetype'] = GRADE_TYPE_VALUE;
1626         $params['grademax']  = $forum->scale;
1627         $params['grademin']  = 0;
1629     } else if ($forum->scale < 0) {
1630         $params['gradetype'] = GRADE_TYPE_SCALE;
1631         $params['scaleid']   = -$forum->scale;
1632     }
1634     if ($grades  === 'reset') {
1635         $params['reset'] = true;
1636         $grades = NULL;
1637     }
1639     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1642 /**
1643  * Delete grade item for given forum
1644  *
1645  * @global object
1646  * @param object $forum object
1647  * @return object grade_item
1648  */
1649 function forum_grade_item_delete($forum) {
1650     global $CFG;
1651     require_once($CFG->libdir.'/gradelib.php');
1653     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1657 /**
1658  * Returns the users with data in one forum
1659  * (users with records in forum_subscriptions, forum_posts and forum_ratings, students)
1660  *
1661  * @global object
1662  * @global object
1663  * @param int $forumid
1664  * @return mixed array or false if none
1665  */
1666 function forum_get_participants($forumid) {
1668     global $CFG, $DB;
1670     //Get students from forum_subscriptions
1671     $st_subscriptions = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1672                                          FROM {user} u,
1673                                               {forum_subscriptions} s
1674                                          WHERE s.forum = ? AND
1675                                                u.id = s.userid", array($forumid));
1676     //Get students from forum_posts
1677     $st_posts = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1678                                  FROM {user} u,
1679                                       {forum_discussions} d,
1680                                       {forum_posts} p
1681                                  WHERE d.forum = ? AND
1682                                        p.discussion = d.id AND
1683                                        u.id = p.userid", array($forumid));
1685     //Get students from forum_ratings
1686     $st_ratings = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1687                                    FROM {user} u,
1688                                         {forum_discussions} d,
1689                                         {forum_posts} p,
1690                                         {forum_ratings} r
1691                                    WHERE d.forum = ? AND
1692                                          p.discussion = d.id AND
1693                                          r.post = p.id AND
1694                                          u.id = r.userid", array($forumid));
1696     //Add st_posts to st_subscriptions
1697     if ($st_posts) {
1698         foreach ($st_posts as $st_post) {
1699             $st_subscriptions[$st_post->id] = $st_post;
1700         }
1701     }
1702     //Add st_ratings to st_subscriptions
1703     if ($st_ratings) {
1704         foreach ($st_ratings as $st_rating) {
1705             $st_subscriptions[$st_rating->id] = $st_rating;
1706         }
1707     }
1708     //Return st_subscriptions array (it contains an array of unique users)
1709     return ($st_subscriptions);
1712 /**
1713  * This function returns if a scale is being used by one forum
1714  *
1715  * @global object
1716  * @param int $forumid
1717  * @param int $scaleid negative number
1718  * @return bool
1719  */
1720 function forum_scale_used ($forumid,$scaleid) {
1721     global $DB;
1722     $return = false;
1724     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1726     if (!empty($rec) && !empty($scaleid)) {
1727         $return = true;
1728     }
1730     return $return;
1733 /**
1734  * Checks if scale is being used by any instance of forum
1735  *
1736  * This is used to find out if scale used anywhere
1737  *
1738  * @global object
1739  * @param $scaleid int
1740  * @return boolean True if the scale is used by any forum
1741  */
1742 function forum_scale_used_anywhere($scaleid) {
1743     global $DB;
1744     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1745         return true;
1746     } else {
1747         return false;
1748     }
1751 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1753 /**
1754  * Gets a post with all info ready for forum_print_post
1755  * Most of these joins are just to get the forum id
1756  *
1757  * @global object
1758  * @global object
1759  * @param int $postid
1760  * @return mixed array of posts or false
1761  */
1762 function forum_get_post_full($postid) {
1763     global $CFG, $DB;
1765     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1766                              FROM {forum_posts} p
1767                                   JOIN {forum_discussions} d ON p.discussion = d.id
1768                                   LEFT JOIN {user} u ON p.userid = u.id
1769                             WHERE p.id = ?", array($postid));
1772 /**
1773  * Gets posts with all info ready for forum_print_post
1774  * We pass forumid in because we always know it so no need to make a
1775  * complicated join to find it out.
1776  *
1777  * @global object
1778  * @global object
1779  * @return mixed array of posts or false
1780  */
1781 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1782     global $CFG, $DB;
1784     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1785                               FROM {forum_posts} p
1786                          LEFT JOIN {user} u ON p.userid = u.id
1787                              WHERE p.discussion = ?
1788                                AND p.parent > 0 $sort", array($discussion));
1791 /**
1792  * Gets all posts in discussion including top parent.
1793  *
1794  * @global object
1795  * @global object
1796  * @global object
1797  * @param int $discussionid
1798  * @param string $sort
1799  * @param bool $tracking does user track the forum?
1800  * @return array of posts
1801  */
1802 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1803     global $CFG, $DB, $USER;
1805     $tr_sel  = "";
1806     $tr_join = "";
1807     $params = array();
1809     if ($tracking) {
1810         $now = time();
1811         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1812         $tr_sel  = ", fr.id AS postread";
1813         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1814         $params[] = $USER->id;
1815     }
1817     $params[] = $discussionid;
1818     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1819                                      FROM {forum_posts} p
1820                                           LEFT JOIN {user} u ON p.userid = u.id
1821                                           $tr_join
1822                                     WHERE p.discussion = ?
1823                                  ORDER BY $sort", $params)) {
1824         return array();
1825     }
1827     foreach ($posts as $pid=>$p) {
1828         if ($tracking) {
1829             if (forum_tp_is_post_old($p)) {
1830                  $posts[$pid]->postread = true;
1831             }
1832         }
1833         if (!$p->parent) {
1834             continue;
1835         }
1836         if (!isset($posts[$p->parent])) {
1837             continue; // parent does not exist??
1838         }
1839         if (!isset($posts[$p->parent]->children)) {
1840             $posts[$p->parent]->children = array();
1841         }
1842         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1843     }
1845     return $posts;
1848 /**
1849  * Gets posts with all info ready for forum_print_post
1850  * We pass forumid in because we always know it so no need to make a
1851  * complicated join to find it out.
1852  *
1853  * @global object
1854  * @global object
1855  * @param int $parent
1856  * @param int $forumid
1857  * @return array
1858  */
1859 function forum_get_child_posts($parent, $forumid) {
1860     global $CFG, $DB;
1862     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1863                               FROM {forum_posts} p
1864                          LEFT JOIN {user} u ON p.userid = u.id
1865                              WHERE p.parent = ?
1866                           ORDER BY p.created ASC", array($parent));
1869 /**
1870  * An array of forum objects that the user is allowed to read/search through.
1871  *
1872  * @global object
1873  * @global object
1874  * @global object
1875  * @param int $userid
1876  * @param int $courseid if 0, we look for forums throughout the whole site.
1877  * @return array of forum objects, or false if no matches
1878  *         Forum objects have the following attributes:
1879  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1880  *         viewhiddentimedposts
1881  */
1882 function forum_get_readable_forums($userid, $courseid=0) {
1884     global $CFG, $DB, $USER;
1885     require_once($CFG->dirroot.'/course/lib.php');
1887     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1888         print_error('notinstalled', 'forum');
1889     }
1891     if ($courseid) {
1892         $courses = $DB->get_records('course', array('id' => $courseid));
1893     } else {
1894         // If no course is specified, then the user can see SITE + his courses.
1895         // And admins can see all courses, so pass the $doanything flag enabled
1896         $courses1 = $DB->get_records('course', array('id' => SITEID));
1897         $courses2 = get_my_courses($userid, null, null, true);
1898         $courses = array_merge($courses1, $courses2);
1899     }
1900     if (!$courses) {
1901         return array();
1902     }
1904     $readableforums = array();
1906     foreach ($courses as $course) {
1908         $modinfo =& get_fast_modinfo($course);
1909         if (is_null($modinfo->groups)) {
1910             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1911         }
1913         if (empty($modinfo->instances['forum'])) {
1914             // hmm, no forums?
1915             continue;
1916         }
1918         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1920         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1921             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1922                 continue;
1923             }
1924             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1925             $forum = $courseforums[$forumid];
1927             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1928                 continue;
1929             }
1931          /// group access
1932             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1933                 if (is_null($modinfo->groups)) {
1934                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1935                 }
1936                 if (empty($CFG->enablegroupings)) {
1937                     $forum->onlygroups = $modinfo->groups[0];
1938                     $forum->onlygroups[] = -1;
1939                 } else if (isset($modinfo->groups[$cm->groupingid])) {
1940                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1941                     $forum->onlygroups[] = -1;
1942                 } else {
1943                     $forum->onlygroups = array(-1);
1944                 }
1945             }
1947         /// hidden timed discussions
1948             $forum->viewhiddentimedposts = true;
1949             if (!empty($CFG->forum_enabletimedposts)) {
1950                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1951                     $forum->viewhiddentimedposts = false;
1952                 }
1953             }
1955         /// qanda access
1956             if ($forum->type == 'qanda'
1957                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1959                 // We need to check whether the user has posted in the qanda forum.
1960                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1961                                                     // the user is allowed to see in this forum.
1962                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1963                     foreach ($discussionspostedin as $d) {
1964                         $forum->onlydiscussions[] = $d->id;
1965                     }
1966                 }
1967             }
1969             $readableforums[$forum->id] = $forum;
1970         }
1972         unset($modinfo);
1974     } // End foreach $courses
1976     //print_object($courses);
1977     //print_object($readableforums);
1979     return $readableforums;
1982 /**
1983  * Returns a list of posts found using an array of search terms.
1984  *
1985  * @global object
1986  * @global object
1987  * @global object
1988  * @param array $searchterms array of search terms, e.g. word +word -word
1989  * @param int $courseid if 0, we search through the whole site
1990  * @param int $limitfrom
1991  * @param int $limitnum
1992  * @param int &$totalcount
1993  * @param string $extrasql
1994  * @return array|bool Array of posts found or false
1995  */
1996 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1997                             &$totalcount, $extrasql='') {
1998     global $CFG, $DB, $USER;
1999     require_once($CFG->libdir.'/searchlib.php');
2001     $forums = forum_get_readable_forums($USER->id, $courseid);
2003     if (count($forums) == 0) {
2004         $totalcount = 0;
2005         return false;
2006     }
2008     $now = round(time(), -2); // db friendly
2010     $fullaccess = array();
2011     $where = array();
2012     $params = array();
2014     foreach ($forums as $forumid => $forum) {
2015         $select = array();
2017         if (!$forum->viewhiddentimedposts) {
2018             $select[] = "(d.userid = :userid OR (d.timestart < : AND (d.timeend = 0 OR d.timeend > :timeend)))";
2019             $params = array('userid'=>$USER->id, 'timestart'=>$now, 'timeend'=>$now);
2020         }
2022         $cm = get_coursemodule_from_instance('forum', $forumid);
2023         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2025         if ($forum->type == 'qanda'
2026             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
2027             if (!empty($forum->onlydiscussions)) {
2028                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda0');
2029                 $params = array_merge($params, $discussionid_params);
2030                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
2031             } else {
2032                 $select[] = "p.parent = 0";
2033             }
2034         }
2036         if (!empty($forum->onlygroups)) {
2037             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps0');
2038             $params = array_merge($params, $groupid_params);
2039             $select[] = "d.groupid $groupid_sql";
2040         }
2042         if ($select) {
2043             $selects = implode(" AND ", $select);
2044             $where[] = "(d.forum = :forum AND $selects)";
2045             $params['forum'] = $forumid;
2046         } else {
2047             $fullaccess[] = $forumid;
2048         }
2049     }
2051     if ($fullaccess) {
2052         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula0');
2053         $params = array_merge($params, $fullid_params);
2054         $where[] = "(d.forum $fullid_sql)";
2055     }
2057     $selectdiscussion = "(".implode(" OR ", $where).")";
2059     $messagesearch = '';
2060     $searchstring = '';
2062     // Need to concat these back together for parser to work.
2063     foreach($searchterms as $searchterm){
2064         if ($searchstring != '') {
2065             $searchstring .= ' ';
2066         }
2067         $searchstring .= $searchterm;
2068     }
2070     // We need to allow quoted strings for the search. The quotes *should* be stripped
2071     // by the parser, but this should be examined carefully for security implications.
2072     $searchstring = str_replace("\\\"","\"",$searchstring);
2073     $parser = new search_parser();
2074     $lexer = new search_lexer($parser);
2076     if ($lexer->parse($searchstring)) {
2077         $parsearray = $parser->get_parsed_array();
2078     // Experimental feature under 1.8! MDL-8830
2079     // Use alternative text searches if defined
2080     // This feature only works under mysql until properly implemented for other DBs
2081     // Requires manual creation of text index for forum_posts before enabling it:
2082     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2083     // Experimental feature under 1.8! MDL-8830
2084         if (!empty($CFG->forum_usetextsearches)) {
2085             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2086                                                  'p.userid', 'u.id', 'u.firstname',
2087                                                  'u.lastname', 'p.modified', 'd.forum');
2088         } else {
2089             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2090                                                  'p.userid', 'u.id', 'u.firstname',
2091                                                  'u.lastname', 'p.modified', 'd.forum');
2092         }
2093         $params = array_merge($params, $msparams);
2094     }
2096     $fromsql = "{forum_posts} p,
2097                   {forum_discussions} d,
2098                   {user} u";
2100     $selectsql = " $messagesearch
2101                AND p.discussion = d.id
2102                AND p.userid = u.id
2103                AND $selectdiscussion
2104                    $extrasql";
2106     $countsql = "SELECT COUNT(*)
2107                    FROM $fromsql
2108                   WHERE $selectsql";
2110     $searchsql = "SELECT p.*,
2111                          d.forum,
2112                          u.firstname,
2113                          u.lastname,
2114                          u.email,
2115                          u.picture,
2116                          u.imagealt
2117                     FROM $fromsql
2118                    WHERE $selectsql
2119                 ORDER BY p.modified DESC";
2121     $totalcount = $DB->count_records_sql($countsql, $params);
2123     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2126 /**
2127  * Returns a list of ratings for all posts in discussion
2128  *
2129  * @global object
2130  * @global object
2131  * @param object $discussion
2132  * @return array of ratings or false
2133  */
2134 function forum_get_all_discussion_ratings($discussion) {
2135     global $CFG, $DB;
2136     return $DB->get_records_sql("SELECT r.id, r.userid, p.id AS postid, r.rating
2137                               FROM {forum_ratings} r,
2138                                    {forum_posts} p
2139                              WHERE r.post = p.id AND p.discussion = ?
2140                              ORDER BY p.id ASC", array($discussion->id));
2143 /**
2144  * Returns a list of ratings for one specific user for all posts in discussion
2145  * @global object
2146  * @global object
2147  * @param object $discussions the discussions for which we return all ratings
2148  * @param int $userid the user for who we return all ratings
2149  * @return array
2150  */
2151 function forum_get_all_user_ratings($userid, $discussions) {
2152     global $CFG, $DB;
2155     foreach ($discussions as $discussion) {
2156      if (!isset($discussionsid)){
2157          $discussionsid = $discussion->id;
2158      }
2159      else {
2160          $discussionsid .= ",".$discussion->id;
2161      }
2162     }
2164     $sql = "SELECT r.id, r.userid, p.id AS postid, r.rating
2165                               FROM {forum_ratings} r,
2166                                    {forum_posts} p
2167                              WHERE r.post = p.id AND p.userid = :userid";
2170     $params = array();
2171     $params['userid'] = $userid;
2172     //postgres compability
2173     if (!isset($discussionsid)) {
2174        $sql .=" AND p.discussion IN (".$discussionsid.")";
2175     }
2176     $sql .=" ORDER BY p.id ASC";
2178     return $DB->get_records_sql($sql, $params);
2183 /**
2184  * Returns a list of ratings for a particular post - sorted.
2185  *
2186  * @global object
2187  * @global object
2188  * @param int $postid
2189  * @param string $sort
2190  * @return array Array of ratings or false
2191  */
2192 function forum_get_ratings($postid, $sort="u.firstname ASC") {
2193     global $CFG, $DB;
2194     return $DB->get_records_sql("SELECT u.*, r.rating, r.time
2195                               FROM {forum_ratings} r,
2196                                    {user} u
2197                              WHERE r.post = ?
2198                                AND r.userid = u.id
2199                              ORDER BY $sort", array($postid));
2202 /**
2203  * Returns a list of all new posts that have not been mailed yet
2204  *
2205  * @global object
2206  * @global object
2207  * @param int $starttime posts created after this time
2208  * @param int $endtime posts created before this
2209  * @param int $now used for timed discussions only
2210  * @return array
2211  */
2212 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2213     global $CFG, $DB;
2215     $params = array($starttime, $endtime);
2216     if (!empty($CFG->forum_enabletimedposts)) {
2217         if (empty($now)) {
2218             $now = time();
2219         }
2220         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2221         $params[] = $now;
2222         $params[] = $now;
2223     } else {
2224         $timedsql = "";
2225     }
2227     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2228                               FROM {forum_posts} p
2229                                    JOIN {forum_discussions} d ON d.id = p.discussion
2230                              WHERE p.mailed = 0
2231                                    AND p.created >= ?
2232                                    AND (p.created < ? OR p.mailnow = 1)
2233                                    $timedsql
2234                           ORDER BY p.modified ASC", $params);
2237 /**
2238  * Marks posts before a certain time as being mailed already
2239  *
2240  * @global object
2241  * @global object
2242  * @param int $endtime
2243  * @param int $now Defaults to time()
2244  * @return bool
2245  */
2246 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2247     global $CFG, $DB;
2248     if (empty($now)) {
2249         $now = time();
2250     }
2252     if (empty($CFG->forum_enabletimedposts)) {
2253         return $DB->execute("UPDATE {forum_posts}
2254                                SET mailed = '1'
2255                              WHERE (created < ? OR mailnow = 1)
2256                                    AND mailed = 0", array($endtime));
2258     } else {
2259         return $DB->execute("UPDATE {forum_posts}
2260                                SET mailed = '1'
2261                              WHERE discussion NOT IN (SELECT d.id
2262                                                         FROM {forum_discussions} d
2263                                                        WHERE d.timestart > ?)
2264                                    AND (created < ? OR mailnow = 1)
2265                                    AND mailed = 0", array($now, $endtime));
2266     }
2269 /**
2270  * Get all the posts for a user in a forum suitable for forum_print_post
2271  *
2272  * @global object
2273  * @global object
2274  * @uses CONTEXT_MODULE
2275  * @return array
2276  */
2277 function forum_get_user_posts($forumid, $userid) {
2278     global $CFG, $DB;
2280     $timedsql = "";
2281     $params = array($forumid, $userid);
2283     if (!empty($CFG->forum_enabletimedposts)) {
2284         $cm = get_coursemodule_from_instance('forum', $forumid);
2285         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2286             $now = time();
2287             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2288             $params[] = $now;
2289             $params[] = $now;
2290         }
2291     }
2293     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2294                               FROM {forum} f
2295                                    JOIN {forum_discussions} d ON d.forum = f.id
2296                                    JOIN {forum_posts} p       ON p.discussion = d.id
2297                                    JOIN {user} u              ON u.id = p.userid
2298                              WHERE f.id = ?
2299                                    AND p.userid = ?
2300                                    $timedsql
2301                           ORDER BY p.modified ASC", $params);
2304 /**
2305  * Get all the discussions user participated in
2306  *
2307  * @global object
2308  * @global object
2309  * @uses CONTEXT_MODULE
2310  * @param int $forumid
2311  * @param int $userid
2312  * @return array Array or false
2313  */
2314 function forum_get_user_involved_discussions($forumid, $userid) {
2315     global $CFG, $DB;
2317     $timedsql = "";
2318     $params = array($forumid, $userid);
2319     if (!empty($CFG->forum_enabletimedposts)) {
2320         $cm = get_coursemodule_from_instance('forum', $forumid);
2321         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2322             $now = time();
2323             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2324             $params[] = $now;
2325             $params[] = $now;
2326         }
2327     }
2329     return $DB->get_records_sql("SELECT DISTINCT d.*
2330                               FROM {forum} f
2331                                    JOIN {forum_discussions} d ON d.forum = f.id
2332                                    JOIN {forum_posts} p       ON p.discussion = d.id
2333                              WHERE f.id = ?
2334                                    AND p.userid = ?
2335                                    $timedsql", $params);
2338 /**
2339  * Get all the posts for a user in a forum suitable for forum_print_post
2340  *
2341  * @global object
2342  * @global object
2343  * @param int $forumid
2344  * @param int $userid
2345  * @return array of counts or false
2346  */
2347 function forum_count_user_posts($forumid, $userid) {
2348     global $CFG, $DB;
2350     $timedsql = "";
2351     $params = array($forumid, $userid);
2352     if (!empty($CFG->forum_enabletimedposts)) {
2353         $cm = get_coursemodule_from_instance('forum', $forumid);
2354         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2355             $now = time();
2356             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2357             $params[] = $now;
2358             $params[] = $now;
2359         }
2360     }
2362     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2363                              FROM {forum} f
2364                                   JOIN {forum_discussions} d ON d.forum = f.id
2365                                   JOIN {forum_posts} p       ON p.discussion = d.id
2366                                   JOIN {user} u              ON u.id = p.userid
2367                             WHERE f.id = ?
2368                                   AND p.userid = ?
2369                                   $timedsql", $params);
2372 /**
2373  * Given a log entry, return the forum post details for it.
2374  *
2375  * @global object
2376  * @global object
2377  * @param object $log
2378  * @return array|null
2379  */
2380 function forum_get_post_from_log($log) {
2381     global $CFG, $DB;
2383     if ($log->action == "add post") {
2385         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2386                                            u.firstname, u.lastname, u.email, u.picture
2387                                  FROM {forum_discussions} d,
2388                                       {forum_posts} p,
2389                                       {forum} f,
2390                                       {user} u
2391                                 WHERE p.id = ?
2392                                   AND d.id = p.discussion
2393                                   AND p.userid = u.id
2394                                   AND u.deleted <> '1'
2395                                   AND f.id = d.forum", array($log->info));
2398     } else if ($log->action == "add discussion") {
2400         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2401                                            u.firstname, u.lastname, u.email, u.picture
2402                                  FROM {forum_discussions} d,
2403                                       {forum_posts} p,
2404                                       {forum} f,
2405                                       {user} u
2406                                 WHERE d.id = ?
2407                                   AND d.firstpost = p.id
2408                                   AND p.userid = u.id
2409                                   AND u.deleted <> '1'
2410                                   AND f.id = d.forum", array($log->info));
2411     }
2412     return NULL;
2415 /**
2416  * Given a discussion id, return the first post from the discussion
2417  *
2418  * @global object
2419  * @global object
2420  * @param int $dicsussionid
2421  * @return array
2422  */
2423 function forum_get_firstpost_from_discussion($discussionid) {
2424     global $CFG, $DB;
2426     return $DB->get_record_sql("SELECT p.*
2427                              FROM {forum_discussions} d,
2428                                   {forum_posts} p
2429                             WHERE d.id = ?
2430                               AND d.firstpost = p.id ", array($discussionid));
2433 /**
2434  * Returns an array of counts of replies to each discussion
2435  *
2436  * @global object
2437  * @global object
2438  * @param int $forumid
2439  * @param string $forumsort
2440  * @param int $limit
2441  * @param int $page
2442  * @param int $perpage
2443  * @return array
2444  */
2445 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2446     global $CFG, $DB;
2448     if ($limit > 0) {
2449         $limitfrom = 0;
2450         $limitnum  = $limit;
2451     } else if ($page != -1) {
2452         $limitfrom = $page*$perpage;
2453         $limitnum  = $perpage;
2454     } else {
2455         $limitfrom = 0;
2456         $limitnum  = 0;
2457     }
2459     if ($forumsort == "") {
2460         $orderby = "";
2461         $groupby = "";
2463     } else {
2464         $orderby = "ORDER BY $forumsort";
2465         $groupby = ", ".strtolower($forumsort);
2466         $groupby = str_replace('desc', '', $groupby);
2467         $groupby = str_replace('asc', '', $groupby);
2468     }
2470     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2471         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2472                   FROM {forum_posts} p
2473                        JOIN {forum_discussions} d ON p.discussion = d.id
2474                  WHERE p.parent > 0 AND d.forum = ?
2475               GROUP BY p.discussion";
2476         return $DB->get_records_sql($sql, array($forumid));
2478     } else {
2479         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2480                   FROM {forum_posts} p
2481                        JOIN {forum_discussions} d ON p.discussion = d.id
2482                  WHERE d.forum = ?
2483               GROUP BY p.discussion $groupby
2484               $orderby";
2485         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2486     }
2489 /**
2490  * @global object
2491  * @global object
2492  * @global object
2493  * @staticvar array $cache
2494  * @param object $forum
2495  * @param object $cm
2496  * @param object $course
2497  * @return mixed
2498  */
2499 function forum_count_discussions($forum, $cm, $course) {
2500     global $CFG, $DB, $USER;
2502     static $cache = array();
2504     $now = round(time(), -2); // db cache friendliness
2506     $params = array($course->id);
2508     if (!isset($cache[$course->id])) {
2509         if (!empty($CFG->forum_enabletimedposts)) {
2510             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2511             $params[] = $now;
2512             $params[] = $now;
2513         } else {
2514             $timedsql = "";
2515         }
2517         $sql = "SELECT f.id, COUNT(d.id) as dcount
2518                   FROM {forum} f
2519                        JOIN {forum_discussions} d ON d.forum = f.id
2520                  WHERE f.course = ?
2521                        $timedsql
2522               GROUP BY f.id";
2524         if ($counts = $DB->get_records_sql($sql, $params)) {
2525             foreach ($counts as $count) {
2526                 $counts[$count->id] = $count->dcount;
2527             }
2528             $cache[$course->id] = $counts;
2529         } else {
2530             $cache[$course->id] = array();
2531         }
2532     }
2534     if (empty($cache[$course->id][$forum->id])) {
2535         return 0;
2536     }
2538     $groupmode = groups_get_activity_groupmode($cm, $course);
2540     if ($groupmode != SEPARATEGROUPS) {
2541         return $cache[$course->id][$forum->id];
2542     }
2544     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2545         return $cache[$course->id][$forum->id];
2546     }
2548     require_once($CFG->dirroot.'/course/lib.php');
2550     $modinfo =& get_fast_modinfo($course);
2551     if (is_null($modinfo->groups)) {
2552         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2553     }
2555     if (empty($CFG->enablegroupings)) {
2556         $mygroups = $modinfo->groups[0];
2557     } else {
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         }
2563     }
2565     // add all groups posts
2566     if (empty($mygroups)) {
2567         $mygroups = array(-1=>-1);
2568     } else {
2569         $mygroups[-1] = -1;
2570     }
2572     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2573     $params[] = $forum->id;
2575     if (!empty($CFG->forum_enabletimedposts)) {
2576         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2577         $params[] = $now;
2578         $params[] = $now;
2579     } else {
2580         $timedsql = "";
2581     }
2583     $sql = "SELECT COUNT(d.id)
2584               FROM {forum_discussions} d
2585              WHERE d.groupid $mygroups_sql AND d.forum = ?
2586                    $timedsql";
2588     return $DB->get_field_sql($sql, $params);
2591 /**
2592  * How many unrated posts are in the given discussion for a given user?
2593  *
2594  * @global object
2595  * @global object
2596  * @param int $discussionid
2597  * @param int $userid
2598  * @return mixed
2599  */
2600 function forum_count_unrated_posts($discussionid, $userid) {
2601     global $CFG, $DB;
2602     if ($posts = $DB->get_record_sql("SELECT count(*) as num
2603                                    FROM {forum_posts}
2604                                   WHERE parent > 0
2605                                     AND discussion = ?
2606                                     AND userid <> ? ", array($discussionid, $userid))) {
2608         if ($rated = $DB->get_record_sql("SELECT count(*) as num
2609                                        FROM {forum_posts} p,
2610                                             {forum_ratings} r
2611                                       WHERE p.discussion = ?
2612                                         AND p.id = r.post
2613                                         AND r.userid = ?", array($discussionid, $userid))) {
2614             $difference = $posts->num - $rated->num;
2615             if ($difference > 0) {
2616                 return $difference;
2617             } else {
2618                 return 0;    // Just in case there was a counting error
2619             }
2620         } else {
2621             return $posts->num;
2622         }
2623     } else {
2624         return 0;
2625     }
2628 /**
2629  * Get all discussions in a forum
2630  *
2631  * @global object
2632  * @global object
2633  * @global object
2634  * @uses CONTEXT_MODULE
2635  * @uses VISIBLEGROUPS
2636  * @param object $cm
2637  * @param string $forumsort
2638  * @param bool $fullpost
2639  * @param int $unused
2640  * @param int $limit
2641  * @param bool $userlastmodified
2642  * @param int $page
2643  * @param int $perpage
2644  * @return array
2645  */
2646 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2647     global $CFG, $DB, $USER;
2649     $timelimit = '';
2651     $modcontext = null;
2653     $now = round(time(), -2);
2654     $params = array($cm->instance);
2656     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2658     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2659         return array();
2660     }
2662     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2664         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2665             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2666             $params[] = $now;
2667             $params[] = $now;
2668             if (isloggedin()) {
2669                 $timelimit .= " OR d.userid = ?";
2670                 $params[] = $USER->id;
2671             }
2672             $timelimit .= ")";
2673         }
2674     }
2676     if ($limit > 0) {
2677         $limitfrom = 0;
2678         $limitnum  = $limit;
2679     } else if ($page != -1) {
2680         $limitfrom = $page*$perpage;
2681         $limitnum  = $perpage;
2682     } else {
2683         $limitfrom = 0;
2684         $limitnum  = 0;
2685     }
2687     $groupmode    = groups_get_activity_groupmode($cm);
2688     $currentgroup = groups_get_activity_group($cm);
2690     if ($groupmode) {
2691         if (empty($modcontext)) {
2692             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2693         }
2695         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2696             if ($currentgroup) {
2697                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2698                 $params[] = $currentgroup;
2699             } else {
2700                 $groupselect = "";
2701             }
2703         } else {
2704             //seprate groups without access all
2705             if ($currentgroup) {
2706                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2707                 $params[] = $currentgroup;
2708             } else {
2709                 $groupselect = "AND d.groupid = -1";
2710             }
2711         }
2712     } else {
2713         $groupselect = "";
2714     }
2717     if (empty($forumsort)) {
2718         $forumsort = "d.timemodified DESC";
2719     }
2720     if (empty($fullpost)) {
2721         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2722     } else {
2723         $postdata = "p.*";
2724     }
2726     if (empty($userlastmodified)) {  // We don't need to know this
2727         $umfields = "";
2728         $umtable  = "";
2729     } else {
2730         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2731         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2732     }
2734     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2735                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2736               FROM {forum_discussions} d
2737                    JOIN {forum_posts} p ON p.discussion = d.id
2738                    JOIN {user} u ON p.userid = u.id
2739                    $umtable
2740              WHERE d.forum = ? AND p.parent = 0
2741                    $timelimit $groupselect
2742           ORDER BY $forumsort";
2743     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2746 /**
2747  *
2748  * @global object
2749  * @global object
2750  * @global object
2751  * @uses CONTEXT_MODULE
2752  * @uses VISIBLEGROUPS
2753  * @param object $cm
2754  * @return array
2755  */
2756 function forum_get_discussions_unread($cm) {
2757     global $CFG, $DB, $USER;
2759     $now = round(time(), -2);
2760     $params = array($cutoffdate);
2761     $groupmode    = groups_get_activity_groupmode($cm);
2762     $currentgroup = groups_get_activity_group($cm);
2764     if ($groupmode) {
2765         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2767         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2768             if ($currentgroup) {
2769                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2770                 $params[] = $currentgroup;
2771             } else {
2772                 $groupselect = "";
2773             }
2775         } else {
2776             //seprate groups without access all
2777             if ($currentgroup) {
2778                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2779                 $params[] = $currentgroup;
2780             } else {
2781                 $groupselect = "AND d.groupid = -1";
2782             }
2783         }
2784     } else {
2785         $groupselect = "";
2786     }
2788     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2790     if (!empty($CFG->forum_enabletimedposts)) {
2791         $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2792         $params[] = $now;
2793         $params[] = $now;
2794     } else {
2795         $timedsql = "";
2796     }
2798     $sql = "SELECT d.id, COUNT(p.id) AS unread
2799               FROM {forum_discussions} d
2800                    JOIN {forum_posts} p     ON p.discussion = d.id
2801                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2802              WHERE d.forum = {$cm->instance}
2803                    AND p.modified >= ? AND r.id is NULL
2804                    $groupselect
2805                    $timedsql
2806           GROUP BY d.id";
2807     if ($unreads = $DB->get_records_sql($sql, $params)) {
2808         foreach ($unreads as $unread) {
2809             $unreads[$unread->id] = $unread->unread;
2810         }
2811         return $unreads;
2812     } else {
2813         return array();
2814     }
2817 /**
2818  * @global object
2819  * @global object
2820  * @global object
2821  * @uses CONEXT_MODULE
2822  * @uses VISIBLEGROUPS
2823  * @param object $cm
2824  * @return array
2825  */
2826 function forum_get_discussions_count($cm) {
2827     global $CFG, $DB, $USER;
2829     $now = round(time(), -2);
2830     $params = array($cm->instance);
2831     $groupmode    = groups_get_activity_groupmode($cm);
2832     $currentgroup = groups_get_activity_group($cm);
2834     if ($groupmode) {
2835         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2837         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2838             if ($currentgroup) {
2839                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2840                 $params[] = $currentgroup;
2841             } else {
2842                 $groupselect = "";
2843             }
2845         } else {
2846             //seprate groups without access all
2847             if ($currentgroup) {
2848                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2849                 $params[] = $currentgroup;
2850             } else {
2851                 $groupselect = "AND d.groupid = -1";
2852             }
2853         }
2854     } else {
2855         $groupselect = "";
2856     }
2858     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2860     $timelimit = "";
2862     if (!empty($CFG->forum_enabletimedposts)) {
2864         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2866         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2867             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2868             $params[] = $now;
2869             $params[] = $now;
2870             if (isloggedin()) {
2871                 $timelimit .= " OR d.userid = ?";
2872                 $params[] = $USER->id;
2873             }
2874             $timelimit .= ")";
2875         }
2876     }
2878     $sql = "SELECT COUNT(d.id)
2879               FROM {forum_discussions} d
2880                    JOIN {forum_posts} p ON p.discussion = d.id
2881              WHERE d.forum = ? AND p.parent = 0
2882                    $groupselect $timelimit";
2884     return $DB->get_field_sql($sql, $params);
2888 /**
2889  * Get all discussions started by a particular user in a course (or group)
2890  * This function no longer used ...
2891  *
2892  * @todo Remove this function if no longer used
2893  * @global object
2894  * @global object
2895  * @param int $courseid
2896  * @param int $userid
2897  * @param int $groupid
2898  * @return array
2899  */
2900 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2901     global $CFG, $DB;
2902     $params = array($courseid, $userid);
2903     if ($groupid) {
2904         $groupselect = " AND d.groupid = ? ";
2905         $params[] = $groupid;
2906     } else  {
2907         $groupselect = "";
2908     }
2910     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2911                                    f.type as forumtype, f.name as forumname, f.id as forumid
2912                               FROM {forum_discussions} d,
2913                                    {forum_posts} p,
2914                                    {user} u,
2915                                    {forum} f
2916                              WHERE d.course = ?
2917                                AND p.discussion = d.id
2918                                AND p.parent = 0
2919                                AND p.userid = u.id
2920                                AND u.id = ?
2921                                AND d.forum = f.id $groupselect
2922                           ORDER BY p.created DESC", $params);
2925 /**
2926  * Get the list of potential subscribers to a forum.
2927  *
2928  * @param object $forumcontext the forum context.
2929  * @param integer $groupid the id of a group, or 0 for all groups.
2930  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2931  * @param string $sort sort order. As for get_users_by_capability.
2932  * @return array list of users.
2933  */
2934 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2935     return get_users_by_capability($forumcontext, 'mod/forum:initialsubscriptions', $fields, $sort, '', '', $groupid, '', false, true);
2938 /**
2939  * Returns list of user objects that are subscribed to this forum
2940  *
2941  * @global object
2942  * @global object
2943  * @param object $course the course
2944  * @param forum $forum the forum
2945  * @param integer $groupid group id, or 0 for all.
2946  * @param object $context the forum context, to save re-fetching it where possible.
2947  * @return array list of users.
2948  */
2949 function forum_subscribed_users($course, $forum, $groupid=0, $context = NULL) {
2950     global $CFG, $DB;
2951     $params = array($forum->id);
2953     if ($groupid) {
2954         $grouptables = ", {groups_members} gm ";
2955         $groupselect = "AND gm.groupid = ? AND u.id = gm.userid";
2956         $params[] = $groupid;
2957     } else  {
2958         $grouptables = '';
2959         $groupselect = '';
2960     }
2962     $fields ="u.id,
2963               u.username,
2964               u.firstname,
2965               u.lastname,
2966               u.maildisplay,
2967               u.mailformat,
2968               u.maildigest,
2969               u.emailstop,
2970               u.imagealt,
2971               u.email,
2972               u.city,
2973               u.country,
2974               u.lastaccess,
2975               u.lastlogin,
2976               u.picture,
2977               u.timezone,
2978               u.theme,
2979               u.lang,
2980               u.trackforums,
2981               u.mnethostid";
2983     if (forum_is_forcesubscribed($forum)) {
2984         if (empty($context)) {
2985             $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2986             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2987         }
2988         $sort = "u.email ASC";
2989         $results = forum_get_potential_subscribers($context, $groupid, $fields, $sort);
2990     } else {
2991         $results = $DB->get_records_sql("SELECT $fields
2992                               FROM {user} u,
2993                                    {forum_subscriptions} s $grouptables
2994                              WHERE s.forum = ?
2995                                AND s.userid = u.id
2996                                AND u.deleted = 0  $groupselect
2997                           ORDER BY u.email ASC", $params);
2998     }
3000     static $guestid = null;
3002     if (is_null($guestid)) {
3003         if ($guest = guest_user()) {
3004             $guestid = $guest->id;
3005         } else {
3006             $guestid = 0;
3007         }
3008     }
3010     // Guest user should never be subscribed to a forum.
3011     unset($results[$guestid]);
3013     return $results;
3018 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
3021 /**
3022  * @global object
3023  * @global object
3024  * @param int $courseid
3025  * @param string $type
3026  */
3027 function forum_get_course_forum($courseid, $type) {
3028 // How to set up special 1-per-course forums
3029     global $CFG, $DB, $OUTPUT;
3031     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
3032         // There should always only be ONE, but with the right combination of
3033         // errors there might be more.  In this case, just return the oldest one (lowest ID).
3034         foreach ($forums as $forum) {
3035             return $forum;   // ie the first one
3036         }
3037     }
3039     // Doesn't exist, so create one now.
3040     $forum->course = $courseid;
3041     $forum->type = "$type";
3042     switch ($forum->type) {
3043         case "news":
3044             $forum->name  = get_string("namenews", "forum");
3045             $forum->intro = get_string("intronews", "forum");
3046             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
3047             $forum->assessed = 0;
3048             if ($courseid == SITEID) {
3049                 $forum->name  = get_string("sitenews");
3050                 $forum->forcesubscribe = 0;
3051             }
3052             break;
3053         case "social":
3054             $forum->name  = get_string("namesocial", "forum");
3055             $forum->intro = get_string("introsocial", "forum");
3056             $forum->assessed = 0;
3057             $forum->forcesubscribe = 0;
3058             break;
3059         case "blog":
3060             $forum->name = get_string('blogforum', 'forum');
3061             $forum->intro = get_string('introblog', 'forum');
3062             $forum->assessed = 0;
3063             $forum->forcesubscribe = 0;
3064             break;
3065         default:
3066             echo $OUTPUT->notification("That forum type doesn't exist!");
3067             return false;
3068             break;
3069     }
3071     $forum->timemodified = time();
3072     $forum->id = $DB->insert_record("forum", $forum);
3074     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
3075         echo $OUTPUT->notification("Could not find forum module!!");
3076         return false;
3077     }
3078     $mod = new object();
3079     $mod->course = $courseid;
3080     $mod->module = $module->id;
3081     $mod->instance = $forum->id;
3082     $mod->section = 0;
3083     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
3084         echo $OUTPUT->notification("Could not add a new course module to the course '" . format_string($course->fullname) . "'");
3085         return false;
3086     }
3087     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
3088         echo $OUTPUT->notification("Could not add the new course module to that section");
3089         return false;
3090     }
3091     if (! $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule))) {
3092         echo $OUTPUT->notification("Could not update the course module with the correct section");
3093         return false;
3094     }
3095     include_once("$CFG->dirroot/course/lib.php");
3096     rebuild_course_cache($courseid);
3098     return $DB->get_record("forum", array("id" => "$forum->id"));
3102 /**
3103  * Given the data about a posting, builds up the HTML to display it and
3104  * returns the HTML in a string.  This is designed for sending via HTML email.
3105  *
3106  * @global object
3107  * @param object $course
3108  * @param object $cm
3109  * @param object $forum
3110  * @param object $discussion
3111  * @param object $post
3112  * @param object $userform
3113  * @param object $userto
3114  * @param bool $ownpost
3115  * @param bool $reply
3116  * @param bool $link
3117  * @param bool $rate
3118  * @param string $footer
3119  * @return string
3120  */
3121 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
3122                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
3124     global $CFG, $OUTPUT;
3126     if (!isset($userto->viewfullnames[$forum->id])) {
3127         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
3128             print_error('invalidcoursemodule');
3129         }
3130         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3131         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3132     } else {
3133         $viewfullnames = $userto->viewfullnames[$forum->id];
3134     }
3136     // format the post body
3137     $options = new object();
3138     $options->para = true;
3139     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3141     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3143     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3144     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3145     $output .= '</td>';
3147     if ($post->parent) {
3148         $output .= '<td class="topic">';
3149     } else {
3150         $output .= '<td class="topic starter">';
3151     }
3152     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3154     $fullname = fullname($userfrom, $viewfullnames);
3155     $by = new object();
3156     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3157     $by->date = userdate($post->modified, '', $userto->timezone);
3158     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3160     $output .= '</td></tr>';
3162     $output .= '<tr><td class="left side" valign="top">';
3164     if (isset($userfrom->groups)) {
3165         $groups = $userfrom->groups[$forum->id];
3166     } else {
3167         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
3168             print_error('invalidcoursemodule');
3169         }
3170         $group = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3171     }
3173     if ($groups) {
3174         $output .= print_group_picture($groups, $course->id, false, true, true);
3175     } else {
3176         $output .= '&nbsp;';
3177     }
3179     $output .= '</td><td class="content">';
3181     $attachments = forum_print_attachments($post, $cm, 'html');
3182     if ($attachments !== '') {
3183         $output .= '<div class="attachments">';
3184         $output .= $attachments;
3185         $output .= '</div>';
3186     }
3188     $output .= $formattedtext;
3190 // Commands
3191     $commands = array();
3193     if ($post->parent) {
3194         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3195                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3196     }
3198     if ($reply) {
3199         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3200                       get_string('reply', 'forum').'</a>';
3201     }
3203     $output .= '<div class="commands">';
3204     $output .= implode(' | ', $commands);
3205     $output .= '</div>';
3207 // Context link to post if required
3208     if ($link) {
3209         $output .= '<div class="link">';
3210         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3211                      get_string('postincontext', 'forum').'</a>';
3212         $output .= '</div>';
3213     }
3215     if ($footer) {
3216         $output .= '<div class="footer">'.$footer.'</div>';
3217     }
3218     $output .= '</td></tr></table>'."\n\n";
3220     return $output;
3223 /**
3224  * Print a forum post
3225  *
3226  * @global object
3227  * @global object
3228  * @uses FORUM_MODE_THREADED
3229  * @uses PORTFOLIO_FORMAT_PLAINHTML
3230  * @uses PORTFOLIO_FORMAT_FILE
3231  * @uses PORTFOLIO_FORMAT_RICHHTML
3232  * @uses PORTFOLIO_ADD_TEXT_LINK
3233  * @uses CONTEXT_MODULE
3234  * @param object $post The post to print.
3235  * @param object $discussion
3236  * @param object $forum
3237  * @param object $cm
3238  * @param object $course
3239  * @param boolean $ownpost Whether this post belongs to the current user.
3240  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3241  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3242  * @param object $ratings -- I don't really know --
3243  * @param string $footer Extra stuff to print after the message.
3244  * @param string $highlight Space-separated list of terms to highlight.
3245  * @param int $post_read true, false or -99. If we already know whether this user
3246  *          has read this post, pass that in, otherwise, pass in -99, and this
3247  *          function will work it out.
3248  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3249  *          the current user can't see this post, if this argument is true
3250  *          (the default) then print a dummy 'you can't see this post' post.
3251  *          If false, don't output anything at all.
3252  * @param bool|null $istracked
3253  */
3254 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3255                           $ratings=NULL, $footer="", $highlight="", $post_read=null, $dummyifcantsee=true, $istracked=null) {
3257     global $USER, $CFG, $OUTPUT;
3259     static $stredit, $strdelete, $strreply, $strparent, $strprune;
3260     static $strpruneheading, $displaymode;
3261     static $strmarkread, $strmarkunread;
3263     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3265     $post->course = $course->id;
3266     $post->forum  = $forum->id;
3267     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'forum_post', $post->id);
3269     // caching
3270     if (!isset($cm->cache)) {
3271         $cm->cache = new object();
3272     }
3274     if (!isset($cm->cache->caps)) {
3275         $cm->cache->caps = array();
3276         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3277         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3278         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3279         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3280         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3281         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3282         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3283         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3284         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3285     }
3287     if (!isset($cm->uservisible)) {
3288         $cm->uservisible = coursemodule_visible_for_user($cm);
3289     }
3291     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3292         if (!$dummyifcantsee) {
3293             return;
3294         }
3295         echo '<a id="p'.$post->id.'"></a>';
3296         echo '<table cellspacing="0" class="forumpost">';
3297         echo '<tr class="header"><td class="picture left">';
3298         //        print_user_picture($post->userid, $courseid, $post->picture);
3299         echo '</td>';
3300         if ($post->parent) {
3301             echo '<td class="topic">';
3302         } else {
3303             echo '<td class="topic starter">';
3304         }
3305         echo '<div class="subject">'.get_string('forumsubjecthidden','forum').'</div>';
3306         echo '<div class="author">';
3307         print_string('forumauthorhidden','forum');
3308         echo '</div></td></tr>';
3310         echo '<tr><td class="left side">';
3311         echo '&nbsp;';
3313         // Actual content
3315         echo '</td><td class="content">'."\n";
3316         echo get_string('forumbodyhidden','forum');
3317         echo '</td></tr></table>';
3318         return;
3319     }
3321     if (empty($stredit)) {
3322         $stredit         = get_string('edit', 'forum');
3323         $strdelete       = get_string('delete', 'forum');
3324         $strreply        = get_string('reply', 'forum');
3325         $strparent       = get_string('parent', 'forum');
3326         $strpruneheading = get_string('pruneheading', 'forum');
3327         $strprune        = get_string('prune', 'forum');
3328         $displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3329         $strmarkread     = get_string('markread', 'forum');
3330         $strmarkunread   = get_string('markunread', 'forum');
3332     }
3334     $read_style = '';
3335     // ignore trackign status if not tracked or tracked param missing
3336     if ($istracked) {
3337         if (is_null($post_read)) {
3338             debugging('fetching post_read info');
3339             $post_read = forum_tp_is_post_read($USER->id, $post);
3340         }
3342         if ($post_read) {
3343             $read_style = ' read';
3344         } else {
3345             $read_style = ' unread';
3346             echo '<a name="unread"></a>';
3347         }
3348     }
3350     echo '<a id="p'.$post->id.'"></a>';
3351     echo '<table cellspacing="0" class="forumpost'.$read_style.'">';
3353     // Picture
3354     $postuser = new object();
3355     $postuser->id        = $post->userid;
3356     $postuser->firstname = $post->firstname;
3357     $postuser->lastname  = $post->lastname;
3358     $postuser->imagealt  = $post->imagealt;
3359     $postuser->picture   = $post->picture;
3361     echo '<tr class="header"><td class="picture left">';
3362     echo $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3363     echo '</td>';
3365     if ($post->parent) {
3366         echo '<td class="topic">';
3367     } else {
3368         echo '<td class="topic starter">';
3369     }
3371     if (!empty($post->subjectnoformat)) {
3372         echo '<div class="subject">'.$post->subject.'</div>';
3373     } else {
3374         echo '<div class="subject">'.format_string($post->subject).'</div>';
3375     }
3377     echo '<div class="author">';
3378     $fullname = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3379     $by = new object();
3380     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.
3381                 $post->userid.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3382     $by->date = userdate($post->modified);
3383     print_string('bynameondate', 'forum', $by);
3384     echo '</div></td></tr>';
3386     echo '<tr><td class="left side">';
3387     if (isset($cm->cache->usersgroups)) {
3388         $groups = array();
3389         if (isset($cm->cache->usersgroups[$post->userid])) {
3390             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3391                 $groups[$gid] = $cm->cache->groups[$gid];
3392             }
3393         }
3394     } else {
3395         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3396     }
3398     if ($groups) {
3399         print_group_picture($groups, $course->id, false, false, true);
3400     } else {
3401         echo '&nbsp;';
3402     }
3404 // Actual content
3406     echo '</td><td class="content">'."\n";
3408     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3410     if ($attachments !== '') {
3411         echo '<div class="attachments">';
3412         echo $attachments;
3413         echo '</div>';
3414     }
3416     $options = new object();
3417     $options->para    = false;
3418     $options->trusted = $post->messagetrust;
3419     if ($link and (strlen(strip_tags($post->message)) > $CFG->forum_longpost)) {
3420         // Print shortened version
3421         echo format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3422         $numwords = count_words(strip_tags($post->message));
3423         echo '<div class="posting"><a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3424         echo get_string('readtherest', 'forum');
3425         echo '</a> ('.get_string('numwords', '', $numwords).')...</div>';
3426     } else {
3427         // Print whole message
3428         echo '<div class="posting">';
3429         if ($highlight) {
3430             echo highlight($highlight, format_text($post->message, $post->messageformat, $options, $course->id));
3431         } else {
3432             echo format_text($post->message, $post->messageformat, $options, $course->id);
3433         }
3434         echo '</div>';
3435         echo $attachedimages;
3436     }
3439 // Commands
3441     $commands = array();
3443     if ($istracked) {
3444         // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3445         // Don't display the mark read / unread controls in this case.
3446         if ($CFG->forum_usermarksread and isloggedin()) {
3447             if ($post_read) {
3448                 $mcmd = '&amp;mark=unread&amp;postid='.$post->id;
3449                 $mtxt = $strmarkunread;
3450             } else {
3451                 $mcmd = '&amp;mark=read&amp;postid='.$post->id;
3452                 $mtxt = $strmarkread;
3453             }
3454             if ($displaymode == FORUM_MODE_THREADED) {
3455                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3456                               $post->discussion.'&amp;parent='.$post->id.$mcmd.'">'.$mtxt.'</a>';
3457             } else {
3458                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3459                               $post->discussion.$mcmd.'#p'.$post->id.'">'.$mtxt.'</a>';
3460             }
3461         }
3462     }
3464     if ($post->parent) {  // Zoom in to the parent specifically
3465         if ($displaymode == FORUM_MODE_THREADED) {
3466             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3467                       $post->discussion.'&amp;parent='.$post->parent.'">'.$strparent.'</a>';
3468         } else {
3469             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3470                       $post->discussion.'#p'.$post->parent.'">'.$strparent.'</a>';
3471         }
3472     }
3474     $age = time() - $post->created;
3475     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3476     if (!$post->parent and $forum->type == 'news' and $discussion->timestart > time()) {
3477         $age = 0;
3478     }
3479     $editanypost = $cm->cache->caps['mod/forum:editanypost'];
3481     if ($ownpost or $editanypost) {
3482         if (($age < $CFG->maxeditingtime) or $editanypost) {
3483             $commands[] =  '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?edit='.$post->id.'">'.$stredit.'</a>';
3484         }
3485     }
3487     if ($cm->cache->caps['mod/forum:splitdiscussions']
3488                 && $post->parent && $forum->type != 'single') {