MDL-28615 forum: Fixed up a couple of minor regressions
[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->dirroot.'/user/selector/lib.php');
29 /// CONSTANTS ///////////////////////////////////////////////////////////
31 define('FORUM_MODE_FLATOLDEST', 1);
32 define('FORUM_MODE_FLATNEWEST', -1);
33 define('FORUM_MODE_THREADED', 2);
34 define('FORUM_MODE_NESTED', 3);
36 define('FORUM_CHOOSESUBSCRIBE', 0);
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 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
47 /**
48  * Given an object containing all the necessary data,
49  * (defined by the form in mod_form.php) this function
50  * will create a new instance and return the id number
51  * of the new instance.
52  *
53  * @global object
54  * @global object
55  * @param object $forum add forum instance (with magic quotes)
56  * @return int intance id
57  */
58 function forum_add_instance($forum, $mform) {
59     global $CFG, $DB;
61     $forum->timemodified = time();
63     if (empty($forum->assessed)) {
64         $forum->assessed = 0;
65     }
67     if (empty($forum->ratingtime) or empty($forum->assessed)) {
68         $forum->assesstimestart  = 0;
69         $forum->assesstimefinish = 0;
70     }
72     $forum->id = $DB->insert_record('forum', $forum);
73     $modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule);
75     if ($forum->type == 'single') {  // Create related discussion.
76         $discussion = new stdClass();
77         $discussion->course        = $forum->course;
78         $discussion->forum         = $forum->id;
79         $discussion->name          = $forum->name;
80         $discussion->assessed      = $forum->assessed;
81         $discussion->message       = $forum->intro;
82         $discussion->messageformat = $forum->introformat;
83         $discussion->messagetrust  = trusttext_trusted(get_context_instance(CONTEXT_COURSE, $forum->course));
84         $discussion->mailnow       = false;
85         $discussion->groupid       = -1;
87         $message = '';
89         $discussion->id = forum_add_discussion($discussion, null, $message);
91         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
92             // ugly hack - we need to copy the files somehow
93             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
94             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
96             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
97             $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
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($modcontext, 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, $mform) {
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 stdClass();
161                 $discussion->course          = $forum->course;
162                 $discussion->forum           = $forum->id;
163                 $discussion->name            = $forum->name;
164                 $discussion->assessed        = $forum->assessed;
165                 $discussion->message         = $forum->intro;
166                 $discussion->messageformat   = $forum->introformat;
167                 $discussion->messagetrust    = true;
168                 $discussion->mailnow         = false;
169                 $discussion->groupid         = -1;
171                 $message = '';
173                 forum_add_discussion($discussion, null, $message);
175                 if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
176                     print_error('cannotadd', 'forum');
177                 }
178             }
179         }
180         if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
181             print_error('cannotfindfirstpost', 'forum');
182         }
184         $cm         = get_coursemodule_from_instance('forum', $forum->id);
185         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
187         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
188             // ugly hack - we need to copy the files somehow
189             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
190             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
192             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
193         }
195         $post->subject       = $forum->name;
196         $post->message       = $forum->intro;
197         $post->messageformat = $forum->introformat;
198         $post->messagetrust  = trusttext_trusted($modcontext);
199         $post->modified      = $forum->timemodified;
200         $post->userid        = $USER->id;    // MDL-18599, so that current teacher can take ownership of activities
202         $DB->update_record('forum_posts', $post);
203         $discussion->name = $forum->name;
204         $DB->update_record('forum_discussions', $discussion);
205     }
207     $DB->update_record('forum', $forum);
209     forum_grade_item_update($forum);
211     return true;
215 /**
216  * Given an ID of an instance of this module,
217  * this function will permanently delete the instance
218  * and any data that depends on it.
219  *
220  * @global object
221  * @param int $id forum instance id
222  * @return bool success
223  */
224 function forum_delete_instance($id) {
225     global $DB;
227     if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
228         return false;
229     }
230     if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
231         return false;
232     }
233     if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
234         return false;
235     }
237     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
239     // now get rid of all files
240     $fs = get_file_storage();
241     $fs->delete_area_files($context->id);
243     $result = true;
245     if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
246         foreach ($discussions as $discussion) {
247             if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
248                 $result = false;
249             }
250         }
251     }
253     if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
254         $result = false;
255     }
257     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
259     if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
260         $result = false;
261     }
263     forum_grade_item_delete($forum);
265     return $result;
269 /**
270  * Indicates API features that the forum supports.
271  *
272  * @uses FEATURE_GROUPS
273  * @uses FEATURE_GROUPINGS
274  * @uses FEATURE_GROUPMEMBERSONLY
275  * @uses FEATURE_MOD_INTRO
276  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
277  * @uses FEATURE_COMPLETION_HAS_RULES
278  * @uses FEATURE_GRADE_HAS_GRADE
279  * @uses FEATURE_GRADE_OUTCOMES
280  * @param string $feature
281  * @return mixed True if yes (some features may use other values)
282  */
283 function forum_supports($feature) {
284     switch($feature) {
285         case FEATURE_GROUPS:                  return true;
286         case FEATURE_GROUPINGS:               return true;
287         case FEATURE_GROUPMEMBERSONLY:        return true;
288         case FEATURE_MOD_INTRO:               return true;
289         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
290         case FEATURE_COMPLETION_HAS_RULES:    return true;
291         case FEATURE_GRADE_HAS_GRADE:         return true;
292         case FEATURE_GRADE_OUTCOMES:          return true;
293         case FEATURE_RATE:                    return true;
294         case FEATURE_BACKUP_MOODLE2:          return true;
295         case FEATURE_SHOW_DESCRIPTION:        return true;
297         default: return null;
298     }
302 /**
303  * Obtains the automatic completion state for this forum based on any conditions
304  * in forum settings.
305  *
306  * @global object
307  * @global object
308  * @param object $course Course
309  * @param object $cm Course-module
310  * @param int $userid User ID
311  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
312  * @return bool True if completed, false if not. (If no conditions, then return
313  *   value depends on comparison type)
314  */
315 function forum_get_completion_state($course,$cm,$userid,$type) {
316     global $CFG,$DB;
318     // Get forum details
319     if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
320         throw new Exception("Can't find forum {$cm->instance}");
321     }
323     $result=$type; // Default return value
325     $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
326     $postcountsql="
327 SELECT
328     COUNT(1)
329 FROM
330     {forum_posts} fp
331     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
332 WHERE
333     fp.userid=:userid AND fd.forum=:forumid";
335     if ($forum->completiondiscussions) {
336         $value = $forum->completiondiscussions <=
337                  $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
338         if ($type == COMPLETION_AND) {
339             $result = $result && $value;
340         } else {
341             $result = $result || $value;
342         }
343     }
344     if ($forum->completionreplies) {
345         $value = $forum->completionreplies <=
346                  $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
347         if ($type==COMPLETION_AND) {
348             $result = $result && $value;
349         } else {
350             $result = $result || $value;
351         }
352     }
353     if ($forum->completionposts) {
354         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
355         if ($type == COMPLETION_AND) {
356             $result = $result && $value;
357         } else {
358             $result = $result || $value;
359         }
360     }
362     return $result;
366 /**
367  * Function to be run periodically according to the moodle cron
368  * Finds all posts that have yet to be mailed out, and mails them
369  * out to all subscribers
370  *
371  * @global object
372  * @global object
373  * @global object
374  * @uses CONTEXT_MODULE
375  * @uses CONTEXT_COURSE
376  * @uses SITEID
377  * @uses FORMAT_PLAIN
378  * @return void
379  */
380 function forum_cron() {
381     global $CFG, $USER, $DB;
383     $site = get_site();
385     // all users that are subscribed to any post that needs sending
386     $users = array();
388     // status arrays
389     $mailcount  = array();
390     $errorcount = array();
392     // caches
393     $discussions     = array();
394     $forums          = array();
395     $courses         = array();
396     $coursemodules   = array();
397     $subscribedusers = array();
400     // Posts older than 2 days will not be mailed.  This is to avoid the problem where
401     // cron has not been running for a long time, and then suddenly people are flooded
402     // with mail from the past few weeks or months
403     $timenow   = time();
404     $endtime   = $timenow - $CFG->maxeditingtime;
405     $starttime = $endtime - 48 * 3600;   // Two days earlier
407     if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
408         // Mark them all now as being mailed.  It's unlikely but possible there
409         // might be an error later so that a post is NOT actually mailed out,
410         // but since mail isn't crucial, we can accept this risk.  Doing it now
411         // prevents the risk of duplicated mails, which is a worse problem.
413         if (!forum_mark_old_posts_as_mailed($endtime)) {
414             mtrace('Errors occurred while trying to mark some posts as being mailed.');
415             return false;  // Don't continue trying to mail them, in case we are in a cron loop
416         }
418         // checking post validity, and adding users to loop through later
419         foreach ($posts as $pid => $post) {
421             $discussionid = $post->discussion;
422             if (!isset($discussions[$discussionid])) {
423                 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
424                     $discussions[$discussionid] = $discussion;
425                 } else {
426                     mtrace('Could not find discussion '.$discussionid);
427                     unset($posts[$pid]);
428                     continue;
429                 }
430             }
431             $forumid = $discussions[$discussionid]->forum;
432             if (!isset($forums[$forumid])) {
433                 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
434                     $forums[$forumid] = $forum;
435                 } else {
436                     mtrace('Could not find forum '.$forumid);
437                     unset($posts[$pid]);
438                     continue;
439                 }
440             }
441             $courseid = $forums[$forumid]->course;
442             if (!isset($courses[$courseid])) {
443                 if ($course = $DB->get_record('course', array('id' => $courseid))) {
444                     $courses[$courseid] = $course;
445                 } else {
446                     mtrace('Could not find course '.$courseid);
447                     unset($posts[$pid]);
448                     continue;
449                 }
450             }
451             if (!isset($coursemodules[$forumid])) {
452                 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
453                     $coursemodules[$forumid] = $cm;
454                 } else {
455                     mtrace('Could not find course module for forum '.$forumid);
456                     unset($posts[$pid]);
457                     continue;
458                 }
459             }
462             // caching subscribed users of each forum
463             if (!isset($subscribedusers[$forumid])) {
464                 $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
465                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext, "u.*")) {
466                     foreach ($subusers as $postuser) {
467                         unset($postuser->description); // not necessary
468                         // this user is subscribed to this forum
469                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
470                         // this user is a user we have to process later
471                         $users[$postuser->id] = $postuser;
472                     }
473                     unset($subusers); // release memory
474                 }
475             }
477             $mailcount[$pid] = 0;
478             $errorcount[$pid] = 0;
479         }
480     }
482     if ($users && $posts) {
484         $urlinfo = parse_url($CFG->wwwroot);
485         $hostname = $urlinfo['host'];
487         foreach ($users as $userto) {
489             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
491             // set this so that the capabilities are cached, and environment matches receiving user
492             cron_setup_user($userto);
494             mtrace('Processing user '.$userto->id);
496             // init caches
497             $userto->viewfullnames = array();
498             $userto->canpost       = array();
499             $userto->markposts     = array();
501             // reset the caches
502             foreach ($coursemodules as $forumid=>$unused) {
503                 $coursemodules[$forumid]->cache       = new stdClass();
504                 $coursemodules[$forumid]->cache->caps = array();
505                 unset($coursemodules[$forumid]->uservisible);
506             }
508             foreach ($posts as $pid => $post) {
510                 // Set up the environment for the post, discussion, forum, course
511                 $discussion = $discussions[$post->discussion];
512                 $forum      = $forums[$discussion->forum];
513                 $course     = $courses[$forum->course];
514                 $cm         =& $coursemodules[$forum->id];
516                 // Do some checks  to see if we can bail out now
517                 // Only active enrolled users are in the list of subscribers
518                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
519                     continue; // user does not subscribe to this forum
520                 }
522                 // Don't send email if the forum is Q&A and the user has not posted
523                 if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id)) {
524                     mtrace('Did not email '.$userto->id.' because user has not posted in discussion');
525                     continue;
526                 }
528                 // Get info about the sending user
529                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
530                     $userfrom = $users[$post->userid];
531                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
532                     unset($userfrom->description); // not necessary
533                     $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
534                 } else {
535                     mtrace('Could not find user '.$post->userid);
536                     continue;
537                 }
539                 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
541                 // setup global $COURSE properly - needed for roles and languages
542                 cron_setup_user($userto, $course);
544                 // Fill caches
545                 if (!isset($userto->viewfullnames[$forum->id])) {
546                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
547                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
548                 }
549                 if (!isset($userto->canpost[$discussion->id])) {
550                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
551                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
552                 }
553                 if (!isset($userfrom->groups[$forum->id])) {
554                     if (!isset($userfrom->groups)) {
555                         $userfrom->groups = array();
556                         $users[$userfrom->id]->groups = array();
557                     }
558                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
559                     $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
560                 }
562                 // Make sure groups allow this user to see this email
563                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
564                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
565                         continue;                           // Be safe and don't send it to anyone
566                     }
568                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
569                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
570                         continue;
571                     }
572                 }
574                 // Make sure we're allowed to see it...
575                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
576                     mtrace('user '.$userto->id. ' can not see '.$post->id);
577                     continue;
578                 }
580                 // OK so we need to send the email.
582                 // Does the user want this post in a digest?  If so postpone it for now.
583                 if ($userto->maildigest > 0) {
584                     // This user wants the mails to be in digest form
585                     $queue = new stdClass();
586                     $queue->userid       = $userto->id;
587                     $queue->discussionid = $discussion->id;
588                     $queue->postid       = $post->id;
589                     $queue->timemodified = $post->created;
590                     $DB->insert_record('forum_queue', $queue);
591                     continue;
592                 }
595                 // Prepare to actually send the post now, and build up the content
597                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
599                 $userfrom->customheaders = array (  // Headers to make emails easier to track
600                            'Precedence: Bulk',
601                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
602                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
603                            'Message-ID: <moodlepost'.$post->id.'@'.$hostname.'>',
604                            'X-Course-Id: '.$course->id,
605                            'X-Course-Name: '.format_string($course->fullname, true)
606                 );
608                 if ($post->parent) {  // This post is a reply, so add headers for threading (see MDL-22551)
609                     $userfrom->customheaders[] = 'In-Reply-To: <moodlepost'.$post->parent.'@'.$hostname.'>';
610                     $userfrom->customheaders[] = 'References: <moodlepost'.$post->parent.'@'.$hostname.'>';
611                 }
613                 $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
615                 $postsubject = "$shortname: ".format_string($post->subject,true);
616                 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
617                 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
619                 // Send the post now!
621                 mtrace('Sending ', '');
623                 $eventdata = new stdClass();
624                 $eventdata->component        = 'mod_forum';
625                 $eventdata->name             = 'posts';
626                 $eventdata->userfrom         = $userfrom;
627                 $eventdata->userto           = $userto;
628                 $eventdata->subject          = $postsubject;
629                 $eventdata->fullmessage      = $posttext;
630                 $eventdata->fullmessageformat = FORMAT_PLAIN;
631                 $eventdata->fullmessagehtml  = $posthtml;
632                 $eventdata->notification = 1;
634                 $smallmessagestrings = new stdClass();
635                 $smallmessagestrings->user = fullname($userfrom);
636                 $smallmessagestrings->forumname = "$shortname: ".format_string($forum->name,true).": ".$discussion->name;
637                 $smallmessagestrings->message = $post->message;
638                 //make sure strings are in message recipients language
639                 $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
641                 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
642                 $eventdata->contexturlname = $discussion->name;
644                 $mailresult = message_send($eventdata);
645                 if (!$mailresult){
646                     mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
647                          " ($userto->email) .. not trying again.");
648                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
649                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
650                     $errorcount[$post->id]++;
651                 } else {
652                     $mailcount[$post->id]++;
654                 // Mark post as read if forum_usermarksread is set off
655                     if (!$CFG->forum_usermarksread) {
656                         $userto->markposts[$post->id] = $post->id;
657                     }
658                 }
660                 mtrace('post '.$post->id. ': '.$post->subject);
661             }
663             // mark processed posts as read
664             forum_tp_mark_posts_read($userto, $userto->markposts);
665         }
666     }
668     if ($posts) {
669         foreach ($posts as $post) {
670             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
671             if ($errorcount[$post->id]) {
672                 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
673             }
674         }
675     }
677     // release some memory
678     unset($subscribedusers);
679     unset($mailcount);
680     unset($errorcount);
682     cron_setup_user();
684     $sitetimezone = $CFG->timezone;
686     // Now see if there are any digest mails waiting to be sent, and if we should send them
688     mtrace('Starting digest processing...');
690     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
692     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
693         set_config('digestmailtimelast', 0);
694     }
696     $timenow = time();
697     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
699     // Delete any really old ones (normally there shouldn't be any)
700     $weekago = $timenow - (7 * 24 * 3600);
701     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
702     mtrace ('Cleaned old digest records');
704     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
706         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
708         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
710         if ($digestposts_rs->valid()) {
712             // We have work to do
713             $usermailcount = 0;
715             //caches - reuse the those filled before too
716             $discussionposts = array();
717             $userdiscussions = array();
719             foreach ($digestposts_rs as $digestpost) {
720                 if (!isset($users[$digestpost->userid])) {
721                     if ($user = $DB->get_record('user', array('id' => $digestpost->userid))) {
722                         $users[$digestpost->userid] = $user;
723                     } else {
724                         continue;
725                     }
726                 }
727                 $postuser = $users[$digestpost->userid];
729                 if (!isset($posts[$digestpost->postid])) {
730                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
731                         $posts[$digestpost->postid] = $post;
732                     } else {
733                         continue;
734                     }
735                 }
736                 $discussionid = $digestpost->discussionid;
737                 if (!isset($discussions[$discussionid])) {
738                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
739                         $discussions[$discussionid] = $discussion;
740                     } else {
741                         continue;
742                     }
743                 }
744                 $forumid = $discussions[$discussionid]->forum;
745                 if (!isset($forums[$forumid])) {
746                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
747                         $forums[$forumid] = $forum;
748                     } else {
749                         continue;
750                     }
751                 }
753                 $courseid = $forums[$forumid]->course;
754                 if (!isset($courses[$courseid])) {
755                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
756                         $courses[$courseid] = $course;
757                     } else {
758                         continue;
759                     }
760                 }
762                 if (!isset($coursemodules[$forumid])) {
763                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
764                         $coursemodules[$forumid] = $cm;
765                     } else {
766                         continue;
767                     }
768                 }
769                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
770                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
771             }
772             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
774             // Data collected, start sending out emails to each user
775             foreach ($userdiscussions as $userid => $thesediscussions) {
777                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
779                 cron_setup_user();
781                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
783                 // First of all delete all the queue entries for this user
784                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
785                 $userto = $users[$userid];
787                 // Override the language and timezone of the "current" user, so that
788                 // mail is customised for the receiver.
789                 cron_setup_user($userto);
791                 // init caches
792                 $userto->viewfullnames = array();
793                 $userto->canpost       = array();
794                 $userto->markposts     = array();
796                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
798                 $headerdata = new stdClass();
799                 $headerdata->sitename = format_string($site->fullname, true);
800                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
802                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
803                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
805                 $posthtml = "<head>";
806 /*                foreach ($CFG->stylesheets as $stylesheet) {
807                     //TODO: MDL-21120
808                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
809                 }*/
810                 $posthtml .= "</head>\n<body id=\"email\">\n";
811                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
813                 foreach ($thesediscussions as $discussionid) {
815                     @set_time_limit(120);   // to be reset for each post
817                     $discussion = $discussions[$discussionid];
818                     $forum      = $forums[$discussion->forum];
819                     $course     = $courses[$forum->course];
820                     $cm         = $coursemodules[$forum->id];
822                     //override language
823                     cron_setup_user($userto, $course);
825                     // Fill caches
826                     if (!isset($userto->viewfullnames[$forum->id])) {
827                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
828                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
829                     }
830                     if (!isset($userto->canpost[$discussion->id])) {
831                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
832                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
833                     }
835                     $strforums      = get_string('forums', 'forum');
836                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
837                     $canreply       = $userto->canpost[$discussion->id];
838                     $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
840                     $posttext .= "\n \n";
841                     $posttext .= '=====================================================================';
842                     $posttext .= "\n \n";
843                     $posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
844                     if ($discussion->name != $forum->name) {
845                         $posttext  .= " -> ".format_string($discussion->name,true);
846                     }
847                     $posttext .= "\n";
849                     $posthtml .= "<p><font face=\"sans-serif\">".
850                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
851                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
852                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
853                     if ($discussion->name == $forum->name) {
854                         $posthtml .= "</font></p>";
855                     } else {
856                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
857                     }
858                     $posthtml .= '<p>';
860                     $postsarray = $discussionposts[$discussionid];
861                     sort($postsarray);
863                     foreach ($postsarray as $postid) {
864                         $post = $posts[$postid];
866                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
867                             $userfrom = $users[$post->userid];
868                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
869                             $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
870                         } else {
871                             mtrace('Could not find user '.$post->userid);
872                             continue;
873                         }
875                         if (!isset($userfrom->groups[$forum->id])) {
876                             if (!isset($userfrom->groups)) {
877                                 $userfrom->groups = array();
878                                 $users[$userfrom->id]->groups = array();
879                             }
880                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
881                             $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
882                         }
884                         $userfrom->customheaders = array ("Precedence: Bulk");
886                         if ($userto->maildigest == 2) {
887                             // Subjects only
888                             $by = new stdClass();
889                             $by->name = fullname($userfrom);
890                             $by->date = userdate($post->modified);
891                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
892                             $posttext .= "\n---------------------------------------------------------------------";
894                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
895                             $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>';
897                         } else {
898                             // The full treatment
899                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
900                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
902                         // Create an array of postid's for this user to mark as read.
903                             if (!$CFG->forum_usermarksread) {
904                                 $userto->markposts[$post->id] = $post->id;
905                             }
906                         }
907                     }
908                     if ($canunsubscribe) {
909                         $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>";
910                     } else {
911                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
912                     }
913                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
914                 }
915                 $posthtml .= '</body>';
917                 if (empty($userto->mailformat) || $userto->mailformat != 1) {
918                     // This user DOESN'T want to receive HTML
919                     $posthtml = '';
920                 }
922                 $attachment = $attachname='';
923                 $usetrueaddress = true;
924                 //directly email forum digests rather than sending them via messaging
925                 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname, $usetrueaddress, $CFG->forum_replytouser);
927                 if (!$mailresult) {
928                     mtrace("ERROR!");
929                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
930                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
931                 } else {
932                     mtrace("success.");
933                     $usermailcount++;
935                     // Mark post as read if forum_usermarksread is set off
936                     forum_tp_mark_posts_read($userto, $userto->markposts);
937                 }
938             }
939         }
940     /// We have finishied all digest emails, update $CFG->digestmailtimelast
941         set_config('digestmailtimelast', $timenow);
942     }
944     cron_setup_user();
946     if (!empty($usermailcount)) {
947         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
948     }
950     if (!empty($CFG->forum_lastreadclean)) {
951         $timenow = time();
952         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
953             set_config('forum_lastreadclean', $timenow);
954             mtrace('Removing old forum read tracking info...');
955             forum_tp_clean_read_records();
956         }
957     } else {
958         set_config('forum_lastreadclean', time());
959     }
962     return true;
965 /**
966  * Builds and returns the body of the email notification in plain text.
967  *
968  * @global object
969  * @global object
970  * @uses CONTEXT_MODULE
971  * @param object $course
972  * @param object $cm
973  * @param object $forum
974  * @param object $discussion
975  * @param object $post
976  * @param object $userfrom
977  * @param object $userto
978  * @param boolean $bare
979  * @return string The email body in plain text format.
980  */
981 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
982     global $CFG, $USER;
984     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
986     if (!isset($userto->viewfullnames[$forum->id])) {
987         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
988     } else {
989         $viewfullnames = $userto->viewfullnames[$forum->id];
990     }
992     if (!isset($userto->canpost[$discussion->id])) {
993         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
994     } else {
995         $canreply = $userto->canpost[$discussion->id];
996     }
998     $by = New stdClass;
999     $by->name = fullname($userfrom, $viewfullnames);
1000     $by->date = userdate($post->modified, "", $userto->timezone);
1002     $strbynameondate = get_string('bynameondate', 'forum', $by);
1004     $strforums = get_string('forums', 'forum');
1006     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1008     $posttext = '';
1010     if (!$bare) {
1011         $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
1012         $posttext  = "$shortname -> $strforums -> ".format_string($forum->name,true);
1014         if ($discussion->name != $forum->name) {
1015             $posttext  .= " -> ".format_string($discussion->name,true);
1016         }
1017     }
1019     // add absolute file links
1020     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
1022     $posttext .= "\n---------------------------------------------------------------------\n";
1023     $posttext .= format_string($post->subject,true);
1024     if ($bare) {
1025         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1026     }
1027     $posttext .= "\n".$strbynameondate."\n";
1028     $posttext .= "---------------------------------------------------------------------\n";
1029     $posttext .= format_text_email($post->message, $post->messageformat);
1030     $posttext .= "\n\n";
1031     $posttext .= forum_print_attachments($post, $cm, "text");
1033     if (!$bare && $canreply) {
1034         $posttext .= "---------------------------------------------------------------------\n";
1035         $posttext .= get_string("postmailinfo", "forum", $shortname)."\n";
1036         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1037     }
1038     if (!$bare && $canunsubscribe) {
1039         $posttext .= "\n---------------------------------------------------------------------\n";
1040         $posttext .= get_string("unsubscribe", "forum");
1041         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1042     }
1044     return $posttext;
1047 /**
1048  * Builds and returns the body of the email notification in html format.
1049  *
1050  * @global object
1051  * @param object $course
1052  * @param object $cm
1053  * @param object $forum
1054  * @param object $discussion
1055  * @param object $post
1056  * @param object $userfrom
1057  * @param object $userto
1058  * @return string The email text in HTML format
1059  */
1060 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1061     global $CFG;
1063     if ($userto->mailformat != 1) {  // Needs to be HTML
1064         return '';
1065     }
1067     if (!isset($userto->canpost[$discussion->id])) {
1068         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course);
1069     } else {
1070         $canreply = $userto->canpost[$discussion->id];
1071     }
1073     $strforums = get_string('forums', 'forum');
1074     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1075     $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
1077     $posthtml = '<head>';
1078 /*    foreach ($CFG->stylesheets as $stylesheet) {
1079         //TODO: MDL-21120
1080         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1081     }*/
1082     $posthtml .= '</head>';
1083     $posthtml .= "\n<body id=\"email\">\n\n";
1085     $posthtml .= '<div class="navbar">'.
1086     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$shortname.'</a> &raquo; '.
1087     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1088     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1089     if ($discussion->name == $forum->name) {
1090         $posthtml .= '</div>';
1091     } else {
1092         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1093                      format_string($discussion->name,true).'</a></div>';
1094     }
1095     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1097     if ($canunsubscribe) {
1098         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1099                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1100                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1101     }
1103     $posthtml .= '</body>';
1105     return $posthtml;
1109 /**
1110  *
1111  * @param object $course
1112  * @param object $user
1113  * @param object $mod TODO this is not used in this function, refactor
1114  * @param object $forum
1115  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1116  */
1117 function forum_user_outline($course, $user, $mod, $forum) {
1118     global $CFG;
1119     require_once("$CFG->libdir/gradelib.php");
1120     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1121     if (empty($grades->items[0]->grades)) {
1122         $grade = false;
1123     } else {
1124         $grade = reset($grades->items[0]->grades);
1125     }
1127     $count = forum_count_user_posts($forum->id, $user->id);
1129     if ($count && $count->postcount > 0) {
1130         $result = new stdClass();
1131         $result->info = get_string("numposts", "forum", $count->postcount);
1132         $result->time = $count->lastpost;
1133         if ($grade) {
1134             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1135         }
1136         return $result;
1137     } else if ($grade) {
1138         $result = new stdClass();
1139         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1141         //datesubmitted == time created. dategraded == time modified or time overridden
1142         //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
1143         //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
1144         if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1145             $result->time = $grade->dategraded;
1146         } else {
1147             $result->time = $grade->datesubmitted;
1148         }
1150         return $result;
1151     }
1152     return NULL;
1156 /**
1157  * @global object
1158  * @global object
1159  * @param object $coure
1160  * @param object $user
1161  * @param object $mod
1162  * @param object $forum
1163  */
1164 function forum_user_complete($course, $user, $mod, $forum) {
1165     global $CFG,$USER, $OUTPUT;
1166     require_once("$CFG->libdir/gradelib.php");
1168     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1169     if (!empty($grades->items[0]->grades)) {
1170         $grade = reset($grades->items[0]->grades);
1171         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1172         if ($grade->str_feedback) {
1173             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1174         }
1175     }
1177     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1179         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1180             print_error('invalidcoursemodule');
1181         }
1182         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1184         foreach ($posts as $post) {
1185             if (!isset($discussions[$post->discussion])) {
1186                 continue;
1187             }
1188             $discussion = $discussions[$post->discussion];
1190             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1191         }
1192     } else {
1193         echo "<p>".get_string("noposts", "forum")."</p>";
1194     }
1202 /**
1203  * @global object
1204  * @global object
1205  * @global object
1206  * @param array $courses
1207  * @param array $htmlarray
1208  */
1209 function forum_print_overview($courses,&$htmlarray) {
1210     global $USER, $CFG, $DB, $SESSION;
1212     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"><span class="postsincelogin">';
1310             $str .= $count.' '.$strnumpostssince."</span>";
1311             if (!empty($showunread)) {
1312                 $str .= '<div class="unreadposts">'.$thisunread .' '.$strnumunread.'</div>';
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;
1457     require_once($CFG->dirroot.'/rating/lib.php');
1459     $ratingoptions = new stdClass;
1460     $ratingoptions->component = 'mod_forum';
1461     $ratingoptions->ratingarea = 'post';
1463     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1464     $ratingoptions->modulename = 'forum';
1465     $ratingoptions->moduleid   = $forum->id;
1466     $ratingoptions->userid = $userid;
1467     $ratingoptions->aggregationmethod = $forum->assessed;
1468     $ratingoptions->scaleid = $forum->scale;
1469     $ratingoptions->itemtable = 'forum_posts';
1470     $ratingoptions->itemtableusercolumn = 'userid';
1472     $rm = new rating_manager();
1473     return $rm->get_user_grades($ratingoptions);
1476 /**
1477  * Update activity grades
1478  *
1479  * @global object
1480  * @global object
1481  * @param object $forum
1482  * @param int $userid specific user only, 0 means all
1483  * @param boolean $nullifnone return null if grade does not exist
1484  * @return void
1485  */
1486 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1487     global $CFG, $DB;
1488     require_once($CFG->libdir.'/gradelib.php');
1490     if (!$forum->assessed) {
1491         forum_grade_item_update($forum);
1493     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1494         forum_grade_item_update($forum, $grades);
1496     } else if ($userid and $nullifnone) {
1497         $grade = new stdClass();
1498         $grade->userid   = $userid;
1499         $grade->rawgrade = NULL;
1500         forum_grade_item_update($forum, $grade);
1502     } else {
1503         forum_grade_item_update($forum);
1504     }
1507 /**
1508  * Update all grades in gradebook.
1509  * @global object
1510  */
1511 function forum_upgrade_grades() {
1512     global $DB;
1514     $sql = "SELECT COUNT('x')
1515               FROM {forum} f, {course_modules} cm, {modules} m
1516              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1517     $count = $DB->count_records_sql($sql);
1519     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1520               FROM {forum} f, {course_modules} cm, {modules} m
1521              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1522     $rs = $DB->get_recordset_sql($sql);
1523     if ($rs->valid()) {
1524         $pbar = new progress_bar('forumupgradegrades', 500, true);
1525         $i=0;
1526         foreach ($rs as $forum) {
1527             $i++;
1528             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1529             forum_update_grades($forum, 0, false);
1530             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1531         }
1532     }
1533     $rs->close();
1536 /**
1537  * Create/update grade item for given forum
1538  *
1539  * @global object
1540  * @uses GRADE_TYPE_NONE
1541  * @uses GRADE_TYPE_VALUE
1542  * @uses GRADE_TYPE_SCALE
1543  * @param object $forum object with extra cmidnumber
1544  * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
1545  * @return int 0 if ok
1546  */
1547 function forum_grade_item_update($forum, $grades=NULL) {
1548     global $CFG;
1549     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1550         require_once($CFG->libdir.'/gradelib.php');
1551     }
1553     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1555     if (!$forum->assessed or $forum->scale == 0) {
1556         $params['gradetype'] = GRADE_TYPE_NONE;
1558     } else if ($forum->scale > 0) {
1559         $params['gradetype'] = GRADE_TYPE_VALUE;
1560         $params['grademax']  = $forum->scale;
1561         $params['grademin']  = 0;
1563     } else if ($forum->scale < 0) {
1564         $params['gradetype'] = GRADE_TYPE_SCALE;
1565         $params['scaleid']   = -$forum->scale;
1566     }
1568     if ($grades  === 'reset') {
1569         $params['reset'] = true;
1570         $grades = NULL;
1571     }
1573     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1576 /**
1577  * Delete grade item for given forum
1578  *
1579  * @global object
1580  * @param object $forum object
1581  * @return object grade_item
1582  */
1583 function forum_grade_item_delete($forum) {
1584     global $CFG;
1585     require_once($CFG->libdir.'/gradelib.php');
1587     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1591 /**
1592  * Returns the users with data in one forum
1593  * (users with records in forum_subscriptions, forum_posts, students)
1594  *
1595  * @todo: deprecated - to be deleted in 2.2
1596  *
1597  * @param int $forumid
1598  * @return mixed array or false if none
1599  */
1600 function forum_get_participants($forumid) {
1602     global $CFG, $DB;
1604     $params = array('forumid' => $forumid);
1606     //Get students from forum_subscriptions
1607     $sql = "SELECT DISTINCT u.id, u.id
1608               FROM {user} u,
1609                    {forum_subscriptions} s
1610              WHERE s.forum = :forumid AND
1611                    u.id = s.userid";
1612     $st_subscriptions = $DB->get_records_sql($sql, $params);
1614     //Get students from forum_posts
1615     $sql = "SELECT DISTINCT u.id, u.id
1616               FROM {user} u,
1617                    {forum_discussions} d,
1618                    {forum_posts} p
1619               WHERE d.forum = :forumid AND
1620                     p.discussion = d.id AND
1621                     u.id = p.userid";
1622     $st_posts = $DB->get_records_sql($sql, $params);
1624     //Get students from the ratings table
1625     $sql = "SELECT DISTINCT r.userid, r.userid AS id
1626               FROM {forum_discussions} d
1627               JOIN {forum_posts} p ON p.discussion = d.id
1628               JOIN {rating} r on r.itemid = p.id
1629              WHERE d.forum = :forumid AND
1630                    r.component = 'mod_forum' AND
1631                    r.ratingarea = 'post'";
1632     $st_ratings = $DB->get_records_sql($sql, $params);
1634     //Add st_posts to st_subscriptions
1635     if ($st_posts) {
1636         foreach ($st_posts as $st_post) {
1637             $st_subscriptions[$st_post->id] = $st_post;
1638         }
1639     }
1640     //Add st_ratings to st_subscriptions
1641     if ($st_ratings) {
1642         foreach ($st_ratings as $st_rating) {
1643             $st_subscriptions[$st_rating->id] = $st_rating;
1644         }
1645     }
1646     //Return st_subscriptions array (it contains an array of unique users)
1647     return ($st_subscriptions);
1650 /**
1651  * This function returns if a scale is being used by one forum
1652  *
1653  * @global object
1654  * @param int $forumid
1655  * @param int $scaleid negative number
1656  * @return bool
1657  */
1658 function forum_scale_used ($forumid,$scaleid) {
1659     global $DB;
1660     $return = false;
1662     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1664     if (!empty($rec) && !empty($scaleid)) {
1665         $return = true;
1666     }
1668     return $return;
1671 /**
1672  * Checks if scale is being used by any instance of forum
1673  *
1674  * This is used to find out if scale used anywhere
1675  *
1676  * @global object
1677  * @param $scaleid int
1678  * @return boolean True if the scale is used by any forum
1679  */
1680 function forum_scale_used_anywhere($scaleid) {
1681     global $DB;
1682     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1683         return true;
1684     } else {
1685         return false;
1686     }
1689 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1691 /**
1692  * Gets a post with all info ready for forum_print_post
1693  * Most of these joins are just to get the forum id
1694  *
1695  * @global object
1696  * @global object
1697  * @param int $postid
1698  * @return mixed array of posts or false
1699  */
1700 function forum_get_post_full($postid) {
1701     global $CFG, $DB;
1703     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1704                              FROM {forum_posts} p
1705                                   JOIN {forum_discussions} d ON p.discussion = d.id
1706                                   LEFT JOIN {user} u ON p.userid = u.id
1707                             WHERE p.id = ?", array($postid));
1710 /**
1711  * Gets posts with all info ready for forum_print_post
1712  * We pass forumid in because we always know it so no need to make a
1713  * complicated join to find it out.
1714  *
1715  * @global object
1716  * @global object
1717  * @return mixed array of posts or false
1718  */
1719 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1720     global $CFG, $DB;
1722     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1723                               FROM {forum_posts} p
1724                          LEFT JOIN {user} u ON p.userid = u.id
1725                              WHERE p.discussion = ?
1726                                AND p.parent > 0 $sort", array($discussion));
1729 /**
1730  * Gets all posts in discussion including top parent.
1731  *
1732  * @global object
1733  * @global object
1734  * @global object
1735  * @param int $discussionid
1736  * @param string $sort
1737  * @param bool $tracking does user track the forum?
1738  * @return array of posts
1739  */
1740 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1741     global $CFG, $DB, $USER;
1743     $tr_sel  = "";
1744     $tr_join = "";
1745     $params = array();
1747     if ($tracking) {
1748         $now = time();
1749         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1750         $tr_sel  = ", fr.id AS postread";
1751         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1752         $params[] = $USER->id;
1753     }
1755     $params[] = $discussionid;
1756     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1757                                      FROM {forum_posts} p
1758                                           LEFT JOIN {user} u ON p.userid = u.id
1759                                           $tr_join
1760                                     WHERE p.discussion = ?
1761                                  ORDER BY $sort", $params)) {
1762         return array();
1763     }
1765     foreach ($posts as $pid=>$p) {
1766         if ($tracking) {
1767             if (forum_tp_is_post_old($p)) {
1768                  $posts[$pid]->postread = true;
1769             }
1770         }
1771         if (!$p->parent) {
1772             continue;
1773         }
1774         if (!isset($posts[$p->parent])) {
1775             continue; // parent does not exist??
1776         }
1777         if (!isset($posts[$p->parent]->children)) {
1778             $posts[$p->parent]->children = array();
1779         }
1780         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1781     }
1783     return $posts;
1786 /**
1787  * Gets posts with all info ready for forum_print_post
1788  * We pass forumid in because we always know it so no need to make a
1789  * complicated join to find it out.
1790  *
1791  * @global object
1792  * @global object
1793  * @param int $parent
1794  * @param int $forumid
1795  * @return array
1796  */
1797 function forum_get_child_posts($parent, $forumid) {
1798     global $CFG, $DB;
1800     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1801                               FROM {forum_posts} p
1802                          LEFT JOIN {user} u ON p.userid = u.id
1803                              WHERE p.parent = ?
1804                           ORDER BY p.created ASC", array($parent));
1807 /**
1808  * An array of forum objects that the user is allowed to read/search through.
1809  *
1810  * @global object
1811  * @global object
1812  * @global object
1813  * @param int $userid
1814  * @param int $courseid if 0, we look for forums throughout the whole site.
1815  * @return array of forum objects, or false if no matches
1816  *         Forum objects have the following attributes:
1817  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1818  *         viewhiddentimedposts
1819  */
1820 function forum_get_readable_forums($userid, $courseid=0) {
1822     global $CFG, $DB, $USER;
1823     require_once($CFG->dirroot.'/course/lib.php');
1825     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1826         print_error('notinstalled', 'forum');
1827     }
1829     if ($courseid) {
1830         $courses = $DB->get_records('course', array('id' => $courseid));
1831     } else {
1832         // If no course is specified, then the user can see SITE + his courses.
1833         $courses1 = $DB->get_records('course', array('id' => SITEID));
1834         $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
1835         $courses = array_merge($courses1, $courses2);
1836     }
1837     if (!$courses) {
1838         return array();
1839     }
1841     $readableforums = array();
1843     foreach ($courses as $course) {
1845         $modinfo =& get_fast_modinfo($course);
1846         if (is_null($modinfo->groups)) {
1847             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1848         }
1850         if (empty($modinfo->instances['forum'])) {
1851             // hmm, no forums?
1852             continue;
1853         }
1855         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1857         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1858             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1859                 continue;
1860             }
1861             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1862             $forum = $courseforums[$forumid];
1863             $forum->context = $context;
1864             $forum->cm = $cm;
1866             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1867                 continue;
1868             }
1870          /// group access
1871             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1872                 if (is_null($modinfo->groups)) {
1873                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1874                 }
1875                 if (isset($modinfo->groups[$cm->groupingid])) {
1876                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1877                     $forum->onlygroups[] = -1;
1878                 } else {
1879                     $forum->onlygroups = array(-1);
1880                 }
1881             }
1883         /// hidden timed discussions
1884             $forum->viewhiddentimedposts = true;
1885             if (!empty($CFG->forum_enabletimedposts)) {
1886                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1887                     $forum->viewhiddentimedposts = false;
1888                 }
1889             }
1891         /// qanda access
1892             if ($forum->type == 'qanda'
1893                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1895                 // We need to check whether the user has posted in the qanda forum.
1896                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1897                                                     // the user is allowed to see in this forum.
1898                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1899                     foreach ($discussionspostedin as $d) {
1900                         $forum->onlydiscussions[] = $d->id;
1901                     }
1902                 }
1903             }
1905             $readableforums[$forum->id] = $forum;
1906         }
1908         unset($modinfo);
1910     } // End foreach $courses
1912     return $readableforums;
1915 /**
1916  * Returns a list of posts found using an array of search terms.
1917  *
1918  * @global object
1919  * @global object
1920  * @global object
1921  * @param array $searchterms array of search terms, e.g. word +word -word
1922  * @param int $courseid if 0, we search through the whole site
1923  * @param int $limitfrom
1924  * @param int $limitnum
1925  * @param int &$totalcount
1926  * @param string $extrasql
1927  * @return array|bool Array of posts found or false
1928  */
1929 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1930                             &$totalcount, $extrasql='') {
1931     global $CFG, $DB, $USER;
1932     require_once($CFG->libdir.'/searchlib.php');
1934     $forums = forum_get_readable_forums($USER->id, $courseid);
1936     if (count($forums) == 0) {
1937         $totalcount = 0;
1938         return false;
1939     }
1941     $now = round(time(), -2); // db friendly
1943     $fullaccess = array();
1944     $where = array();
1945     $params = array();
1947     foreach ($forums as $forumid => $forum) {
1948         $select = array();
1950         if (!$forum->viewhiddentimedposts) {
1951             $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
1952             $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
1953         }
1955         $cm = $forum->cm;
1956         $context = $forum->context;
1958         if ($forum->type == 'qanda'
1959             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1960             if (!empty($forum->onlydiscussions)) {
1961                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
1962                 $params = array_merge($params, $discussionid_params);
1963                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
1964             } else {
1965                 $select[] = "p.parent = 0";
1966             }
1967         }
1969         if (!empty($forum->onlygroups)) {
1970             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
1971             $params = array_merge($params, $groupid_params);
1972             $select[] = "d.groupid $groupid_sql";
1973         }
1975         if ($select) {
1976             $selects = implode(" AND ", $select);
1977             $where[] = "(d.forum = :forum{$forumid} AND $selects)";
1978             $params['forum'.$forumid] = $forumid;
1979         } else {
1980             $fullaccess[] = $forumid;
1981         }
1982     }
1984     if ($fullaccess) {
1985         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
1986         $params = array_merge($params, $fullid_params);
1987         $where[] = "(d.forum $fullid_sql)";
1988     }
1990     $selectdiscussion = "(".implode(" OR ", $where).")";
1992     $messagesearch = '';
1993     $searchstring = '';
1995     // Need to concat these back together for parser to work.
1996     foreach($searchterms as $searchterm){
1997         if ($searchstring != '') {
1998             $searchstring .= ' ';
1999         }
2000         $searchstring .= $searchterm;
2001     }
2003     // We need to allow quoted strings for the search. The quotes *should* be stripped
2004     // by the parser, but this should be examined carefully for security implications.
2005     $searchstring = str_replace("\\\"","\"",$searchstring);
2006     $parser = new search_parser();
2007     $lexer = new search_lexer($parser);
2009     if ($lexer->parse($searchstring)) {
2010         $parsearray = $parser->get_parsed_array();
2011     // Experimental feature under 1.8! MDL-8830
2012     // Use alternative text searches if defined
2013     // This feature only works under mysql until properly implemented for other DBs
2014     // Requires manual creation of text index for forum_posts before enabling it:
2015     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2016     // Experimental feature under 1.8! MDL-8830
2017         if (!empty($CFG->forum_usetextsearches)) {
2018             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2019                                                  'p.userid', 'u.id', 'u.firstname',
2020                                                  'u.lastname', 'p.modified', 'd.forum');
2021         } else {
2022             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2023                                                  'p.userid', 'u.id', 'u.firstname',
2024                                                  'u.lastname', 'p.modified', 'd.forum');
2025         }
2026         $params = array_merge($params, $msparams);
2027     }
2029     $fromsql = "{forum_posts} p,
2030                   {forum_discussions} d,
2031                   {user} u";
2033     $selectsql = " $messagesearch
2034                AND p.discussion = d.id
2035                AND p.userid = u.id
2036                AND $selectdiscussion
2037                    $extrasql";
2039     $countsql = "SELECT COUNT(*)
2040                    FROM $fromsql
2041                   WHERE $selectsql";
2043     $searchsql = "SELECT p.*,
2044                          d.forum,
2045                          u.firstname,
2046                          u.lastname,
2047                          u.email,
2048                          u.picture,
2049                          u.imagealt,
2050                          u.email
2051                     FROM $fromsql
2052                    WHERE $selectsql
2053                 ORDER BY p.modified DESC";
2055     $totalcount = $DB->count_records_sql($countsql, $params);
2057     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2060 /**
2061  * Returns a list of ratings for a particular post - sorted.
2062  *
2063  * TODO: Check if this function is actually used anywhere.
2064  * Up until the fix for MDL-27471 this function wasn't even returning.
2065  *
2066  * @param stdClass $context
2067  * @param int $postid
2068  * @param string $sort
2069  * @return array Array of ratings or false
2070  */
2071 function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
2072     $options = new stdClass;
2073     $options->context = $context;
2074     $options->component = 'mod_forum';
2075     $options->ratingarea = 'post';
2076     $options->itemid = $postid;
2077     $options->sort = "ORDER BY $sort";
2079     $rm = new rating_manager();
2080     return $rm->get_all_ratings_for_item($options);
2083 /**
2084  * Returns a list of all new posts that have not been mailed yet
2085  *
2086  * @param int $starttime posts created after this time
2087  * @param int $endtime posts created before this
2088  * @param int $now used for timed discussions only
2089  * @return array
2090  */
2091 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2092     global $CFG, $DB;
2094     $params = array($starttime, $endtime);
2095     if (!empty($CFG->forum_enabletimedposts)) {
2096         if (empty($now)) {
2097             $now = time();
2098         }
2099         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2100         $params[] = $now;
2101         $params[] = $now;
2102     } else {
2103         $timedsql = "";
2104     }
2106     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2107                               FROM {forum_posts} p
2108                                    JOIN {forum_discussions} d ON d.id = p.discussion
2109                              WHERE p.mailed = 0
2110                                    AND p.created >= ?
2111                                    AND (p.created < ? OR p.mailnow = 1)
2112                                    $timedsql
2113                           ORDER BY p.modified ASC", $params);
2116 /**
2117  * Marks posts before a certain time as being mailed already
2118  *
2119  * @global object
2120  * @global object
2121  * @param int $endtime
2122  * @param int $now Defaults to time()
2123  * @return bool
2124  */
2125 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2126     global $CFG, $DB;
2127     if (empty($now)) {
2128         $now = time();
2129     }
2131     if (empty($CFG->forum_enabletimedposts)) {
2132         return $DB->execute("UPDATE {forum_posts}
2133                                SET mailed = '1'
2134                              WHERE (created < ? OR mailnow = 1)
2135                                    AND mailed = 0", array($endtime));
2137     } else {
2138         return $DB->execute("UPDATE {forum_posts}
2139                                SET mailed = '1'
2140                              WHERE discussion NOT IN (SELECT d.id
2141                                                         FROM {forum_discussions} d
2142                                                        WHERE d.timestart > ?)
2143                                    AND (created < ? OR mailnow = 1)
2144                                    AND mailed = 0", array($now, $endtime));
2145     }
2148 /**
2149  * Get all the posts for a user in a forum suitable for forum_print_post
2150  *
2151  * @global object
2152  * @global object
2153  * @uses CONTEXT_MODULE
2154  * @return array
2155  */
2156 function forum_get_user_posts($forumid, $userid) {
2157     global $CFG, $DB;
2159     $timedsql = "";
2160     $params = array($forumid, $userid);
2162     if (!empty($CFG->forum_enabletimedposts)) {
2163         $cm = get_coursemodule_from_instance('forum', $forumid);
2164         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2165             $now = time();
2166             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2167             $params[] = $now;
2168             $params[] = $now;
2169         }
2170     }
2172     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2173                               FROM {forum} f
2174                                    JOIN {forum_discussions} d ON d.forum = f.id
2175                                    JOIN {forum_posts} p       ON p.discussion = d.id
2176                                    JOIN {user} u              ON u.id = p.userid
2177                              WHERE f.id = ?
2178                                    AND p.userid = ?
2179                                    $timedsql
2180                           ORDER BY p.modified ASC", $params);
2183 /**
2184  * Get all the discussions user participated in
2185  *
2186  * @global object
2187  * @global object
2188  * @uses CONTEXT_MODULE
2189  * @param int $forumid
2190  * @param int $userid
2191  * @return array Array or false
2192  */
2193 function forum_get_user_involved_discussions($forumid, $userid) {
2194     global $CFG, $DB;
2196     $timedsql = "";
2197     $params = array($forumid, $userid);
2198     if (!empty($CFG->forum_enabletimedposts)) {
2199         $cm = get_coursemodule_from_instance('forum', $forumid);
2200         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2201             $now = time();
2202             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2203             $params[] = $now;
2204             $params[] = $now;
2205         }
2206     }
2208     return $DB->get_records_sql("SELECT DISTINCT d.*
2209                               FROM {forum} f
2210                                    JOIN {forum_discussions} d ON d.forum = f.id
2211                                    JOIN {forum_posts} p       ON p.discussion = d.id
2212                              WHERE f.id = ?
2213                                    AND p.userid = ?
2214                                    $timedsql", $params);
2217 /**
2218  * Get all the posts for a user in a forum suitable for forum_print_post
2219  *
2220  * @global object
2221  * @global object
2222  * @param int $forumid
2223  * @param int $userid
2224  * @return array of counts or false
2225  */
2226 function forum_count_user_posts($forumid, $userid) {
2227     global $CFG, $DB;
2229     $timedsql = "";
2230     $params = array($forumid, $userid);
2231     if (!empty($CFG->forum_enabletimedposts)) {
2232         $cm = get_coursemodule_from_instance('forum', $forumid);
2233         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2234             $now = time();
2235             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2236             $params[] = $now;
2237             $params[] = $now;
2238         }
2239     }
2241     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2242                              FROM {forum} f
2243                                   JOIN {forum_discussions} d ON d.forum = f.id
2244                                   JOIN {forum_posts} p       ON p.discussion = d.id
2245                                   JOIN {user} u              ON u.id = p.userid
2246                             WHERE f.id = ?
2247                                   AND p.userid = ?
2248                                   $timedsql", $params);
2251 /**
2252  * Given a log entry, return the forum post details for it.
2253  *
2254  * @global object
2255  * @global object
2256  * @param object $log
2257  * @return array|null
2258  */
2259 function forum_get_post_from_log($log) {
2260     global $CFG, $DB;
2262     if ($log->action == "add post") {
2264         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2265                                            u.firstname, u.lastname, u.email, u.picture
2266                                  FROM {forum_discussions} d,
2267                                       {forum_posts} p,
2268                                       {forum} f,
2269                                       {user} u
2270                                 WHERE p.id = ?
2271                                   AND d.id = p.discussion
2272                                   AND p.userid = u.id
2273                                   AND u.deleted <> '1'
2274                                   AND f.id = d.forum", array($log->info));
2277     } else if ($log->action == "add discussion") {
2279         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2280                                            u.firstname, u.lastname, u.email, u.picture
2281                                  FROM {forum_discussions} d,
2282                                       {forum_posts} p,
2283                                       {forum} f,
2284                                       {user} u
2285                                 WHERE d.id = ?
2286                                   AND d.firstpost = p.id
2287                                   AND p.userid = u.id
2288                                   AND u.deleted <> '1'
2289                                   AND f.id = d.forum", array($log->info));
2290     }
2291     return NULL;
2294 /**
2295  * Given a discussion id, return the first post from the discussion
2296  *
2297  * @global object
2298  * @global object
2299  * @param int $dicsussionid
2300  * @return array
2301  */
2302 function forum_get_firstpost_from_discussion($discussionid) {
2303     global $CFG, $DB;
2305     return $DB->get_record_sql("SELECT p.*
2306                              FROM {forum_discussions} d,
2307                                   {forum_posts} p
2308                             WHERE d.id = ?
2309                               AND d.firstpost = p.id ", array($discussionid));
2312 /**
2313  * Returns an array of counts of replies to each discussion
2314  *
2315  * @global object
2316  * @global object
2317  * @param int $forumid
2318  * @param string $forumsort
2319  * @param int $limit
2320  * @param int $page
2321  * @param int $perpage
2322  * @return array
2323  */
2324 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2325     global $CFG, $DB;
2327     if ($limit > 0) {
2328         $limitfrom = 0;
2329         $limitnum  = $limit;
2330     } else if ($page != -1) {
2331         $limitfrom = $page*$perpage;
2332         $limitnum  = $perpage;
2333     } else {
2334         $limitfrom = 0;
2335         $limitnum  = 0;
2336     }
2338     if ($forumsort == "") {
2339         $orderby = "";
2340         $groupby = "";
2342     } else {
2343         $orderby = "ORDER BY $forumsort";
2344         $groupby = ", ".strtolower($forumsort);
2345         $groupby = str_replace('desc', '', $groupby);
2346         $groupby = str_replace('asc', '', $groupby);
2347     }
2349     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2350         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2351                   FROM {forum_posts} p
2352                        JOIN {forum_discussions} d ON p.discussion = d.id
2353                  WHERE p.parent > 0 AND d.forum = ?
2354               GROUP BY p.discussion";
2355         return $DB->get_records_sql($sql, array($forumid));
2357     } else {
2358         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2359                   FROM {forum_posts} p
2360                        JOIN {forum_discussions} d ON p.discussion = d.id
2361                  WHERE d.forum = ?
2362               GROUP BY p.discussion $groupby
2363               $orderby";
2364         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2365     }
2368 /**
2369  * @global object
2370  * @global object
2371  * @global object
2372  * @staticvar array $cache
2373  * @param object $forum
2374  * @param object $cm
2375  * @param object $course
2376  * @return mixed
2377  */
2378 function forum_count_discussions($forum, $cm, $course) {
2379     global $CFG, $DB, $USER;
2381     static $cache = array();
2383     $now = round(time(), -2); // db cache friendliness
2385     $params = array($course->id);
2387     if (!isset($cache[$course->id])) {
2388         if (!empty($CFG->forum_enabletimedposts)) {
2389             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2390             $params[] = $now;
2391             $params[] = $now;
2392         } else {
2393             $timedsql = "";
2394         }
2396         $sql = "SELECT f.id, COUNT(d.id) as dcount
2397                   FROM {forum} f
2398                        JOIN {forum_discussions} d ON d.forum = f.id
2399                  WHERE f.course = ?
2400                        $timedsql
2401               GROUP BY f.id";
2403         if ($counts = $DB->get_records_sql($sql, $params)) {
2404             foreach ($counts as $count) {
2405                 $counts[$count->id] = $count->dcount;
2406             }
2407             $cache[$course->id] = $counts;
2408         } else {
2409             $cache[$course->id] = array();
2410         }
2411     }
2413     if (empty($cache[$course->id][$forum->id])) {
2414         return 0;
2415     }
2417     $groupmode = groups_get_activity_groupmode($cm, $course);
2419     if ($groupmode != SEPARATEGROUPS) {
2420         return $cache[$course->id][$forum->id];
2421     }
2423     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2424         return $cache[$course->id][$forum->id];
2425     }
2427     require_once($CFG->dirroot.'/course/lib.php');
2429     $modinfo =& get_fast_modinfo($course);
2430     if (is_null($modinfo->groups)) {
2431         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2432     }
2434     if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2435         $mygroups = $modinfo->groups[$cm->groupingid];
2436     } else {
2437         $mygroups = false; // Will be set below
2438     }
2440     // add all groups posts
2441     if (empty($mygroups)) {
2442         $mygroups = array(-1=>-1);
2443     } else {
2444         $mygroups[-1] = -1;
2445     }
2447     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2448     $params[] = $forum->id;
2450     if (!empty($CFG->forum_enabletimedposts)) {
2451         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2452         $params[] = $now;
2453         $params[] = $now;
2454     } else {
2455         $timedsql = "";
2456     }
2458     $sql = "SELECT COUNT(d.id)
2459               FROM {forum_discussions} d
2460              WHERE d.groupid $mygroups_sql AND d.forum = ?
2461                    $timedsql";
2463     return $DB->get_field_sql($sql, $params);
2466 /**
2467  * How many posts by other users are unrated by a given user in the given discussion?
2468  *
2469  * TODO: Is this function still used anywhere?
2470  *
2471  * @param int $discussionid
2472  * @param int $userid
2473  * @return mixed
2474  */
2475 function forum_count_unrated_posts($discussionid, $userid) {
2476     global $CFG, $DB;
2478     $sql = "SELECT COUNT(*) as num
2479               FROM {forum_posts}
2480              WHERE parent > 0
2481                AND discussion = :discussionid
2482                AND userid <> :userid";
2483     $params = array('discussionid' => $discussionid, 'userid' => $userid);
2484     $posts = $DB->get_record_sql($sql, $params);
2485     if ($posts) {
2486         $sql = "SELECT count(*) as num
2487                   FROM {forum_posts} p,
2488                        {rating} r
2489                  WHERE p.discussion = :discussionid AND
2490                        p.id = r.itemid AND
2491                        r.userid = userid AND
2492                        r.component = 'mod_forum' AND
2493                        r.ratingarea = 'post'";
2494         $rated = $DB->get_record_sql($sql, $params);
2495         if ($rated) {
2496             if ($posts->num > $rated->num) {
2497                 return $posts->num - $rated->num;
2498             } else {
2499                 return 0;    // Just in case there was a counting error
2500             }
2501         } else {
2502             return $posts->num;
2503         }
2504     } else {
2505         return 0;
2506     }
2509 /**
2510  * Get all discussions in a forum
2511  *
2512  * @global object
2513  * @global object
2514  * @global object
2515  * @uses CONTEXT_MODULE
2516  * @uses VISIBLEGROUPS
2517  * @param object $cm
2518  * @param string $forumsort
2519  * @param bool $fullpost
2520  * @param int $unused
2521  * @param int $limit
2522  * @param bool $userlastmodified
2523  * @param int $page
2524  * @param int $perpage
2525  * @return array
2526  */
2527 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2528     global $CFG, $DB, $USER;
2530     $timelimit = '';
2532     $now = round(time(), -2);
2533     $params = array($cm->instance);
2535     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2537     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2538         return array();
2539     }
2541     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2543         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2544             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2545             $params[] = $now;
2546             $params[] = $now;
2547             if (isloggedin()) {
2548                 $timelimit .= " OR d.userid = ?";
2549                 $params[] = $USER->id;
2550             }
2551             $timelimit .= ")";
2552         }
2553     }
2555     if ($limit > 0) {
2556         $limitfrom = 0;
2557         $limitnum  = $limit;
2558     } else if ($page != -1) {
2559         $limitfrom = $page*$perpage;
2560         $limitnum  = $perpage;
2561     } else {
2562         $limitfrom = 0;
2563         $limitnum  = 0;
2564     }
2566     $groupmode    = groups_get_activity_groupmode($cm);
2567     $currentgroup = groups_get_activity_group($cm);
2569     if ($groupmode) {
2570         if (empty($modcontext)) {
2571             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2572         }
2574         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2575             if ($currentgroup) {
2576                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2577                 $params[] = $currentgroup;
2578             } else {
2579                 $groupselect = "";
2580             }
2582         } else {
2583             //seprate groups without access all
2584             if ($currentgroup) {
2585                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2586                 $params[] = $currentgroup;
2587             } else {
2588                 $groupselect = "AND d.groupid = -1";
2589             }
2590         }
2591     } else {
2592         $groupselect = "";
2593     }
2596     if (empty($forumsort)) {
2597         $forumsort = "d.timemodified DESC";
2598     }
2599     if (empty($fullpost)) {
2600         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2601     } else {
2602         $postdata = "p.*";
2603     }
2605     if (empty($userlastmodified)) {  // We don't need to know this
2606         $umfields = "";
2607         $umtable  = "";
2608     } else {
2609         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2610         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2611     }
2613     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2614                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2615               FROM {forum_discussions} d
2616                    JOIN {forum_posts} p ON p.discussion = d.id
2617                    JOIN {user} u ON p.userid = u.id
2618                    $umtable
2619              WHERE d.forum = ? AND p.parent = 0
2620                    $timelimit $groupselect
2621           ORDER BY $forumsort";
2622     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2625 /**
2626  *
2627  * @global object
2628  * @global object
2629  * @global object
2630  * @uses CONTEXT_MODULE
2631  * @uses VISIBLEGROUPS
2632  * @param object $cm
2633  * @return array
2634  */
2635 function forum_get_discussions_unread($cm) {
2636     global $CFG, $DB, $USER;
2638     $now = round(time(), -2);
2639     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2641     $params = array();
2642     $groupmode    = groups_get_activity_groupmode($cm);
2643     $currentgroup = groups_get_activity_group($cm);
2645     if ($groupmode) {
2646         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2648         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2649             if ($currentgroup) {
2650                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2651                 $params['currentgroup'] = $currentgroup;
2652             } else {
2653                 $groupselect = "";
2654             }
2656         } else {
2657             //separate groups without access all
2658             if ($currentgroup) {
2659                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2660                 $params['currentgroup'] = $currentgroup;
2661             } else {
2662                 $groupselect = "AND d.groupid = -1";
2663             }
2664         }
2665     } else {
2666         $groupselect = "";
2667     }
2669     if (!empty($CFG->forum_enabletimedposts)) {
2670         $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2671         $params['now1'] = $now;
2672         $params['now2'] = $now;
2673     } else {
2674         $timedsql = "";
2675     }
2677     $sql = "SELECT d.id, COUNT(p.id) AS unread
2678               FROM {forum_discussions} d
2679                    JOIN {forum_posts} p     ON p.discussion = d.id
2680                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2681              WHERE d.forum = {$cm->instance}
2682                    AND p.modified >= :cutoffdate AND r.id is NULL
2683                    $groupselect
2684                    $timedsql
2685           GROUP BY d.id";
2686     $params['cutoffdate'] = $cutoffdate;
2688     if ($unreads = $DB->get_records_sql($sql, $params)) {
2689         foreach ($unreads as $unread) {
2690             $unreads[$unread->id] = $unread->unread;
2691         }
2692         return $unreads;
2693     } else {
2694         return array();
2695     }
2698 /**
2699  * @global object
2700  * @global object
2701  * @global object
2702  * @uses CONEXT_MODULE
2703  * @uses VISIBLEGROUPS
2704  * @param object $cm
2705  * @return array
2706  */
2707 function forum_get_discussions_count($cm) {
2708     global $CFG, $DB, $USER;
2710     $now = round(time(), -2);
2711     $params = array($cm->instance);
2712     $groupmode    = groups_get_activity_groupmode($cm);
2713     $currentgroup = groups_get_activity_group($cm);
2715     if ($groupmode) {
2716         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2718         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2719             if ($currentgroup) {
2720                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2721                 $params[] = $currentgroup;
2722             } else {
2723                 $groupselect = "";
2724             }
2726         } else {
2727             //seprate groups without access all
2728             if ($currentgroup) {
2729                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2730                 $params[] = $currentgroup;
2731             } else {
2732                 $groupselect = "AND d.groupid = -1";
2733             }
2734         }
2735     } else {
2736         $groupselect = "";
2737     }
2739     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2741     $timelimit = "";
2743     if (!empty($CFG->forum_enabletimedposts)) {
2745         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2747         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2748             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2749             $params[] = $now;
2750             $params[] = $now;
2751             if (isloggedin()) {
2752                 $timelimit .= " OR d.userid = ?";
2753                 $params[] = $USER->id;
2754             }
2755             $timelimit .= ")";
2756         }
2757     }
2759     $sql = "SELECT COUNT(d.id)
2760               FROM {forum_discussions} d
2761                    JOIN {forum_posts} p ON p.discussion = d.id
2762              WHERE d.forum = ? AND p.parent = 0
2763                    $groupselect $timelimit";
2765     return $DB->get_field_sql($sql, $params);
2769 /**
2770  * Get all discussions started by a particular user in a course (or group)
2771  * This function no longer used ...
2772  *
2773  * @todo Remove this function if no longer used
2774  * @global object
2775  * @global object
2776  * @param int $courseid
2777  * @param int $userid
2778  * @param int $groupid
2779  * @return array
2780  */
2781 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2782     global $CFG, $DB;
2783     $params = array($courseid, $userid);
2784     if ($groupid) {
2785         $groupselect = " AND d.groupid = ? ";
2786         $params[] = $groupid;
2787     } else  {
2788         $groupselect = "";
2789     }
2791     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2792                                    f.type as forumtype, f.name as forumname, f.id as forumid
2793                               FROM {forum_discussions} d,
2794                                    {forum_posts} p,
2795                                    {user} u,
2796                                    {forum} f
2797                              WHERE d.course = ?
2798                                AND p.discussion = d.id
2799                                AND p.parent = 0
2800                                AND p.userid = u.id
2801                                AND u.id = ?
2802                                AND d.forum = f.id $groupselect
2803                           ORDER BY p.created DESC", $params);
2806 /**
2807  * Get the list of potential subscribers to a forum.
2808  *
2809  * @param object $forumcontext the forum context.
2810  * @param integer $groupid the id of a group, or 0 for all groups.
2811  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2812  * @param string $sort sort order. As for get_users_by_capability.
2813  * @return array list of users.
2814  */
2815 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2816     global $DB;
2818     // only active enrolled users or everybody on the frontpage with this capability
2819     list($esql, $params) = get_enrolled_sql($forumcontext, 'mod/forum:initialsubscriptions', $groupid, true);
2821     $sql = "SELECT $fields
2822               FROM {user} u
2823               JOIN ($esql) je ON je.id = u.id";
2824     if ($sort) {
2825         $sql = "$sql ORDER BY $sort";
2826     } else {
2827         $sql = "$sql ORDER BY u.lastname ASC, u.firstname ASC";
2828     }
2830     return $DB->get_records_sql($sql, $params);
2833 /**
2834  * Returns list of user objects that are subscribed to this forum
2835  *
2836  * @global object
2837  * @global object
2838  * @param object $course the course
2839  * @param forum $forum the forum
2840  * @param integer $groupid group id, or 0 for all.
2841  * @param object $context the forum context, to save re-fetching it where possible.
2842  * @param string $fields requested user fields (with "u." table prefix)
2843  * @return array list of users.
2844  */
2845 function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
2846     global $CFG, $DB;
2848     if (empty($fields)) {
2849         $fields ="u.id,
2850                   u.username,
2851                   u.firstname,
2852                   u.lastname,
2853                   u.maildisplay,
2854                   u.mailformat,
2855                   u.maildigest,
2856                   u.imagealt,
2857                   u.email,
2858                   u.city,
2859                   u.country,
2860                   u.lastaccess,
2861                   u.lastlogin,
2862                   u.picture,
2863                   u.timezone,
2864                   u.theme,
2865                   u.lang,
2866                   u.trackforums,
2867                   u.mnethostid";
2868     }
2870     if (empty($context)) {
2871         $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2872         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2873     }
2875     if (forum_is_forcesubscribed($forum)) {
2876         $results = forum_get_potential_subscribers($context, $groupid, $fields, "u.email ASC");
2878     } else {
2879         // only active enrolled users or everybody on the frontpage
2880         list($esql, $params) = get_enrolled_sql($context, '', $groupid, true);
2881         $params['forumid'] = $forum->id;
2882         $results = $DB->get_records_sql("SELECT $fields
2883                                            FROM {user} u
2884                                            JOIN ($esql) je ON je.id = u.id
2885                                            JOIN {forum_subscriptions} s ON s.userid = u.id
2886                                           WHERE s.forum = :forumid
2887                                        ORDER BY u.email ASC", $params);
2888     }
2890     // Guest user should never be subscribed to a forum.
2891     unset($results[$CFG->siteguest]);
2893     return $results;
2898 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2901 /**
2902  * @global object
2903  * @global object
2904  * @param int $courseid
2905  * @param string $type
2906  */
2907 function forum_get_course_forum($courseid, $type) {
2908 // How to set up special 1-per-course forums
2909     global $CFG, $DB, $OUTPUT;
2911     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2912         // There should always only be ONE, but with the right combination of
2913         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2914         foreach ($forums as $forum) {
2915             return $forum;   // ie the first one
2916         }
2917     }
2919     // Doesn't exist, so create one now.
2920     $forum->course = $courseid;
2921     $forum->type = "$type";
2922     switch ($forum->type) {
2923         case "news":
2924             $forum->name  = get_string("namenews", "forum");
2925             $forum->intro = get_string("intronews", "forum");
2926             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2927             $forum->assessed = 0;
2928             if ($courseid == SITEID) {
2929                 $forum->name  = get_string("sitenews");
2930                 $forum->forcesubscribe = 0;
2931             }
2932             break;
2933         case "social":
2934             $forum->name  = get_string("namesocial", "forum");
2935             $forum->intro = get_string("introsocial", "forum");
2936             $forum->assessed = 0;
2937             $forum->forcesubscribe = 0;
2938             break;
2939         case "blog":
2940             $forum->name = get_string('blogforum', 'forum');
2941             $forum->intro = get_string('introblog', 'forum');
2942             $forum->assessed = 0;
2943             $forum->forcesubscribe = 0;
2944             break;
2945         default:
2946             echo $OUTPUT->notification("That forum type doesn't exist!");
2947             return false;
2948             break;
2949     }
2951     $forum->timemodified = time();
2952     $forum->id = $DB->insert_record("forum", $forum);
2954     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2955         echo $OUTPUT->notification("Could not find forum module!!");
2956         return false;
2957     }
2958     $mod = new stdClass();
2959     $mod->course = $courseid;
2960     $mod->module = $module->id;
2961     $mod->instance = $forum->id;
2962     $mod->section = 0;
2963     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
2964         echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
2965         return false;
2966     }
2967     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
2968         echo $OUTPUT->notification("Could not add the new course module to that section");
2969         return false;
2970     }
2971     $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule));
2973     include_once("$CFG->dirroot/course/lib.php");
2974     rebuild_course_cache($courseid);
2976     return $DB->get_record("forum", array("id" => "$forum->id"));
2980 /**
2981  * Given the data about a posting, builds up the HTML to display it and
2982  * returns the HTML in a string.  This is designed for sending via HTML email.
2983  *
2984  * @global object
2985  * @param object $course
2986  * @param object $cm
2987  * @param object $forum
2988  * @param object $discussion
2989  * @param object $post
2990  * @param object $userform
2991  * @param object $userto
2992  * @param bool $ownpost
2993  * @param bool $reply
2994  * @param bool $link
2995  * @param bool $rate
2996  * @param string $footer
2997  * @return string
2998  */
2999 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
3000                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
3002     global $CFG, $OUTPUT;
3004     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3006     if (!isset($userto->viewfullnames[$forum->id])) {
3007         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3008     } else {
3009         $viewfullnames = $userto->viewfullnames[$forum->id];
3010     }
3012     // add absolute file links
3013     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3015     // format the post body
3016     $options = new stdClass();
3017     $options->para = true;
3018     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3020     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3022     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3023     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3024     $output .= '</td>';
3026     if ($post->parent) {
3027         $output .= '<td class="topic">';
3028     } else {
3029         $output .= '<td class="topic starter">';
3030     }
3031     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3033     $fullname = fullname($userfrom, $viewfullnames);
3034     $by = new stdClass();
3035     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3036     $by->date = userdate($post->modified, '', $userto->timezone);
3037     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3039     $output .= '</td></tr>';
3041     $output .= '<tr><td class="left side" valign="top">';
3043     if (isset($userfrom->groups)) {
3044         $groups = $userfrom->groups[$forum->id];
3045     } else {
3046         $groups = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3047     }
3049     if ($groups) {
3050         $output .= print_group_picture($groups, $course->id, false, true, true);
3051     } else {
3052         $output .= '&nbsp;';
3053     }
3055     $output .= '</td><td class="content">';
3057     $attachments = forum_print_attachments($post, $cm, 'html');
3058     if ($attachments !== '') {
3059         $output .= '<div class="attachments">';
3060         $output .= $attachments;
3061         $output .= '</div>';
3062     }
3064     $output .= $formattedtext;
3066 // Commands
3067     $commands = array();
3069     if ($post->parent) {
3070         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3071                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3072     }
3074     if ($reply) {
3075         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3076                       get_string('reply', 'forum').'</a>';
3077     }
3079     $output .= '<div class="commands">';
3080     $output .= implode(' | ', $commands);
3081     $output .= '</div>';
3083 // Context link to post if required
3084     if ($link) {
3085         $output .= '<div class="link">';
3086         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3087                      get_string('postincontext', 'forum').'</a>';
3088         $output .= '</div>';
3089     }
3091     if ($footer) {
3092         $output .= '<div class="footer">'.$footer.'</div>';
3093     }
3094     $output .= '</td></tr></table>'."\n\n";
3096     return $output;
3099 /**
3100  * Print a forum post
3101  *
3102  * @global object
3103  * @global object
3104  * @uses FORUM_MODE_THREADED
3105  * @uses PORTFOLIO_FORMAT_PLAINHTML
3106  * @uses PORTFOLIO_FORMAT_FILE
3107  * @uses PORTFOLIO_FORMAT_RICHHTML
3108  * @uses PORTFOLIO_ADD_TEXT_LINK
3109  * @uses CONTEXT_MODULE
3110  * @param object $post The post to print.
3111  * @param object $discussion
3112  * @param object $forum
3113  * @param object $cm
3114  * @param object $course
3115  * @param boolean $ownpost Whether this post belongs to the current user.
3116  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3117  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3118  * @param string $footer Extra stuff to print after the message.
3119  * @param string $highlight Space-separated list of terms to highlight.
3120  * @param int $post_read true, false or -99. If we already know whether this user
3121  *          has read this post, pass that in, otherwise, pass in -99, and this
3122  *          function will work it out.
3123  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3124  *          the current user can't see this post, if this argument is true
3125  *          (the default) then print a dummy 'you can't see this post' post.
3126  *          If false, don't output anything at all.
3127  * @param bool|null $istracked
3128  * @return void
3129  */
3130 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3131                           $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3132     global $USER, $CFG, $OUTPUT;
3134     require_once($CFG->libdir . '/filelib.php');
3136     // String cache
3137     static $str;
3139     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3141     $post->course = $course->id;
3142     $post->forum  = $forum->id;
3143     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3145     // caching
3146     if (!isset($cm->cache)) {
3147         $cm->cache = new stdClass;
3148     }
3150     if (!isset($cm->cache->caps)) {
3151         $cm->cache->caps = array();
3152         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3153         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3154         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3155         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3156         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3157         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3158         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3159         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3160         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3161     }
3163     if (!isset($cm->uservisible)) {
3164         $cm->uservisible = coursemodule_visible_for_user($cm);
3165     }
3167     if ($istracked && is_null($postisread)) {
3168         $postisread = forum_tp_is_post_read($USER->id, $post);
3169     }
3171     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3172         $output = '';
3173         if (!$dummyifcantsee) {
3174             if ($return) {
3175                 return $output;
3176             }
3177             echo $output;
3178             return;
3179         }
3180         $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3181         $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'));
3182         $output .= html_writer::start_tag('div', array('class'=>'row header'));
3183         $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3184         if ($post->parent) {
3185             $output .= html_writer::start_tag('div', array('class'=>'topic'));
3186         } else {
3187             $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3188         }
3189         $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class'=>'subject')); // Subject
3190         $output .= html_writer::tag('div', get_string('forumauthorhidden','forum'), array('class'=>'author')); // author
3191         $output .= html_writer::end_tag('div');
3192         $output .= html_writer::end_tag('div'); // row
3193         $output .= html_writer::start_tag('div', array('class'=>'row'));
3194         $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3195         $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3196         $output .= html_writer::end_tag('div'); // row
3197         $output .= html_writer::end_tag('div'); // forumpost
3199         if ($return) {
3200             return $output;
3201         }
3202         echo $output;
3203         return;
3204     }
3206     if (empty($str)) {
3207         $str = new stdClass;
3208         $str->edit         = get_string('edit', 'forum');
3209         $str->delete       = get_string('delete', 'forum');
3210         $str->reply        = get_string('reply', 'forum');
3211         $str->parent       = get_string('parent', 'forum');
3212         $str->pruneheading = get_string('pruneheading', 'forum');
3213         $str->prune        = get_string('prune', 'forum');
3214         $str->displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3215         $str->markread     = get_string('markread', 'forum');
3216         $str->markunread   = get_string('markunread', 'forum');
3217     }
3219     $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3221     // Build an object that represents the posting user
3222     $postuser = new stdClass;
3223     $postuser->id        = $post->userid;
3224     $postuser->firstname = $post->firstname;
3225     $postuser->lastname  = $post->lastname;
3226     $postuser->imagealt  = $post->imagealt;
3227     $postuser->picture   = $post->picture;
3228     $postuser->email     = $post->email;
3229     // Some handy things for later on
3230     $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3231     $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3233     // Prepare the groups the posting user belongs to
3234     if (isset($cm->cache->usersgroups)) {
3235         $groups = array();
3236         if (isset($cm->cache->usersgroups[$post->userid])) {
3237             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3238                 $groups[$gid] = $cm->cache->groups[$gid];
3239             }
3240         }
3241     } else {
3242         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3243     }
3245     // Prepare the attachements for the post, files then images
3246     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3248     // Determine if we need to shorten this post
3249     $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3252     // Prepare an array of commands
3253     $commands = array();
3255     // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3256     // Don't display the mark read / unread controls in this case.
3257     if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3258         $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3259         $text = $str->markunread;
3260         if (!$postisread) {
3261             $url->param('mark', 'read');
3262             $text = $str->markread;
3263         }
3264         if ($str->displaymode == FORUM_MODE_THREADED) {
3265             $url->param('parent', $post->parent);
3266         } else {
3267             $url->set_anchor('p'.$post->id);
3268         }
3269         $commands[] = array('url'=>$url, 'text'=>$text);
3270     }
3272     // Zoom in to the parent specifically
3273     if ($post->parent) {
3274         $url = new moodle_url($discussionlink);
3275         if ($str->displaymode == FORUM_MODE_THREADED) {
3276             $url->param('parent', $post->parent);
3277         } else {
3278             $url->set_anchor('p'.$post->parent);
3279         }
3280         $commands[] = array('url'=>$url, 'text'=>$str->parent);
3281     }
3283     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3284     $age = time() - $post->created;
3285     if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3286         $age = 0;
3287     }
3288     if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3289         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3290     }
3292     if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3293         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3294     }
3296     if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3297         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3298     }
3300     if ($reply) {
3301         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('reply'=>$post->id)), 'text'=>$str->reply);
3302     }
3304     if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
3305         $p = array('postid' => $post->id);
3306         require_once($CFG->libdir.'/portfoliolib.php');
3307         $button = new portfolio_add_button();
3308         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php');
3309         if (empty($attachments)) {
3310             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3311         } else {
3312             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3313         }
3315         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3316         if (!empty($porfoliohtml)) {
3317             $commands[] = $porfoliohtml;
3318         }
3319     }
3320     // Finished building commands
3323     // Begin output
3325     $output  = '';
3327     if ($istracked) {
3328         if ($postisread) {
3329             $forumpostclass = ' read';
3330         } else {
3331             $forumpostclass = ' unread';
3332             $output .= html_writer::tag('a', '', array('name'=>'unread'));
3333         }
3334     } else {
3335         // ignore trackign status if not tracked or tracked param missing
3336         $forumpostclass = '';
3337     }
3339     $topicclass = '';
3340     if (empty($post->parent)) {
3341         $topicclass = ' firstpost starter';
3342     }
3344     $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3345     $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass));
3346     $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3347     $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3348     $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3349     $output .= html_writer::end_tag('div');
3352     $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3354     $postsubject = $post->subject;
3355     if (empty($post->subjectnoformat)) {
3356         $postsubject = format_string($postsubject);
3357     }
3358     $output .= html_writer::tag('div', $postsubject, array('class'=>'subject'));
3360     $by = new stdClass();
3361     $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3362     $by->date = userdate($post->modified);
3363     $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author'));
3365     $output .= html_writer::end_tag('div'); //topic
3366     $output .= html_writer::end_tag('div'); //row
3368     $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3369     $output .= html_writer::start_tag('div', array('class'=>'left'));
3371     $groupoutput = '';
3372     if ($groups) {
3373         $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3374     }
3375     if (empty($groupoutput)) {
3376         $groupoutput = '&nbsp;';
3377     }
3378     $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3380     $output .= html_writer::end_tag('div'); //left side
3381     $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3382     $output .= html_writer::start_tag('div', array('class'=>'content'));
3383     if (!empty($attachments)) {
3384         $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3385     }
3387     $options = new stdClass;
3388     $options->para    = false;
3389     $options->trusted = $post->messagetrust;
3390     $options->context = $modcontext;
3391     if ($shortenpost) {
3392         // Prepare shortened version
3393         $postclass    = 'shortenedpost';
3394         $postcontent  = format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3395         $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
3396         $postcontent .= html_writer::tag('span', '('.get_string('numwords', 'moodle', count_words(strip_tags($post->message))).')...', array('class'=>'post-word-count'));
3397     } else {
3398         // Prepare whole post
3399         $postclass    = 'fullpost';
3400         $postcontent  = format_text($post->message, $post->messageformat, $options, $course->id);
3401         if (!empty($highlight)) {
3402             $postcontent = highlight($highlight, $postcontent);
3403         }
3404         $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
3405     }
3406     // Output the post content
3407     $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
3408     $output .= html_writer::end_tag('div'); // Content
3409     $output .= html_writer::end_tag('div'); // Content mask
3410     $output .= html_writer::end_tag('div'); // Row
3412     $output .= html_writer::start_tag('div', array('class'=>'row side'));
3413     $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
3414     $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
3416     // Output ratings
3417     if (!empty($post->rating)) {
3418         $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
3419     }
3421     // Output the commands
3422     $commandhtml = array();
3423     foreach ($commands as $command) {
3424         if (is_array($command)) {
3425             $commandhtml[] = html_writer::link($command['url'], $command['text']);
3426         } else {
3427             $commandhtml[] = $command;
3428         }
3429     }
3430     $output .= html_writer::tag('div', implode(' | ', $commandhtml), array('class'=>'commands'));
3432     // Output link to post if required
3433     if ($link) {
3434         if ($post->replies == 1) {
3435             $replystring = get_string('repliesone', 'forum', $post->replies);
3436         } else {
3437             $replystring = get_string('repliesmany', 'forum', $post->replies);
3438         }
3440         $output .= html_writer::start_tag('div', array('class'=>'link'));
3441         $output .= html_writer::link($discussionlink, get_string('discussthistopic', 'forum'));
3442         $output .= '&nbsp;('.$replystring.')';
3443         $output .= html_writer::end_tag('div'); // link
3444     }
3446     // Output footer if required
3447     if ($footer) {
3448         $output .= html_writer::tag('div', $footer, array('class'=>'footer'));
3449     }
3451     // Close remaining open divs
3452     $output .= html_writer::end_tag('div'); // content
3453     $output .= html_writer::end_tag('div'); // row
3454     $output .= html_writer::end_tag('div'); // forumpost
3456     // Mark the forum post as read if required
3457     if ($istracked && !$CFG->forum_usermarksread && !$postisread) {
3458         forum_tp_mark_post_read($USER->id, $post, $forum->id);
3459     }
3461     if ($return) {
3462         return $output;
3463     }
3464     echo $output;
3465     return;
3468 /**
3469  * Return rating related permissions
3470  *
3471  * @param string $options the context id
3472  * @return array an associative array of the user's rating permissions
3473  */
3474 function forum_rating_permissions($contextid, $component, $ratingarea) {
3475     $context = get_context_instance_by_id($contextid, MUST_EXIST);
3476     if ($component != 'mod_forum' || $ratingarea != 'post') {
3477         // We don't know about this component/ratingarea so just return null to get the
3478         // default restrictive permissions.
3479         return null;
3480     }
3481     return array(
3482         'view'    => has_capability('mod/forum:viewrating', $context),
3483         'viewany' => has_capability('mod/forum:viewanyrating', $context),
3484         'viewall' => has_capability('mod/forum:viewallratings', $context),
3485         'rate'    => has_capability('mod/forum:rate', $context)
3486     );
3489 /**
3490  * Validates a submitted rating
3491  * @param array $params submitted data
3492  *            context => object the context in which the rated items exists [required]
3493  *            component => The component for this module - should always be mod_forum [required]
3494  *            ratingarea => object the context in which the rated items exists [required]
3495  *            itemid => int the ID of the object being rated [required]
3496  *            scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
3497  *            rating => int the submitted rating [required]
3498  *            rateduserid => int the id of the user whose items have been rated. NOT the user who submitted the ratings. 0 to update all. [required]
3499  *            aggregation => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [required]
3500  * @return boolean true if the rating is valid. Will throw rating_exception if not
3501  */
3502 function forum_rating_validate($params) {
3503     global $DB, $USER;