Merge branch 'MDL-26647-master' of git://github.com/sammarshallou/moodle
[moodle.git] / mod / forum / lib.php
bloba3f28f1a395b7766aad8b69a4a4ff906951f379c
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.
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
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.
55 * @global object
56 * @global object
57 * @param object $forum add forum instance (with magic quotes)
58 * @return int intance id
60 function forum_add_instance($forum, $mform) {
61 global $CFG, $DB;
63 $forum->timemodified = time();
65 if (empty($forum->assessed)) {
66 $forum->assessed = 0;
69 if (empty($forum->ratingtime) or empty($forum->assessed)) {
70 $forum->assesstimestart = 0;
71 $forum->assesstimefinish = 0;
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));
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);
116 forum_grade_item_update($forum);
118 return $forum->id;
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.
127 * @global object
128 * @param object $forum forum instance (with magic quotes)
129 * @return bool success
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;
141 if (empty($forum->ratingtime) or empty($forum->assessed)) {
142 $forum->assesstimestart = 0;
143 $forum->assesstimefinish = 0;
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
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');
182 if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
183 print_error('cannotfindfirstpost', 'forum');
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);
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);
209 $DB->update_record('forum', $forum);
211 forum_grade_item_update($forum);
213 return true;
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.
222 * @global object
223 * @param int $id forum instance id
224 * @return bool success
226 function forum_delete_instance($id) {
227 global $DB;
229 if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
230 return false;
232 if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
233 return false;
235 if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
236 return false;
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;
255 if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
256 $result = false;
259 forum_tp_delete_read_records(-1, -1, -1, $forum->id);
261 if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
262 $result = false;
265 forum_grade_item_delete($forum);
267 return $result;
272 * Indicates API features that the forum supports.
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)
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;
305 * Obtains the automatic completion state for this forum based on any conditions
306 * in forum settings.
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)
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}");
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;
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;
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;
364 return $result;
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
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
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
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;
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;
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;
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;
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;
475 unset($subusers); // release memory
479 $mailcount[$pid] = 0;
480 $errorcount[$pid] = 0;
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);
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
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;
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;
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);
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);
555 if (!isset($userfrom->groups[$forum->id])) {
556 if (!isset($userfrom->groups)) {
557 $userfrom->groups = array();
558 $users[$userfrom->id]->groups = array();
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];
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
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;
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;
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;
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)
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.'>';
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;
662 mtrace('post '.$post->id. ': '.$post->subject);
665 // mark processed posts as read
666 forum_tp_mark_posts_read($userto, $userto->markposts);
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"));
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);
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;
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;
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;
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;
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;
764 if (!isset($coursemodules[$forumid])) {
765 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
766 $coursemodules[$forumid] = $cm;
767 } else {
768 continue;
771 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
772 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
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";
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);
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);
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);
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>";
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;
877 if (!isset($userfrom->groups[$forum->id])) {
878 if (!isset($userfrom->groups)) {
879 $userfrom->groups = array();
880 $users[$userfrom->id]->groups = array();
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];
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;
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>";
915 $posthtml .= '<hr size="1" noshade="noshade" /></p>';
917 $posthtml .= '</body>';
919 if (empty($userto->mailformat) || $userto->mailformat != 1) {
920 // This user DOESN'T want to receive HTML
921 $posthtml = '';
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);
942 /// We have finishied all digest emails, update $CFG->digestmailtimelast
943 set_config('digestmailtimelast', $timenow);
946 cron_setup_user();
948 if (!empty($usermailcount)) {
949 mtrace(get_string('digestsentusers', 'forum', $usermailcount));
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();
959 } else {
960 set_config('forum_lastreadclean', time());
964 return true;
968 * Builds and returns the body of the email notification in plain text.
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.
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];
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];
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);
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)";
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";
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";
1046 return $posttext;
1050 * Builds and returns the body of the email notification in html format.
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
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 '';
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];
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";
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>';
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>';
1105 $posthtml .= '</body>';
1107 return $posthtml;
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)
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);
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;
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;
1152 return $result;
1154 return NULL;
1159 * @global object
1160 * @global object
1161 * @param object $coure
1162 * @param object $user
1163 * @param object $mod
1164 * @param object $forum
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);
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');
1184 $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1186 foreach ($posts as $post) {
1187 if (!isset($discussions[$post->discussion])) {
1188 continue;
1190 $discussion = $discussions[$post->discussion];
1192 forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1194 } else {
1195 echo "<p>".get_string("noposts", "forum")."</p>";
1205 * @global object
1206 * @global object
1207 * @global object
1208 * @param array $courses
1209 * @param array $htmlarray
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();
1218 if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1219 return;
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;
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
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;
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;
1274 $params[] = $groupid;
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();
1283 } else {
1284 $unread = array();
1287 if (empty($unread) and empty($new)) {
1288 return;
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;
1304 if (array_key_exists($forum->id,$unread)) {
1305 $thisunread = $unread[$forum->id]->count;
1306 $showunread = true;
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>';
1316 $str .= '</div></div>';
1318 if (!empty($str)) {
1319 if (!array_key_exists($forum->course,$htmlarray)) {
1320 $htmlarray[$forum->course] = array();
1322 if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1323 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1325 $htmlarray[$forum->course]['forum'] .= $str;
1331 * Given a course and a date, prints a summary of all the new
1332 * messages posted in the course since that date
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
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;
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;
1374 $cm = $modinfo->instances['forum'][$post->forum];
1375 if (!$cm->uservisible) {
1376 continue;
1378 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1380 if (!has_capability('mod/forum:viewdiscussion', $context)) {
1381 continue;
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;
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;
1403 if (is_null($modinfo->groups)) {
1404 $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1407 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1408 continue;
1413 $printposts[] = $post;
1415 unset($posts);
1417 if (!$printposts) {
1418 return false;
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.'">';
1437 $post->subject = break_up_long_words(format_string($post->subject, true));
1438 echo $post->subject;
1439 echo "</a>\"</div></li>\n";
1442 echo "</ul>\n";
1444 return true;
1448 * Return grade for given user or all users.
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
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);
1479 * Update activity grades
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
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);
1510 * Update all grades in gradebook.
1511 * @global object
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).");
1535 $rs->close();
1539 * Create/update grade item for given forum
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
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');
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;
1570 if ($grades === 'reset') {
1571 $params['reset'] = true;
1572 $grades = NULL;
1575 return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1579 * Delete grade item for given forum
1581 * @global object
1582 * @param object $forum object
1583 * @return object grade_item
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));
1594 * Returns the users with data in one forum
1595 * (users with records in forum_subscriptions, forum_posts, students)
1597 * @todo: deprecated - to be deleted in 2.2
1599 * @param int $forumid
1600 * @return mixed array or false if none
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;
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;
1648 //Return st_subscriptions array (it contains an array of unique users)
1649 return ($st_subscriptions);
1653 * This function returns if a scale is being used by one forum
1655 * @global object
1656 * @param int $forumid
1657 * @param int $scaleid negative number
1658 * @return bool
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;
1670 return $return;
1674 * Checks if scale is being used by any instance of forum
1676 * This is used to find out if scale used anywhere
1678 * @global object
1679 * @param $scaleid int
1680 * @return boolean True if the scale is used by any forum
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;
1691 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1694 * Gets a post with all info ready for forum_print_post
1695 * Most of these joins are just to get the forum id
1697 * @global object
1698 * @global object
1699 * @param int $postid
1700 * @return mixed array of posts or false
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));
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.
1717 * @global object
1718 * @global object
1719 * @return mixed array of posts or false
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));
1732 * Gets all posts in discussion including top parent.
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
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;
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();
1767 foreach ($posts as $pid=>$p) {
1768 if ($tracking) {
1769 if (forum_tp_is_post_old($p)) {
1770 $posts[$pid]->postread = true;
1773 if (!$p->parent) {
1774 continue;
1776 if (!isset($posts[$p->parent])) {
1777 continue; // parent does not exist??
1779 if (!isset($posts[$p->parent]->children)) {
1780 $posts[$p->parent]->children = array();
1782 $posts[$p->parent]->children[$pid] =& $posts[$pid];
1785 return $posts;
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.
1793 * @global object
1794 * @global object
1795 * @param int $parent
1796 * @param int $forumid
1797 * @return array
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));
1810 * An array of forum objects that the user is allowed to read/search through.
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
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');
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);
1839 if (!$courses) {
1840 return array();
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);
1852 if (empty($modinfo->instances['forum'])) {
1853 // hmm, no forums?
1854 continue;
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;
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;
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);
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);
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;
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;
1907 $readableforums[$forum->id] = $forum;
1910 unset($modinfo);
1912 } // End foreach $courses
1914 return $readableforums;
1918 * Returns a list of posts found using an array of search terms.
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
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;
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));
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";
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";
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;
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)";
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 .= ' ';
2002 $searchstring .= $searchterm;
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');
2028 $params = array_merge($params, $msparams);
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);
2063 * Returns a list of ratings for a particular post - sorted.
2065 * TODO: Check if this function is actually used anywhere.
2066 * Up until the fix for MDL-27471 this function wasn't even returning.
2068 * @param stdClass $context
2069 * @param int $postid
2070 * @param string $sort
2071 * @return array Array of ratings or false
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);
2086 * Returns a list of all new posts that have not been mailed yet
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
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();
2101 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2102 $params[] = $now;
2103 $params[] = $now;
2104 } else {
2105 $timedsql = "";
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);
2119 * Marks posts before a certain time as being mailed already
2121 * @global object
2122 * @global object
2123 * @param int $endtime
2124 * @param int $now Defaults to time()
2125 * @return bool
2127 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2128 global $CFG, $DB;
2129 if (empty($now)) {
2130 $now = time();
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));
2151 * Get all the posts for a user in a forum suitable for forum_print_post
2153 * @global object
2154 * @global object
2155 * @uses CONTEXT_MODULE
2156 * @return array
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;
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);
2186 * Get all the discussions user participated in
2188 * @global object
2189 * @global object
2190 * @uses CONTEXT_MODULE
2191 * @param int $forumid
2192 * @param int $userid
2193 * @return array Array or false
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;
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);
2220 * Get all the posts for a user in a forum suitable for forum_print_post
2222 * @global object
2223 * @global object
2224 * @param int $forumid
2225 * @param int $userid
2226 * @return array of counts or false
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;
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);
2254 * Given a log entry, return the forum post details for it.
2256 * @global object
2257 * @global object
2258 * @param object $log
2259 * @return array|null
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));
2293 return NULL;
2297 * Given a discussion id, return the first post from the discussion
2299 * @global object
2300 * @global object
2301 * @param int $dicsussionid
2302 * @return array
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));
2315 * Returns an array of counts of replies to each discussion
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
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;
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);
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);
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
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 = "";
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;
2409 $cache[$course->id] = $counts;
2410 } else {
2411 $cache[$course->id] = array();
2415 if (empty($cache[$course->id][$forum->id])) {
2416 return 0;
2419 $groupmode = groups_get_activity_groupmode($cm, $course);
2421 if ($groupmode != SEPARATEGROUPS) {
2422 return $cache[$course->id][$forum->id];
2425 if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2426 return $cache[$course->id][$forum->id];
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);
2436 if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2437 $mygroups = $modinfo->groups[$cm->groupingid];
2438 } else {
2439 $mygroups = false; // Will be set below
2442 // add all groups posts
2443 if (empty($mygroups)) {
2444 $mygroups = array(-1=>-1);
2445 } else {
2446 $mygroups[-1] = -1;
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 = "";
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);
2469 * How many posts by other users are unrated by a given user in the given discussion?
2471 * TODO: Is this function still used anywhere?
2473 * @param int $discussionid
2474 * @param int $userid
2475 * @return mixed
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
2503 } else {
2504 return $posts->num;
2506 } else {
2507 return 0;
2512 * Get all discussions in a forum
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
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();
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;
2553 $timelimit .= ")";
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;
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);
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 = "";
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";
2593 } else {
2594 $groupselect = "";
2598 if (empty($forumsort)) {
2599 $forumsort = "d.timemodified DESC";
2601 if (empty($fullpost)) {
2602 $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2603 } else {
2604 $postdata = "p.*";
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)";
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);
2629 * @global object
2630 * @global object
2631 * @global object
2632 * @uses CONTEXT_MODULE
2633 * @uses VISIBLEGROUPS
2634 * @param object $cm
2635 * @return array
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 = "";
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";
2667 } else {
2668 $groupselect = "";
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 = "";
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;
2694 return $unreads;
2695 } else {
2696 return array();
2701 * @global object
2702 * @global object
2703 * @global object
2704 * @uses CONEXT_MODULE
2705 * @uses VISIBLEGROUPS
2706 * @param object $cm
2707 * @return array
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 = "";
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";
2737 } else {
2738 $groupselect = "";
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;
2757 $timelimit .= ")";
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);
2772 * Get all discussions started by a particular user in a course (or group)
2773 * This function no longer used ...
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
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 = "";
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);
2809 * Get the list of potential subscribers to a forum.
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.
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";
2832 return $DB->get_records_sql($sql, $params);
2836 * Returns list of user objects that are subscribed to this forum
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.
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";
2872 if (empty($context)) {
2873 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2874 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
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);
2892 // Guest user should never be subscribed to a forum.
2893 unset($results[$CFG->siteguest]);
2895 return $results;
2900 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2904 * @global object
2905 * @global object
2906 * @param int $courseid
2907 * @param string $type
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
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;
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;
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;
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;
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;
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"));
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.
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
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];
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">';
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);
3051 if ($groups) {
3052 $output .= print_group_picture($groups, $course->id, false, true, true);
3053 } else {
3054 $output .= '&nbsp;';
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>';
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>';
3076 if ($reply) {
3077 $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3078 get_string('reply', 'forum').'</a>';
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>';
3093 if ($footer) {
3094 $output .= '<div class="footer">'.$footer.'</div>';
3096 $output .= '</td></tr></table>'."\n\n";
3098 return $output;
3102 * Print a forum post
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
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;
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);
3165 if (!isset($cm->uservisible)) {
3166 $cm->uservisible = coursemodule_visible_for_user($cm);
3169 if ($istracked && is_null($postisread)) {
3170 $postisread = forum_tp_is_post_read($USER->id, $post);
3173 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3174 $output = '';
3175 if (!$dummyifcantsee) {
3176 if ($return) {
3177 return $output;
3179 echo $output;
3180 return;
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'));
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;
3204 echo $output;
3205 return;
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');
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];
3243 } else {
3244 $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
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;
3266 if ($str->displaymode == FORUM_MODE_THREADED) {
3267 $url->param('parent', $post->parent);
3268 } else {
3269 $url->set_anchor('p'.$post->id);
3271 $commands[] = array('url'=>$url, 'text'=>$text);
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);
3282 $commands[] = array('url'=>$url, 'text'=>$str->parent);
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;
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);
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);
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);
3302 if ($reply) {
3303 $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('reply'=>$post->id)), 'text'=>$str->reply);
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);
3317 $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3318 if (!empty($porfoliohtml)) {
3319 $commands[] = $porfoliohtml;
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'));
3336 } else {
3337 // ignore trackign status if not tracked or tracked param missing
3338 $forumpostclass = '';
3341 $topicclass = '';
3342 if (empty($post->parent)) {
3343 $topicclass = ' firstpost starter';
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);
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);
3377 if (empty($groupoutput)) {
3378 $groupoutput = '&nbsp;';
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'));
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);
3406 $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
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'));
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;
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);
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
3448 // Output footer if required
3449 if ($footer) {
3450 $output .= html_writer::tag('div', $footer, array('class'=>'footer'));
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);
3463 if ($return) {
3464 return $output;
3466 echo $output;
3467 return;
3471 * Return rating related permissions
3473 * @param string $options the context id
3474 * @return array an associative array of the user's rating permissions
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;
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)
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
3504 function forum_rating_validate($params) {
3505 global $DB, $USER;
3507 // Check the component is mod_forum
3508 if ($params['component'] != 'mod_forum') {
3509 throw new rating_exception('invalidcomponent');
3512 // Check the ratingarea is post (the only rating area in forum)
3513 if ($params['ratingarea'] != 'post') {
3514 throw new rating_exception('invalidratingarea');
3517 // Check the rateduserid is not the current user .. you can't rate your own posts
3518 if ($params['rateduserid'] == $USER->id) {
3519 throw new rating_exception('nopermissiontorate');
3522 // Fetch all the related records ... we need to do this anyway to call forum_user_can_see_post
3523 $post = $DB->get_record('forum_posts', array('id' => $params['itemid'], 'userid' => $params['rateduserid']), '*', MUST_EXIST);
3524 $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion), '*', MUST_EXIST);
3525 $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
3526 $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
3527 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id , false, MUST_EXIST);
3528 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
3530 // Make sure the context provided is the context of the forum
3531 if ($context->id != $params['context']->id) {
3532 throw new rating_exception('invalidcontext');
3535 if ($forum->scale != $params['scaleid']) {
3536 //the scale being submitted doesnt match the one in the database
3537 throw new rating_exception('invalidscaleid');
3540 // check the item we're rating was created in the assessable time window
3541 if (!empty($forum->assesstimestart) && !empty($forum->assesstimefinish)) {
3542 if ($post->created < $forum->assesstimestart || $post->created > $forum->assesstimefinish) {
3543 throw new rating_exception('notavailable');
3547 //check that the submitted rating is valid for the scale
3549 // lower limit
3550 if ($params['rating'] < 0 && $params['rating'] != RATING_UNSET_RATING) {
3551 throw new rating_exception('invalidnum');
3554 // upper limit
3555 if ($forum->scale < 0) {
3556 //its a custom scale
3557 $scalerecord = $DB->get_record('scale', array('id' => -$forum->scale));
3558 if ($scalerecord) {
3559 $scalearray = explode(',', $scalerecord->scale);
3560 if ($params['rating'] > count($scalearray)) {
3561 throw new rating_exception('invalidnum');
3563 } else {
3564 throw new rating_exception('invalidscaleid');
3566 } else if ($params['rating'] > $forum->scale) {
3567 //if its numeric and submitted rating is above maximum
3568 throw new rating_exception('invalidnum');
3571 // Make sure groups allow this user to see the item they're rating
3572 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used
3573 if (!groups_group_exists($discussion->groupid)) { // Can't find group
3574 throw new rating_exception('cannotfindgroup');//something is wrong
3577 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
3578 // do not allow rating of posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
3579 throw new rating_exception('notmemberofgroup');
3583 // perform some final capability checks
3584 if (!forum_user_can_see_post($forum, $discussion, $post, $USER, $cm)) {
3585 throw new rating_exception('nopermissiontorate');
3588 return true;
3593 * This function prints the overview of a discussion in the forum listing.
3594 * It needs some discussion information and some post information, these
3595 * happen to be combined for efficiency in the $post parameter by the function
3596 * that calls this one: forum_print_latest_discussions()
3598 * @global object
3599 * @global object
3600 * @param object $post The post object (passed by reference for speed).
3601 * @param object $forum The forum object.
3602 * @param int $group Current group.
3603 * @param string $datestring Format to use for the dates.
3604 * @param boolean $cantrack Is tracking enabled for this forum.
3605 * @param boolean $forumtracked Is the user tracking this forum.
3606 * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
3608 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
3609 $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) {
3611 global $USER, $CFG, $OUTPUT;
3613 static $rowcount;
3614 static $strmarkalldread;
3616 if (empty($modcontext)) {
3617 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3618 print_error('invalidcoursemodule');
3620 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3623 if (!isset($rowcount)) {
3624 $rowcount = 0;
3625 $strmarkalldread = get_string('markalldread', 'forum');
3626 } else {
3627 $rowcount = ($rowcount + 1) % 2;
3630 $post->subject = format_string($post->subject,true);
3632 echo "\n\n";
3633 echo '<tr class="discussion r'.$rowcount.'">';
3635 // Topic
3636 echo '<td class="topic starter">';
3637 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.$post->subject.'</a>';
3638 echo "</td>\n";
3640 // Picture
3641 $postuser = new stdClass();
3642 $postuser->id = $post->userid;
3643 $postuser->firstname = $post->firstname;
3644 $postuser->lastname = $post->lastname;
3645 $postuser->imagealt = $post->imagealt;
3646 $postuser->picture = $post->picture;
3647 $postuser->email = $post->email;
3649 echo '<td class="picture">';
3650 echo $OUTPUT->user_picture($postuser, array('courseid'=>$forum->course));
3651 echo "</td>\n";
3653 // User name
3654 $fullname = fullname($post, has_capability('moodle/site:viewfullnames', $modcontext));
3655 echo '<td class="author">';
3656 echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->userid.'&amp;course='.$forum->course.'">'.$fullname.'</a>';
3657 echo "</td>\n";
3659 // Group picture
3660 if ($group !== -1) { // Groups are active - group is a group data object or NULL
3661 echo '<td class="picture group">';
3662 if (!empty($group->picture) and empty($group->hidepicture)) {
3663 print_group_picture($group, $forum->course, false, false, true);
3664 } else if (isset($group->id)) {
3665 if($canviewparticipants) {
3666 echo '<a href="'.$CFG->wwwroot.'/user/index.php?id='.$forum->course.'&amp;group='.$group->id.'">'.$group->name.'</a>';
3667 } else {
3668 echo $group->name;
3671 echo "</td>\n";
3674 if (has_capability('mod/forum:viewdiscussion', $modcontext)) { // Show the column with replies
3675 echo '<td class="replies">';
3676 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3677 echo $post->replies.'</a>';
3678 echo "</td>\n";
3680 if ($cantrack) {
3681 echo '<td class="replies">';
3682 if ($forumtracked) {
3683 if ($post->unread > 0) {
3684 echo '<span class="unread">';
3685 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#unread">';
3686 echo $post->unread;
3687 echo '</a>';
3688 echo '<a title="'.$strmarkalldread.'" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
3689 $forum->id.'&amp;d='.$post->discussion.'&amp;mark=read&amp;returnpage=view.php">' .
3690 '<img src="'.$OUTPUT->pix_url('t/clear') . '" class="iconsmall" alt="'.$strmarkalldread.'" /></a>';
3691 echo '</span>';
3692 } else {
3693 echo '<span class="read">';
3694 echo $post->unread;
3695 echo '</span>';
3697 } else {
3698 echo '<span class="read">';
3699 echo '-';
3700 echo '</span>';
3702 echo "</td>\n";
3706 echo '<td class="lastpost">';
3707 $usedate = (empty($post->timemodified)) ? $post->modified : $post->timemodified; // Just in case
3708 $parenturl = (empty($post->lastpostid)) ? '' : '&amp;parent='.$post->lastpostid;
3709 $usermodified = new stdClass();
3710 $usermodified->id = $post->usermodified;
3711 $usermodified->firstname = $post->umfirstname;
3712 $usermodified->lastname = $post->umlastname;
3713 echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->usermodified.'&amp;course='.$forum->course.'">'.
3714 fullname($usermodified).'</a><br />';
3715 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.$parenturl.'">'.
3716 userdate($usedate, $datestring).'</a>';
3717 echo "</td>\n";
3719 echo "</tr>\n\n";
3725 * Given a post object that we already know has a long message
3726 * this function truncates the message nicely to the first
3727 * sane place between $CFG->forum_longpost and $CFG->forum_shortpost
3729 * @global object
3730 * @param string $message
3731 * @return string
3733 function forum_shorten_post($message) {
3735 global $CFG;
3737 $i = 0;
3738 $tag = false;
3739 $length = strlen($message);
3740 $count = 0;
3741 $stopzone = false;
3742 $truncate = 0;
3744 for ($i=0; $i<$length; $i++) {
3745 $char = $message[$i];
3747 switch ($char) {
3748 case "<":
3749 $tag = true;
3750 break;
3751 case ">":
3752 $tag = false;
3753 break;
3754 default:
3755 if (!$tag) {
3756 if ($stopzone) {
3757 if ($char == ".") {
3758 $truncate = $i+1;
3759 break 2;
3762 $count++;
3764 break;
3766 if (!$stopzone) {
3767 if ($count > $CFG->forum_shortpost) {
3768 $stopzone = true;
3773 if (!$truncate) {
3774 $truncate = $i;
3777 return substr($message, 0, $truncate);
3781 * Print the drop down that allows the user to select how they want to have
3782 * the discussion displayed.
3784 * @param int $id forum id if $forumtype is 'single',
3785 * discussion id for any other forum type
3786 * @param mixed $mode forum layout mode
3787 * @param string $forumtype optional
3789 function forum_print_mode_form($id, $mode, $forumtype='') {
3790 global $OUTPUT;
3791 if ($forumtype == 'single') {
3792 $select = new single_select(new moodle_url("/mod/forum/view.php", array('f'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode");
3793 $select->class = "forummode";
3794 } else {
3795 $select = new single_select(new moodle_url("/mod/forum/discuss.php", array('d'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode");
3797 echo $OUTPUT->render($select);
3801 * @global object
3802 * @param object $course
3803 * @param string $search
3804 * @return string
3806 function forum_search_form($course, $search='') {
3807 global $CFG, $OUTPUT;
3809 $output = '<div class="forumsearch">';
3810 $output .= '<form action="'.$CFG->wwwroot.'/mod/forum/search.php" style="display:inline">';
3811 $output .= '<fieldset class="invisiblefieldset">';
3812 $output .= $OUTPUT->help_icon('search');
3813 $output .= '<label class="accesshide" for="search" >'.get_string('search', 'forum').'</label>';
3814 $output .= '<input id="search" name="search" type="text" size="18" value="'.s($search, true).'" alt="search" />';
3815 $output .= '<label class="accesshide" for="searchforums" >'.get_string('searchforums', 'forum').'</label>';
3816 $output .= '<input id="searchforums" value="'.get_string('searchforums', 'forum').'" type="submit" />';
3817 $output .= '<input name="id" type="hidden" value="'.$course->id.'" />';
3818 $output .= '</fieldset>';
3819 $output .= '</form>';
3820 $output .= '</div>';
3822 return $output;
3827 * @global object
3828 * @global object
3830 function forum_set_return() {
3831 global $CFG, $SESSION;
3833 if (! isset($SESSION->fromdiscussion)) {
3834 if (!empty($_SERVER['HTTP_REFERER'])) {
3835 $referer = $_SERVER['HTTP_REFERER'];
3836 } else {
3837 $referer = "";
3839 // If the referer is NOT a login screen then save it.
3840 if (! strncasecmp("$CFG->wwwroot/login", $referer, 300)) {
3841 $SESSION->fromdiscussion = $_SERVER["HTTP_REFERER"];
3848 * @global object
3849 * @param string $default
3850 * @return string
3852 function forum_go_back_to($default) {
3853 global $SESSION;
3855 if (!empty($SESSION->fromdiscussion)) {
3856 $returnto = $SESSION->fromdiscussion;
3857 unset($SESSION->fromdiscussion);
3858 return $returnto;
3859 } else {
3860 return $default;
3865 * Given a discussion object that is being moved to $forumto,
3866 * this function checks all posts in that discussion
3867 * for attachments, and if any are found, these are
3868 * moved to the new forum directory.
3870 * @global object
3871 * @param object $discussion
3872 * @param int $forumfrom source forum id
3873 * @param int $forumto target forum id
3874 * @return bool success
3876 function forum_move_attachments($discussion, $forumfrom, $forumto) {
3877 global $DB;
3879 $fs = get_file_storage();
3881 $newcm = get_coursemodule_from_instance('forum', $forumto);
3882 $oldcm = get_coursemodule_from_instance('forum', $forumfrom);
3884 $newcontext = get_context_instance(CONTEXT_MODULE, $newcm->id);
3885 $oldcontext = get_context_instance(CONTEXT_MODULE, $oldcm->id);
3887 // loop through all posts, better not use attachment flag ;-)
3888 if ($posts = $DB->get_records('forum_posts', array('discussion'=>$discussion->id), '', 'id, attachment')) {
3889 foreach ($posts as $post) {
3890 $fs->move_area_files_to_new_context($oldcontext->id,
3891 $newcontext->id, 'mod_forum', 'post', $post->id);
3892 $attachmentsmoved = $fs->move_area_files_to_new_context($oldcontext->id,
3893 $newcontext->id, 'mod_forum', 'attachment', $post->id);
3894 if ($attachmentsmoved > 0 && $post->attachment != '1') {
3895 // Weird - let's fix it
3896 $post->attachment = '1';
3897 $DB->update_record('forum_posts', $post);
3898 } else if ($attachmentsmoved == 0 && $post->attachment != '') {
3899 // Weird - let's fix it
3900 $post->attachment = '';
3901 $DB->update_record('forum_posts', $post);
3906 return true;
3910 * Returns attachments as formated text/html optionally with separate images
3912 * @global object
3913 * @global object
3914 * @global object
3915 * @param object $post
3916 * @param object $cm
3917 * @param string $type html/text/separateimages
3918 * @return mixed string or array of (html text withouth images and image HTML)
3920 function forum_print_attachments($post, $cm, $type) {
3921 global $CFG, $DB, $USER, $OUTPUT;
3923 if (empty($post->attachment)) {
3924 return $type !== 'separateimages' ? '' : array('', '');
3927 if (!in_array($type, array('separateimages', 'html', 'text'))) {
3928 return $type !== 'separateimages' ? '' : array('', '');
3931 if (!$context = get_context_instance(CONTEXT_MODULE, $cm->id)) {
3932 return $type !== 'separateimages' ? '' : array('', '');
3934 $strattachment = get_string('attachment', 'forum');
3936 $fs = get_file_storage();
3938 $imagereturn = '';
3939 $output = '';
3941 $canexport = !empty($CFG->enableportfolios) && (has_capability('mod/forum:exportpost', $context) || ($post->userid == $USER->id && has_capability('mod/forum:exportownpost', $context)));
3943 if ($canexport) {
3944 require_once($CFG->libdir.'/portfoliolib.php');
3947 $files = $fs->get_area_files($context->id, 'mod_forum', 'attachment', $post->id, "timemodified", false);
3948 if ($files) {
3949 if ($canexport) {
3950 $button = new portfolio_add_button();
3952 foreach ($files as $file) {
3953 $filename = $file->get_filename();
3954 $mimetype = $file->get_mimetype();
3955 $iconimage = '<img src="'.$OUTPUT->pix_url(file_mimetype_icon($mimetype)).'" class="icon" alt="'.$mimetype.'" />';
3956 $path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$context->id.'/mod_forum/attachment/'.$post->id.'/'.$filename);
3958 if ($type == 'html') {
3959 $output .= "<a href=\"$path\">$iconimage</a> ";
3960 $output .= "<a href=\"$path\">".s($filename)."</a>";
3961 if ($canexport) {
3962 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php');
3963 $button->set_format_by_file($file);
3964 $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
3966 $output .= "<br />";
3968 } else if ($type == 'text') {
3969 $output .= "$strattachment ".s($filename).":\n$path\n";
3971 } else { //'returnimages'
3972 if (in_array($mimetype, array('image/gif', 'image/jpeg', 'image/png'))) {
3973 // Image attachments don't get printed as links
3974 $imagereturn .= "<br /><img src=\"$path\" alt=\"\" />";
3975 if ($canexport) {
3976 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php');
3977 $button->set_format_by_file($file);
3978 $imagereturn .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
3980 } else {
3981 $output .= "<a href=\"$path\">$iconimage</a> ";
3982 $output .= format_text("<a href=\"$path\">".s($filename)."</a>", FORMAT_HTML, array('context'=>$context));
3983 if ($canexport) {
3984 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php');
3985 $button->set_format_by_file($file);
3986 $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
3988 $output .= '<br />';
3994 if ($type !== 'separateimages') {
3995 return $output;
3997 } else {
3998 return array($output, $imagereturn);
4003 * Lists all browsable file areas
4005 * @param object $course
4006 * @param object $cm
4007 * @param object $context
4008 * @return array
4010 function forum_get_file_areas($course, $cm, $context) {
4011 $areas = array();
4012 return $areas;
4016 * Serves the forum attachments. Implements needed access control ;-)
4018 * @param object $course
4019 * @param object $cm
4020 * @param object $context
4021 * @param string $filearea
4022 * @param array $args
4023 * @param bool $forcedownload
4024 * @return bool false if file not found, does not return if found - justsend the file
4026 function forum_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) {
4027 global $CFG, $DB;
4029 if ($context->contextlevel != CONTEXT_MODULE) {
4030 return false;
4033 require_course_login($course, true, $cm);
4035 $fileareas = array('attachment', 'post');
4036 if (!in_array($filearea, $fileareas)) {
4037 return false;
4040 $postid = (int)array_shift($args);
4042 if (!$post = $DB->get_record('forum_posts', array('id'=>$postid))) {
4043 return false;
4046 if (!$discussion = $DB->get_record('forum_discussions', array('id'=>$post->discussion))) {
4047 return false;
4050 if (!$forum = $DB->get_record('forum', array('id'=>$cm->instance))) {
4051 return false;
4054 $fs = get_file_storage();
4055 $relativepath = implode('/', $args);
4056 $fullpath = "/$context->id/mod_forum/$filearea/$postid/$relativepath";
4057 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
4058 return false;
4061 // Make sure groups allow this user to see this file
4062 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used
4063 if (!groups_group_exists($discussion->groupid)) { // Can't find group
4064 return false; // Be safe and don't send it to anyone
4067 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
4068 // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
4069 return false;
4073 // Make sure we're allowed to see it...
4074 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
4075 return false;
4079 // finally send the file
4080 send_stored_file($file, 0, 0, true); // download MUST be forced - security!
4084 * If successful, this function returns the name of the file
4086 * @global object
4087 * @param object $post is a full post record, including course and forum
4088 * @param object $forum
4089 * @param object $cm
4090 * @param mixed $mform
4091 * @param string $message
4092 * @return bool
4094 function forum_add_attachment($post, $forum, $cm, $mform=null, &$message=null) {
4095 global $DB;
4097 if (empty($mform)) {
4098 return false;
4101 if (empty($post->attachments)) {
4102 return true; // Nothing to do
4105 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4107 $info = file_get_draft_area_info($post->attachments);
4108 $present = ($info['filecount']>0) ? '1' : '';
4109 file_save_draft_area_files($post->attachments, $context->id, 'mod_forum', 'attachment', $post->id);
4111 $DB->set_field('forum_posts', 'attachment', $present, array('id'=>$post->id));
4113 return true;
4117 * Add a new post in an existing discussion.
4119 * @global object
4120 * @global object
4121 * @global object
4122 * @param object $post
4123 * @param mixed $mform
4124 * @param string $message
4125 * @return int
4127 function forum_add_new_post($post, $mform, &$message) {
4128 global $USER, $CFG, $DB;
4130 $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion));
4131 $forum = $DB->get_record('forum', array('id' => $discussion->forum));
4132 $cm = get_coursemodule_from_instance('forum', $forum->id);
4133 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4135 $post->created = $post->modified = time();
4136 $post->mailed = "0";
4137 $post->userid = $USER->id;
4138 $post->attachment = "";
4140 $post->id = $DB->insert_record("forum_posts", $post);
4141 $post->message = file_save_draft_area_files($post->itemid, $context->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
4142 $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
4143 forum_add_attachment($post, $forum, $cm, $mform, $message);
4145 // Update discussion modified date
4146 $DB->set_field("forum_discussions", "timemodified", $post->modified, array("id" => $post->discussion));
4147 $DB->set_field("forum_discussions", "usermodified", $post->userid, array("id" => $post->discussion));
4149 if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
4150 forum_tp_mark_post_read($post->userid, $post, $post->forum);
4153 return $post->id;
4157 * Update a post
4159 * @global object
4160 * @global object
4161 * @global object
4162 * @param object $post
4163 * @param mixed $mform
4164 * @param string $message
4165 * @return bool
4167 function forum_update_post($post, $mform, &$message) {
4168 global $USER, $CFG, $DB;
4170 $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion));
4171 $forum = $DB->get_record('forum', array('id' => $discussion->forum));
4172 $cm = get_coursemodule_from_instance('forum', $forum->id);
4173 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4175 $post->modified = time();
4177 $DB->update_record('forum_posts', $post);
4179 $discussion->timemodified = $post->modified; // last modified tracking
4180 $discussion->usermodified = $post->userid; // last modified tracking
4182 if (!$post->parent) { // Post is a discussion starter - update discussion title and times too
4183 $discussion->name = $post->subject;
4184 $discussion->timestart = $post->timestart;
4185 $discussion->timeend = $post->timeend;
4187 $post->message = file_save_draft_area_files($post->itemid, $context->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
4188 $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
4190 $DB->update_record('forum_discussions', $discussion);
4192 forum_add_attachment($post, $forum, $cm, $mform, $message);
4194 if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
4195 forum_tp_mark_post_read($post->userid, $post, $post->forum);
4198 return true;
4202 * Given an object containing all the necessary data,
4203 * create a new discussion and return the id
4205 * @global object
4206 * @global object
4207 * @global object
4208 * @param object $post
4209 * @param mixed $mform
4210 * @param string $message
4211 * @param int $userid
4212 * @return object
4214 function forum_add_discussion($discussion, $mform=null, &$message=null, $userid=null) {
4215 global $USER, $CFG, $DB;
4217 $timenow = time();
4219 if (is_null($userid)) {
4220 $userid = $USER->id;
4223 // The first post is stored as a real post, and linked
4224 // to from the discuss entry.
4226 $forum = $DB->get_record('forum', array('id'=>$discussion->forum));
4227 $cm = get_coursemodule_from_instance('forum', $forum->id);
4229 $post = new stdClass();
4230 $post->discussion = 0;
4231 $post->parent = 0;
4232 $post->userid = $userid;
4233 $post->created = $timenow;
4234 $post->modified = $timenow;
4235 $post->mailed = 0;
4236 $post->subject = $discussion->name;
4237 $post->message = $discussion->message;
4238 $post->messageformat = $discussion->messageformat;
4239 $post->messagetrust = $discussion->messagetrust;
4240 $post->attachments = isset($discussion->attachments) ? $discussion->attachments : null;
4241 $post->forum = $forum->id; // speedup
4242 $post->course = $forum->course; // speedup
4243 $post->mailnow = $discussion->mailnow;
4245 $post->id = $DB->insert_record("forum_posts", $post);
4247 // TODO: Fix the calling code so that there always is a $cm when this function is called
4248 if (!empty($cm->id) && !empty($discussion->itemid)) { // In "single simple discussions" this may not exist yet
4249 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4250 $text = file_save_draft_area_files($discussion->itemid, $context->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
4251 $DB->set_field('forum_posts', 'message', $text, array('id'=>$post->id));
4254 // Now do the main entry for the discussion, linking to this first post
4256 $discussion->firstpost = $post->id;
4257 $discussion->timemodified = $timenow;
4258 $discussion->usermodified = $post->userid;
4259 $discussion->userid = $userid;
4261 $post->discussion = $DB->insert_record("forum_discussions", $discussion);
4263 // Finally, set the pointer on the post.
4264 $DB->set_field("forum_posts", "discussion", $post->discussion, array("id"=>$post->id));
4266 if (!empty($cm->id)) {
4267 forum_add_attachment($post, $forum, $cm, $mform, $message);
4270 if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
4271 forum_tp_mark_post_read($post->userid, $post, $post->forum);
4274 return $post->discussion;
4279 * Deletes a discussion and handles all associated cleanup.
4281 * @global object
4282 * @param object $discussion Discussion to delete
4283 * @param bool $fulldelete True when deleting entire forum
4284 * @param object $course Course
4285 * @param object $cm Course-module
4286 * @param object $forum Forum
4287 * @return bool
4289 function forum_delete_discussion($discussion, $fulldelete, $course, $cm, $forum) {
4290 global $DB, $CFG;
4291 require_once($CFG->libdir.'/completionlib.php');
4293 $result = true;
4295 if ($posts = $DB->get_records("forum_posts", array("discussion" => $discussion->id))) {
4296 foreach ($posts as $post) {
4297 $post->course = $discussion->course;
4298 $post->forum = $discussion->forum;
4299 if (!forum_delete_post($post, 'ignore', $course, $cm, $forum, $fulldelete)) {
4300 $result = false;
4305 forum_tp_delete_read_records(-1, -1, $discussion->id);
4307 if (!$DB->delete_records("forum_discussions", array("id"=>$discussion->id))) {
4308 $result = false;
4311 // Update completion state if we are tracking completion based on number of posts
4312 // But don't bother when deleting whole thing
4313 if (!$fulldelete) {
4314 $completion = new completion_info($course);
4315 if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC &&
4316 ($forum->completiondiscussions || $forum->completionreplies || $forum->completionposts)) {
4317 $completion->update_state($cm, COMPLETION_INCOMPLETE, $discussion->userid);
4321 return $result;
4326 * Deletes a single forum post.
4328 * @global object
4329 * @param object $post Forum post object
4330 * @param mixed $children Whether to delete children. If false, returns false
4331 * if there are any children (without deleting the post). If true,
4332 * recursively deletes all children. If set to special value 'ignore', deletes
4333 * post regardless of children (this is for use only when deleting all posts
4334 * in a disussion).
4335 * @param object $course Course
4336 * @param object $cm Course-module
4337 * @param object $forum Forum
4338 * @param bool $skipcompletion True to skip updating completion state if it
4339 * would otherwise be updated, i.e. when deleting entire forum anyway.
4340 * @return bool
4342 function forum_delete_post($post, $children, $course, $cm, $forum, $skipcompletion=false) {
4343 global $DB, $CFG;
4344 require_once($CFG->libdir.'/completionlib.php');
4346 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4348 if ($children != 'ignore' && ($childposts = $DB->get_records('forum_posts', array('parent'=>$post->id)))) {
4349 if ($children) {
4350 foreach ($childposts as $childpost) {
4351 forum_delete_post($childpost, true, $course, $cm, $forum, $skipcompletion);
4353 } else {
4354 return false;
4358 //delete ratings
4359 require_once($CFG->dirroot.'/rating/lib.php');
4360 $delopt = new stdClass;
4361 $delopt->contextid = $context->id;
4362 $delopt->component = 'mod_forum';
4363 $delopt->ratingarea = 'post';
4364 $delopt->itemid = $post->id;
4365 $rm = new rating_manager();
4366 $rm->delete_ratings($delopt);
4368 //delete attachments
4369 $fs = get_file_storage();
4370 $fs->delete_area_files($context->id, 'mod_forum', 'attachment', $post->id);
4371 $fs->delete_area_files($context->id, 'mod_forum', 'post', $post->id);
4373 if ($DB->delete_records("forum_posts", array("id" => $post->id))) {
4375 forum_tp_delete_read_records(-1, $post->id);
4377 // Just in case we are deleting the last post
4378 forum_discussion_update_last_post($post->discussion);
4380 // Update completion state if we are tracking completion based on number of posts
4381 // But don't bother when deleting whole thing
4383 if (!$skipcompletion) {
4384 $completion = new completion_info($course);
4385 if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC &&
4386 ($forum->completiondiscussions || $forum->completionreplies || $forum->completionposts)) {
4387 $completion->update_state($cm, COMPLETION_INCOMPLETE, $post->userid);
4391 return true;
4393 return false;
4397 * @global object
4398 * @param object $post
4399 * @param bool $children
4400 * @return int
4402 function forum_count_replies($post, $children=true) {
4403 global $DB;
4404 $count = 0;
4406 if ($children) {
4407 if ($childposts = $DB->get_records('forum_posts', array('parent' => $post->id))) {
4408 foreach ($childposts as $childpost) {
4409 $count ++; // For this child
4410 $count += forum_count_replies($childpost, true);
4413 } else {
4414 $count += $DB->count_records('forum_posts', array('parent' => $post->id));
4417 return $count;
4422 * @global object
4423 * @param int $forumid
4424 * @param mixed $value
4425 * @return bool
4427 function forum_forcesubscribe($forumid, $value=1) {
4428 global $DB;
4429 return $DB->set_field("forum", "forcesubscribe", $value, array("id" => $forumid));
4433 * @global object
4434 * @param object $forum
4435 * @return bool
4437 function forum_is_forcesubscribed($forum) {
4438 global $DB;
4439 if (isset($forum->forcesubscribe)) { // then we use that
4440 return ($forum->forcesubscribe == FORUM_FORCESUBSCRIBE);
4441 } else { // Check the database
4442 return ($DB->get_field('forum', 'forcesubscribe', array('id' => $forum)) == FORUM_FORCESUBSCRIBE);
4446 function forum_get_forcesubscribed($forum) {
4447 global $DB;
4448 if (isset($forum->forcesubscribe)) { // then we use that
4449 return $forum->forcesubscribe;
4450 } else { // Check the database
4451 return $DB->get_field('forum', 'forcesubscribe', array('id' => $forum));
4456 * @global object
4457 * @param int $userid
4458 * @param object $forum
4459 * @return bool
4461 function forum_is_subscribed($userid, $forum) {
4462 global $DB;
4463 if (is_numeric($forum)) {
4464 $forum = $DB->get_record('forum', array('id' => $forum));
4466 if (forum_is_forcesubscribed($forum)) {
4467 return true;
4469 return $DB->record_exists("forum_subscriptions", array("userid" => $userid, "forum" => $forum->id));
4472 function forum_get_subscribed_forums($course) {
4473 global $USER, $CFG, $DB;
4474 $sql = "SELECT f.id
4475 FROM {forum} f
4476 LEFT JOIN {forum_subscriptions} fs ON (fs.forum = f.id AND fs.userid = ?)
4477 WHERE f.forcesubscribe <> ".FORUM_DISALLOWSUBSCRIBE."
4478 AND (f.forcesubscribe = ".FORUM_FORCESUBSCRIBE." OR fs.id IS NOT NULL)";
4479 if ($subscribed = $DB->get_records_sql($sql, array($USER->id))) {
4480 foreach ($subscribed as $s) {
4481 $subscribed[$s->id] = $s->id;
4483 return $subscribed;
4484 } else {
4485 return array();
4490 * Adds user to the subscriber list
4492 * @global object
4493 * @param int $userid
4494 * @param int $forumid
4496 function forum_subscribe($userid, $forumid) {
4497 global $DB;
4499 if ($DB->record_exists("forum_subscriptions", array("userid"=>$userid, "forum"=>$forumid))) {
4500 return true;
4503 $sub = new stdClass();
4504 $sub->userid = $userid;
4505 $sub->forum = $forumid;
4507 return $DB->insert_record("forum_subscriptions", $sub);
4511 * Removes user from the subscriber list
4513 * @global object
4514 * @param int $userid
4515 * @param int $forumid
4517 function forum_unsubscribe($userid, $forumid) {
4518 global $DB;
4519 return $DB->delete_records("forum_subscriptions", array("userid"=>$userid, "forum"=>$forumid));
4523 * Given a new post, subscribes or unsubscribes as appropriate.
4524 * Returns some text which describes what happened.
4526 * @global objec
4527 * @param object $post
4528 * @param object $forum
4530 function forum_post_subscription($post, $forum) {
4532 global $USER;
4534 $action = '';
4535 $subscribed = forum_is_subscribed($USER->id, $forum);
4537 if ($forum->forcesubscribe == FORUM_FORCESUBSCRIBE) { // database ignored
4538 return "";
4540 } elseif (($forum->forcesubscribe == FORUM_DISALLOWSUBSCRIBE)
4541 && !has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_COURSE, $forum->course), $USER->id)) {
4542 if ($subscribed) {
4543 $action = 'unsubscribe'; // sanity check, following MDL-14558
4544 } else {
4545 return "";
4548 } else { // go with the user's choice
4549 if (isset($post->subscribe)) {
4550 // no change
4551 if ((!empty($post->subscribe) && $subscribed)
4552 || (empty($post->subscribe) && !$subscribed)) {
4553 return "";
4555 } elseif (!empty($post->subscribe) && !$subscribed) {
4556 $action = 'subscribe';
4558 } elseif (empty($post->subscribe) && $subscribed) {
4559 $action = 'unsubscribe';
4564 $info = new stdClass();
4565 $info->name = fullname($USER);
4566 $info->forum = format_string($forum->name);
4568 switch ($action) {
4569 case 'subscribe':
4570 forum_subscribe($USER->id, $post->forum);
4571 return "<p>".get_string("nowsubscribed", "forum", $info)."</p>";
4572 case 'unsubscribe':
4573 forum_unsubscribe($USER->id, $post->forum);
4574 return "<p>".get_string("nownotsubscribed", "forum", $info)."</p>";
4579 * Generate and return the subscribe or unsubscribe link for a forum.
4581 * @param object $forum the forum. Fields used are $forum->id and $forum->forcesubscribe.
4582 * @param object $context the context object for this forum.
4583 * @param array $messages text used for the link in its various states
4584 * (subscribed, unsubscribed, forcesubscribed or cantsubscribe).
4585 * Any strings not passed in are taken from the $defaultmessages array
4586 * at the top of the function.
4587 * @param bool $cantaccessagroup
4588 * @param bool $fakelink
4589 * @param bool $backtoindex
4590 * @param array $subscribed_forums
4591 * @return string
4593 function forum_get_subscribe_link($forum, $context, $messages = array(), $cantaccessagroup = false, $fakelink=true, $backtoindex=false, $subscribed_forums=null) {
4594 global $CFG, $USER, $PAGE, $OUTPUT;
4595 $defaultmessages = array(
4596 'subscribed' => get_string('unsubscribe', 'forum'),
4597 'unsubscribed' => get_string('subscribe', 'forum'),
4598 'cantaccessgroup' => get_string('no'),
4599 'forcesubscribed' => get_string('everyoneissubscribed', 'forum'),
4600 'cantsubscribe' => get_string('disallowsubscribe','forum')
4602 $messages = $messages + $defaultmessages;
4604 if (forum_is_forcesubscribed($forum)) {
4605 return $messages['forcesubscribed'];
4606 } else if ($forum->forcesubscribe == FORUM_DISALLOWSUBSCRIBE && !has_capability('mod/forum:managesubscriptions', $context)) {
4607 return $messages['cantsubscribe'];
4608 } else if ($cantaccessagroup) {
4609 return $messages['cantaccessgroup'];
4610 } else {
4611 if (!is_enrolled($context, $USER, '', true)) {
4612 return '';
4614 if (is_null($subscribed_forums)) {
4615 $subscribed = forum_is_subscribed($USER->id, $forum);
4616 } else {
4617 $subscribed = !empty($subscribed_forums[$forum->id]);
4619 if ($subscribed) {
4620 $linktext = $messages['subscribed'];
4621 $linktitle = get_string('subscribestop', 'forum');
4622 } else {
4623 $linktext = $messages['unsubscribed'];
4624 $linktitle = get_string('subscribestart', 'forum');
4627 $options = array();
4628 if ($backtoindex) {
4629 $backtoindexlink = '&amp;backtoindex=1';
4630 $options['backtoindex'] = 1;
4631 } else {
4632 $backtoindexlink = '';
4634 $link = '';
4636 if ($fakelink) {
4637 $PAGE->requires->js('/mod/forum/forum.js');
4638 $PAGE->requires->js_function_call('forum_produce_subscribe_link', array($forum->id, $backtoindexlink, $linktext, $linktitle));
4639 $link = "<noscript>";
4641 $options['id'] = $forum->id;
4642 $options['sesskey'] = sesskey();
4643 $url = new moodle_url('/mod/forum/subscribe.php', $options);
4644 $link .= $OUTPUT->single_button($url, $linktext, 'get', array('title'=>$linktitle));
4645 if ($fakelink) {
4646 $link .= '</noscript>';
4649 return $link;
4655 * Generate and return the track or no track link for a forum.
4657 * @global object
4658 * @global object
4659 * @global object
4660 * @param object $forum the forum. Fields used are $forum->id and $forum->forcesubscribe.
4661 * @param array $messages
4662 * @param bool $fakelink
4663 * @return string
4665 function forum_get_tracking_link($forum, $messages=array(), $fakelink=true) {
4666 global $CFG, $USER, $PAGE, $OUTPUT;
4668 static $strnotrackforum, $strtrackforum;
4670 if (isset($messages['trackforum'])) {
4671 $strtrackforum = $messages['trackforum'];
4673 if (isset($messages['notrackforum'])) {
4674 $strnotrackforum = $messages['notrackforum'];
4676 if (empty($strtrackforum)) {
4677 $strtrackforum = get_string('trackforum', 'forum');
4679 if (empty($strnotrackforum)) {
4680 $strnotrackforum = get_string('notrackforum', 'forum');
4683 if (forum_tp_is_tracked($forum)) {
4684 $linktitle = $strnotrackforum;
4685 $linktext = $strnotrackforum;
4686 } else {
4687 $linktitle = $strtrackforum;
4688 $linktext = $strtrackforum;
4691 $link = '';
4692 if ($fakelink) {
4693 $PAGE->requires->js('/mod/forum/forum.js');
4694 $PAGE->requires->js_function_call('forum_produce_tracking_link', Array($forum->id, $linktext, $linktitle));
4695 // use <noscript> to print button in case javascript is not enabled
4696 $link .= '<noscript>';
4698 $url = new moodle_url('/mod/forum/settracking.php', array('id'=>$forum->id));
4699 $link .= $OUTPUT->single_button($url, $linktext, 'get', array('title'=>$linktitle));
4701 if ($fakelink) {
4702 $link .= '</noscript>';
4705 return $link;
4711 * Returns true if user created new discussion already
4713 * @global object
4714 * @global object
4715 * @param int $forumid
4716 * @param int $userid
4717 * @return bool
4719 function forum_user_has_posted_discussion($forumid, $userid) {
4720 global $CFG, $DB;
4722 $sql = "SELECT 'x'
4723 FROM {forum_discussions} d, {forum_posts} p
4724 WHERE d.forum = ? AND p.discussion = d.id AND p.parent = 0 and p.userid = ?";
4726 return $DB->record_exists_sql($sql, array($forumid, $userid));
4730 * @global object
4731 * @global object
4732 * @param int $forumid
4733 * @param int $userid
4734 * @return array
4736 function forum_discussions_user_has_posted_in($forumid, $userid) {
4737 global $CFG, $DB;
4739 $haspostedsql = "SELECT d.id AS id,
4741 FROM {forum_posts} p,
4742 {forum_discussions} d
4743 WHERE p.discussion = d.id
4744 AND d.forum = ?
4745 AND p.userid = ?";
4747 return $DB->get_records_sql($haspostedsql, array($forumid, $userid));
4751 * @global object
4752 * @global object
4753 * @param int $forumid
4754 * @param int $did
4755 * @param int $userid
4756 * @return bool
4758 function forum_user_has_posted($forumid, $did, $userid) {
4759 global $DB;
4761 if (empty($did)) {
4762 // posted in any forum discussion?
4763 $sql = "SELECT 'x'
4764 FROM {forum_posts} p
4765 JOIN {forum_discussions} d ON d.id = p.discussion
4766 WHERE p.userid = :userid AND d.forum = :forumid";
4767 return $DB->record_exists_sql($sql, array('forumid'=>$forumid,'userid'=>$userid));
4768 } else {
4769 return $DB->record_exists('forum_posts', array('discussion'=>$did,'userid'=>$userid));
4774 * Returns creation time of the first user's post in given discussion
4775 * @global object $DB
4776 * @param int $did Discussion id
4777 * @param int $userid User id
4778 * @return int|bool post creation time stamp or return false
4780 function forum_get_user_posted_time($did, $userid) {
4781 global $DB;
4783 $posttime = $DB->get_field('forum_posts', 'MIN(created)', array('userid'=>$userid, 'discussion'=>$did));
4784 if (empty($posttime)) {
4785 return false;
4787 return $posttime;
4791 * @global object
4792 * @param object $forum
4793 * @param object $currentgroup
4794 * @param int $unused
4795 * @param object $cm
4796 * @param object $context
4797 * @return bool
4799 function forum_user_can_post_discussion($forum, $currentgroup=null, $unused=-1, $cm=NULL, $context=NULL) {
4800 // $forum is an object
4801 global $USER;
4803 // shortcut - guest and not-logged-in users can not post
4804 if (isguestuser() or !isloggedin()) {
4805 return false;
4808 if (!$cm) {
4809 debugging('missing cm', DEBUG_DEVELOPER);
4810 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
4811 print_error('invalidcoursemodule');
4815 if (!$context) {
4816 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4819 if ($currentgroup === null) {
4820 $currentgroup = groups_get_activity_group($cm);
4823 $groupmode = groups_get_activity_groupmode($cm);
4825 if ($forum->type == 'news') {
4826 $capname = 'mod/forum:addnews';
4827 } else {
4828 $capname = 'mod/forum:startdiscussion';
4831 if (!has_capability($capname, $context)) {
4832 return false;
4835 if ($forum->type == 'eachuser') {
4836 if (forum_user_has_posted_discussion($forum->id, $USER->id)) {
4837 return false;
4841 if (!$groupmode or has_capability('moodle/site:accessallgroups', $context)) {
4842 return true;
4845 if ($currentgroup) {
4846 return groups_is_member($currentgroup);
4847 } else {
4848 // no group membership and no accessallgroups means no new discussions
4849 // reverted to 1.7 behaviour in 1.9+, buggy in 1.8.0-1.9.0
4850 return false;
4855 * This function checks whether the user can reply to posts in a forum
4856 * discussion. Use forum_user_can_post_discussion() to check whether the user
4857 * can start discussions.
4859 * @global object
4860 * @global object
4861 * @uses DEBUG_DEVELOPER
4862 * @uses CONTEXT_MODULE
4863 * @uses VISIBLEGROUPS
4864 * @param object $forum forum object
4865 * @param object $discussion
4866 * @param object $user
4867 * @param object $cm
4868 * @param object $course
4869 * @param object $context
4870 * @return bool
4872 function forum_user_can_post($forum, $discussion, $user=NULL, $cm=NULL, $course=NULL, $context=NULL) {
4873 global $USER, $DB;
4874 if (empty($user)) {
4875 $user = $USER;
4878 // shortcut - guest and not-logged-in users can not post
4879 if (isguestuser($user) or empty($user->id)) {
4880 return false;
4883 if (!isset($discussion->groupid)) {
4884 debugging('incorrect discussion parameter', DEBUG_DEVELOPER);
4885 return false;
4888 if (!$cm) {
4889 debugging('missing cm', DEBUG_DEVELOPER);
4890 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
4891 print_error('invalidcoursemodule');
4895 if (!$course) {
4896 debugging('missing course', DEBUG_DEVELOPER);
4897 if (!$course = $DB->get_record('course', array('id' => $forum->course))) {
4898 print_error('invalidcourseid');
4902 if (!$context) {
4903 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4906 // normal users with temporary guest access can not post, suspended users can not post either
4907 if (!is_viewing($context, $user->id) and !is_enrolled($context, $user->id, '', true)) {
4908 return false;
4911 if ($forum->type == 'news') {
4912 $capname = 'mod/forum:replynews';
4913 } else {
4914 $capname = 'mod/forum:replypost';
4917 if (!has_capability($capname, $context, $user->id)) {
4918 return false;
4921 if (!$groupmode = groups_get_activity_groupmode($cm, $course)) {
4922 return true;
4925 if (has_capability('moodle/site:accessallgroups', $context)) {
4926 return true;
4929 if ($groupmode == VISIBLEGROUPS) {
4930 if ($discussion->groupid == -1) {
4931 // allow students to reply to all participants discussions - this was not possible in Moodle <1.8
4932 return true;
4934 return groups_is_member($discussion->groupid);
4936 } else {
4937 //separate groups
4938 if ($discussion->groupid == -1) {
4939 return false;
4941 return groups_is_member($discussion->groupid);
4947 * checks to see if a user can view a particular post
4949 * @global object
4950 * @global object
4951 * @uses CONTEXT_MODULE
4952 * @uses SEPARATEGROUPS
4953 * @param object $post
4954 * @param object $course
4955 * @param object $cm
4956 * @param object $forum
4957 * @param object $discussion
4958 * @param object $user
4960 function forum_user_can_view_post($post, $course, $cm, $forum, $discussion, $user=NULL){
4962 global $CFG, $USER;
4964 if (!$user){
4965 $user = $USER;
4968 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
4969 if (!has_capability('mod/forum:viewdiscussion', $modcontext)) {
4970 return false;
4973 // If it's a grouped discussion, make sure the user is a member
4974 if ($discussion->groupid > 0) {
4975 $groupmode = groups_get_activity_groupmode($cm);
4976 if ($groupmode == SEPARATEGROUPS) {
4977 return groups_is_member($discussion->groupid) || has_capability('moodle/site:accessallgroups', $modcontext);
4980 return true;
4985 * @global object
4986 * @global object
4987 * @uses DEBUG_DEVELOPER
4988 * @param object $forum
4989 * @param object $discussion
4990 * @param object $context
4991 * @param object $user
4992 * @return bool
4994 function forum_user_can_see_discussion($forum, $discussion, $context, $user=NULL) {
4995 global $USER, $DB;
4997 if (empty($user) || empty($user->id)) {
4998 $user = $USER;
5001 // retrieve objects (yuk)
5002 if (is_numeric($forum)) {
5003 debugging('missing full forum', DEBUG_DEVELOPER);
5004 if (!$forum = $DB->get_record('forum',array('id'=>$forum))) {
5005 return false;
5008 if (is_numeric($discussion)) {
5009 debugging('missing full discussion', DEBUG_DEVELOPER);
5010 if (!$discussion = $DB->get_record('forum_discussions',array('id'=>$discussion))) {
5011 return false;
5015 if (!has_capability('mod/forum:viewdiscussion', $context)) {
5016 return false;
5019 if ($forum->type == 'qanda' &&
5020 !forum_user_has_posted($forum->id, $discussion->id, $user->id) &&
5021 !has_capability('mod/forum:viewqandawithoutposting', $context)) {
5022 return false;
5024 return true;
5029 * @global object
5030 * @global object
5031 * @param object $forum
5032 * @param object $discussion
5033 * @param object $post
5034 * @param object $user
5035 * @param object $cm
5036 * @return bool
5038 function forum_user_can_see_post($forum, $discussion, $post, $user=NULL, $cm=NULL) {
5039 global $CFG, $USER, $DB;
5041 // retrieve objects (yuk)
5042 if (is_numeric($forum)) {
5043 debugging('missing full forum', DEBUG_DEVELOPER);
5044 if (!$forum = $DB->get_record('forum',array('id'=>$forum))) {
5045 return false;
5049 if (is_numeric($discussion)) {
5050 debugging('missing full discussion', DEBUG_DEVELOPER);
5051 if (!$discussion = $DB->get_record('forum_discussions',array('id'=>$discussion))) {
5052 return false;
5055 if (is_numeric($post)) {
5056 debugging('missing full post', DEBUG_DEVELOPER);
5057 if (!$post = $DB->get_record('forum_posts',array('id'=>$post))) {
5058 return false;
5061 if (!isset($post->id) && isset($post->parent)) {
5062 $post->id = $post->parent;
5065 if (!$cm) {
5066 debugging('missing cm', DEBUG_DEVELOPER);
5067 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5068 print_error('invalidcoursemodule');
5072 if (empty($user) || empty($user->id)) {
5073 $user = $USER;
5076 $canviewdiscussion = !empty($cm->cache->caps['mod/forum:viewdiscussion']) || has_capability('mod/forum:viewdiscussion', get_context_instance(CONTEXT_MODULE, $cm->id), $user->id);
5077 if (!$canviewdiscussion && !has_all_capabilities(array('moodle/user:viewdetails', 'moodle/user:readuserposts'), get_context_instance(CONTEXT_USER, $post->userid))) {
5078 return false;
5081 if (isset($cm->uservisible)) {
5082 if (!$cm->uservisible) {
5083 return false;
5085 } else {
5086 if (!coursemodule_visible_for_user($cm, $user->id)) {
5087 return false;
5091 if ($forum->type == 'qanda') {
5092 $firstpost = forum_get_firstpost_from_discussion($discussion->id);
5093 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
5094 $userfirstpost = forum_get_user_posted_time($discussion->id, $user->id);
5096 return (($userfirstpost !== false && (time() - $userfirstpost >= $CFG->maxeditingtime)) ||
5097 $firstpost->id == $post->id || $post->userid == $user->id || $firstpost->userid == $user->id ||
5098 has_capability('mod/forum:viewqandawithoutposting', $modcontext, $user->id, false));
5100 return true;
5105 * Prints the discussion view screen for a forum.
5107 * @global object
5108 * @global object
5109 * @param object $course The current course object.
5110 * @param object $forum Forum to be printed.
5111 * @param int $maxdiscussions .
5112 * @param string $displayformat The display format to use (optional).
5113 * @param string $sort Sort arguments for database query (optional).
5114 * @param int $groupmode Group mode of the forum (optional).
5115 * @param void $unused (originally current group)
5116 * @param int $page Page mode, page to display (optional).
5117 * @param int $perpage The maximum number of discussions per page(optional)
5120 function forum_print_latest_discussions($course, $forum, $maxdiscussions=-1, $displayformat='plain', $sort='',
5121 $currentgroup=-1, $groupmode=-1, $page=-1, $perpage=100, $cm=NULL) {
5122 global $CFG, $USER, $OUTPUT;
5124 if (!$cm) {
5125 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5126 print_error('invalidcoursemodule');
5129 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
5131 if (empty($sort)) {
5132 $sort = "d.timemodified DESC";
5135 $olddiscussionlink = false;
5137 // Sort out some defaults
5138 if ($perpage <= 0) {
5139 $perpage = 0;
5140 $page = -1;
5143 if ($maxdiscussions == 0) {
5144 // all discussions - backwards compatibility
5145 $page = -1;
5146 $perpage = 0;
5147 if ($displayformat == 'plain') {
5148 $displayformat = 'header'; // Abbreviate display by default
5151 } else if ($maxdiscussions > 0) {
5152 $page = -1;
5153 $perpage = $maxdiscussions;
5156 $fullpost = false;
5157 if ($displayformat == 'plain') {
5158 $fullpost = true;
5162 // Decide if current user is allowed to see ALL the current discussions or not
5164 // First check the group stuff
5165 if ($currentgroup == -1 or $groupmode == -1) {
5166 $groupmode = groups_get_activity_groupmode($cm, $course);
5167 $currentgroup = groups_get_activity_group($cm);
5170 $groups = array(); //cache
5172 // If the user can post discussions, then this is a good place to put the
5173 // button for it. We do not show the button if we are showing site news
5174 // and the current user is a guest.
5176 $canstart = forum_user_can_post_discussion($forum, $currentgroup, $groupmode, $cm, $context);
5177 if (!$canstart and $forum->type !== 'news') {
5178 if (isguestuser() or !isloggedin()) {
5179 $canstart = true;
5181 if (!is_enrolled($context) and !is_viewing($context)) {
5182 // allow guests and not-logged-in to see the button - they are prompted to log in after clicking the link
5183 // normal users with temporary guest access see this button too, they are asked to enrol instead
5184 // do not show the button to users with suspended enrolments here
5185 $canstart = enrol_selfenrol_available($course->id);
5189 if ($canstart) {
5190 echo '<div class="singlebutton forumaddnew">';
5191 echo "<form id=\"newdiscussionform\" method=\"get\" action=\"$CFG->wwwroot/mod/forum/post.php\">";
5192 echo '<div>';
5193 echo "<input type=\"hidden\" name=\"forum\" value=\"$forum->id\" />";
5194 switch ($forum->type) {
5195 case 'news':
5196 case 'blog':
5197 $buttonadd = get_string('addanewtopic', 'forum');
5198 break;
5199 case 'qanda':
5200 $buttonadd = get_string('addanewquestion', 'forum');
5201 break;
5202 default:
5203 $buttonadd = get_string('addanewdiscussion', 'forum');
5204 break;
5206 echo '<input type="submit" value="'.$buttonadd.'" />';
5207 echo '</div>';
5208 echo '</form>';
5209 echo "</div>\n";
5211 } else if (isguestuser() or !isloggedin() or $forum->type == 'news') {
5212 // no button and no info
5214 } else if ($groupmode and has_capability('mod/forum:startdiscussion', $context)) {
5215 // inform users why they can not post new discussion
5216 if ($currentgroup) {
5217 echo $OUTPUT->notification(get_string('cannotadddiscussion', 'forum'));
5218 } else {
5219 echo $OUTPUT->notification(get_string('cannotadddiscussionall', 'forum'));
5223 // Get all the recent discussions we're allowed to see
5225 $getuserlastmodified = ($displayformat == 'header');
5227 if (! $discussions = forum_get_discussions($cm, $sort, $fullpost, null, $maxdiscussions, $getuserlastmodified, $page, $perpage) ) {
5228 echo '<div class="forumnodiscuss">';
5229 if ($forum->type == 'news') {
5230 echo '('.get_string('nonews', 'forum').')';
5231 } else if ($forum->type == 'qanda') {
5232 echo '('.get_string('noquestions','forum').')';
5233 } else {
5234 echo '('.get_string('nodiscussions', 'forum').')';
5236 echo "</div>\n";
5237 return;
5240 // If we want paging
5241 if ($page != -1) {
5242 ///Get the number of discussions found
5243 $numdiscussions = forum_get_discussions_count($cm);
5245 ///Show the paging bar
5246 echo $OUTPUT->paging_bar($numdiscussions, $page, $perpage, "view.php?f=$forum->id");
5247 if ($numdiscussions > 1000) {
5248 // saves some memory on sites with very large forums
5249 $replies = forum_count_discussion_replies($forum->id, $sort, $maxdiscussions, $page, $perpage);
5250 } else {
5251 $replies = forum_count_discussion_replies($forum->id);
5254 } else {
5255 $replies = forum_count_discussion_replies($forum->id);
5257 if ($maxdiscussions > 0 and $maxdiscussions <= count($discussions)) {
5258 $olddiscussionlink = true;
5262 $canviewparticipants = has_capability('moodle/course:viewparticipants',$context);
5264 $strdatestring = get_string('strftimerecentfull');
5266 // Check if the forum is tracked.
5267 if ($cantrack = forum_tp_can_track_forums($forum)) {
5268 $forumtracked = forum_tp_is_tracked($forum);
5269 } else {
5270 $forumtracked = false;
5273 if ($forumtracked) {
5274 $unreads = forum_get_discussions_unread($cm);
5275 } else {
5276 $unreads = array();
5279 if ($displayformat == 'header') {
5280 echo '<table cellspacing="0" class="forumheaderlist">';
5281 echo '<thead>';
5282 echo '<tr>';
5283 echo '<th class="header topic" scope="col">'.get_string('discussion', 'forum').'</th>';
5284 echo '<th class="header author" colspan="2" scope="col">'.get_string('startedby', 'forum').'</th>';
5285 if ($groupmode > 0) {
5286 echo '<th class="header group" scope="col">'.get_string('group').'</th>';
5288 if (has_capability('mod/forum:viewdiscussion', $context)) {
5289 echo '<th class="header replies" scope="col">'.get_string('replies', 'forum').'</th>';
5290 // If the forum can be tracked, display the unread column.
5291 if ($cantrack) {
5292 echo '<th class="header replies" scope="col">'.get_string('unread', 'forum');
5293 if ($forumtracked) {
5294 echo '&nbsp;<a title="'.get_string('markallread', 'forum').
5295 '" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
5296 $forum->id.'&amp;mark=read&amp;returnpage=view.php">'.
5297 '<img src="'.$OUTPUT->pix_url('t/clear') . '" class="iconsmall" alt="'.get_string('markallread', 'forum').'" /></a>';
5299 echo '</th>';
5302 echo '<th class="header lastpost" scope="col">'.get_string('lastpost', 'forum').'</th>';
5303 echo '</tr>';
5304 echo '</thead>';
5305 echo '<tbody>';
5308 foreach ($discussions as $discussion) {
5309 if (!empty($replies[$discussion->discussion])) {
5310 $discussion->replies = $replies[$discussion->discussion]->replies;
5311 $discussion->lastpostid = $replies[$discussion->discussion]->lastpostid;
5312 } else {
5313 $discussion->replies = 0;
5316 // SPECIAL CASE: The front page can display a news item post to non-logged in users.
5317 // All posts are read in this case.
5318 if (!$forumtracked) {
5319 $discussion->unread = '-';
5320 } else if (empty($USER)) {
5321 $discussion->unread = 0;
5322 } else {
5323 if (empty($unreads[$discussion->discussion])) {
5324 $discussion->unread = 0;
5325 } else {
5326 $discussion->unread = $unreads[$discussion->discussion];
5330 if (isloggedin()) {
5331 $ownpost = ($discussion->userid == $USER->id);
5332 } else {
5333 $ownpost=false;
5335 // Use discussion name instead of subject of first post
5336 $discussion->subject = $discussion->name;
5338 switch ($displayformat) {
5339 case 'header':
5340 if ($groupmode > 0) {
5341 if (isset($groups[$discussion->groupid])) {
5342 $group = $groups[$discussion->groupid];
5343 } else {
5344 $group = $groups[$discussion->groupid] = groups_get_group($discussion->groupid);
5346 } else {
5347 $group = -1;
5349 forum_print_discussion_header($discussion, $forum, $group, $strdatestring, $cantrack, $forumtracked,
5350 $canviewparticipants, $context);
5351 break;
5352 default:
5353 $link = false;
5355 if ($discussion->replies) {
5356 $link = true;
5357 } else {
5358 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
5359 $link = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
5362 $discussion->forum = $forum->id;
5364 forum_print_post($discussion, $discussion, $forum, $cm, $course, $ownpost, 0, $link, false);
5365 break;
5369 if ($displayformat == "header") {
5370 echo '</tbody>';
5371 echo '</table>';
5374 if ($olddiscussionlink) {
5375 if ($forum->type == 'news') {
5376 $strolder = get_string('oldertopics', 'forum');
5377 } else {
5378 $strolder = get_string('olderdiscussions', 'forum');
5380 echo '<div class="forumolddiscuss">';
5381 echo '<a href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'&amp;showall=1">';
5382 echo $strolder.'</a> ...</div>';
5385 if ($page != -1) { ///Show the paging bar
5386 echo $OUTPUT->paging_bar($numdiscussions, $page, $perpage, "view.php?f=$forum->id");
5392 * Prints a forum discussion
5394 * @uses CONTEXT_MODULE
5395 * @uses FORUM_MODE_FLATNEWEST
5396 * @uses FORUM_MODE_FLATOLDEST
5397 * @uses FORUM_MODE_THREADED
5398 * @uses FORUM_MODE_NESTED
5399 * @param stdClass $course
5400 * @param stdClass $cm
5401 * @param stdClass $forum
5402 * @param stdClass $discussion
5403 * @param stdClass $post
5404 * @param int $mode
5405 * @param mixed $canreply
5406 * @param bool $canrate
5408 function forum_print_discussion($course, $cm, $forum, $discussion, $post, $mode, $canreply=NULL, $canrate=false) {
5409 global $USER, $CFG;
5411 require_once($CFG->dirroot.'/rating/lib.php');
5413 $ownpost = (isloggedin() && $USER->id == $post->userid);
5415 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
5416 if ($canreply === NULL) {
5417 $reply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
5418 } else {
5419 $reply = $canreply;
5422 // $cm holds general cache for forum functions
5423 $cm->cache = new stdClass;
5424 $cm->cache->groups = groups_get_all_groups($course->id, 0, $cm->groupingid);
5425 $cm->cache->usersgroups = array();
5427 $posters = array();
5429 // preload all posts - TODO: improve...
5430 if ($mode == FORUM_MODE_FLATNEWEST) {
5431 $sort = "p.created DESC";
5432 } else {
5433 $sort = "p.created ASC";
5436 $forumtracked = forum_tp_is_tracked($forum);
5437 $posts = forum_get_all_discussion_posts($discussion->id, $sort, $forumtracked);
5438 $post = $posts[$post->id];
5440 foreach ($posts as $pid=>$p) {
5441 $posters[$p->userid] = $p->userid;
5444 // preload all groups of ppl that posted in this discussion
5445 if ($postersgroups = groups_get_all_groups($course->id, $posters, $cm->groupingid, 'gm.id, gm.groupid, gm.userid')) {
5446 foreach($postersgroups as $pg) {
5447 if (!isset($cm->cache->usersgroups[$pg->userid])) {
5448 $cm->cache->usersgroups[$pg->userid] = array();
5450 $cm->cache->usersgroups[$pg->userid][$pg->groupid] = $pg->groupid;
5452 unset($postersgroups);
5455 //load ratings
5456 if ($forum->assessed != RATING_AGGREGATE_NONE) {
5457 $ratingoptions = new stdClass;
5458 $ratingoptions->context = $modcontext;
5459 $ratingoptions->component = 'mod_forum';
5460 $ratingoptions->ratingarea = 'post';
5461 $ratingoptions->items = $posts;
5462 $ratingoptions->aggregate = $forum->assessed;//the aggregation method
5463 $ratingoptions->scaleid = $forum->scale;
5464 $ratingoptions->userid = $USER->id;
5465 if ($forum->type == 'single' or !$discussion->id) {
5466 $ratingoptions->returnurl = "$CFG->wwwroot/mod/forum/view.php?id=$cm->id";
5467 } else {
5468 $ratingoptions->returnurl = "$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id";
5470 $ratingoptions->assesstimestart = $forum->assesstimestart;
5471 $ratingoptions->assesstimefinish = $forum->assesstimefinish;
5473 $rm = new rating_manager();
5474 $posts = $rm->get_ratings($ratingoptions);
5478 $post->forum = $forum->id; // Add the forum id to the post object, later used by forum_print_post
5479 $post->forumtype = $forum->type;
5481 $post->subject = format_string($post->subject);
5483 $postread = !empty($post->postread);
5485 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, false,
5486 '', '', $postread, true, $forumtracked);
5488 switch ($mode) {
5489 case FORUM_MODE_FLATOLDEST :
5490 case FORUM_MODE_FLATNEWEST :
5491 default:
5492 forum_print_posts_flat($course, $cm, $forum, $discussion, $post, $mode, $reply, $forumtracked, $posts);
5493 break;
5495 case FORUM_MODE_THREADED :
5496 forum_print_posts_threaded($course, $cm, $forum, $discussion, $post, 0, $reply, $forumtracked, $posts);
5497 break;
5499 case FORUM_MODE_NESTED :
5500 forum_print_posts_nested($course, $cm, $forum, $discussion, $post, $reply, $forumtracked, $posts);
5501 break;
5507 * @global object
5508 * @global object
5509 * @uses FORUM_MODE_FLATNEWEST
5510 * @param object $course
5511 * @param object $cm
5512 * @param object $forum
5513 * @param object $discussion
5514 * @param object $post
5515 * @param object $mode
5516 * @param bool $reply
5517 * @param bool $forumtracked
5518 * @param array $posts
5519 * @return void
5521 function forum_print_posts_flat($course, &$cm, $forum, $discussion, $post, $mode, $reply, $forumtracked, $posts) {
5522 global $USER, $CFG;
5524 $link = false;
5526 if ($mode == FORUM_MODE_FLATNEWEST) {
5527 $sort = "ORDER BY created DESC";
5528 } else {
5529 $sort = "ORDER BY created ASC";
5532 foreach ($posts as $post) {
5533 if (!$post->parent) {
5534 continue;
5536 $post->subject = format_string($post->subject);
5537 $ownpost = ($USER->id == $post->userid);
5539 $postread = !empty($post->postread);
5541 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
5542 '', '', $postread, true, $forumtracked);
5547 * @todo Document this function
5549 * @global object
5550 * @global object
5551 * @uses CONTEXT_MODULE
5552 * @return void
5554 function forum_print_posts_threaded($course, &$cm, $forum, $discussion, $parent, $depth, $reply, $forumtracked, $posts) {
5555 global $USER, $CFG;
5557 $link = false;
5559 if (!empty($posts[$parent->id]->children)) {
5560 $posts = $posts[$parent->id]->children;
5562 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
5563 $canviewfullnames = has_capability('moodle/site:viewfullnames', $modcontext);
5565 foreach ($posts as $post) {
5567 echo '<div class="indent">';
5568 if ($depth > 0) {
5569 $ownpost = ($USER->id == $post->userid);
5570 $post->subject = format_string($post->subject);
5572 $postread = !empty($post->postread);
5574 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
5575 '', '', $postread, true, $forumtracked);
5576 } else {
5577 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
5578 echo "</div>\n";
5579 continue;
5581 $by = new stdClass();
5582 $by->name = fullname($post, $canviewfullnames);
5583 $by->date = userdate($post->modified);
5585 if ($forumtracked) {
5586 if (!empty($post->postread)) {
5587 $style = '<span class="forumthread read">';
5588 } else {
5589 $style = '<span class="forumthread unread">';
5591 } else {
5592 $style = '<span class="forumthread">';
5594 echo $style."<a name=\"$post->id\"></a>".
5595 "<a href=\"discuss.php?d=$post->discussion&amp;parent=$post->id\">".format_string($post->subject,true)."</a> ";
5596 print_string("bynameondate", "forum", $by);
5597 echo "</span>";
5600 forum_print_posts_threaded($course, $cm, $forum, $discussion, $post, $depth-1, $reply, $forumtracked, $posts);
5601 echo "</div>\n";
5607 * @todo Document this function
5608 * @global object
5609 * @global object
5610 * @return void
5612 function forum_print_posts_nested($course, &$cm, $forum, $discussion, $parent, $reply, $forumtracked, $posts) {
5613 global $USER, $CFG;
5615 $link = false;
5617 if (!empty($posts[$parent->id]->children)) {
5618 $posts = $posts[$parent->id]->children;
5620 foreach ($posts as $post) {
5622 echo '<div class="indent">';
5623 if (!isloggedin()) {
5624 $ownpost = false;
5625 } else {
5626 $ownpost = ($USER->id == $post->userid);
5629 $post->subject = format_string($post->subject);
5630 $postread = !empty($post->postread);
5632 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
5633 '', '', $postread, true, $forumtracked);
5634 forum_print_posts_nested($course, $cm, $forum, $discussion, $post, $reply, $forumtracked, $posts);
5635 echo "</div>\n";
5641 * Returns all forum posts since a given time in specified forum.
5643 * @todo Document this functions args
5644 * @global object
5645 * @global object
5646 * @global object
5647 * @global object
5649 function forum_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
5650 global $CFG, $COURSE, $USER, $DB;
5652 if ($COURSE->id == $courseid) {
5653 $course = $COURSE;
5654 } else {
5655 $course = $DB->get_record('course', array('id' => $courseid));
5658 $modinfo =& get_fast_modinfo($course);
5660 $cm = $modinfo->cms[$cmid];
5661 $params = array($timestart, $cm->instance);
5663 if ($userid) {
5664 $userselect = "AND u.id = ?";
5665 $params[] = $userid;
5666 } else {
5667 $userselect = "";
5670 if ($groupid) {
5671 $groupselect = "AND gm.groupid = ?";
5672 $groupjoin = "JOIN {groups_members} gm ON gm.userid=u.id";
5673 $params[] = $groupid;
5674 } else {
5675 $groupselect = "";
5676 $groupjoin = "";
5679 if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
5680 d.timestart, d.timeend, d.userid AS duserid,
5681 u.firstname, u.lastname, u.email, u.picture, u.imagealt, u.email
5682 FROM {forum_posts} p
5683 JOIN {forum_discussions} d ON d.id = p.discussion
5684 JOIN {forum} f ON f.id = d.forum
5685 JOIN {user} u ON u.id = p.userid
5686 $groupjoin
5687 WHERE p.created > ? AND f.id = ?
5688 $userselect $groupselect
5689 ORDER BY p.id ASC", $params)) { // order by initial posting date
5690 return;
5693 $groupmode = groups_get_activity_groupmode($cm, $course);
5694 $cm_context = get_context_instance(CONTEXT_MODULE, $cm->id);
5695 $viewhiddentimed = has_capability('mod/forum:viewhiddentimedposts', $cm_context);
5696 $accessallgroups = has_capability('moodle/site:accessallgroups', $cm_context);
5698 if (is_null($modinfo->groups)) {
5699 $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
5702 $printposts = array();
5703 foreach ($posts as $post) {
5705 if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
5706 and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
5707 if (!$viewhiddentimed) {
5708 continue;
5712 if ($groupmode) {
5713 if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or $accessallgroups) {
5714 // oki (Open discussions have groupid -1)
5715 } else {
5716 // separate mode
5717 if (isguestuser()) {
5718 // shortcut
5719 continue;
5722 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
5723 continue;
5728 $printposts[] = $post;
5731 if (!$printposts) {
5732 return;
5735 $aname = format_string($cm->name,true);
5737 foreach ($printposts as $post) {
5738 $tmpactivity = new stdClass();
5740 $tmpactivity->type = 'forum';
5741 $tmpactivity->cmid = $cm->id;
5742 $tmpactivity->name = $aname;
5743 $tmpactivity->sectionnum = $cm->sectionnum;
5744 $tmpactivity->timestamp = $post->modified;
5746 $tmpactivity->content = new stdClass();
5747 $tmpactivity->content->id = $post->id;
5748 $tmpactivity->content->discussion = $post->discussion;
5749 $tmpactivity->content->subject = format_string($post->subject);
5750 $tmpactivity->content->parent = $post->parent;
5752 $tmpactivity->user = new stdClass();
5753 $tmpactivity->user->id = $post->userid;
5754 $tmpactivity->user->firstname = $post->firstname;
5755 $tmpactivity->user->lastname = $post->lastname;
5756 $tmpactivity->user->picture = $post->picture;
5757 $tmpactivity->user->imagealt = $post->imagealt;
5758 $tmpactivity->user->email = $post->email;
5760 $activities[$index++] = $tmpactivity;
5763 return;
5767 * @todo Document this function
5768 * @global object
5770 function forum_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
5771 global $CFG, $OUTPUT;
5773 if ($activity->content->parent) {
5774 $class = 'reply';
5775 } else {
5776 $class = 'discussion';
5779 echo '<table border="0" cellpadding="3" cellspacing="0" class="forum-recent">';
5781 echo "<tr><td class=\"userpicture\" valign=\"top\">";
5782 echo $OUTPUT->user_picture($activity->user, array('courseid'=>$courseid));
5783 echo "</td><td class=\"$class\">";
5785 echo '<div class="title">';
5786 if ($detail) {
5787 $aname = s($activity->name);
5788 echo "<img src=\"" . $OUTPUT->pix_url('icon', $activity->type) . "\" ".
5789 "class=\"icon\" alt=\"{$aname}\" />";
5791 echo "<a href=\"$CFG->wwwroot/mod/forum/discuss.php?d={$activity->content->discussion}"
5792 ."#p{$activity->content->id}\">{$activity->content->subject}</a>";
5793 echo '</div>';
5795 echo '<div class="user">';
5796 $fullname = fullname($activity->user, $viewfullnames);
5797 echo "<a href=\"$CFG->wwwroot/user/view.php?id={$activity->user->id}&amp;course=$courseid\">"
5798 ."{$fullname}</a> - ".userdate($activity->timestamp);
5799 echo '</div>';
5800 echo "</td></tr></table>";
5802 return;
5806 * recursively sets the discussion field to $discussionid on $postid and all its children
5807 * used when pruning a post
5809 * @global object
5810 * @param int $postid
5811 * @param int $discussionid
5812 * @return bool
5814 function forum_change_discussionid($postid, $discussionid) {
5815 global $DB;
5816 $DB->set_field('forum_posts', 'discussion', $discussionid, array('id' => $postid));
5817 if ($posts = $DB->get_records('forum_posts', array('parent' => $postid))) {
5818 foreach ($posts as $post) {
5819 forum_change_discussionid($post->id, $discussionid);
5822 return true;
5826 * Prints the editing button on subscribers page
5828 * @global object
5829 * @global object
5830 * @param int $courseid
5831 * @param int $forumid
5832 * @return string
5834 function forum_update_subscriptions_button($courseid, $forumid) {
5835 global $CFG, $USER;
5837 if (!empty($USER->subscriptionsediting)) {
5838 $string = get_string('turneditingoff');
5839 $edit = "off";
5840 } else {
5841 $string = get_string('turneditingon');
5842 $edit = "on";
5845 return "<form method=\"get\" action=\"$CFG->wwwroot/mod/forum/subscribers.php\">".
5846 "<input type=\"hidden\" name=\"id\" value=\"$forumid\" />".
5847 "<input type=\"hidden\" name=\"edit\" value=\"$edit\" />".
5848 "<input type=\"submit\" value=\"$string\" /></form>";
5852 * This function gets run whenever user is enrolled into course
5854 * @param stdClass $cp
5855 * @return void
5857 function forum_user_enrolled($cp) {
5858 global $DB;
5860 // NOTE: this has to be as fast as possible - we do not want to slow down enrolments!
5861 // Originally there used to be 'mod/forum:initialsubscriptions' which was
5862 // introduced because we did not have enrolment information in earlier versions...
5864 $sql = "SELECT f.id
5865 FROM {forum} f
5866 LEFT JOIN {forum_subscriptions} fs ON (fs.forum = f.id AND fs.userid = :userid)
5867 WHERE f.course = :courseid AND f.forcesubscribe = :initial AND fs.id IS NULL";
5868 $params = array('courseid'=>$cp->courseid, 'userid'=>$cp->userid, 'initial'=>FORUM_INITIALSUBSCRIBE);
5870 $forums = $DB->get_records_sql($sql, $params);
5871 foreach ($forums as $forum) {
5872 forum_subscribe($cp->userid, $forum->id);
5877 * This function gets run whenever user is unenrolled from course
5879 * @param stdClass $cp
5880 * @return void
5882 function forum_user_unenrolled($cp) {
5883 global $DB;
5885 // NOTE: this has to be as fast as possible!
5887 if ($cp->lastenrol) {
5888 $params = array('userid'=>$cp->userid, 'courseid'=>$cp->courseid);
5889 $forumselect = "IN (SELECT f.id FROM {forum} f WHERE f.course = :courseid)";
5891 $DB->delete_records_select('forum_subscriptions', "userid = :userid AND forum $forumselect", $params);
5892 $DB->delete_records_select('forum_track_prefs', "userid = :userid AND forumid $forumselect", $params);
5893 $DB->delete_records_select('forum_read', "userid = :userid AND forumid $forumselect", $params);
5897 // Functions to do with read tracking.
5900 * Mark posts as read.
5902 * @global object
5903 * @global object
5904 * @param object $user object
5905 * @param array $postids array of post ids
5906 * @return boolean success
5908 function forum_tp_mark_posts_read($user, $postids) {
5909 global $CFG, $DB;
5911 if (!forum_tp_can_track_forums(false, $user)) {
5912 return true;
5915 $status = true;
5917 $now = time();
5918 $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
5920 if (empty($postids)) {
5921 return true;
5923 } else if (count($postids) > 200) {
5924 while ($part = array_splice($postids, 0, 200)) {
5925 $status = forum_tp_mark_posts_read($user, $part) && $status;
5927 return $status;
5930 list($usql, $params) = $DB->get_in_or_equal($postids);
5931 $params[] = $user->id;
5933 $sql = "SELECT id
5934 FROM {forum_read}
5935 WHERE postid $usql AND userid = ?";
5936 if ($existing = $DB->get_records_sql($sql, $params)) {
5937 $existing = array_keys($existing);
5938 } else {
5939 $existing = array();
5942 $new = array_diff($postids, $existing);
5944 if ($new) {
5945 list($usql, $new_params) = $DB->get_in_or_equal($new);
5946 $params = array($user->id, $now, $now, $user->id, $cutoffdate);
5947 $params = array_merge($params, $new_params);
5949 $sql = "INSERT INTO {forum_read} (userid, postid, discussionid, forumid, firstread, lastread)
5951 SELECT ?, p.id, p.discussion, d.forum, ?, ?
5952 FROM {forum_posts} p
5953 JOIN {forum_discussions} d ON d.id = p.discussion
5954 JOIN {forum} f ON f.id = d.forum
5955 LEFT JOIN {forum_track_prefs} tf ON (tf.userid = ? AND tf.forumid = f.id)
5956 WHERE p.id $usql
5957 AND p.modified >= ?
5958 AND (f.trackingtype = ".FORUM_TRACKING_ON."
5959 OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND tf.id IS NULL))";
5960 $status = $DB->execute($sql, $params) && $status;
5963 if ($existing) {
5964 list($usql, $new_params) = $DB->get_in_or_equal($existing);
5965 $params = array($now, $user->id);
5966 $params = array_merge($params, $new_params);
5968 $sql = "UPDATE {forum_read}
5969 SET lastread = ?
5970 WHERE userid = ? AND postid $usql";
5971 $status = $DB->execute($sql, $params) && $status;
5974 return $status;
5978 * Mark post as read.
5979 * @global object
5980 * @global object
5981 * @param int $userid
5982 * @param int $postid
5984 function forum_tp_add_read_record($userid, $postid) {
5985 global $CFG, $DB;
5987 $now = time();
5988 $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
5990 if (!$DB->record_exists('forum_read', array('userid' => $userid, 'postid' => $postid))) {
5991 $sql = "INSERT INTO {forum_read} (userid, postid, discussionid, forumid, firstread, lastread)
5993 SELECT ?, p.id, p.discussion, d.forum, ?, ?
5994 FROM {forum_posts} p
5995 JOIN {forum_discussions} d ON d.id = p.discussion
5996 WHERE p.id = ? AND p.modified >= ?";
5997 return $DB->execute($sql, array($userid, $now, $now, $postid, $cutoffdate));
5999 } else {
6000 $sql = "UPDATE {forum_read}
6001 SET lastread = ?
6002 WHERE userid = ? AND postid = ?";
6003 return $DB->execute($sql, array($now, $userid, $userid));
6008 * Returns all records in the 'forum_read' table matching the passed keys, indexed
6009 * by userid.
6011 * @global object
6012 * @param int $userid
6013 * @param int $postid
6014 * @param int $discussionid
6015 * @param int $forumid
6016 * @return array
6018 function forum_tp_get_read_records($userid=-1, $postid=-1, $discussionid=-1, $forumid=-1) {
6019 global $DB;
6020 $select = '';
6021 $params = array();
6023 if ($userid > -1) {
6024 if ($select != '') $select .= ' AND ';
6025 $select .= 'userid = ?';
6026 $params[] = $userid;
6028 if ($postid > -1) {
6029 if ($select != '') $select .= ' AND ';
6030 $select .= 'postid = ?';
6031 $params[] = $postid;
6033 if ($discussionid > -1) {
6034 if ($select != '') $select .= ' AND ';
6035 $select .= 'discussionid = ?';
6036 $params[] = $discussionid;
6038 if ($forumid > -1) {
6039 if ($select != '') $select .= ' AND ';
6040 $select .= 'forumid = ?';
6041 $params[] = $forumid;
6044 return $DB->get_records_select('forum_read', $select, $params);
6048 * Returns all read records for the provided user and discussion, indexed by postid.
6050 * @global object
6051 * @param inti $userid
6052 * @param int $discussionid
6054 function forum_tp_get_discussion_read_records($userid, $discussionid) {
6055 global $DB;
6056 $select = 'userid = ? AND discussionid = ?';
6057 $fields = 'postid, firstread, lastread';
6058 return $DB->get_records_select('forum_read', $select, array($userid, $discussionid), '', $fields);
6062 * If its an old post, do nothing. If the record exists, the maintenance will clear it up later.
6064 * @return bool
6066 function forum_tp_mark_post_read($userid, $post, $forumid) {
6067 if (!forum_tp_is_post_old($post)) {
6068 return forum_tp_add_read_record($userid, $post->id);
6069 } else {
6070 return true;
6075 * Marks a whole forum as read, for a given user
6077 * @global object
6078 * @global object
6079 * @param object $user
6080 * @param int $forumid
6081 * @param int|bool $groupid
6082 * @return bool
6084 function forum_tp_mark_forum_read($user, $forumid, $groupid=false) {
6085 global $CFG, $DB;
6087 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6089 $groupsel = "";
6090 $params = array($user->id, $forumid, $cutoffdate);
6092 if ($groupid !== false) {
6093 $groupsel = " AND (d.groupid = ? OR d.groupid = -1)";
6094 $params[] = $groupid;
6097 $sql = "SELECT p.id
6098 FROM {forum_posts} p
6099 LEFT JOIN {forum_discussions} d ON d.id = p.discussion
6100 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6101 WHERE d.forum = ?
6102 AND p.modified >= ? AND r.id is NULL
6103 $groupsel";
6105 if ($posts = $DB->get_records_sql($sql, $params)) {
6106 $postids = array_keys($posts);
6107 return forum_tp_mark_posts_read($user, $postids);
6110 return true;
6114 * Marks a whole discussion as read, for a given user
6116 * @global object
6117 * @global object
6118 * @param object $user
6119 * @param int $discussionid
6120 * @return bool
6122 function forum_tp_mark_discussion_read($user, $discussionid) {
6123 global $CFG, $DB;
6125 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6127 $sql = "SELECT p.id
6128 FROM {forum_posts} p
6129 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6130 WHERE p.discussion = ?
6131 AND p.modified >= ? AND r.id is NULL";
6133 if ($posts = $DB->get_records_sql($sql, array($user->id, $discussionid, $cutoffdate))) {
6134 $postids = array_keys($posts);
6135 return forum_tp_mark_posts_read($user, $postids);
6138 return true;
6142 * @global object
6143 * @param int $userid
6144 * @param object $post
6146 function forum_tp_is_post_read($userid, $post) {
6147 global $DB;
6148 return (forum_tp_is_post_old($post) ||
6149 $DB->record_exists('forum_read', array('userid' => $userid, 'postid' => $post->id)));
6153 * @global object
6154 * @param object $post
6155 * @param int $time Defautls to time()
6157 function forum_tp_is_post_old($post, $time=null) {
6158 global $CFG;
6160 if (is_null($time)) {
6161 $time = time();
6163 return ($post->modified < ($time - ($CFG->forum_oldpostdays * 24 * 3600)));
6167 * Returns the count of records for the provided user and discussion.
6169 * @global object
6170 * @global object
6171 * @param int $userid
6172 * @param int $discussionid
6173 * @return bool
6175 function forum_tp_count_discussion_read_records($userid, $discussionid) {
6176 global $CFG, $DB;
6178 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
6180 $sql = 'SELECT COUNT(DISTINCT p.id) '.
6181 'FROM {forum_discussions} d '.
6182 'LEFT JOIN {forum_read} r ON d.id = r.discussionid AND r.userid = ? '.
6183 'LEFT JOIN {forum_posts} p ON p.discussion = d.id '.
6184 'AND (p.modified < ? OR p.id = r.postid) '.
6185 'WHERE d.id = ? ';
6187 return ($DB->count_records_sql($sql, array($userid, $cutoffdate, $discussionid)));
6191 * Returns the count of records for the provided user and discussion.
6193 * @global object
6194 * @global object
6195 * @param int $userid
6196 * @param int $discussionid
6197 * @return int
6199 function forum_tp_count_discussion_unread_posts($userid, $discussionid) {
6200 global $CFG, $DB;
6202 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
6204 $sql = 'SELECT COUNT(p.id) '.
6205 'FROM {forum_posts} p '.
6206 'LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? '.
6207 'WHERE p.discussion = ? '.
6208 'AND p.modified >= ? AND r.id is NULL';
6210 return $DB->count_records_sql($sql, array($userid, $cutoffdate, $discussionid));
6214 * Returns the count of posts for the provided forum and [optionally] group.
6215 * @global object
6216 * @global object
6217 * @param int $forumid
6218 * @param int|bool $groupid
6219 * @return int
6221 function forum_tp_count_forum_posts($forumid, $groupid=false) {
6222 global $CFG, $DB;
6223 $params = array($forumid);
6224 $sql = 'SELECT COUNT(*) '.
6225 'FROM {forum_posts} fp,{forum_discussions} fd '.
6226 'WHERE fd.forum = ? AND fp.discussion = fd.id';
6227 if ($groupid !== false) {
6228 $sql .= ' AND (fd.groupid = ? OR fd.groupid = -1)';
6229 $params[] = $groupid;
6231 $count = $DB->count_records_sql($sql, $params);
6234 return $count;
6238 * Returns the count of records for the provided user and forum and [optionally] group.
6239 * @global object
6240 * @global object
6241 * @param int $userid
6242 * @param int $forumid
6243 * @param int|bool $groupid
6244 * @return int
6246 function forum_tp_count_forum_read_records($userid, $forumid, $groupid=false) {
6247 global $CFG, $DB;
6249 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6251 $groupsel = '';
6252 $params = array($userid, $forumid, $cutoffdate);
6253 if ($groupid !== false) {
6254 $groupsel = "AND (d.groupid = ? OR d.groupid = -1)";
6255 $params[] = $groupid;
6258 $sql = "SELECT COUNT(p.id)
6259 FROM {forum_posts} p
6260 JOIN {forum_discussions} d ON d.id = p.discussion
6261 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid= ?)
6262 WHERE d.forum = ?
6263 AND (p.modified < $cutoffdate OR (p.modified >= ? AND r.id IS NOT NULL))
6264 $groupsel";
6266 return $DB->get_field_sql($sql, $params);
6270 * Returns the count of records for the provided user and course.
6271 * Please note that group access is ignored!
6273 * @global object
6274 * @global object
6275 * @param int $userid
6276 * @param int $courseid
6277 * @return array
6279 function forum_tp_get_course_unread_posts($userid, $courseid) {
6280 global $CFG, $DB;
6282 $now = round(time(), -2); // db cache friendliness
6283 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
6284 $params = array($userid, $userid, $courseid, $cutoffdate);
6286 if (!empty($CFG->forum_enabletimedposts)) {
6287 $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
6288 $params[] = $now;
6289 $params[] = $now;
6290 } else {
6291 $timedsql = "";
6294 $sql = "SELECT f.id, COUNT(p.id) AS unread
6295 FROM {forum_posts} p
6296 JOIN {forum_discussions} d ON d.id = p.discussion
6297 JOIN {forum} f ON f.id = d.forum
6298 JOIN {course} c ON c.id = f.course
6299 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6300 LEFT JOIN {forum_track_prefs} tf ON (tf.userid = ? AND tf.forumid = f.id)
6301 WHERE f.course = ?
6302 AND p.modified >= ? AND r.id is NULL
6303 AND (f.trackingtype = ".FORUM_TRACKING_ON."
6304 OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND tf.id IS NULL))
6305 $timedsql
6306 GROUP BY f.id";
6308 if ($return = $DB->get_records_sql($sql, $params)) {
6309 return $return;
6312 return array();
6316 * Returns the count of records for the provided user and forum and [optionally] group.
6318 * @global object
6319 * @global object
6320 * @global object
6321 * @param object $cm
6322 * @param object $course
6323 * @return int
6325 function forum_tp_count_forum_unread_posts($cm, $course) {
6326 global $CFG, $USER, $DB;
6328 static $readcache = array();
6330 $forumid = $cm->instance;
6332 if (!isset($readcache[$course->id])) {
6333 $readcache[$course->id] = array();
6334 if ($counts = forum_tp_get_course_unread_posts($USER->id, $course->id)) {
6335 foreach ($counts as $count) {
6336 $readcache[$course->id][$count->id] = $count->unread;
6341 if (empty($readcache[$course->id][$forumid])) {
6342 // no need to check group mode ;-)
6343 return 0;
6346 $groupmode = groups_get_activity_groupmode($cm, $course);
6348 if ($groupmode != SEPARATEGROUPS) {
6349 return $readcache[$course->id][$forumid];
6352 if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
6353 return $readcache[$course->id][$forumid];
6356 require_once($CFG->dirroot.'/course/lib.php');
6358 $modinfo =& get_fast_modinfo($course);
6359 if (is_null($modinfo->groups)) {
6360 $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
6363 $mygroups = $modinfo->groups[$cm->groupingid];
6365 // add all groups posts
6366 if (empty($mygroups)) {
6367 $mygroups = array(-1=>-1);
6368 } else {
6369 $mygroups[-1] = -1;
6372 list ($groups_sql, $groups_params) = $DB->get_in_or_equal($mygroups);
6374 $now = round(time(), -2); // db cache friendliness
6375 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
6376 $params = array($USER->id, $forumid, $cutoffdate);
6378 if (!empty($CFG->forum_enabletimedposts)) {
6379 $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
6380 $params[] = $now;
6381 $params[] = $now;
6382 } else {
6383 $timedsql = "";
6386 $params = array_merge($params, $groups_params);
6388 $sql = "SELECT COUNT(p.id)
6389 FROM {forum_posts} p
6390 JOIN {forum_discussions} d ON p.discussion = d.id
6391 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6392 WHERE d.forum = ?
6393 AND p.modified >= ? AND r.id is NULL
6394 $timedsql
6395 AND d.groupid $groups_sql";
6397 return $DB->get_field_sql($sql, $params);
6401 * Deletes read records for the specified index. At least one parameter must be specified.
6403 * @global object
6404 * @param int $userid
6405 * @param int $postid
6406 * @param int $discussionid
6407 * @param int $forumid
6408 * @return bool
6410 function forum_tp_delete_read_records($userid=-1, $postid=-1, $discussionid=-1, $forumid=-1) {
6411 global $DB;
6412 $params = array();
6414 $select = '';
6415 if ($userid > -1) {
6416 if ($select != '') $select .= ' AND ';
6417 $select .= 'userid = ?';
6418 $params[] = $userid;
6420 if ($postid > -1) {
6421 if ($select != '') $select .= ' AND ';
6422 $select .= 'postid = ?';
6423 $params[] = $postid;
6425 if ($discussionid > -1) {
6426 if ($select != '') $select .= ' AND ';
6427 $select .= 'discussionid = ?';
6428 $params[] = $discussionid;
6430 if ($forumid > -1) {
6431 if ($select != '') $select .= ' AND ';
6432 $select .= 'forumid = ?';
6433 $params[] = $forumid;
6435 if ($select == '') {
6436 return false;
6438 else {
6439 return $DB->delete_records_select('forum_read', $select, $params);
6443 * Get a list of forums not tracked by the user.
6445 * @global object
6446 * @global object
6447 * @param int $userid The id of the user to use.
6448 * @param int $courseid The id of the course being checked.
6449 * @return mixed An array indexed by forum id, or false.
6451 function forum_tp_get_untracked_forums($userid, $courseid) {
6452 global $CFG, $DB;
6454 $sql = "SELECT f.id
6455 FROM {forum} f
6456 LEFT JOIN {forum_track_prefs} ft ON (ft.forumid = f.id AND ft.userid = ?)
6457 WHERE f.course = ?
6458 AND (f.trackingtype = ".FORUM_TRACKING_OFF."
6459 OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND ft.id IS NOT NULL))";
6461 if ($forums = $DB->get_records_sql($sql, array($userid, $courseid))) {
6462 foreach ($forums as $forum) {
6463 $forums[$forum->id] = $forum;
6465 return $forums;
6467 } else {
6468 return array();
6473 * Determine if a user can track forums and optionally a particular forum.
6474 * Checks the site settings, the user settings and the forum settings (if
6475 * requested).
6477 * @global object
6478 * @global object
6479 * @global object
6480 * @param mixed $forum The forum object to test, or the int id (optional).
6481 * @param mixed $userid The user object to check for (optional).
6482 * @return boolean
6484 function forum_tp_can_track_forums($forum=false, $user=false) {
6485 global $USER, $CFG, $DB;
6487 // if possible, avoid expensive
6488 // queries
6489 if (empty($CFG->forum_trackreadposts)) {
6490 return false;
6493 if ($user === false) {
6494 $user = $USER;
6497 if (isguestuser($user) or empty($user->id)) {
6498 return false;
6501 if ($forum === false) {
6502 // general abitily to track forums
6503 return (bool)$user->trackforums;
6507 // Work toward always passing an object...
6508 if (is_numeric($forum)) {
6509 debugging('Better use proper forum object.', DEBUG_DEVELOPER);
6510 $forum = $DB->get_record('forum', array('id' => $forum), '', 'id,trackingtype');
6513 $forumallows = ($forum->trackingtype == FORUM_TRACKING_OPTIONAL);
6514 $forumforced = ($forum->trackingtype == FORUM_TRACKING_ON);
6516 return ($forumforced || $forumallows) && !empty($user->trackforums);
6520 * Tells whether a specific forum is tracked by the user. A user can optionally
6521 * be specified. If not specified, the current user is assumed.
6523 * @global object
6524 * @global object
6525 * @global object
6526 * @param mixed $forum If int, the id of the forum being checked; if object, the forum object
6527 * @param int $userid The id of the user being checked (optional).
6528 * @return boolean
6530 function forum_tp_is_tracked($forum, $user=false) {
6531 global $USER, $CFG, $DB;
6533 if ($user === false) {
6534 $user = $USER;
6537 if (isguestuser($user) or empty($user->id)) {
6538 return false;
6541 // Work toward always passing an object...
6542 if (is_numeric($forum)) {
6543 debugging('Better use proper forum object.', DEBUG_DEVELOPER);
6544 $forum = $DB->get_record('forum', array('id' => $forum));
6547 if (!forum_tp_can_track_forums($forum, $user)) {
6548 return false;
6551 $forumallows = ($forum->trackingtype == FORUM_TRACKING_OPTIONAL);
6552 $forumforced = ($forum->trackingtype == FORUM_TRACKING_ON);
6554 return $forumforced ||
6555 ($forumallows && $DB->get_record('forum_track_prefs', array('userid' => $user->id, 'forumid' => $forum->id)) === false);
6559 * @global object
6560 * @global object
6561 * @param int $forumid
6562 * @param int $userid
6564 function forum_tp_start_tracking($forumid, $userid=false) {
6565 global $USER, $DB;
6567 if ($userid === false) {
6568 $userid = $USER->id;
6571 return $DB->delete_records('forum_track_prefs', array('userid' => $userid, 'forumid' => $forumid));
6575 * @global object
6576 * @global object
6577 * @param int $forumid
6578 * @param int $userid
6580 function forum_tp_stop_tracking($forumid, $userid=false) {
6581 global $USER, $DB;
6583 if ($userid === false) {
6584 $userid = $USER->id;
6587 if (!$DB->record_exists('forum_track_prefs', array('userid' => $userid, 'forumid' => $forumid))) {
6588 $track_prefs = new stdClass();
6589 $track_prefs->userid = $userid;
6590 $track_prefs->forumid = $forumid;
6591 $DB->insert_record('forum_track_prefs', $track_prefs);
6594 return forum_tp_delete_read_records($userid, -1, -1, $forumid);
6599 * Clean old records from the forum_read table.
6600 * @global object
6601 * @global object
6602 * @return void
6604 function forum_tp_clean_read_records() {
6605 global $CFG, $DB;
6607 if (!isset($CFG->forum_oldpostdays)) {
6608 return;
6610 // Look for records older than the cutoffdate that are still in the forum_read table.
6611 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6613 //first get the oldest tracking present - we need tis to speedup the next delete query
6614 $sql = "SELECT MIN(fp.modified) AS first
6615 FROM {forum_posts} fp
6616 JOIN {forum_read} fr ON fr.postid=fp.id";
6617 if (!$first = $DB->get_field_sql($sql)) {
6618 // nothing to delete;
6619 return;
6622 // now delete old tracking info
6623 $sql = "DELETE
6624 FROM {forum_read}
6625 WHERE postid IN (SELECT fp.id
6626 FROM {forum_posts} fp
6627 WHERE fp.modified >= ? AND fp.modified < ?)";
6628 $DB->execute($sql, array($first, $cutoffdate));
6632 * Sets the last post for a given discussion
6634 * @global object
6635 * @global object
6636 * @param into $discussionid
6637 * @return bool|int
6639 function forum_discussion_update_last_post($discussionid) {
6640 global $CFG, $DB;
6642 // Check the given discussion exists
6643 if (!$DB->record_exists('forum_discussions', array('id' => $discussionid))) {
6644 return false;
6647 // Use SQL to find the last post for this discussion
6648 $sql = "SELECT id, userid, modified
6649 FROM {forum_posts}
6650 WHERE discussion=?
6651 ORDER BY modified DESC";
6653 // Lets go find the last post
6654 if (($lastposts = $DB->get_records_sql($sql, array($discussionid), 0, 1))) {
6655 $lastpost = reset($lastposts);
6656 $discussionobject = new stdClass();
6657 $discussionobject->id = $discussionid;
6658 $discussionobject->usermodified = $lastpost->userid;
6659 $discussionobject->timemodified = $lastpost->modified;
6660 $DB->update_record('forum_discussions', $discussionobject);
6661 return $lastpost->id;
6664 // To get here either we couldn't find a post for the discussion (weird)
6665 // or we couldn't update the discussion record (weird x2)
6666 return false;
6671 * @return array
6673 function forum_get_view_actions() {
6674 return array('view discussion', 'search', 'forum', 'forums', 'subscribers', 'view forum');
6678 * @return array
6680 function forum_get_post_actions() {
6681 return array('add discussion','add post','delete discussion','delete post','move discussion','prune post','update post');
6685 * this function returns all the separate forum ids, given a courseid
6687 * @global object
6688 * @global object
6689 * @param int $courseid
6690 * @return array
6692 function forum_get_separate_modules($courseid) {
6694 global $CFG,$DB;
6695 $forummodule = $DB->get_record("modules", array("name" => "forum"));
6697 $sql = 'SELECT f.id, f.id FROM {forum} f, {course_modules} cm WHERE
6698 f.id = cm.instance AND cm.module =? AND cm.visible = 1 AND cm.course = ?
6699 AND cm.groupmode ='.SEPARATEGROUPS;
6701 return $DB->get_records_sql($sql, array($forummodule->id, $courseid));
6706 * @global object
6707 * @global object
6708 * @global object
6709 * @param object $forum
6710 * @param object $cm
6711 * @return bool
6713 function forum_check_throttling($forum, $cm=null) {
6714 global $USER, $CFG, $DB, $OUTPUT;
6716 if (is_numeric($forum)) {
6717 $forum = $DB->get_record('forum',array('id'=>$forum));
6719 if (!is_object($forum)) {
6720 return false; // this is broken.
6723 if (empty($forum->blockafter)) {
6724 return true;
6727 if (empty($forum->blockperiod)) {
6728 return true;
6731 if (!$cm) {
6732 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
6733 print_error('invalidcoursemodule');
6737 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
6738 if(has_capability('mod/forum:postwithoutthrottling', $modcontext)) {
6739 return true;
6742 // get the number of posts in the last period we care about
6743 $timenow = time();
6744 $timeafter = $timenow - $forum->blockperiod;
6746 $numposts = $DB->count_records_sql('SELECT COUNT(p.id) FROM {forum_posts} p'
6747 .' JOIN {forum_discussions} d'
6748 .' ON p.discussion = d.id WHERE d.forum = ?'
6749 .' AND p.userid = ? AND p.created > ?', array($forum->id, $USER->id, $timeafter));
6751 $a = new stdClass();
6752 $a->blockafter = $forum->blockafter;
6753 $a->numposts = $numposts;
6754 $a->blockperiod = get_string('secondstotime'.$forum->blockperiod);
6756 if ($forum->blockafter <= $numposts) {
6757 print_error('forumblockingtoomanyposts', 'error', $CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id, $a);
6759 if ($forum->warnafter <= $numposts) {
6760 echo $OUTPUT->notification(get_string('forumblockingalmosttoomanyposts','forum',$a));
6768 * Removes all grades from gradebook
6770 * @global object
6771 * @global object
6772 * @param int $courseid
6773 * @param string $type optional
6775 function forum_reset_gradebook($courseid, $type='') {
6776 global $CFG, $DB;
6778 $wheresql = '';
6779 $params = array($courseid);
6780 if ($type) {
6781 $wheresql = "AND f.type=?";
6782 $params[] = $type;
6785 $sql = "SELECT f.*, cm.idnumber as cmidnumber, f.course as courseid
6786 FROM {forum} f, {course_modules} cm, {modules} m
6787 WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id AND f.course=? $wheresql";
6789 if ($forums = $DB->get_records_sql($sql, $params)) {
6790 foreach ($forums as $forum) {
6791 forum_grade_item_update($forum, 'reset');
6797 * This function is used by the reset_course_userdata function in moodlelib.
6798 * This function will remove all posts from the specified forum
6799 * and clean up any related data.
6801 * @global object
6802 * @global object
6803 * @param $data the data submitted from the reset course.
6804 * @return array status array
6806 function forum_reset_userdata($data) {
6807 global $CFG, $DB;
6808 require_once($CFG->dirroot.'/rating/lib.php');
6810 $componentstr = get_string('modulenameplural', 'forum');
6811 $status = array();
6813 $params = array($data->courseid);
6815 $removeposts = false;
6816 $typesql = "";
6817 if (!empty($data->reset_forum_all)) {
6818 $removeposts = true;
6819 $typesstr = get_string('resetforumsall', 'forum');
6820 $types = array();
6821 } else if (!empty($data->reset_forum_types)){
6822 $removeposts = true;
6823 $typesql = "";
6824 $types = array();
6825 $forum_types_all = forum_get_forum_types_all();
6826 foreach ($data->reset_forum_types as $type) {
6827 if (!array_key_exists($type, $forum_types_all)) {
6828 continue;
6830 $typesql .= " AND f.type=?";
6831 $types[] = $forum_types_all[$type];
6832 $params[] = $type;
6834 $typesstr = get_string('resetforums', 'forum').': '.implode(', ', $types);
6836 $alldiscussionssql = "SELECT fd.id
6837 FROM {forum_discussions} fd, {forum} f
6838 WHERE f.course=? AND f.id=fd.forum";
6840 $allforumssql = "SELECT f.id
6841 FROM {forum} f
6842 WHERE f.course=?";
6844 $allpostssql = "SELECT fp.id
6845 FROM {forum_posts} fp, {forum_discussions} fd, {forum} f
6846 WHERE f.course=? AND f.id=fd.forum AND fd.id=fp.discussion";
6848 $forumssql = $forums = $rm = null;
6850 if( $removeposts || !empty($data->reset_forum_ratings) ) {
6851 $forumssql = "$allforumssql $typesql";
6852 $forums = $forums = $DB->get_records_sql($forumssql, $params);
6853 $rm = new rating_manager();;
6854 $ratingdeloptions = new stdClass;
6855 $ratingdeloptions->component = 'mod_forum';
6856 $ratingdeloptions->ratingarea = 'post';
6859 if ($removeposts) {
6860 $discussionssql = "$alldiscussionssql $typesql";
6861 $postssql = "$allpostssql $typesql";
6863 // now get rid of all attachments
6864 $fs = get_file_storage();
6865 if ($forums) {
6866 foreach ($forums as $forumid=>$unused) {
6867 if (!$cm = get_coursemodule_from_instance('forum', $forumid)) {
6868 continue;
6870 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
6871 $fs->delete_area_files($context->id, 'mod_forum', 'attachment');
6872 $fs->delete_area_files($context->id, 'mod_forum', 'post');
6874 //remove ratings
6875 $ratingdeloptions->contextid = $context->id;
6876 $rm->delete_ratings($ratingdeloptions);
6880 // first delete all read flags
6881 $DB->delete_records_select('forum_read', "forumid IN ($forumssql)", $params);
6883 // remove tracking prefs
6884 $DB->delete_records_select('forum_track_prefs', "forumid IN ($forumssql)", $params);
6886 // remove posts from queue
6887 $DB->delete_records_select('forum_queue', "discussionid IN ($discussionssql)", $params);
6889 // all posts - initial posts must be kept in single simple discussion forums
6890 $DB->delete_records_select('forum_posts', "discussion IN ($discussionssql) AND parent <> 0", $params); // first all children
6891 $DB->delete_records_select('forum_posts', "discussion IN ($discussionssql AND f.type <> 'single') AND parent = 0", $params); // now the initial posts for non single simple
6893 // finally all discussions except single simple forums
6894 $DB->delete_records_select('forum_discussions', "forum IN ($forumssql AND f.type <> 'single')", $params);
6896 // remove all grades from gradebook
6897 if (empty($data->reset_gradebook_grades)) {
6898 if (empty($types)) {
6899 forum_reset_gradebook($data->courseid);
6900 } else {
6901 foreach ($types as $type) {
6902 forum_reset_gradebook($data->courseid, $type);
6907 $status[] = array('component'=>$componentstr, 'item'=>$typesstr, 'error'=>false);
6910 // remove all ratings in this course's forums
6911 if (!empty($data->reset_forum_ratings)) {
6912 if ($forums) {
6913 foreach ($forums as $forumid=>$unused) {
6914 if (!$cm = get_coursemodule_from_instance('forum', $forumid)) {
6915 continue;
6917 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
6919 //remove ratings
6920 $ratingdeloptions->contextid = $context->id;
6921 $rm->delete_ratings($ratingdeloptions);
6925 // remove all grades from gradebook
6926 if (empty($data->reset_gradebook_grades)) {
6927 forum_reset_gradebook($data->courseid);
6931 // remove all subscriptions unconditionally - even for users still enrolled in course
6932 if (!empty($data->reset_forum_subscriptions)) {
6933 $DB->delete_records_select('forum_subscriptions', "forum IN ($allforumssql)", $params);
6934 $status[] = array('component'=>$componentstr, 'item'=>get_string('resetsubscriptions','forum'), 'error'=>false);
6937 // remove all tracking prefs unconditionally - even for users still enrolled in course
6938 if (!empty($data->reset_forum_track_prefs)) {
6939 $DB->delete_records_select('forum_track_prefs', "forumid IN ($allforumssql)", $params);
6940 $status[] = array('component'=>$componentstr, 'item'=>get_string('resettrackprefs','forum'), 'error'=>false);
6943 /// updating dates - shift may be negative too
6944 if ($data->timeshift) {
6945 shift_course_mod_dates('forum', array('assesstimestart', 'assesstimefinish'), $data->timeshift, $data->courseid);
6946 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
6949 return $status;
6953 * Called by course/reset.php
6955 * @param $mform form passed by reference
6957 function forum_reset_course_form_definition(&$mform) {
6958 $mform->addElement('header', 'forumheader', get_string('modulenameplural', 'forum'));
6960 $mform->addElement('checkbox', 'reset_forum_all', get_string('resetforumsall','forum'));
6962 $mform->addElement('select', 'reset_forum_types', get_string('resetforums', 'forum'), forum_get_forum_types_all(), array('multiple' => 'multiple'));
6963 $mform->setAdvanced('reset_forum_types');
6964 $mform->disabledIf('reset_forum_types', 'reset_forum_all', 'checked');
6966 $mform->addElement('checkbox', 'reset_forum_subscriptions', get_string('resetsubscriptions','forum'));
6967 $mform->setAdvanced('reset_forum_subscriptions');
6969 $mform->addElement('checkbox', 'reset_forum_track_prefs', get_string('resettrackprefs','forum'));
6970 $mform->setAdvanced('reset_forum_track_prefs');
6971 $mform->disabledIf('reset_forum_track_prefs', 'reset_forum_all', 'checked');
6973 $mform->addElement('checkbox', 'reset_forum_ratings', get_string('deleteallratings'));
6974 $mform->disabledIf('reset_forum_ratings', 'reset_forum_all', 'checked');
6978 * Course reset form defaults.
6979 * @return array
6981 function forum_reset_course_form_defaults($course) {
6982 return array('reset_forum_all'=>1, 'reset_forum_subscriptions'=>0, 'reset_forum_track_prefs'=>0, 'reset_forum_ratings'=>1);
6986 * Converts a forum to use the Roles System
6988 * @global object
6989 * @global object
6990 * @param object $forum a forum object with the same attributes as a record
6991 * from the forum database table
6992 * @param int $forummodid the id of the forum module, from the modules table
6993 * @param array $teacherroles array of roles that have archetype teacher
6994 * @param array $studentroles array of roles that have archetype student
6995 * @param array $guestroles array of roles that have archetype guest
6996 * @param int $cmid the course_module id for this forum instance
6997 * @return boolean forum was converted or not
6999 function forum_convert_to_roles($forum, $forummodid, $teacherroles=array(),
7000 $studentroles=array(), $guestroles=array(), $cmid=NULL) {
7002 global $CFG, $DB, $OUTPUT;
7004 if (!isset($forum->open) && !isset($forum->assesspublic)) {
7005 // We assume that this forum has already been converted to use the
7006 // Roles System. Columns forum.open and forum.assesspublic get dropped
7007 // once the forum module has been upgraded to use Roles.
7008 return false;
7011 if ($forum->type == 'teacher') {
7013 // Teacher forums should be converted to normal forums that
7014 // use the Roles System to implement the old behavior.
7015 // Note:
7016 // Seems that teacher forums were never backed up in 1.6 since they
7017 // didn't have an entry in the course_modules table.
7018 require_once($CFG->dirroot.'/course/lib.php');
7020 if ($DB->count_records('forum_discussions', array('forum' => $forum->id)) == 0) {
7021 // Delete empty teacher forums.
7022 $DB->delete_records('forum', array('id' => $forum->id));
7023 } else {
7024 // Create a course module for the forum and assign it to
7025 // section 0 in the course.
7026 $mod = new stdClass();
7027 $mod->course = $forum->course;
7028 $mod->module = $forummodid;
7029 $mod->instance = $forum->id;
7030 $mod->section = 0;
7031 $mod->visible = 0; // Hide the forum
7032 $mod->visibleold = 0; // Hide the forum
7033 $mod->groupmode = 0;
7035 if (!$cmid = add_course_module($mod)) {
7036 print_error('cannotcreateinstanceforteacher', 'forum');
7037 } else {
7038 $mod->coursemodule = $cmid;
7039 if (!$sectionid = add_mod_to_section($mod)) {
7040 print_error('cannotaddteacherforumto', 'forum');
7041 } else {
7042 $DB->set_field('course_modules', 'section', $sectionid, array('id' => $cmid));
7046 // Change the forum type to general.
7047 $forum->type = 'general';
7048 $DB->update_record('forum', $forum);
7050 $context = get_context_instance(CONTEXT_MODULE, $cmid);
7052 // Create overrides for default student and guest roles (prevent).
7053 foreach ($studentroles as $studentrole) {
7054 assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
7055 assign_capability('mod/forum:viewhiddentimedposts', CAP_PREVENT, $studentrole->id, $context->id);
7056 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
7057 assign_capability('mod/forum:replypost', CAP_PREVENT, $studentrole->id, $context->id);
7058 assign_capability('mod/forum:viewrating', CAP_PREVENT, $studentrole->id, $context->id);
7059 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $studentrole->id, $context->id);
7060 assign_capability('mod/forum:rate', CAP_PREVENT, $studentrole->id, $context->id);
7061 assign_capability('mod/forum:createattachment', CAP_PREVENT, $studentrole->id, $context->id);
7062 assign_capability('mod/forum:deleteownpost', CAP_PREVENT, $studentrole->id, $context->id);
7063 assign_capability('mod/forum:deleteanypost', CAP_PREVENT, $studentrole->id, $context->id);
7064 assign_capability('mod/forum:splitdiscussions', CAP_PREVENT, $studentrole->id, $context->id);
7065 assign_capability('mod/forum:movediscussions', CAP_PREVENT, $studentrole->id, $context->id);
7066 assign_capability('mod/forum:editanypost', CAP_PREVENT, $studentrole->id, $context->id);
7067 assign_capability('mod/forum:viewqandawithoutposting', CAP_PREVENT, $studentrole->id, $context->id);
7068 assign_capability('mod/forum:viewsubscribers', CAP_PREVENT, $studentrole->id, $context->id);
7069 assign_capability('mod/forum:managesubscriptions', CAP_PREVENT, $studentrole->id, $context->id);
7070 assign_capability('mod/forum:postwithoutthrottling', CAP_PREVENT, $studentrole->id, $context->id);
7072 foreach ($guestroles as $guestrole) {
7073 assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $guestrole->id, $context->id);
7074 assign_capability('mod/forum:viewhiddentimedposts', CAP_PREVENT, $guestrole->id, $context->id);
7075 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $guestrole->id, $context->id);
7076 assign_capability('mod/forum:replypost', CAP_PREVENT, $guestrole->id, $context->id);
7077 assign_capability('mod/forum:viewrating', CAP_PREVENT, $guestrole->id, $context->id);
7078 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $guestrole->id, $context->id);
7079 assign_capability('mod/forum:rate', CAP_PREVENT, $guestrole->id, $context->id);
7080 assign_capability('mod/forum:createattachment', CAP_PREVENT, $guestrole->id, $context->id);
7081 assign_capability('mod/forum:deleteownpost', CAP_PREVENT, $guestrole->id, $context->id);
7082 assign_capability('mod/forum:deleteanypost', CAP_PREVENT, $guestrole->id, $context->id);
7083 assign_capability('mod/forum:splitdiscussions', CAP_PREVENT, $guestrole->id, $context->id);
7084 assign_capability('mod/forum:movediscussions', CAP_PREVENT, $guestrole->id, $context->id);
7085 assign_capability('mod/forum:editanypost', CAP_PREVENT, $guestrole->id, $context->id);
7086 assign_capability('mod/forum:viewqandawithoutposting', CAP_PREVENT, $guestrole->id, $context->id);
7087 assign_capability('mod/forum:viewsubscribers', CAP_PREVENT, $guestrole->id, $context->id);
7088 assign_capability('mod/forum:managesubscriptions', CAP_PREVENT, $guestrole->id, $context->id);
7089 assign_capability('mod/forum:postwithoutthrottling', CAP_PREVENT, $guestrole->id, $context->id);
7092 } else {
7093 // Non-teacher forum.
7095 if (empty($cmid)) {
7096 // We were not given the course_module id. Try to find it.
7097 if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
7098 echo $OUTPUT->notification('Could not get the course module for the forum');
7099 return false;
7100 } else {
7101 $cmid = $cm->id;
7104 $context = get_context_instance(CONTEXT_MODULE, $cmid);
7106 // $forum->open defines what students can do:
7107 // 0 = No discussions, no replies
7108 // 1 = No discussions, but replies are allowed
7109 // 2 = Discussions and replies are allowed
7110 switch ($forum->open) {
7111 case 0:
7112 foreach ($studentroles as $studentrole) {
7113 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
7114 assign_capability('mod/forum:replypost', CAP_PREVENT, $studentrole->id, $context->id);
7116 break;
7117 case 1:
7118 foreach ($studentroles as $studentrole) {
7119 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
7120 assign_capability('mod/forum:replypost', CAP_ALLOW, $studentrole->id, $context->id);
7122 break;
7123 case 2:
7124 foreach ($studentroles as $studentrole) {
7125 assign_capability('mod/forum:startdiscussion', CAP_ALLOW, $studentrole->id, $context->id);
7126 assign_capability('mod/forum:replypost', CAP_ALLOW, $studentrole->id, $context->id);
7128 break;
7131 // $forum->assessed defines whether forum rating is turned
7132 // on (1 or 2) and who can rate posts:
7133 // 1 = Everyone can rate posts
7134 // 2 = Only teachers can rate posts
7135 switch ($forum->assessed) {
7136 case 1:
7137 foreach ($studentroles as $studentrole) {
7138 assign_capability('mod/forum:rate', CAP_ALLOW, $studentrole->id, $context->id);
7140 foreach ($teacherroles as $teacherrole) {
7141 assign_capability('mod/forum:rate', CAP_ALLOW, $teacherrole->id, $context->id);
7143 break;
7144 case 2:
7145 foreach ($studentroles as $studentrole) {
7146 assign_capability('mod/forum:rate', CAP_PREVENT, $studentrole->id, $context->id);
7148 foreach ($teacherroles as $teacherrole) {
7149 assign_capability('mod/forum:rate', CAP_ALLOW, $teacherrole->id, $context->id);
7151 break;
7154 // $forum->assesspublic defines whether students can see
7155 // everybody's ratings:
7156 // 0 = Students can only see their own ratings
7157 // 1 = Students can see everyone's ratings
7158 switch ($forum->assesspublic) {
7159 case 0:
7160 foreach ($studentroles as $studentrole) {
7161 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $studentrole->id, $context->id);
7163 foreach ($teacherroles as $teacherrole) {
7164 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $teacherrole->id, $context->id);
7166 break;
7167 case 1:
7168 foreach ($studentroles as $studentrole) {
7169 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $studentrole->id, $context->id);
7171 foreach ($teacherroles as $teacherrole) {
7172 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $teacherrole->id, $context->id);
7174 break;
7177 if (empty($cm)) {
7178 $cm = $DB->get_record('course_modules', array('id' => $cmid));
7181 // $cm->groupmode:
7182 // 0 - No groups
7183 // 1 - Separate groups
7184 // 2 - Visible groups
7185 switch ($cm->groupmode) {
7186 case 0:
7187 break;
7188 case 1:
7189 foreach ($studentroles as $studentrole) {
7190 assign_capability('moodle/site:accessallgroups', CAP_PREVENT, $studentrole->id, $context->id);
7192 foreach ($teacherroles as $teacherrole) {
7193 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
7195 break;
7196 case 2:
7197 foreach ($studentroles as $studentrole) {
7198 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $studentrole->id, $context->id);
7200 foreach ($teacherroles as $teacherrole) {
7201 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
7203 break;
7206 return true;
7210 * Returns array of forum layout modes
7212 * @return array
7214 function forum_get_layout_modes() {
7215 return array (FORUM_MODE_FLATOLDEST => get_string('modeflatoldestfirst', 'forum'),
7216 FORUM_MODE_FLATNEWEST => get_string('modeflatnewestfirst', 'forum'),
7217 FORUM_MODE_THREADED => get_string('modethreaded', 'forum'),
7218 FORUM_MODE_NESTED => get_string('modenested', 'forum'));
7222 * Returns array of forum types chooseable on the forum editing form
7224 * @return array
7226 function forum_get_forum_types() {
7227 return array ('general' => get_string('generalforum', 'forum'),
7228 'eachuser' => get_string('eachuserforum', 'forum'),
7229 'single' => get_string('singleforum', 'forum'),
7230 'qanda' => get_string('qandaforum', 'forum'),
7231 'blog' => get_string('blogforum', 'forum'));
7235 * Returns array of all forum layout modes
7237 * @return array
7239 function forum_get_forum_types_all() {
7240 return array ('news' => get_string('namenews','forum'),
7241 'social' => get_string('namesocial','forum'),
7242 'general' => get_string('generalforum', 'forum'),
7243 'eachuser' => get_string('eachuserforum', 'forum'),
7244 'single' => get_string('singleforum', 'forum'),
7245 'qanda' => get_string('qandaforum', 'forum'),
7246 'blog' => get_string('blogforum', 'forum'));
7250 * Returns array of forum open modes
7252 * @return array
7254 function forum_get_open_modes() {
7255 return array ('2' => get_string('openmode2', 'forum'),
7256 '1' => get_string('openmode1', 'forum'),
7257 '0' => get_string('openmode0', 'forum') );
7261 * Returns all other caps used in module
7263 * @return array
7265 function forum_get_extra_capabilities() {
7266 return array('moodle/site:accessallgroups', 'moodle/site:viewfullnames', 'moodle/site:trustcontent', 'moodle/rating:view', 'moodle/rating:viewany', 'moodle/rating:viewall', 'moodle/rating:rate');
7271 * This function is used to extend the global navigation by add forum nodes if there
7272 * is relevant content.
7274 * @param navigation_node $navref
7275 * @param stdClass $course
7276 * @param stdClass $module
7277 * @param stdClass $cm
7279 /*************************************************
7280 function forum_extend_navigation($navref, $course, $module, $cm) {
7281 global $CFG, $OUTPUT, $USER;
7283 $limit = 5;
7285 $discussions = forum_get_discussions($cm,"d.timemodified DESC", false, -1, $limit);
7286 $discussioncount = forum_get_discussions_count($cm);
7287 if (!is_array($discussions) || count($discussions)==0) {
7288 return;
7290 $discussionnode = $navref->add(get_string('discussions', 'forum').' ('.$discussioncount.')');
7291 $discussionnode->mainnavonly = true;
7292 $discussionnode->display = false; // Do not display on navigation (only on navbar)
7294 foreach ($discussions as $discussion) {
7295 $icon = new pix_icon('i/feedback', '');
7296 $url = new moodle_url('/mod/forum/discuss.php', array('d'=>$discussion->discussion));
7297 $discussionnode->add($discussion->subject, $url, navigation_node::TYPE_SETTING, null, null, $icon);
7300 if ($discussioncount > count($discussions)) {
7301 if (!empty($navref->action)) {
7302 $url = $navref->action;
7303 } else {
7304 $url = new moodle_url('/mod/forum/view.php', array('id'=>$cm->id));
7306 $discussionnode->add(get_string('viewalldiscussions', 'forum'), $url, navigation_node::TYPE_SETTING, null, null, $icon);
7309 $index = 0;
7310 $recentposts = array();
7311 $lastlogin = time() - COURSE_MAX_RECENT_PERIOD;
7312 if (!isguestuser() and !empty($USER->lastcourseaccess[$course->id])) {
7313 if ($USER->lastcourseaccess[$course->id] > $lastlogin) {
7314 $lastlogin = $USER->lastcourseaccess[$course->id];
7317 forum_get_recent_mod_activity($recentposts, $index, $lastlogin, $course->id, $cm->id);
7319 if (is_array($recentposts) && count($recentposts)>0) {
7320 $recentnode = $navref->add(get_string('recentactivity').' ('.count($recentposts).')');
7321 $recentnode->mainnavonly = true;
7322 $recentnode->display = false;
7323 foreach ($recentposts as $post) {
7324 $icon = new pix_icon('i/feedback', '');
7325 $url = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->content->discussion));
7326 $title = $post->content->subject."\n".userdate($post->timestamp, get_string('strftimerecent', 'langconfig'))."\n".$post->user->firstname.' '.$post->user->lastname;
7327 $recentnode->add($title, $url, navigation_node::TYPE_SETTING, null, null, $icon);
7331 *************************/
7334 * Adds module specific settings to the settings block
7336 * @param settings_navigation $settings The settings navigation object
7337 * @param navigation_node $forumnode The node to add module settings to
7339 function forum_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $forumnode) {
7340 global $USER, $PAGE, $CFG, $DB, $OUTPUT;
7342 $forumobject = $DB->get_record("forum", array("id" => $PAGE->cm->instance));
7343 if (empty($PAGE->cm->context)) {
7344 $PAGE->cm->context = get_context_instance(CONTEXT_MODULE, $PAGE->cm->instance);
7347 // for some actions you need to be enrolled, beiing admin is not enough sometimes here
7348 $enrolled = is_enrolled($PAGE->cm->context, $USER, '', false);
7349 $activeenrolled = is_enrolled($PAGE->cm->context, $USER, '', true);
7351 $canmanage = has_capability('mod/forum:managesubscriptions', $PAGE->cm->context);
7352 $subscriptionmode = forum_get_forcesubscribed($forumobject);
7353 $cansubscribe = ($activeenrolled && $subscriptionmode != FORUM_FORCESUBSCRIBE && ($subscriptionmode != FORUM_DISALLOWSUBSCRIBE || $canmanage));
7355 if ($canmanage) {
7356 $mode = $forumnode->add(get_string('subscriptionmode', 'forum'), null, navigation_node::TYPE_CONTAINER);
7358 $allowchoice = $mode->add(get_string('subscriptionoptional', 'forum'), new moodle_url('/mod/forum/subscribe.php', array('id'=>$forumobject->id, 'mode'=>FORUM_CHOOSESUBSCRIBE, 'sesskey'=>sesskey())), navigation_node::TYPE_SETTING);
7359 $forceforever = $mode->add(get_string("subscriptionforced", "forum"), new moodle_url('/mod/forum/subscribe.php', array('id'=>$forumobject->id, 'mode'=>FORUM_FORCESUBSCRIBE, 'sesskey'=>sesskey())), navigation_node::TYPE_SETTING);
7360 $forceinitially = $mode->add(get_string("subscriptionauto", "forum"), new moodle_url('/mod/forum/subscribe.php', array('id'=>$forumobject->id, 'mode'=>FORUM_INITIALSUBSCRIBE, 'sesskey'=>sesskey())), navigation_node::TYPE_SETTING);
7361 $disallowchoice = $mode->add(get_string('subscriptiondisabled', 'forum'), new moodle_url('/mod/forum/subscribe.php', array('id'=>$forumobject->id, 'mode'=>FORUM_DISALLOWSUBSCRIBE, 'sesskey'=>sesskey())), navigation_node::TYPE_SETTING);
7363 switch ($subscriptionmode) {
7364 case FORUM_CHOOSESUBSCRIBE : // 0
7365 $allowchoice->action = null;
7366 $allowchoice->add_class('activesetting');
7367 break;
7368 case FORUM_FORCESUBSCRIBE : // 1
7369 $forceforever->action = null;
7370 $forceforever->add_class('activesetting');
7371 break;
7372 case FORUM_INITIALSUBSCRIBE : // 2
7373 $forceinitially->action = null;
7374 $forceinitially->add_class('activesetting');
7375 break;
7376 case FORUM_DISALLOWSUBSCRIBE : // 3
7377 $disallowchoice->action = null;
7378 $disallowchoice->add_class('activesetting');
7379 break;
7382 } else if ($activeenrolled) {
7384 switch ($subscriptionmode) {
7385 case FORUM_CHOOSESUBSCRIBE : // 0
7386 $notenode = $forumnode->add(get_string('subscriptionoptional', 'forum'));
7387 break;
7388 case FORUM_FORCESUBSCRIBE : // 1
7389 $notenode = $forumnode->add(get_string('subscriptionforced', 'forum'));
7390 break;
7391 case FORUM_INITIALSUBSCRIBE : // 2
7392 $notenode = $forumnode->add(get_string('subscriptionauto', 'forum'));
7393 break;
7394 case FORUM_DISALLOWSUBSCRIBE : // 3
7395 $notenode = $forumnode->add(get_string('subscriptiondisabled', 'forum'));
7396 break;
7400 if ($cansubscribe) {
7401 if (forum_is_subscribed($USER->id, $forumobject)) {
7402 $linktext = get_string('unsubscribe', 'forum');
7403 } else {
7404 $linktext = get_string('subscribe', 'forum');
7406 $url = new moodle_url('/mod/forum/subscribe.php', array('id'=>$forumobject->id, 'sesskey'=>sesskey()));
7407 $forumnode->add($linktext, $url, navigation_node::TYPE_SETTING);
7410 if (has_capability('mod/forum:viewsubscribers', $PAGE->cm->context)){
7411 $url = new moodle_url('/mod/forum/subscribers.php', array('id'=>$forumobject->id));
7412 $forumnode->add(get_string('showsubscribers', 'forum'), $url, navigation_node::TYPE_SETTING);
7415 if ($enrolled && forum_tp_can_track_forums($forumobject)) { // keep tracking info for users with suspended enrolments
7416 if ($forumobject->trackingtype != FORUM_TRACKING_OPTIONAL) {
7417 //tracking forced on or off in forum settings so dont provide a link here to change it
7418 //could add unclickable text like for forced subscription but not sure this justifies adding another menu item
7419 } else {
7420 if (forum_tp_is_tracked($forumobject)) {
7421 $linktext = get_string('notrackforum', 'forum');
7422 } else {
7423 $linktext = get_string('trackforum', 'forum');
7425 $url = new moodle_url('/mod/forum/settracking.php', array('id'=>$forumobject->id));
7426 $forumnode->add($linktext, $url, navigation_node::TYPE_SETTING);
7430 if ($enrolled && !empty($CFG->enablerssfeeds) && !empty($CFG->forum_enablerssfeeds) && $forumobject->rsstype && $forumobject->rssarticles) {
7432 if (!function_exists('rss_get_url')) {
7433 require_once("$CFG->libdir/rsslib.php");
7436 if ($forumobject->rsstype == 1) {
7437 $string = get_string('rsssubscriberssdiscussions','forum');
7438 } else {
7439 $string = get_string('rsssubscriberssposts','forum');
7441 if (!isloggedin()) {
7442 $userid = 0;
7443 } else {
7444 $userid = $USER->id;
7446 $url = new moodle_url(rss_get_url($PAGE->cm->context->id, $userid, "mod_forum", $forumobject->id));
7447 $forumnode->add($string, $url, settings_navigation::TYPE_SETTING, null, null, new pix_icon('i/rss', ''));
7452 * Abstract class used by forum subscriber selection controls
7453 * @package mod-forum
7454 * @copyright 2009 Sam Hemelryk
7455 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7457 abstract class forum_subscriber_selector_base extends user_selector_base {
7460 * The id of the forum this selector is being used for
7461 * @var int
7463 protected $forumid = null;
7465 * The context of the forum this selector is being used for
7466 * @var object
7468 protected $context = null;
7470 * The id of the current group
7471 * @var int
7473 protected $currentgroup = null;
7476 * Constructor method
7477 * @param string $name
7478 * @param array $options
7480 public function __construct($name, $options) {
7481 $options['accesscontext'] = $options['context'];
7482 parent::__construct($name, $options);
7483 if (isset($options['context'])) {
7484 $this->context = $options['context'];
7486 if (isset($options['currentgroup'])) {
7487 $this->currentgroup = $options['currentgroup'];
7489 if (isset($options['forumid'])) {
7490 $this->forumid = $options['forumid'];
7495 * Returns an array of options to seralise and store for searches
7497 * @return array
7499 protected function get_options() {
7500 global $CFG;
7501 $options = parent::get_options();
7502 $options['file'] = substr(__FILE__, strlen($CFG->dirroot.'/'));
7503 $options['context'] = $this->context;
7504 $options['currentgroup'] = $this->currentgroup;
7505 $options['forumid'] = $this->forumid;
7506 return $options;
7512 * A user selector control for potential subscribers to the selected forum
7513 * @package mod-forum
7514 * @copyright 2009 Sam Hemelryk
7515 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7517 class forum_potential_subscriber_selector extends forum_subscriber_selector_base {
7520 * If set to true EVERYONE in this course is force subscribed to this forum
7521 * @var bool
7523 protected $forcesubscribed = false;
7525 * Can be used to store existing subscribers so that they can be removed from
7526 * the potential subscribers list
7528 protected $existingsubscribers = array();
7531 * Constructor method
7532 * @param string $name
7533 * @param array $options
7535 public function __construct($name, $options) {
7536 parent::__construct($name, $options);
7537 if (isset($options['forcesubscribed'])) {
7538 $this->forcesubscribed=true;
7543 * Returns an arary of options for this control
7544 * @return array
7546 protected function get_options() {
7547 $options = parent::get_options();
7548 if ($this->forcesubscribed===true) {
7549 $options['forcesubscribed']=1;
7551 return $options;
7555 * Finds all potential users
7557 * Potential users are determined by checking for users with a capability
7558 * determined in {@see forum_get_potential_subscribers()}
7560 * @param string $search
7561 * @return array
7563 public function find_users($search) {
7564 global $DB;
7566 $availableusers = forum_get_potential_subscribers($this->context, $this->currentgroup, $this->required_fields_sql('u'), 'u.firstname ASC, u.lastname ASC');
7568 if (empty($availableusers)) {
7569 $availableusers = array();
7570 } else if ($search) {
7571 $search = strtolower($search);
7572 foreach ($availableusers as $key=>$user) {
7573 if (stripos($user->firstname, $search) === false && stripos($user->lastname, $search) === false) {
7574 unset($availableusers[$key]);
7579 // Unset any existing subscribers
7580 if (count($this->existingsubscribers)>0 && !$this->forcesubscribed) {
7581 foreach ($this->existingsubscribers as $group) {
7582 foreach ($group as $user) {
7583 if (array_key_exists($user->id, $availableusers)) {
7584 unset($availableusers[$user->id]);
7590 if ($this->forcesubscribed) {
7591 return array(get_string("existingsubscribers", 'forum') => $availableusers);
7592 } else {
7593 return array(get_string("potentialsubscribers", 'forum') => $availableusers);
7598 * Sets the existing subscribers
7599 * @param array $users
7601 public function set_existing_subscribers(array $users) {
7602 $this->existingsubscribers = $users;
7606 * Sets this forum as force subscribed or not
7608 public function set_force_subscribed($setting=true) {
7609 $this->forcesubscribed = true;
7614 * User selector control for removing subscribed users
7615 * @package mod-forum
7616 * @copyright 2009 Sam Hemelryk
7617 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7619 class forum_existing_subscriber_selector extends forum_subscriber_selector_base {
7622 * Finds all subscribed users
7624 * @param string $search
7625 * @return array
7627 public function find_users($search) {
7628 global $DB;
7629 list($wherecondition, $params) = $this->search_sql($search, 'u');
7630 $params['forumid'] = $this->forumid;
7632 // only active enrolled or everybody on the frontpage
7633 list($esql, $eparams) = get_enrolled_sql($this->context, '', $this->currentgroup, true);
7634 $params = array_merge($params, $eparams);
7636 $fields = $this->required_fields_sql('u');
7638 $subscribers = $DB->get_records_sql("SELECT $fields
7639 FROM {user} u
7640 JOIN ($esql) je ON je.id = u.id
7641 JOIN {forum_subscriptions} s ON s.userid = u.id
7642 WHERE $wherecondition AND s.forum = :forumid
7643 ORDER BY u.lastname ASC, u.firstname ASC", $params);
7645 return array(get_string("existingsubscribers", 'forum') => $subscribers);
7651 * Adds information about unread messages, that is only required for the course view page (and
7652 * similar), to the course-module object.
7653 * @param cm_info $cm Course-module object
7655 function forum_cm_info_view(cm_info $cm) {
7656 global $CFG;
7658 // Get tracking status (once per request)
7659 static $initialised;
7660 static $usetracking, $strunreadpostsone;
7661 if (!isset($initialised)) {
7662 if ($usetracking = forum_tp_can_track_forums()) {
7663 $strunreadpostsone = get_string('unreadpostsone', 'forum');
7665 $initialised = true;
7668 if ($usetracking) {
7669 if ($unread = forum_tp_count_forum_unread_posts($cm, $cm->get_course())) {
7670 $out = '<span class="unread"> <a href="' . $cm->get_url() . '">';
7671 if ($unread == 1) {
7672 $out .= $strunreadpostsone;
7673 } else {
7674 $out .= get_string('unreadpostsnumber', 'forum', $unread);
7676 $out .= '</a></span>';
7677 $cm->set_after_link($out);
7683 * Return a list of page types
7684 * @param string $pagetype current page type
7685 * @param stdClass $parentcontext Block's parent context
7686 * @param stdClass $currentcontext Current context of block
7688 function forum_page_type_list($pagetype, $parentcontext, $currentcontext) {
7689 $forum_pagetype = array(
7690 'mod-forum-*'=>get_string('page-mod-forum-x', 'forum'),
7691 'mod-forum-view'=>get_string('page-mod-forum-view', 'forum'),
7692 'mod-forum-discuss'=>get_string('page-mod-forum-discuss', 'forum')
7694 return $forum_pagetype;
7698 * Gets all of the courses where the provided user has posted in a forum.
7700 * @global moodle_database $DB The database connection
7701 * @param stdClass $user The user who's posts we are looking for
7702 * @param bool $discussionsonly If true only look for discussions started by the user
7703 * @param bool $includecontexts If set to trye contexts for the courses will be preloaded
7704 * @param int $limitfrom The offset of records to return
7705 * @param int $limitnum The number of records to return
7706 * @return array An array of courses
7708 function forum_get_courses_user_posted_in($user, $discussionsonly = false, $includecontexts = true, $limitfrom = null, $limitnum = null) {
7709 global $DB;
7711 // If we are only after discussions we need only look at the forum_discussions
7712 // table and join to the userid there. If we are looking for posts then we need
7713 // to join to the forum_posts table.
7714 if (!$discussionsonly) {
7715 $joinsql = 'JOIN {forum_discussions} fd ON fd.course = c.id
7716 JOIN {forum_posts} fp ON fp.discussion = fd.id';
7717 $wheresql = 'fp.userid = :userid';
7718 $params = array('userid' => $user->id);
7719 } else {
7720 $joinsql = 'JOIN {forum_discussions} fd ON fd.course = c.id';
7721 $wheresql = 'fd.userid = :userid';
7722 $params = array('userid' => $user->id);
7725 // Join to the context table so that we can preload contexts if required.
7726 if ($includecontexts) {
7727 list($ctxselect, $ctxjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
7728 } else {
7729 $ctxselect = '';
7730 $ctxjoin = '';
7733 // Now we need to get all of the courses to search.
7734 // All courses where the user has posted within a forum will be returned.
7735 $sql = "SELECT DISTINCT c.* $ctxselect
7736 FROM {course} c
7737 $joinsql
7738 $ctxjoin
7739 WHERE $wheresql";
7740 $courses = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
7741 if ($includecontexts) {
7742 array_map('context_instance_preload', $courses);
7744 return $courses;
7748 * Gets all of the forums a user has posted in for one or more courses.
7750 * @global moodle_database $DB
7751 * @param stdClass $user
7752 * @param array $courseids An array of courseids to search or if not provided
7753 * all courses the user has posted within
7754 * @param bool $discussionsonly If true then only forums where the user has started
7755 * a discussion will be returned.
7756 * @param int $limitfrom The offset of records to return
7757 * @param int $limitnum The number of records to return
7758 * @return array An array of forums the user has posted within in the provided courses
7760 function forum_get_forums_user_posted_in($user, array $courseids = null, $discussionsonly = false, $limitfrom = null, $limitnum = null) {
7761 global $DB;
7763 $where = array("m.name = 'forum'");
7764 $params = array();
7765 if (!is_null($courseids)) {
7766 list($coursewhere, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED, 'courseid');
7767 $where[] = 'f.course '.$coursewhere;
7769 if (!$discussionsonly) {
7770 $joinsql = 'JOIN {forum_discussions} fd ON fd.forum = f.id
7771 JOIN {forum_posts} fp ON fp.discussion = fd.id';
7772 $where[] = 'fp.userid = :userid';
7773 } else {
7774 $joinsql = 'JOIN {forum_discussions} fd ON fd.forum = f.id';
7775 $where[] = 'fd.userid = :userid';
7777 $params['userid'] = $user->id;
7778 $wheresql = join(' AND ', $where);
7780 $sql = "SELECT DISTINCT f.*, cm.id AS cmid
7781 FROM {forum} f
7782 JOIN {course_modules} cm ON cm.instance = f.id
7783 JOIN {modules} m ON m.id = cm.module
7784 $joinsql
7785 WHERE $wheresql";
7786 $courseforums = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
7787 return $courseforums;
7791 * Returns posts made by the selected user in the requested courses.
7793 * This method can be used to return all of the posts made by the requested user
7794 * within the given courses.
7795 * For each course the access of the current user and requested user is checked
7796 * and then for each post access to the post and forum is checked as well.
7798 * This function is safe to use with usercapabilities.
7800 * @global moodle_database $DB
7801 * @param stdClass $user The user whose posts we want to get
7802 * @param array $courses The courses to search
7803 * @param bool $musthaveaccess If set to true errors will be thrown if the user
7804 * cannot access one or more of the courses to search
7805 * @param bool $discussionsonly If set to true only discussion starting posts
7806 * will be returned.
7807 * @param int $limitfrom The offset of records to return
7808 * @param int $limitnum The number of records to return
7809 * @return stdClass An object the following properties
7810 * ->totalcount: the total number of posts made by the requested user
7811 * that the current user can see.
7812 * ->courses: An array of courses the current user can see that the
7813 * requested user has posted in.
7814 * ->forums: An array of forums relating to the posts returned in the
7815 * property below.
7816 * ->posts: An array containing the posts to show for this request.
7818 function forum_get_posts_by_user($user, array $courses, $musthaveaccess = false, $discussionsonly = false, $limitfrom = 0, $limitnum = 50) {
7819 global $DB, $USER;
7821 $return = new stdClass;
7822 $return->totalcount = 0; // The total number of posts that the current user is able to view
7823 $return->courses = array(); // The courses the current user can access
7824 $return->forums = array(); // The forums that the current user can access that contain posts
7825 $return->posts = array(); // The posts to display
7827 // First up a small sanity check. If there are no courses to check we can
7828 // return immediately, there is obviously nothing to search.
7829 if (empty($courses)) {
7830 return $return;
7833 // A couple of quick setups
7834 $isloggedin = isloggedin();
7835 $isguestuser = $isloggedin && isguestuser();
7836 $iscurrentuser = $isloggedin && $USER->id == $user->id;
7838 // Checkout whether or not the current user has capabilities over the requested
7839 // user and if so they have the capabilities required to view the requested
7840 // users content.
7841 $usercontext = get_context_instance(CONTEXT_USER, $user->id, MUST_EXIST);
7842 $hascapsonuser = !$iscurrentuser && $DB->record_exists('role_assignments', array('userid' => $USER->id, 'contextid' => $usercontext->id));
7843 $hascapsonuser = $hascapsonuser && has_all_capabilities(array('moodle/user:viewdetails', 'moodle/user:readuserposts'), $usercontext);
7845 // Before we actually search each course we need to check the user's access to the
7846 // course. If the user doesn't have the appropraite access then we either throw an
7847 // error if a particular course was requested or we just skip over the course.
7848 foreach ($courses as $course) {
7849 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id, MUST_EXIST);
7850 if ($iscurrentuser || $hascapsonuser) {
7851 // If it is the current user, or the current user has capabilities to the
7852 // requested user then all we need to do is check the requested users
7853 // current access to the course.
7854 // Note: There is no need to check group access or anything of the like
7855 // as either the current user is the requested user, or has granted
7856 // capabilities on the requested user. Either way they can see what the
7857 // requested user posted, although its VERY unlikely in the `parent` situation
7858 // that the current user will be able to view the posts in context.
7859 if (!is_viewing($coursecontext, $user) && !is_enrolled($coursecontext, $user)) {
7860 // Need to have full access to a course to see the rest of own info
7861 if ($musthaveaccess) {
7862 print_error('errorenrolmentrequired', 'forum');
7864 continue;
7866 } else {
7867 // Check whether the current user is enrolled or has access to view the course
7868 // if they don't we immediately have a problem.
7869 if (!can_access_course($course)) {
7870 if ($musthaveaccess) {
7871 print_error('errorenrolmentrequired', 'forum');
7873 continue;
7876 // Check whether the requested user is enrolled or has access to view the course
7877 // if they don't we immediately have a problem.
7878 if (!can_access_course($course, $user)) {
7879 if ($musthaveaccess) {
7880 print_error('notenrolled', 'forum');
7882 continue;
7885 // If groups are in use and enforced throughout the course then make sure
7886 // we can meet in at least one course level group.
7887 // Note that we check if either the current user or the requested user have
7888 // the capability to access all groups. This is because with that capability
7889 // a user in group A could post in the group B forum. Grrrr.
7890 if (groups_get_course_groupmode($course) == SEPARATEGROUPS && $course->groupmodeforce
7891 && !has_capability('moodle/site:accessallgroups', $coursecontext) && !has_capability('moodle/site:accessallgroups', $coursecontext, $user->id)) {
7892 // If its the guest user to bad... the guest user cannot access groups
7893 if (!$isloggedin or $isguestuser) {
7894 // do not use require_login() here because we might have already used require_login($course)
7895 if ($musthaveaccess) {
7896 redirect(get_login_url());
7898 continue;
7900 // Get the groups of the current user
7901 $mygroups = array_keys(groups_get_all_groups($course->id, $USER->id, $course->defaultgroupingid, 'g.id, g.name'));
7902 // Get the groups the requested user is a member of
7903 $usergroups = array_keys(groups_get_all_groups($course->id, $user->id, $course->defaultgroupingid, 'g.id, g.name'));
7904 // Check whether they are members of the same group. If they are great.
7905 $intersect = array_intersect($mygroups, $usergroups);
7906 if (empty($intersect)) {
7907 // But they're not... if it was a specific course throw an error otherwise
7908 // just skip this course so that it is not searched.
7909 if ($musthaveaccess) {
7910 print_error("groupnotamember", '', $CFG->wwwroot."/course/view.php?id=$course->id");
7912 continue;
7916 // Woo hoo we got this far which means the current user can search this
7917 // this course for the requested user. Although this is only the course accessibility
7918 // handling that is complete, the forum accessibility tests are yet to come.
7919 $return->courses[$course->id] = $course;
7921 // No longer beed $courses array - lose it not it may be big
7922 unset($courses);
7924 // Make sure that we have some courses to search
7925 if (empty($return->courses)) {
7926 // If we don't have any courses to search then the reality is that the current
7927 // user doesn't have access to any courses is which the requested user has posted.
7928 // Although we do know at this point that the requested user has posts.
7929 if ($musthaveaccess) {
7930 print_error('permissiondenied');
7931 } else {
7932 return $return;
7936 // Next step: Collect all of the forums that we will want to search.
7937 // It is important to note that this step isn't actually about searching, it is
7938 // about determining which forums we can search by testing accessibility.
7939 $forums = forum_get_forums_user_posted_in($user, array_keys($return->courses), $discussionsonly);
7941 // Will be used to build the where conditions for the search
7942 $forumsearchwhere = array();
7943 // Will be used to store the where condition params for the search
7944 $forumsearchparams = array();
7945 // Will record forums where the user can freely access everything
7946 $forumsearchfullaccess = array();
7947 // DB caching friendly
7948 $now = round(time(), -2);
7949 // For each course to search we want to find the forums the user has posted in
7950 // and providing the current user can access the forum create a search condition
7951 // for the forum to get the requested users posts.
7952 foreach ($return->courses as $course) {
7953 // Now we need to get the forums
7954 $modinfo = get_fast_modinfo($course);
7955 if (empty($modinfo->instances['forum'])) {
7956 // hmmm, no forums? well at least its easy... skip!
7957 continue;
7959 // Iterate
7960 foreach ($modinfo->get_instances_of('forum') as $forumid => $cm) {
7961 if (!$cm->uservisible or !isset($forums[$forumid])) {
7962 continue;
7964 // Get the forum in question
7965 $forum = $forums[$forumid];
7966 // This is needed for functionality later on in the forum code....
7967 $forum->cm = $cm;
7969 // Check that either the current user can view the forum, or that the
7970 // current user has capabilities over the requested user and the requested
7971 // user can view the discussion
7972 if (!has_capability('mod/forum:viewdiscussion', $cm->context) && !($hascapsonuser && has_capability('mod/forum:viewdiscussion', $cm->context, $user->id))) {
7973 continue;
7976 // This will contain forum specific where clauses
7977 $forumsearchselect = array();
7978 if (!$iscurrentuser && !$hascapsonuser) {
7979 // Make sure we check group access
7980 if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $cm->context)) {
7981 $groups = $modinfo->get_groups($cm->groupingid);
7982 $groups[] = -1;
7983 list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
7984 $forumsearchparams = array_merge($forumsearchparams, $groupid_params);
7985 $forumsearchselect[] = "d.groupid $groupid_sql";
7988 // hidden timed discussions
7989 if (!empty($CFG->forum_enabletimedposts) && !has_capability('mod/forum:viewhiddentimedposts', $cm->context)) {
7990 $forumsearchselect[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
7991 $forumsearchparams['userid'.$forumid] = $user->id;
7992 $forumsearchparams['timestart'.$forumid] = $now;
7993 $forumsearchparams['timeend'.$forumid] = $now;
7996 // qanda access
7997 if ($forum->type == 'qanda' && !has_capability('mod/forum:viewqandawithoutposting', $cm->context)) {
7998 // We need to check whether the user has posted in the qanda forum.
7999 $discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $user->id);
8000 if (!empty($discussionspostedin)) {
8001 $forumonlydiscussions = array(); // Holds discussion ids for the discussions the user is allowed to see in this forum.
8002 foreach ($discussionspostedin as $d) {
8003 $forumonlydiscussions[] = $d->id;
8005 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forumonlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
8006 $forumsearchparams = array_merge($forumsearchparams, $discussionid_params);
8007 $forumsearchselect[] = "(d.id $discussionid_sql OR p.parent = 0)";
8008 } else {
8009 $forumsearchselect[] = "p.parent = 0";
8014 if (count($forumsearchselect) > 0) {
8015 $forumsearchwhere[] = "(d.forum = :forum{$forumid} AND ".implode(" AND ", $forumsearchselect).")";
8016 $forumsearchparams['forum'.$forumid] = $forumid;
8017 } else {
8018 $forumsearchfullaccess[] = $forumid;
8020 } else {
8021 // The current user/parent can see all of their own posts
8022 $forumsearchfullaccess[] = $forumid;
8027 // If we dont have any search conditions, and we don't have any forums where
8028 // the user has full access then we just return the default.
8029 if (empty($forumsearchwhere) && empty($forumsearchfullaccess)) {
8030 return $return;
8033 // Prepare a where condition for the full access forums.
8034 if (count($forumsearchfullaccess) > 0) {
8035 list($fullidsql, $fullidparams) = $DB->get_in_or_equal($forumsearchfullaccess, SQL_PARAMS_NAMED, 'fula');
8036 $forumsearchparams = array_merge($forumsearchparams, $fullidparams);
8037 $forumsearchwhere[] = "(d.forum $fullidsql)";
8040 // Prepare SQL to both count and search
8041 $userfields = user_picture::fields('u', null, 'userid');
8042 $countsql = 'SELECT COUNT(*) ';
8043 $selectsql = 'SELECT p.*, d.forum, d.name AS discussionname, '.$userfields.' ';
8044 $wheresql = implode(" OR ", $forumsearchwhere);
8046 if ($discussionsonly) {
8047 if ($wheresql == '') {
8048 $wheresql = 'p.parent = 0';
8049 } else {
8050 $wheresql = 'p.parent = 0 AND ('.$wheresql.')';
8054 $sql = "FROM {forum_posts} p
8055 JOIN {forum_discussions} d ON d.id = p.discussion
8056 JOIN {user} u ON u.id = p.userid
8057 WHERE ($wheresql)
8058 AND p.userid = :userid ";
8059 $orderby = "ORDER BY p.modified DESC";
8060 $forumsearchparams['userid'] = $user->id;
8062 // Set the total number posts made by the requested user that the current user can see
8063 $return->totalcount = $DB->count_records_sql($countsql.$sql, $forumsearchparams);
8064 // Set the collection of posts that has been requested
8065 $return->posts = $DB->get_records_sql($selectsql.$sql.$orderby, $forumsearchparams, $limitfrom, $limitnum);
8067 // We need to build an array of forums for which posts will be displayed.
8068 // We do this here to save the caller needing to retrieve them themselves before
8069 // printing these forums posts. Given we have the forums already there is
8070 // practically no overhead here.
8071 foreach ($return->posts as $post) {
8072 if (!array_key_exists($post->forum, $return->forums)) {
8073 $return->forums[$post->forum] = $forums[$post->forum];
8077 return $return;