MDL-19676 MDL-14408 MDL-8776 Blog improvements
[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.'/portfoliolib.php');
28 require_once($CFG->libdir . '/completionlib.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 ( events_trigger('message_send', $eventdata) > 0){
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                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
788                 }
789                 $posthtml .= "</head>\n<body id=\"email\">\n";
790                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
792                 foreach ($thesediscussions as $discussionid) {
794                     @set_time_limit(120);   // to be reset for each post
796                     $discussion = $discussions[$discussionid];
797                     $forum      = $forums[$discussion->forum];
798                     $course     = $courses[$forum->course];
799                     $cm         = $coursemodules[$forum->id];
801                     //override language
802                     cron_setup_user($userto, $course);
804                     // Fill caches
805                     if (!isset($userto->viewfullnames[$forum->id])) {
806                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
807                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
808                     }
809                     if (!isset($userto->canpost[$discussion->id])) {
810                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
811                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
812                     }
814                     $strforums      = get_string('forums', 'forum');
815                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
816                     $canreply       = $userto->canpost[$discussion->id];
818                     $posttext .= "\n \n";
819                     $posttext .= '=====================================================================';
820                     $posttext .= "\n \n";
821                     $posttext .= "$course->shortname -> $strforums -> ".format_string($forum->name,true);
822                     if ($discussion->name != $forum->name) {
823                         $posttext  .= " -> ".format_string($discussion->name,true);
824                     }
825                     $posttext .= "\n";
827                     $posthtml .= "<p><font face=\"sans-serif\">".
828                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> -> ".
829                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
830                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
831                     if ($discussion->name == $forum->name) {
832                         $posthtml .= "</font></p>";
833                     } else {
834                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
835                     }
836                     $posthtml .= '<p>';
838                     $postsarray = $discussionposts[$discussionid];
839                     sort($postsarray);
841                     foreach ($postsarray as $postid) {
842                         $post = $posts[$postid];
844                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
845                             $userfrom = $users[$post->userid];
846                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
847                             $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
848                         } else {
849                             mtrace('Could not find user '.$post->userid);
850                             continue;
851                         }
853                         if (!isset($userfrom->groups[$forum->id])) {
854                             if (!isset($userfrom->groups)) {
855                                 $userfrom->groups = array();
856                                 $users[$userfrom->id]->groups = array();
857                             }
858                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
859                             $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
860                         }
862                         $userfrom->customheaders = array ("Precedence: Bulk");
864                         if ($userto->maildigest == 2) {
865                             // Subjects only
866                             $by = new object();
867                             $by->name = fullname($userfrom);
868                             $by->date = userdate($post->modified);
869                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
870                             $posttext .= "\n---------------------------------------------------------------------";
872                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
873                             $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>';
875                         } else {
876                             // The full treatment
877                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
878                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
880                         // Create an array of postid's for this user to mark as read.
881                             if (!$CFG->forum_usermarksread) {
882                                 $userto->markposts[$post->id] = $post->id;
883                             }
884                         }
885                     }
886                     if ($canunsubscribe) {
887                         $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>";
888                     } else {
889                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
890                     }
891                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
892                 }
893                 $posthtml .= '</body>';
895                 if ($userto->mailformat != 1) {
896                     // This user DOESN'T want to receive HTML
897                     $posthtml = '';
898                 }
900                 $eventdata = new object();
901                 $eventdata->component        = 'mod/forum';
902                 $eventdata->name             = 'digests';
903                 $eventdata->userfrom         = $site->shortname;
904                 $eventdata->userto           = $userto;
905                 $eventdata->subject          = $postsubject;
906                 $eventdata->fullmessage      = $posttext;
907                 $eventdata->fullmessageformat = FORMAT_PLAIN;
908                 $eventdata->fullmessagehtml  = $posthtml;
909                 $eventdata->smallmessage     = '';
910                 if ( events_trigger('message_send', $eventdata) > 0){
911                     mtrace("ERROR!");
912                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
913                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
914                 } else if ($mailresult === 'emailstop') {
915                     // should not happen anymore - see check above
916                 } else {
917                     mtrace("success.");
918                     $usermailcount++;
920                     // Mark post as read if forum_usermarksread is set off
921                     forum_tp_mark_posts_read($userto, $userto->markposts);
922                 }
923             }
924         }
925     /// We have finishied all digest emails, update $CFG->digestmailtimelast
926         set_config('digestmailtimelast', $timenow);
927     }
929     cron_setup_user();
931     if (!empty($usermailcount)) {
932         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
933     }
935     if (!empty($CFG->forum_lastreadclean)) {
936         $timenow = time();
937         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
938             set_config('forum_lastreadclean', $timenow);
939             mtrace('Removing old forum read tracking info...');
940             forum_tp_clean_read_records();
941         }
942     } else {
943         set_config('forum_lastreadclean', time());
944     }
947     return true;
950 /**
951  * Builds and returns the body of the email notification in plain text.
952  *
953  * @global object
954  * @global object
955  * @uses CONTEXT_MODULE
956  * @param object $course
957  * @param object $cm
958  * @param object $forum
959  * @param object $discussion
960  * @param object $post
961  * @param object $userfrom
962  * @param object $userto
963  * @param boolean $bare
964  * @return string The email body in plain text format.
965  */
966 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
967     global $CFG, $USER;
969     if (!isset($userto->viewfullnames[$forum->id])) {
970         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
971         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
972     } else {
973         $viewfullnames = $userto->viewfullnames[$forum->id];
974     }
976     if (!isset($userto->canpost[$discussion->id])) {
977         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
978         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
979     } else {
980         $canreply = $userto->canpost[$discussion->id];
981     }
983     $by = New stdClass;
984     $by->name = fullname($userfrom, $viewfullnames);
985     $by->date = userdate($post->modified, "", $userto->timezone);
987     $strbynameondate = get_string('bynameondate', 'forum', $by);
989     $strforums = get_string('forums', 'forum');
991     $canunsubscribe = ! forum_is_forcesubscribed($forum);
993     $posttext = '';
995     if (!$bare) {
996         $posttext  = "$course->shortname -> $strforums -> ".format_string($forum->name,true);
998         if ($discussion->name != $forum->name) {
999             $posttext  .= " -> ".format_string($discussion->name,true);
1000         }
1001     }
1003     $posttext .= "\n---------------------------------------------------------------------\n";
1004     $posttext .= format_string($post->subject,true);
1005     if ($bare) {
1006         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1007     }
1008     $posttext .= "\n".$strbynameondate."\n";
1009     $posttext .= "---------------------------------------------------------------------\n";
1010     $posttext .= format_text_email($post->message, $post->messageformat);
1011     $posttext .= "\n\n";
1012     $posttext .= forum_print_attachments($post, $cm, "text");
1014     if (!$bare && $canreply) {
1015         $posttext .= "---------------------------------------------------------------------\n";
1016         $posttext .= get_string("postmailinfo", "forum", $course->shortname)."\n";
1017         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1018     }
1019     if (!$bare && $canunsubscribe) {
1020         $posttext .= "\n---------------------------------------------------------------------\n";
1021         $posttext .= get_string("unsubscribe", "forum");
1022         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1023     }
1025     return $posttext;
1028 /**
1029  * Builds and returns the body of the email notification in html format.
1030  *
1031  * @global object
1032  * @param object $course
1033  * @param object $cm
1034  * @param object $forum
1035  * @param object $discussion
1036  * @param object $post
1037  * @param object $userfrom
1038  * @param object $userto
1039  * @return string The email text in HTML format
1040  */
1041 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1042     global $CFG;
1044     if ($userto->mailformat != 1) {  // Needs to be HTML
1045         return '';
1046     }
1048     if (!isset($userto->canpost[$discussion->id])) {
1049         $canreply = forum_user_can_post($forum, $discussion, $userto);
1050     } else {
1051         $canreply = $userto->canpost[$discussion->id];
1052     }
1054     $strforums = get_string('forums', 'forum');
1055     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1057     $posthtml = '<head>';
1058     foreach ($CFG->stylesheets as $stylesheet) {
1059         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1060     }
1061     $posthtml .= '</head>';
1062     $posthtml .= "\n<body id=\"email\">\n\n";
1064     $posthtml .= '<div class="navbar">'.
1065     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$course->shortname.'</a> &raquo; '.
1066     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1067     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1068     if ($discussion->name == $forum->name) {
1069         $posthtml .= '</div>';
1070     } else {
1071         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1072                      format_string($discussion->name,true).'</a></div>';
1073     }
1074     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1076     if ($canunsubscribe) {
1077         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1078                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1079                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1080     }
1082     $posthtml .= '</body>';
1084     return $posthtml;
1088 /**
1089  *
1090  * @param object $course
1091  * @param object $user
1092  * @param object $mod TODO this is not used in this function, refactor
1093  * @param object $forum
1094  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1095  */
1096 function forum_user_outline($course, $user, $mod, $forum) {
1097     global $CFG;
1098     require_once("$CFG->libdir/gradelib.php");
1099     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1100     if (empty($grades->items[0]->grades)) {
1101         $grade = false;
1102     } else {
1103         $grade = reset($grades->items[0]->grades);
1104     }
1106     $count = forum_count_user_posts($forum->id, $user->id);
1108     if ($count && $count->postcount > 0) {
1109         $result = new object();
1110         $result->info = get_string("numposts", "forum", $count->postcount);
1111         $result->time = $count->lastpost;
1112         if ($grade) {
1113             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1114         }
1115         return $result;
1116     } else if ($grade) {
1117         $result = new object();
1118         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1119         $result->time = $grade->dategraded;
1120         return $result;
1121     }
1122     return NULL;
1126 /**
1127  * @global object
1128  * @global object
1129  * @param object $coure
1130  * @param object $user
1131  * @param object $mod
1132  * @param object $forum
1133  */
1134 function forum_user_complete($course, $user, $mod, $forum) {
1135     global $CFG,$USER, $OUTPUT;
1136     require_once("$CFG->libdir/gradelib.php");
1138     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1139     if (!empty($grades->items[0]->grades)) {
1140         $grade = reset($grades->items[0]->grades);
1141         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1142         if ($grade->str_feedback) {
1143             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1144         }
1145     }
1147     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1149         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1150             print_error('invalidcoursemodule');
1151         }
1152         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1154         // preload all user ratings for these discussions - one query only and minimal memory
1155         $cm->cache->ratings = array();
1156         $cm->cache->myratings = array();
1157         if ($postratings = forum_get_all_user_ratings($user->id, $discussions)) {
1158             foreach ($postratings as $pr) {
1159                 if (!isset($cm->cache->ratings[$pr->postid])) {
1160                     $cm->cache->ratings[$pr->postid] = array();
1161                 }
1162                 $cm->cache->ratings[$pr->postid][$pr->id] = $pr->rating;
1164                 if ($pr->userid == $USER->id) {
1165                     $cm->cache->myratings[$pr->postid] = $pr->rating;
1166                 }
1167             }
1168             unset($postratings);
1169         }
1171         foreach ($posts as $post) {
1172             if (!isset($discussions[$post->discussion])) {
1173                 continue;
1174             }
1175             $discussion = $discussions[$post->discussion];
1177             $ratings = null;
1179             if ($forum->assessed) {
1180                 if ($scale = make_grades_menu($forum->scale)) {
1181                     $ratings =new object();
1182                     $ratings->scale = $scale;
1183                     $ratings->assesstimestart = $forum->assesstimestart;
1184                     $ratings->assesstimefinish = $forum->assesstimefinish;
1185                     $ratings->allow = false;
1186                 }
1187             }
1188             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false, $ratings);
1189         }
1190     } else {
1191         echo "<p>".get_string("noposts", "forum")."</p>";
1192     }
1200 /**
1201  * @global object
1202  * @global object
1203  * @global object
1204  * @param array $courses
1205  * @param array $htmlarray
1206  */
1207 function forum_print_overview($courses,&$htmlarray) {
1208     global $USER, $CFG, $DB, $SESSION;
1210     $LIKE = $DB->sql_ilike();
1212     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1213         return array();
1214     }
1216     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1217         return;
1218     }
1221     // get all forum logs in ONE query (much better!)
1222     $params = array();
1223     $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {log} l "
1224         ." JOIN {course_modules} cm ON cm.id = cmid "
1225         ." WHERE (";
1226     foreach ($courses as $course) {
1227         $sql .= '(l.course = ? AND l.time > ?) OR ';
1228         $params[] = $course->id;
1229         $params[] = $course->lastaccess;
1230     }
1231     $sql = substr($sql,0,-3); // take off the last OR
1233     $sql .= ") AND l.module = 'forum' AND action = 'add post' "
1234         ." AND userid != ? GROUP BY cmid,l.course,instance";
1236     $params[] = $USER->id;
1238     if (!$new = $DB->get_records_sql($sql, $params)) {
1239         $new = array(); // avoid warnings
1240     }
1242     // also get all forum tracking stuff ONCE.
1243     $trackingforums = array();
1244     foreach ($forums as $forum) {
1245         if (forum_tp_can_track_forums($forum)) {
1246             $trackingforums[$forum->id] = $forum;
1247         }
1248     }
1250     if (count($trackingforums) > 0) {
1251         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1252         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1253             ' FROM {forum_posts} p '.
1254             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1255             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1256         $params = array($USER->id);
1258         foreach ($trackingforums as $track) {
1259             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1260             $params[] = $track->id;
1261             if (isset($SESSION->currentgroup[$track->course])) {
1262                 $groupid =  $SESSION->currentgroup[$track->course];
1263             } else {
1264                 $groupid = groups_get_all_groups($track->course, $USER->id);
1265                 if (is_array($groupid)) {
1266                     $groupid = array_shift(array_keys($groupid));
1267                     $SESSION->currentgroup[$track->course] = $groupid;
1268                 } else {
1269                     $groupid = 0;
1270                 }
1271             }
1272             $params[] = $groupid;
1273         }
1274         $sql = substr($sql,0,-3); // take off the last OR
1275         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1276         $params[] = $cutoffdate;
1278         if (!$unread = $DB->get_records_sql($sql, $params)) {
1279             $unread = array();
1280         }
1281     } else {
1282         $unread = array();
1283     }
1285     if (empty($unread) and empty($new)) {
1286         return;
1287     }
1289     $strforum = get_string('modulename','forum');
1290     $strnumunread = get_string('overviewnumunread','forum');
1291     $strnumpostssince = get_string('overviewnumpostssince','forum');
1293     foreach ($forums as $forum) {
1294         $str = '';
1295         $count = 0;
1296         $thisunread = 0;
1297         $showunread = false;
1298         // either we have something from logs, or trackposts, or nothing.
1299         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1300             $count = $new[$forum->id]->count;
1301         }
1302         if (array_key_exists($forum->id,$unread)) {
1303             $thisunread = $unread[$forum->id]->count;
1304             $showunread = true;
1305         }
1306         if ($count > 0 || $thisunread > 0) {
1307             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1308                 $forum->name.'</a></div>';
1309             $str .= '<div class="info">';
1310             $str .= $count.' '.$strnumpostssince;
1311             if (!empty($showunread)) {
1312                 $str .= '<br />'.$thisunread .' '.$strnumunread;
1313             }
1314             $str .= '</div></div>';
1315         }
1316         if (!empty($str)) {
1317             if (!array_key_exists($forum->course,$htmlarray)) {
1318                 $htmlarray[$forum->course] = array();
1319             }
1320             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1321                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1322             }
1323             $htmlarray[$forum->course]['forum'] .= $str;
1324         }
1325     }
1328 /**
1329  * Given a course and a date, prints a summary of all the new
1330  * messages posted in the course since that date
1331  *
1332  * @global object
1333  * @global object
1334  * @global object
1335  * @uses CONTEXT_MODULE
1336  * @uses VISIBLEGROUPS
1337  * @param object $course
1338  * @param bool $viewfullnames capability
1339  * @param int $timestart
1340  * @return bool success
1341  */
1342 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1343     global $CFG, $USER, $DB, $OUTPUT;
1345     // do not use log table if possible, it may be huge and is expensive to join with other tables
1347     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1348                                               d.timestart, d.timeend, d.userid AS duserid,
1349                                               u.firstname, u.lastname, u.email, u.picture
1350                                          FROM {forum_posts} p
1351                                               JOIN {forum_discussions} d ON d.id = p.discussion
1352                                               JOIN {forum} f             ON f.id = d.forum
1353                                               JOIN {user} u              ON u.id = p.userid
1354                                         WHERE p.created > ? AND f.course = ?
1355                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1356          return false;
1357     }
1359     $modinfo =& get_fast_modinfo($course);
1361     $groupmodes = array();
1362     $cms    = array();
1364     $strftimerecent = get_string('strftimerecent');
1366     $printposts = array();
1367     foreach ($posts as $post) {
1368         if (!isset($modinfo->instances['forum'][$post->forum])) {
1369             // not visible
1370             continue;
1371         }
1372         $cm = $modinfo->instances['forum'][$post->forum];
1373         if (!$cm->uservisible) {
1374             continue;
1375         }
1376         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1378         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1379             continue;
1380         }
1382         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1383           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1384             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1385                 continue;
1386             }
1387         }
1389         $groupmode = groups_get_activity_groupmode($cm, $course);
1391         if ($groupmode) {
1392             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1393                 // oki (Open discussions have groupid -1)
1394             } else {
1395                 // separate mode
1396                 if (isguestuser()) {
1397                     // shortcut
1398                     continue;
1399                 }
1401                 if (is_null($modinfo->groups)) {
1402                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1403                 }
1405                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1406                     continue;
1407                 }
1408             }
1409         }
1411         $printposts[] = $post;
1412     }
1413     unset($posts);
1415     if (!$printposts) {
1416         return false;
1417     }
1419     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1420     echo "\n<ul class='unlist'>\n";
1422     foreach ($printposts as $post) {
1423         $subjectclass = empty($post->parent) ? ' bold' : '';
1425         echo '<li><div class="head">'.
1426                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1427                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1428              '</div>';
1429         echo '<div class="info'.$subjectclass.'">';
1430         if (empty($post->parent)) {
1431             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1432         } else {
1433             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1434         }
1435         $post->subject = break_up_long_words(format_string($post->subject, true));
1436         echo $post->subject;
1437         echo "</a>\"</div></li>\n";
1438     }
1440     echo "</ul>\n";
1442     return true;
1445 /**
1446  * Return grade for given user or all users.
1447  *
1448  * @global object
1449  * @global object
1450  * @param object $forum
1451  * @param int $userid optional user id, 0 means all users
1452  * @return array array of grades, false if none
1453  */
1454 function forum_get_user_grades($forum, $userid=0) {
1455     global $CFG, $DB;
1457     $params= array();
1458     if ($userid) {
1459         $params[] = $userid;
1460         $user = "AND u.id = ?";
1461     } else {
1462         $user = "";
1463     }
1465     $params[] = $forum->id;
1467     $aggtype = $forum->assessed;
1468     switch ($aggtype) {
1469         case FORUM_AGGREGATE_COUNT :
1470             $sql = "SELECT u.id, u.id AS userid, COUNT(fr.rating) AS rawgrade
1471                       FROM {user} u, {forum_posts} fp,
1472                            {forum_ratings} fr, {forum_discussions} fd
1473                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1474                            AND fr.userid != u.id AND fd.forum = ?
1475                            $user
1476                   GROUP BY u.id";
1477             break;
1478         case FORUM_AGGREGATE_MAX :
1479             $sql = "SELECT u.id, u.id AS userid, MAX(fr.rating) AS rawgrade
1480                       FROM {user} u, {forum_posts} fp,
1481                            {forum_ratings} fr, {forum_discussions} fd
1482                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1483                            AND fr.userid != u.id AND fd.forum = ?
1484                            $user
1485                   GROUP BY u.id";
1486             break;
1487         case FORUM_AGGREGATE_MIN :
1488             $sql = "SELECT u.id, u.id AS userid, MIN(fr.rating) AS rawgrade
1489                       FROM {user} u, {forum_posts} fp,
1490                            {forum_ratings} fr, {forum_discussions} fd
1491                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1492                            AND fr.userid != u.id AND fd.forum = ?
1493                            $user
1494                   GROUP BY u.id";
1495             break;
1496         case FORUM_AGGREGATE_SUM :
1497             $sql = "SELECT u.id, u.id AS userid, SUM(fr.rating) AS rawgrade
1498                      FROM {user} u, {forum_posts} fp,
1499                           {forum_ratings} fr, {forum_discussions} fd
1500                     WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1501                           AND fr.userid != u.id AND fd.forum = ?
1502                           $user
1503                  GROUP BY u.id";
1504             break;
1505         default : //avg
1506             $sql = "SELECT u.id, u.id AS userid, AVG(fr.rating) AS rawgrade
1507                       FROM {user} u, {forum_posts} fp,
1508                            {forum_ratings} fr, {forum_discussions} fd
1509                      WHERE u.id = fp.userid AND fp.discussion = fd.id AND fr.post = fp.id
1510                            AND fr.userid != u.id AND fd.forum = ?
1511                            $user
1512                   GROUP BY u.id";
1513             break;
1514     }
1516     if ($results = $DB->get_records_sql($sql, $params)) {
1517         // it could throw off the grading if count and sum returned a rawgrade higher than scale
1518         // 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)
1519         foreach ($results as $rid=>$result) {
1520             if ($forum->scale >= 0) {
1521                 //numeric
1522                 if ($result->rawgrade > $forum->scale) {
1523                     $results[$rid]->rawgrade = $forum->scale;
1524                 }
1525             } else {
1526                 //scales
1527                 if ($scale = $DB->get_record('scale', array('id' => -$forum->scale))) {
1528                     $scale = explode(',', $scale->scale);
1529                     $max = count($scale);
1530                     if ($result->rawgrade > $max) {
1531                         $results[$rid]->rawgrade = $max;
1532                     }
1533                 }
1534             }
1535         }
1536     }
1538     return $results;
1541 /**
1542  * Update activity grades
1543  *
1544  * @global object
1545  * @global object
1546  * @param object $forum
1547  * @param int $userid specific user only, 0 means all
1548  * @param boolean $nullifnone return null if grade does not exist
1549  * @return void
1550  */
1551 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1552     global $CFG, $DB;
1553     require_once($CFG->libdir.'/gradelib.php');
1555     if (!$forum->assessed) {
1556         forum_grade_item_update($forum);
1558     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1559         forum_grade_item_update($forum, $grades);
1561     } else if ($userid and $nullifnone) {
1562         $grade = new object();
1563         $grade->userid   = $userid;
1564         $grade->rawgrade = NULL;
1565         forum_grade_item_update($forum, $grade);
1567     } else {
1568         forum_grade_item_update($forum);
1569     }
1572 /**
1573  * Update all grades in gradebook.
1574  * @global object
1575  */
1576 function forum_upgrade_grades() {
1577     global $DB;
1579     $sql = "SELECT COUNT('x')
1580               FROM {forum} f, {course_modules} cm, {modules} m
1581              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1582     $count = $DB->count_records_sql($sql);
1584     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1585               FROM {forum} f, {course_modules} cm, {modules} m
1586              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1587     if ($rs = $DB->get_recordset_sql($sql)) {
1588         $pbar = new progress_bar('forumupgradegrades', 500, true);
1589         $i=0;
1590         foreach ($rs as $forum) {
1591             $i++;
1592             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1593             forum_update_grades($forum, 0, false);
1594             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1595         }
1596         $rs->close();
1597     }
1600 /**
1601  * Create/update grade item for given forum
1602  *
1603  * @global object
1604  * @uses GRADE_TYPE_NONE
1605  * @uses GRADE_TYPE_VALUE
1606  * @uses GRADE_TYPE_SCALE
1607  * @param object $forum object with extra cmidnumber
1608  * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
1609  * @return int 0 if ok
1610  */
1611 function forum_grade_item_update($forum, $grades=NULL) {
1612     global $CFG;
1613     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1614         require_once($CFG->libdir.'/gradelib.php');
1615     }
1617     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1619     if (!$forum->assessed or $forum->scale == 0) {
1620         $params['gradetype'] = GRADE_TYPE_NONE;
1622     } else if ($forum->scale > 0) {
1623         $params['gradetype'] = GRADE_TYPE_VALUE;
1624         $params['grademax']  = $forum->scale;
1625         $params['grademin']  = 0;
1627     } else if ($forum->scale < 0) {
1628         $params['gradetype'] = GRADE_TYPE_SCALE;
1629         $params['scaleid']   = -$forum->scale;
1630     }
1632     if ($grades  === 'reset') {
1633         $params['reset'] = true;
1634         $grades = NULL;
1635     }
1637     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1640 /**
1641  * Delete grade item for given forum
1642  *
1643  * @global object
1644  * @param object $forum object
1645  * @return object grade_item
1646  */
1647 function forum_grade_item_delete($forum) {
1648     global $CFG;
1649     require_once($CFG->libdir.'/gradelib.php');
1651     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1655 /**
1656  * Returns the users with data in one forum
1657  * (users with records in forum_subscriptions, forum_posts and forum_ratings, students)
1658  *
1659  * @global object
1660  * @global object
1661  * @param int $forumid
1662  * @return mixed array or false if none
1663  */
1664 function forum_get_participants($forumid) {
1666     global $CFG, $DB;
1668     //Get students from forum_subscriptions
1669     $st_subscriptions = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1670                                          FROM {user} u,
1671                                               {forum_subscriptions} s
1672                                          WHERE s.forum = ? AND
1673                                                u.id = s.userid", array($forumid));
1674     //Get students from forum_posts
1675     $st_posts = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1676                                  FROM {user} u,
1677                                       {forum_discussions} d,
1678                                       {forum_posts} p
1679                                  WHERE d.forum = ? AND
1680                                        p.discussion = d.id AND
1681                                        u.id = p.userid", array($forumid));
1683     //Get students from forum_ratings
1684     $st_ratings = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
1685                                    FROM {user} u,
1686                                         {forum_discussions} d,
1687                                         {forum_posts} p,
1688                                         {forum_ratings} r
1689                                    WHERE d.forum = ? AND
1690                                          p.discussion = d.id AND
1691                                          r.post = p.id AND
1692                                          u.id = r.userid", array($forumid));
1694     //Add st_posts to st_subscriptions
1695     if ($st_posts) {
1696         foreach ($st_posts as $st_post) {
1697             $st_subscriptions[$st_post->id] = $st_post;
1698         }
1699     }
1700     //Add st_ratings to st_subscriptions
1701     if ($st_ratings) {
1702         foreach ($st_ratings as $st_rating) {
1703             $st_subscriptions[$st_rating->id] = $st_rating;
1704         }
1705     }
1706     //Return st_subscriptions array (it contains an array of unique users)
1707     return ($st_subscriptions);
1710 /**
1711  * This function returns if a scale is being used by one forum
1712  *
1713  * @global object
1714  * @param int $forumid
1715  * @param int $scaleid negative number
1716  * @return bool
1717  */
1718 function forum_scale_used ($forumid,$scaleid) {
1719     global $DB;
1720     $return = false;
1722     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1724     if (!empty($rec) && !empty($scaleid)) {
1725         $return = true;
1726     }
1728     return $return;
1731 /**
1732  * Checks if scale is being used by any instance of forum
1733  *
1734  * This is used to find out if scale used anywhere
1735  *
1736  * @global object
1737  * @param $scaleid int
1738  * @return boolean True if the scale is used by any forum
1739  */
1740 function forum_scale_used_anywhere($scaleid) {
1741     global $DB;
1742     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1743         return true;
1744     } else {
1745         return false;
1746     }
1749 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1751 /**
1752  * Gets a post with all info ready for forum_print_post
1753  * Most of these joins are just to get the forum id
1754  *
1755  * @global object
1756  * @global object
1757  * @param int $postid
1758  * @return mixed array of posts or false
1759  */
1760 function forum_get_post_full($postid) {
1761     global $CFG, $DB;
1763     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1764                              FROM {forum_posts} p
1765                                   JOIN {forum_discussions} d ON p.discussion = d.id
1766                                   LEFT JOIN {user} u ON p.userid = u.id
1767                             WHERE p.id = ?", array($postid));
1770 /**
1771  * Gets posts with all info ready for forum_print_post
1772  * We pass forumid in because we always know it so no need to make a
1773  * complicated join to find it out.
1774  *
1775  * @global object
1776  * @global object
1777  * @return mixed array of posts or false
1778  */
1779 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1780     global $CFG, $DB;
1782     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1783                               FROM {forum_posts} p
1784                          LEFT JOIN {user} u ON p.userid = u.id
1785                              WHERE p.discussion = ?
1786                                AND p.parent > 0 $sort", array($discussion));
1789 /**
1790  * Gets all posts in discussion including top parent.
1791  *
1792  * @global object
1793  * @global object
1794  * @global object
1795  * @param int $discussionid
1796  * @param string $sort
1797  * @param bool $tracking does user track the forum?
1798  * @return array of posts
1799  */
1800 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1801     global $CFG, $DB, $USER;
1803     $tr_sel  = "";
1804     $tr_join = "";
1805     $params = array();
1807     if ($tracking) {
1808         $now = time();
1809         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1810         $tr_sel  = ", fr.id AS postread";
1811         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1812         $params[] = $USER->id;
1813     }
1815     $params[] = $discussionid;
1816     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1817                                      FROM {forum_posts} p
1818                                           LEFT JOIN {user} u ON p.userid = u.id
1819                                           $tr_join
1820                                     WHERE p.discussion = ?
1821                                  ORDER BY $sort", $params)) {
1822         return array();
1823     }
1825     foreach ($posts as $pid=>$p) {
1826         if ($tracking) {
1827             if (forum_tp_is_post_old($p)) {
1828                  $posts[$pid]->postread = true;
1829             }
1830         }
1831         if (!$p->parent) {
1832             continue;
1833         }
1834         if (!isset($posts[$p->parent])) {
1835             continue; // parent does not exist??
1836         }
1837         if (!isset($posts[$p->parent]->children)) {
1838             $posts[$p->parent]->children = array();
1839         }
1840         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1841     }
1843     return $posts;
1846 /**
1847  * Gets posts with all info ready for forum_print_post
1848  * We pass forumid in because we always know it so no need to make a
1849  * complicated join to find it out.
1850  *
1851  * @global object
1852  * @global object
1853  * @param int $parent
1854  * @param int $forumid
1855  * @return array
1856  */
1857 function forum_get_child_posts($parent, $forumid) {
1858     global $CFG, $DB;
1860     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1861                               FROM {forum_posts} p
1862                          LEFT JOIN {user} u ON p.userid = u.id
1863                              WHERE p.parent = ?
1864                           ORDER BY p.created ASC", array($parent));
1867 /**
1868  * An array of forum objects that the user is allowed to read/search through.
1869  *
1870  * @global object
1871  * @global object
1872  * @global object
1873  * @param int $userid
1874  * @param int $courseid if 0, we look for forums throughout the whole site.
1875  * @return array of forum objects, or false if no matches
1876  *         Forum objects have the following attributes:
1877  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1878  *         viewhiddentimedposts
1879  */
1880 function forum_get_readable_forums($userid, $courseid=0) {
1882     global $CFG, $DB, $USER;
1883     require_once($CFG->dirroot.'/course/lib.php');
1885     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1886         print_error('notinstalled', 'forum');
1887     }
1889     if ($courseid) {
1890         $courses = $DB->get_records('course', array('id' => $courseid));
1891     } else {
1892         // If no course is specified, then the user can see SITE + his courses.
1893         // And admins can see all courses, so pass the $doanything flag enabled
1894         $courses1 = $DB->get_records('course', array('id' => SITEID));
1895         $courses2 = get_my_courses($userid, null, null, true);
1896         $courses = array_merge($courses1, $courses2);
1897     }
1898     if (!$courses) {
1899         return array();
1900     }
1902     $readableforums = array();
1904     foreach ($courses as $course) {
1906         $modinfo =& get_fast_modinfo($course);
1907         if (is_null($modinfo->groups)) {
1908             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1909         }
1911         if (empty($modinfo->instances['forum'])) {
1912             // hmm, no forums?
1913             continue;
1914         }
1916         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1918         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1919             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1920                 continue;
1921             }
1922             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1923             $forum = $courseforums[$forumid];
1925             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1926                 continue;
1927             }
1929          /// group access
1930             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1931                 if (is_null($modinfo->groups)) {
1932                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1933                 }
1934                 if (empty($CFG->enablegroupings)) {
1935                     $forum->onlygroups = $modinfo->groups[0];
1936                     $forum->onlygroups[] = -1;
1937                 } else if (isset($modinfo->groups[$cm->groupingid])) {
1938                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1939                     $forum->onlygroups[] = -1;
1940                 } else {
1941                     $forum->onlygroups = array(-1);
1942                 }
1943             }
1945         /// hidden timed discussions
1946             $forum->viewhiddentimedposts = true;
1947             if (!empty($CFG->forum_enabletimedposts)) {
1948                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1949                     $forum->viewhiddentimedposts = false;
1950                 }
1951             }
1953         /// qanda access
1954             if ($forum->type == 'qanda'
1955                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1957                 // We need to check whether the user has posted in the qanda forum.
1958                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1959                                                     // the user is allowed to see in this forum.
1960                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1961                     foreach ($discussionspostedin as $d) {
1962                         $forum->onlydiscussions[] = $d->id;
1963                     }
1964                 }
1965             }
1967             $readableforums[$forum->id] = $forum;
1968         }
1970         unset($modinfo);
1972     } // End foreach $courses
1974     //print_object($courses);
1975     //print_object($readableforums);
1977     return $readableforums;
1980 /**
1981  * Returns a list of posts found using an array of search terms.
1982  *
1983  * @global object
1984  * @global object
1985  * @global object
1986  * @param array $searchterms array of search terms, e.g. word +word -word
1987  * @param int $courseid if 0, we search through the whole site
1988  * @param int $limitfrom
1989  * @param int $limitnum
1990  * @param int &$totalcount
1991  * @param string $extrasql
1992  * @return array|bool Array of posts found or false
1993  */
1994 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1995                             &$totalcount, $extrasql='') {
1996     global $CFG, $DB, $USER;
1997     require_once($CFG->libdir.'/searchlib.php');
1999     $forums = forum_get_readable_forums($USER->id, $courseid);
2001     if (count($forums) == 0) {
2002         $totalcount = 0;
2003         return false;
2004     }
2006     $now = round(time(), -2); // db friendly
2008     $fullaccess = array();
2009     $where = array();
2010     $params = array();
2012     foreach ($forums as $forumid => $forum) {
2013         $select = array();
2015         if (!$forum->viewhiddentimedposts) {
2016             $select[] = "(d.userid = :userid OR (d.timestart < : AND (d.timeend = 0 OR d.timeend > :timeend)))";
2017             $params = array('userid'=>$USER->id, 'timestart'=>$now, 'timeend'=>$now);
2018         }
2020         $cm = get_coursemodule_from_instance('forum', $forumid);
2021         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2023         if ($forum->type == 'qanda'
2024             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
2025             if (!empty($forum->onlydiscussions)) {
2026                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda0');
2027                 $params = array_merge($params, $discussionid_params);
2028                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
2029             } else {
2030                 $select[] = "p.parent = 0";
2031             }
2032         }
2034         if (!empty($forum->onlygroups)) {
2035             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps0');
2036             $params = array_merge($params, $groupid_params);
2037             $select[] = "d.groupid $groupid_sql";
2038         }
2040         if ($select) {
2041             $selects = implode(" AND ", $select);
2042             $where[] = "(d.forum = :forum AND $selects)";
2043             $params['forum'] = $forumid;
2044         } else {
2045             $fullaccess[] = $forumid;
2046         }
2047     }
2049     if ($fullaccess) {
2050         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula0');
2051         $params = array_merge($params, $fullid_params);
2052         $where[] = "(d.forum $fullid_sql)";
2053     }
2055     $selectdiscussion = "(".implode(" OR ", $where).")";
2057     $messagesearch = '';
2058     $searchstring = '';
2060     // Need to concat these back together for parser to work.
2061     foreach($searchterms as $searchterm){
2062         if ($searchstring != '') {
2063             $searchstring .= ' ';
2064         }
2065         $searchstring .= $searchterm;
2066     }
2068     // We need to allow quoted strings for the search. The quotes *should* be stripped
2069     // by the parser, but this should be examined carefully for security implications.
2070     $searchstring = str_replace("\\\"","\"",$searchstring);
2071     $parser = new search_parser();
2072     $lexer = new search_lexer($parser);
2074     if ($lexer->parse($searchstring)) {
2075         $parsearray = $parser->get_parsed_array();
2076     // Experimental feature under 1.8! MDL-8830
2077     // Use alternative text searches if defined
2078     // This feature only works under mysql until properly implemented for other DBs
2079     // Requires manual creation of text index for forum_posts before enabling it:
2080     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2081     // Experimental feature under 1.8! MDL-8830
2082         if (!empty($CFG->forum_usetextsearches)) {
2083             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2084                                                  'p.userid', 'u.id', 'u.firstname',
2085                                                  'u.lastname', 'p.modified', 'd.forum');
2086         } else {
2087             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2088                                                  'p.userid', 'u.id', 'u.firstname',
2089                                                  'u.lastname', 'p.modified', 'd.forum');
2090         }
2091         $params = array_merge($params, $msparams);
2092     }
2094     $fromsql = "{forum_posts} p,
2095                   {forum_discussions} d,
2096                   {user} u";
2098     $selectsql = " $messagesearch
2099                AND p.discussion = d.id
2100                AND p.userid = u.id
2101                AND $selectdiscussion
2102                    $extrasql";
2104     $countsql = "SELECT COUNT(*)
2105                    FROM $fromsql
2106                   WHERE $selectsql";
2108     $searchsql = "SELECT p.*,
2109                          d.forum,
2110                          u.firstname,
2111                          u.lastname,
2112                          u.email,
2113                          u.picture,
2114                          u.imagealt
2115                     FROM $fromsql
2116                    WHERE $selectsql
2117                 ORDER BY p.modified DESC";
2119     $totalcount = $DB->count_records_sql($countsql, $params);
2121     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2124 /**
2125  * Returns a list of ratings for all posts in discussion
2126  *
2127  * @global object
2128  * @global object
2129  * @param object $discussion
2130  * @return array of ratings or false
2131  */
2132 function forum_get_all_discussion_ratings($discussion) {
2133     global $CFG, $DB;
2134     return $DB->get_records_sql("SELECT r.id, r.userid, p.id AS postid, r.rating
2135                               FROM {forum_ratings} r,
2136                                    {forum_posts} p
2137                              WHERE r.post = p.id AND p.discussion = ?
2138                              ORDER BY p.id ASC", array($discussion->id));
2141 /**
2142  * Returns a list of ratings for one specific user for all posts in discussion
2143  * @global object
2144  * @global object
2145  * @param object $discussions the discussions for which we return all ratings
2146  * @param int $userid the user for who we return all ratings
2147  * @return array
2148  */
2149 function forum_get_all_user_ratings($userid, $discussions) {
2150     global $CFG, $DB;
2153     foreach ($discussions as $discussion) {
2154      if (!isset($discussionsid)){
2155          $discussionsid = $discussion->id;
2156      }
2157      else {
2158          $discussionsid .= ",".$discussion->id;
2159      }
2160     }
2162     $sql = "SELECT r.id, r.userid, p.id AS postid, r.rating
2163                               FROM {forum_ratings} r,
2164                                    {forum_posts} p
2165                              WHERE r.post = p.id AND p.userid = :userid";
2168     $params = array();
2169     $params['userid'] = $userid;
2170     //postgres compability
2171     if (!isset($discussionsid)) {
2172        $sql .=" AND p.discussion IN (".$discussionsid.")";
2173     }
2174     $sql .=" ORDER BY p.id ASC";
2176     return $DB->get_records_sql($sql, $params);
2181 /**
2182  * Returns a list of ratings for a particular post - sorted.
2183  *
2184  * @global object
2185  * @global object
2186  * @param int $postid
2187  * @param string $sort
2188  * @return array Array of ratings or false
2189  */
2190 function forum_get_ratings($postid, $sort="u.firstname ASC") {
2191     global $CFG, $DB;
2192     return $DB->get_records_sql("SELECT u.*, r.rating, r.time
2193                               FROM {forum_ratings} r,
2194                                    {user} u
2195                              WHERE r.post = ?
2196                                AND r.userid = u.id
2197                              ORDER BY $sort", array($postid));
2200 /**
2201  * Returns a list of all new posts that have not been mailed yet
2202  *
2203  * @global object
2204  * @global object
2205  * @param int $starttime posts created after this time
2206  * @param int $endtime posts created before this
2207  * @param int $now used for timed discussions only
2208  * @return array
2209  */
2210 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2211     global $CFG, $DB;
2213     $params = array($starttime, $endtime);
2214     if (!empty($CFG->forum_enabletimedposts)) {
2215         if (empty($now)) {
2216             $now = time();
2217         }
2218         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2219         $params[] = $now;
2220         $params[] = $now;
2221     } else {
2222         $timedsql = "";
2223     }
2225     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2226                               FROM {forum_posts} p
2227                                    JOIN {forum_discussions} d ON d.id = p.discussion
2228                              WHERE p.mailed = 0
2229                                    AND p.created >= ?
2230                                    AND (p.created < ? OR p.mailnow = 1)
2231                                    $timedsql
2232                           ORDER BY p.modified ASC", $params);
2235 /**
2236  * Marks posts before a certain time as being mailed already
2237  *
2238  * @global object
2239  * @global object
2240  * @param int $endtime
2241  * @param int $now Defaults to time()
2242  * @return bool
2243  */
2244 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2245     global $CFG, $DB;
2246     if (empty($now)) {
2247         $now = time();
2248     }
2250     if (empty($CFG->forum_enabletimedposts)) {
2251         return $DB->execute("UPDATE {forum_posts}
2252                                SET mailed = '1'
2253                              WHERE (created < ? OR mailnow = 1)
2254                                    AND mailed = 0", array($endtime));
2256     } else {
2257         return $DB->execute("UPDATE {forum_posts}
2258                                SET mailed = '1'
2259                              WHERE discussion NOT IN (SELECT d.id
2260                                                         FROM {forum_discussions} d
2261                                                        WHERE d.timestart > ?)
2262                                    AND (created < ? OR mailnow = 1)
2263                                    AND mailed = 0", array($now, $endtime));
2264     }
2267 /**
2268  * Get all the posts for a user in a forum suitable for forum_print_post
2269  *
2270  * @global object
2271  * @global object
2272  * @uses CONTEXT_MODULE
2273  * @return array
2274  */
2275 function forum_get_user_posts($forumid, $userid) {
2276     global $CFG, $DB;
2278     $timedsql = "";
2279     $params = array($forumid, $userid);
2281     if (!empty($CFG->forum_enabletimedposts)) {
2282         $cm = get_coursemodule_from_instance('forum', $forumid);
2283         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2284             $now = time();
2285             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2286             $params[] = $now;
2287             $params[] = $now;
2288         }
2289     }
2291     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2292                               FROM {forum} f
2293                                    JOIN {forum_discussions} d ON d.forum = f.id
2294                                    JOIN {forum_posts} p       ON p.discussion = d.id
2295                                    JOIN {user} u              ON u.id = p.userid
2296                              WHERE f.id = ?
2297                                    AND p.userid = ?
2298                                    $timedsql
2299                           ORDER BY p.modified ASC", $params);
2302 /**
2303  * Get all the discussions user participated in
2304  *
2305  * @global object
2306  * @global object
2307  * @uses CONTEXT_MODULE
2308  * @param int $forumid
2309  * @param int $userid
2310  * @return array Array or false
2311  */
2312 function forum_get_user_involved_discussions($forumid, $userid) {
2313     global $CFG, $DB;
2315     $timedsql = "";
2316     $params = array($forumid, $userid);
2317     if (!empty($CFG->forum_enabletimedposts)) {
2318         $cm = get_coursemodule_from_instance('forum', $forumid);
2319         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2320             $now = time();
2321             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2322             $params[] = $now;
2323             $params[] = $now;
2324         }
2325     }
2327     return $DB->get_records_sql("SELECT DISTINCT d.*
2328                               FROM {forum} f
2329                                    JOIN {forum_discussions} d ON d.forum = f.id
2330                                    JOIN {forum_posts} p       ON p.discussion = d.id
2331                              WHERE f.id = ?
2332                                    AND p.userid = ?
2333                                    $timedsql", $params);
2336 /**
2337  * Get all the posts for a user in a forum suitable for forum_print_post
2338  *
2339  * @global object
2340  * @global object
2341  * @param int $forumid
2342  * @param int $userid
2343  * @return array of counts or false
2344  */
2345 function forum_count_user_posts($forumid, $userid) {
2346     global $CFG, $DB;
2348     $timedsql = "";
2349     $params = array($forumid, $userid);
2350     if (!empty($CFG->forum_enabletimedposts)) {
2351         $cm = get_coursemodule_from_instance('forum', $forumid);
2352         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2353             $now = time();
2354             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2355             $params[] = $now;
2356             $params[] = $now;
2357         }
2358     }
2360     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2361                              FROM {forum} f
2362                                   JOIN {forum_discussions} d ON d.forum = f.id
2363                                   JOIN {forum_posts} p       ON p.discussion = d.id
2364                                   JOIN {user} u              ON u.id = p.userid
2365                             WHERE f.id = ?
2366                                   AND p.userid = ?
2367                                   $timedsql", $params);
2370 /**
2371  * Given a log entry, return the forum post details for it.
2372  *
2373  * @global object
2374  * @global object
2375  * @param object $log
2376  * @return array|null
2377  */
2378 function forum_get_post_from_log($log) {
2379     global $CFG, $DB;
2381     if ($log->action == "add post") {
2383         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2384                                            u.firstname, u.lastname, u.email, u.picture
2385                                  FROM {forum_discussions} d,
2386                                       {forum_posts} p,
2387                                       {forum} f,
2388                                       {user} u
2389                                 WHERE p.id = ?
2390                                   AND d.id = p.discussion
2391                                   AND p.userid = u.id
2392                                   AND u.deleted <> '1'
2393                                   AND f.id = d.forum", array($log->info));
2396     } else if ($log->action == "add discussion") {
2398         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2399                                            u.firstname, u.lastname, u.email, u.picture
2400                                  FROM {forum_discussions} d,
2401                                       {forum_posts} p,
2402                                       {forum} f,
2403                                       {user} u
2404                                 WHERE d.id = ?
2405                                   AND d.firstpost = p.id
2406                                   AND p.userid = u.id
2407                                   AND u.deleted <> '1'
2408                                   AND f.id = d.forum", array($log->info));
2409     }
2410     return NULL;
2413 /**
2414  * Given a discussion id, return the first post from the discussion
2415  *
2416  * @global object
2417  * @global object
2418  * @param int $dicsussionid
2419  * @return array
2420  */
2421 function forum_get_firstpost_from_discussion($discussionid) {
2422     global $CFG, $DB;
2424     return $DB->get_record_sql("SELECT p.*
2425                              FROM {forum_discussions} d,
2426                                   {forum_posts} p
2427                             WHERE d.id = ?
2428                               AND d.firstpost = p.id ", array($discussionid));
2431 /**
2432  * Returns an array of counts of replies to each discussion
2433  *
2434  * @global object
2435  * @global object
2436  * @param int $forumid
2437  * @param string $forumsort
2438  * @param int $limit
2439  * @param int $page
2440  * @param int $perpage
2441  * @return array
2442  */
2443 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2444     global $CFG, $DB;
2446     if ($limit > 0) {
2447         $limitfrom = 0;
2448         $limitnum  = $limit;
2449     } else if ($page != -1) {
2450         $limitfrom = $page*$perpage;
2451         $limitnum  = $perpage;
2452     } else {
2453         $limitfrom = 0;
2454         $limitnum  = 0;
2455     }
2457     if ($forumsort == "") {
2458         $orderby = "";
2459         $groupby = "";
2461     } else {
2462         $orderby = "ORDER BY $forumsort";
2463         $groupby = ", ".strtolower($forumsort);
2464         $groupby = str_replace('desc', '', $groupby);
2465         $groupby = str_replace('asc', '', $groupby);
2466     }
2468     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2469         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2470                   FROM {forum_posts} p
2471                        JOIN {forum_discussions} d ON p.discussion = d.id
2472                  WHERE p.parent > 0 AND d.forum = ?
2473               GROUP BY p.discussion";
2474         return $DB->get_records_sql($sql, array($forumid));
2476     } else {
2477         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2478                   FROM {forum_posts} p
2479                        JOIN {forum_discussions} d ON p.discussion = d.id
2480                  WHERE d.forum = ?
2481               GROUP BY p.discussion $groupby
2482               $orderby";
2483         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2484     }
2487 /**
2488  * @global object
2489  * @global object
2490  * @global object
2491  * @staticvar array $cache
2492  * @param object $forum
2493  * @param object $cm
2494  * @param object $course
2495  * @return mixed
2496  */
2497 function forum_count_discussions($forum, $cm, $course) {
2498     global $CFG, $DB, $USER;
2500     static $cache = array();
2502     $now = round(time(), -2); // db cache friendliness
2504     $params = array($course->id);
2506     if (!isset($cache[$course->id])) {
2507         if (!empty($CFG->forum_enabletimedposts)) {
2508             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2509             $params[] = $now;
2510             $params[] = $now;
2511         } else {
2512             $timedsql = "";
2513         }
2515         $sql = "SELECT f.id, COUNT(d.id) as dcount
2516                   FROM {forum} f
2517                        JOIN {forum_discussions} d ON d.forum = f.id
2518                  WHERE f.course = ?
2519                        $timedsql
2520               GROUP BY f.id";
2522         if ($counts = $DB->get_records_sql($sql, $params)) {
2523             foreach ($counts as $count) {
2524                 $counts[$count->id] = $count->dcount;
2525             }
2526             $cache[$course->id] = $counts;
2527         } else {
2528             $cache[$course->id] = array();
2529         }
2530     }
2532     if (empty($cache[$course->id][$forum->id])) {
2533         return 0;
2534     }
2536     $groupmode = groups_get_activity_groupmode($cm, $course);
2538     if ($groupmode != SEPARATEGROUPS) {
2539         return $cache[$course->id][$forum->id];
2540     }
2542     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2543         return $cache[$course->id][$forum->id];
2544     }
2546     require_once($CFG->dirroot.'/course/lib.php');
2548     $modinfo =& get_fast_modinfo($course);
2549     if (is_null($modinfo->groups)) {
2550         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2551     }
2553     if (empty($CFG->enablegroupings)) {
2554         $mygroups = $modinfo->groups[0];
2555     } else {
2556         if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2557             $mygroups = $modinfo->groups[$cm->groupingid];
2558         } else {
2559             $mygroups = false; // Will be set below
2560         }
2561     }
2563     // add all groups posts
2564     if (empty($mygroups)) {
2565         $mygroups = array(-1=>-1);
2566     } else {
2567         $mygroups[-1] = -1;
2568     }
2570     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2571     $params[] = $forum->id;
2573     if (!empty($CFG->forum_enabletimedposts)) {
2574         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2575         $params[] = $now;
2576         $params[] = $now;
2577     } else {
2578         $timedsql = "";
2579     }
2581     $sql = "SELECT COUNT(d.id)
2582               FROM {forum_discussions} d
2583              WHERE d.groupid $mygroups_sql AND d.forum = ?
2584                    $timedsql";
2586     return $DB->get_field_sql($sql, $params);
2589 /**
2590  * How many unrated posts are in the given discussion for a given user?
2591  *
2592  * @global object
2593  * @global object
2594  * @param int $discussionid
2595  * @param int $userid
2596  * @return mixed
2597  */
2598 function forum_count_unrated_posts($discussionid, $userid) {
2599     global $CFG, $DB;
2600     if ($posts = $DB->get_record_sql("SELECT count(*) as num
2601                                    FROM {forum_posts}
2602                                   WHERE parent > 0
2603                                     AND discussion = ?
2604                                     AND userid <> ? ", array($discussionid, $userid))) {
2606         if ($rated = $DB->get_record_sql("SELECT count(*) as num
2607                                        FROM {forum_posts} p,
2608                                             {forum_ratings} r
2609                                       WHERE p.discussion = ?
2610                                         AND p.id = r.post
2611                                         AND r.userid = ?", array($discussionid, $userid))) {
2612             $difference = $posts->num - $rated->num;
2613             if ($difference > 0) {
2614                 return $difference;
2615             } else {
2616                 return 0;    // Just in case there was a counting error
2617             }
2618         } else {
2619             return $posts->num;
2620         }
2621     } else {
2622         return 0;
2623     }
2626 /**
2627  * Get all discussions in a forum
2628  *
2629  * @global object
2630  * @global object
2631  * @global object
2632  * @uses CONTEXT_MODULE
2633  * @uses VISIBLEGROUPS
2634  * @param object $cm
2635  * @param string $forumsort
2636  * @param bool $fullpost
2637  * @param int $unused
2638  * @param int $limit
2639  * @param bool $userlastmodified
2640  * @param int $page
2641  * @param int $perpage
2642  * @return array
2643  */
2644 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2645     global $CFG, $DB, $USER;
2647     $timelimit = '';
2649     $modcontext = null;
2651     $now = round(time(), -2);
2652     $params = array($cm->instance);
2654     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2656     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2657         return array();
2658     }
2660     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2662         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2663             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2664             $params[] = $now;
2665             $params[] = $now;
2666             if (isloggedin()) {
2667                 $timelimit .= " OR d.userid = ?";
2668                 $params[] = $USER->id;
2669             }
2670             $timelimit .= ")";
2671         }
2672     }
2674     if ($limit > 0) {
2675         $limitfrom = 0;
2676         $limitnum  = $limit;
2677     } else if ($page != -1) {
2678         $limitfrom = $page*$perpage;
2679         $limitnum  = $perpage;
2680     } else {
2681         $limitfrom = 0;
2682         $limitnum  = 0;
2683     }
2685     $groupmode    = groups_get_activity_groupmode($cm);
2686     $currentgroup = groups_get_activity_group($cm);
2688     if ($groupmode) {
2689         if (empty($modcontext)) {
2690             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2691         }
2693         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2694             if ($currentgroup) {
2695                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2696                 $params[] = $currentgroup;
2697             } else {
2698                 $groupselect = "";
2699             }
2701         } else {
2702             //seprate groups without access all
2703             if ($currentgroup) {
2704                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2705                 $params[] = $currentgroup;
2706             } else {
2707                 $groupselect = "AND d.groupid = -1";
2708             }
2709         }
2710     } else {
2711         $groupselect = "";
2712     }
2715     if (empty($forumsort)) {
2716         $forumsort = "d.timemodified DESC";
2717     }
2718     if (empty($fullpost)) {
2719         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2720     } else {
2721         $postdata = "p.*";
2722     }
2724     if (empty($userlastmodified)) {  // We don't need to know this
2725         $umfields = "";
2726         $umtable  = "";
2727     } else {
2728         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2729         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2730     }
2732     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2733                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2734               FROM {forum_discussions} d
2735                    JOIN {forum_posts} p ON p.discussion = d.id
2736                    JOIN {user} u ON p.userid = u.id
2737                    $umtable
2738              WHERE d.forum = ? AND p.parent = 0
2739                    $timelimit $groupselect
2740           ORDER BY $forumsort";
2741     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2744 /**
2745  *
2746  * @global object
2747  * @global object
2748  * @global object
2749  * @uses CONTEXT_MODULE
2750  * @uses VISIBLEGROUPS
2751  * @param object $cm
2752  * @return array
2753  */
2754 function forum_get_discussions_unread($cm) {
2755     global $CFG, $DB, $USER;
2757     $now = round(time(), -2);
2758     $params = array($cutoffdate);
2759     $groupmode    = groups_get_activity_groupmode($cm);
2760     $currentgroup = groups_get_activity_group($cm);
2762     if ($groupmode) {
2763         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2765         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2766             if ($currentgroup) {
2767                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2768                 $params[] = $currentgroup;
2769             } else {
2770                 $groupselect = "";
2771             }
2773         } else {
2774             //seprate groups without access all
2775             if ($currentgroup) {
2776                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2777                 $params[] = $currentgroup;
2778             } else {
2779                 $groupselect = "AND d.groupid = -1";
2780             }
2781         }
2782     } else {
2783         $groupselect = "";
2784     }
2786     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2788     if (!empty($CFG->forum_enabletimedposts)) {
2789         $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2790         $params[] = $now;
2791         $params[] = $now;
2792     } else {
2793         $timedsql = "";
2794     }
2796     $sql = "SELECT d.id, COUNT(p.id) AS unread
2797               FROM {forum_discussions} d
2798                    JOIN {forum_posts} p     ON p.discussion = d.id
2799                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2800              WHERE d.forum = {$cm->instance}
2801                    AND p.modified >= ? AND r.id is NULL
2802                    $groupselect
2803                    $timedsql
2804           GROUP BY d.id";
2805     if ($unreads = $DB->get_records_sql($sql, $params)) {
2806         foreach ($unreads as $unread) {
2807             $unreads[$unread->id] = $unread->unread;
2808         }
2809         return $unreads;
2810     } else {
2811         return array();
2812     }
2815 /**
2816  * @global object
2817  * @global object
2818  * @global object
2819  * @uses CONEXT_MODULE
2820  * @uses VISIBLEGROUPS
2821  * @param object $cm
2822  * @return array
2823  */
2824 function forum_get_discussions_count($cm) {
2825     global $CFG, $DB, $USER;
2827     $now = round(time(), -2);
2828     $params = array($cm->instance);
2829     $groupmode    = groups_get_activity_groupmode($cm);
2830     $currentgroup = groups_get_activity_group($cm);
2832     if ($groupmode) {
2833         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2835         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2836             if ($currentgroup) {
2837                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2838                 $params[] = $currentgroup;
2839             } else {
2840                 $groupselect = "";
2841             }
2843         } else {
2844             //seprate groups without access all
2845             if ($currentgroup) {
2846                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2847                 $params[] = $currentgroup;
2848             } else {
2849                 $groupselect = "AND d.groupid = -1";
2850             }
2851         }
2852     } else {
2853         $groupselect = "";
2854     }
2856     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2858     $timelimit = "";
2860     if (!empty($CFG->forum_enabletimedposts)) {
2862         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2864         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2865             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2866             $params[] = $now;
2867             $params[] = $now;
2868             if (isloggedin()) {
2869                 $timelimit .= " OR d.userid = ?";
2870                 $params[] = $USER->id;
2871             }
2872             $timelimit .= ")";
2873         }
2874     }
2876     $sql = "SELECT COUNT(d.id)
2877               FROM {forum_discussions} d
2878                    JOIN {forum_posts} p ON p.discussion = d.id
2879              WHERE d.forum = ? AND p.parent = 0
2880                    $groupselect $timelimit";
2882     return $DB->get_field_sql($sql, $params);
2886 /**
2887  * Get all discussions started by a particular user in a course (or group)
2888  * This function no longer used ...
2889  *
2890  * @todo Remove this function if no longer used
2891  * @global object
2892  * @global object
2893  * @param int $courseid
2894  * @param int $userid
2895  * @param int $groupid
2896  * @return array
2897  */
2898 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2899     global $CFG, $DB;
2900     $params = array($courseid, $userid);
2901     if ($groupid) {
2902         $groupselect = " AND d.groupid = ? ";
2903         $params[] = $groupid;
2904     } else  {
2905         $groupselect = "";
2906     }
2908     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2909                                    f.type as forumtype, f.name as forumname, f.id as forumid
2910                               FROM {forum_discussions} d,
2911                                    {forum_posts} p,
2912                                    {user} u,
2913                                    {forum} f
2914                              WHERE d.course = ?
2915                                AND p.discussion = d.id
2916                                AND p.parent = 0
2917                                AND p.userid = u.id
2918                                AND u.id = ?
2919                                AND d.forum = f.id $groupselect
2920                           ORDER BY p.created DESC", $params);
2923 /**
2924  * Get the list of potential subscribers to a forum.
2925  *
2926  * @param object $forumcontext the forum context.
2927  * @param integer $groupid the id of a group, or 0 for all groups.
2928  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2929  * @param string $sort sort order. As for get_users_by_capability.
2930  * @return array list of users.
2931  */
2932 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2933     return get_users_by_capability($forumcontext, 'mod/forum:initialsubscriptions', $fields, $sort, '', '', $groupid, '', false, true);
2936 /**
2937  * Returns list of user objects that are subscribed to this forum
2938  *
2939  * @global object
2940  * @global object
2941  * @param object $course the course
2942  * @param forum $forum the forum
2943  * @param integer $groupid group id, or 0 for all.
2944  * @param object $context the forum context, to save re-fetching it where possible.
2945  * @return array list of users.
2946  */
2947 function forum_subscribed_users($course, $forum, $groupid=0, $context = NULL) {
2948     global $CFG, $DB;
2949     $params = array($forum->id);
2951     if ($groupid) {
2952         $grouptables = ", {groups_members} gm ";
2953         $groupselect = "AND gm.groupid = ? AND u.id = gm.userid";
2954         $params[] = $groupid;
2955     } else  {
2956         $grouptables = '';
2957         $groupselect = '';
2958     }
2960     $fields ="u.id,
2961               u.username,
2962               u.firstname,
2963               u.lastname,
2964               u.maildisplay,
2965               u.mailformat,
2966               u.maildigest,
2967               u.emailstop,
2968               u.imagealt,
2969               u.email,
2970               u.city,
2971               u.country,
2972               u.lastaccess,
2973               u.lastlogin,
2974               u.picture,
2975               u.timezone,
2976               u.theme,
2977               u.lang,
2978               u.trackforums,
2979               u.mnethostid";
2981     if (forum_is_forcesubscribed($forum)) {
2982         if (empty($context)) {
2983             $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2984             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2985         }
2986         $sort = "u.email ASC";
2987         $results = forum_get_potential_subscribers($context, $groupid, $fields, $sort);
2988     } else {
2989         $results = $DB->get_records_sql("SELECT $fields
2990                               FROM {user} u,
2991                                    {forum_subscriptions} s $grouptables
2992                              WHERE s.forum = ?
2993                                AND s.userid = u.id
2994                                AND u.deleted = 0  $groupselect
2995                           ORDER BY u.email ASC", $params);
2996     }
2998     static $guestid = null;
3000     if (is_null($guestid)) {
3001         if ($guest = guest_user()) {
3002             $guestid = $guest->id;
3003         } else {
3004             $guestid = 0;
3005         }
3006     }
3008     // Guest user should never be subscribed to a forum.
3009     unset($results[$guestid]);
3011     return $results;
3016 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
3019 /**
3020  * @global object
3021  * @global object
3022  * @param int $courseid
3023  * @param string $type
3024  */
3025 function forum_get_course_forum($courseid, $type) {
3026 // How to set up special 1-per-course forums
3027     global $CFG, $DB, $OUTPUT;
3029     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
3030         // There should always only be ONE, but with the right combination of
3031         // errors there might be more.  In this case, just return the oldest one (lowest ID).
3032         foreach ($forums as $forum) {
3033             return $forum;   // ie the first one
3034         }
3035     }
3037     // Doesn't exist, so create one now.
3038     $forum->course = $courseid;
3039     $forum->type = "$type";
3040     switch ($forum->type) {
3041         case "news":
3042             $forum->name  = get_string("namenews", "forum");
3043             $forum->intro = get_string("intronews", "forum");
3044             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
3045             $forum->assessed = 0;
3046             if ($courseid == SITEID) {
3047                 $forum->name  = get_string("sitenews");
3048                 $forum->forcesubscribe = 0;
3049             }
3050             break;
3051         case "social":
3052             $forum->name  = get_string("namesocial", "forum");
3053             $forum->intro = get_string("introsocial", "forum");
3054             $forum->assessed = 0;
3055             $forum->forcesubscribe = 0;
3056             break;
3057         case "blog":
3058             $forum->name = get_string('blogforum', 'forum');
3059             $forum->intro = get_string('introblog', 'forum');
3060             $forum->assessed = 0;
3061             $forum->forcesubscribe = 0;
3062             break;
3063         default:
3064             echo $OUTPUT->notification("That forum type doesn't exist!");
3065             return false;
3066             break;
3067     }
3069     $forum->timemodified = time();
3070     $forum->id = $DB->insert_record("forum", $forum);
3072     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
3073         echo $OUTPUT->notification("Could not find forum module!!");
3074         return false;
3075     }
3076     $mod = new object();
3077     $mod->course = $courseid;
3078     $mod->module = $module->id;
3079     $mod->instance = $forum->id;
3080     $mod->section = 0;
3081     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
3082         echo $OUTPUT->notification("Could not add a new course module to the course '" . format_string($course->fullname) . "'");
3083         return false;
3084     }
3085     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
3086         echo $OUTPUT->notification("Could not add the new course module to that section");
3087         return false;
3088     }
3089     if (! $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule))) {
3090         echo $OUTPUT->notification("Could not update the course module with the correct section");
3091         return false;
3092     }
3093     include_once("$CFG->dirroot/course/lib.php");
3094     rebuild_course_cache($courseid);
3096     return $DB->get_record("forum", array("id" => "$forum->id"));
3100 /**
3101  * Given the data about a posting, builds up the HTML to display it and
3102  * returns the HTML in a string.  This is designed for sending via HTML email.
3103  *
3104  * @global object
3105  * @param object $course
3106  * @param object $cm
3107  * @param object $forum
3108  * @param object $discussion
3109  * @param object $post
3110  * @param object $userform
3111  * @param object $userto
3112  * @param bool $ownpost
3113  * @param bool $reply
3114  * @param bool $link
3115  * @param bool $rate
3116  * @param string $footer
3117  * @return string
3118  */
3119 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
3120                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
3122     global $CFG, $OUTPUT;
3124     if (!isset($userto->viewfullnames[$forum->id])) {
3125         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
3126             print_error('invalidcoursemodule');
3127         }
3128         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3129         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3130     } else {
3131         $viewfullnames = $userto->viewfullnames[$forum->id];
3132     }
3134     // format the post body
3135     $options = new object();
3136     $options->para = true;
3137     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3139     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3141     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3142     $output .= $OUTPUT->user_picture(moodle_user_picture::make($userfrom, $course->id));
3143     $output .= '</td>';
3145     if ($post->parent) {
3146         $output .= '<td class="topic">';
3147     } else {
3148         $output .= '<td class="topic starter">';
3149     }
3150     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3152     $fullname = fullname($userfrom, $viewfullnames);
3153     $by = new object();
3154     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3155     $by->date = userdate($post->modified, '', $userto->timezone);
3156     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3158     $output .= '</td></tr>';
3160     $output .= '<tr><td class="left side" valign="top">';
3162     if (isset($userfrom->groups)) {
3163         $groups = $userfrom->groups[$forum->id];
3164     } else {
3165         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
3166             print_error('invalidcoursemodule');
3167         }
3168         $group = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3169     }
3171     if ($groups) {
3172         $output .= print_group_picture($groups, $course->id, false, true, true);
3173     } else {
3174         $output .= '&nbsp;';
3175     }
3177     $output .= '</td><td class="content">';
3179     $attachments = forum_print_attachments($post, $cm, 'html');
3180     if ($attachments !== '') {
3181         $output .= '<div class="attachments">';
3182         $output .= $attachments;
3183         $output .= '</div>';
3184     }
3186     $output .= $formattedtext;
3188 // Commands
3189     $commands = array();
3191     if ($post->parent) {
3192         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3193                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3194     }
3196     if ($reply) {
3197         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3198                       get_string('reply', 'forum').'</a>';
3199     }
3201     $output .= '<div class="commands">';
3202     $output .= implode(' | ', $commands);
3203     $output .= '</div>';
3205 // Context link to post if required
3206     if ($link) {
3207         $output .= '<div class="link">';
3208         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3209                      get_string('postincontext', 'forum').'</a>';
3210         $output .= '</div>';
3211     }
3213     if ($footer) {
3214         $output .= '<div class="footer">'.$footer.'</div>';
3215     }
3216     $output .= '</td></tr></table>'."\n\n";
3218     return $output;
3221 /**
3222  * Print a forum post
3223  *
3224  * @global object
3225  * @global object
3226  * @uses FORUM_MODE_THREADED
3227  * @uses PORTFOLIO_FORMAT_PLAINHTML
3228  * @uses PORTFOLIO_FORMAT_FILE
3229  * @uses PORTFOLIO_FORMAT_RICHHTML
3230  * @uses PORTFOLIO_ADD_TEXT_LINK
3231  * @uses CONTEXT_MODULE
3232  * @param object $post The post to print.
3233  * @param object $discussion
3234  * @param object $forum
3235  * @param object $cm
3236  * @param object $course
3237  * @param boolean $ownpost Whether this post belongs to the current user.
3238  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3239  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3240  * @param object $ratings -- I don't really know --
3241  * @param string $footer Extra stuff to print after the message.
3242  * @param string $highlight Space-separated list of terms to highlight.
3243  * @param int $post_read true, false or -99. If we already know whether this user
3244  *          has read this post, pass that in, otherwise, pass in -99, and this
3245  *          function will work it out.
3246  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3247  *          the current user can't see this post, if this argument is true
3248  *          (the default) then print a dummy 'you can't see this post' post.
3249  *          If false, don't output anything at all.
3250  * @param bool|null $istracked
3251  */
3252 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3253                           $ratings=NULL, $footer="", $highlight="", $post_read=null, $dummyifcantsee=true, $istracked=null) {
3255     global $USER, $CFG, $OUTPUT;
3257     static $stredit, $strdelete, $strreply, $strparent, $strprune;
3258     static $strpruneheading, $displaymode;
3259     static $strmarkread, $strmarkunread;
3261     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3263     $post->course = $course->id;
3264     $post->forum  = $forum->id;
3265     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'forum_post', $post->id);
3267     // caching
3268     if (!isset($cm->cache)) {
3269         $cm->cache = new object();
3270     }
3272     if (!isset($cm->cache->caps)) {
3273         $cm->cache->caps = array();
3274         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3275         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3276         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3277         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3278         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3279         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3280         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3281         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3282         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3283     }
3285     if (!isset($cm->uservisible)) {
3286         $cm->uservisible = coursemodule_visible_for_user($cm);
3287     }
3289     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3290         if (!$dummyifcantsee) {
3291             return;
3292         }
3293         echo '<a id="p'.$post->id.'"></a>';
3294         echo '<table cellspacing="0" class="forumpost">';
3295         echo '<tr class="header"><td class="picture left">';
3296         //        print_user_picture($post->userid, $courseid, $post->picture);
3297         echo '</td>';
3298         if ($post->parent) {
3299             echo '<td class="topic">';
3300         } else {
3301             echo '<td class="topic starter">';
3302         }
3303         echo '<div class="subject">'.get_string('forumsubjecthidden','forum').'</div>';
3304         echo '<div class="author">';
3305         print_string('forumauthorhidden','forum');
3306         echo '</div></td></tr>';
3308         echo '<tr><td class="left side">';
3309         echo '&nbsp;';
3311         // Actual content
3313         echo '</td><td class="content">'."\n";
3314         echo get_string('forumbodyhidden','forum');
3315         echo '</td></tr></table>';
3316         return;
3317     }
3319     if (empty($stredit)) {
3320         $stredit         = get_string('edit', 'forum');
3321         $strdelete       = get_string('delete', 'forum');
3322         $strreply        = get_string('reply', 'forum');
3323         $strparent       = get_string('parent', 'forum');
3324         $strpruneheading = get_string('pruneheading', 'forum');
3325         $strprune        = get_string('prune', 'forum');
3326         $displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3327         $strmarkread     = get_string('markread', 'forum');
3328         $strmarkunread   = get_string('markunread', 'forum');
3330     }
3332     $read_style = '';
3333     // ignore trackign status if not tracked or tracked param missing
3334     if ($istracked) {
3335         if (is_null($post_read)) {
3336             debugging('fetching post_read info');
3337             $post_read = forum_tp_is_post_read($USER->id, $post);
3338         }
3340         if ($post_read) {
3341             $read_style = ' read';
3342         } else {
3343             $read_style = ' unread';
3344             echo '<a name="unread"></a>';
3345         }
3346     }
3348     echo '<a id="p'.$post->id.'"></a>';
3349     echo '<table cellspacing="0" class="forumpost'.$read_style.'">';
3351     // Picture
3352     $postuser = new object();
3353     $postuser->id        = $post->userid;
3354     $postuser->firstname = $post->firstname;
3355     $postuser->lastname  = $post->lastname;
3356     $postuser->imagealt  = $post->imagealt;
3357     $postuser->picture   = $post->picture;
3359     echo '<tr class="header"><td class="picture left">';
3360     echo $OUTPUT->user_picture(moodle_user_picture::make($postuser, $course->id));
3361     echo '</td>';
3363     if ($post->parent) {
3364         echo '<td class="topic">';
3365     } else {
3366         echo '<td class="topic starter">';
3367     }
3369     if (!empty($post->subjectnoformat)) {
3370         echo '<div class="subject">'.$post->subject.'</div>';
3371     } else {
3372         echo '<div class="subject">'.format_string($post->subject).'</div>';
3373     }
3375     echo '<div class="author">';
3376     $fullname = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3377     $by = new object();
3378     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.
3379                 $post->userid.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3380     $by->date = userdate($post->modified);
3381     print_string('bynameondate', 'forum', $by);
3382     echo '</div></td></tr>';
3384     echo '<tr><td class="left side">';
3385     if (isset($cm->cache->usersgroups)) {
3386         $groups = array();
3387         if (isset($cm->cache->usersgroups[$post->userid])) {
3388             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3389                 $groups[$gid] = $cm->cache->groups[$gid];
3390             }
3391         }
3392     } else {
3393         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3394     }
3396     if ($groups) {
3397         print_group_picture($groups, $course->id, false, false, true);
3398     } else {
3399         echo '&nbsp;';
3400     }
3402 // Actual content
3404     echo '</td><td class="content">'."\n";
3406     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3408     if ($attachments !== '') {
3409         echo '<div class="attachments">';
3410         echo $attachments;
3411         echo '</div>';
3412     }
3414     $options = new object();
3415     $options->para    = false;
3416     $options->trusted = $post->messagetrust;
3417     if ($link and (strlen(strip_tags($post->message)) > $CFG->forum_longpost)) {
3418         // Print shortened version
3419         echo format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3420         $numwords = count_words(strip_tags($post->message));
3421         echo '<div class="posting"><a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3422         echo get_string('readtherest', 'forum');
3423         echo '</a> ('.get_string('numwords', '', $numwords).')...</div>';
3424     } else {
3425         // Print whole message
3426         echo '<div class="posting">';
3427         if ($highlight) {
3428             echo highlight($highlight, format_text($post->message, $post->messageformat, $options, $course->id));
3429         } else {
3430             echo format_text($post->message, $post->messageformat, $options, $course->id);
3431         }
3432         echo '</div>';
3433         echo $attachedimages;
3434     }
3437 // Commands
3439     $commands = array();
3441     if ($istracked) {
3442         // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3443         // Don't display the mark read / unread controls in this case.
3444         if ($CFG->forum_usermarksread and isloggedin()) {
3445             if ($post_read) {
3446                 $mcmd = '&amp;mark=unread&amp;postid='.$post->id;
3447                 $mtxt = $strmarkunread;
3448             } else {
3449                 $mcmd = '&amp;mark=read&amp;postid='.$post->id;
3450                 $mtxt = $strmarkread;
3451             }
3452             if ($displaymode == FORUM_MODE_THREADED) {
3453                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3454                               $post->discussion.'&amp;parent='.$post->id.$mcmd.'">'.$mtxt.'</a>';
3455             } else {
3456                 $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3457                               $post->discussion.$mcmd.'#p'.$post->id.'">'.$mtxt.'</a>';
3458             }
3459         }
3460     }
3462     if ($post->parent) {  // Zoom in to the parent specifically
3463         if ($displaymode == FORUM_MODE_THREADED) {
3464             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3465                       $post->discussion.'&amp;parent='.$post->parent.'">'.$strparent.'</a>';
3466         } else {
3467             $commands[] = '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3468                       $post->discussion.'#p'.$post->parent.'">'.$strparent.'</a>';
3469         }
3470     }
3472     $age = time() - $post->created;
3473     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3474     if (!$post->parent and $forum->type == 'news' and $discussion->timestart > time()) {
3475         $age = 0;
3476     }
3477     $editanypost = $cm->cache->caps['mod/forum:editanypost'];
3479     if ($ownpost or $editanypost) {
3480         if (($age < $CFG->maxeditingtime) or $editanypost) {
3481             $commands[] =  '<a href="'.$CFG->wwwroot.'/mod/forum/post.php?edit='.$post->id.'">'.$stredit.'</a>';
3482         }
3483     }
3485     if ($cm->cache->caps['mod/forum:splitdiscussions']