MDL-27735 Q&A forum: add new caps to add question. By default, teacher, editing teach...
[moodle.git] / mod / forum / lib.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * @package    mod
19  * @subpackage 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 defined('MOODLE_INTERNAL') || die();
26 /** Include required files */
27 require_once($CFG->libdir.'/filelib.php');
28 require_once($CFG->libdir.'/eventslib.php');
29 require_once($CFG->dirroot.'/user/selector/lib.php');
31 /// CONSTANTS ///////////////////////////////////////////////////////////
33 define('FORUM_MODE_FLATOLDEST', 1);
34 define('FORUM_MODE_FLATNEWEST', -1);
35 define('FORUM_MODE_THREADED', 2);
36 define('FORUM_MODE_NESTED', 3);
38 define('FORUM_CHOOSESUBSCRIBE', 0);
39 define('FORUM_FORCESUBSCRIBE', 1);
40 define('FORUM_INITIALSUBSCRIBE', 2);
41 define('FORUM_DISALLOWSUBSCRIBE',3);
43 define('FORUM_TRACKING_OFF', 0);
44 define('FORUM_TRACKING_OPTIONAL', 1);
45 define('FORUM_TRACKING_ON', 2);
47 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
49 /**
50  * Given an object containing all the necessary data,
51  * (defined by the form in mod_form.php) this function
52  * will create a new instance and return the id number
53  * of the new instance.
54  *
55  * @global object
56  * @global object
57  * @param object $forum add forum instance (with magic quotes)
58  * @return int intance id
59  */
60 function forum_add_instance($forum, $mform) {
61     global $CFG, $DB;
63     $forum->timemodified = time();
65     if (empty($forum->assessed)) {
66         $forum->assessed = 0;
67     }
69     if (empty($forum->ratingtime) or empty($forum->assessed)) {
70         $forum->assesstimestart  = 0;
71         $forum->assesstimefinish = 0;
72     }
74     $forum->id = $DB->insert_record('forum', $forum);
75     $modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule);
77     if ($forum->type == 'single') {  // Create related discussion.
78         $discussion = new stdClass();
79         $discussion->course        = $forum->course;
80         $discussion->forum         = $forum->id;
81         $discussion->name          = $forum->name;
82         $discussion->assessed      = $forum->assessed;
83         $discussion->message       = $forum->intro;
84         $discussion->messageformat = $forum->introformat;
85         $discussion->messagetrust  = trusttext_trusted(get_context_instance(CONTEXT_COURSE, $forum->course));
86         $discussion->mailnow       = false;
87         $discussion->groupid       = -1;
89         $message = '';
91         $discussion->id = forum_add_discussion($discussion, null, $message);
93         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
94             // ugly hack - we need to copy the files somehow
95             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
96             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
98             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
99             $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
100         }
101     }
103     if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
104     /// all users should be subscribed initially
105     /// Note: forum_get_potential_subscribers should take the forum context,
106     /// but that does not exist yet, becuase the forum is only half build at this
107     /// stage. However, because the forum is brand new, we know that there are
108     /// no role assignments or overrides in the forum context, so using the
109     /// course context gives the same list of users.
110         $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
111         foreach ($users as $user) {
112             forum_subscribe($user->id, $forum->id);
113         }
114     }
116     forum_grade_item_update($forum);
118     return $forum->id;
122 /**
123  * Given an object containing all the necessary data,
124  * (defined by the form in mod_form.php) this function
125  * will update an existing instance with new data.
126  *
127  * @global object
128  * @param object $forum forum instance (with magic quotes)
129  * @return bool success
130  */
131 function forum_update_instance($forum, $mform) {
132     global $DB, $OUTPUT, $USER;
134     $forum->timemodified = time();
135     $forum->id           = $forum->instance;
137     if (empty($forum->assessed)) {
138         $forum->assessed = 0;
139     }
141     if (empty($forum->ratingtime) or empty($forum->assessed)) {
142         $forum->assesstimestart  = 0;
143         $forum->assesstimefinish = 0;
144     }
146     $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
148     // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
149     // if  scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
150     // 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
151     if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
152         forum_update_grades($forum); // recalculate grades for the forum
153     }
155     if ($forum->type == 'single') {  // Update related discussion and post.
156         if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
157             if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC')) {
158                 echo $OUTPUT->notification('Warning! There is more than one discussion in this forum - using the most recent');
159                 $discussion = array_pop($discussions);
160             } else {
161                 // try to recover by creating initial discussion - MDL-16262
162                 $discussion = new stdClass();
163                 $discussion->course          = $forum->course;
164                 $discussion->forum           = $forum->id;
165                 $discussion->name            = $forum->name;
166                 $discussion->assessed        = $forum->assessed;
167                 $discussion->message         = $forum->intro;
168                 $discussion->messageformat   = $forum->introformat;
169                 $discussion->messagetrust    = true;
170                 $discussion->mailnow         = false;
171                 $discussion->groupid         = -1;
173                 $message = '';
175                 forum_add_discussion($discussion, null, $message);
177                 if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
178                     print_error('cannotadd', 'forum');
179                 }
180             }
181         }
182         if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
183             print_error('cannotfindfirstpost', 'forum');
184         }
186         $cm         = get_coursemodule_from_instance('forum', $forum->id);
187         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
189         if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
190             // ugly hack - we need to copy the files somehow
191             $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
192             $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
194             $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
195         }
197         $post->subject       = $forum->name;
198         $post->message       = $forum->intro;
199         $post->messageformat = $forum->introformat;
200         $post->messagetrust  = trusttext_trusted($modcontext);
201         $post->modified      = $forum->timemodified;
202         $post->userid        = $USER->id;    // MDL-18599, so that current teacher can take ownership of activities
204         $DB->update_record('forum_posts', $post);
205         $discussion->name = $forum->name;
206         $DB->update_record('forum_discussions', $discussion);
207     }
209     $DB->update_record('forum', $forum);
211     forum_grade_item_update($forum);
213     return true;
217 /**
218  * Given an ID of an instance of this module,
219  * this function will permanently delete the instance
220  * and any data that depends on it.
221  *
222  * @global object
223  * @param int $id forum instance id
224  * @return bool success
225  */
226 function forum_delete_instance($id) {
227     global $DB;
229     if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
230         return false;
231     }
232     if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
233         return false;
234     }
235     if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
236         return false;
237     }
239     $context = get_context_instance(CONTEXT_MODULE, $cm->id);
241     // now get rid of all files
242     $fs = get_file_storage();
243     $fs->delete_area_files($context->id);
245     $result = true;
247     if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
248         foreach ($discussions as $discussion) {
249             if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
250                 $result = false;
251             }
252         }
253     }
255     if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
256         $result = false;
257     }
259     forum_tp_delete_read_records(-1, -1, -1, $forum->id);
261     if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
262         $result = false;
263     }
265     forum_grade_item_delete($forum);
267     return $result;
271 /**
272  * Indicates API features that the forum supports.
273  *
274  * @uses FEATURE_GROUPS
275  * @uses FEATURE_GROUPINGS
276  * @uses FEATURE_GROUPMEMBERSONLY
277  * @uses FEATURE_MOD_INTRO
278  * @uses FEATURE_COMPLETION_TRACKS_VIEWS
279  * @uses FEATURE_COMPLETION_HAS_RULES
280  * @uses FEATURE_GRADE_HAS_GRADE
281  * @uses FEATURE_GRADE_OUTCOMES
282  * @param string $feature
283  * @return mixed True if yes (some features may use other values)
284  */
285 function forum_supports($feature) {
286     switch($feature) {
287         case FEATURE_GROUPS:                  return true;
288         case FEATURE_GROUPINGS:               return true;
289         case FEATURE_GROUPMEMBERSONLY:        return true;
290         case FEATURE_MOD_INTRO:               return true;
291         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
292         case FEATURE_COMPLETION_HAS_RULES:    return true;
293         case FEATURE_GRADE_HAS_GRADE:         return true;
294         case FEATURE_GRADE_OUTCOMES:          return true;
295         case FEATURE_RATE:                    return true;
296         case FEATURE_BACKUP_MOODLE2:          return true;
297         case FEATURE_SHOW_DESCRIPTION:        return true;
299         default: return null;
300     }
304 /**
305  * Obtains the automatic completion state for this forum based on any conditions
306  * in forum settings.
307  *
308  * @global object
309  * @global object
310  * @param object $course Course
311  * @param object $cm Course-module
312  * @param int $userid User ID
313  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
314  * @return bool True if completed, false if not. (If no conditions, then return
315  *   value depends on comparison type)
316  */
317 function forum_get_completion_state($course,$cm,$userid,$type) {
318     global $CFG,$DB;
320     // Get forum details
321     if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
322         throw new Exception("Can't find forum {$cm->instance}");
323     }
325     $result=$type; // Default return value
327     $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
328     $postcountsql="
329 SELECT
330     COUNT(1)
331 FROM
332     {forum_posts} fp
333     INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
334 WHERE
335     fp.userid=:userid AND fd.forum=:forumid";
337     if ($forum->completiondiscussions) {
338         $value = $forum->completiondiscussions <=
339                  $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
340         if ($type == COMPLETION_AND) {
341             $result = $result && $value;
342         } else {
343             $result = $result || $value;
344         }
345     }
346     if ($forum->completionreplies) {
347         $value = $forum->completionreplies <=
348                  $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
349         if ($type==COMPLETION_AND) {
350             $result = $result && $value;
351         } else {
352             $result = $result || $value;
353         }
354     }
355     if ($forum->completionposts) {
356         $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
357         if ($type == COMPLETION_AND) {
358             $result = $result && $value;
359         } else {
360             $result = $result || $value;
361         }
362     }
364     return $result;
368 /**
369  * Function to be run periodically according to the moodle cron
370  * Finds all posts that have yet to be mailed out, and mails them
371  * out to all subscribers
372  *
373  * @global object
374  * @global object
375  * @global object
376  * @uses CONTEXT_MODULE
377  * @uses CONTEXT_COURSE
378  * @uses SITEID
379  * @uses FORMAT_PLAIN
380  * @return void
381  */
382 function forum_cron() {
383     global $CFG, $USER, $DB;
385     $site = get_site();
387     // all users that are subscribed to any post that needs sending
388     $users = array();
390     // status arrays
391     $mailcount  = array();
392     $errorcount = array();
394     // caches
395     $discussions     = array();
396     $forums          = array();
397     $courses         = array();
398     $coursemodules   = array();
399     $subscribedusers = array();
402     // Posts older than 2 days will not be mailed.  This is to avoid the problem where
403     // cron has not been running for a long time, and then suddenly people are flooded
404     // with mail from the past few weeks or months
405     $timenow   = time();
406     $endtime   = $timenow - $CFG->maxeditingtime;
407     $starttime = $endtime - 48 * 3600;   // Two days earlier
409     if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
410         // Mark them all now as being mailed.  It's unlikely but possible there
411         // might be an error later so that a post is NOT actually mailed out,
412         // but since mail isn't crucial, we can accept this risk.  Doing it now
413         // prevents the risk of duplicated mails, which is a worse problem.
415         if (!forum_mark_old_posts_as_mailed($endtime)) {
416             mtrace('Errors occurred while trying to mark some posts as being mailed.');
417             return false;  // Don't continue trying to mail them, in case we are in a cron loop
418         }
420         // checking post validity, and adding users to loop through later
421         foreach ($posts as $pid => $post) {
423             $discussionid = $post->discussion;
424             if (!isset($discussions[$discussionid])) {
425                 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
426                     $discussions[$discussionid] = $discussion;
427                 } else {
428                     mtrace('Could not find discussion '.$discussionid);
429                     unset($posts[$pid]);
430                     continue;
431                 }
432             }
433             $forumid = $discussions[$discussionid]->forum;
434             if (!isset($forums[$forumid])) {
435                 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
436                     $forums[$forumid] = $forum;
437                 } else {
438                     mtrace('Could not find forum '.$forumid);
439                     unset($posts[$pid]);
440                     continue;
441                 }
442             }
443             $courseid = $forums[$forumid]->course;
444             if (!isset($courses[$courseid])) {
445                 if ($course = $DB->get_record('course', array('id' => $courseid))) {
446                     $courses[$courseid] = $course;
447                 } else {
448                     mtrace('Could not find course '.$courseid);
449                     unset($posts[$pid]);
450                     continue;
451                 }
452             }
453             if (!isset($coursemodules[$forumid])) {
454                 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
455                     $coursemodules[$forumid] = $cm;
456                 } else {
457                     mtrace('Could not find course module for forum '.$forumid);
458                     unset($posts[$pid]);
459                     continue;
460                 }
461             }
464             // caching subscribed users of each forum
465             if (!isset($subscribedusers[$forumid])) {
466                 $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
467                 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext, "u.*")) {
468                     foreach ($subusers as $postuser) {
469                         unset($postuser->description); // not necessary
470                         // this user is subscribed to this forum
471                         $subscribedusers[$forumid][$postuser->id] = $postuser->id;
472                         // this user is a user we have to process later
473                         $users[$postuser->id] = $postuser;
474                     }
475                     unset($subusers); // release memory
476                 }
477             }
479             $mailcount[$pid] = 0;
480             $errorcount[$pid] = 0;
481         }
482     }
484     if ($users && $posts) {
486         $urlinfo = parse_url($CFG->wwwroot);
487         $hostname = $urlinfo['host'];
489         foreach ($users as $userto) {
491             @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
493             // set this so that the capabilities are cached, and environment matches receiving user
494             cron_setup_user($userto);
496             mtrace('Processing user '.$userto->id);
498             // init caches
499             $userto->viewfullnames = array();
500             $userto->canpost       = array();
501             $userto->markposts     = array();
503             // reset the caches
504             foreach ($coursemodules as $forumid=>$unused) {
505                 $coursemodules[$forumid]->cache       = new stdClass();
506                 $coursemodules[$forumid]->cache->caps = array();
507                 unset($coursemodules[$forumid]->uservisible);
508             }
510             foreach ($posts as $pid => $post) {
512                 // Set up the environment for the post, discussion, forum, course
513                 $discussion = $discussions[$post->discussion];
514                 $forum      = $forums[$discussion->forum];
515                 $course     = $courses[$forum->course];
516                 $cm         =& $coursemodules[$forum->id];
518                 // Do some checks  to see if we can bail out now
519                 // Only active enrolled users are in the list of subscribers
520                 if (!isset($subscribedusers[$forum->id][$userto->id])) {
521                     continue; // user does not subscribe to this forum
522                 }
524                 // Don't send email if the forum is Q&A and the user has not posted
525                 if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id)) {
526                     mtrace('Did not email '.$userto->id.' because user has not posted in discussion');
527                     continue;
528                 }
530                 // Get info about the sending user
531                 if (array_key_exists($post->userid, $users)) { // we might know him/her already
532                     $userfrom = $users[$post->userid];
533                 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
534                     unset($userfrom->description); // not necessary
535                     $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
536                 } else {
537                     mtrace('Could not find user '.$post->userid);
538                     continue;
539                 }
541                 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
543                 // setup global $COURSE properly - needed for roles and languages
544                 cron_setup_user($userto, $course);
546                 // Fill caches
547                 if (!isset($userto->viewfullnames[$forum->id])) {
548                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
549                     $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
550                 }
551                 if (!isset($userto->canpost[$discussion->id])) {
552                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
553                     $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
554                 }
555                 if (!isset($userfrom->groups[$forum->id])) {
556                     if (!isset($userfrom->groups)) {
557                         $userfrom->groups = array();
558                         $users[$userfrom->id]->groups = array();
559                     }
560                     $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
561                     $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
562                 }
564                 // Make sure groups allow this user to see this email
565                 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
566                     if (!groups_group_exists($discussion->groupid)) { // Can't find group
567                         continue;                           // Be safe and don't send it to anyone
568                     }
570                     if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
571                         // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
572                         continue;
573                     }
574                 }
576                 // Make sure we're allowed to see it...
577                 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
578                     mtrace('user '.$userto->id. ' can not see '.$post->id);
579                     continue;
580                 }
582                 // OK so we need to send the email.
584                 // Does the user want this post in a digest?  If so postpone it for now.
585                 if ($userto->maildigest > 0) {
586                     // This user wants the mails to be in digest form
587                     $queue = new stdClass();
588                     $queue->userid       = $userto->id;
589                     $queue->discussionid = $discussion->id;
590                     $queue->postid       = $post->id;
591                     $queue->timemodified = $post->created;
592                     $DB->insert_record('forum_queue', $queue);
593                     continue;
594                 }
597                 // Prepare to actually send the post now, and build up the content
599                 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
601                 $userfrom->customheaders = array (  // Headers to make emails easier to track
602                            'Precedence: Bulk',
603                            'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
604                            'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
605                            'Message-ID: <moodlepost'.$post->id.'@'.$hostname.'>',
606                            'X-Course-Id: '.$course->id,
607                            'X-Course-Name: '.format_string($course->fullname, true)
608                 );
610                 if ($post->parent) {  // This post is a reply, so add headers for threading (see MDL-22551)
611                     $userfrom->customheaders[] = 'In-Reply-To: <moodlepost'.$post->parent.'@'.$hostname.'>';
612                     $userfrom->customheaders[] = 'References: <moodlepost'.$post->parent.'@'.$hostname.'>';
613                 }
615                 $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
617                 $postsubject = "$shortname: ".format_string($post->subject,true);
618                 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
619                 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
621                 // Send the post now!
623                 mtrace('Sending ', '');
625                 $eventdata = new stdClass();
626                 $eventdata->component        = 'mod_forum';
627                 $eventdata->name             = 'posts';
628                 $eventdata->userfrom         = $userfrom;
629                 $eventdata->userto           = $userto;
630                 $eventdata->subject          = $postsubject;
631                 $eventdata->fullmessage      = $posttext;
632                 $eventdata->fullmessageformat = FORMAT_PLAIN;
633                 $eventdata->fullmessagehtml  = $posthtml;
634                 $eventdata->notification = 1;
636                 $smallmessagestrings = new stdClass();
637                 $smallmessagestrings->user = fullname($userfrom);
638                 $smallmessagestrings->forumname = "$shortname: ".format_string($forum->name,true).": ".$discussion->name;
639                 $smallmessagestrings->message = $post->message;
640                 //make sure strings are in message recipients language
641                 $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
643                 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
644                 $eventdata->contexturlname = $discussion->name;
646                 $mailresult = message_send($eventdata);
647                 if (!$mailresult){
648                     mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
649                          " ($userto->email) .. not trying again.");
650                     add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
651                                substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
652                     $errorcount[$post->id]++;
653                 } else {
654                     $mailcount[$post->id]++;
656                 // Mark post as read if forum_usermarksread is set off
657                     if (!$CFG->forum_usermarksread) {
658                         $userto->markposts[$post->id] = $post->id;
659                     }
660                 }
662                 mtrace('post '.$post->id. ': '.$post->subject);
663             }
665             // mark processed posts as read
666             forum_tp_mark_posts_read($userto, $userto->markposts);
667         }
668     }
670     if ($posts) {
671         foreach ($posts as $post) {
672             mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
673             if ($errorcount[$post->id]) {
674                 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
675             }
676         }
677     }
679     // release some memory
680     unset($subscribedusers);
681     unset($mailcount);
682     unset($errorcount);
684     cron_setup_user();
686     $sitetimezone = $CFG->timezone;
688     // Now see if there are any digest mails waiting to be sent, and if we should send them
690     mtrace('Starting digest processing...');
692     @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
694     if (!isset($CFG->digestmailtimelast)) {    // To catch the first time
695         set_config('digestmailtimelast', 0);
696     }
698     $timenow = time();
699     $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
701     // Delete any really old ones (normally there shouldn't be any)
702     $weekago = $timenow - (7 * 24 * 3600);
703     $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
704     mtrace ('Cleaned old digest records');
706     if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
708         mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
710         $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
712         if ($digestposts_rs->valid()) {
714             // We have work to do
715             $usermailcount = 0;
717             //caches - reuse the those filled before too
718             $discussionposts = array();
719             $userdiscussions = array();
721             foreach ($digestposts_rs as $digestpost) {
722                 if (!isset($users[$digestpost->userid])) {
723                     if ($user = $DB->get_record('user', array('id' => $digestpost->userid))) {
724                         $users[$digestpost->userid] = $user;
725                     } else {
726                         continue;
727                     }
728                 }
729                 $postuser = $users[$digestpost->userid];
731                 if (!isset($posts[$digestpost->postid])) {
732                     if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
733                         $posts[$digestpost->postid] = $post;
734                     } else {
735                         continue;
736                     }
737                 }
738                 $discussionid = $digestpost->discussionid;
739                 if (!isset($discussions[$discussionid])) {
740                     if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
741                         $discussions[$discussionid] = $discussion;
742                     } else {
743                         continue;
744                     }
745                 }
746                 $forumid = $discussions[$discussionid]->forum;
747                 if (!isset($forums[$forumid])) {
748                     if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
749                         $forums[$forumid] = $forum;
750                     } else {
751                         continue;
752                     }
753                 }
755                 $courseid = $forums[$forumid]->course;
756                 if (!isset($courses[$courseid])) {
757                     if ($course = $DB->get_record('course', array('id' => $courseid))) {
758                         $courses[$courseid] = $course;
759                     } else {
760                         continue;
761                     }
762                 }
764                 if (!isset($coursemodules[$forumid])) {
765                     if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
766                         $coursemodules[$forumid] = $cm;
767                     } else {
768                         continue;
769                     }
770                 }
771                 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
772                 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
773             }
774             $digestposts_rs->close(); /// Finished iteration, let's close the resultset
776             // Data collected, start sending out emails to each user
777             foreach ($userdiscussions as $userid => $thesediscussions) {
779                 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
781                 cron_setup_user();
783                 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
785                 // First of all delete all the queue entries for this user
786                 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
787                 $userto = $users[$userid];
789                 // Override the language and timezone of the "current" user, so that
790                 // mail is customised for the receiver.
791                 cron_setup_user($userto);
793                 // init caches
794                 $userto->viewfullnames = array();
795                 $userto->canpost       = array();
796                 $userto->markposts     = array();
798                 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
800                 $headerdata = new stdClass();
801                 $headerdata->sitename = format_string($site->fullname, true);
802                 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
804                 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
805                 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
807                 $posthtml = "<head>";
808 /*                foreach ($CFG->stylesheets as $stylesheet) {
809                     //TODO: MDL-21120
810                     $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
811                 }*/
812                 $posthtml .= "</head>\n<body id=\"email\">\n";
813                 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
815                 foreach ($thesediscussions as $discussionid) {
817                     @set_time_limit(120);   // to be reset for each post
819                     $discussion = $discussions[$discussionid];
820                     $forum      = $forums[$discussion->forum];
821                     $course     = $courses[$forum->course];
822                     $cm         = $coursemodules[$forum->id];
824                     //override language
825                     cron_setup_user($userto, $course);
827                     // Fill caches
828                     if (!isset($userto->viewfullnames[$forum->id])) {
829                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
830                         $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
831                     }
832                     if (!isset($userto->canpost[$discussion->id])) {
833                         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
834                         $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
835                     }
837                     $strforums      = get_string('forums', 'forum');
838                     $canunsubscribe = ! forum_is_forcesubscribed($forum);
839                     $canreply       = $userto->canpost[$discussion->id];
840                     $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
842                     $posttext .= "\n \n";
843                     $posttext .= '=====================================================================';
844                     $posttext .= "\n \n";
845                     $posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
846                     if ($discussion->name != $forum->name) {
847                         $posttext  .= " -> ".format_string($discussion->name,true);
848                     }
849                     $posttext .= "\n";
851                     $posthtml .= "<p><font face=\"sans-serif\">".
852                     "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
853                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
854                     "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
855                     if ($discussion->name == $forum->name) {
856                         $posthtml .= "</font></p>";
857                     } else {
858                         $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
859                     }
860                     $posthtml .= '<p>';
862                     $postsarray = $discussionposts[$discussionid];
863                     sort($postsarray);
865                     foreach ($postsarray as $postid) {
866                         $post = $posts[$postid];
868                         if (array_key_exists($post->userid, $users)) { // we might know him/her already
869                             $userfrom = $users[$post->userid];
870                         } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
871                             $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
872                         } else {
873                             mtrace('Could not find user '.$post->userid);
874                             continue;
875                         }
877                         if (!isset($userfrom->groups[$forum->id])) {
878                             if (!isset($userfrom->groups)) {
879                                 $userfrom->groups = array();
880                                 $users[$userfrom->id]->groups = array();
881                             }
882                             $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
883                             $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
884                         }
886                         $userfrom->customheaders = array ("Precedence: Bulk");
888                         if ($userto->maildigest == 2) {
889                             // Subjects only
890                             $by = new stdClass();
891                             $by->name = fullname($userfrom);
892                             $by->date = userdate($post->modified);
893                             $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
894                             $posttext .= "\n---------------------------------------------------------------------";
896                             $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
897                             $posthtml .= '<div><a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'#p'.$post->id.'">'.format_string($post->subject,true).'</a> '.get_string("bynameondate", "forum", $by).'</div>';
899                         } else {
900                             // The full treatment
901                             $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
902                             $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
904                         // Create an array of postid's for this user to mark as read.
905                             if (!$CFG->forum_usermarksread) {
906                                 $userto->markposts[$post->id] = $post->id;
907                             }
908                         }
909                     }
910                     if ($canunsubscribe) {
911                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\"><a href=\"$CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\">".get_string("unsubscribe", "forum")."</a></font></div>";
912                     } else {
913                         $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
914                     }
915                     $posthtml .= '<hr size="1" noshade="noshade" /></p>';
916                 }
917                 $posthtml .= '</body>';
919                 if (empty($userto->mailformat) || $userto->mailformat != 1) {
920                     // This user DOESN'T want to receive HTML
921                     $posthtml = '';
922                 }
924                 $attachment = $attachname='';
925                 $usetrueaddress = true;
926                 //directly email forum digests rather than sending them via messaging
927                 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname, $usetrueaddress, $CFG->forum_replytouser);
929                 if (!$mailresult) {
930                     mtrace("ERROR!");
931                     echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
932                     add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
933                 } else {
934                     mtrace("success.");
935                     $usermailcount++;
937                     // Mark post as read if forum_usermarksread is set off
938                     forum_tp_mark_posts_read($userto, $userto->markposts);
939                 }
940             }
941         }
942     /// We have finishied all digest emails, update $CFG->digestmailtimelast
943         set_config('digestmailtimelast', $timenow);
944     }
946     cron_setup_user();
948     if (!empty($usermailcount)) {
949         mtrace(get_string('digestsentusers', 'forum', $usermailcount));
950     }
952     if (!empty($CFG->forum_lastreadclean)) {
953         $timenow = time();
954         if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
955             set_config('forum_lastreadclean', $timenow);
956             mtrace('Removing old forum read tracking info...');
957             forum_tp_clean_read_records();
958         }
959     } else {
960         set_config('forum_lastreadclean', time());
961     }
964     return true;
967 /**
968  * Builds and returns the body of the email notification in plain text.
969  *
970  * @global object
971  * @global object
972  * @uses CONTEXT_MODULE
973  * @param object $course
974  * @param object $cm
975  * @param object $forum
976  * @param object $discussion
977  * @param object $post
978  * @param object $userfrom
979  * @param object $userto
980  * @param boolean $bare
981  * @return string The email body in plain text format.
982  */
983 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
984     global $CFG, $USER;
986     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
988     if (!isset($userto->viewfullnames[$forum->id])) {
989         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
990     } else {
991         $viewfullnames = $userto->viewfullnames[$forum->id];
992     }
994     if (!isset($userto->canpost[$discussion->id])) {
995         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
996     } else {
997         $canreply = $userto->canpost[$discussion->id];
998     }
1000     $by = New stdClass;
1001     $by->name = fullname($userfrom, $viewfullnames);
1002     $by->date = userdate($post->modified, "", $userto->timezone);
1004     $strbynameondate = get_string('bynameondate', 'forum', $by);
1006     $strforums = get_string('forums', 'forum');
1008     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1010     $posttext = '';
1012     if (!$bare) {
1013         $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
1014         $posttext  = "$shortname -> $strforums -> ".format_string($forum->name,true);
1016         if ($discussion->name != $forum->name) {
1017             $posttext  .= " -> ".format_string($discussion->name,true);
1018         }
1019     }
1021     // add absolute file links
1022     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
1024     $posttext .= "\n---------------------------------------------------------------------\n";
1025     $posttext .= format_string($post->subject,true);
1026     if ($bare) {
1027         $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1028     }
1029     $posttext .= "\n".$strbynameondate."\n";
1030     $posttext .= "---------------------------------------------------------------------\n";
1031     $posttext .= format_text_email($post->message, $post->messageformat);
1032     $posttext .= "\n\n";
1033     $posttext .= forum_print_attachments($post, $cm, "text");
1035     if (!$bare && $canreply) {
1036         $posttext .= "---------------------------------------------------------------------\n";
1037         $posttext .= get_string("postmailinfo", "forum", $shortname)."\n";
1038         $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1039     }
1040     if (!$bare && $canunsubscribe) {
1041         $posttext .= "\n---------------------------------------------------------------------\n";
1042         $posttext .= get_string("unsubscribe", "forum");
1043         $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1044     }
1046     return $posttext;
1049 /**
1050  * Builds and returns the body of the email notification in html format.
1051  *
1052  * @global object
1053  * @param object $course
1054  * @param object $cm
1055  * @param object $forum
1056  * @param object $discussion
1057  * @param object $post
1058  * @param object $userfrom
1059  * @param object $userto
1060  * @return string The email text in HTML format
1061  */
1062 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1063     global $CFG;
1065     if ($userto->mailformat != 1) {  // Needs to be HTML
1066         return '';
1067     }
1069     if (!isset($userto->canpost[$discussion->id])) {
1070         $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course);
1071     } else {
1072         $canreply = $userto->canpost[$discussion->id];
1073     }
1075     $strforums = get_string('forums', 'forum');
1076     $canunsubscribe = ! forum_is_forcesubscribed($forum);
1077     $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
1079     $posthtml = '<head>';
1080 /*    foreach ($CFG->stylesheets as $stylesheet) {
1081         //TODO: MDL-21120
1082         $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1083     }*/
1084     $posthtml .= '</head>';
1085     $posthtml .= "\n<body id=\"email\">\n\n";
1087     $posthtml .= '<div class="navbar">'.
1088     '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$shortname.'</a> &raquo; '.
1089     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1090     '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1091     if ($discussion->name == $forum->name) {
1092         $posthtml .= '</div>';
1093     } else {
1094         $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1095                      format_string($discussion->name,true).'</a></div>';
1096     }
1097     $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1099     if ($canunsubscribe) {
1100         $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1101                       <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1102                       <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1103     }
1105     $posthtml .= '</body>';
1107     return $posthtml;
1111 /**
1112  *
1113  * @param object $course
1114  * @param object $user
1115  * @param object $mod TODO this is not used in this function, refactor
1116  * @param object $forum
1117  * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1118  */
1119 function forum_user_outline($course, $user, $mod, $forum) {
1120     global $CFG;
1121     require_once("$CFG->libdir/gradelib.php");
1122     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1123     if (empty($grades->items[0]->grades)) {
1124         $grade = false;
1125     } else {
1126         $grade = reset($grades->items[0]->grades);
1127     }
1129     $count = forum_count_user_posts($forum->id, $user->id);
1131     if ($count && $count->postcount > 0) {
1132         $result = new stdClass();
1133         $result->info = get_string("numposts", "forum", $count->postcount);
1134         $result->time = $count->lastpost;
1135         if ($grade) {
1136             $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1137         }
1138         return $result;
1139     } else if ($grade) {
1140         $result = new stdClass();
1141         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1143         //datesubmitted == time created. dategraded == time modified or time overridden
1144         //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
1145         //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
1146         if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1147             $result->time = $grade->dategraded;
1148         } else {
1149             $result->time = $grade->datesubmitted;
1150         }
1152         return $result;
1153     }
1154     return NULL;
1158 /**
1159  * @global object
1160  * @global object
1161  * @param object $coure
1162  * @param object $user
1163  * @param object $mod
1164  * @param object $forum
1165  */
1166 function forum_user_complete($course, $user, $mod, $forum) {
1167     global $CFG,$USER, $OUTPUT;
1168     require_once("$CFG->libdir/gradelib.php");
1170     $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1171     if (!empty($grades->items[0]->grades)) {
1172         $grade = reset($grades->items[0]->grades);
1173         echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1174         if ($grade->str_feedback) {
1175             echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1176         }
1177     }
1179     if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1181         if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1182             print_error('invalidcoursemodule');
1183         }
1184         $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1186         foreach ($posts as $post) {
1187             if (!isset($discussions[$post->discussion])) {
1188                 continue;
1189             }
1190             $discussion = $discussions[$post->discussion];
1192             forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1193         }
1194     } else {
1195         echo "<p>".get_string("noposts", "forum")."</p>";
1196     }
1204 /**
1205  * @global object
1206  * @global object
1207  * @global object
1208  * @param array $courses
1209  * @param array $htmlarray
1210  */
1211 function forum_print_overview($courses,&$htmlarray) {
1212     global $USER, $CFG, $DB, $SESSION;
1214     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1215         return array();
1216     }
1218     if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1219         return;
1220     }
1223     // get all forum logs in ONE query (much better!)
1224     $params = array();
1225     $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {log} l "
1226         ." JOIN {course_modules} cm ON cm.id = cmid "
1227         ." WHERE (";
1228     foreach ($courses as $course) {
1229         $sql .= '(l.course = ? AND l.time > ?) OR ';
1230         $params[] = $course->id;
1231         $params[] = $course->lastaccess;
1232     }
1233     $sql = substr($sql,0,-3); // take off the last OR
1235     $sql .= ") AND l.module = 'forum' AND action = 'add post' "
1236         ." AND userid != ? GROUP BY cmid,l.course,instance";
1238     $params[] = $USER->id;
1240     if (!$new = $DB->get_records_sql($sql, $params)) {
1241         $new = array(); // avoid warnings
1242     }
1244     // also get all forum tracking stuff ONCE.
1245     $trackingforums = array();
1246     foreach ($forums as $forum) {
1247         if (forum_tp_can_track_forums($forum)) {
1248             $trackingforums[$forum->id] = $forum;
1249         }
1250     }
1252     if (count($trackingforums) > 0) {
1253         $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1254         $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1255             ' FROM {forum_posts} p '.
1256             ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1257             ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1258         $params = array($USER->id);
1260         foreach ($trackingforums as $track) {
1261             $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1262             $params[] = $track->id;
1263             if (isset($SESSION->currentgroup[$track->course])) {
1264                 $groupid =  $SESSION->currentgroup[$track->course];
1265             } else {
1266                 $groupid = groups_get_all_groups($track->course, $USER->id);
1267                 if (is_array($groupid)) {
1268                     $groupid = array_shift(array_keys($groupid));
1269                     $SESSION->currentgroup[$track->course] = $groupid;
1270                 } else {
1271                     $groupid = 0;
1272                 }
1273             }
1274             $params[] = $groupid;
1275         }
1276         $sql = substr($sql,0,-3); // take off the last OR
1277         $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1278         $params[] = $cutoffdate;
1280         if (!$unread = $DB->get_records_sql($sql, $params)) {
1281             $unread = array();
1282         }
1283     } else {
1284         $unread = array();
1285     }
1287     if (empty($unread) and empty($new)) {
1288         return;
1289     }
1291     $strforum = get_string('modulename','forum');
1292     $strnumunread = get_string('overviewnumunread','forum');
1293     $strnumpostssince = get_string('overviewnumpostssince','forum');
1295     foreach ($forums as $forum) {
1296         $str = '';
1297         $count = 0;
1298         $thisunread = 0;
1299         $showunread = false;
1300         // either we have something from logs, or trackposts, or nothing.
1301         if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1302             $count = $new[$forum->id]->count;
1303         }
1304         if (array_key_exists($forum->id,$unread)) {
1305             $thisunread = $unread[$forum->id]->count;
1306             $showunread = true;
1307         }
1308         if ($count > 0 || $thisunread > 0) {
1309             $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1310                 $forum->name.'</a></div>';
1311             $str .= '<div class="info"><span class="postsincelogin">';
1312             $str .= $count.' '.$strnumpostssince."</span>";
1313             if (!empty($showunread)) {
1314                 $str .= '<div class="unreadposts">'.$thisunread .' '.$strnumunread.'</div>';
1315             }
1316             $str .= '</div></div>';
1317         }
1318         if (!empty($str)) {
1319             if (!array_key_exists($forum->course,$htmlarray)) {
1320                 $htmlarray[$forum->course] = array();
1321             }
1322             if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1323                 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1324             }
1325             $htmlarray[$forum->course]['forum'] .= $str;
1326         }
1327     }
1330 /**
1331  * Given a course and a date, prints a summary of all the new
1332  * messages posted in the course since that date
1333  *
1334  * @global object
1335  * @global object
1336  * @global object
1337  * @uses CONTEXT_MODULE
1338  * @uses VISIBLEGROUPS
1339  * @param object $course
1340  * @param bool $viewfullnames capability
1341  * @param int $timestart
1342  * @return bool success
1343  */
1344 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1345     global $CFG, $USER, $DB, $OUTPUT;
1347     // do not use log table if possible, it may be huge and is expensive to join with other tables
1349     if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1350                                               d.timestart, d.timeend, d.userid AS duserid,
1351                                               u.firstname, u.lastname, u.email, u.picture
1352                                          FROM {forum_posts} p
1353                                               JOIN {forum_discussions} d ON d.id = p.discussion
1354                                               JOIN {forum} f             ON f.id = d.forum
1355                                               JOIN {user} u              ON u.id = p.userid
1356                                         WHERE p.created > ? AND f.course = ?
1357                                      ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1358          return false;
1359     }
1361     $modinfo =& get_fast_modinfo($course);
1363     $groupmodes = array();
1364     $cms    = array();
1366     $strftimerecent = get_string('strftimerecent');
1368     $printposts = array();
1369     foreach ($posts as $post) {
1370         if (!isset($modinfo->instances['forum'][$post->forum])) {
1371             // not visible
1372             continue;
1373         }
1374         $cm = $modinfo->instances['forum'][$post->forum];
1375         if (!$cm->uservisible) {
1376             continue;
1377         }
1378         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1380         if (!has_capability('mod/forum:viewdiscussion', $context)) {
1381             continue;
1382         }
1384         if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1385           and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1386             if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1387                 continue;
1388             }
1389         }
1391         $groupmode = groups_get_activity_groupmode($cm, $course);
1393         if ($groupmode) {
1394             if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1395                 // oki (Open discussions have groupid -1)
1396             } else {
1397                 // separate mode
1398                 if (isguestuser()) {
1399                     // shortcut
1400                     continue;
1401                 }
1403                 if (is_null($modinfo->groups)) {
1404                     $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1405                 }
1407                 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1408                     continue;
1409                 }
1410             }
1411         }
1413         $printposts[] = $post;
1414     }
1415     unset($posts);
1417     if (!$printposts) {
1418         return false;
1419     }
1421     echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1422     echo "\n<ul class='unlist'>\n";
1424     foreach ($printposts as $post) {
1425         $subjectclass = empty($post->parent) ? ' bold' : '';
1427         echo '<li><div class="head">'.
1428                '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1429                '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1430              '</div>';
1431         echo '<div class="info'.$subjectclass.'">';
1432         if (empty($post->parent)) {
1433             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1434         } else {
1435             echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1436         }
1437         $post->subject = break_up_long_words(format_string($post->subject, true));
1438         echo $post->subject;
1439         echo "</a>\"</div></li>\n";
1440     }
1442     echo "</ul>\n";
1444     return true;
1447 /**
1448  * Return grade for given user or all users.
1449  *
1450  * @global object
1451  * @global object
1452  * @param object $forum
1453  * @param int $userid optional user id, 0 means all users
1454  * @return array array of grades, false if none
1455  */
1456 function forum_get_user_grades($forum, $userid = 0) {
1457     global $CFG;
1459     require_once($CFG->dirroot.'/rating/lib.php');
1461     $ratingoptions = new stdClass;
1462     $ratingoptions->component = 'mod_forum';
1463     $ratingoptions->ratingarea = 'post';
1465     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1466     $ratingoptions->modulename = 'forum';
1467     $ratingoptions->moduleid   = $forum->id;
1468     $ratingoptions->userid = $userid;
1469     $ratingoptions->aggregationmethod = $forum->assessed;
1470     $ratingoptions->scaleid = $forum->scale;
1471     $ratingoptions->itemtable = 'forum_posts';
1472     $ratingoptions->itemtableusercolumn = 'userid';
1474     $rm = new rating_manager();
1475     return $rm->get_user_grades($ratingoptions);
1478 /**
1479  * Update activity grades
1480  *
1481  * @global object
1482  * @global object
1483  * @param object $forum
1484  * @param int $userid specific user only, 0 means all
1485  * @param boolean $nullifnone return null if grade does not exist
1486  * @return void
1487  */
1488 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1489     global $CFG, $DB;
1490     require_once($CFG->libdir.'/gradelib.php');
1492     if (!$forum->assessed) {
1493         forum_grade_item_update($forum);
1495     } else if ($grades = forum_get_user_grades($forum, $userid)) {
1496         forum_grade_item_update($forum, $grades);
1498     } else if ($userid and $nullifnone) {
1499         $grade = new stdClass();
1500         $grade->userid   = $userid;
1501         $grade->rawgrade = NULL;
1502         forum_grade_item_update($forum, $grade);
1504     } else {
1505         forum_grade_item_update($forum);
1506     }
1509 /**
1510  * Update all grades in gradebook.
1511  * @global object
1512  */
1513 function forum_upgrade_grades() {
1514     global $DB;
1516     $sql = "SELECT COUNT('x')
1517               FROM {forum} f, {course_modules} cm, {modules} m
1518              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1519     $count = $DB->count_records_sql($sql);
1521     $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1522               FROM {forum} f, {course_modules} cm, {modules} m
1523              WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1524     $rs = $DB->get_recordset_sql($sql);
1525     if ($rs->valid()) {
1526         $pbar = new progress_bar('forumupgradegrades', 500, true);
1527         $i=0;
1528         foreach ($rs as $forum) {
1529             $i++;
1530             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1531             forum_update_grades($forum, 0, false);
1532             $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1533         }
1534     }
1535     $rs->close();
1538 /**
1539  * Create/update grade item for given forum
1540  *
1541  * @global object
1542  * @uses GRADE_TYPE_NONE
1543  * @uses GRADE_TYPE_VALUE
1544  * @uses GRADE_TYPE_SCALE
1545  * @param object $forum object with extra cmidnumber
1546  * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
1547  * @return int 0 if ok
1548  */
1549 function forum_grade_item_update($forum, $grades=NULL) {
1550     global $CFG;
1551     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1552         require_once($CFG->libdir.'/gradelib.php');
1553     }
1555     $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1557     if (!$forum->assessed or $forum->scale == 0) {
1558         $params['gradetype'] = GRADE_TYPE_NONE;
1560     } else if ($forum->scale > 0) {
1561         $params['gradetype'] = GRADE_TYPE_VALUE;
1562         $params['grademax']  = $forum->scale;
1563         $params['grademin']  = 0;
1565     } else if ($forum->scale < 0) {
1566         $params['gradetype'] = GRADE_TYPE_SCALE;
1567         $params['scaleid']   = -$forum->scale;
1568     }
1570     if ($grades  === 'reset') {
1571         $params['reset'] = true;
1572         $grades = NULL;
1573     }
1575     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1578 /**
1579  * Delete grade item for given forum
1580  *
1581  * @global object
1582  * @param object $forum object
1583  * @return object grade_item
1584  */
1585 function forum_grade_item_delete($forum) {
1586     global $CFG;
1587     require_once($CFG->libdir.'/gradelib.php');
1589     return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1593 /**
1594  * Returns the users with data in one forum
1595  * (users with records in forum_subscriptions, forum_posts, students)
1596  *
1597  * @todo: deprecated - to be deleted in 2.2
1598  *
1599  * @param int $forumid
1600  * @return mixed array or false if none
1601  */
1602 function forum_get_participants($forumid) {
1604     global $CFG, $DB;
1606     $params = array('forumid' => $forumid);
1608     //Get students from forum_subscriptions
1609     $sql = "SELECT DISTINCT u.id, u.id
1610               FROM {user} u,
1611                    {forum_subscriptions} s
1612              WHERE s.forum = :forumid AND
1613                    u.id = s.userid";
1614     $st_subscriptions = $DB->get_records_sql($sql, $params);
1616     //Get students from forum_posts
1617     $sql = "SELECT DISTINCT u.id, u.id
1618               FROM {user} u,
1619                    {forum_discussions} d,
1620                    {forum_posts} p
1621               WHERE d.forum = :forumid AND
1622                     p.discussion = d.id AND
1623                     u.id = p.userid";
1624     $st_posts = $DB->get_records_sql($sql, $params);
1626     //Get students from the ratings table
1627     $sql = "SELECT DISTINCT r.userid, r.userid AS id
1628               FROM {forum_discussions} d
1629               JOIN {forum_posts} p ON p.discussion = d.id
1630               JOIN {rating} r on r.itemid = p.id
1631              WHERE d.forum = :forumid AND
1632                    r.component = 'mod_forum' AND
1633                    r.ratingarea = 'post'";
1634     $st_ratings = $DB->get_records_sql($sql, $params);
1636     //Add st_posts to st_subscriptions
1637     if ($st_posts) {
1638         foreach ($st_posts as $st_post) {
1639             $st_subscriptions[$st_post->id] = $st_post;
1640         }
1641     }
1642     //Add st_ratings to st_subscriptions
1643     if ($st_ratings) {
1644         foreach ($st_ratings as $st_rating) {
1645             $st_subscriptions[$st_rating->id] = $st_rating;
1646         }
1647     }
1648     //Return st_subscriptions array (it contains an array of unique users)
1649     return ($st_subscriptions);
1652 /**
1653  * This function returns if a scale is being used by one forum
1654  *
1655  * @global object
1656  * @param int $forumid
1657  * @param int $scaleid negative number
1658  * @return bool
1659  */
1660 function forum_scale_used ($forumid,$scaleid) {
1661     global $DB;
1662     $return = false;
1664     $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1666     if (!empty($rec) && !empty($scaleid)) {
1667         $return = true;
1668     }
1670     return $return;
1673 /**
1674  * Checks if scale is being used by any instance of forum
1675  *
1676  * This is used to find out if scale used anywhere
1677  *
1678  * @global object
1679  * @param $scaleid int
1680  * @return boolean True if the scale is used by any forum
1681  */
1682 function forum_scale_used_anywhere($scaleid) {
1683     global $DB;
1684     if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1685         return true;
1686     } else {
1687         return false;
1688     }
1691 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1693 /**
1694  * Gets a post with all info ready for forum_print_post
1695  * Most of these joins are just to get the forum id
1696  *
1697  * @global object
1698  * @global object
1699  * @param int $postid
1700  * @return mixed array of posts or false
1701  */
1702 function forum_get_post_full($postid) {
1703     global $CFG, $DB;
1705     return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1706                              FROM {forum_posts} p
1707                                   JOIN {forum_discussions} d ON p.discussion = d.id
1708                                   LEFT JOIN {user} u ON p.userid = u.id
1709                             WHERE p.id = ?", array($postid));
1712 /**
1713  * Gets posts with all info ready for forum_print_post
1714  * We pass forumid in because we always know it so no need to make a
1715  * complicated join to find it out.
1716  *
1717  * @global object
1718  * @global object
1719  * @return mixed array of posts or false
1720  */
1721 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1722     global $CFG, $DB;
1724     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1725                               FROM {forum_posts} p
1726                          LEFT JOIN {user} u ON p.userid = u.id
1727                              WHERE p.discussion = ?
1728                                AND p.parent > 0 $sort", array($discussion));
1731 /**
1732  * Gets all posts in discussion including top parent.
1733  *
1734  * @global object
1735  * @global object
1736  * @global object
1737  * @param int $discussionid
1738  * @param string $sort
1739  * @param bool $tracking does user track the forum?
1740  * @return array of posts
1741  */
1742 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1743     global $CFG, $DB, $USER;
1745     $tr_sel  = "";
1746     $tr_join = "";
1747     $params = array();
1749     if ($tracking) {
1750         $now = time();
1751         $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1752         $tr_sel  = ", fr.id AS postread";
1753         $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1754         $params[] = $USER->id;
1755     }
1757     $params[] = $discussionid;
1758     if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1759                                      FROM {forum_posts} p
1760                                           LEFT JOIN {user} u ON p.userid = u.id
1761                                           $tr_join
1762                                     WHERE p.discussion = ?
1763                                  ORDER BY $sort", $params)) {
1764         return array();
1765     }
1767     foreach ($posts as $pid=>$p) {
1768         if ($tracking) {
1769             if (forum_tp_is_post_old($p)) {
1770                  $posts[$pid]->postread = true;
1771             }
1772         }
1773         if (!$p->parent) {
1774             continue;
1775         }
1776         if (!isset($posts[$p->parent])) {
1777             continue; // parent does not exist??
1778         }
1779         if (!isset($posts[$p->parent]->children)) {
1780             $posts[$p->parent]->children = array();
1781         }
1782         $posts[$p->parent]->children[$pid] =& $posts[$pid];
1783     }
1785     return $posts;
1788 /**
1789  * Gets posts with all info ready for forum_print_post
1790  * We pass forumid in because we always know it so no need to make a
1791  * complicated join to find it out.
1792  *
1793  * @global object
1794  * @global object
1795  * @param int $parent
1796  * @param int $forumid
1797  * @return array
1798  */
1799 function forum_get_child_posts($parent, $forumid) {
1800     global $CFG, $DB;
1802     return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1803                               FROM {forum_posts} p
1804                          LEFT JOIN {user} u ON p.userid = u.id
1805                              WHERE p.parent = ?
1806                           ORDER BY p.created ASC", array($parent));
1809 /**
1810  * An array of forum objects that the user is allowed to read/search through.
1811  *
1812  * @global object
1813  * @global object
1814  * @global object
1815  * @param int $userid
1816  * @param int $courseid if 0, we look for forums throughout the whole site.
1817  * @return array of forum objects, or false if no matches
1818  *         Forum objects have the following attributes:
1819  *         id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1820  *         viewhiddentimedposts
1821  */
1822 function forum_get_readable_forums($userid, $courseid=0) {
1824     global $CFG, $DB, $USER;
1825     require_once($CFG->dirroot.'/course/lib.php');
1827     if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1828         print_error('notinstalled', 'forum');
1829     }
1831     if ($courseid) {
1832         $courses = $DB->get_records('course', array('id' => $courseid));
1833     } else {
1834         // If no course is specified, then the user can see SITE + his courses.
1835         $courses1 = $DB->get_records('course', array('id' => SITEID));
1836         $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
1837         $courses = array_merge($courses1, $courses2);
1838     }
1839     if (!$courses) {
1840         return array();
1841     }
1843     $readableforums = array();
1845     foreach ($courses as $course) {
1847         $modinfo =& get_fast_modinfo($course);
1848         if (is_null($modinfo->groups)) {
1849             $modinfo->groups = groups_get_user_groups($course->id, $userid);
1850         }
1852         if (empty($modinfo->instances['forum'])) {
1853             // hmm, no forums?
1854             continue;
1855         }
1857         $courseforums = $DB->get_records('forum', array('course' => $course->id));
1859         foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1860             if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1861                 continue;
1862             }
1863             $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1864             $forum = $courseforums[$forumid];
1865             $forum->context = $context;
1866             $forum->cm = $cm;
1868             if (!has_capability('mod/forum:viewdiscussion', $context)) {
1869                 continue;
1870             }
1872          /// group access
1873             if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1874                 if (is_null($modinfo->groups)) {
1875                     $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1876                 }
1877                 if (isset($modinfo->groups[$cm->groupingid])) {
1878                     $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1879                     $forum->onlygroups[] = -1;
1880                 } else {
1881                     $forum->onlygroups = array(-1);
1882                 }
1883             }
1885         /// hidden timed discussions
1886             $forum->viewhiddentimedposts = true;
1887             if (!empty($CFG->forum_enabletimedposts)) {
1888                 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1889                     $forum->viewhiddentimedposts = false;
1890                 }
1891             }
1893         /// qanda access
1894             if ($forum->type == 'qanda'
1895                     && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1897                 // We need to check whether the user has posted in the qanda forum.
1898                 $forum->onlydiscussions = array();  // Holds discussion ids for the discussions
1899                                                     // the user is allowed to see in this forum.
1900                 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1901                     foreach ($discussionspostedin as $d) {
1902                         $forum->onlydiscussions[] = $d->id;
1903                     }
1904                 }
1905             }
1907             $readableforums[$forum->id] = $forum;
1908         }
1910         unset($modinfo);
1912     } // End foreach $courses
1914     return $readableforums;
1917 /**
1918  * Returns a list of posts found using an array of search terms.
1919  *
1920  * @global object
1921  * @global object
1922  * @global object
1923  * @param array $searchterms array of search terms, e.g. word +word -word
1924  * @param int $courseid if 0, we search through the whole site
1925  * @param int $limitfrom
1926  * @param int $limitnum
1927  * @param int &$totalcount
1928  * @param string $extrasql
1929  * @return array|bool Array of posts found or false
1930  */
1931 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1932                             &$totalcount, $extrasql='') {
1933     global $CFG, $DB, $USER;
1934     require_once($CFG->libdir.'/searchlib.php');
1936     $forums = forum_get_readable_forums($USER->id, $courseid);
1938     if (count($forums) == 0) {
1939         $totalcount = 0;
1940         return false;
1941     }
1943     $now = round(time(), -2); // db friendly
1945     $fullaccess = array();
1946     $where = array();
1947     $params = array();
1949     foreach ($forums as $forumid => $forum) {
1950         $select = array();
1952         if (!$forum->viewhiddentimedposts) {
1953             $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
1954             $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
1955         }
1957         $cm = $forum->cm;
1958         $context = $forum->context;
1960         if ($forum->type == 'qanda'
1961             && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1962             if (!empty($forum->onlydiscussions)) {
1963                 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
1964                 $params = array_merge($params, $discussionid_params);
1965                 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
1966             } else {
1967                 $select[] = "p.parent = 0";
1968             }
1969         }
1971         if (!empty($forum->onlygroups)) {
1972             list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
1973             $params = array_merge($params, $groupid_params);
1974             $select[] = "d.groupid $groupid_sql";
1975         }
1977         if ($select) {
1978             $selects = implode(" AND ", $select);
1979             $where[] = "(d.forum = :forum{$forumid} AND $selects)";
1980             $params['forum'.$forumid] = $forumid;
1981         } else {
1982             $fullaccess[] = $forumid;
1983         }
1984     }
1986     if ($fullaccess) {
1987         list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
1988         $params = array_merge($params, $fullid_params);
1989         $where[] = "(d.forum $fullid_sql)";
1990     }
1992     $selectdiscussion = "(".implode(" OR ", $where).")";
1994     $messagesearch = '';
1995     $searchstring = '';
1997     // Need to concat these back together for parser to work.
1998     foreach($searchterms as $searchterm){
1999         if ($searchstring != '') {
2000             $searchstring .= ' ';
2001         }
2002         $searchstring .= $searchterm;
2003     }
2005     // We need to allow quoted strings for the search. The quotes *should* be stripped
2006     // by the parser, but this should be examined carefully for security implications.
2007     $searchstring = str_replace("\\\"","\"",$searchstring);
2008     $parser = new search_parser();
2009     $lexer = new search_lexer($parser);
2011     if ($lexer->parse($searchstring)) {
2012         $parsearray = $parser->get_parsed_array();
2013     // Experimental feature under 1.8! MDL-8830
2014     // Use alternative text searches if defined
2015     // This feature only works under mysql until properly implemented for other DBs
2016     // Requires manual creation of text index for forum_posts before enabling it:
2017     // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2018     // Experimental feature under 1.8! MDL-8830
2019         if (!empty($CFG->forum_usetextsearches)) {
2020             list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2021                                                  'p.userid', 'u.id', 'u.firstname',
2022                                                  'u.lastname', 'p.modified', 'd.forum');
2023         } else {
2024             list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2025                                                  'p.userid', 'u.id', 'u.firstname',
2026                                                  'u.lastname', 'p.modified', 'd.forum');
2027         }
2028         $params = array_merge($params, $msparams);
2029     }
2031     $fromsql = "{forum_posts} p,
2032                   {forum_discussions} d,
2033                   {user} u";
2035     $selectsql = " $messagesearch
2036                AND p.discussion = d.id
2037                AND p.userid = u.id
2038                AND $selectdiscussion
2039                    $extrasql";
2041     $countsql = "SELECT COUNT(*)
2042                    FROM $fromsql
2043                   WHERE $selectsql";
2045     $searchsql = "SELECT p.*,
2046                          d.forum,
2047                          u.firstname,
2048                          u.lastname,
2049                          u.email,
2050                          u.picture,
2051                          u.imagealt,
2052                          u.email
2053                     FROM $fromsql
2054                    WHERE $selectsql
2055                 ORDER BY p.modified DESC";
2057     $totalcount = $DB->count_records_sql($countsql, $params);
2059     return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2062 /**
2063  * Returns a list of ratings for a particular post - sorted.
2064  *
2065  * TODO: Check if this function is actually used anywhere.
2066  * Up until the fix for MDL-27471 this function wasn't even returning.
2067  *
2068  * @param stdClass $context
2069  * @param int $postid
2070  * @param string $sort
2071  * @return array Array of ratings or false
2072  */
2073 function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
2074     $options = new stdClass;
2075     $options->context = $context;
2076     $options->component = 'mod_forum';
2077     $options->ratingarea = 'post';
2078     $options->itemid = $postid;
2079     $options->sort = "ORDER BY $sort";
2081     $rm = new rating_manager();
2082     return $rm->get_all_ratings_for_item($options);
2085 /**
2086  * Returns a list of all new posts that have not been mailed yet
2087  *
2088  * @param int $starttime posts created after this time
2089  * @param int $endtime posts created before this
2090  * @param int $now used for timed discussions only
2091  * @return array
2092  */
2093 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2094     global $CFG, $DB;
2096     $params = array($starttime, $endtime);
2097     if (!empty($CFG->forum_enabletimedposts)) {
2098         if (empty($now)) {
2099             $now = time();
2100         }
2101         $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2102         $params[] = $now;
2103         $params[] = $now;
2104     } else {
2105         $timedsql = "";
2106     }
2108     return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2109                               FROM {forum_posts} p
2110                                    JOIN {forum_discussions} d ON d.id = p.discussion
2111                              WHERE p.mailed = 0
2112                                    AND p.created >= ?
2113                                    AND (p.created < ? OR p.mailnow = 1)
2114                                    $timedsql
2115                           ORDER BY p.modified ASC", $params);
2118 /**
2119  * Marks posts before a certain time as being mailed already
2120  *
2121  * @global object
2122  * @global object
2123  * @param int $endtime
2124  * @param int $now Defaults to time()
2125  * @return bool
2126  */
2127 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2128     global $CFG, $DB;
2129     if (empty($now)) {
2130         $now = time();
2131     }
2133     if (empty($CFG->forum_enabletimedposts)) {
2134         return $DB->execute("UPDATE {forum_posts}
2135                                SET mailed = '1'
2136                              WHERE (created < ? OR mailnow = 1)
2137                                    AND mailed = 0", array($endtime));
2139     } else {
2140         return $DB->execute("UPDATE {forum_posts}
2141                                SET mailed = '1'
2142                              WHERE discussion NOT IN (SELECT d.id
2143                                                         FROM {forum_discussions} d
2144                                                        WHERE d.timestart > ?)
2145                                    AND (created < ? OR mailnow = 1)
2146                                    AND mailed = 0", array($now, $endtime));
2147     }
2150 /**
2151  * Get all the posts for a user in a forum suitable for forum_print_post
2152  *
2153  * @global object
2154  * @global object
2155  * @uses CONTEXT_MODULE
2156  * @return array
2157  */
2158 function forum_get_user_posts($forumid, $userid) {
2159     global $CFG, $DB;
2161     $timedsql = "";
2162     $params = array($forumid, $userid);
2164     if (!empty($CFG->forum_enabletimedposts)) {
2165         $cm = get_coursemodule_from_instance('forum', $forumid);
2166         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2167             $now = time();
2168             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2169             $params[] = $now;
2170             $params[] = $now;
2171         }
2172     }
2174     return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2175                               FROM {forum} f
2176                                    JOIN {forum_discussions} d ON d.forum = f.id
2177                                    JOIN {forum_posts} p       ON p.discussion = d.id
2178                                    JOIN {user} u              ON u.id = p.userid
2179                              WHERE f.id = ?
2180                                    AND p.userid = ?
2181                                    $timedsql
2182                           ORDER BY p.modified ASC", $params);
2185 /**
2186  * Get all the discussions user participated in
2187  *
2188  * @global object
2189  * @global object
2190  * @uses CONTEXT_MODULE
2191  * @param int $forumid
2192  * @param int $userid
2193  * @return array Array or false
2194  */
2195 function forum_get_user_involved_discussions($forumid, $userid) {
2196     global $CFG, $DB;
2198     $timedsql = "";
2199     $params = array($forumid, $userid);
2200     if (!empty($CFG->forum_enabletimedposts)) {
2201         $cm = get_coursemodule_from_instance('forum', $forumid);
2202         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2203             $now = time();
2204             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2205             $params[] = $now;
2206             $params[] = $now;
2207         }
2208     }
2210     return $DB->get_records_sql("SELECT DISTINCT d.*
2211                               FROM {forum} f
2212                                    JOIN {forum_discussions} d ON d.forum = f.id
2213                                    JOIN {forum_posts} p       ON p.discussion = d.id
2214                              WHERE f.id = ?
2215                                    AND p.userid = ?
2216                                    $timedsql", $params);
2219 /**
2220  * Get all the posts for a user in a forum suitable for forum_print_post
2221  *
2222  * @global object
2223  * @global object
2224  * @param int $forumid
2225  * @param int $userid
2226  * @return array of counts or false
2227  */
2228 function forum_count_user_posts($forumid, $userid) {
2229     global $CFG, $DB;
2231     $timedsql = "";
2232     $params = array($forumid, $userid);
2233     if (!empty($CFG->forum_enabletimedposts)) {
2234         $cm = get_coursemodule_from_instance('forum', $forumid);
2235         if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2236             $now = time();
2237             $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2238             $params[] = $now;
2239             $params[] = $now;
2240         }
2241     }
2243     return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2244                              FROM {forum} f
2245                                   JOIN {forum_discussions} d ON d.forum = f.id
2246                                   JOIN {forum_posts} p       ON p.discussion = d.id
2247                                   JOIN {user} u              ON u.id = p.userid
2248                             WHERE f.id = ?
2249                                   AND p.userid = ?
2250                                   $timedsql", $params);
2253 /**
2254  * Given a log entry, return the forum post details for it.
2255  *
2256  * @global object
2257  * @global object
2258  * @param object $log
2259  * @return array|null
2260  */
2261 function forum_get_post_from_log($log) {
2262     global $CFG, $DB;
2264     if ($log->action == "add post") {
2266         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2267                                            u.firstname, u.lastname, u.email, u.picture
2268                                  FROM {forum_discussions} d,
2269                                       {forum_posts} p,
2270                                       {forum} f,
2271                                       {user} u
2272                                 WHERE p.id = ?
2273                                   AND d.id = p.discussion
2274                                   AND p.userid = u.id
2275                                   AND u.deleted <> '1'
2276                                   AND f.id = d.forum", array($log->info));
2279     } else if ($log->action == "add discussion") {
2281         return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2282                                            u.firstname, u.lastname, u.email, u.picture
2283                                  FROM {forum_discussions} d,
2284                                       {forum_posts} p,
2285                                       {forum} f,
2286                                       {user} u
2287                                 WHERE d.id = ?
2288                                   AND d.firstpost = p.id
2289                                   AND p.userid = u.id
2290                                   AND u.deleted <> '1'
2291                                   AND f.id = d.forum", array($log->info));
2292     }
2293     return NULL;
2296 /**
2297  * Given a discussion id, return the first post from the discussion
2298  *
2299  * @global object
2300  * @global object
2301  * @param int $dicsussionid
2302  * @return array
2303  */
2304 function forum_get_firstpost_from_discussion($discussionid) {
2305     global $CFG, $DB;
2307     return $DB->get_record_sql("SELECT p.*
2308                              FROM {forum_discussions} d,
2309                                   {forum_posts} p
2310                             WHERE d.id = ?
2311                               AND d.firstpost = p.id ", array($discussionid));
2314 /**
2315  * Returns an array of counts of replies to each discussion
2316  *
2317  * @global object
2318  * @global object
2319  * @param int $forumid
2320  * @param string $forumsort
2321  * @param int $limit
2322  * @param int $page
2323  * @param int $perpage
2324  * @return array
2325  */
2326 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2327     global $CFG, $DB;
2329     if ($limit > 0) {
2330         $limitfrom = 0;
2331         $limitnum  = $limit;
2332     } else if ($page != -1) {
2333         $limitfrom = $page*$perpage;
2334         $limitnum  = $perpage;
2335     } else {
2336         $limitfrom = 0;
2337         $limitnum  = 0;
2338     }
2340     if ($forumsort == "") {
2341         $orderby = "";
2342         $groupby = "";
2344     } else {
2345         $orderby = "ORDER BY $forumsort";
2346         $groupby = ", ".strtolower($forumsort);
2347         $groupby = str_replace('desc', '', $groupby);
2348         $groupby = str_replace('asc', '', $groupby);
2349     }
2351     if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2352         $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2353                   FROM {forum_posts} p
2354                        JOIN {forum_discussions} d ON p.discussion = d.id
2355                  WHERE p.parent > 0 AND d.forum = ?
2356               GROUP BY p.discussion";
2357         return $DB->get_records_sql($sql, array($forumid));
2359     } else {
2360         $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2361                   FROM {forum_posts} p
2362                        JOIN {forum_discussions} d ON p.discussion = d.id
2363                  WHERE d.forum = ?
2364               GROUP BY p.discussion $groupby
2365               $orderby";
2366         return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2367     }
2370 /**
2371  * @global object
2372  * @global object
2373  * @global object
2374  * @staticvar array $cache
2375  * @param object $forum
2376  * @param object $cm
2377  * @param object $course
2378  * @return mixed
2379  */
2380 function forum_count_discussions($forum, $cm, $course) {
2381     global $CFG, $DB, $USER;
2383     static $cache = array();
2385     $now = round(time(), -2); // db cache friendliness
2387     $params = array($course->id);
2389     if (!isset($cache[$course->id])) {
2390         if (!empty($CFG->forum_enabletimedposts)) {
2391             $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2392             $params[] = $now;
2393             $params[] = $now;
2394         } else {
2395             $timedsql = "";
2396         }
2398         $sql = "SELECT f.id, COUNT(d.id) as dcount
2399                   FROM {forum} f
2400                        JOIN {forum_discussions} d ON d.forum = f.id
2401                  WHERE f.course = ?
2402                        $timedsql
2403               GROUP BY f.id";
2405         if ($counts = $DB->get_records_sql($sql, $params)) {
2406             foreach ($counts as $count) {
2407                 $counts[$count->id] = $count->dcount;
2408             }
2409             $cache[$course->id] = $counts;
2410         } else {
2411             $cache[$course->id] = array();
2412         }
2413     }
2415     if (empty($cache[$course->id][$forum->id])) {
2416         return 0;
2417     }
2419     $groupmode = groups_get_activity_groupmode($cm, $course);
2421     if ($groupmode != SEPARATEGROUPS) {
2422         return $cache[$course->id][$forum->id];
2423     }
2425     if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2426         return $cache[$course->id][$forum->id];
2427     }
2429     require_once($CFG->dirroot.'/course/lib.php');
2431     $modinfo =& get_fast_modinfo($course);
2432     if (is_null($modinfo->groups)) {
2433         $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2434     }
2436     if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2437         $mygroups = $modinfo->groups[$cm->groupingid];
2438     } else {
2439         $mygroups = false; // Will be set below
2440     }
2442     // add all groups posts
2443     if (empty($mygroups)) {
2444         $mygroups = array(-1=>-1);
2445     } else {
2446         $mygroups[-1] = -1;
2447     }
2449     list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2450     $params[] = $forum->id;
2452     if (!empty($CFG->forum_enabletimedposts)) {
2453         $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2454         $params[] = $now;
2455         $params[] = $now;
2456     } else {
2457         $timedsql = "";
2458     }
2460     $sql = "SELECT COUNT(d.id)
2461               FROM {forum_discussions} d
2462              WHERE d.groupid $mygroups_sql AND d.forum = ?
2463                    $timedsql";
2465     return $DB->get_field_sql($sql, $params);
2468 /**
2469  * How many posts by other users are unrated by a given user in the given discussion?
2470  *
2471  * TODO: Is this function still used anywhere?
2472  *
2473  * @param int $discussionid
2474  * @param int $userid
2475  * @return mixed
2476  */
2477 function forum_count_unrated_posts($discussionid, $userid) {
2478     global $CFG, $DB;
2480     $sql = "SELECT COUNT(*) as num
2481               FROM {forum_posts}
2482              WHERE parent > 0
2483                AND discussion = :discussionid
2484                AND userid <> :userid";
2485     $params = array('discussionid' => $discussionid, 'userid' => $userid);
2486     $posts = $DB->get_record_sql($sql, $params);
2487     if ($posts) {
2488         $sql = "SELECT count(*) as num
2489                   FROM {forum_posts} p,
2490                        {rating} r
2491                  WHERE p.discussion = :discussionid AND
2492                        p.id = r.itemid AND
2493                        r.userid = userid AND
2494                        r.component = 'mod_forum' AND
2495                        r.ratingarea = 'post'";
2496         $rated = $DB->get_record_sql($sql, $params);
2497         if ($rated) {
2498             if ($posts->num > $rated->num) {
2499                 return $posts->num - $rated->num;
2500             } else {
2501                 return 0;    // Just in case there was a counting error
2502             }
2503         } else {
2504             return $posts->num;
2505         }
2506     } else {
2507         return 0;
2508     }
2511 /**
2512  * Get all discussions in a forum
2513  *
2514  * @global object
2515  * @global object
2516  * @global object
2517  * @uses CONTEXT_MODULE
2518  * @uses VISIBLEGROUPS
2519  * @param object $cm
2520  * @param string $forumsort
2521  * @param bool $fullpost
2522  * @param int $unused
2523  * @param int $limit
2524  * @param bool $userlastmodified
2525  * @param int $page
2526  * @param int $perpage
2527  * @return array
2528  */
2529 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2530     global $CFG, $DB, $USER;
2532     $timelimit = '';
2534     $now = round(time(), -2);
2535     $params = array($cm->instance);
2537     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2539     if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2540         return array();
2541     }
2543     if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2545         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2546             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2547             $params[] = $now;
2548             $params[] = $now;
2549             if (isloggedin()) {
2550                 $timelimit .= " OR d.userid = ?";
2551                 $params[] = $USER->id;
2552             }
2553             $timelimit .= ")";
2554         }
2555     }
2557     if ($limit > 0) {
2558         $limitfrom = 0;
2559         $limitnum  = $limit;
2560     } else if ($page != -1) {
2561         $limitfrom = $page*$perpage;
2562         $limitnum  = $perpage;
2563     } else {
2564         $limitfrom = 0;
2565         $limitnum  = 0;
2566     }
2568     $groupmode    = groups_get_activity_groupmode($cm);
2569     $currentgroup = groups_get_activity_group($cm);
2571     if ($groupmode) {
2572         if (empty($modcontext)) {
2573             $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2574         }
2576         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2577             if ($currentgroup) {
2578                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2579                 $params[] = $currentgroup;
2580             } else {
2581                 $groupselect = "";
2582             }
2584         } else {
2585             //seprate groups without access all
2586             if ($currentgroup) {
2587                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2588                 $params[] = $currentgroup;
2589             } else {
2590                 $groupselect = "AND d.groupid = -1";
2591             }
2592         }
2593     } else {
2594         $groupselect = "";
2595     }
2598     if (empty($forumsort)) {
2599         $forumsort = "d.timemodified DESC";
2600     }
2601     if (empty($fullpost)) {
2602         $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2603     } else {
2604         $postdata = "p.*";
2605     }
2607     if (empty($userlastmodified)) {  // We don't need to know this
2608         $umfields = "";
2609         $umtable  = "";
2610     } else {
2611         $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2612         $umtable  = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2613     }
2615     $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2616                    u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2617               FROM {forum_discussions} d
2618                    JOIN {forum_posts} p ON p.discussion = d.id
2619                    JOIN {user} u ON p.userid = u.id
2620                    $umtable
2621              WHERE d.forum = ? AND p.parent = 0
2622                    $timelimit $groupselect
2623           ORDER BY $forumsort";
2624     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2627 /**
2628  *
2629  * @global object
2630  * @global object
2631  * @global object
2632  * @uses CONTEXT_MODULE
2633  * @uses VISIBLEGROUPS
2634  * @param object $cm
2635  * @return array
2636  */
2637 function forum_get_discussions_unread($cm) {
2638     global $CFG, $DB, $USER;
2640     $now = round(time(), -2);
2641     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2643     $params = array();
2644     $groupmode    = groups_get_activity_groupmode($cm);
2645     $currentgroup = groups_get_activity_group($cm);
2647     if ($groupmode) {
2648         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2650         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2651             if ($currentgroup) {
2652                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2653                 $params['currentgroup'] = $currentgroup;
2654             } else {
2655                 $groupselect = "";
2656             }
2658         } else {
2659             //separate groups without access all
2660             if ($currentgroup) {
2661                 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2662                 $params['currentgroup'] = $currentgroup;
2663             } else {
2664                 $groupselect = "AND d.groupid = -1";
2665             }
2666         }
2667     } else {
2668         $groupselect = "";
2669     }
2671     if (!empty($CFG->forum_enabletimedposts)) {
2672         $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2673         $params['now1'] = $now;
2674         $params['now2'] = $now;
2675     } else {
2676         $timedsql = "";
2677     }
2679     $sql = "SELECT d.id, COUNT(p.id) AS unread
2680               FROM {forum_discussions} d
2681                    JOIN {forum_posts} p     ON p.discussion = d.id
2682                    LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2683              WHERE d.forum = {$cm->instance}
2684                    AND p.modified >= :cutoffdate AND r.id is NULL
2685                    $groupselect
2686                    $timedsql
2687           GROUP BY d.id";
2688     $params['cutoffdate'] = $cutoffdate;
2690     if ($unreads = $DB->get_records_sql($sql, $params)) {
2691         foreach ($unreads as $unread) {
2692             $unreads[$unread->id] = $unread->unread;
2693         }
2694         return $unreads;
2695     } else {
2696         return array();
2697     }
2700 /**
2701  * @global object
2702  * @global object
2703  * @global object
2704  * @uses CONEXT_MODULE
2705  * @uses VISIBLEGROUPS
2706  * @param object $cm
2707  * @return array
2708  */
2709 function forum_get_discussions_count($cm) {
2710     global $CFG, $DB, $USER;
2712     $now = round(time(), -2);
2713     $params = array($cm->instance);
2714     $groupmode    = groups_get_activity_groupmode($cm);
2715     $currentgroup = groups_get_activity_group($cm);
2717     if ($groupmode) {
2718         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2720         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2721             if ($currentgroup) {
2722                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2723                 $params[] = $currentgroup;
2724             } else {
2725                 $groupselect = "";
2726             }
2728         } else {
2729             //seprate groups without access all
2730             if ($currentgroup) {
2731                 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2732                 $params[] = $currentgroup;
2733             } else {
2734                 $groupselect = "AND d.groupid = -1";
2735             }
2736         }
2737     } else {
2738         $groupselect = "";
2739     }
2741     $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2743     $timelimit = "";
2745     if (!empty($CFG->forum_enabletimedposts)) {
2747         $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2749         if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2750             $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2751             $params[] = $now;
2752             $params[] = $now;
2753             if (isloggedin()) {
2754                 $timelimit .= " OR d.userid = ?";
2755                 $params[] = $USER->id;
2756             }
2757             $timelimit .= ")";
2758         }
2759     }
2761     $sql = "SELECT COUNT(d.id)
2762               FROM {forum_discussions} d
2763                    JOIN {forum_posts} p ON p.discussion = d.id
2764              WHERE d.forum = ? AND p.parent = 0
2765                    $groupselect $timelimit";
2767     return $DB->get_field_sql($sql, $params);
2771 /**
2772  * Get all discussions started by a particular user in a course (or group)
2773  * This function no longer used ...
2774  *
2775  * @todo Remove this function if no longer used
2776  * @global object
2777  * @global object
2778  * @param int $courseid
2779  * @param int $userid
2780  * @param int $groupid
2781  * @return array
2782  */
2783 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2784     global $CFG, $DB;
2785     $params = array($courseid, $userid);
2786     if ($groupid) {
2787         $groupselect = " AND d.groupid = ? ";
2788         $params[] = $groupid;
2789     } else  {
2790         $groupselect = "";
2791     }
2793     return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2794                                    f.type as forumtype, f.name as forumname, f.id as forumid
2795                               FROM {forum_discussions} d,
2796                                    {forum_posts} p,
2797                                    {user} u,
2798                                    {forum} f
2799                              WHERE d.course = ?
2800                                AND p.discussion = d.id
2801                                AND p.parent = 0
2802                                AND p.userid = u.id
2803                                AND u.id = ?
2804                                AND d.forum = f.id $groupselect
2805                           ORDER BY p.created DESC", $params);
2808 /**
2809  * Get the list of potential subscribers to a forum.
2810  *
2811  * @param object $forumcontext the forum context.
2812  * @param integer $groupid the id of a group, or 0 for all groups.
2813  * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2814  * @param string $sort sort order. As for get_users_by_capability.
2815  * @return array list of users.
2816  */
2817 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2818     global $DB;
2820     // only active enrolled users or everybody on the frontpage
2821     list($esql, $params) = get_enrolled_sql($forumcontext, '', $groupid, true);
2823     $sql = "SELECT $fields
2824               FROM {user} u
2825               JOIN ($esql) je ON je.id = u.id";
2826     if ($sort) {
2827         $sql = "$sql ORDER BY $sort";
2828     } else {
2829         $sql = "$sql ORDER BY u.lastname ASC, u.firstname ASC";
2830     }
2832     return $DB->get_records_sql($sql, $params);
2835 /**
2836  * Returns list of user objects that are subscribed to this forum
2837  *
2838  * @global object
2839  * @global object
2840  * @param object $course the course
2841  * @param forum $forum the forum
2842  * @param integer $groupid group id, or 0 for all.
2843  * @param object $context the forum context, to save re-fetching it where possible.
2844  * @param string $fields requested user fields (with "u." table prefix)
2845  * @return array list of users.
2846  */
2847 function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
2848     global $CFG, $DB;
2850     if (empty($fields)) {
2851         $fields ="u.id,
2852                   u.username,
2853                   u.firstname,
2854                   u.lastname,
2855                   u.maildisplay,
2856                   u.mailformat,
2857                   u.maildigest,
2858                   u.imagealt,
2859                   u.email,
2860                   u.city,
2861                   u.country,
2862                   u.lastaccess,
2863                   u.lastlogin,
2864                   u.picture,
2865                   u.timezone,
2866                   u.theme,
2867                   u.lang,
2868                   u.trackforums,
2869                   u.mnethostid";
2870     }
2872     if (empty($context)) {
2873         $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2874         $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2875     }
2877     if (forum_is_forcesubscribed($forum)) {
2878         $results = forum_get_potential_subscribers($context, $groupid, $fields, "u.email ASC");
2880     } else {
2881         // only active enrolled users or everybody on the frontpage
2882         list($esql, $params) = get_enrolled_sql($context, '', $groupid, true);
2883         $params['forumid'] = $forum->id;
2884         $results = $DB->get_records_sql("SELECT $fields
2885                                            FROM {user} u
2886                                            JOIN ($esql) je ON je.id = u.id
2887                                            JOIN {forum_subscriptions} s ON s.userid = u.id
2888                                           WHERE s.forum = :forumid
2889                                        ORDER BY u.email ASC", $params);
2890     }
2892     // Guest user should never be subscribed to a forum.
2893     unset($results[$CFG->siteguest]);
2895     return $results;
2900 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2903 /**
2904  * @global object
2905  * @global object
2906  * @param int $courseid
2907  * @param string $type
2908  */
2909 function forum_get_course_forum($courseid, $type) {
2910 // How to set up special 1-per-course forums
2911     global $CFG, $DB, $OUTPUT;
2913     if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2914         // There should always only be ONE, but with the right combination of
2915         // errors there might be more.  In this case, just return the oldest one (lowest ID).
2916         foreach ($forums as $forum) {
2917             return $forum;   // ie the first one
2918         }
2919     }
2921     // Doesn't exist, so create one now.
2922     $forum->course = $courseid;
2923     $forum->type = "$type";
2924     switch ($forum->type) {
2925         case "news":
2926             $forum->name  = get_string("namenews", "forum");
2927             $forum->intro = get_string("intronews", "forum");
2928             $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2929             $forum->assessed = 0;
2930             if ($courseid == SITEID) {
2931                 $forum->name  = get_string("sitenews");
2932                 $forum->forcesubscribe = 0;
2933             }
2934             break;
2935         case "social":
2936             $forum->name  = get_string("namesocial", "forum");
2937             $forum->intro = get_string("introsocial", "forum");
2938             $forum->assessed = 0;
2939             $forum->forcesubscribe = 0;
2940             break;
2941         case "blog":
2942             $forum->name = get_string('blogforum', 'forum');
2943             $forum->intro = get_string('introblog', 'forum');
2944             $forum->assessed = 0;
2945             $forum->forcesubscribe = 0;
2946             break;
2947         default:
2948             echo $OUTPUT->notification("That forum type doesn't exist!");
2949             return false;
2950             break;
2951     }
2953     $forum->timemodified = time();
2954     $forum->id = $DB->insert_record("forum", $forum);
2956     if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2957         echo $OUTPUT->notification("Could not find forum module!!");
2958         return false;
2959     }
2960     $mod = new stdClass();
2961     $mod->course = $courseid;
2962     $mod->module = $module->id;
2963     $mod->instance = $forum->id;
2964     $mod->section = 0;
2965     if (! $mod->coursemodule = add_course_module($mod) ) {   // assumes course/lib.php is loaded
2966         echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
2967         return false;
2968     }
2969     if (! $sectionid = add_mod_to_section($mod) ) {   // assumes course/lib.php is loaded
2970         echo $OUTPUT->notification("Could not add the new course module to that section");
2971         return false;
2972     }
2973     $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule));
2975     include_once("$CFG->dirroot/course/lib.php");
2976     rebuild_course_cache($courseid);
2978     return $DB->get_record("forum", array("id" => "$forum->id"));
2982 /**
2983  * Given the data about a posting, builds up the HTML to display it and
2984  * returns the HTML in a string.  This is designed for sending via HTML email.
2985  *
2986  * @global object
2987  * @param object $course
2988  * @param object $cm
2989  * @param object $forum
2990  * @param object $discussion
2991  * @param object $post
2992  * @param object $userform
2993  * @param object $userto
2994  * @param bool $ownpost
2995  * @param bool $reply
2996  * @param bool $link
2997  * @param bool $rate
2998  * @param string $footer
2999  * @return string
3000  */
3001 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
3002                               $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
3004     global $CFG, $OUTPUT;
3006     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3008     if (!isset($userto->viewfullnames[$forum->id])) {
3009         $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3010     } else {
3011         $viewfullnames = $userto->viewfullnames[$forum->id];
3012     }
3014     // add absolute file links
3015     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3017     // format the post body
3018     $options = new stdClass();
3019     $options->para = true;
3020     $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3022     $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3024     $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3025     $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3026     $output .= '</td>';
3028     if ($post->parent) {
3029         $output .= '<td class="topic">';
3030     } else {
3031         $output .= '<td class="topic starter">';
3032     }
3033     $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3035     $fullname = fullname($userfrom, $viewfullnames);
3036     $by = new stdClass();
3037     $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3038     $by->date = userdate($post->modified, '', $userto->timezone);
3039     $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3041     $output .= '</td></tr>';
3043     $output .= '<tr><td class="left side" valign="top">';
3045     if (isset($userfrom->groups)) {
3046         $groups = $userfrom->groups[$forum->id];
3047     } else {
3048         $groups = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3049     }
3051     if ($groups) {
3052         $output .= print_group_picture($groups, $course->id, false, true, true);
3053     } else {
3054         $output .= '&nbsp;';
3055     }
3057     $output .= '</td><td class="content">';
3059     $attachments = forum_print_attachments($post, $cm, 'html');
3060     if ($attachments !== '') {
3061         $output .= '<div class="attachments">';
3062         $output .= $attachments;
3063         $output .= '</div>';
3064     }
3066     $output .= $formattedtext;
3068 // Commands
3069     $commands = array();
3071     if ($post->parent) {
3072         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3073                       $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3074     }
3076     if ($reply) {
3077         $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3078                       get_string('reply', 'forum').'</a>';
3079     }
3081     $output .= '<div class="commands">';
3082     $output .= implode(' | ', $commands);
3083     $output .= '</div>';
3085 // Context link to post if required
3086     if ($link) {
3087         $output .= '<div class="link">';
3088         $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3089                      get_string('postincontext', 'forum').'</a>';
3090         $output .= '</div>';
3091     }
3093     if ($footer) {
3094         $output .= '<div class="footer">'.$footer.'</div>';
3095     }
3096     $output .= '</td></tr></table>'."\n\n";
3098     return $output;
3101 /**
3102  * Print a forum post
3103  *
3104  * @global object
3105  * @global object
3106  * @uses FORUM_MODE_THREADED
3107  * @uses PORTFOLIO_FORMAT_PLAINHTML
3108  * @uses PORTFOLIO_FORMAT_FILE
3109  * @uses PORTFOLIO_FORMAT_RICHHTML
3110  * @uses PORTFOLIO_ADD_TEXT_LINK
3111  * @uses CONTEXT_MODULE
3112  * @param object $post The post to print.
3113  * @param object $discussion
3114  * @param object $forum
3115  * @param object $cm
3116  * @param object $course
3117  * @param boolean $ownpost Whether this post belongs to the current user.
3118  * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3119  * @param boolean $link Just print a shortened version of the post as a link to the full post.
3120  * @param string $footer Extra stuff to print after the message.
3121  * @param string $highlight Space-separated list of terms to highlight.
3122  * @param int $post_read true, false or -99. If we already know whether this user
3123  *          has read this post, pass that in, otherwise, pass in -99, and this
3124  *          function will work it out.
3125  * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3126  *          the current user can't see this post, if this argument is true
3127  *          (the default) then print a dummy 'you can't see this post' post.
3128  *          If false, don't output anything at all.
3129  * @param bool|null $istracked
3130  * @return void
3131  */
3132 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3133                           $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3134     global $USER, $CFG, $OUTPUT;
3136     require_once($CFG->libdir . '/filelib.php');
3138     // String cache
3139     static $str;
3141     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3143     $post->course = $course->id;
3144     $post->forum  = $forum->id;
3145     $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3147     // caching
3148     if (!isset($cm->cache)) {
3149         $cm->cache = new stdClass;
3150     }
3152     if (!isset($cm->cache->caps)) {
3153         $cm->cache->caps = array();
3154         $cm->cache->caps['mod/forum:viewdiscussion']   = has_capability('mod/forum:viewdiscussion', $modcontext);
3155         $cm->cache->caps['moodle/site:viewfullnames']  = has_capability('moodle/site:viewfullnames', $modcontext);
3156         $cm->cache->caps['mod/forum:editanypost']      = has_capability('mod/forum:editanypost', $modcontext);
3157         $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3158         $cm->cache->caps['mod/forum:deleteownpost']    = has_capability('mod/forum:deleteownpost', $modcontext);
3159         $cm->cache->caps['mod/forum:deleteanypost']    = has_capability('mod/forum:deleteanypost', $modcontext);
3160         $cm->cache->caps['mod/forum:viewanyrating']    = has_capability('mod/forum:viewanyrating', $modcontext);
3161         $cm->cache->caps['mod/forum:exportpost']       = has_capability('mod/forum:exportpost', $modcontext);
3162         $cm->cache->caps['mod/forum:exportownpost']    = has_capability('mod/forum:exportownpost', $modcontext);
3163     }
3165     if (!isset($cm->uservisible)) {
3166         $cm->uservisible = coursemodule_visible_for_user($cm);
3167     }
3169     if ($istracked && is_null($postisread)) {
3170         $postisread = forum_tp_is_post_read($USER->id, $post);
3171     }
3173     if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3174         $output = '';
3175         if (!$dummyifcantsee) {
3176             if ($return) {
3177                 return $output;
3178             }
3179             echo $output;
3180             return;
3181         }
3182         $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3183         $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'));
3184         $output .= html_writer::start_tag('div', array('class'=>'row header'));
3185         $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3186         if ($post->parent) {
3187             $output .= html_writer::start_tag('div', array('class'=>'topic'));
3188         } else {
3189             $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3190         }
3191         $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class'=>'subject')); // Subject
3192         $output .= html_writer::tag('div', get_string('forumauthorhidden','forum'), array('class'=>'author')); // author
3193         $output .= html_writer::end_tag('div');
3194         $output .= html_writer::end_tag('div'); // row
3195         $output .= html_writer::start_tag('div', array('class'=>'row'));
3196         $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3197         $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3198         $output .= html_writer::end_tag('div'); // row
3199         $output .= html_writer::end_tag('div'); // forumpost
3201         if ($return) {
3202             return $output;
3203         }
3204         echo $output;
3205         return;
3206     }
3208     if (empty($str)) {
3209         $str = new stdClass;
3210         $str->edit         = get_string('edit', 'forum');
3211         $str->delete       = get_string('delete', 'forum');
3212         $str->reply        = get_string('reply', 'forum');
3213         $str->parent       = get_string('parent', 'forum');
3214         $str->pruneheading = get_string('pruneheading', 'forum');
3215         $str->prune        = get_string('prune', 'forum');
3216         $str->displaymode     = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3217         $str->markread     = get_string('markread', 'forum');
3218         $str->markunread   = get_string('markunread', 'forum');
3219     }
3221     $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3223     // Build an object that represents the posting user
3224     $postuser = new stdClass;
3225     $postuser->id        = $post->userid;
3226     $postuser->firstname = $post->firstname;
3227     $postuser->lastname  = $post->lastname;
3228     $postuser->imagealt  = $post->imagealt;
3229     $postuser->picture   = $post->picture;
3230     $postuser->email     = $post->email;
3231     // Some handy things for later on
3232     $postuser->fullname    = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3233     $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3235     // Prepare the groups the posting user belongs to
3236     if (isset($cm->cache->usersgroups)) {
3237         $groups = array();
3238         if (isset($cm->cache->usersgroups[$post->userid])) {
3239             foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3240                 $groups[$gid] = $cm->cache->groups[$gid];
3241             }
3242         }
3243     } else {
3244         $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3245     }
3247     // Prepare the attachements for the post, files then images
3248     list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3250     // Determine if we need to shorten this post
3251     $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3254     // Prepare an array of commands
3255     $commands = array();
3257     // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3258     // Don't display the mark read / unread controls in this case.
3259     if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3260         $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3261         $text = $str->markunread;
3262         if (!$postisread) {
3263             $url->param('mark', 'read');
3264             $text = $str->markread;
3265         }
3266         if ($str->displaymode == FORUM_MODE_THREADED) {
3267             $url->param('parent', $post->parent);
3268         } else {
3269             $url->set_anchor('p'.$post->id);
3270         }
3271         $commands[] = array('url'=>$url, 'text'=>$text);
3272     }
3274     // Zoom in to the parent specifically
3275     if ($post->parent) {
3276         $url = new moodle_url($discussionlink);
3277         if ($str->displaymode == FORUM_MODE_THREADED) {
3278             $url->param('parent', $post->parent);
3279         } else {
3280             $url->set_anchor('p'.$post->parent);
3281         }
3282         $commands[] = array('url'=>$url, 'text'=>$str->parent);
3283     }
3285     // Hack for allow to edit news posts those are not displayed yet until they are displayed
3286     $age = time() - $post->created;
3287     if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3288         $age = 0;
3289     }
3290     if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3291         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3292     }
3294     if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3295         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3296     }
3298     if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3299         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3300     }
3302     if ($reply) {
3303         $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('reply'=>$post->id)), 'text'=>$str->reply);
3304     }
3306     if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
3307         $p = array('postid' => $post->id);
3308         require_once($CFG->libdir.'/portfoliolib.php');
3309         $button = new portfolio_add_button();
3310         $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php');
3311         if (empty($attachments)) {
3312             $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3313         } else {
3314             $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3315         }
3317         $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3318         if (!empty($porfoliohtml)) {
3319             $commands[] = $porfoliohtml;
3320         }
3321     }
3322     // Finished building commands
3325     // Begin output
3327     $output  = '';
3329     if ($istracked) {
3330         if ($postisread) {
3331             $forumpostclass = ' read';
3332         } else {
3333             $forumpostclass = ' unread';
3334             $output .= html_writer::tag('a', '', array('name'=>'unread'));
3335         }
3336     } else {
3337         // ignore trackign status if not tracked or tracked param missing
3338         $forumpostclass = '';
3339     }
3341     $topicclass = '';
3342     if (empty($post->parent)) {
3343         $topicclass = ' firstpost starter';
3344     }
3346     $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3347     $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass));
3348     $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3349     $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3350     $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3351     $output .= html_writer::end_tag('div');
3354     $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3356     $postsubject = $post->subject;
3357     if (empty($post->subjectnoformat)) {
3358         $postsubject = format_string($postsubject);
3359     }
3360     $output .= html_writer::tag('div', $postsubject, array('class'=>'subject'));
3362     $by = new stdClass();
3363     $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3364     $by->date = userdate($post->modified);
3365     $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author'));
3367     $output .= html_writer::end_tag('div'); //topic
3368     $output .= html_writer::end_tag('div'); //row
3370     $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3371     $output .= html_writer::start_tag('div', array('class'=>'left'));
3373     $groupoutput = '';
3374     if ($groups) {
3375         $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3376     }
3377     if (empty($groupoutput)) {
3378         $groupoutput = '&nbsp;';
3379     }
3380     $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3382     $output .= html_writer::end_tag('div'); //left side
3383     $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3384     $output .= html_writer::start_tag('div', array('class'=>'content'));
3385     if (!empty($attachments)) {
3386         $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3387     }
3389     $options = new stdClass;
3390     $options->para    = false;
3391     $options->trusted = $post->messagetrust;
3392     $options->context = $modcontext;
3393     if ($shortenpost) {
3394         // Prepare shortened version
3395         $postclass    = 'shortenedpost';
3396         $postcontent  = format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3397         $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
3398         $postcontent .= html_writer::tag('span', '('.get_string('numwords', 'moodle', count_words(strip_tags($post->message))).')...', array('class'=>'post-word-count'));
3399     } else {
3400         // Prepare whole post
3401         $postclass    = 'fullpost';
3402         $postcontent  = format_text($post->message, $post->messageformat, $options, $course->id);
3403         if (!empty($highlight)) {
3404             $postcontent = highlight($highlight, $postcontent);
3405         }
3406         $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
3407     }
3408     // Output the post content
3409     $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
3410     $output .= html_writer::end_tag('div'); // Content
3411     $output .= html_writer::end_tag('div'); // Content mask
3412     $output .= html_writer::end_tag('div'); // Row
3414     $output .= html_writer::start_tag('div', array('class'=>'row side'));
3415     $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
3416     $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
3418     // Output ratings
3419     if (!empty($post->rating)) {
3420         $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
3421     }
3423     // Output the commands
3424     $commandhtml = array();
3425     foreach ($commands as $command) {
3426         if (is_array($command)) {
3427             $commandhtml[] = html_writer::link($command['url'], $command['text']);
3428         } else {
3429             $commandhtml[] = $command;
3430         }
3431     }
3432     $output .= html_writer::tag('div', implode(' | ', $commandhtml), array('class'=>'commands'));
3434     // Output link to post if required
3435     if ($link) {
3436         if ($post->replies == 1) {
3437             $replystring = get_string('repliesone', 'forum', $post->replies);
3438         } else {
3439             $replystring = get_string('repliesmany', 'forum', $post->replies);
3440         }
3442         $output .= html_writer::start_tag('div', array('class'=>'link'));
3443         $output .= html_writer::link($discussionlink, get_string('discussthistopic', 'forum'));
3444         $output .= '&nbsp;('.$replystring.')';
3445         $output .= html_writer::end_tag('div'); // link
3446     }
3448     // Output footer if required
3449     if ($footer) {
3450         $output .= html_writer::tag('div', $footer, array('class'=>'footer'));
3451     }
3453     // Close remaining open divs
3454     $output .= html_writer::end_tag('div'); // content
3455     $output .= html_writer::end_tag('div'); // row
3456     $output .= html_writer::end_tag('div'); // forumpost
3458     // Mark the forum post as read if required
3459     if ($istracked && !$CFG->forum_usermarksread && !$postisread) {
3460         forum_tp_mark_post_read($USER->id, $post, $forum->id);
3461     }
3463     if ($return) {
3464         return $output;
3465     }
3466     echo $output;
3467     return;
3470 /**
3471  * Return rating related permissions
3472  *
3473  * @param string $options the context id
3474  * @return array an associative array of the user's rating permissions
3475  */
3476 function forum_rating_permissions($contextid, $component, $ratingarea) {
3477     $context = get_context_instance_by_id($contextid, MUST_EXIST);
3478     if ($component != 'mod_forum' || $ratingarea != 'post') {
3479         // We don't know about this component/ratingarea so just return null to get the
3480         // default restrictive permissions.
3481         return null;
3482     }
3483     return array(
3484         'view'    => has_capability('mod/forum:viewrating', $context),
3485         'viewany' => has_capability('mod/forum:viewanyrating', $context),
3486         'viewall' => has_capability('mod/forum:viewallratings', $context),
3487         'rate'    => has_capability('mod/forum:rate', $context)
3488     );
3491 /**
3492  * Validates a submitted rating
3493  * @param array $params submitted data
3494  *            context => object the context in which the rated items exists [required]
3495  *            component => The component for this module - should always be mod_forum [required]
3496  *            ratingarea => object the context in which the rated items exists [required]
3497  *            itemid => int the ID of the object being rated [required]
3498  *            scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
3499  *            rating => int the submitted rating [required]
3500  *            rateduserid => int the id of the user whose items have been rated. NOT the user who submitted the ratings. 0 to update all. [required]
3501  *            aggregation => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [required]
3502  * @return boolean true if the rating is valid. Will throw rating_exception if not
3503  */