Revert "MDL-32125 mod_forum: updating subscription mode not reflected"
[moodle.git] / mod / forum / lib.php
blob7a6889f9cff8558815fbff9e3428ee7b58390fdf
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 $discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC');
157 if (!empty($discussions)) {
158 if (count($discussions) > 1) {
159 echo $OUTPUT->notification(get_string('warnformorepost', 'forum'));
161 $discussion = array_pop($discussions);
162 } else {
163 // try to recover by creating initial discussion - MDL-16262
164 $discussion = new stdClass();
165 $discussion->course = $forum->course;
166 $discussion->forum = $forum->id;
167 $discussion->name = $forum->name;
168 $discussion->assessed = $forum->assessed;
169 $discussion->message = $forum->intro;
170 $discussion->messageformat = $forum->introformat;
171 $discussion->messagetrust = true;
172 $discussion->mailnow = false;
173 $discussion->groupid = -1;
175 $message = '';
177 forum_add_discussion($discussion, null, $message);
179 if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
180 print_error('cannotadd', 'forum');
183 if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
184 print_error('cannotfindfirstpost', 'forum');
187 $cm = get_coursemodule_from_instance('forum', $forum->id);
188 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
190 if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
191 // ugly hack - we need to copy the files somehow
192 $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
193 $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
195 $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
198 $post->subject = $forum->name;
199 $post->message = $forum->intro;
200 $post->messageformat = $forum->introformat;
201 $post->messagetrust = trusttext_trusted($modcontext);
202 $post->modified = $forum->timemodified;
203 $post->userid = $USER->id; // MDL-18599, so that current teacher can take ownership of activities
205 $DB->update_record('forum_posts', $post);
206 $discussion->name = $forum->name;
207 $DB->update_record('forum_discussions', $discussion);
210 $DB->update_record('forum', $forum);
212 forum_grade_item_update($forum);
214 return true;
219 * Given an ID of an instance of this module,
220 * this function will permanently delete the instance
221 * and any data that depends on it.
223 * @global object
224 * @param int $id forum instance id
225 * @return bool success
227 function forum_delete_instance($id) {
228 global $DB;
230 if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
231 return false;
233 if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
234 return false;
236 if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
237 return false;
240 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
242 // now get rid of all files
243 $fs = get_file_storage();
244 $fs->delete_area_files($context->id);
246 $result = true;
248 if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
249 foreach ($discussions as $discussion) {
250 if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
251 $result = false;
256 if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
257 $result = false;
260 forum_tp_delete_read_records(-1, -1, -1, $forum->id);
262 if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
263 $result = false;
266 forum_grade_item_delete($forum);
268 return $result;
273 * Indicates API features that the forum supports.
275 * @uses FEATURE_GROUPS
276 * @uses FEATURE_GROUPINGS
277 * @uses FEATURE_GROUPMEMBERSONLY
278 * @uses FEATURE_MOD_INTRO
279 * @uses FEATURE_COMPLETION_TRACKS_VIEWS
280 * @uses FEATURE_COMPLETION_HAS_RULES
281 * @uses FEATURE_GRADE_HAS_GRADE
282 * @uses FEATURE_GRADE_OUTCOMES
283 * @param string $feature
284 * @return mixed True if yes (some features may use other values)
286 function forum_supports($feature) {
287 switch($feature) {
288 case FEATURE_GROUPS: return true;
289 case FEATURE_GROUPINGS: return true;
290 case FEATURE_GROUPMEMBERSONLY: return true;
291 case FEATURE_MOD_INTRO: return true;
292 case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
293 case FEATURE_COMPLETION_HAS_RULES: return true;
294 case FEATURE_GRADE_HAS_GRADE: return true;
295 case FEATURE_GRADE_OUTCOMES: return true;
296 case FEATURE_RATE: return true;
297 case FEATURE_BACKUP_MOODLE2: return true;
298 case FEATURE_SHOW_DESCRIPTION: return true;
300 default: return null;
306 * Obtains the automatic completion state for this forum based on any conditions
307 * in forum settings.
309 * @global object
310 * @global object
311 * @param object $course Course
312 * @param object $cm Course-module
313 * @param int $userid User ID
314 * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
315 * @return bool True if completed, false if not. (If no conditions, then return
316 * value depends on comparison type)
318 function forum_get_completion_state($course,$cm,$userid,$type) {
319 global $CFG,$DB;
321 // Get forum details
322 if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
323 throw new Exception("Can't find forum {$cm->instance}");
326 $result=$type; // Default return value
328 $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
329 $postcountsql="
330 SELECT
331 COUNT(1)
332 FROM
333 {forum_posts} fp
334 INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
335 WHERE
336 fp.userid=:userid AND fd.forum=:forumid";
338 if ($forum->completiondiscussions) {
339 $value = $forum->completiondiscussions <=
340 $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
341 if ($type == COMPLETION_AND) {
342 $result = $result && $value;
343 } else {
344 $result = $result || $value;
347 if ($forum->completionreplies) {
348 $value = $forum->completionreplies <=
349 $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
350 if ($type==COMPLETION_AND) {
351 $result = $result && $value;
352 } else {
353 $result = $result || $value;
356 if ($forum->completionposts) {
357 $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
358 if ($type == COMPLETION_AND) {
359 $result = $result && $value;
360 } else {
361 $result = $result || $value;
365 return $result;
370 * Function to be run periodically according to the moodle cron
371 * Finds all posts that have yet to be mailed out, and mails them
372 * out to all subscribers
374 * @global object
375 * @global object
376 * @global object
377 * @uses CONTEXT_MODULE
378 * @uses CONTEXT_COURSE
379 * @uses SITEID
380 * @uses FORMAT_PLAIN
381 * @return void
383 function forum_cron() {
384 global $CFG, $USER, $DB;
386 $site = get_site();
388 // all users that are subscribed to any post that needs sending
389 $users = array();
391 // status arrays
392 $mailcount = array();
393 $errorcount = array();
395 // caches
396 $discussions = array();
397 $forums = array();
398 $courses = array();
399 $coursemodules = array();
400 $subscribedusers = array();
403 // Posts older than 2 days will not be mailed. This is to avoid the problem where
404 // cron has not been running for a long time, and then suddenly people are flooded
405 // with mail from the past few weeks or months
406 $timenow = time();
407 $endtime = $timenow - $CFG->maxeditingtime;
408 $starttime = $endtime - 48 * 3600; // Two days earlier
410 if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
411 // Mark them all now as being mailed. It's unlikely but possible there
412 // might be an error later so that a post is NOT actually mailed out,
413 // but since mail isn't crucial, we can accept this risk. Doing it now
414 // prevents the risk of duplicated mails, which is a worse problem.
416 if (!forum_mark_old_posts_as_mailed($endtime)) {
417 mtrace('Errors occurred while trying to mark some posts as being mailed.');
418 return false; // Don't continue trying to mail them, in case we are in a cron loop
421 // checking post validity, and adding users to loop through later
422 foreach ($posts as $pid => $post) {
424 $discussionid = $post->discussion;
425 if (!isset($discussions[$discussionid])) {
426 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
427 $discussions[$discussionid] = $discussion;
428 } else {
429 mtrace('Could not find discussion '.$discussionid);
430 unset($posts[$pid]);
431 continue;
434 $forumid = $discussions[$discussionid]->forum;
435 if (!isset($forums[$forumid])) {
436 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
437 $forums[$forumid] = $forum;
438 } else {
439 mtrace('Could not find forum '.$forumid);
440 unset($posts[$pid]);
441 continue;
444 $courseid = $forums[$forumid]->course;
445 if (!isset($courses[$courseid])) {
446 if ($course = $DB->get_record('course', array('id' => $courseid))) {
447 $courses[$courseid] = $course;
448 } else {
449 mtrace('Could not find course '.$courseid);
450 unset($posts[$pid]);
451 continue;
454 if (!isset($coursemodules[$forumid])) {
455 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
456 $coursemodules[$forumid] = $cm;
457 } else {
458 mtrace('Could not find course module for forum '.$forumid);
459 unset($posts[$pid]);
460 continue;
465 // caching subscribed users of each forum
466 if (!isset($subscribedusers[$forumid])) {
467 $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
468 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext, "u.*")) {
469 foreach ($subusers as $postuser) {
470 unset($postuser->description); // not necessary
471 // this user is subscribed to this forum
472 $subscribedusers[$forumid][$postuser->id] = $postuser->id;
473 // this user is a user we have to process later
474 $users[$postuser->id] = $postuser;
476 unset($subusers); // release memory
480 $mailcount[$pid] = 0;
481 $errorcount[$pid] = 0;
485 if ($users && $posts) {
487 $urlinfo = parse_url($CFG->wwwroot);
488 $hostname = $urlinfo['host'];
490 foreach ($users as $userto) {
492 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
494 // set this so that the capabilities are cached, and environment matches receiving user
495 cron_setup_user($userto);
497 mtrace('Processing user '.$userto->id);
499 // init caches
500 $userto->viewfullnames = array();
501 $userto->canpost = array();
502 $userto->markposts = array();
504 // reset the caches
505 foreach ($coursemodules as $forumid=>$unused) {
506 $coursemodules[$forumid]->cache = new stdClass();
507 $coursemodules[$forumid]->cache->caps = array();
508 unset($coursemodules[$forumid]->uservisible);
511 foreach ($posts as $pid => $post) {
513 // Set up the environment for the post, discussion, forum, course
514 $discussion = $discussions[$post->discussion];
515 $forum = $forums[$discussion->forum];
516 $course = $courses[$forum->course];
517 $cm =& $coursemodules[$forum->id];
519 // Do some checks to see if we can bail out now
520 // Only active enrolled users are in the list of subscribers
521 if (!isset($subscribedusers[$forum->id][$userto->id])) {
522 continue; // user does not subscribe to this forum
525 // Don't send email if the forum is Q&A and the user has not posted
526 // Initial topics are still mailed
527 if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id) && $pid != $discussion->firstpost) {
528 mtrace('Did not email '.$userto->id.' because user has not posted in discussion');
529 continue;
532 // Get info about the sending user
533 if (array_key_exists($post->userid, $users)) { // we might know him/her already
534 $userfrom = $users[$post->userid];
535 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
536 unset($userfrom->description); // not necessary
537 $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
538 } else {
539 mtrace('Could not find user '.$post->userid);
540 continue;
543 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
545 // setup global $COURSE properly - needed for roles and languages
546 cron_setup_user($userto, $course);
548 // Fill caches
549 if (!isset($userto->viewfullnames[$forum->id])) {
550 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
551 $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
553 if (!isset($userto->canpost[$discussion->id])) {
554 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
555 $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
557 if (!isset($userfrom->groups[$forum->id])) {
558 if (!isset($userfrom->groups)) {
559 $userfrom->groups = array();
560 $users[$userfrom->id]->groups = array();
562 $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
563 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
566 // Make sure groups allow this user to see this email
567 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used
568 if (!groups_group_exists($discussion->groupid)) { // Can't find group
569 continue; // Be safe and don't send it to anyone
572 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
573 // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
574 continue;
578 // Make sure we're allowed to see it...
579 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
580 mtrace('user '.$userto->id. ' can not see '.$post->id);
581 continue;
584 // OK so we need to send the email.
586 // Does the user want this post in a digest? If so postpone it for now.
587 if ($userto->maildigest > 0) {
588 // This user wants the mails to be in digest form
589 $queue = new stdClass();
590 $queue->userid = $userto->id;
591 $queue->discussionid = $discussion->id;
592 $queue->postid = $post->id;
593 $queue->timemodified = $post->created;
594 $DB->insert_record('forum_queue', $queue);
595 continue;
599 // Prepare to actually send the post now, and build up the content
601 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
603 $userfrom->customheaders = array ( // Headers to make emails easier to track
604 'Precedence: Bulk',
605 'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
606 'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
607 'Message-ID: <moodlepost'.$post->id.'@'.$hostname.'>',
608 'X-Course-Id: '.$course->id,
609 'X-Course-Name: '.format_string($course->fullname, true)
612 if ($post->parent) { // This post is a reply, so add headers for threading (see MDL-22551)
613 $userfrom->customheaders[] = 'In-Reply-To: <moodlepost'.$post->parent.'@'.$hostname.'>';
614 $userfrom->customheaders[] = 'References: <moodlepost'.$post->parent.'@'.$hostname.'>';
617 $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
619 $postsubject = html_to_text("$shortname: ".format_string($post->subject, true));
620 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
621 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
623 // Send the post now!
625 mtrace('Sending ', '');
627 $eventdata = new stdClass();
628 $eventdata->component = 'mod_forum';
629 $eventdata->name = 'posts';
630 $eventdata->userfrom = $userfrom;
631 $eventdata->userto = $userto;
632 $eventdata->subject = $postsubject;
633 $eventdata->fullmessage = $posttext;
634 $eventdata->fullmessageformat = FORMAT_PLAIN;
635 $eventdata->fullmessagehtml = $posthtml;
636 $eventdata->notification = 1;
638 $smallmessagestrings = new stdClass();
639 $smallmessagestrings->user = fullname($userfrom);
640 $smallmessagestrings->forumname = "$shortname: ".format_string($forum->name,true).": ".$discussion->name;
641 $smallmessagestrings->message = $post->message;
642 //make sure strings are in message recipients language
643 $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
645 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
646 $eventdata->contexturlname = $discussion->name;
648 $mailresult = message_send($eventdata);
649 if (!$mailresult){
650 mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
651 " ($userto->email) .. not trying again.");
652 add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
653 substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
654 $errorcount[$post->id]++;
655 } else {
656 $mailcount[$post->id]++;
658 // Mark post as read if forum_usermarksread is set off
659 if (!$CFG->forum_usermarksread) {
660 $userto->markposts[$post->id] = $post->id;
664 mtrace('post '.$post->id. ': '.$post->subject);
667 // mark processed posts as read
668 forum_tp_mark_posts_read($userto, $userto->markposts);
672 if ($posts) {
673 foreach ($posts as $post) {
674 mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
675 if ($errorcount[$post->id]) {
676 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
681 // release some memory
682 unset($subscribedusers);
683 unset($mailcount);
684 unset($errorcount);
686 cron_setup_user();
688 $sitetimezone = $CFG->timezone;
690 // Now see if there are any digest mails waiting to be sent, and if we should send them
692 mtrace('Starting digest processing...');
694 @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
696 if (!isset($CFG->digestmailtimelast)) { // To catch the first time
697 set_config('digestmailtimelast', 0);
700 $timenow = time();
701 $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
703 // Delete any really old ones (normally there shouldn't be any)
704 $weekago = $timenow - (7 * 24 * 3600);
705 $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
706 mtrace ('Cleaned old digest records');
708 if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
710 mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
712 $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
714 if ($digestposts_rs->valid()) {
716 // We have work to do
717 $usermailcount = 0;
719 //caches - reuse the those filled before too
720 $discussionposts = array();
721 $userdiscussions = array();
723 foreach ($digestposts_rs as $digestpost) {
724 if (!isset($users[$digestpost->userid])) {
725 if ($user = $DB->get_record('user', array('id' => $digestpost->userid))) {
726 $users[$digestpost->userid] = $user;
727 } else {
728 continue;
731 $postuser = $users[$digestpost->userid];
733 if (!isset($posts[$digestpost->postid])) {
734 if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
735 $posts[$digestpost->postid] = $post;
736 } else {
737 continue;
740 $discussionid = $digestpost->discussionid;
741 if (!isset($discussions[$discussionid])) {
742 if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
743 $discussions[$discussionid] = $discussion;
744 } else {
745 continue;
748 $forumid = $discussions[$discussionid]->forum;
749 if (!isset($forums[$forumid])) {
750 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
751 $forums[$forumid] = $forum;
752 } else {
753 continue;
757 $courseid = $forums[$forumid]->course;
758 if (!isset($courses[$courseid])) {
759 if ($course = $DB->get_record('course', array('id' => $courseid))) {
760 $courses[$courseid] = $course;
761 } else {
762 continue;
766 if (!isset($coursemodules[$forumid])) {
767 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
768 $coursemodules[$forumid] = $cm;
769 } else {
770 continue;
773 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
774 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
776 $digestposts_rs->close(); /// Finished iteration, let's close the resultset
778 // Data collected, start sending out emails to each user
779 foreach ($userdiscussions as $userid => $thesediscussions) {
781 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
783 cron_setup_user();
785 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
787 // First of all delete all the queue entries for this user
788 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
789 $userto = $users[$userid];
791 // Override the language and timezone of the "current" user, so that
792 // mail is customised for the receiver.
793 cron_setup_user($userto);
795 // init caches
796 $userto->viewfullnames = array();
797 $userto->canpost = array();
798 $userto->markposts = array();
800 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
802 $headerdata = new stdClass();
803 $headerdata->sitename = format_string($site->fullname, true);
804 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
806 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
807 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
809 $posthtml = "<head>";
810 /* foreach ($CFG->stylesheets as $stylesheet) {
811 //TODO: MDL-21120
812 $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
814 $posthtml .= "</head>\n<body id=\"email\">\n";
815 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
817 foreach ($thesediscussions as $discussionid) {
819 @set_time_limit(120); // to be reset for each post
821 $discussion = $discussions[$discussionid];
822 $forum = $forums[$discussion->forum];
823 $course = $courses[$forum->course];
824 $cm = $coursemodules[$forum->id];
826 //override language
827 cron_setup_user($userto, $course);
829 // Fill caches
830 if (!isset($userto->viewfullnames[$forum->id])) {
831 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
832 $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
834 if (!isset($userto->canpost[$discussion->id])) {
835 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
836 $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
839 $strforums = get_string('forums', 'forum');
840 $canunsubscribe = ! forum_is_forcesubscribed($forum);
841 $canreply = $userto->canpost[$discussion->id];
842 $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
844 $posttext .= "\n \n";
845 $posttext .= '=====================================================================';
846 $posttext .= "\n \n";
847 $posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
848 if ($discussion->name != $forum->name) {
849 $posttext .= " -> ".format_string($discussion->name,true);
851 $posttext .= "\n";
853 $posthtml .= "<p><font face=\"sans-serif\">".
854 "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
855 "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
856 "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
857 if ($discussion->name == $forum->name) {
858 $posthtml .= "</font></p>";
859 } else {
860 $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
862 $posthtml .= '<p>';
864 $postsarray = $discussionposts[$discussionid];
865 sort($postsarray);
867 foreach ($postsarray as $postid) {
868 $post = $posts[$postid];
870 if (array_key_exists($post->userid, $users)) { // we might know him/her already
871 $userfrom = $users[$post->userid];
872 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
873 $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
874 } else {
875 mtrace('Could not find user '.$post->userid);
876 continue;
879 if (!isset($userfrom->groups[$forum->id])) {
880 if (!isset($userfrom->groups)) {
881 $userfrom->groups = array();
882 $users[$userfrom->id]->groups = array();
884 $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
885 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
888 $userfrom->customheaders = array ("Precedence: Bulk");
890 if ($userto->maildigest == 2) {
891 // Subjects only
892 $by = new stdClass();
893 $by->name = fullname($userfrom);
894 $by->date = userdate($post->modified);
895 $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
896 $posttext .= "\n---------------------------------------------------------------------";
898 $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
899 $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>';
901 } else {
902 // The full treatment
903 $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
904 $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
906 // Create an array of postid's for this user to mark as read.
907 if (!$CFG->forum_usermarksread) {
908 $userto->markposts[$post->id] = $post->id;
912 if ($canunsubscribe) {
913 $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>";
914 } else {
915 $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
917 $posthtml .= '<hr size="1" noshade="noshade" /></p>';
919 $posthtml .= '</body>';
921 if (empty($userto->mailformat) || $userto->mailformat != 1) {
922 // This user DOESN'T want to receive HTML
923 $posthtml = '';
926 $attachment = $attachname='';
927 $usetrueaddress = true;
928 // Directly email forum digests rather than sending them via messaging, use the
929 // site shortname as 'from name', the noreply address will be used by email_to_user.
930 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname, $usetrueaddress, $CFG->forum_replytouser);
932 if (!$mailresult) {
933 mtrace("ERROR!");
934 echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
935 add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
936 } else {
937 mtrace("success.");
938 $usermailcount++;
940 // Mark post as read if forum_usermarksread is set off
941 forum_tp_mark_posts_read($userto, $userto->markposts);
945 /// We have finishied all digest emails, update $CFG->digestmailtimelast
946 set_config('digestmailtimelast', $timenow);
949 cron_setup_user();
951 if (!empty($usermailcount)) {
952 mtrace(get_string('digestsentusers', 'forum', $usermailcount));
955 if (!empty($CFG->forum_lastreadclean)) {
956 $timenow = time();
957 if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
958 set_config('forum_lastreadclean', $timenow);
959 mtrace('Removing old forum read tracking info...');
960 forum_tp_clean_read_records();
962 } else {
963 set_config('forum_lastreadclean', time());
967 return true;
971 * Builds and returns the body of the email notification in plain text.
973 * @global object
974 * @global object
975 * @uses CONTEXT_MODULE
976 * @param object $course
977 * @param object $cm
978 * @param object $forum
979 * @param object $discussion
980 * @param object $post
981 * @param object $userfrom
982 * @param object $userto
983 * @param boolean $bare
984 * @return string The email body in plain text format.
986 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
987 global $CFG, $USER;
989 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
991 if (!isset($userto->viewfullnames[$forum->id])) {
992 $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
993 } else {
994 $viewfullnames = $userto->viewfullnames[$forum->id];
997 if (!isset($userto->canpost[$discussion->id])) {
998 $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
999 } else {
1000 $canreply = $userto->canpost[$discussion->id];
1003 $by = New stdClass;
1004 $by->name = fullname($userfrom, $viewfullnames);
1005 $by->date = userdate($post->modified, "", $userto->timezone);
1007 $strbynameondate = get_string('bynameondate', 'forum', $by);
1009 $strforums = get_string('forums', 'forum');
1011 $canunsubscribe = ! forum_is_forcesubscribed($forum);
1013 $posttext = '';
1015 if (!$bare) {
1016 $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
1017 $posttext = "$shortname -> $strforums -> ".format_string($forum->name,true);
1019 if ($discussion->name != $forum->name) {
1020 $posttext .= " -> ".format_string($discussion->name,true);
1024 // add absolute file links
1025 $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
1027 $posttext .= "\n---------------------------------------------------------------------\n";
1028 $posttext .= format_string($post->subject,true);
1029 if ($bare) {
1030 $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1032 $posttext .= "\n".$strbynameondate."\n";
1033 $posttext .= "---------------------------------------------------------------------\n";
1034 $posttext .= format_text_email($post->message, $post->messageformat);
1035 $posttext .= "\n\n";
1036 $posttext .= forum_print_attachments($post, $cm, "text");
1038 if (!$bare && $canreply) {
1039 $posttext .= "---------------------------------------------------------------------\n";
1040 $posttext .= get_string("postmailinfo", "forum", $shortname)."\n";
1041 $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1043 if (!$bare && $canunsubscribe) {
1044 $posttext .= "\n---------------------------------------------------------------------\n";
1045 $posttext .= get_string("unsubscribe", "forum");
1046 $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1049 return $posttext;
1053 * Builds and returns the body of the email notification in html format.
1055 * @global object
1056 * @param object $course
1057 * @param object $cm
1058 * @param object $forum
1059 * @param object $discussion
1060 * @param object $post
1061 * @param object $userfrom
1062 * @param object $userto
1063 * @return string The email text in HTML format
1065 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1066 global $CFG;
1068 if ($userto->mailformat != 1) { // Needs to be HTML
1069 return '';
1072 if (!isset($userto->canpost[$discussion->id])) {
1073 $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course);
1074 } else {
1075 $canreply = $userto->canpost[$discussion->id];
1078 $strforums = get_string('forums', 'forum');
1079 $canunsubscribe = ! forum_is_forcesubscribed($forum);
1080 $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
1082 $posthtml = '<head>';
1083 /* foreach ($CFG->stylesheets as $stylesheet) {
1084 //TODO: MDL-21120
1085 $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1087 $posthtml .= '</head>';
1088 $posthtml .= "\n<body id=\"email\">\n\n";
1090 $posthtml .= '<div class="navbar">'.
1091 '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$shortname.'</a> &raquo; '.
1092 '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1093 '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1094 if ($discussion->name == $forum->name) {
1095 $posthtml .= '</div>';
1096 } else {
1097 $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1098 format_string($discussion->name,true).'</a></div>';
1100 $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1102 if ($canunsubscribe) {
1103 $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1104 <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1105 <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1108 $posthtml .= '</body>';
1110 return $posthtml;
1116 * @param object $course
1117 * @param object $user
1118 * @param object $mod TODO this is not used in this function, refactor
1119 * @param object $forum
1120 * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1122 function forum_user_outline($course, $user, $mod, $forum) {
1123 global $CFG;
1124 require_once("$CFG->libdir/gradelib.php");
1125 $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1126 if (empty($grades->items[0]->grades)) {
1127 $grade = false;
1128 } else {
1129 $grade = reset($grades->items[0]->grades);
1132 $count = forum_count_user_posts($forum->id, $user->id);
1134 if ($count && $count->postcount > 0) {
1135 $result = new stdClass();
1136 $result->info = get_string("numposts", "forum", $count->postcount);
1137 $result->time = $count->lastpost;
1138 if ($grade) {
1139 $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1141 return $result;
1142 } else if ($grade) {
1143 $result = new stdClass();
1144 $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1146 //datesubmitted == time created. dategraded == time modified or time overridden
1147 //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
1148 //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
1149 if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1150 $result->time = $grade->dategraded;
1151 } else {
1152 $result->time = $grade->datesubmitted;
1155 return $result;
1157 return NULL;
1162 * @global object
1163 * @global object
1164 * @param object $coure
1165 * @param object $user
1166 * @param object $mod
1167 * @param object $forum
1169 function forum_user_complete($course, $user, $mod, $forum) {
1170 global $CFG,$USER, $OUTPUT;
1171 require_once("$CFG->libdir/gradelib.php");
1173 $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1174 if (!empty($grades->items[0]->grades)) {
1175 $grade = reset($grades->items[0]->grades);
1176 echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1177 if ($grade->str_feedback) {
1178 echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1182 if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1184 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1185 print_error('invalidcoursemodule');
1187 $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1189 foreach ($posts as $post) {
1190 if (!isset($discussions[$post->discussion])) {
1191 continue;
1193 $discussion = $discussions[$post->discussion];
1195 forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1197 } else {
1198 echo "<p>".get_string("noposts", "forum")."</p>";
1208 * @global object
1209 * @global object
1210 * @global object
1211 * @param array $courses
1212 * @param array $htmlarray
1214 function forum_print_overview($courses,&$htmlarray) {
1215 global $USER, $CFG, $DB, $SESSION;
1217 if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1218 return array();
1221 if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1222 return;
1225 // Courses to search for new posts
1226 $coursessqls = array();
1227 $params = array();
1228 foreach ($courses as $course) {
1230 // If the user has never entered into the course all posts are pending
1231 if ($course->lastaccess == 0) {
1232 $coursessqls[] = '(f.course = ?)';
1233 $params[] = $course->id;
1235 // Only posts created after the course last access
1236 } else {
1237 $coursessqls[] = '(f.course = ? AND p.created > ?)';
1238 $params[] = $course->id;
1239 $params[] = $course->lastaccess;
1242 $params[] = $USER->id;
1243 $coursessql = implode(' OR ', $coursessqls);
1245 $sql = "SELECT f.id, COUNT(*) as count "
1246 .'FROM {forum} f '
1247 .'JOIN {forum_discussions} d ON d.forum = f.id '
1248 .'JOIN {forum_posts} p ON p.discussion = d.id '
1249 ."WHERE ($coursessql) "
1250 .'AND p.userid != ? '
1251 .'GROUP BY f.id';
1253 if (!$new = $DB->get_records_sql($sql, $params)) {
1254 $new = array(); // avoid warnings
1257 // also get all forum tracking stuff ONCE.
1258 $trackingforums = array();
1259 foreach ($forums as $forum) {
1260 if (forum_tp_can_track_forums($forum)) {
1261 $trackingforums[$forum->id] = $forum;
1265 if (count($trackingforums) > 0) {
1266 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1267 $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1268 ' FROM {forum_posts} p '.
1269 ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1270 ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1271 $params = array($USER->id);
1273 foreach ($trackingforums as $track) {
1274 $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1275 $params[] = $track->id;
1276 if (isset($SESSION->currentgroup[$track->course])) {
1277 $groupid = $SESSION->currentgroup[$track->course];
1278 } else {
1279 $groupid = groups_get_all_groups($track->course, $USER->id);
1280 if (is_array($groupid)) {
1281 $groupid = array_shift(array_keys($groupid));
1282 $SESSION->currentgroup[$track->course] = $groupid;
1283 } else {
1284 $groupid = 0;
1287 $params[] = $groupid;
1289 $sql = substr($sql,0,-3); // take off the last OR
1290 $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1291 $params[] = $cutoffdate;
1293 if (!$unread = $DB->get_records_sql($sql, $params)) {
1294 $unread = array();
1296 } else {
1297 $unread = array();
1300 if (empty($unread) and empty($new)) {
1301 return;
1304 $strforum = get_string('modulename','forum');
1306 foreach ($forums as $forum) {
1307 $str = '';
1308 $count = 0;
1309 $thisunread = 0;
1310 $showunread = false;
1311 // either we have something from logs, or trackposts, or nothing.
1312 if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1313 $count = $new[$forum->id]->count;
1315 if (array_key_exists($forum->id,$unread)) {
1316 $thisunread = $unread[$forum->id]->count;
1317 $showunread = true;
1319 if ($count > 0 || $thisunread > 0) {
1320 $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1321 $forum->name.'</a></div>';
1322 $str .= '<div class="info"><span class="postsincelogin">';
1323 $str .= get_string('overviewnumpostssince', 'forum', $count)."</span>";
1324 if (!empty($showunread)) {
1325 $str .= '<div class="unreadposts">'.get_string('overviewnumunread', 'forum', $thisunread).'</div>';
1327 $str .= '</div></div>';
1329 if (!empty($str)) {
1330 if (!array_key_exists($forum->course,$htmlarray)) {
1331 $htmlarray[$forum->course] = array();
1333 if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1334 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1336 $htmlarray[$forum->course]['forum'] .= $str;
1342 * Given a course and a date, prints a summary of all the new
1343 * messages posted in the course since that date
1345 * @global object
1346 * @global object
1347 * @global object
1348 * @uses CONTEXT_MODULE
1349 * @uses VISIBLEGROUPS
1350 * @param object $course
1351 * @param bool $viewfullnames capability
1352 * @param int $timestart
1353 * @return bool success
1355 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1356 global $CFG, $USER, $DB, $OUTPUT;
1358 // do not use log table if possible, it may be huge and is expensive to join with other tables
1360 if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1361 d.timestart, d.timeend, d.userid AS duserid,
1362 u.firstname, u.lastname, u.email, u.picture
1363 FROM {forum_posts} p
1364 JOIN {forum_discussions} d ON d.id = p.discussion
1365 JOIN {forum} f ON f.id = d.forum
1366 JOIN {user} u ON u.id = p.userid
1367 WHERE p.created > ? AND f.course = ?
1368 ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1369 return false;
1372 $modinfo =& get_fast_modinfo($course);
1374 $groupmodes = array();
1375 $cms = array();
1377 $strftimerecent = get_string('strftimerecent');
1379 $printposts = array();
1380 foreach ($posts as $post) {
1381 if (!isset($modinfo->instances['forum'][$post->forum])) {
1382 // not visible
1383 continue;
1385 $cm = $modinfo->instances['forum'][$post->forum];
1386 if (!$cm->uservisible) {
1387 continue;
1389 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1391 if (!has_capability('mod/forum:viewdiscussion', $context)) {
1392 continue;
1395 if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1396 and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1397 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1398 continue;
1402 $groupmode = groups_get_activity_groupmode($cm, $course);
1404 if ($groupmode) {
1405 if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1406 // oki (Open discussions have groupid -1)
1407 } else {
1408 // separate mode
1409 if (isguestuser()) {
1410 // shortcut
1411 continue;
1414 if (is_null($modinfo->groups)) {
1415 $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1418 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1419 continue;
1424 $printposts[] = $post;
1426 unset($posts);
1428 if (!$printposts) {
1429 return false;
1432 echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1433 echo "\n<ul class='unlist'>\n";
1435 foreach ($printposts as $post) {
1436 $subjectclass = empty($post->parent) ? ' bold' : '';
1438 echo '<li><div class="head">'.
1439 '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1440 '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1441 '</div>';
1442 echo '<div class="info'.$subjectclass.'">';
1443 if (empty($post->parent)) {
1444 echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1445 } else {
1446 echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1448 $post->subject = break_up_long_words(format_string($post->subject, true));
1449 echo $post->subject;
1450 echo "</a>\"</div></li>\n";
1453 echo "</ul>\n";
1455 return true;
1459 * Return grade for given user or all users.
1461 * @global object
1462 * @global object
1463 * @param object $forum
1464 * @param int $userid optional user id, 0 means all users
1465 * @return array array of grades, false if none
1467 function forum_get_user_grades($forum, $userid = 0) {
1468 global $CFG;
1470 require_once($CFG->dirroot.'/rating/lib.php');
1472 $ratingoptions = new stdClass;
1473 $ratingoptions->component = 'mod_forum';
1474 $ratingoptions->ratingarea = 'post';
1476 //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1477 $ratingoptions->modulename = 'forum';
1478 $ratingoptions->moduleid = $forum->id;
1479 $ratingoptions->userid = $userid;
1480 $ratingoptions->aggregationmethod = $forum->assessed;
1481 $ratingoptions->scaleid = $forum->scale;
1482 $ratingoptions->itemtable = 'forum_posts';
1483 $ratingoptions->itemtableusercolumn = 'userid';
1485 $rm = new rating_manager();
1486 return $rm->get_user_grades($ratingoptions);
1490 * Update activity grades
1492 * @global object
1493 * @global object
1494 * @param object $forum
1495 * @param int $userid specific user only, 0 means all
1496 * @param boolean $nullifnone return null if grade does not exist
1497 * @return void
1499 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1500 global $CFG, $DB;
1501 require_once($CFG->libdir.'/gradelib.php');
1503 if (!$forum->assessed) {
1504 forum_grade_item_update($forum);
1506 } else if ($grades = forum_get_user_grades($forum, $userid)) {
1507 forum_grade_item_update($forum, $grades);
1509 } else if ($userid and $nullifnone) {
1510 $grade = new stdClass();
1511 $grade->userid = $userid;
1512 $grade->rawgrade = NULL;
1513 forum_grade_item_update($forum, $grade);
1515 } else {
1516 forum_grade_item_update($forum);
1521 * Update all grades in gradebook.
1522 * @global object
1524 function forum_upgrade_grades() {
1525 global $DB;
1527 $sql = "SELECT COUNT('x')
1528 FROM {forum} f, {course_modules} cm, {modules} m
1529 WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1530 $count = $DB->count_records_sql($sql);
1532 $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1533 FROM {forum} f, {course_modules} cm, {modules} m
1534 WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1535 $rs = $DB->get_recordset_sql($sql);
1536 if ($rs->valid()) {
1537 $pbar = new progress_bar('forumupgradegrades', 500, true);
1538 $i=0;
1539 foreach ($rs as $forum) {
1540 $i++;
1541 upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1542 forum_update_grades($forum, 0, false);
1543 $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1546 $rs->close();
1550 * Create/update grade item for given forum
1552 * @global object
1553 * @uses GRADE_TYPE_NONE
1554 * @uses GRADE_TYPE_VALUE
1555 * @uses GRADE_TYPE_SCALE
1556 * @param object $forum object with extra cmidnumber
1557 * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
1558 * @return int 0 if ok
1560 function forum_grade_item_update($forum, $grades=NULL) {
1561 global $CFG;
1562 if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1563 require_once($CFG->libdir.'/gradelib.php');
1566 $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1568 if (!$forum->assessed or $forum->scale == 0) {
1569 $params['gradetype'] = GRADE_TYPE_NONE;
1571 } else if ($forum->scale > 0) {
1572 $params['gradetype'] = GRADE_TYPE_VALUE;
1573 $params['grademax'] = $forum->scale;
1574 $params['grademin'] = 0;
1576 } else if ($forum->scale < 0) {
1577 $params['gradetype'] = GRADE_TYPE_SCALE;
1578 $params['scaleid'] = -$forum->scale;
1581 if ($grades === 'reset') {
1582 $params['reset'] = true;
1583 $grades = NULL;
1586 return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1590 * Delete grade item for given forum
1592 * @global object
1593 * @param object $forum object
1594 * @return object grade_item
1596 function forum_grade_item_delete($forum) {
1597 global $CFG;
1598 require_once($CFG->libdir.'/gradelib.php');
1600 return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1605 * Returns the users with data in one forum
1606 * (users with records in forum_subscriptions, forum_posts, students)
1608 * @todo: deprecated - to be deleted in 2.2
1610 * @param int $forumid
1611 * @return mixed array or false if none
1613 function forum_get_participants($forumid) {
1615 global $CFG, $DB;
1617 $params = array('forumid' => $forumid);
1619 //Get students from forum_subscriptions
1620 $sql = "SELECT DISTINCT u.id, u.id
1621 FROM {user} u,
1622 {forum_subscriptions} s
1623 WHERE s.forum = :forumid AND
1624 u.id = s.userid";
1625 $st_subscriptions = $DB->get_records_sql($sql, $params);
1627 //Get students from forum_posts
1628 $sql = "SELECT DISTINCT u.id, u.id
1629 FROM {user} u,
1630 {forum_discussions} d,
1631 {forum_posts} p
1632 WHERE d.forum = :forumid AND
1633 p.discussion = d.id AND
1634 u.id = p.userid";
1635 $st_posts = $DB->get_records_sql($sql, $params);
1637 //Get students from the ratings table
1638 $sql = "SELECT DISTINCT r.userid, r.userid AS id
1639 FROM {forum_discussions} d
1640 JOIN {forum_posts} p ON p.discussion = d.id
1641 JOIN {rating} r on r.itemid = p.id
1642 WHERE d.forum = :forumid AND
1643 r.component = 'mod_forum' AND
1644 r.ratingarea = 'post'";
1645 $st_ratings = $DB->get_records_sql($sql, $params);
1647 //Add st_posts to st_subscriptions
1648 if ($st_posts) {
1649 foreach ($st_posts as $st_post) {
1650 $st_subscriptions[$st_post->id] = $st_post;
1653 //Add st_ratings to st_subscriptions
1654 if ($st_ratings) {
1655 foreach ($st_ratings as $st_rating) {
1656 $st_subscriptions[$st_rating->id] = $st_rating;
1659 //Return st_subscriptions array (it contains an array of unique users)
1660 return ($st_subscriptions);
1664 * This function returns if a scale is being used by one forum
1666 * @global object
1667 * @param int $forumid
1668 * @param int $scaleid negative number
1669 * @return bool
1671 function forum_scale_used ($forumid,$scaleid) {
1672 global $DB;
1673 $return = false;
1675 $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1677 if (!empty($rec) && !empty($scaleid)) {
1678 $return = true;
1681 return $return;
1685 * Checks if scale is being used by any instance of forum
1687 * This is used to find out if scale used anywhere
1689 * @global object
1690 * @param $scaleid int
1691 * @return boolean True if the scale is used by any forum
1693 function forum_scale_used_anywhere($scaleid) {
1694 global $DB;
1695 if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1696 return true;
1697 } else {
1698 return false;
1702 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1705 * Gets a post with all info ready for forum_print_post
1706 * Most of these joins are just to get the forum id
1708 * @global object
1709 * @global object
1710 * @param int $postid
1711 * @return mixed array of posts or false
1713 function forum_get_post_full($postid) {
1714 global $CFG, $DB;
1716 return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1717 FROM {forum_posts} p
1718 JOIN {forum_discussions} d ON p.discussion = d.id
1719 LEFT JOIN {user} u ON p.userid = u.id
1720 WHERE p.id = ?", array($postid));
1724 * Gets posts with all info ready for forum_print_post
1725 * We pass forumid in because we always know it so no need to make a
1726 * complicated join to find it out.
1728 * @global object
1729 * @global object
1730 * @return mixed array of posts or false
1732 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1733 global $CFG, $DB;
1735 return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1736 FROM {forum_posts} p
1737 LEFT JOIN {user} u ON p.userid = u.id
1738 WHERE p.discussion = ?
1739 AND p.parent > 0 $sort", array($discussion));
1743 * Gets all posts in discussion including top parent.
1745 * @global object
1746 * @global object
1747 * @global object
1748 * @param int $discussionid
1749 * @param string $sort
1750 * @param bool $tracking does user track the forum?
1751 * @return array of posts
1753 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1754 global $CFG, $DB, $USER;
1756 $tr_sel = "";
1757 $tr_join = "";
1758 $params = array();
1760 if ($tracking) {
1761 $now = time();
1762 $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1763 $tr_sel = ", fr.id AS postread";
1764 $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1765 $params[] = $USER->id;
1768 $params[] = $discussionid;
1769 if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1770 FROM {forum_posts} p
1771 LEFT JOIN {user} u ON p.userid = u.id
1772 $tr_join
1773 WHERE p.discussion = ?
1774 ORDER BY $sort", $params)) {
1775 return array();
1778 foreach ($posts as $pid=>$p) {
1779 if ($tracking) {
1780 if (forum_tp_is_post_old($p)) {
1781 $posts[$pid]->postread = true;
1784 if (!$p->parent) {
1785 continue;
1787 if (!isset($posts[$p->parent])) {
1788 continue; // parent does not exist??
1790 if (!isset($posts[$p->parent]->children)) {
1791 $posts[$p->parent]->children = array();
1793 $posts[$p->parent]->children[$pid] =& $posts[$pid];
1796 return $posts;
1800 * Gets posts with all info ready for forum_print_post
1801 * We pass forumid in because we always know it so no need to make a
1802 * complicated join to find it out.
1804 * @global object
1805 * @global object
1806 * @param int $parent
1807 * @param int $forumid
1808 * @return array
1810 function forum_get_child_posts($parent, $forumid) {
1811 global $CFG, $DB;
1813 return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1814 FROM {forum_posts} p
1815 LEFT JOIN {user} u ON p.userid = u.id
1816 WHERE p.parent = ?
1817 ORDER BY p.created ASC", array($parent));
1821 * An array of forum objects that the user is allowed to read/search through.
1823 * @global object
1824 * @global object
1825 * @global object
1826 * @param int $userid
1827 * @param int $courseid if 0, we look for forums throughout the whole site.
1828 * @return array of forum objects, or false if no matches
1829 * Forum objects have the following attributes:
1830 * id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1831 * viewhiddentimedposts
1833 function forum_get_readable_forums($userid, $courseid=0) {
1835 global $CFG, $DB, $USER;
1836 require_once($CFG->dirroot.'/course/lib.php');
1838 if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1839 print_error('notinstalled', 'forum');
1842 if ($courseid) {
1843 $courses = $DB->get_records('course', array('id' => $courseid));
1844 } else {
1845 // If no course is specified, then the user can see SITE + his courses.
1846 $courses1 = $DB->get_records('course', array('id' => SITEID));
1847 $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
1848 $courses = array_merge($courses1, $courses2);
1850 if (!$courses) {
1851 return array();
1854 $readableforums = array();
1856 foreach ($courses as $course) {
1858 $modinfo =& get_fast_modinfo($course);
1859 if (is_null($modinfo->groups)) {
1860 $modinfo->groups = groups_get_user_groups($course->id, $userid);
1863 if (empty($modinfo->instances['forum'])) {
1864 // hmm, no forums?
1865 continue;
1868 $courseforums = $DB->get_records('forum', array('course' => $course->id));
1870 foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1871 if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1872 continue;
1874 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1875 $forum = $courseforums[$forumid];
1876 $forum->context = $context;
1877 $forum->cm = $cm;
1879 if (!has_capability('mod/forum:viewdiscussion', $context)) {
1880 continue;
1883 /// group access
1884 if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1885 if (is_null($modinfo->groups)) {
1886 $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1888 if (isset($modinfo->groups[$cm->groupingid])) {
1889 $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1890 $forum->onlygroups[] = -1;
1891 } else {
1892 $forum->onlygroups = array(-1);
1896 /// hidden timed discussions
1897 $forum->viewhiddentimedposts = true;
1898 if (!empty($CFG->forum_enabletimedposts)) {
1899 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1900 $forum->viewhiddentimedposts = false;
1904 /// qanda access
1905 if ($forum->type == 'qanda'
1906 && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1908 // We need to check whether the user has posted in the qanda forum.
1909 $forum->onlydiscussions = array(); // Holds discussion ids for the discussions
1910 // the user is allowed to see in this forum.
1911 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1912 foreach ($discussionspostedin as $d) {
1913 $forum->onlydiscussions[] = $d->id;
1918 $readableforums[$forum->id] = $forum;
1921 unset($modinfo);
1923 } // End foreach $courses
1925 return $readableforums;
1929 * Returns a list of posts found using an array of search terms.
1931 * @global object
1932 * @global object
1933 * @global object
1934 * @param array $searchterms array of search terms, e.g. word +word -word
1935 * @param int $courseid if 0, we search through the whole site
1936 * @param int $limitfrom
1937 * @param int $limitnum
1938 * @param int &$totalcount
1939 * @param string $extrasql
1940 * @return array|bool Array of posts found or false
1942 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1943 &$totalcount, $extrasql='') {
1944 global $CFG, $DB, $USER;
1945 require_once($CFG->libdir.'/searchlib.php');
1947 $forums = forum_get_readable_forums($USER->id, $courseid);
1949 if (count($forums) == 0) {
1950 $totalcount = 0;
1951 return false;
1954 $now = round(time(), -2); // db friendly
1956 $fullaccess = array();
1957 $where = array();
1958 $params = array();
1960 foreach ($forums as $forumid => $forum) {
1961 $select = array();
1963 if (!$forum->viewhiddentimedposts) {
1964 $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
1965 $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
1968 $cm = $forum->cm;
1969 $context = $forum->context;
1971 if ($forum->type == 'qanda'
1972 && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1973 if (!empty($forum->onlydiscussions)) {
1974 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
1975 $params = array_merge($params, $discussionid_params);
1976 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
1977 } else {
1978 $select[] = "p.parent = 0";
1982 if (!empty($forum->onlygroups)) {
1983 list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
1984 $params = array_merge($params, $groupid_params);
1985 $select[] = "d.groupid $groupid_sql";
1988 if ($select) {
1989 $selects = implode(" AND ", $select);
1990 $where[] = "(d.forum = :forum{$forumid} AND $selects)";
1991 $params['forum'.$forumid] = $forumid;
1992 } else {
1993 $fullaccess[] = $forumid;
1997 if ($fullaccess) {
1998 list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
1999 $params = array_merge($params, $fullid_params);
2000 $where[] = "(d.forum $fullid_sql)";
2003 $selectdiscussion = "(".implode(" OR ", $where).")";
2005 $messagesearch = '';
2006 $searchstring = '';
2008 // Need to concat these back together for parser to work.
2009 foreach($searchterms as $searchterm){
2010 if ($searchstring != '') {
2011 $searchstring .= ' ';
2013 $searchstring .= $searchterm;
2016 // We need to allow quoted strings for the search. The quotes *should* be stripped
2017 // by the parser, but this should be examined carefully for security implications.
2018 $searchstring = str_replace("\\\"","\"",$searchstring);
2019 $parser = new search_parser();
2020 $lexer = new search_lexer($parser);
2022 if ($lexer->parse($searchstring)) {
2023 $parsearray = $parser->get_parsed_array();
2024 // Experimental feature under 1.8! MDL-8830
2025 // Use alternative text searches if defined
2026 // This feature only works under mysql until properly implemented for other DBs
2027 // Requires manual creation of text index for forum_posts before enabling it:
2028 // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2029 // Experimental feature under 1.8! MDL-8830
2030 if (!empty($CFG->forum_usetextsearches)) {
2031 list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2032 'p.userid', 'u.id', 'u.firstname',
2033 'u.lastname', 'p.modified', 'd.forum');
2034 } else {
2035 list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2036 'p.userid', 'u.id', 'u.firstname',
2037 'u.lastname', 'p.modified', 'd.forum');
2039 $params = array_merge($params, $msparams);
2042 $fromsql = "{forum_posts} p,
2043 {forum_discussions} d,
2044 {user} u";
2046 $selectsql = " $messagesearch
2047 AND p.discussion = d.id
2048 AND p.userid = u.id
2049 AND $selectdiscussion
2050 $extrasql";
2052 $countsql = "SELECT COUNT(*)
2053 FROM $fromsql
2054 WHERE $selectsql";
2056 $searchsql = "SELECT p.*,
2057 d.forum,
2058 u.firstname,
2059 u.lastname,
2060 u.email,
2061 u.picture,
2062 u.imagealt
2063 FROM $fromsql
2064 WHERE $selectsql
2065 ORDER BY p.modified DESC";
2067 $totalcount = $DB->count_records_sql($countsql, $params);
2069 return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2073 * Returns a list of ratings for a particular post - sorted.
2075 * TODO: Check if this function is actually used anywhere.
2076 * Up until the fix for MDL-27471 this function wasn't even returning.
2078 * @param stdClass $context
2079 * @param int $postid
2080 * @param string $sort
2081 * @return array Array of ratings or false
2083 function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
2084 $options = new stdClass;
2085 $options->context = $context;
2086 $options->component = 'mod_forum';
2087 $options->ratingarea = 'post';
2088 $options->itemid = $postid;
2089 $options->sort = "ORDER BY $sort";
2091 $rm = new rating_manager();
2092 return $rm->get_all_ratings_for_item($options);
2096 * Returns a list of all new posts that have not been mailed yet
2098 * @param int $starttime posts created after this time
2099 * @param int $endtime posts created before this
2100 * @param int $now used for timed discussions only
2101 * @return array
2103 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2104 global $CFG, $DB;
2106 $params = array($starttime, $endtime);
2107 if (!empty($CFG->forum_enabletimedposts)) {
2108 if (empty($now)) {
2109 $now = time();
2111 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2112 $params[] = $now;
2113 $params[] = $now;
2114 } else {
2115 $timedsql = "";
2118 return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2119 FROM {forum_posts} p
2120 JOIN {forum_discussions} d ON d.id = p.discussion
2121 WHERE p.mailed = 0
2122 AND p.created >= ?
2123 AND (p.created < ? OR p.mailnow = 1)
2124 $timedsql
2125 ORDER BY p.modified ASC", $params);
2129 * Marks posts before a certain time as being mailed already
2131 * @global object
2132 * @global object
2133 * @param int $endtime
2134 * @param int $now Defaults to time()
2135 * @return bool
2137 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2138 global $CFG, $DB;
2139 if (empty($now)) {
2140 $now = time();
2143 if (empty($CFG->forum_enabletimedposts)) {
2144 return $DB->execute("UPDATE {forum_posts}
2145 SET mailed = '1'
2146 WHERE (created < ? OR mailnow = 1)
2147 AND mailed = 0", array($endtime));
2149 } else {
2150 return $DB->execute("UPDATE {forum_posts}
2151 SET mailed = '1'
2152 WHERE discussion NOT IN (SELECT d.id
2153 FROM {forum_discussions} d
2154 WHERE d.timestart > ?)
2155 AND (created < ? OR mailnow = 1)
2156 AND mailed = 0", array($now, $endtime));
2161 * Get all the posts for a user in a forum suitable for forum_print_post
2163 * @global object
2164 * @global object
2165 * @uses CONTEXT_MODULE
2166 * @return array
2168 function forum_get_user_posts($forumid, $userid) {
2169 global $CFG, $DB;
2171 $timedsql = "";
2172 $params = array($forumid, $userid);
2174 if (!empty($CFG->forum_enabletimedposts)) {
2175 $cm = get_coursemodule_from_instance('forum', $forumid);
2176 if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2177 $now = time();
2178 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2179 $params[] = $now;
2180 $params[] = $now;
2184 return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2185 FROM {forum} f
2186 JOIN {forum_discussions} d ON d.forum = f.id
2187 JOIN {forum_posts} p ON p.discussion = d.id
2188 JOIN {user} u ON u.id = p.userid
2189 WHERE f.id = ?
2190 AND p.userid = ?
2191 $timedsql
2192 ORDER BY p.modified ASC", $params);
2196 * Get all the discussions user participated in
2198 * @global object
2199 * @global object
2200 * @uses CONTEXT_MODULE
2201 * @param int $forumid
2202 * @param int $userid
2203 * @return array Array or false
2205 function forum_get_user_involved_discussions($forumid, $userid) {
2206 global $CFG, $DB;
2208 $timedsql = "";
2209 $params = array($forumid, $userid);
2210 if (!empty($CFG->forum_enabletimedposts)) {
2211 $cm = get_coursemodule_from_instance('forum', $forumid);
2212 if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2213 $now = time();
2214 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2215 $params[] = $now;
2216 $params[] = $now;
2220 return $DB->get_records_sql("SELECT DISTINCT d.*
2221 FROM {forum} f
2222 JOIN {forum_discussions} d ON d.forum = f.id
2223 JOIN {forum_posts} p ON p.discussion = d.id
2224 WHERE f.id = ?
2225 AND p.userid = ?
2226 $timedsql", $params);
2230 * Get all the posts for a user in a forum suitable for forum_print_post
2232 * @global object
2233 * @global object
2234 * @param int $forumid
2235 * @param int $userid
2236 * @return array of counts or false
2238 function forum_count_user_posts($forumid, $userid) {
2239 global $CFG, $DB;
2241 $timedsql = "";
2242 $params = array($forumid, $userid);
2243 if (!empty($CFG->forum_enabletimedposts)) {
2244 $cm = get_coursemodule_from_instance('forum', $forumid);
2245 if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2246 $now = time();
2247 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2248 $params[] = $now;
2249 $params[] = $now;
2253 return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2254 FROM {forum} f
2255 JOIN {forum_discussions} d ON d.forum = f.id
2256 JOIN {forum_posts} p ON p.discussion = d.id
2257 JOIN {user} u ON u.id = p.userid
2258 WHERE f.id = ?
2259 AND p.userid = ?
2260 $timedsql", $params);
2264 * Given a log entry, return the forum post details for it.
2266 * @global object
2267 * @global object
2268 * @param object $log
2269 * @return array|null
2271 function forum_get_post_from_log($log) {
2272 global $CFG, $DB;
2274 if ($log->action == "add post") {
2276 return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2277 u.firstname, u.lastname, u.email, u.picture
2278 FROM {forum_discussions} d,
2279 {forum_posts} p,
2280 {forum} f,
2281 {user} u
2282 WHERE p.id = ?
2283 AND d.id = p.discussion
2284 AND p.userid = u.id
2285 AND u.deleted <> '1'
2286 AND f.id = d.forum", array($log->info));
2289 } else if ($log->action == "add discussion") {
2291 return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2292 u.firstname, u.lastname, u.email, u.picture
2293 FROM {forum_discussions} d,
2294 {forum_posts} p,
2295 {forum} f,
2296 {user} u
2297 WHERE d.id = ?
2298 AND d.firstpost = p.id
2299 AND p.userid = u.id
2300 AND u.deleted <> '1'
2301 AND f.id = d.forum", array($log->info));
2303 return NULL;
2307 * Given a discussion id, return the first post from the discussion
2309 * @global object
2310 * @global object
2311 * @param int $dicsussionid
2312 * @return array
2314 function forum_get_firstpost_from_discussion($discussionid) {
2315 global $CFG, $DB;
2317 return $DB->get_record_sql("SELECT p.*
2318 FROM {forum_discussions} d,
2319 {forum_posts} p
2320 WHERE d.id = ?
2321 AND d.firstpost = p.id ", array($discussionid));
2325 * Returns an array of counts of replies to each discussion
2327 * @global object
2328 * @global object
2329 * @param int $forumid
2330 * @param string $forumsort
2331 * @param int $limit
2332 * @param int $page
2333 * @param int $perpage
2334 * @return array
2336 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2337 global $CFG, $DB;
2339 if ($limit > 0) {
2340 $limitfrom = 0;
2341 $limitnum = $limit;
2342 } else if ($page != -1) {
2343 $limitfrom = $page*$perpage;
2344 $limitnum = $perpage;
2345 } else {
2346 $limitfrom = 0;
2347 $limitnum = 0;
2350 if ($forumsort == "") {
2351 $orderby = "";
2352 $groupby = "";
2354 } else {
2355 $orderby = "ORDER BY $forumsort";
2356 $groupby = ", ".strtolower($forumsort);
2357 $groupby = str_replace('desc', '', $groupby);
2358 $groupby = str_replace('asc', '', $groupby);
2361 if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2362 $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2363 FROM {forum_posts} p
2364 JOIN {forum_discussions} d ON p.discussion = d.id
2365 WHERE p.parent > 0 AND d.forum = ?
2366 GROUP BY p.discussion";
2367 return $DB->get_records_sql($sql, array($forumid));
2369 } else {
2370 $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2371 FROM {forum_posts} p
2372 JOIN {forum_discussions} d ON p.discussion = d.id
2373 WHERE d.forum = ?
2374 GROUP BY p.discussion $groupby
2375 $orderby";
2376 return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2381 * @global object
2382 * @global object
2383 * @global object
2384 * @staticvar array $cache
2385 * @param object $forum
2386 * @param object $cm
2387 * @param object $course
2388 * @return mixed
2390 function forum_count_discussions($forum, $cm, $course) {
2391 global $CFG, $DB, $USER;
2393 static $cache = array();
2395 $now = round(time(), -2); // db cache friendliness
2397 $params = array($course->id);
2399 if (!isset($cache[$course->id])) {
2400 if (!empty($CFG->forum_enabletimedposts)) {
2401 $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2402 $params[] = $now;
2403 $params[] = $now;
2404 } else {
2405 $timedsql = "";
2408 $sql = "SELECT f.id, COUNT(d.id) as dcount
2409 FROM {forum} f
2410 JOIN {forum_discussions} d ON d.forum = f.id
2411 WHERE f.course = ?
2412 $timedsql
2413 GROUP BY f.id";
2415 if ($counts = $DB->get_records_sql($sql, $params)) {
2416 foreach ($counts as $count) {
2417 $counts[$count->id] = $count->dcount;
2419 $cache[$course->id] = $counts;
2420 } else {
2421 $cache[$course->id] = array();
2425 if (empty($cache[$course->id][$forum->id])) {
2426 return 0;
2429 $groupmode = groups_get_activity_groupmode($cm, $course);
2431 if ($groupmode != SEPARATEGROUPS) {
2432 return $cache[$course->id][$forum->id];
2435 if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2436 return $cache[$course->id][$forum->id];
2439 require_once($CFG->dirroot.'/course/lib.php');
2441 $modinfo =& get_fast_modinfo($course);
2442 if (is_null($modinfo->groups)) {
2443 $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2446 if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2447 $mygroups = $modinfo->groups[$cm->groupingid];
2448 } else {
2449 $mygroups = false; // Will be set below
2452 // add all groups posts
2453 if (empty($mygroups)) {
2454 $mygroups = array(-1=>-1);
2455 } else {
2456 $mygroups[-1] = -1;
2459 list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2460 $params[] = $forum->id;
2462 if (!empty($CFG->forum_enabletimedposts)) {
2463 $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2464 $params[] = $now;
2465 $params[] = $now;
2466 } else {
2467 $timedsql = "";
2470 $sql = "SELECT COUNT(d.id)
2471 FROM {forum_discussions} d
2472 WHERE d.groupid $mygroups_sql AND d.forum = ?
2473 $timedsql";
2475 return $DB->get_field_sql($sql, $params);
2479 * How many posts by other users are unrated by a given user in the given discussion?
2481 * TODO: Is this function still used anywhere?
2483 * @param int $discussionid
2484 * @param int $userid
2485 * @return mixed
2487 function forum_count_unrated_posts($discussionid, $userid) {
2488 global $CFG, $DB;
2490 $sql = "SELECT COUNT(*) as num
2491 FROM {forum_posts}
2492 WHERE parent > 0
2493 AND discussion = :discussionid
2494 AND userid <> :userid";
2495 $params = array('discussionid' => $discussionid, 'userid' => $userid);
2496 $posts = $DB->get_record_sql($sql, $params);
2497 if ($posts) {
2498 $sql = "SELECT count(*) as num
2499 FROM {forum_posts} p,
2500 {rating} r
2501 WHERE p.discussion = :discussionid AND
2502 p.id = r.itemid AND
2503 r.userid = userid AND
2504 r.component = 'mod_forum' AND
2505 r.ratingarea = 'post'";
2506 $rated = $DB->get_record_sql($sql, $params);
2507 if ($rated) {
2508 if ($posts->num > $rated->num) {
2509 return $posts->num - $rated->num;
2510 } else {
2511 return 0; // Just in case there was a counting error
2513 } else {
2514 return $posts->num;
2516 } else {
2517 return 0;
2522 * Get all discussions in a forum
2524 * @global object
2525 * @global object
2526 * @global object
2527 * @uses CONTEXT_MODULE
2528 * @uses VISIBLEGROUPS
2529 * @param object $cm
2530 * @param string $forumsort
2531 * @param bool $fullpost
2532 * @param int $unused
2533 * @param int $limit
2534 * @param bool $userlastmodified
2535 * @param int $page
2536 * @param int $perpage
2537 * @return array
2539 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2540 global $CFG, $DB, $USER;
2542 $timelimit = '';
2544 $now = round(time(), -2);
2545 $params = array($cm->instance);
2547 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2549 if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2550 return array();
2553 if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2555 if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2556 $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2557 $params[] = $now;
2558 $params[] = $now;
2559 if (isloggedin()) {
2560 $timelimit .= " OR d.userid = ?";
2561 $params[] = $USER->id;
2563 $timelimit .= ")";
2567 if ($limit > 0) {
2568 $limitfrom = 0;
2569 $limitnum = $limit;
2570 } else if ($page != -1) {
2571 $limitfrom = $page*$perpage;
2572 $limitnum = $perpage;
2573 } else {
2574 $limitfrom = 0;
2575 $limitnum = 0;
2578 $groupmode = groups_get_activity_groupmode($cm);
2579 $currentgroup = groups_get_activity_group($cm);
2581 if ($groupmode) {
2582 if (empty($modcontext)) {
2583 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2586 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2587 if ($currentgroup) {
2588 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2589 $params[] = $currentgroup;
2590 } else {
2591 $groupselect = "";
2594 } else {
2595 //seprate groups without access all
2596 if ($currentgroup) {
2597 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2598 $params[] = $currentgroup;
2599 } else {
2600 $groupselect = "AND d.groupid = -1";
2603 } else {
2604 $groupselect = "";
2608 if (empty($forumsort)) {
2609 $forumsort = "d.timemodified DESC";
2611 if (empty($fullpost)) {
2612 $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2613 } else {
2614 $postdata = "p.*";
2617 if (empty($userlastmodified)) { // We don't need to know this
2618 $umfields = "";
2619 $umtable = "";
2620 } else {
2621 $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2622 $umtable = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2625 $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2626 u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2627 FROM {forum_discussions} d
2628 JOIN {forum_posts} p ON p.discussion = d.id
2629 JOIN {user} u ON p.userid = u.id
2630 $umtable
2631 WHERE d.forum = ? AND p.parent = 0
2632 $timelimit $groupselect
2633 ORDER BY $forumsort";
2634 return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2639 * @global object
2640 * @global object
2641 * @global object
2642 * @uses CONTEXT_MODULE
2643 * @uses VISIBLEGROUPS
2644 * @param object $cm
2645 * @return array
2647 function forum_get_discussions_unread($cm) {
2648 global $CFG, $DB, $USER;
2650 $now = round(time(), -2);
2651 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2653 $params = array();
2654 $groupmode = groups_get_activity_groupmode($cm);
2655 $currentgroup = groups_get_activity_group($cm);
2657 if ($groupmode) {
2658 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2660 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2661 if ($currentgroup) {
2662 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2663 $params['currentgroup'] = $currentgroup;
2664 } else {
2665 $groupselect = "";
2668 } else {
2669 //separate groups without access all
2670 if ($currentgroup) {
2671 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2672 $params['currentgroup'] = $currentgroup;
2673 } else {
2674 $groupselect = "AND d.groupid = -1";
2677 } else {
2678 $groupselect = "";
2681 if (!empty($CFG->forum_enabletimedposts)) {
2682 $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2683 $params['now1'] = $now;
2684 $params['now2'] = $now;
2685 } else {
2686 $timedsql = "";
2689 $sql = "SELECT d.id, COUNT(p.id) AS unread
2690 FROM {forum_discussions} d
2691 JOIN {forum_posts} p ON p.discussion = d.id
2692 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2693 WHERE d.forum = {$cm->instance}
2694 AND p.modified >= :cutoffdate AND r.id is NULL
2695 $groupselect
2696 $timedsql
2697 GROUP BY d.id";
2698 $params['cutoffdate'] = $cutoffdate;
2700 if ($unreads = $DB->get_records_sql($sql, $params)) {
2701 foreach ($unreads as $unread) {
2702 $unreads[$unread->id] = $unread->unread;
2704 return $unreads;
2705 } else {
2706 return array();
2711 * @global object
2712 * @global object
2713 * @global object
2714 * @uses CONEXT_MODULE
2715 * @uses VISIBLEGROUPS
2716 * @param object $cm
2717 * @return array
2719 function forum_get_discussions_count($cm) {
2720 global $CFG, $DB, $USER;
2722 $now = round(time(), -2);
2723 $params = array($cm->instance);
2724 $groupmode = groups_get_activity_groupmode($cm);
2725 $currentgroup = groups_get_activity_group($cm);
2727 if ($groupmode) {
2728 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2730 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2731 if ($currentgroup) {
2732 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2733 $params[] = $currentgroup;
2734 } else {
2735 $groupselect = "";
2738 } else {
2739 //seprate groups without access all
2740 if ($currentgroup) {
2741 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2742 $params[] = $currentgroup;
2743 } else {
2744 $groupselect = "AND d.groupid = -1";
2747 } else {
2748 $groupselect = "";
2751 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2753 $timelimit = "";
2755 if (!empty($CFG->forum_enabletimedposts)) {
2757 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2759 if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2760 $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2761 $params[] = $now;
2762 $params[] = $now;
2763 if (isloggedin()) {
2764 $timelimit .= " OR d.userid = ?";
2765 $params[] = $USER->id;
2767 $timelimit .= ")";
2771 $sql = "SELECT COUNT(d.id)
2772 FROM {forum_discussions} d
2773 JOIN {forum_posts} p ON p.discussion = d.id
2774 WHERE d.forum = ? AND p.parent = 0
2775 $groupselect $timelimit";
2777 return $DB->get_field_sql($sql, $params);
2782 * Get all discussions started by a particular user in a course (or group)
2783 * This function no longer used ...
2785 * @todo Remove this function if no longer used
2786 * @global object
2787 * @global object
2788 * @param int $courseid
2789 * @param int $userid
2790 * @param int $groupid
2791 * @return array
2793 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2794 global $CFG, $DB;
2795 $params = array($courseid, $userid);
2796 if ($groupid) {
2797 $groupselect = " AND d.groupid = ? ";
2798 $params[] = $groupid;
2799 } else {
2800 $groupselect = "";
2803 return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2804 f.type as forumtype, f.name as forumname, f.id as forumid
2805 FROM {forum_discussions} d,
2806 {forum_posts} p,
2807 {user} u,
2808 {forum} f
2809 WHERE d.course = ?
2810 AND p.discussion = d.id
2811 AND p.parent = 0
2812 AND p.userid = u.id
2813 AND u.id = ?
2814 AND d.forum = f.id $groupselect
2815 ORDER BY p.created DESC", $params);
2819 * Get the list of potential subscribers to a forum.
2821 * @param object $forumcontext the forum context.
2822 * @param integer $groupid the id of a group, or 0 for all groups.
2823 * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2824 * @param string $sort sort order. As for get_users_by_capability.
2825 * @return array list of users.
2827 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2828 global $DB;
2830 // only active enrolled users or everybody on the frontpage
2831 list($esql, $params) = get_enrolled_sql($forumcontext, '', $groupid, true);
2833 $sql = "SELECT $fields
2834 FROM {user} u
2835 JOIN ($esql) je ON je.id = u.id";
2836 if ($sort) {
2837 $sql = "$sql ORDER BY $sort";
2838 } else {
2839 $sql = "$sql ORDER BY u.lastname ASC, u.firstname ASC";
2842 return $DB->get_records_sql($sql, $params);
2846 * Returns list of user objects that are subscribed to this forum
2848 * @global object
2849 * @global object
2850 * @param object $course the course
2851 * @param forum $forum the forum
2852 * @param integer $groupid group id, or 0 for all.
2853 * @param object $context the forum context, to save re-fetching it where possible.
2854 * @param string $fields requested user fields (with "u." table prefix)
2855 * @return array list of users.
2857 function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
2858 global $CFG, $DB;
2860 if (empty($fields)) {
2861 $fields ="u.id,
2862 u.username,
2863 u.firstname,
2864 u.lastname,
2865 u.maildisplay,
2866 u.mailformat,
2867 u.maildigest,
2868 u.imagealt,
2869 u.email,
2870 u.emailstop,
2871 u.city,
2872 u.country,
2873 u.lastaccess,
2874 u.lastlogin,
2875 u.picture,
2876 u.timezone,
2877 u.theme,
2878 u.lang,
2879 u.trackforums,
2880 u.mnethostid";
2883 if (empty($context)) {
2884 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2885 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2888 if (forum_is_forcesubscribed($forum)) {
2889 $results = forum_get_potential_subscribers($context, $groupid, $fields, "u.email ASC");
2891 } else {
2892 // only active enrolled users or everybody on the frontpage
2893 list($esql, $params) = get_enrolled_sql($context, '', $groupid, true);
2894 $params['forumid'] = $forum->id;
2895 $results = $DB->get_records_sql("SELECT $fields
2896 FROM {user} u
2897 JOIN ($esql) je ON je.id = u.id
2898 JOIN {forum_subscriptions} s ON s.userid = u.id
2899 WHERE s.forum = :forumid
2900 ORDER BY u.email ASC", $params);
2903 // Guest user should never be subscribed to a forum.
2904 unset($results[$CFG->siteguest]);
2906 return $results;
2911 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2915 * @global object
2916 * @global object
2917 * @param int $courseid
2918 * @param string $type
2920 function forum_get_course_forum($courseid, $type) {
2921 // How to set up special 1-per-course forums
2922 global $CFG, $DB, $OUTPUT;
2924 if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2925 // There should always only be ONE, but with the right combination of
2926 // errors there might be more. In this case, just return the oldest one (lowest ID).
2927 foreach ($forums as $forum) {
2928 return $forum; // ie the first one
2932 // Doesn't exist, so create one now.
2933 $forum = new stdClass();
2934 $forum->course = $courseid;
2935 $forum->type = "$type";
2936 switch ($forum->type) {
2937 case "news":
2938 $forum->name = get_string("namenews", "forum");
2939 $forum->intro = get_string("intronews", "forum");
2940 $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2941 $forum->assessed = 0;
2942 if ($courseid == SITEID) {
2943 $forum->name = get_string("sitenews");
2944 $forum->forcesubscribe = 0;
2946 break;
2947 case "social":
2948 $forum->name = get_string("namesocial", "forum");
2949 $forum->intro = get_string("introsocial", "forum");
2950 $forum->assessed = 0;
2951 $forum->forcesubscribe = 0;
2952 break;
2953 case "blog":
2954 $forum->name = get_string('blogforum', 'forum');
2955 $forum->intro = get_string('introblog', 'forum');
2956 $forum->assessed = 0;
2957 $forum->forcesubscribe = 0;
2958 break;
2959 default:
2960 echo $OUTPUT->notification("That forum type doesn't exist!");
2961 return false;
2962 break;
2965 $forum->timemodified = time();
2966 $forum->id = $DB->insert_record("forum", $forum);
2968 if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2969 echo $OUTPUT->notification("Could not find forum module!!");
2970 return false;
2972 $mod = new stdClass();
2973 $mod->course = $courseid;
2974 $mod->module = $module->id;
2975 $mod->instance = $forum->id;
2976 $mod->section = 0;
2977 if (! $mod->coursemodule = add_course_module($mod) ) { // assumes course/lib.php is loaded
2978 echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
2979 return false;
2981 if (! $sectionid = add_mod_to_section($mod) ) { // assumes course/lib.php is loaded
2982 echo $OUTPUT->notification("Could not add the new course module to that section");
2983 return false;
2985 $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule));
2987 include_once("$CFG->dirroot/course/lib.php");
2988 rebuild_course_cache($courseid);
2990 return $DB->get_record("forum", array("id" => "$forum->id"));
2995 * Given the data about a posting, builds up the HTML to display it and
2996 * returns the HTML in a string. This is designed for sending via HTML email.
2998 * @global object
2999 * @param object $course
3000 * @param object $cm
3001 * @param object $forum
3002 * @param object $discussion
3003 * @param object $post
3004 * @param object $userform
3005 * @param object $userto
3006 * @param bool $ownpost
3007 * @param bool $reply
3008 * @param bool $link
3009 * @param bool $rate
3010 * @param string $footer
3011 * @return string
3013 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
3014 $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
3016 global $CFG, $OUTPUT;
3018 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3020 if (!isset($userto->viewfullnames[$forum->id])) {
3021 $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3022 } else {
3023 $viewfullnames = $userto->viewfullnames[$forum->id];
3026 // add absolute file links
3027 $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3029 // format the post body
3030 $options = new stdClass();
3031 $options->para = true;
3032 $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3034 $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3036 $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3037 $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3038 $output .= '</td>';
3040 if ($post->parent) {
3041 $output .= '<td class="topic">';
3042 } else {
3043 $output .= '<td class="topic starter">';
3045 $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3047 $fullname = fullname($userfrom, $viewfullnames);
3048 $by = new stdClass();
3049 $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3050 $by->date = userdate($post->modified, '', $userto->timezone);
3051 $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3053 $output .= '</td></tr>';
3055 $output .= '<tr><td class="left side" valign="top">';
3057 if (isset($userfrom->groups)) {
3058 $groups = $userfrom->groups[$forum->id];
3059 } else {
3060 $groups = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3063 if ($groups) {
3064 $output .= print_group_picture($groups, $course->id, false, true, true);
3065 } else {
3066 $output .= '&nbsp;';
3069 $output .= '</td><td class="content">';
3071 $attachments = forum_print_attachments($post, $cm, 'html');
3072 if ($attachments !== '') {
3073 $output .= '<div class="attachments">';
3074 $output .= $attachments;
3075 $output .= '</div>';
3078 $output .= $formattedtext;
3080 // Commands
3081 $commands = array();
3083 if ($post->parent) {
3084 $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3085 $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3088 if ($reply) {
3089 $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3090 get_string('reply', 'forum').'</a>';
3093 $output .= '<div class="commands">';
3094 $output .= implode(' | ', $commands);
3095 $output .= '</div>';
3097 // Context link to post if required
3098 if ($link) {
3099 $output .= '<div class="link">';
3100 $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3101 get_string('postincontext', 'forum').'</a>';
3102 $output .= '</div>';
3105 if ($footer) {
3106 $output .= '<div class="footer">'.$footer.'</div>';
3108 $output .= '</td></tr></table>'."\n\n";
3110 return $output;
3114 * Print a forum post
3116 * @global object
3117 * @global object
3118 * @uses FORUM_MODE_THREADED
3119 * @uses PORTFOLIO_FORMAT_PLAINHTML
3120 * @uses PORTFOLIO_FORMAT_FILE
3121 * @uses PORTFOLIO_FORMAT_RICHHTML
3122 * @uses PORTFOLIO_ADD_TEXT_LINK
3123 * @uses CONTEXT_MODULE
3124 * @param object $post The post to print.
3125 * @param object $discussion
3126 * @param object $forum
3127 * @param object $cm
3128 * @param object $course
3129 * @param boolean $ownpost Whether this post belongs to the current user.
3130 * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3131 * @param boolean $link Just print a shortened version of the post as a link to the full post.
3132 * @param string $footer Extra stuff to print after the message.
3133 * @param string $highlight Space-separated list of terms to highlight.
3134 * @param int $post_read true, false or -99. If we already know whether this user
3135 * has read this post, pass that in, otherwise, pass in -99, and this
3136 * function will work it out.
3137 * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3138 * the current user can't see this post, if this argument is true
3139 * (the default) then print a dummy 'you can't see this post' post.
3140 * If false, don't output anything at all.
3141 * @param bool|null $istracked
3142 * @return void
3144 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3145 $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3146 global $USER, $CFG, $OUTPUT;
3148 require_once($CFG->libdir . '/filelib.php');
3150 // String cache
3151 static $str;
3153 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3155 $post->course = $course->id;
3156 $post->forum = $forum->id;
3157 $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3159 // caching
3160 if (!isset($cm->cache)) {
3161 $cm->cache = new stdClass;
3164 if (!isset($cm->cache->caps)) {
3165 $cm->cache->caps = array();
3166 $cm->cache->caps['mod/forum:viewdiscussion'] = has_capability('mod/forum:viewdiscussion', $modcontext);
3167 $cm->cache->caps['moodle/site:viewfullnames'] = has_capability('moodle/site:viewfullnames', $modcontext);
3168 $cm->cache->caps['mod/forum:editanypost'] = has_capability('mod/forum:editanypost', $modcontext);
3169 $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3170 $cm->cache->caps['mod/forum:deleteownpost'] = has_capability('mod/forum:deleteownpost', $modcontext);
3171 $cm->cache->caps['mod/forum:deleteanypost'] = has_capability('mod/forum:deleteanypost', $modcontext);
3172 $cm->cache->caps['mod/forum:viewanyrating'] = has_capability('mod/forum:viewanyrating', $modcontext);
3173 $cm->cache->caps['mod/forum:exportpost'] = has_capability('mod/forum:exportpost', $modcontext);
3174 $cm->cache->caps['mod/forum:exportownpost'] = has_capability('mod/forum:exportownpost', $modcontext);
3177 if (!isset($cm->uservisible)) {
3178 $cm->uservisible = coursemodule_visible_for_user($cm);
3181 if ($istracked && is_null($postisread)) {
3182 $postisread = forum_tp_is_post_read($USER->id, $post);
3185 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3186 $output = '';
3187 if (!$dummyifcantsee) {
3188 if ($return) {
3189 return $output;
3191 echo $output;
3192 return;
3194 $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3195 $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'));
3196 $output .= html_writer::start_tag('div', array('class'=>'row header'));
3197 $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3198 if ($post->parent) {
3199 $output .= html_writer::start_tag('div', array('class'=>'topic'));
3200 } else {
3201 $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3203 $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class'=>'subject')); // Subject
3204 $output .= html_writer::tag('div', get_string('forumauthorhidden','forum'), array('class'=>'author')); // author
3205 $output .= html_writer::end_tag('div');
3206 $output .= html_writer::end_tag('div'); // row
3207 $output .= html_writer::start_tag('div', array('class'=>'row'));
3208 $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3209 $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3210 $output .= html_writer::end_tag('div'); // row
3211 $output .= html_writer::end_tag('div'); // forumpost
3213 if ($return) {
3214 return $output;
3216 echo $output;
3217 return;
3220 if (empty($str)) {
3221 $str = new stdClass;
3222 $str->edit = get_string('edit', 'forum');
3223 $str->delete = get_string('delete', 'forum');
3224 $str->reply = get_string('reply', 'forum');
3225 $str->parent = get_string('parent', 'forum');
3226 $str->pruneheading = get_string('pruneheading', 'forum');
3227 $str->prune = get_string('prune', 'forum');
3228 $str->displaymode = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3229 $str->markread = get_string('markread', 'forum');
3230 $str->markunread = get_string('markunread', 'forum');
3233 $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3235 // Build an object that represents the posting user
3236 $postuser = new stdClass;
3237 $postuser->id = $post->userid;
3238 $postuser->firstname = $post->firstname;
3239 $postuser->lastname = $post->lastname;
3240 $postuser->imagealt = $post->imagealt;
3241 $postuser->picture = $post->picture;
3242 $postuser->email = $post->email;
3243 // Some handy things for later on
3244 $postuser->fullname = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3245 $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3247 // Prepare the groups the posting user belongs to
3248 if (isset($cm->cache->usersgroups)) {
3249 $groups = array();
3250 if (isset($cm->cache->usersgroups[$post->userid])) {
3251 foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3252 $groups[$gid] = $cm->cache->groups[$gid];
3255 } else {
3256 $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3259 // Prepare the attachements for the post, files then images
3260 list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3262 // Determine if we need to shorten this post
3263 $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3266 // Prepare an array of commands
3267 $commands = array();
3269 // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3270 // Don't display the mark read / unread controls in this case.
3271 if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3272 $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3273 $text = $str->markunread;
3274 if (!$postisread) {
3275 $url->param('mark', 'read');
3276 $text = $str->markread;
3278 if ($str->displaymode == FORUM_MODE_THREADED) {
3279 $url->param('parent', $post->parent);
3280 } else {
3281 $url->set_anchor('p'.$post->id);
3283 $commands[] = array('url'=>$url, 'text'=>$text);
3286 // Zoom in to the parent specifically
3287 if ($post->parent) {
3288 $url = new moodle_url($discussionlink);
3289 if ($str->displaymode == FORUM_MODE_THREADED) {
3290 $url->param('parent', $post->parent);
3291 } else {
3292 $url->set_anchor('p'.$post->parent);
3294 $commands[] = array('url'=>$url, 'text'=>$str->parent);
3297 // Hack for allow to edit news posts those are not displayed yet until they are displayed
3298 $age = time() - $post->created;
3299 if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3300 $age = 0;
3302 if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3303 $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3306 if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3307 $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3310 if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3311 $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3314 if ($reply) {
3315 $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('reply'=>$post->id)), 'text'=>$str->reply);
3318 if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
3319 $p = array('postid' => $post->id);
3320 require_once($CFG->libdir.'/portfoliolib.php');
3321 $button = new portfolio_add_button();
3322 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php');
3323 if (empty($attachments)) {
3324 $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3325 } else {
3326 $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3329 $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3330 if (!empty($porfoliohtml)) {
3331 $commands[] = $porfoliohtml;
3334 // Finished building commands
3337 // Begin output
3339 $output = '';
3341 if ($istracked) {
3342 if ($postisread) {
3343 $forumpostclass = ' read';
3344 } else {
3345 $forumpostclass = ' unread';
3346 $output .= html_writer::tag('a', '', array('name'=>'unread'));
3348 } else {
3349 // ignore trackign status if not tracked or tracked param missing
3350 $forumpostclass = '';
3353 $topicclass = '';
3354 if (empty($post->parent)) {
3355 $topicclass = ' firstpost starter';
3358 $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3359 $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass));
3360 $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3361 $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3362 $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3363 $output .= html_writer::end_tag('div');
3366 $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3368 $postsubject = $post->subject;
3369 if (empty($post->subjectnoformat)) {
3370 $postsubject = format_string($postsubject);
3372 $output .= html_writer::tag('div', $postsubject, array('class'=>'subject'));
3374 $by = new stdClass();
3375 $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3376 $by->date = userdate($post->modified);
3377 $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author'));
3379 $output .= html_writer::end_tag('div'); //topic
3380 $output .= html_writer::end_tag('div'); //row
3382 $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3383 $output .= html_writer::start_tag('div', array('class'=>'left'));
3385 $groupoutput = '';
3386 if ($groups) {
3387 $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3389 if (empty($groupoutput)) {
3390 $groupoutput = '&nbsp;';
3392 $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3394 $output .= html_writer::end_tag('div'); //left side
3395 $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3396 $output .= html_writer::start_tag('div', array('class'=>'content'));
3397 if (!empty($attachments)) {
3398 $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3401 $options = new stdClass;
3402 $options->para = false;
3403 $options->trusted = $post->messagetrust;
3404 $options->context = $modcontext;
3405 if ($shortenpost) {
3406 // Prepare shortened version
3407 $postclass = 'shortenedpost';
3408 $postcontent = format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3409 $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
3410 $postcontent .= html_writer::tag('span', '('.get_string('numwords', 'moodle', count_words(strip_tags($post->message))).')...', array('class'=>'post-word-count'));
3411 } else {
3412 // Prepare whole post
3413 $postclass = 'fullpost';
3414 $postcontent = format_text($post->message, $post->messageformat, $options, $course->id);
3415 if (!empty($highlight)) {
3416 $postcontent = highlight($highlight, $postcontent);
3418 $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
3420 // Output the post content
3421 $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
3422 $output .= html_writer::end_tag('div'); // Content
3423 $output .= html_writer::end_tag('div'); // Content mask
3424 $output .= html_writer::end_tag('div'); // Row
3426 $output .= html_writer::start_tag('div', array('class'=>'row side'));
3427 $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
3428 $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
3430 // Output ratings
3431 if (!empty($post->rating)) {
3432 $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
3435 // Output the commands
3436 $commandhtml = array();
3437 foreach ($commands as $command) {
3438 if (is_array($command)) {
3439 $commandhtml[] = html_writer::link($command['url'], $command['text']);
3440 } else {
3441 $commandhtml[] = $command;
3444 $output .= html_writer::tag('div', implode(' | ', $commandhtml), array('class'=>'commands'));
3446 // Output link to post if required
3447 if ($link) {
3448 if ($post->replies == 1) {
3449 $replystring = get_string('repliesone', 'forum', $post->replies);
3450 } else {
3451 $replystring = get_string('repliesmany', 'forum', $post->replies);
3454 $output .= html_writer::start_tag('div', array('class'=>'link'));
3455 $output .= html_writer::link($discussionlink, get_string('discussthistopic', 'forum'));
3456 $output .= '&nbsp;('.$replystring.')';
3457 $output .= html_writer::end_tag('div'); // link
3460 // Output footer if required
3461 if ($footer) {
3462 $output .= html_writer::tag('div', $footer, array('class'=>'footer'));
3465 // Close remaining open divs
3466 $output .= html_writer::end_tag('div'); // content
3467 $output .= html_writer::end_tag('div'); // row
3468 $output .= html_writer::end_tag('div'); // forumpost
3470 // Mark the forum post as read if required
3471 if ($istracked && !$CFG->forum_usermarksread && !$postisread) {
3472 forum_tp_mark_post_read($USER->id, $post, $forum->id);
3475 if ($return) {
3476 return $output;
3478 echo $output;
3479 return;
3483 * Return rating related permissions
3485 * @param string $options the context id
3486 * @return array an associative array of the user's rating permissions
3488 function forum_rating_permissions($contextid, $component, $ratingarea) {
3489 $context = get_context_instance_by_id($contextid, MUST_EXIST);
3490 if ($component != 'mod_forum' || $ratingarea != 'post') {
3491 // We don't know about this component/ratingarea so just return null to get the
3492 // default restrictive permissions.
3493 return null;
3495 return array(
3496 'view' => has_capability('mod/forum:viewrating', $context),
3497 'viewany' => has_capability('mod/forum:viewanyrating', $context),
3498 'viewall' => has_capability('mod/forum:viewallratings', $context),
3499 'rate' => has_capability('mod/forum:rate', $context)
3504 * Validates a submitted rating
3505 * @param array $params submitted data
3506 * context => object the context in which the rated items exists [required]
3507 * component => The component for this module - should always be mod_forum [required]
3508 * ratingarea => object the context in which the rated items exists [required]
3509 * itemid => int the ID of the object being rated [required]
3510 * scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
3511 * rating => int the submitted rating [required]
3512 * rateduserid => int the id of the user whose items have been rated. NOT the user who submitted the ratings. 0 to update all. [required]
3513 * aggregation => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [required]
3514 * @return boolean true if the rating is valid. Will throw rating_exception if not
3516 function forum_rating_validate($params) {
3517 global $DB, $USER;
3519 // Check the component is mod_forum
3520 if ($params['component'] != 'mod_forum') {
3521 throw new rating_exception('invalidcomponent');
3524 // Check the ratingarea is post (the only rating area in forum)
3525 if ($params['ratingarea'] != 'post') {
3526 throw new rating_exception('invalidratingarea');
3529 // Check the rateduserid is not the current user .. you can't rate your own posts
3530 if ($params['rateduserid'] == $USER->id) {
3531 throw new rating_exception('nopermissiontorate');
3534 // Fetch all the related records ... we need to do this anyway to call forum_user_can_see_post
3535 $post = $DB->get_record('forum_posts', array('id' => $params['itemid'], 'userid' => $params['rateduserid']), '*', MUST_EXIST);
3536 $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion), '*', MUST_EXIST);
3537 $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
3538 $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
3539 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id , false, MUST_EXIST);
3540 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
3542 // Make sure the context provided is the context of the forum
3543 if ($context->id != $params['context']->id) {
3544 throw new rating_exception('invalidcontext');
3547 if ($forum->scale != $params['scaleid']) {
3548 //the scale being submitted doesnt match the one in the database
3549 throw new rating_exception('invalidscaleid');
3552 // check the item we're rating was created in the assessable time window
3553 if (!empty($forum->assesstimestart) && !empty($forum->assesstimefinish)) {
3554 if ($post->created < $forum->assesstimestart || $post->created > $forum->assesstimefinish) {
3555 throw new rating_exception('notavailable');
3559 //check that the submitted rating is valid for the scale
3561 // lower limit
3562 if ($params['rating'] < 0 && $params['rating'] != RATING_UNSET_RATING) {
3563 throw new rating_exception('invalidnum');
3566 // upper limit
3567 if ($forum->scale < 0) {
3568 //its a custom scale
3569 $scalerecord = $DB->get_record('scale', array('id' => -$forum->scale));
3570 if ($scalerecord) {
3571 $scalearray = explode(',', $scalerecord->scale);
3572 if ($params['rating'] > count($scalearray)) {
3573 throw new rating_exception('invalidnum');
3575 } else {
3576 throw new rating_exception('invalidscaleid');
3578 } else if ($params['rating'] > $forum->scale) {
3579 //if its numeric and submitted rating is above maximum
3580 throw new rating_exception('invalidnum');
3583 // Make sure groups allow this user to see the item they're rating
3584 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used
3585 if (!groups_group_exists($discussion->groupid)) { // Can't find group
3586 throw new rating_exception('cannotfindgroup');//something is wrong
3589 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
3590 // do not allow rating of posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
3591 throw new rating_exception('notmemberofgroup');
3595 // perform some final capability checks
3596 if (!forum_user_can_see_post($forum, $discussion, $post, $USER, $cm)) {
3597 throw new rating_exception('nopermissiontorate');
3600 return true;
3605 * This function prints the overview of a discussion in the forum listing.
3606 * It needs some discussion information and some post information, these
3607 * happen to be combined for efficiency in the $post parameter by the function
3608 * that calls this one: forum_print_latest_discussions()
3610 * @global object
3611 * @global object
3612 * @param object $post The post object (passed by reference for speed).
3613 * @param object $forum The forum object.
3614 * @param int $group Current group.
3615 * @param string $datestring Format to use for the dates.
3616 * @param boolean $cantrack Is tracking enabled for this forum.
3617 * @param boolean $forumtracked Is the user tracking this forum.
3618 * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
3620 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
3621 $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) {
3623 global $USER, $CFG, $OUTPUT;
3625 static $rowcount;
3626 static $strmarkalldread;
3628 if (empty($modcontext)) {
3629 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3630 print_error('invalidcoursemodule');
3632 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3635 if (!isset($rowcount)) {
3636 $rowcount = 0;
3637 $strmarkalldread = get_string('markalldread', 'forum');
3638 } else {
3639 $rowcount = ($rowcount + 1) % 2;
3642 $post->subject = format_string($post->subject,true);
3644 echo "\n\n";
3645 echo '<tr class="discussion r'.$rowcount.'">';
3647 // Topic
3648 echo '<td class="topic starter">';
3649 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.$post->subject.'</a>';
3650 echo "</td>\n";
3652 // Picture
3653 $postuser = new stdClass();
3654 $postuser->id = $post->userid;
3655 $postuser->firstname = $post->firstname;
3656 $postuser->lastname = $post->lastname;
3657 $postuser->imagealt = $post->imagealt;
3658 $postuser->picture = $post->picture;
3659 $postuser->email = $post->email;
3661 echo '<td class="picture">';
3662 echo $OUTPUT->user_picture($postuser, array('courseid'=>$forum->course));
3663 echo "</td>\n";
3665 // User name
3666 $fullname = fullname($post, has_capability('moodle/site:viewfullnames', $modcontext));
3667 echo '<td class="author">';
3668 echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->userid.'&amp;course='.$forum->course.'">'.$fullname.'</a>';
3669 echo "</td>\n";
3671 // Group picture
3672 if ($group !== -1) { // Groups are active - group is a group data object or NULL
3673 echo '<td class="picture group">';
3674 if (!empty($group->picture) and empty($group->hidepicture)) {
3675 print_group_picture($group, $forum->course, false, false, true);
3676 } else if (isset($group->id)) {
3677 if($canviewparticipants) {
3678 echo '<a href="'.$CFG->wwwroot.'/user/index.php?id='.$forum->course.'&amp;group='.$group->id.'">'.$group->name.'</a>';
3679 } else {
3680 echo $group->name;
3683 echo "</td>\n";
3686 if (has_capability('mod/forum:viewdiscussion', $modcontext)) { // Show the column with replies
3687 echo '<td class="replies">';
3688 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3689 echo $post->replies.'</a>';
3690 echo "</td>\n";
3692 if ($cantrack) {
3693 echo '<td class="replies">';
3694 if ($forumtracked) {
3695 if ($post->unread > 0) {
3696 echo '<span class="unread">';
3697 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#unread">';
3698 echo $post->unread;
3699 echo '</a>';
3700 echo '<a title="'.$strmarkalldread.'" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
3701 $forum->id.'&amp;d='.$post->discussion.'&amp;mark=read&amp;returnpage=view.php">' .
3702 '<img src="'.$OUTPUT->pix_url('t/clear') . '" class="iconsmall" alt="'.$strmarkalldread.'" /></a>';
3703 echo '</span>';
3704 } else {
3705 echo '<span class="read">';
3706 echo $post->unread;
3707 echo '</span>';
3709 } else {
3710 echo '<span class="read">';
3711 echo '-';
3712 echo '</span>';
3714 echo "</td>\n";
3718 echo '<td class="lastpost">';
3719 $usedate = (empty($post->timemodified)) ? $post->modified : $post->timemodified; // Just in case
3720 $parenturl = (empty($post->lastpostid)) ? '' : '&amp;parent='.$post->lastpostid;
3721 $usermodified = new stdClass();
3722 $usermodified->id = $post->usermodified;
3723 $usermodified->firstname = $post->umfirstname;
3724 $usermodified->lastname = $post->umlastname;
3725 echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->usermodified.'&amp;course='.$forum->course.'">'.
3726 fullname($usermodified).'</a><br />';
3727 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.$parenturl.'">'.
3728 userdate($usedate, $datestring).'</a>';
3729 echo "</td>\n";
3731 echo "</tr>\n\n";
3737 * Given a post object that we already know has a long message
3738 * this function truncates the message nicely to the first
3739 * sane place between $CFG->forum_longpost and $CFG->forum_shortpost
3741 * @global object
3742 * @param string $message
3743 * @return string
3745 function forum_shorten_post($message) {
3747 global $CFG;
3749 $i = 0;
3750 $tag = false;
3751 $length = strlen($message);
3752 $count = 0;
3753 $stopzone = false;
3754 $truncate = 0;
3756 for ($i=0; $i<$length; $i++) {
3757 $char = $message[$i];
3759 switch ($char) {
3760 case "<":
3761 $tag = true;
3762 break;
3763 case ">":
3764 $tag = false;
3765 break;
3766 default:
3767 if (!$tag) {
3768 if ($stopzone) {
3769 if ($char == ".") {
3770 $truncate = $i+1;
3771 break 2;
3774 $count++;
3776 break;
3778 if (!$stopzone) {
3779 if ($count > $CFG->forum_shortpost) {
3780 $stopzone = true;
3785 if (!$truncate) {
3786 $truncate = $i;
3789 return substr($message, 0, $truncate);
3793 * Print the drop down that allows the user to select how they want to have
3794 * the discussion displayed.
3796 * @param int $id forum id if $forumtype is 'single',
3797 * discussion id for any other forum type
3798 * @param mixed $mode forum layout mode
3799 * @param string $forumtype optional
3801 function forum_print_mode_form($id, $mode, $forumtype='') {
3802 global $OUTPUT;
3803 if ($forumtype == 'single') {
3804 $select = new single_select(new moodle_url("/mod/forum/view.php", array('f'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode");
3805 $select->set_label(get_string('displaymode', 'forum'), array('class' => 'accesshide'));
3806 $select->class = "forummode";
3807 } else {
3808 $select = new single_select(new moodle_url("/mod/forum/discuss.php", array('d'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode");
3809 $select->set_label(get_string('displaymode', 'forum'), array('class' => 'accesshide'));
3811 echo $OUTPUT->render($select);
3815 * @global object
3816 * @param object $course
3817 * @param string $search
3818 * @return string
3820 function forum_search_form($course, $search='') {
3821 global $CFG, $OUTPUT;
3823 $output = '<div class="forumsearch">';
3824 $output .= '<form action="'.$CFG->wwwroot.'/mod/forum/search.php" style="display:inline">';
3825 $output .= '<fieldset class="invisiblefieldset">';
3826 $output .= $OUTPUT->help_icon('search');
3827 $output .= '<label class="accesshide" for="search" >'.get_string('search', 'forum').'</label>';
3828 $output .= '<input id="search" name="search" type="text" size="18" value="'.s($search, true).'" alt="search" />';
3829 $output .= '<label class="accesshide" for="searchforums" >'.get_string('searchforums', 'forum').'</label>';
3830 $output .= '<input id="searchforums" value="'.get_string('searchforums', 'forum').'" type="submit" />';
3831 $output .= '<input name="id" type="hidden" value="'.$course->id.'" />';
3832 $output .= '</fieldset>';
3833 $output .= '</form>';
3834 $output .= '</div>';
3836 return $output;
3841 * @global object
3842 * @global object
3844 function forum_set_return() {
3845 global $CFG, $SESSION;
3847 if (! isset($SESSION->fromdiscussion)) {
3848 if (!empty($_SERVER['HTTP_REFERER'])) {
3849 $referer = $_SERVER['HTTP_REFERER'];
3850 } else {
3851 $referer = "";
3853 // If the referer is NOT a login screen then save it.
3854 if (! strncasecmp("$CFG->wwwroot/login", $referer, 300)) {
3855 $SESSION->fromdiscussion = $_SERVER["HTTP_REFERER"];
3862 * @global object
3863 * @param string $default
3864 * @return string
3866 function forum_go_back_to($default) {
3867 global $SESSION;
3869 if (!empty($SESSION->fromdiscussion)) {
3870 $returnto = $SESSION->fromdiscussion;
3871 unset($SESSION->fromdiscussion);
3872 return $returnto;
3873 } else {
3874 return $default;
3879 * Given a discussion object that is being moved to $forumto,
3880 * this function checks all posts in that discussion
3881 * for attachments, and if any are found, these are
3882 * moved to the new forum directory.
3884 * @global object
3885 * @param object $discussion
3886 * @param int $forumfrom source forum id
3887 * @param int $forumto target forum id
3888 * @return bool success
3890 function forum_move_attachments($discussion, $forumfrom, $forumto) {
3891 global $DB;
3893 $fs = get_file_storage();
3895 $newcm = get_coursemodule_from_instance('forum', $forumto);
3896 $oldcm = get_coursemodule_from_instance('forum', $forumfrom);
3898 $newcontext = get_context_instance(CONTEXT_MODULE, $newcm->id);
3899 $oldcontext = get_context_instance(CONTEXT_MODULE, $oldcm->id);
3901 // loop through all posts, better not use attachment flag ;-)
3902 if ($posts = $DB->get_records('forum_posts', array('discussion'=>$discussion->id), '', 'id, attachment')) {
3903 foreach ($posts as $post) {
3904 $fs->move_area_files_to_new_context($oldcontext->id,
3905 $newcontext->id, 'mod_forum', 'post', $post->id);
3906 $attachmentsmoved = $fs->move_area_files_to_new_context($oldcontext->id,
3907 $newcontext->id, 'mod_forum', 'attachment', $post->id);
3908 if ($attachmentsmoved > 0 && $post->attachment != '1') {
3909 // Weird - let's fix it
3910 $post->attachment = '1';
3911 $DB->update_record('forum_posts', $post);
3912 } else if ($attachmentsmoved == 0 && $post->attachment != '') {
3913 // Weird - let's fix it
3914 $post->attachment = '';
3915 $DB->update_record('forum_posts', $post);
3920 return true;
3924 * Returns attachments as formated text/html optionally with separate images
3926 * @global object
3927 * @global object
3928 * @global object
3929 * @param object $post
3930 * @param object $cm
3931 * @param string $type html/text/separateimages
3932 * @return mixed string or array of (html text withouth images and image HTML)
3934 function forum_print_attachments($post, $cm, $type) {
3935 global $CFG, $DB, $USER, $OUTPUT;
3937 if (empty($post->attachment)) {
3938 return $type !== 'separateimages' ? '' : array('', '');
3941 if (!in_array($type, array('separateimages', 'html', 'text'))) {
3942 return $type !== 'separateimages' ? '' : array('', '');
3945 if (!$context = get_context_instance(CONTEXT_MODULE, $cm->id)) {
3946 return $type !== 'separateimages' ? '' : array('', '');
3948 $strattachment = get_string('attachment', 'forum');
3950 $fs = get_file_storage();
3952 $imagereturn = '';
3953 $output = '';
3955 $canexport = !empty($CFG->enableportfolios) && (has_capability('mod/forum:exportpost', $context) || ($post->userid == $USER->id && has_capability('mod/forum:exportownpost', $context)));
3957 if ($canexport) {
3958 require_once($CFG->libdir.'/portfoliolib.php');
3961 $files = $fs->get_area_files($context->id, 'mod_forum', 'attachment', $post->id, "timemodified", false);
3962 if ($files) {
3963 if ($canexport) {
3964 $button = new portfolio_add_button();
3966 foreach ($files as $file) {
3967 $filename = $file->get_filename();
3968 $mimetype = $file->get_mimetype();
3969 $iconimage = '<img src="'.$OUTPUT->pix_url(file_mimetype_icon($mimetype)).'" class="icon" alt="'.$mimetype.'" />';
3970 $path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$context->id.'/mod_forum/attachment/'.$post->id.'/'.$filename);
3972 if ($type == 'html') {
3973 $output .= "<a href=\"$path\">$iconimage</a> ";
3974 $output .= "<a href=\"$path\">".s($filename)."</a>";
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 $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
3980 $output .= "<br />";
3982 } else if ($type == 'text') {
3983 $output .= "$strattachment ".s($filename).":\n$path\n";
3985 } else { //'returnimages'
3986 if (in_array($mimetype, array('image/gif', 'image/jpeg', 'image/png'))) {
3987 // Image attachments don't get printed as links
3988 $imagereturn .= "<br /><img src=\"$path\" alt=\"\" />";
3989 if ($canexport) {
3990 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php');
3991 $button->set_format_by_file($file);
3992 $imagereturn .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
3994 } else {
3995 $output .= "<a href=\"$path\">$iconimage</a> ";
3996 $output .= format_text("<a href=\"$path\">".s($filename)."</a>", FORMAT_HTML, array('context'=>$context));
3997 if ($canexport) {
3998 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php');
3999 $button->set_format_by_file($file);
4000 $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
4002 $output .= '<br />';
4008 if ($type !== 'separateimages') {
4009 return $output;
4011 } else {
4012 return array($output, $imagereturn);
4017 * Lists all browsable file areas
4019 * @param object $course
4020 * @param object $cm
4021 * @param object $context
4022 * @return array
4024 function forum_get_file_areas($course, $cm, $context) {
4025 $areas = array();
4026 return $areas;
4030 * File browsing support for forum module.
4032 * @param object $browser
4033 * @param object $areas
4034 * @param object $course
4035 * @param object $cm
4036 * @param object $context
4037 * @param string $filearea
4038 * @param int $itemid
4039 * @param string $filepath
4040 * @param string $filename
4041 * @return object file_info instance or null if not found
4043 function forum_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
4044 global $CFG, $DB;
4046 if ($context->contextlevel != CONTEXT_MODULE) {
4047 return null;
4050 $fileareas = array('attachment', 'post');
4051 if (!in_array($filearea, $fileareas)) {
4052 return null;
4055 if (!$post = $DB->get_record('forum_posts', array('id' => $itemid))) {
4056 return null;
4059 if (!$discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion))) {
4060 return null;
4063 if (!$forum = $DB->get_record('forum', array('id' => $cm->instance))) {
4064 return null;
4067 $fs = get_file_storage();
4068 $filepath = is_null($filepath) ? '/' : $filepath;
4069 $filename = is_null($filename) ? '.' : $filename;
4070 if (!($storedfile = $fs->get_file($context->id, 'mod_forum', $filearea, $itemid, $filepath, $filename))) {
4071 return null;
4074 // Make sure groups allow this user to see this file
4075 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used
4076 if (!groups_group_exists($discussion->groupid)) { // Can't find group
4077 return null; // Be safe and don't send it to anyone
4080 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
4081 // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
4082 return null;
4086 // Make sure we're allowed to see it...
4087 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
4088 return null;
4091 $urlbase = $CFG->wwwroot.'/pluginfile.php';
4092 return new file_info_stored($browser, $context, $storedfile, $urlbase, $filearea, $itemid, true, true, false);
4096 * Serves the forum attachments. Implements needed access control ;-)
4098 * @param object $course
4099 * @param object $cm
4100 * @param object $context
4101 * @param string $filearea
4102 * @param array $args
4103 * @param bool $forcedownload
4104 * @return bool false if file not found, does not return if found - justsend the file
4106 function forum_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) {
4107 global $CFG, $DB;
4109 if ($context->contextlevel != CONTEXT_MODULE) {
4110 return false;
4113 require_course_login($course, true, $cm);
4115 $fileareas = array('attachment', 'post');
4116 if (!in_array($filearea, $fileareas)) {
4117 return false;
4120 $postid = (int)array_shift($args);
4122 if (!$post = $DB->get_record('forum_posts', array('id'=>$postid))) {
4123 return false;
4126 if (!$discussion = $DB->get_record('forum_discussions', array('id'=>$post->discussion))) {
4127 return false;
4130 if (!$forum = $DB->get_record('forum', array('id'=>$cm->instance))) {
4131 return false;
4134 $fs = get_file_storage();
4135 $relativepath = implode('/', $args);
4136 $fullpath = "/$context->id/mod_forum/$filearea/$postid/$relativepath";
4137 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
4138 return false;
4141 // Make sure groups allow this user to see this file
4142 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used
4143 if (!groups_group_exists($discussion->groupid)) { // Can't find group
4144 return false; // Be safe and don't send it to anyone
4147 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
4148 // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
4149 return false;
4153 // Make sure we're allowed to see it...
4154 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
4155 return false;
4159 // finally send the file
4160 send_stored_file($file, 0, 0, true); // download MUST be forced - security!
4164 * If successful, this function returns the name of the file
4166 * @global object
4167 * @param object $post is a full post record, including course and forum
4168 * @param object $forum
4169 * @param object $cm
4170 * @param mixed $mform
4171 * @param string $message
4172 * @return bool
4174 function forum_add_attachment($post, $forum, $cm, $mform=null, &$message=null) {
4175 global $DB;
4177 if (empty($mform)) {
4178 return false;
4181 if (empty($post->attachments)) {
4182 return true; // Nothing to do
4185 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4187 $info = file_get_draft_area_info($post->attachments);
4188 $present = ($info['filecount']>0) ? '1' : '';
4189 file_save_draft_area_files($post->attachments, $context->id, 'mod_forum', 'attachment', $post->id);
4191 $DB->set_field('forum_posts', 'attachment', $present, array('id'=>$post->id));
4193 return true;
4197 * Add a new post in an existing discussion.
4199 * @global object
4200 * @global object
4201 * @global object
4202 * @param object $post
4203 * @param mixed $mform
4204 * @param string $message
4205 * @return int
4207 function forum_add_new_post($post, $mform, &$message) {
4208 global $USER, $CFG, $DB;
4210 $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion));
4211 $forum = $DB->get_record('forum', array('id' => $discussion->forum));
4212 $cm = get_coursemodule_from_instance('forum', $forum->id);
4213 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4215 $post->created = $post->modified = time();
4216 $post->mailed = "0";
4217 $post->userid = $USER->id;
4218 $post->attachment = "";
4220 $post->id = $DB->insert_record("forum_posts", $post);
4221 $post->message = file_save_draft_area_files($post->itemid, $context->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
4222 $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
4223 forum_add_attachment($post, $forum, $cm, $mform, $message);
4225 // Update discussion modified date
4226 $DB->set_field("forum_discussions", "timemodified", $post->modified, array("id" => $post->discussion));
4227 $DB->set_field("forum_discussions", "usermodified", $post->userid, array("id" => $post->discussion));
4229 if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
4230 forum_tp_mark_post_read($post->userid, $post, $post->forum);
4233 return $post->id;
4237 * Update a post
4239 * @global object
4240 * @global object
4241 * @global object
4242 * @param object $post
4243 * @param mixed $mform
4244 * @param string $message
4245 * @return bool
4247 function forum_update_post($post, $mform, &$message) {
4248 global $USER, $CFG, $DB;
4250 $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion));
4251 $forum = $DB->get_record('forum', array('id' => $discussion->forum));
4252 $cm = get_coursemodule_from_instance('forum', $forum->id);
4253 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4255 $post->modified = time();
4257 $DB->update_record('forum_posts', $post);
4259 $discussion->timemodified = $post->modified; // last modified tracking
4260 $discussion->usermodified = $post->userid; // last modified tracking
4262 if (!$post->parent) { // Post is a discussion starter - update discussion title and times too
4263 $discussion->name = $post->subject;
4264 $discussion->timestart = $post->timestart;
4265 $discussion->timeend = $post->timeend;
4267 $post->message = file_save_draft_area_files($post->itemid, $context->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
4268 $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
4270 $DB->update_record('forum_discussions', $discussion);
4272 forum_add_attachment($post, $forum, $cm, $mform, $message);
4274 if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
4275 forum_tp_mark_post_read($post->userid, $post, $post->forum);
4278 return true;
4282 * Given an object containing all the necessary data,
4283 * create a new discussion and return the id
4285 * @global object
4286 * @global object
4287 * @global object
4288 * @param object $post
4289 * @param mixed $mform
4290 * @param string $message
4291 * @param int $userid
4292 * @return object
4294 function forum_add_discussion($discussion, $mform=null, &$message=null, $userid=null) {
4295 global $USER, $CFG, $DB;
4297 $timenow = time();
4299 if (is_null($userid)) {
4300 $userid = $USER->id;
4303 // The first post is stored as a real post, and linked
4304 // to from the discuss entry.
4306 $forum = $DB->get_record('forum', array('id'=>$discussion->forum));
4307 $cm = get_coursemodule_from_instance('forum', $forum->id);
4309 $post = new stdClass();
4310 $post->discussion = 0;
4311 $post->parent = 0;
4312 $post->userid = $userid;
4313 $post->created = $timenow;
4314 $post->modified = $timenow;
4315 $post->mailed = 0;
4316 $post->subject = $discussion->name;
4317 $post->message = $discussion->message;
4318 $post->messageformat = $discussion->messageformat;
4319 $post->messagetrust = $discussion->messagetrust;
4320 $post->attachments = isset($discussion->attachments) ? $discussion->attachments : null;
4321 $post->forum = $forum->id; // speedup
4322 $post->course = $forum->course; // speedup
4323 $post->mailnow = $discussion->mailnow;
4325 $post->id = $DB->insert_record("forum_posts", $post);
4327 // TODO: Fix the calling code so that there always is a $cm when this function is called
4328 if (!empty($cm->id) && !empty($discussion->itemid)) { // In "single simple discussions" this may not exist yet
4329 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4330 $text = file_save_draft_area_files($discussion->itemid, $context->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
4331 $DB->set_field('forum_posts', 'message', $text, array('id'=>$post->id));
4334 // Now do the main entry for the discussion, linking to this first post
4336 $discussion->firstpost = $post->id;
4337 $discussion->timemodified = $timenow;
4338 $discussion->usermodified = $post->userid;
4339 $discussion->userid = $userid;
4341 $post->discussion = $DB->insert_record("forum_discussions", $discussion);
4343 // Finally, set the pointer on the post.
4344 $DB->set_field("forum_posts", "discussion", $post->discussion, array("id"=>$post->id));
4346 if (!empty($cm->id)) {
4347 forum_add_attachment($post, $forum, $cm, $mform, $message);
4350 if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
4351 forum_tp_mark_post_read($post->userid, $post, $post->forum);
4354 return $post->discussion;
4359 * Deletes a discussion and handles all associated cleanup.
4361 * @global object
4362 * @param object $discussion Discussion to delete
4363 * @param bool $fulldelete True when deleting entire forum
4364 * @param object $course Course
4365 * @param object $cm Course-module
4366 * @param object $forum Forum
4367 * @return bool
4369 function forum_delete_discussion($discussion, $fulldelete, $course, $cm, $forum) {
4370 global $DB, $CFG;
4371 require_once($CFG->libdir.'/completionlib.php');
4373 $result = true;
4375 if ($posts = $DB->get_records("forum_posts", array("discussion" => $discussion->id))) {
4376 foreach ($posts as $post) {
4377 $post->course = $discussion->course;
4378 $post->forum = $discussion->forum;
4379 if (!forum_delete_post($post, 'ignore', $course, $cm, $forum, $fulldelete)) {
4380 $result = false;
4385 forum_tp_delete_read_records(-1, -1, $discussion->id);
4387 if (!$DB->delete_records("forum_discussions", array("id"=>$discussion->id))) {
4388 $result = false;
4391 // Update completion state if we are tracking completion based on number of posts
4392 // But don't bother when deleting whole thing
4393 if (!$fulldelete) {
4394 $completion = new completion_info($course);
4395 if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC &&
4396 ($forum->completiondiscussions || $forum->completionreplies || $forum->completionposts)) {
4397 $completion->update_state($cm, COMPLETION_INCOMPLETE, $discussion->userid);
4401 return $result;
4406 * Deletes a single forum post.
4408 * @global object
4409 * @param object $post Forum post object
4410 * @param mixed $children Whether to delete children. If false, returns false
4411 * if there are any children (without deleting the post). If true,
4412 * recursively deletes all children. If set to special value 'ignore', deletes
4413 * post regardless of children (this is for use only when deleting all posts
4414 * in a disussion).
4415 * @param object $course Course
4416 * @param object $cm Course-module
4417 * @param object $forum Forum
4418 * @param bool $skipcompletion True to skip updating completion state if it
4419 * would otherwise be updated, i.e. when deleting entire forum anyway.
4420 * @return bool
4422 function forum_delete_post($post, $children, $course, $cm, $forum, $skipcompletion=false) {
4423 global $DB, $CFG;
4424 require_once($CFG->libdir.'/completionlib.php');
4426 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4428 if ($children !== 'ignore' && ($childposts = $DB->get_records('forum_posts', array('parent'=>$post->id)))) {
4429 if ($children) {
4430 foreach ($childposts as $childpost) {
4431 forum_delete_post($childpost, true, $course, $cm, $forum, $skipcompletion);
4433 } else {
4434 return false;
4438 //delete ratings
4439 require_once($CFG->dirroot.'/rating/lib.php');
4440 $delopt = new stdClass;
4441 $delopt->contextid = $context->id;
4442 $delopt->component = 'mod_forum';
4443 $delopt->ratingarea = 'post';
4444 $delopt->itemid = $post->id;
4445 $rm = new rating_manager();
4446 $rm->delete_ratings($delopt);
4448 //delete attachments
4449 $fs = get_file_storage();
4450 $fs->delete_area_files($context->id, 'mod_forum', 'attachment', $post->id);
4451 $fs->delete_area_files($context->id, 'mod_forum', 'post', $post->id);
4453 if ($DB->delete_records("forum_posts", array("id" => $post->id))) {
4455 forum_tp_delete_read_records(-1, $post->id);
4457 // Just in case we are deleting the last post
4458 forum_discussion_update_last_post($post->discussion);
4460 // Update completion state if we are tracking completion based on number of posts
4461 // But don't bother when deleting whole thing
4463 if (!$skipcompletion) {
4464 $completion = new completion_info($course);
4465 if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC &&
4466 ($forum->completiondiscussions || $forum->completionreplies || $forum->completionposts)) {
4467 $completion->update_state($cm, COMPLETION_INCOMPLETE, $post->userid);
4471 return true;
4473 return false;
4477 * @global object
4478 * @param object $post
4479 * @param bool $children
4480 * @return int
4482 function forum_count_replies($post, $children=true) {
4483 global $DB;
4484 $count = 0;
4486 if ($children) {
4487 if ($childposts = $DB->get_records('forum_posts', array('parent' => $post->id))) {
4488 foreach ($childposts as $childpost) {
4489 $count ++; // For this child
4490 $count += forum_count_replies($childpost, true);
4493 } else {
4494 $count += $DB->count_records('forum_posts', array('parent' => $post->id));
4497 return $count;
4502 * @global object
4503 * @param int $forumid
4504 * @param mixed $value
4505 * @return bool
4507 function forum_forcesubscribe($forumid, $value=1) {
4508 global $DB;
4509 return $DB->set_field("forum", "forcesubscribe", $value, array("id" => $forumid));
4513 * @global object
4514 * @param object $forum
4515 * @return bool
4517 function forum_is_forcesubscribed($forum) {
4518 global $DB;
4519 if (isset($forum->forcesubscribe)) { // then we use that
4520 return ($forum->forcesubscribe == FORUM_FORCESUBSCRIBE);
4521 } else { // Check the database
4522 return ($DB->get_field('forum', 'forcesubscribe', array('id' => $forum)) == FORUM_FORCESUBSCRIBE);
4526 function forum_get_forcesubscribed($forum) {
4527 global $DB;
4528 if (isset($forum->forcesubscribe)) { // then we use that
4529 return $forum->forcesubscribe;
4530 } else { // Check the database
4531 return $DB->get_field('forum', 'forcesubscribe', array('id' => $forum));
4536 * @global object
4537 * @param int $userid
4538 * @param object $forum
4539 * @return bool
4541 function forum_is_subscribed($userid, $forum) {
4542 global $DB;
4543 if (is_numeric($forum)) {
4544 $forum = $DB->get_record('forum', array('id' => $forum));
4546 if (forum_is_forcesubscribed($forum)) {
4547 return true;
4549 return $DB->record_exists("forum_subscriptions", array("userid" => $userid, "forum" => $forum->id));
4552 function forum_get_subscribed_forums($course) {
4553 global $USER, $CFG, $DB;
4554 $sql = "SELECT f.id
4555 FROM {forum} f
4556 LEFT JOIN {forum_subscriptions} fs ON (fs.forum = f.id AND fs.userid = ?)
4557 WHERE f.course = ?
4558 AND f.forcesubscribe <> ".FORUM_DISALLOWSUBSCRIBE."
4559 AND (f.forcesubscribe = ".FORUM_FORCESUBSCRIBE." OR fs.id IS NOT NULL)";
4560 if ($subscribed = $DB->get_records_sql($sql, array($USER->id, $course->id))) {
4561 foreach ($subscribed as $s) {
4562 $subscribed[$s->id] = $s->id;
4564 return $subscribed;
4565 } else {
4566 return array();
4571 * Returns an array of forums that the current user is subscribed to and is allowed to unsubscribe from
4573 * @return array An array of unsubscribable forums
4575 function forum_get_optional_subscribed_forums() {
4576 global $USER, $DB;
4578 // Get courses that $USER is enrolled in and can see
4579 $courses = enrol_get_my_courses();
4580 if (empty($courses)) {
4581 return array();
4584 $courseids = array();
4585 foreach($courses as $course) {
4586 $courseids[] = $course->id;
4588 list($coursesql, $courseparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED, 'c');
4590 // get all forums from the user's courses that they are subscribed to and which are not set to forced
4591 $sql = "SELECT f.id, cm.id as cm, cm.visible
4592 FROM {forum} f
4593 JOIN {course_modules} cm ON cm.instance = f.id
4594 JOIN {modules} m ON m.name = :modulename AND m.id = cm.module
4595 LEFT JOIN {forum_subscriptions} fs ON (fs.forum = f.id AND fs.userid = :userid)
4596 WHERE f.forcesubscribe <> :forcesubscribe AND fs.id IS NOT NULL
4597 AND cm.course $coursesql";
4598 $params = array_merge($courseparams, array('modulename'=>'forum', 'userid'=>$USER->id, 'forcesubscribe'=>FORUM_FORCESUBSCRIBE));
4599 if (!$forums = $DB->get_records_sql($sql, $params)) {
4600 return array();
4603 $unsubscribableforums = array(); // Array to return
4605 foreach($forums as $forum) {
4607 if (empty($forum->visible)) {
4608 // the forum is hidden
4609 $context = context_module::instance($forum->cm);
4610 if (!has_capability('moodle/course:viewhiddenactivities', $context)) {
4611 // the user can't see the hidden forum
4612 continue;
4616 // subscribe.php only requires 'mod/forum:managesubscriptions' when
4617 // unsubscribing a user other than yourself so we don't require it here either
4619 // A check for whether the forum has subscription set to forced is built into the SQL above
4621 $unsubscribableforums[] = $forum;
4624 return $unsubscribableforums;
4628 * Adds user to the subscriber list
4630 * @global object
4631 * @param int $userid
4632 * @param int $forumid
4634 function forum_subscribe($userid, $forumid) {
4635 global $DB;
4637 if ($DB->record_exists("forum_subscriptions", array("userid"=>$userid, "forum"=>$forumid))) {
4638 return true;
4641 $sub = new stdClass();
4642 $sub->userid = $userid;
4643 $sub->forum = $forumid;
4645 return $DB->insert_record("forum_subscriptions", $sub);
4649 * Removes user from the subscriber list
4651 * @global object
4652 * @param int $userid
4653 * @param int $forumid
4655 function forum_unsubscribe($userid, $forumid) {
4656 global $DB;
4657 return $DB->delete_records("forum_subscriptions", array("userid"=>$userid, "forum"=>$forumid));
4661 * Given a new post, subscribes or unsubscribes as appropriate.
4662 * Returns some text which describes what happened.
4664 * @global objec
4665 * @param object $post
4666 * @param object $forum
4668 function forum_post_subscription($post, $forum) {
4670 global $USER;
4672 $action = '';
4673 $subscribed = forum_is_subscribed($USER->id, $forum);
4675 if ($forum->forcesubscribe == FORUM_FORCESUBSCRIBE) { // database ignored
4676 return "";
4678 } elseif (($forum->forcesubscribe == FORUM_DISALLOWSUBSCRIBE)
4679 && !has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_COURSE, $forum->course), $USER->id)) {
4680 if ($subscribed) {
4681 $action = 'unsubscribe'; // sanity check, following MDL-14558
4682 } else {
4683 return "";
4686 } else { // go with the user's choice
4687 if (isset($post->subscribe)) {
4688 // no change
4689 if ((!empty($post->subscribe) && $subscribed)
4690 || (empty($post->subscribe) && !$subscribed)) {
4691 return "";
4693 } elseif (!empty($post->subscribe) && !$subscribed) {
4694 $action = 'subscribe';
4696 } elseif (empty($post->subscribe) && $subscribed) {
4697 $action = 'unsubscribe';
4702 $info = new stdClass();
4703 $info->name = fullname($USER);
4704 $info->forum = format_string($forum->name);
4706 switch ($action) {
4707 case 'subscribe':
4708 forum_subscribe($USER->id, $post->forum);
4709 return "<p>".get_string("nowsubscribed", "forum", $info)."</p>";
4710 case 'unsubscribe':
4711 forum_unsubscribe($USER->id, $post->forum);
4712 return "<p>".get_string("nownotsubscribed", "forum", $info)."</p>";
4717 * Generate and return the subscribe or unsubscribe link for a forum.
4719 * @param object $forum the forum. Fields used are $forum->id and $forum->forcesubscribe.
4720 * @param object $context the context object for this forum.
4721 * @param array $messages text used for the link in its various states
4722 * (subscribed, unsubscribed, forcesubscribed or cantsubscribe).
4723 * Any strings not passed in are taken from the $defaultmessages array
4724 * at the top of the function.
4725 * @param bool $cantaccessagroup
4726 * @param bool $fakelink
4727 * @param bool $backtoindex
4728 * @param array $subscribed_forums
4729 * @return string
4731 function forum_get_subscribe_link($forum, $context, $messages = array(), $cantaccessagroup = false, $fakelink=true, $backtoindex=false, $subscribed_forums=null) {
4732 global $CFG, $USER, $PAGE, $OUTPUT;
4733 $defaultmessages = array(
4734 'subscribed' => get_string('unsubscribe', 'forum'),
4735 'unsubscribed' => get_string('subscribe', 'forum'),
4736 'cantaccessgroup' => get_string('no'),
4737 'forcesubscribed' => get_string('everyoneissubscribed', 'forum'),
4738 'cantsubscribe' => get_string('disallowsubscribe','forum')
4740 $messages = $messages + $defaultmessages;
4742 if (forum_is_forcesubscribed($forum)) {
4743 return $messages['forcesubscribed'];
4744 } else if ($forum->forcesubscribe == FORUM_DISALLOWSUBSCRIBE && !has_capability('mod/forum:managesubscriptions', $context)) {
4745 return $messages['cantsubscribe'];
4746 } else if ($cantaccessagroup) {
4747 return $messages['cantaccessgroup'];
4748 } else {
4749 if (!is_enrolled($context, $USER, '', true)) {
4750 return '';
4752 if (is_null($subscribed_forums)) {
4753 $subscribed = forum_is_subscribed($USER->id, $forum);
4754 } else {
4755 $subscribed = !empty($subscribed_forums[$forum->id]);
4757 if ($subscribed) {
4758 $linktext = $messages['subscribed'];
4759 $linktitle = get_string('subscribestop', 'forum');
4760 } else {
4761 $linktext = $messages['unsubscribed'];
4762 $linktitle = get_string('subscribestart', 'forum');
4765 $options = array();
4766 if ($backtoindex) {
4767 $backtoindexlink = '&amp;backtoindex=1';
4768 $options['backtoindex'] = 1;
4769 } else {
4770 $backtoindexlink = '';
4772 $link = '';
4774 if ($fakelink) {
4775 $PAGE->requires->js('/mod/forum/forum.js');
4776 $PAGE->requires->js_function_call('forum_produce_subscribe_link', array($forum->id, $backtoindexlink, $linktext, $linktitle));
4777 $link = "<noscript>";
4779 $options['id'] = $forum->id;
4780 $options['sesskey'] = sesskey();
4781 $url = new moodle_url('/mod/forum/subscribe.php', $options);
4782 $link .= $OUTPUT->single_button($url, $linktext, 'get', array('title'=>$linktitle));
4783 if ($fakelink) {
4784 $link .= '</noscript>';
4787 return $link;
4793 * Generate and return the track or no track link for a forum.
4795 * @global object
4796 * @global object
4797 * @global object
4798 * @param object $forum the forum. Fields used are $forum->id and $forum->forcesubscribe.
4799 * @param array $messages
4800 * @param bool $fakelink
4801 * @return string
4803 function forum_get_tracking_link($forum, $messages=array(), $fakelink=true) {
4804 global $CFG, $USER, $PAGE, $OUTPUT;
4806 static $strnotrackforum, $strtrackforum;
4808 if (isset($messages['trackforum'])) {
4809 $strtrackforum = $messages['trackforum'];
4811 if (isset($messages['notrackforum'])) {
4812 $strnotrackforum = $messages['notrackforum'];
4814 if (empty($strtrackforum)) {
4815 $strtrackforum = get_string('trackforum', 'forum');
4817 if (empty($strnotrackforum)) {
4818 $strnotrackforum = get_string('notrackforum', 'forum');
4821 if (forum_tp_is_tracked($forum)) {
4822 $linktitle = $strnotrackforum;
4823 $linktext = $strnotrackforum;
4824 } else {
4825 $linktitle = $strtrackforum;
4826 $linktext = $strtrackforum;
4829 $link = '';
4830 if ($fakelink) {
4831 $PAGE->requires->js('/mod/forum/forum.js');
4832 $PAGE->requires->js_function_call('forum_produce_tracking_link', Array($forum->id, $linktext, $linktitle));
4833 // use <noscript> to print button in case javascript is not enabled
4834 $link .= '<noscript>';
4836 $url = new moodle_url('/mod/forum/settracking.php', array('id'=>$forum->id));
4837 $link .= $OUTPUT->single_button($url, $linktext, 'get', array('title'=>$linktitle));
4839 if ($fakelink) {
4840 $link .= '</noscript>';
4843 return $link;
4849 * Returns true if user created new discussion already
4851 * @global object
4852 * @global object
4853 * @param int $forumid
4854 * @param int $userid
4855 * @return bool
4857 function forum_user_has_posted_discussion($forumid, $userid) {
4858 global $CFG, $DB;
4860 $sql = "SELECT 'x'
4861 FROM {forum_discussions} d, {forum_posts} p
4862 WHERE d.forum = ? AND p.discussion = d.id AND p.parent = 0 and p.userid = ?";
4864 return $DB->record_exists_sql($sql, array($forumid, $userid));
4868 * @global object
4869 * @global object
4870 * @param int $forumid
4871 * @param int $userid
4872 * @return array
4874 function forum_discussions_user_has_posted_in($forumid, $userid) {
4875 global $CFG, $DB;
4877 $haspostedsql = "SELECT d.id AS id,
4879 FROM {forum_posts} p,
4880 {forum_discussions} d
4881 WHERE p.discussion = d.id
4882 AND d.forum = ?
4883 AND p.userid = ?";
4885 return $DB->get_records_sql($haspostedsql, array($forumid, $userid));
4889 * @global object
4890 * @global object
4891 * @param int $forumid
4892 * @param int $did
4893 * @param int $userid
4894 * @return bool
4896 function forum_user_has_posted($forumid, $did, $userid) {
4897 global $DB;
4899 if (empty($did)) {
4900 // posted in any forum discussion?
4901 $sql = "SELECT 'x'
4902 FROM {forum_posts} p
4903 JOIN {forum_discussions} d ON d.id = p.discussion
4904 WHERE p.userid = :userid AND d.forum = :forumid";
4905 return $DB->record_exists_sql($sql, array('forumid'=>$forumid,'userid'=>$userid));
4906 } else {
4907 return $DB->record_exists('forum_posts', array('discussion'=>$did,'userid'=>$userid));
4912 * Returns creation time of the first user's post in given discussion
4913 * @global object $DB
4914 * @param int $did Discussion id
4915 * @param int $userid User id
4916 * @return int|bool post creation time stamp or return false
4918 function forum_get_user_posted_time($did, $userid) {
4919 global $DB;
4921 $posttime = $DB->get_field('forum_posts', 'MIN(created)', array('userid'=>$userid, 'discussion'=>$did));
4922 if (empty($posttime)) {
4923 return false;
4925 return $posttime;
4929 * @global object
4930 * @param object $forum
4931 * @param object $currentgroup
4932 * @param int $unused
4933 * @param object $cm
4934 * @param object $context
4935 * @return bool
4937 function forum_user_can_post_discussion($forum, $currentgroup=null, $unused=-1, $cm=NULL, $context=NULL) {
4938 // $forum is an object
4939 global $USER;
4941 // shortcut - guest and not-logged-in users can not post
4942 if (isguestuser() or !isloggedin()) {
4943 return false;
4946 if (!$cm) {
4947 debugging('missing cm', DEBUG_DEVELOPER);
4948 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
4949 print_error('invalidcoursemodule');
4953 if (!$context) {
4954 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4957 if ($currentgroup === null) {
4958 $currentgroup = groups_get_activity_group($cm);
4961 $groupmode = groups_get_activity_groupmode($cm);
4963 if ($forum->type == 'news') {
4964 $capname = 'mod/forum:addnews';
4965 } else if ($forum->type == 'qanda') {
4966 $capname = 'mod/forum:addquestion';
4967 } else {
4968 $capname = 'mod/forum:startdiscussion';
4971 if (!has_capability($capname, $context)) {
4972 return false;
4975 if ($forum->type == 'single') {
4976 return false;
4979 if ($forum->type == 'eachuser') {
4980 if (forum_user_has_posted_discussion($forum->id, $USER->id)) {
4981 return false;
4985 if (!$groupmode or has_capability('moodle/site:accessallgroups', $context)) {
4986 return true;
4989 if ($currentgroup) {
4990 return groups_is_member($currentgroup);
4991 } else {
4992 // no group membership and no accessallgroups means no new discussions
4993 // reverted to 1.7 behaviour in 1.9+, buggy in 1.8.0-1.9.0
4994 return false;
4999 * This function checks whether the user can reply to posts in a forum
5000 * discussion. Use forum_user_can_post_discussion() to check whether the user
5001 * can start discussions.
5003 * @global object
5004 * @global object
5005 * @uses DEBUG_DEVELOPER
5006 * @uses CONTEXT_MODULE
5007 * @uses VISIBLEGROUPS
5008 * @param object $forum forum object
5009 * @param object $discussion
5010 * @param object $user
5011 * @param object $cm
5012 * @param object $course
5013 * @param object $context
5014 * @return bool
5016 function forum_user_can_post($forum, $discussion, $user=NULL, $cm=NULL, $course=NULL, $context=NULL) {
5017 global $USER, $DB;
5018 if (empty($user)) {
5019 $user = $USER;
5022 // shortcut - guest and not-logged-in users can not post
5023 if (isguestuser($user) or empty($user->id)) {
5024 return false;
5027 if (!isset($discussion->groupid)) {
5028 debugging('incorrect discussion parameter', DEBUG_DEVELOPER);
5029 return false;
5032 if (!$cm) {
5033 debugging('missing cm', DEBUG_DEVELOPER);
5034 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5035 print_error('invalidcoursemodule');
5039 if (!$course) {
5040 debugging('missing course', DEBUG_DEVELOPER);
5041 if (!$course = $DB->get_record('course', array('id' => $forum->course))) {
5042 print_error('invalidcourseid');
5046 if (!$context) {
5047 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
5050 // normal users with temporary guest access can not post, suspended users can not post either
5051 if (!is_viewing($context, $user->id) and !is_enrolled($context, $user->id, '', true)) {
5052 return false;
5055 if ($forum->type == 'news') {
5056 $capname = 'mod/forum:replynews';
5057 } else {
5058 $capname = 'mod/forum:replypost';
5061 if (!has_capability($capname, $context, $user->id)) {
5062 return false;
5065 if (!$groupmode = groups_get_activity_groupmode($cm, $course)) {
5066 return true;
5069 if (has_capability('moodle/site:accessallgroups', $context)) {
5070 return true;
5073 if ($groupmode == VISIBLEGROUPS) {
5074 if ($discussion->groupid == -1) {
5075 // allow students to reply to all participants discussions - this was not possible in Moodle <1.8
5076 return true;
5078 return groups_is_member($discussion->groupid);
5080 } else {
5081 //separate groups
5082 if ($discussion->groupid == -1) {
5083 return false;
5085 return groups_is_member($discussion->groupid);
5091 * checks to see if a user can view a particular post
5093 * @global object
5094 * @global object
5095 * @uses CONTEXT_MODULE
5096 * @uses SEPARATEGROUPS
5097 * @param object $post
5098 * @param object $course
5099 * @param object $cm
5100 * @param object $forum
5101 * @param object $discussion
5102 * @param object $user
5104 function forum_user_can_view_post($post, $course, $cm, $forum, $discussion, $user=NULL){
5106 global $CFG, $USER;
5108 if (!$user){
5109 $user = $USER;
5112 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
5113 if (!has_capability('mod/forum:viewdiscussion', $modcontext)) {
5114 return false;
5117 // If it's a grouped discussion, make sure the user is a member
5118 if ($discussion->groupid > 0) {
5119 $groupmode = groups_get_activity_groupmode($cm);
5120 if ($groupmode == SEPARATEGROUPS) {
5121 return groups_is_member($discussion->groupid) || has_capability('moodle/site:accessallgroups', $modcontext);
5124 return true;
5129 * @global object
5130 * @global object
5131 * @uses DEBUG_DEVELOPER
5132 * @param object $forum
5133 * @param object $discussion
5134 * @param object $context
5135 * @param object $user
5136 * @return bool
5138 function forum_user_can_see_discussion($forum, $discussion, $context, $user=NULL) {
5139 global $USER, $DB;
5141 if (empty($user) || empty($user->id)) {
5142 $user = $USER;
5145 // retrieve objects (yuk)
5146 if (is_numeric($forum)) {
5147 debugging('missing full forum', DEBUG_DEVELOPER);
5148 if (!$forum = $DB->get_record('forum',array('id'=>$forum))) {
5149 return false;
5152 if (is_numeric($discussion)) {
5153 debugging('missing full discussion', DEBUG_DEVELOPER);
5154 if (!$discussion = $DB->get_record('forum_discussions',array('id'=>$discussion))) {
5155 return false;
5159 if (!has_capability('mod/forum:viewdiscussion', $context)) {
5160 return false;
5163 if ($forum->type == 'qanda' &&
5164 !forum_user_has_posted($forum->id, $discussion->id, $user->id) &&
5165 !has_capability('mod/forum:viewqandawithoutposting', $context)) {
5166 return false;
5168 return true;
5173 * @global object
5174 * @global object
5175 * @param object $forum
5176 * @param object $discussion
5177 * @param object $post
5178 * @param object $user
5179 * @param object $cm
5180 * @return bool
5182 function forum_user_can_see_post($forum, $discussion, $post, $user=NULL, $cm=NULL) {
5183 global $CFG, $USER, $DB;
5185 // retrieve objects (yuk)
5186 if (is_numeric($forum)) {
5187 debugging('missing full forum', DEBUG_DEVELOPER);
5188 if (!$forum = $DB->get_record('forum',array('id'=>$forum))) {
5189 return false;
5193 if (is_numeric($discussion)) {
5194 debugging('missing full discussion', DEBUG_DEVELOPER);
5195 if (!$discussion = $DB->get_record('forum_discussions',array('id'=>$discussion))) {
5196 return false;
5199 if (is_numeric($post)) {
5200 debugging('missing full post', DEBUG_DEVELOPER);
5201 if (!$post = $DB->get_record('forum_posts',array('id'=>$post))) {
5202 return false;
5205 if (!isset($post->id) && isset($post->parent)) {
5206 $post->id = $post->parent;
5209 if (!$cm) {
5210 debugging('missing cm', DEBUG_DEVELOPER);
5211 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5212 print_error('invalidcoursemodule');
5216 if (empty($user) || empty($user->id)) {
5217 $user = $USER;
5220 $canviewdiscussion = !empty($cm->cache->caps['mod/forum:viewdiscussion']) || has_capability('mod/forum:viewdiscussion', get_context_instance(CONTEXT_MODULE, $cm->id), $user->id);
5221 if (!$canviewdiscussion && !has_all_capabilities(array('moodle/user:viewdetails', 'moodle/user:readuserposts'), get_context_instance(CONTEXT_USER, $post->userid))) {
5222 return false;
5225 if (isset($cm->uservisible)) {
5226 if (!$cm->uservisible) {
5227 return false;
5229 } else {
5230 if (!coursemodule_visible_for_user($cm, $user->id)) {
5231 return false;
5235 if ($forum->type == 'qanda') {
5236 $firstpost = forum_get_firstpost_from_discussion($discussion->id);
5237 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
5238 $userfirstpost = forum_get_user_posted_time($discussion->id, $user->id);
5240 return (($userfirstpost !== false && (time() - $userfirstpost >= $CFG->maxeditingtime)) ||
5241 $firstpost->id == $post->id || $post->userid == $user->id || $firstpost->userid == $user->id ||
5242 has_capability('mod/forum:viewqandawithoutposting', $modcontext, $user->id, false));
5244 return true;
5249 * Prints the discussion view screen for a forum.
5251 * @global object
5252 * @global object
5253 * @param object $course The current course object.
5254 * @param object $forum Forum to be printed.
5255 * @param int $maxdiscussions .
5256 * @param string $displayformat The display format to use (optional).
5257 * @param string $sort Sort arguments for database query (optional).
5258 * @param int $groupmode Group mode of the forum (optional).
5259 * @param void $unused (originally current group)
5260 * @param int $page Page mode, page to display (optional).
5261 * @param int $perpage The maximum number of discussions per page(optional)
5264 function forum_print_latest_discussions($course, $forum, $maxdiscussions=-1, $displayformat='plain', $sort='',
5265 $currentgroup=-1, $groupmode=-1, $page=-1, $perpage=100, $cm=NULL) {
5266 global $CFG, $USER, $OUTPUT;
5268 if (!$cm) {
5269 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5270 print_error('invalidcoursemodule');
5273 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
5275 if (empty($sort)) {
5276 $sort = "d.timemodified DESC";
5279 $olddiscussionlink = false;
5281 // Sort out some defaults
5282 if ($perpage <= 0) {
5283 $perpage = 0;
5284 $page = -1;
5287 if ($maxdiscussions == 0) {
5288 // all discussions - backwards compatibility
5289 $page = -1;
5290 $perpage = 0;
5291 if ($displayformat == 'plain') {
5292 $displayformat = 'header'; // Abbreviate display by default
5295 } else if ($maxdiscussions > 0) {
5296 $page = -1;
5297 $perpage = $maxdiscussions;
5300 $fullpost = false;
5301 if ($displayformat == 'plain') {
5302 $fullpost = true;
5306 // Decide if current user is allowed to see ALL the current discussions or not
5308 // First check the group stuff
5309 if ($currentgroup == -1 or $groupmode == -1) {
5310 $groupmode = groups_get_activity_groupmode($cm, $course);
5311 $currentgroup = groups_get_activity_group($cm);
5314 $groups = array(); //cache
5316 // If the user can post discussions, then this is a good place to put the
5317 // button for it. We do not show the button if we are showing site news
5318 // and the current user is a guest.
5320 $canstart = forum_user_can_post_discussion($forum, $currentgroup, $groupmode, $cm, $context);
5321 if (!$canstart and $forum->type !== 'news') {
5322 if (isguestuser() or !isloggedin()) {
5323 $canstart = true;
5325 if (!is_enrolled($context) and !is_viewing($context)) {
5326 // allow guests and not-logged-in to see the button - they are prompted to log in after clicking the link
5327 // normal users with temporary guest access see this button too, they are asked to enrol instead
5328 // do not show the button to users with suspended enrolments here
5329 $canstart = enrol_selfenrol_available($course->id);
5333 if ($canstart) {
5334 echo '<div class="singlebutton forumaddnew">';
5335 echo "<form id=\"newdiscussionform\" method=\"get\" action=\"$CFG->wwwroot/mod/forum/post.php\">";
5336 echo '<div>';
5337 echo "<input type=\"hidden\" name=\"forum\" value=\"$forum->id\" />";
5338 switch ($forum->type) {
5339 case 'news':
5340 case 'blog':
5341 $buttonadd = get_string('addanewtopic', 'forum');
5342 break;
5343 case 'qanda':
5344 $buttonadd = get_string('addanewquestion', 'forum');
5345 break;
5346 default:
5347 $buttonadd = get_string('addanewdiscussion', 'forum');
5348 break;
5350 echo '<input type="submit" value="'.$buttonadd.'" />';
5351 echo '</div>';
5352 echo '</form>';
5353 echo "</div>\n";
5355 } else if (isguestuser() or !isloggedin() or $forum->type == 'news') {
5356 // no button and no info
5358 } else if ($groupmode and has_capability('mod/forum:startdiscussion', $context)) {
5359 // inform users why they can not post new discussion
5360 if ($currentgroup) {
5361 echo $OUTPUT->notification(get_string('cannotadddiscussion', 'forum'));
5362 } else {
5363 echo $OUTPUT->notification(get_string('cannotadddiscussionall', 'forum'));
5367 // Get all the recent discussions we're allowed to see
5369 $getuserlastmodified = ($displayformat == 'header');
5371 if (! $discussions = forum_get_discussions($cm, $sort, $fullpost, null, $maxdiscussions, $getuserlastmodified, $page, $perpage) ) {
5372 echo '<div class="forumnodiscuss">';
5373 if ($forum->type == 'news') {
5374 echo '('.get_string('nonews', 'forum').')';
5375 } else if ($forum->type == 'qanda') {
5376 echo '('.get_string('noquestions','forum').')';
5377 } else {
5378 echo '('.get_string('nodiscussions', 'forum').')';
5380 echo "</div>\n";
5381 return;
5384 // If we want paging
5385 if ($page != -1) {
5386 ///Get the number of discussions found
5387 $numdiscussions = forum_get_discussions_count($cm);
5389 ///Show the paging bar
5390 echo $OUTPUT->paging_bar($numdiscussions, $page, $perpage, "view.php?f=$forum->id");
5391 if ($numdiscussions > 1000) {
5392 // saves some memory on sites with very large forums
5393 $replies = forum_count_discussion_replies($forum->id, $sort, $maxdiscussions, $page, $perpage);
5394 } else {
5395 $replies = forum_count_discussion_replies($forum->id);
5398 } else {
5399 $replies = forum_count_discussion_replies($forum->id);
5401 if ($maxdiscussions > 0 and $maxdiscussions <= count($discussions)) {
5402 $olddiscussionlink = true;
5406 $canviewparticipants = has_capability('moodle/course:viewparticipants',$context);
5408 $strdatestring = get_string('strftimerecentfull');
5410 // Check if the forum is tracked.
5411 if ($cantrack = forum_tp_can_track_forums($forum)) {
5412 $forumtracked = forum_tp_is_tracked($forum);
5413 } else {
5414 $forumtracked = false;
5417 if ($forumtracked) {
5418 $unreads = forum_get_discussions_unread($cm);
5419 } else {
5420 $unreads = array();
5423 if ($displayformat == 'header') {
5424 echo '<table cellspacing="0" class="forumheaderlist">';
5425 echo '<thead>';
5426 echo '<tr>';
5427 echo '<th class="header topic" scope="col">'.get_string('discussion', 'forum').'</th>';
5428 echo '<th class="header author" colspan="2" scope="col">'.get_string('startedby', 'forum').'</th>';
5429 if ($groupmode > 0) {
5430 echo '<th class="header group" scope="col">'.get_string('group').'</th>';
5432 if (has_capability('mod/forum:viewdiscussion', $context)) {
5433 echo '<th class="header replies" scope="col">'.get_string('replies', 'forum').'</th>';
5434 // If the forum can be tracked, display the unread column.
5435 if ($cantrack) {
5436 echo '<th class="header replies" scope="col">'.get_string('unread', 'forum');
5437 if ($forumtracked) {
5438 echo '&nbsp;<a title="'.get_string('markallread', 'forum').
5439 '" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
5440 $forum->id.'&amp;mark=read&amp;returnpage=view.php">'.
5441 '<img src="'.$OUTPUT->pix_url('t/clear') . '" class="iconsmall" alt="'.get_string('markallread', 'forum').'" /></a>';
5443 echo '</th>';
5446 echo '<th class="header lastpost" scope="col">'.get_string('lastpost', 'forum').'</th>';
5447 echo '</tr>';
5448 echo '</thead>';
5449 echo '<tbody>';
5452 foreach ($discussions as $discussion) {
5453 if (!empty($replies[$discussion->discussion])) {
5454 $discussion->replies = $replies[$discussion->discussion]->replies;
5455 $discussion->lastpostid = $replies[$discussion->discussion]->lastpostid;
5456 } else {
5457 $discussion->replies = 0;
5460 // SPECIAL CASE: The front page can display a news item post to non-logged in users.
5461 // All posts are read in this case.
5462 if (!$forumtracked) {
5463 $discussion->unread = '-';
5464 } else if (empty($USER)) {
5465 $discussion->unread = 0;
5466 } else {
5467 if (empty($unreads[$discussion->discussion])) {
5468 $discussion->unread = 0;
5469 } else {
5470 $discussion->unread = $unreads[$discussion->discussion];
5474 if (isloggedin()) {
5475 $ownpost = ($discussion->userid == $USER->id);
5476 } else {
5477 $ownpost=false;
5479 // Use discussion name instead of subject of first post
5480 $discussion->subject = $discussion->name;
5482 switch ($displayformat) {
5483 case 'header':
5484 if ($groupmode > 0) {
5485 if (isset($groups[$discussion->groupid])) {
5486 $group = $groups[$discussion->groupid];
5487 } else {
5488 $group = $groups[$discussion->groupid] = groups_get_group($discussion->groupid);
5490 } else {
5491 $group = -1;
5493 forum_print_discussion_header($discussion, $forum, $group, $strdatestring, $cantrack, $forumtracked,
5494 $canviewparticipants, $context);
5495 break;
5496 default:
5497 $link = false;
5499 if ($discussion->replies) {
5500 $link = true;
5501 } else {
5502 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
5503 $link = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
5506 $discussion->forum = $forum->id;
5508 forum_print_post($discussion, $discussion, $forum, $cm, $course, $ownpost, 0, $link, false,
5509 '', null, true, $forumtracked);
5510 break;
5514 if ($displayformat == "header") {
5515 echo '</tbody>';
5516 echo '</table>';
5519 if ($olddiscussionlink) {
5520 if ($forum->type == 'news') {
5521 $strolder = get_string('oldertopics', 'forum');
5522 } else {
5523 $strolder = get_string('olderdiscussions', 'forum');
5525 echo '<div class="forumolddiscuss">';
5526 echo '<a href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'&amp;showall=1">';
5527 echo $strolder.'</a> ...</div>';
5530 if ($page != -1) { ///Show the paging bar
5531 echo $OUTPUT->paging_bar($numdiscussions, $page, $perpage, "view.php?f=$forum->id");
5537 * Prints a forum discussion
5539 * @uses CONTEXT_MODULE
5540 * @uses FORUM_MODE_FLATNEWEST
5541 * @uses FORUM_MODE_FLATOLDEST
5542 * @uses FORUM_MODE_THREADED
5543 * @uses FORUM_MODE_NESTED
5544 * @param stdClass $course
5545 * @param stdClass $cm
5546 * @param stdClass $forum
5547 * @param stdClass $discussion
5548 * @param stdClass $post
5549 * @param int $mode
5550 * @param mixed $canreply
5551 * @param bool $canrate
5553 function forum_print_discussion($course, $cm, $forum, $discussion, $post, $mode, $canreply=NULL, $canrate=false) {
5554 global $USER, $CFG;
5556 require_once($CFG->dirroot.'/rating/lib.php');
5558 $ownpost = (isloggedin() && $USER->id == $post->userid);
5560 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
5561 if ($canreply === NULL) {
5562 $reply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
5563 } else {
5564 $reply = $canreply;
5567 // $cm holds general cache for forum functions
5568 $cm->cache = new stdClass;
5569 $cm->cache->groups = groups_get_all_groups($course->id, 0, $cm->groupingid);
5570 $cm->cache->usersgroups = array();
5572 $posters = array();
5574 // preload all posts - TODO: improve...
5575 if ($mode == FORUM_MODE_FLATNEWEST) {
5576 $sort = "p.created DESC";
5577 } else {
5578 $sort = "p.created ASC";
5581 $forumtracked = forum_tp_is_tracked($forum);
5582 $posts = forum_get_all_discussion_posts($discussion->id, $sort, $forumtracked);
5583 $post = $posts[$post->id];
5585 foreach ($posts as $pid=>$p) {
5586 $posters[$p->userid] = $p->userid;
5589 // preload all groups of ppl that posted in this discussion
5590 if ($postersgroups = groups_get_all_groups($course->id, $posters, $cm->groupingid, 'gm.id, gm.groupid, gm.userid')) {
5591 foreach($postersgroups as $pg) {
5592 if (!isset($cm->cache->usersgroups[$pg->userid])) {
5593 $cm->cache->usersgroups[$pg->userid] = array();
5595 $cm->cache->usersgroups[$pg->userid][$pg->groupid] = $pg->groupid;
5597 unset($postersgroups);
5600 //load ratings
5601 if ($forum->assessed != RATING_AGGREGATE_NONE) {
5602 $ratingoptions = new stdClass;
5603 $ratingoptions->context = $modcontext;
5604 $ratingoptions->component = 'mod_forum';
5605 $ratingoptions->ratingarea = 'post';
5606 $ratingoptions->items = $posts;
5607 $ratingoptions->aggregate = $forum->assessed;//the aggregation method
5608 $ratingoptions->scaleid = $forum->scale;
5609 $ratingoptions->userid = $USER->id;
5610 if ($forum->type == 'single' or !$discussion->id) {
5611 $ratingoptions->returnurl = "$CFG->wwwroot/mod/forum/view.php?id=$cm->id";
5612 } else {
5613 $ratingoptions->returnurl = "$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id";
5615 $ratingoptions->assesstimestart = $forum->assesstimestart;
5616 $ratingoptions->assesstimefinish = $forum->assesstimefinish;
5618 $rm = new rating_manager();
5619 $posts = $rm->get_ratings($ratingoptions);
5623 $post->forum = $forum->id; // Add the forum id to the post object, later used by forum_print_post
5624 $post->forumtype = $forum->type;
5626 $post->subject = format_string($post->subject);
5628 $postread = !empty($post->postread);
5630 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, false,
5631 '', '', $postread, true, $forumtracked);
5633 switch ($mode) {
5634 case FORUM_MODE_FLATOLDEST :
5635 case FORUM_MODE_FLATNEWEST :
5636 default:
5637 forum_print_posts_flat($course, $cm, $forum, $discussion, $post, $mode, $reply, $forumtracked, $posts);
5638 break;
5640 case FORUM_MODE_THREADED :
5641 forum_print_posts_threaded($course, $cm, $forum, $discussion, $post, 0, $reply, $forumtracked, $posts);
5642 break;
5644 case FORUM_MODE_NESTED :
5645 forum_print_posts_nested($course, $cm, $forum, $discussion, $post, $reply, $forumtracked, $posts);
5646 break;
5652 * @global object
5653 * @global object
5654 * @uses FORUM_MODE_FLATNEWEST
5655 * @param object $course
5656 * @param object $cm
5657 * @param object $forum
5658 * @param object $discussion
5659 * @param object $post
5660 * @param object $mode
5661 * @param bool $reply
5662 * @param bool $forumtracked
5663 * @param array $posts
5664 * @return void
5666 function forum_print_posts_flat($course, &$cm, $forum, $discussion, $post, $mode, $reply, $forumtracked, $posts) {
5667 global $USER, $CFG;
5669 $link = false;
5671 if ($mode == FORUM_MODE_FLATNEWEST) {
5672 $sort = "ORDER BY created DESC";
5673 } else {
5674 $sort = "ORDER BY created ASC";
5677 foreach ($posts as $post) {
5678 if (!$post->parent) {
5679 continue;
5681 $post->subject = format_string($post->subject);
5682 $ownpost = ($USER->id == $post->userid);
5684 $postread = !empty($post->postread);
5686 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
5687 '', '', $postread, true, $forumtracked);
5692 * @todo Document this function
5694 * @global object
5695 * @global object
5696 * @uses CONTEXT_MODULE
5697 * @return void
5699 function forum_print_posts_threaded($course, &$cm, $forum, $discussion, $parent, $depth, $reply, $forumtracked, $posts) {
5700 global $USER, $CFG;
5702 $link = false;
5704 if (!empty($posts[$parent->id]->children)) {
5705 $posts = $posts[$parent->id]->children;
5707 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
5708 $canviewfullnames = has_capability('moodle/site:viewfullnames', $modcontext);
5710 foreach ($posts as $post) {
5712 echo '<div class="indent">';
5713 if ($depth > 0) {
5714 $ownpost = ($USER->id == $post->userid);
5715 $post->subject = format_string($post->subject);
5717 $postread = !empty($post->postread);
5719 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
5720 '', '', $postread, true, $forumtracked);
5721 } else {
5722 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
5723 echo "</div>\n";
5724 continue;
5726 $by = new stdClass();
5727 $by->name = fullname($post, $canviewfullnames);
5728 $by->date = userdate($post->modified);
5730 if ($forumtracked) {
5731 if (!empty($post->postread)) {
5732 $style = '<span class="forumthread read">';
5733 } else {
5734 $style = '<span class="forumthread unread">';
5736 } else {
5737 $style = '<span class="forumthread">';
5739 echo $style."<a name=\"$post->id\"></a>".
5740 "<a href=\"discuss.php?d=$post->discussion&amp;parent=$post->id\">".format_string($post->subject,true)."</a> ";
5741 print_string("bynameondate", "forum", $by);
5742 echo "</span>";
5745 forum_print_posts_threaded($course, $cm, $forum, $discussion, $post, $depth-1, $reply, $forumtracked, $posts);
5746 echo "</div>\n";
5752 * @todo Document this function
5753 * @global object
5754 * @global object
5755 * @return void
5757 function forum_print_posts_nested($course, &$cm, $forum, $discussion, $parent, $reply, $forumtracked, $posts) {
5758 global $USER, $CFG;
5760 $link = false;
5762 if (!empty($posts[$parent->id]->children)) {
5763 $posts = $posts[$parent->id]->children;
5765 foreach ($posts as $post) {
5767 echo '<div class="indent">';
5768 if (!isloggedin()) {
5769 $ownpost = false;
5770 } else {
5771 $ownpost = ($USER->id == $post->userid);
5774 $post->subject = format_string($post->subject);
5775 $postread = !empty($post->postread);
5777 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
5778 '', '', $postread, true, $forumtracked);
5779 forum_print_posts_nested($course, $cm, $forum, $discussion, $post, $reply, $forumtracked, $posts);
5780 echo "</div>\n";
5786 * Returns all forum posts since a given time in specified forum.
5788 * @todo Document this functions args
5789 * @global object
5790 * @global object
5791 * @global object
5792 * @global object
5794 function forum_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
5795 global $CFG, $COURSE, $USER, $DB;
5797 if ($COURSE->id == $courseid) {
5798 $course = $COURSE;
5799 } else {
5800 $course = $DB->get_record('course', array('id' => $courseid));
5803 $modinfo =& get_fast_modinfo($course);
5805 $cm = $modinfo->cms[$cmid];
5806 $params = array($timestart, $cm->instance);
5808 if ($userid) {
5809 $userselect = "AND u.id = ?";
5810 $params[] = $userid;
5811 } else {
5812 $userselect = "";
5815 if ($groupid) {
5816 $groupselect = "AND gm.groupid = ?";
5817 $groupjoin = "JOIN {groups_members} gm ON gm.userid=u.id";
5818 $params[] = $groupid;
5819 } else {
5820 $groupselect = "";
5821 $groupjoin = "";
5824 if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
5825 d.timestart, d.timeend, d.userid AS duserid,
5826 u.firstname, u.lastname, u.email, u.picture, u.imagealt, u.email
5827 FROM {forum_posts} p
5828 JOIN {forum_discussions} d ON d.id = p.discussion
5829 JOIN {forum} f ON f.id = d.forum
5830 JOIN {user} u ON u.id = p.userid
5831 $groupjoin
5832 WHERE p.created > ? AND f.id = ?
5833 $userselect $groupselect
5834 ORDER BY p.id ASC", $params)) { // order by initial posting date
5835 return;
5838 $groupmode = groups_get_activity_groupmode($cm, $course);
5839 $cm_context = get_context_instance(CONTEXT_MODULE, $cm->id);
5840 $viewhiddentimed = has_capability('mod/forum:viewhiddentimedposts', $cm_context);
5841 $accessallgroups = has_capability('moodle/site:accessallgroups', $cm_context);
5843 if (is_null($modinfo->groups)) {
5844 $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
5847 $printposts = array();
5848 foreach ($posts as $post) {
5850 if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
5851 and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
5852 if (!$viewhiddentimed) {
5853 continue;
5857 if ($groupmode) {
5858 if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or $accessallgroups) {
5859 // oki (Open discussions have groupid -1)
5860 } else {
5861 // separate mode
5862 if (isguestuser()) {
5863 // shortcut
5864 continue;
5867 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
5868 continue;
5873 $printposts[] = $post;
5876 if (!$printposts) {
5877 return;
5880 $aname = format_string($cm->name,true);
5882 foreach ($printposts as $post) {
5883 $tmpactivity = new stdClass();
5885 $tmpactivity->type = 'forum';
5886 $tmpactivity->cmid = $cm->id;
5887 $tmpactivity->name = $aname;
5888 $tmpactivity->sectionnum = $cm->sectionnum;
5889 $tmpactivity->timestamp = $post->modified;
5891 $tmpactivity->content = new stdClass();
5892 $tmpactivity->content->id = $post->id;
5893 $tmpactivity->content->discussion = $post->discussion;
5894 $tmpactivity->content->subject = format_string($post->subject);
5895 $tmpactivity->content->parent = $post->parent;
5897 $tmpactivity->user = new stdClass();
5898 $tmpactivity->user->id = $post->userid;
5899 $tmpactivity->user->firstname = $post->firstname;
5900 $tmpactivity->user->lastname = $post->lastname;
5901 $tmpactivity->user->picture = $post->picture;
5902 $tmpactivity->user->imagealt = $post->imagealt;
5903 $tmpactivity->user->email = $post->email;
5905 $activities[$index++] = $tmpactivity;
5908 return;
5912 * @todo Document this function
5913 * @global object
5915 function forum_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
5916 global $CFG, $OUTPUT;
5918 if ($activity->content->parent) {
5919 $class = 'reply';
5920 } else {
5921 $class = 'discussion';
5924 echo '<table border="0" cellpadding="3" cellspacing="0" class="forum-recent">';
5926 echo "<tr><td class=\"userpicture\" valign=\"top\">";
5927 echo $OUTPUT->user_picture($activity->user, array('courseid'=>$courseid));
5928 echo "</td><td class=\"$class\">";
5930 echo '<div class="title">';
5931 if ($detail) {
5932 $aname = s($activity->name);
5933 echo "<img src=\"" . $OUTPUT->pix_url('icon', $activity->type) . "\" ".
5934 "class=\"icon\" alt=\"{$aname}\" />";
5936 echo "<a href=\"$CFG->wwwroot/mod/forum/discuss.php?d={$activity->content->discussion}"
5937 ."#p{$activity->content->id}\">{$activity->content->subject}</a>";
5938 echo '</div>';
5940 echo '<div class="user">';
5941 $fullname = fullname($activity->user, $viewfullnames);
5942 echo "<a href=\"$CFG->wwwroot/user/view.php?id={$activity->user->id}&amp;course=$courseid\">"
5943 ."{$fullname}</a> - ".userdate($activity->timestamp);
5944 echo '</div>';
5945 echo "</td></tr></table>";
5947 return;
5951 * recursively sets the discussion field to $discussionid on $postid and all its children
5952 * used when pruning a post
5954 * @global object
5955 * @param int $postid
5956 * @param int $discussionid
5957 * @return bool
5959 function forum_change_discussionid($postid, $discussionid) {
5960 global $DB;
5961 $DB->set_field('forum_posts', 'discussion', $discussionid, array('id' => $postid));
5962 if ($posts = $DB->get_records('forum_posts', array('parent' => $postid))) {
5963 foreach ($posts as $post) {
5964 forum_change_discussionid($post->id, $discussionid);
5967 return true;
5971 * Prints the editing button on subscribers page
5973 * @global object
5974 * @global object
5975 * @param int $courseid
5976 * @param int $forumid
5977 * @return string
5979 function forum_update_subscriptions_button($courseid, $forumid) {
5980 global $CFG, $USER;
5982 if (!empty($USER->subscriptionsediting)) {
5983 $string = get_string('turneditingoff');
5984 $edit = "off";
5985 } else {
5986 $string = get_string('turneditingon');
5987 $edit = "on";
5990 return "<form method=\"get\" action=\"$CFG->wwwroot/mod/forum/subscribers.php\">".
5991 "<input type=\"hidden\" name=\"id\" value=\"$forumid\" />".
5992 "<input type=\"hidden\" name=\"edit\" value=\"$edit\" />".
5993 "<input type=\"submit\" value=\"$string\" /></form>";
5997 * This function gets run whenever user is enrolled into course
5999 * @param stdClass $cp
6000 * @return void
6002 function forum_user_enrolled($cp) {
6003 global $DB;
6005 // NOTE: this has to be as fast as possible - we do not want to slow down enrolments!
6006 // Originally there used to be 'mod/forum:initialsubscriptions' which was
6007 // introduced because we did not have enrolment information in earlier versions...
6009 $sql = "SELECT f.id
6010 FROM {forum} f
6011 LEFT JOIN {forum_subscriptions} fs ON (fs.forum = f.id AND fs.userid = :userid)
6012 WHERE f.course = :courseid AND f.forcesubscribe = :initial AND fs.id IS NULL";
6013 $params = array('courseid'=>$cp->courseid, 'userid'=>$cp->userid, 'initial'=>FORUM_INITIALSUBSCRIBE);
6015 $forums = $DB->get_records_sql($sql, $params);
6016 foreach ($forums as $forum) {
6017 forum_subscribe($cp->userid, $forum->id);
6022 * This function gets run whenever user is unenrolled from course
6024 * @param stdClass $cp
6025 * @return void
6027 function forum_user_unenrolled($cp) {
6028 global $DB;
6030 // NOTE: this has to be as fast as possible!
6032 if ($cp->lastenrol) {
6033 $params = array('userid'=>$cp->userid, 'courseid'=>$cp->courseid);
6034 $forumselect = "IN (SELECT f.id FROM {forum} f WHERE f.course = :courseid)";
6036 $DB->delete_records_select('forum_subscriptions', "userid = :userid AND forum $forumselect", $params);
6037 $DB->delete_records_select('forum_track_prefs', "userid = :userid AND forumid $forumselect", $params);
6038 $DB->delete_records_select('forum_read', "userid = :userid AND forumid $forumselect", $params);
6042 // Functions to do with read tracking.
6045 * Mark posts as read.
6047 * @global object
6048 * @global object
6049 * @param object $user object
6050 * @param array $postids array of post ids
6051 * @return boolean success
6053 function forum_tp_mark_posts_read($user, $postids) {
6054 global $CFG, $DB;
6056 if (!forum_tp_can_track_forums(false, $user)) {
6057 return true;
6060 $status = true;
6062 $now = time();
6063 $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
6065 if (empty($postids)) {
6066 return true;
6068 } else if (count($postids) > 200) {
6069 while ($part = array_splice($postids, 0, 200)) {
6070 $status = forum_tp_mark_posts_read($user, $part) && $status;
6072 return $status;
6075 list($usql, $params) = $DB->get_in_or_equal($postids);
6076 $params[] = $user->id;
6078 $sql = "SELECT id
6079 FROM {forum_read}
6080 WHERE postid $usql AND userid = ?";
6081 if ($existing = $DB->get_records_sql($sql, $params)) {
6082 $existing = array_keys($existing);
6083 } else {
6084 $existing = array();
6087 $new = array_diff($postids, $existing);
6089 if ($new) {
6090 list($usql, $new_params) = $DB->get_in_or_equal($new);
6091 $params = array($user->id, $now, $now, $user->id);
6092 $params = array_merge($params, $new_params);
6093 $params[] = $cutoffdate;
6095 $sql = "INSERT INTO {forum_read} (userid, postid, discussionid, forumid, firstread, lastread)
6097 SELECT ?, p.id, p.discussion, d.forum, ?, ?
6098 FROM {forum_posts} p
6099 JOIN {forum_discussions} d ON d.id = p.discussion
6100 JOIN {forum} f ON f.id = d.forum
6101 LEFT JOIN {forum_track_prefs} tf ON (tf.userid = ? AND tf.forumid = f.id)
6102 WHERE p.id $usql
6103 AND p.modified >= ?
6104 AND (f.trackingtype = ".FORUM_TRACKING_ON."
6105 OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND tf.id IS NULL))";
6106 $status = $DB->execute($sql, $params) && $status;
6109 if ($existing) {
6110 list($usql, $new_params) = $DB->get_in_or_equal($existing);
6111 $params = array($now, $user->id);
6112 $params = array_merge($params, $new_params);
6114 $sql = "UPDATE {forum_read}
6115 SET lastread = ?
6116 WHERE userid = ? AND postid $usql";
6117 $status = $DB->execute($sql, $params) && $status;
6120 return $status;
6124 * Mark post as read.
6125 * @global object
6126 * @global object
6127 * @param int $userid
6128 * @param int $postid
6130 function forum_tp_add_read_record($userid, $postid) {
6131 global $CFG, $DB;
6133 $now = time();
6134 $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
6136 if (!$DB->record_exists('forum_read', array('userid' => $userid, 'postid' => $postid))) {
6137 $sql = "INSERT INTO {forum_read} (userid, postid, discussionid, forumid, firstread, lastread)
6139 SELECT ?, p.id, p.discussion, d.forum, ?, ?
6140 FROM {forum_posts} p
6141 JOIN {forum_discussions} d ON d.id = p.discussion
6142 WHERE p.id = ? AND p.modified >= ?";
6143 return $DB->execute($sql, array($userid, $now, $now, $postid, $cutoffdate));
6145 } else {
6146 $sql = "UPDATE {forum_read}
6147 SET lastread = ?
6148 WHERE userid = ? AND postid = ?";
6149 return $DB->execute($sql, array($now, $userid, $userid));
6154 * Returns all records in the 'forum_read' table matching the passed keys, indexed
6155 * by userid.
6157 * @global object
6158 * @param int $userid
6159 * @param int $postid
6160 * @param int $discussionid
6161 * @param int $forumid
6162 * @return array
6164 function forum_tp_get_read_records($userid=-1, $postid=-1, $discussionid=-1, $forumid=-1) {
6165 global $DB;
6166 $select = '';
6167 $params = array();
6169 if ($userid > -1) {
6170 if ($select != '') $select .= ' AND ';
6171 $select .= 'userid = ?';
6172 $params[] = $userid;
6174 if ($postid > -1) {
6175 if ($select != '') $select .= ' AND ';
6176 $select .= 'postid = ?';
6177 $params[] = $postid;
6179 if ($discussionid > -1) {
6180 if ($select != '') $select .= ' AND ';
6181 $select .= 'discussionid = ?';
6182 $params[] = $discussionid;
6184 if ($forumid > -1) {
6185 if ($select != '') $select .= ' AND ';
6186 $select .= 'forumid = ?';
6187 $params[] = $forumid;
6190 return $DB->get_records_select('forum_read', $select, $params);
6194 * Returns all read records for the provided user and discussion, indexed by postid.
6196 * @global object
6197 * @param inti $userid
6198 * @param int $discussionid
6200 function forum_tp_get_discussion_read_records($userid, $discussionid) {
6201 global $DB;
6202 $select = 'userid = ? AND discussionid = ?';
6203 $fields = 'postid, firstread, lastread';
6204 return $DB->get_records_select('forum_read', $select, array($userid, $discussionid), '', $fields);
6208 * If its an old post, do nothing. If the record exists, the maintenance will clear it up later.
6210 * @return bool
6212 function forum_tp_mark_post_read($userid, $post, $forumid) {
6213 if (!forum_tp_is_post_old($post)) {
6214 return forum_tp_add_read_record($userid, $post->id);
6215 } else {
6216 return true;
6221 * Marks a whole forum as read, for a given user
6223 * @global object
6224 * @global object
6225 * @param object $user
6226 * @param int $forumid
6227 * @param int|bool $groupid
6228 * @return bool
6230 function forum_tp_mark_forum_read($user, $forumid, $groupid=false) {
6231 global $CFG, $DB;
6233 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6235 $groupsel = "";
6236 $params = array($user->id, $forumid, $cutoffdate);
6238 if ($groupid !== false) {
6239 $groupsel = " AND (d.groupid = ? OR d.groupid = -1)";
6240 $params[] = $groupid;
6243 $sql = "SELECT p.id
6244 FROM {forum_posts} p
6245 LEFT JOIN {forum_discussions} d ON d.id = p.discussion
6246 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6247 WHERE d.forum = ?
6248 AND p.modified >= ? AND r.id is NULL
6249 $groupsel";
6251 if ($posts = $DB->get_records_sql($sql, $params)) {
6252 $postids = array_keys($posts);
6253 return forum_tp_mark_posts_read($user, $postids);
6256 return true;
6260 * Marks a whole discussion as read, for a given user
6262 * @global object
6263 * @global object
6264 * @param object $user
6265 * @param int $discussionid
6266 * @return bool
6268 function forum_tp_mark_discussion_read($user, $discussionid) {
6269 global $CFG, $DB;
6271 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6273 $sql = "SELECT p.id
6274 FROM {forum_posts} p
6275 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6276 WHERE p.discussion = ?
6277 AND p.modified >= ? AND r.id is NULL";
6279 if ($posts = $DB->get_records_sql($sql, array($user->id, $discussionid, $cutoffdate))) {
6280 $postids = array_keys($posts);
6281 return forum_tp_mark_posts_read($user, $postids);
6284 return true;
6288 * @global object
6289 * @param int $userid
6290 * @param object $post
6292 function forum_tp_is_post_read($userid, $post) {
6293 global $DB;
6294 return (forum_tp_is_post_old($post) ||
6295 $DB->record_exists('forum_read', array('userid' => $userid, 'postid' => $post->id)));
6299 * @global object
6300 * @param object $post
6301 * @param int $time Defautls to time()
6303 function forum_tp_is_post_old($post, $time=null) {
6304 global $CFG;
6306 if (is_null($time)) {
6307 $time = time();
6309 return ($post->modified < ($time - ($CFG->forum_oldpostdays * 24 * 3600)));
6313 * Returns the count of records for the provided user and discussion.
6315 * @global object
6316 * @global object
6317 * @param int $userid
6318 * @param int $discussionid
6319 * @return bool
6321 function forum_tp_count_discussion_read_records($userid, $discussionid) {
6322 global $CFG, $DB;
6324 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
6326 $sql = 'SELECT COUNT(DISTINCT p.id) '.
6327 'FROM {forum_discussions} d '.
6328 'LEFT JOIN {forum_read} r ON d.id = r.discussionid AND r.userid = ? '.
6329 'LEFT JOIN {forum_posts} p ON p.discussion = d.id '.
6330 'AND (p.modified < ? OR p.id = r.postid) '.
6331 'WHERE d.id = ? ';
6333 return ($DB->count_records_sql($sql, array($userid, $cutoffdate, $discussionid)));
6337 * Returns the count of records for the provided user and discussion.
6339 * @global object
6340 * @global object
6341 * @param int $userid
6342 * @param int $discussionid
6343 * @return int
6345 function forum_tp_count_discussion_unread_posts($userid, $discussionid) {
6346 global $CFG, $DB;
6348 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
6350 $sql = 'SELECT COUNT(p.id) '.
6351 'FROM {forum_posts} p '.
6352 'LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? '.
6353 'WHERE p.discussion = ? '.
6354 'AND p.modified >= ? AND r.id is NULL';
6356 return $DB->count_records_sql($sql, array($userid, $discussionid, $cutoffdate));
6360 * Returns the count of posts for the provided forum and [optionally] group.
6361 * @global object
6362 * @global object
6363 * @param int $forumid
6364 * @param int|bool $groupid
6365 * @return int
6367 function forum_tp_count_forum_posts($forumid, $groupid=false) {
6368 global $CFG, $DB;
6369 $params = array($forumid);
6370 $sql = 'SELECT COUNT(*) '.
6371 'FROM {forum_posts} fp,{forum_discussions} fd '.
6372 'WHERE fd.forum = ? AND fp.discussion = fd.id';
6373 if ($groupid !== false) {
6374 $sql .= ' AND (fd.groupid = ? OR fd.groupid = -1)';
6375 $params[] = $groupid;
6377 $count = $DB->count_records_sql($sql, $params);
6380 return $count;
6384 * Returns the count of records for the provided user and forum and [optionally] group.
6385 * @global object
6386 * @global object
6387 * @param int $userid
6388 * @param int $forumid
6389 * @param int|bool $groupid
6390 * @return int
6392 function forum_tp_count_forum_read_records($userid, $forumid, $groupid=false) {
6393 global $CFG, $DB;
6395 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6397 $groupsel = '';
6398 $params = array($userid, $forumid, $cutoffdate);
6399 if ($groupid !== false) {
6400 $groupsel = "AND (d.groupid = ? OR d.groupid = -1)";
6401 $params[] = $groupid;
6404 $sql = "SELECT COUNT(p.id)
6405 FROM {forum_posts} p
6406 JOIN {forum_discussions} d ON d.id = p.discussion
6407 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid= ?)
6408 WHERE d.forum = ?
6409 AND (p.modified < $cutoffdate OR (p.modified >= ? AND r.id IS NOT NULL))
6410 $groupsel";
6412 return $DB->get_field_sql($sql, $params);
6416 * Returns the count of records for the provided user and course.
6417 * Please note that group access is ignored!
6419 * @global object
6420 * @global object
6421 * @param int $userid
6422 * @param int $courseid
6423 * @return array
6425 function forum_tp_get_course_unread_posts($userid, $courseid) {
6426 global $CFG, $DB;
6428 $now = round(time(), -2); // db cache friendliness
6429 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
6430 $params = array($userid, $userid, $courseid, $cutoffdate);
6432 if (!empty($CFG->forum_enabletimedposts)) {
6433 $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
6434 $params[] = $now;
6435 $params[] = $now;
6436 } else {
6437 $timedsql = "";
6440 $sql = "SELECT f.id, COUNT(p.id) AS unread
6441 FROM {forum_posts} p
6442 JOIN {forum_discussions} d ON d.id = p.discussion
6443 JOIN {forum} f ON f.id = d.forum
6444 JOIN {course} c ON c.id = f.course
6445 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6446 LEFT JOIN {forum_track_prefs} tf ON (tf.userid = ? AND tf.forumid = f.id)
6447 WHERE f.course = ?
6448 AND p.modified >= ? AND r.id is NULL
6449 AND (f.trackingtype = ".FORUM_TRACKING_ON."
6450 OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND tf.id IS NULL))
6451 $timedsql
6452 GROUP BY f.id";
6454 if ($return = $DB->get_records_sql($sql, $params)) {
6455 return $return;
6458 return array();
6462 * Returns the count of records for the provided user and forum and [optionally] group.
6464 * @global object
6465 * @global object
6466 * @global object
6467 * @param object $cm
6468 * @param object $course
6469 * @return int
6471 function forum_tp_count_forum_unread_posts($cm, $course) {
6472 global $CFG, $USER, $DB;
6474 static $readcache = array();
6476 $forumid = $cm->instance;
6478 if (!isset($readcache[$course->id])) {
6479 $readcache[$course->id] = array();
6480 if ($counts = forum_tp_get_course_unread_posts($USER->id, $course->id)) {
6481 foreach ($counts as $count) {
6482 $readcache[$course->id][$count->id] = $count->unread;
6487 if (empty($readcache[$course->id][$forumid])) {
6488 // no need to check group mode ;-)
6489 return 0;
6492 $groupmode = groups_get_activity_groupmode($cm, $course);
6494 if ($groupmode != SEPARATEGROUPS) {
6495 return $readcache[$course->id][$forumid];
6498 if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
6499 return $readcache[$course->id][$forumid];
6502 require_once($CFG->dirroot.'/course/lib.php');
6504 $modinfo =& get_fast_modinfo($course);
6505 if (is_null($modinfo->groups)) {
6506 $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
6509 $mygroups = $modinfo->groups[$cm->groupingid];
6511 // add all groups posts
6512 if (empty($mygroups)) {
6513 $mygroups = array(-1=>-1);
6514 } else {
6515 $mygroups[-1] = -1;
6518 list ($groups_sql, $groups_params) = $DB->get_in_or_equal($mygroups);
6520 $now = round(time(), -2); // db cache friendliness
6521 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
6522 $params = array($USER->id, $forumid, $cutoffdate);
6524 if (!empty($CFG->forum_enabletimedposts)) {
6525 $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
6526 $params[] = $now;
6527 $params[] = $now;
6528 } else {
6529 $timedsql = "";
6532 $params = array_merge($params, $groups_params);
6534 $sql = "SELECT COUNT(p.id)
6535 FROM {forum_posts} p
6536 JOIN {forum_discussions} d ON p.discussion = d.id
6537 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6538 WHERE d.forum = ?
6539 AND p.modified >= ? AND r.id is NULL
6540 $timedsql
6541 AND d.groupid $groups_sql";
6543 return $DB->get_field_sql($sql, $params);
6547 * Deletes read records for the specified index. At least one parameter must be specified.
6549 * @global object
6550 * @param int $userid
6551 * @param int $postid
6552 * @param int $discussionid
6553 * @param int $forumid
6554 * @return bool
6556 function forum_tp_delete_read_records($userid=-1, $postid=-1, $discussionid=-1, $forumid=-1) {
6557 global $DB;
6558 $params = array();
6560 $select = '';
6561 if ($userid > -1) {
6562 if ($select != '') $select .= ' AND ';
6563 $select .= 'userid = ?';
6564 $params[] = $userid;
6566 if ($postid > -1) {
6567 if ($select != '') $select .= ' AND ';
6568 $select .= 'postid = ?';
6569 $params[] = $postid;
6571 if ($discussionid > -1) {
6572 if ($select != '') $select .= ' AND ';
6573 $select .= 'discussionid = ?';
6574 $params[] = $discussionid;
6576 if ($forumid > -1) {
6577 if ($select != '') $select .= ' AND ';
6578 $select .= 'forumid = ?';
6579 $params[] = $forumid;
6581 if ($select == '') {
6582 return false;
6584 else {
6585 return $DB->delete_records_select('forum_read', $select, $params);
6589 * Get a list of forums not tracked by the user.
6591 * @global object
6592 * @global object
6593 * @param int $userid The id of the user to use.
6594 * @param int $courseid The id of the course being checked.
6595 * @return mixed An array indexed by forum id, or false.
6597 function forum_tp_get_untracked_forums($userid, $courseid) {
6598 global $CFG, $DB;
6600 $sql = "SELECT f.id
6601 FROM {forum} f
6602 LEFT JOIN {forum_track_prefs} ft ON (ft.forumid = f.id AND ft.userid = ?)
6603 WHERE f.course = ?
6604 AND (f.trackingtype = ".FORUM_TRACKING_OFF."
6605 OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND ft.id IS NOT NULL))";
6607 if ($forums = $DB->get_records_sql($sql, array($userid, $courseid))) {
6608 foreach ($forums as $forum) {
6609 $forums[$forum->id] = $forum;
6611 return $forums;
6613 } else {
6614 return array();
6619 * Determine if a user can track forums and optionally a particular forum.
6620 * Checks the site settings, the user settings and the forum settings (if
6621 * requested).
6623 * @global object
6624 * @global object
6625 * @global object
6626 * @param mixed $forum The forum object to test, or the int id (optional).
6627 * @param mixed $userid The user object to check for (optional).
6628 * @return boolean
6630 function forum_tp_can_track_forums($forum=false, $user=false) {
6631 global $USER, $CFG, $DB;
6633 // if possible, avoid expensive
6634 // queries
6635 if (empty($CFG->forum_trackreadposts)) {
6636 return false;
6639 if ($user === false) {
6640 $user = $USER;
6643 if (isguestuser($user) or empty($user->id)) {
6644 return false;
6647 if ($forum === false) {
6648 // general abitily to track forums
6649 return (bool)$user->trackforums;
6653 // Work toward always passing an object...
6654 if (is_numeric($forum)) {
6655 debugging('Better use proper forum object.', DEBUG_DEVELOPER);
6656 $forum = $DB->get_record('forum', array('id' => $forum), '', 'id,trackingtype');
6659 $forumallows = ($forum->trackingtype == FORUM_TRACKING_OPTIONAL);
6660 $forumforced = ($forum->trackingtype == FORUM_TRACKING_ON);
6662 return ($forumforced || $forumallows) && !empty($user->trackforums);
6666 * Tells whether a specific forum is tracked by the user. A user can optionally
6667 * be specified. If not specified, the current user is assumed.
6669 * @global object
6670 * @global object
6671 * @global object
6672 * @param mixed $forum If int, the id of the forum being checked; if object, the forum object
6673 * @param int $userid The id of the user being checked (optional).
6674 * @return boolean
6676 function forum_tp_is_tracked($forum, $user=false) {
6677 global $USER, $CFG, $DB;
6679 if ($user === false) {
6680 $user = $USER;
6683 if (isguestuser($user) or empty($user->id)) {
6684 return false;
6687 // Work toward always passing an object...
6688 if (is_numeric($forum)) {
6689 debugging('Better use proper forum object.', DEBUG_DEVELOPER);
6690 $forum = $DB->get_record('forum', array('id' => $forum));
6693 if (!forum_tp_can_track_forums($forum, $user)) {
6694 return false;
6697 $forumallows = ($forum->trackingtype == FORUM_TRACKING_OPTIONAL);
6698 $forumforced = ($forum->trackingtype == FORUM_TRACKING_ON);
6700 return $forumforced ||
6701 ($forumallows && $DB->get_record('forum_track_prefs', array('userid' => $user->id, 'forumid' => $forum->id)) === false);
6705 * @global object
6706 * @global object
6707 * @param int $forumid
6708 * @param int $userid
6710 function forum_tp_start_tracking($forumid, $userid=false) {
6711 global $USER, $DB;
6713 if ($userid === false) {
6714 $userid = $USER->id;
6717 return $DB->delete_records('forum_track_prefs', array('userid' => $userid, 'forumid' => $forumid));
6721 * @global object
6722 * @global object
6723 * @param int $forumid
6724 * @param int $userid
6726 function forum_tp_stop_tracking($forumid, $userid=false) {
6727 global $USER, $DB;
6729 if ($userid === false) {
6730 $userid = $USER->id;
6733 if (!$DB->record_exists('forum_track_prefs', array('userid' => $userid, 'forumid' => $forumid))) {
6734 $track_prefs = new stdClass();
6735 $track_prefs->userid = $userid;
6736 $track_prefs->forumid = $forumid;
6737 $DB->insert_record('forum_track_prefs', $track_prefs);
6740 return forum_tp_delete_read_records($userid, -1, -1, $forumid);
6745 * Clean old records from the forum_read table.
6746 * @global object
6747 * @global object
6748 * @return void
6750 function forum_tp_clean_read_records() {
6751 global $CFG, $DB;
6753 if (!isset($CFG->forum_oldpostdays)) {
6754 return;
6756 // Look for records older than the cutoffdate that are still in the forum_read table.
6757 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6759 //first get the oldest tracking present - we need tis to speedup the next delete query
6760 $sql = "SELECT MIN(fp.modified) AS first
6761 FROM {forum_posts} fp
6762 JOIN {forum_read} fr ON fr.postid=fp.id";
6763 if (!$first = $DB->get_field_sql($sql)) {
6764 // nothing to delete;
6765 return;
6768 // now delete old tracking info
6769 $sql = "DELETE
6770 FROM {forum_read}
6771 WHERE postid IN (SELECT fp.id
6772 FROM {forum_posts} fp
6773 WHERE fp.modified >= ? AND fp.modified < ?)";
6774 $DB->execute($sql, array($first, $cutoffdate));
6778 * Sets the last post for a given discussion
6780 * @global object
6781 * @global object
6782 * @param into $discussionid
6783 * @return bool|int
6785 function forum_discussion_update_last_post($discussionid) {
6786 global $CFG, $DB;
6788 // Check the given discussion exists
6789 if (!$DB->record_exists('forum_discussions', array('id' => $discussionid))) {
6790 return false;
6793 // Use SQL to find the last post for this discussion
6794 $sql = "SELECT id, userid, modified
6795 FROM {forum_posts}
6796 WHERE discussion=?
6797 ORDER BY modified DESC";
6799 // Lets go find the last post
6800 if (($lastposts = $DB->get_records_sql($sql, array($discussionid), 0, 1))) {
6801 $lastpost = reset($lastposts);
6802 $discussionobject = new stdClass();
6803 $discussionobject->id = $discussionid;
6804 $discussionobject->usermodified = $lastpost->userid;
6805 $discussionobject->timemodified = $lastpost->modified;
6806 $DB->update_record('forum_discussions', $discussionobject);
6807 return $lastpost->id;
6810 // To get here either we couldn't find a post for the discussion (weird)
6811 // or we couldn't update the discussion record (weird x2)
6812 return false;
6817 * @return array
6819 function forum_get_view_actions() {
6820 return array('view discussion', 'search', 'forum', 'forums', 'subscribers', 'view forum');
6824 * @return array
6826 function forum_get_post_actions() {
6827 return array('add discussion','add post','delete discussion','delete post','move discussion','prune post','update post');
6831 * this function returns all the separate forum ids, given a courseid
6833 * @global object
6834 * @global object
6835 * @param int $courseid
6836 * @return array
6838 function forum_get_separate_modules($courseid) {
6840 global $CFG,$DB;
6841 $forummodule = $DB->get_record("modules", array("name" => "forum"));
6843 $sql = 'SELECT f.id, f.id FROM {forum} f, {course_modules} cm WHERE
6844 f.id = cm.instance AND cm.module =? AND cm.visible = 1 AND cm.course = ?
6845 AND cm.groupmode ='.SEPARATEGROUPS;
6847 return $DB->get_records_sql($sql, array($forummodule->id, $courseid));
6852 * @global object
6853 * @global object
6854 * @global object
6855 * @param object $forum
6856 * @param object $cm
6857 * @return bool
6859 function forum_check_throttling($forum, $cm=null) {
6860 global $USER, $CFG, $DB, $OUTPUT;
6862 if (is_numeric($forum)) {
6863 $forum = $DB->get_record('forum',array('id'=>$forum));
6865 if (!is_object($forum)) {
6866 return false; // this is broken.
6869 if (empty($forum->blockafter)) {
6870 return true;
6873 if (empty($forum->blockperiod)) {
6874 return true;
6877 if (!$cm) {
6878 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
6879 print_error('invalidcoursemodule');
6883 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
6884 if(has_capability('mod/forum:postwithoutthrottling', $modcontext)) {
6885 return true;
6888 // get the number of posts in the last period we care about
6889 $timenow = time();
6890 $timeafter = $timenow - $forum->blockperiod;
6892 $numposts = $DB->count_records_sql('SELECT COUNT(p.id) FROM {forum_posts} p'
6893 .' JOIN {forum_discussions} d'
6894 .' ON p.discussion = d.id WHERE d.forum = ?'
6895 .' AND p.userid = ? AND p.created > ?', array($forum->id, $USER->id, $timeafter));
6897 $a = new stdClass();
6898 $a->blockafter = $forum->blockafter;
6899 $a->numposts = $numposts;
6900 $a->blockperiod = get_string('secondstotime'.$forum->blockperiod);
6902 if ($forum->blockafter <= $numposts) {
6903 print_error('forumblockingtoomanyposts', 'error', $CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id, $a);
6905 if ($forum->warnafter <= $numposts) {
6906 echo $OUTPUT->notification(get_string('forumblockingalmosttoomanyposts','forum',$a));
6914 * Removes all grades from gradebook
6916 * @global object
6917 * @global object
6918 * @param int $courseid
6919 * @param string $type optional
6921 function forum_reset_gradebook($courseid, $type='') {
6922 global $CFG, $DB;
6924 $wheresql = '';
6925 $params = array($courseid);
6926 if ($type) {
6927 $wheresql = "AND f.type=?";
6928 $params[] = $type;
6931 $sql = "SELECT f.*, cm.idnumber as cmidnumber, f.course as courseid
6932 FROM {forum} f, {course_modules} cm, {modules} m
6933 WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id AND f.course=? $wheresql";
6935 if ($forums = $DB->get_records_sql($sql, $params)) {
6936 foreach ($forums as $forum) {
6937 forum_grade_item_update($forum, 'reset');
6943 * This function is used by the reset_course_userdata function in moodlelib.
6944 * This function will remove all posts from the specified forum
6945 * and clean up any related data.
6947 * @global object
6948 * @global object
6949 * @param $data the data submitted from the reset course.
6950 * @return array status array
6952 function forum_reset_userdata($data) {
6953 global $CFG, $DB;
6954 require_once($CFG->dirroot.'/rating/lib.php');
6956 $componentstr = get_string('modulenameplural', 'forum');
6957 $status = array();
6959 $params = array($data->courseid);
6961 $removeposts = false;
6962 $typesql = "";
6963 if (!empty($data->reset_forum_all)) {
6964 $removeposts = true;
6965 $typesstr = get_string('resetforumsall', 'forum');
6966 $types = array();
6967 } else if (!empty($data->reset_forum_types)){
6968 $removeposts = true;
6969 $typesql = "";
6970 $types = array();
6971 $forum_types_all = forum_get_forum_types_all();
6972 foreach ($data->reset_forum_types as $type) {
6973 if (!array_key_exists($type, $forum_types_all)) {
6974 continue;
6976 $typesql .= " AND f.type=?";
6977 $types[] = $forum_types_all[$type];
6978 $params[] = $type;
6980 $typesstr = get_string('resetforums', 'forum').': '.implode(', ', $types);
6982 $alldiscussionssql = "SELECT fd.id
6983 FROM {forum_discussions} fd, {forum} f
6984 WHERE f.course=? AND f.id=fd.forum";
6986 $allforumssql = "SELECT f.id
6987 FROM {forum} f
6988 WHERE f.course=?";
6990 $allpostssql = "SELECT fp.id
6991 FROM {forum_posts} fp, {forum_discussions} fd, {forum} f
6992 WHERE f.course=? AND f.id=fd.forum AND fd.id=fp.discussion";
6994 $forumssql = $forums = $rm = null;
6996 if( $removeposts || !empty($data->reset_forum_ratings) ) {
6997 $forumssql = "$allforumssql $typesql";
6998 $forums = $forums = $DB->get_records_sql($forumssql, $params);
6999 $rm = new rating_manager();;
7000 $ratingdeloptions = new stdClass;
7001 $ratingdeloptions->component = 'mod_forum';
7002 $ratingdeloptions->ratingarea = 'post';
7005 if ($removeposts) {
7006 $discussionssql = "$alldiscussionssql $typesql";
7007 $postssql = "$allpostssql $typesql";
7009 // now get rid of all attachments
7010 $fs = get_file_storage();
7011 if ($forums) {
7012 foreach ($forums as $forumid=>$unused) {
7013 if (!$cm = get_coursemodule_from_instance('forum', $forumid)) {
7014 continue;
7016 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
7017 $fs->delete_area_files($context->id, 'mod_forum', 'attachment');
7018 $fs->delete_area_files($context->id, 'mod_forum', 'post');
7020 //remove ratings
7021 $ratingdeloptions->contextid = $context->id;
7022 $rm->delete_ratings($ratingdeloptions);
7026 // first delete all read flags
7027 $DB->delete_records_select('forum_read', "forumid IN ($forumssql)", $params);
7029 // remove tracking prefs
7030 $DB->delete_records_select('forum_track_prefs', "forumid IN ($forumssql)", $params);
7032 // remove posts from queue
7033 $DB->delete_records_select('forum_queue', "discussionid IN ($discussionssql)", $params);
7035 // all posts - initial posts must be kept in single simple discussion forums
7036 $DB->delete_records_select('forum_posts', "discussion IN ($discussionssql) AND parent <> 0", $params); // first all children
7037 $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
7039 // finally all discussions except single simple forums
7040 $DB->delete_records_select('forum_discussions', "forum IN ($forumssql AND f.type <> 'single')", $params);
7042 // remove all grades from gradebook
7043 if (empty($data->reset_gradebook_grades)) {
7044 if (empty($types)) {
7045 forum_reset_gradebook($data->courseid);
7046 } else {
7047 foreach ($types as $type) {
7048 forum_reset_gradebook($data->courseid, $type);
7053 $status[] = array('component'=>$componentstr, 'item'=>$typesstr, 'error'=>false);
7056 // remove all ratings in this course's forums
7057 if (!empty($data->reset_forum_ratings)) {
7058 if ($forums) {
7059 foreach ($forums as $forumid=>$unused) {
7060 if (!$cm = get_coursemodule_from_instance('forum', $forumid)) {
7061 continue;
7063 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
7065 //remove ratings
7066 $ratingdeloptions->contextid = $context->id;
7067 $rm->delete_ratings($ratingdeloptions);
7071 // remove all grades from gradebook
7072 if (empty($data->reset_gradebook_grades)) {
7073 forum_reset_gradebook($data->courseid);
7077 // remove all subscriptions unconditionally - even for users still enrolled in course
7078 if (!empty($data->reset_forum_subscriptions)) {
7079 $DB->delete_records_select('forum_subscriptions', "forum IN ($allforumssql)", $params);
7080 $status[] = array('component'=>$componentstr, 'item'=>get_string('resetsubscriptions','forum'), 'error'=>false);
7083 // remove all tracking prefs unconditionally - even for users still enrolled in course
7084 if (!empty($data->reset_forum_track_prefs)) {
7085 $DB->delete_records_select('forum_track_prefs', "forumid IN ($allforumssql)", $params);
7086 $status[] = array('component'=>$componentstr, 'item'=>get_string('resettrackprefs','forum'), 'error'=>false);
7089 /// updating dates - shift may be negative too
7090 if ($data->timeshift) {
7091 shift_course_mod_dates('forum', array('assesstimestart', 'assesstimefinish'), $data->timeshift, $data->courseid);
7092 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
7095 return $status;
7099 * Called by course/reset.php
7101 * @param $mform form passed by reference
7103 function forum_reset_course_form_definition(&$mform) {
7104 $mform->addElement('header', 'forumheader', get_string('modulenameplural', 'forum'));
7106 $mform->addElement('checkbox', 'reset_forum_all', get_string('resetforumsall','forum'));
7108 $mform->addElement('select', 'reset_forum_types', get_string('resetforums', 'forum'), forum_get_forum_types_all(), array('multiple' => 'multiple'));
7109 $mform->setAdvanced('reset_forum_types');
7110 $mform->disabledIf('reset_forum_types', 'reset_forum_all', 'checked');
7112 $mform->addElement('checkbox', 'reset_forum_subscriptions', get_string('resetsubscriptions','forum'));
7113 $mform->setAdvanced('reset_forum_subscriptions');
7115 $mform->addElement('checkbox', 'reset_forum_track_prefs', get_string('resettrackprefs','forum'));
7116 $mform->setAdvanced('reset_forum_track_prefs');
7117 $mform->disabledIf('reset_forum_track_prefs', 'reset_forum_all', 'checked');
7119 $mform->addElement('checkbox', 'reset_forum_ratings', get_string('deleteallratings'));
7120 $mform->disabledIf('reset_forum_ratings', 'reset_forum_all', 'checked');
7124 * Course reset form defaults.
7125 * @return array
7127 function forum_reset_course_form_defaults($course) {
7128 return array('reset_forum_all'=>1, 'reset_forum_subscriptions'=>0, 'reset_forum_track_prefs'=>0, 'reset_forum_ratings'=>1);
7132 * Converts a forum to use the Roles System
7134 * @global object
7135 * @global object
7136 * @param object $forum a forum object with the same attributes as a record
7137 * from the forum database table
7138 * @param int $forummodid the id of the forum module, from the modules table
7139 * @param array $teacherroles array of roles that have archetype teacher
7140 * @param array $studentroles array of roles that have archetype student
7141 * @param array $guestroles array of roles that have archetype guest
7142 * @param int $cmid the course_module id for this forum instance
7143 * @return boolean forum was converted or not
7145 function forum_convert_to_roles($forum, $forummodid, $teacherroles=array(),
7146 $studentroles=array(), $guestroles=array(), $cmid=NULL) {
7148 global $CFG, $DB, $OUTPUT;
7150 if (!isset($forum->open) && !isset($forum->assesspublic)) {
7151 // We assume that this forum has already been converted to use the
7152 // Roles System. Columns forum.open and forum.assesspublic get dropped
7153 // once the forum module has been upgraded to use Roles.
7154 return false;
7157 if ($forum->type == 'teacher') {
7159 // Teacher forums should be converted to normal forums that
7160 // use the Roles System to implement the old behavior.
7161 // Note:
7162 // Seems that teacher forums were never backed up in 1.6 since they
7163 // didn't have an entry in the course_modules table.
7164 require_once($CFG->dirroot.'/course/lib.php');
7166 if ($DB->count_records('forum_discussions', array('forum' => $forum->id)) == 0) {
7167 // Delete empty teacher forums.
7168 $DB->delete_records('forum', array('id' => $forum->id));
7169 } else {
7170 // Create a course module for the forum and assign it to
7171 // section 0 in the course.
7172 $mod = new stdClass();
7173 $mod->course = $forum->course;
7174 $mod->module = $forummodid;
7175 $mod->instance = $forum->id;
7176 $mod->section = 0;
7177 $mod->visible = 0; // Hide the forum
7178 $mod->visibleold = 0; // Hide the forum
7179 $mod->groupmode = 0;
7181 if (!$cmid = add_course_module($mod)) {
7182 print_error('cannotcreateinstanceforteacher', 'forum');
7183 } else {
7184 $mod->coursemodule = $cmid;
7185 if (!$sectionid = add_mod_to_section($mod)) {
7186 print_error('cannotaddteacherforumto', 'forum');
7187 } else {
7188 $DB->set_field('course_modules', 'section', $sectionid, array('id' => $cmid));
7192 // Change the forum type to general.
7193 $forum->type = 'general';
7194 $DB->update_record('forum', $forum);
7196 $context = get_context_instance(CONTEXT_MODULE, $cmid);
7198 // Create overrides for default student and guest roles (prevent).
7199 foreach ($studentroles as $studentrole) {
7200 assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
7201 assign_capability('mod/forum:viewhiddentimedposts', CAP_PREVENT, $studentrole->id, $context->id);
7202 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
7203 assign_capability('mod/forum:replypost', CAP_PREVENT, $studentrole->id, $context->id);
7204 assign_capability('mod/forum:viewrating', CAP_PREVENT, $studentrole->id, $context->id);
7205 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $studentrole->id, $context->id);
7206 assign_capability('mod/forum:rate', CAP_PREVENT, $studentrole->id, $context->id);
7207 assign_capability('mod/forum:createattachment', CAP_PREVENT, $studentrole->id, $context->id);
7208 assign_capability('mod/forum:deleteownpost', CAP_PREVENT, $studentrole->id, $context->id);
7209 assign_capability('mod/forum:deleteanypost', CAP_PREVENT, $studentrole->id, $context->id);
7210 assign_capability('mod/forum:splitdiscussions', CAP_PREVENT, $studentrole->id, $context->id);
7211 assign_capability('mod/forum:movediscussions', CAP_PREVENT, $studentrole->id, $context->id);
7212 assign_capability('mod/forum:editanypost', CAP_PREVENT, $studentrole->id, $context->id);
7213 assign_capability('mod/forum:viewqandawithoutposting', CAP_PREVENT, $studentrole->id, $context->id);
7214 assign_capability('mod/forum:viewsubscribers', CAP_PREVENT, $studentrole->id, $context->id);
7215 assign_capability('mod/forum:managesubscriptions', CAP_PREVENT, $studentrole->id, $context->id);
7216 assign_capability('mod/forum:postwithoutthrottling', CAP_PREVENT, $studentrole->id, $context->id);
7218 foreach ($guestroles as $guestrole) {
7219 assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $guestrole->id, $context->id);
7220 assign_capability('mod/forum:viewhiddentimedposts', CAP_PREVENT, $guestrole->id, $context->id);
7221 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $guestrole->id, $context->id);
7222 assign_capability('mod/forum:replypost', CAP_PREVENT, $guestrole->id, $context->id);
7223 assign_capability('mod/forum:viewrating', CAP_PREVENT, $guestrole->id, $context->id);
7224 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $guestrole->id, $context->id);
7225 assign_capability('mod/forum:rate', CAP_PREVENT, $guestrole->id, $context->id);
7226 assign_capability('mod/forum:createattachment', CAP_PREVENT, $guestrole->id, $context->id);
7227 assign_capability('mod/forum:deleteownpost', CAP_PREVENT, $guestrole->id, $context->id);
7228 assign_capability('mod/forum:deleteanypost', CAP_PREVENT, $guestrole->id, $context->id);
7229 assign_capability('mod/forum:splitdiscussions', CAP_PREVENT, $guestrole->id, $context->id);
7230 assign_capability('mod/forum:movediscussions', CAP_PREVENT, $guestrole->id, $context->id);
7231 assign_capability('mod/forum:editanypost', CAP_PREVENT, $guestrole->id, $context->id);
7232 assign_capability('mod/forum:viewqandawithoutposting', CAP_PREVENT, $guestrole->id, $context->id);
7233 assign_capability('mod/forum:viewsubscribers', CAP_PREVENT, $guestrole->id, $context->id);
7234 assign_capability('mod/forum:managesubscriptions', CAP_PREVENT, $guestrole->id, $context->id);
7235 assign_capability('mod/forum:postwithoutthrottling', CAP_PREVENT, $guestrole->id, $context->id);
7238 } else {
7239 // Non-teacher forum.
7241 if (empty($cmid)) {
7242 // We were not given the course_module id. Try to find it.
7243 if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
7244 echo $OUTPUT->notification('Could not get the course module for the forum');
7245 return false;
7246 } else {
7247 $cmid = $cm->id;
7250 $context = get_context_instance(CONTEXT_MODULE, $cmid);
7252 // $forum->open defines what students can do:
7253 // 0 = No discussions, no replies
7254 // 1 = No discussions, but replies are allowed
7255 // 2 = Discussions and replies are allowed
7256 switch ($forum->open) {
7257 case 0:
7258 foreach ($studentroles as $studentrole) {
7259 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
7260 assign_capability('mod/forum:replypost', CAP_PREVENT, $studentrole->id, $context->id);
7262 break;
7263 case 1:
7264 foreach ($studentroles as $studentrole) {
7265 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
7266 assign_capability('mod/forum:replypost', CAP_ALLOW, $studentrole->id, $context->id);
7268 break;
7269 case 2:
7270 foreach ($studentroles as $studentrole) {
7271 assign_capability('mod/forum:startdiscussion', CAP_ALLOW, $studentrole->id, $context->id);
7272 assign_capability('mod/forum:replypost', CAP_ALLOW, $studentrole->id, $context->id);
7274 break;
7277 // $forum->assessed defines whether forum rating is turned
7278 // on (1 or 2) and who can rate posts:
7279 // 1 = Everyone can rate posts
7280 // 2 = Only teachers can rate posts
7281 switch ($forum->assessed) {
7282 case 1:
7283 foreach ($studentroles as $studentrole) {
7284 assign_capability('mod/forum:rate', CAP_ALLOW, $studentrole->id, $context->id);
7286 foreach ($teacherroles as $teacherrole) {
7287 assign_capability('mod/forum:rate', CAP_ALLOW, $teacherrole->id, $context->id);
7289 break;
7290 case 2:
7291 foreach ($studentroles as $studentrole) {
7292 assign_capability('mod/forum:rate', CAP_PREVENT, $studentrole->id, $context->id);
7294 foreach ($teacherroles as $teacherrole) {
7295 assign_capability('mod/forum:rate', CAP_ALLOW, $teacherrole->id, $context->id);
7297 break;
7300 // $forum->assesspublic defines whether students can see
7301 // everybody's ratings:
7302 // 0 = Students can only see their own ratings
7303 // 1 = Students can see everyone's ratings
7304 switch ($forum->assesspublic) {
7305 case 0:
7306 foreach ($studentroles as $studentrole) {
7307 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $studentrole->id, $context->id);
7309 foreach ($teacherroles as $teacherrole) {
7310 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $teacherrole->id, $context->id);
7312 break;
7313 case 1:
7314 foreach ($studentroles as $studentrole) {
7315 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $studentrole->id, $context->id);
7317 foreach ($teacherroles as $teacherrole) {
7318 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $teacherrole->id, $context->id);
7320 break;
7323 if (empty($cm)) {
7324 $cm = $DB->get_record('course_modules', array('id' => $cmid));
7327 // $cm->groupmode:
7328 // 0 - No groups
7329 // 1 - Separate groups
7330 // 2 - Visible groups
7331 switch ($cm->groupmode) {
7332 case 0:
7333 break;
7334 case 1:
7335 foreach ($studentroles as $studentrole) {
7336 assign_capability('moodle/site:accessallgroups', CAP_PREVENT, $studentrole->id, $context->id);
7338 foreach ($teacherroles as $teacherrole) {
7339 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
7341 break;
7342 case 2:
7343 foreach ($studentroles as $studentrole) {
7344 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $studentrole->id, $context->id);
7346 foreach ($teacherroles as $teacherrole) {
7347 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
7349 break;
7352 return true;
7356 * Returns array of forum layout modes
7358 * @return array
7360 function forum_get_layout_modes() {
7361 return array (FORUM_MODE_FLATOLDEST => get_string('modeflatoldestfirst', 'forum'),
7362 FORUM_MODE_FLATNEWEST => get_string('modeflatnewestfirst', 'forum'),
7363 FORUM_MODE_THREADED => get_string('modethreaded', 'forum'),
7364 FORUM_MODE_NESTED => get_string('modenested', 'forum'));
7368 * Returns array of forum types chooseable on the forum editing form
7370 * @return array
7372 function forum_get_forum_types() {
7373 return array ('general' => get_string('generalforum', 'forum'),
7374 'eachuser' => get_string('eachuserforum', 'forum'),
7375 'single' => get_string('singleforum', 'forum'),
7376 'qanda' => get_string('qandaforum', 'forum'),
7377 'blog' => get_string('blogforum', 'forum'));
7381 * Returns array of all forum layout modes
7383 * @return array
7385 function forum_get_forum_types_all() {
7386 return array ('news' => get_string('namenews','forum'),
7387 'social' => get_string('namesocial','forum'),
7388 'general' => get_string('generalforum', 'forum'),
7389 'eachuser' => get_string('eachuserforum', 'forum'),
7390 'single' => get_string('singleforum', 'forum'),
7391 'qanda' => get_string('qandaforum', 'forum'),
7392 'blog' => get_string('blogforum', 'forum'));
7396 * Returns array of forum open modes
7398 * @return array
7400 function forum_get_open_modes() {
7401 return array ('2' => get_string('openmode2', 'forum'),
7402 '1' => get_string('openmode1', 'forum'),
7403 '0' => get_string('openmode0', 'forum') );
7407 * Returns all other caps used in module
7409 * @return array
7411 function forum_get_extra_capabilities() {
7412 return array('moodle/site:accessallgroups', 'moodle/site:viewfullnames', 'moodle/site:trustcontent', 'moodle/rating:view', 'moodle/rating:viewany', 'moodle/rating:viewall', 'moodle/rating:rate');
7417 * This function is used to extend the global navigation by add forum nodes if there
7418 * is relevant content.
7420 * @param navigation_node $navref
7421 * @param stdClass $course
7422 * @param stdClass $module
7423 * @param stdClass $cm
7425 /*************************************************
7426 function forum_extend_navigation($navref, $course, $module, $cm) {
7427 global $CFG, $OUTPUT, $USER;
7429 $limit = 5;
7431 $discussions = forum_get_discussions($cm,"d.timemodified DESC", false, -1, $limit);
7432 $discussioncount = forum_get_discussions_count($cm);
7433 if (!is_array($discussions) || count($discussions)==0) {
7434 return;
7436 $discussionnode = $navref->add(get_string('discussions', 'forum').' ('.$discussioncount.')');
7437 $discussionnode->mainnavonly = true;
7438 $discussionnode->display = false; // Do not display on navigation (only on navbar)
7440 foreach ($discussions as $discussion) {
7441 $icon = new pix_icon('i/feedback', '');
7442 $url = new moodle_url('/mod/forum/discuss.php', array('d'=>$discussion->discussion));
7443 $discussionnode->add($discussion->subject, $url, navigation_node::TYPE_SETTING, null, null, $icon);
7446 if ($discussioncount > count($discussions)) {
7447 if (!empty($navref->action)) {
7448 $url = $navref->action;
7449 } else {
7450 $url = new moodle_url('/mod/forum/view.php', array('id'=>$cm->id));
7452 $discussionnode->add(get_string('viewalldiscussions', 'forum'), $url, navigation_node::TYPE_SETTING, null, null, $icon);
7455 $index = 0;
7456 $recentposts = array();
7457 $lastlogin = time() - COURSE_MAX_RECENT_PERIOD;
7458 if (!isguestuser() and !empty($USER->lastcourseaccess[$course->id])) {
7459 if ($USER->lastcourseaccess[$course->id] > $lastlogin) {
7460 $lastlogin = $USER->lastcourseaccess[$course->id];
7463 forum_get_recent_mod_activity($recentposts, $index, $lastlogin, $course->id, $cm->id);
7465 if (is_array($recentposts) && count($recentposts)>0) {
7466 $recentnode = $navref->add(get_string('recentactivity').' ('.count($recentposts).')');
7467 $recentnode->mainnavonly = true;
7468 $recentnode->display = false;
7469 foreach ($recentposts as $post) {
7470 $icon = new pix_icon('i/feedback', '');
7471 $url = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->content->discussion));
7472 $title = $post->content->subject."\n".userdate($post->timestamp, get_string('strftimerecent', 'langconfig'))."\n".$post->user->firstname.' '.$post->user->lastname;
7473 $recentnode->add($title, $url, navigation_node::TYPE_SETTING, null, null, $icon);
7477 *************************/
7480 * Adds module specific settings to the settings block
7482 * @param settings_navigation $settings The settings navigation object
7483 * @param navigation_node $forumnode The node to add module settings to
7485 function forum_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $forumnode) {
7486 global $USER, $PAGE, $CFG, $DB, $OUTPUT;
7488 $forumobject = $DB->get_record("forum", array("id" => $PAGE->cm->instance));
7489 if (empty($PAGE->cm->context)) {
7490 $PAGE->cm->context = get_context_instance(CONTEXT_MODULE, $PAGE->cm->instance);
7493 // for some actions you need to be enrolled, beiing admin is not enough sometimes here
7494 $enrolled = is_enrolled($PAGE->cm->context, $USER, '', false);
7495 $activeenrolled = is_enrolled($PAGE->cm->context, $USER, '', true);
7497 $canmanage = has_capability('mod/forum:managesubscriptions', $PAGE->cm->context);
7498 $subscriptionmode = forum_get_forcesubscribed($forumobject);
7499 $cansubscribe = ($activeenrolled && $subscriptionmode != FORUM_FORCESUBSCRIBE && ($subscriptionmode != FORUM_DISALLOWSUBSCRIBE || $canmanage));
7501 if ($canmanage) {
7502 $mode = $forumnode->add(get_string('subscriptionmode', 'forum'), null, navigation_node::TYPE_CONTAINER);
7504 $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);
7505 $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);
7506 $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);
7507 $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);
7509 switch ($subscriptionmode) {
7510 case FORUM_CHOOSESUBSCRIBE : // 0
7511 $allowchoice->action = null;
7512 $allowchoice->add_class('activesetting');
7513 break;
7514 case FORUM_FORCESUBSCRIBE : // 1
7515 $forceforever->action = null;
7516 $forceforever->add_class('activesetting');
7517 break;
7518 case FORUM_INITIALSUBSCRIBE : // 2
7519 $forceinitially->action = null;
7520 $forceinitially->add_class('activesetting');
7521 break;
7522 case FORUM_DISALLOWSUBSCRIBE : // 3
7523 $disallowchoice->action = null;
7524 $disallowchoice->add_class('activesetting');
7525 break;
7528 } else if ($activeenrolled) {
7530 switch ($subscriptionmode) {
7531 case FORUM_CHOOSESUBSCRIBE : // 0
7532 $notenode = $forumnode->add(get_string('subscriptionoptional', 'forum'));
7533 break;
7534 case FORUM_FORCESUBSCRIBE : // 1
7535 $notenode = $forumnode->add(get_string('subscriptionforced', 'forum'));
7536 break;
7537 case FORUM_INITIALSUBSCRIBE : // 2
7538 $notenode = $forumnode->add(get_string('subscriptionauto', 'forum'));
7539 break;
7540 case FORUM_DISALLOWSUBSCRIBE : // 3
7541 $notenode = $forumnode->add(get_string('subscriptiondisabled', 'forum'));
7542 break;
7546 if ($cansubscribe) {
7547 if (forum_is_subscribed($USER->id, $forumobject)) {
7548 $linktext = get_string('unsubscribe', 'forum');
7549 } else {
7550 $linktext = get_string('subscribe', 'forum');
7552 $url = new moodle_url('/mod/forum/subscribe.php', array('id'=>$forumobject->id, 'sesskey'=>sesskey()));
7553 $forumnode->add($linktext, $url, navigation_node::TYPE_SETTING);
7556 if (has_capability('mod/forum:viewsubscribers', $PAGE->cm->context)){
7557 $url = new moodle_url('/mod/forum/subscribers.php', array('id'=>$forumobject->id));
7558 $forumnode->add(get_string('showsubscribers', 'forum'), $url, navigation_node::TYPE_SETTING);
7561 if ($enrolled && forum_tp_can_track_forums($forumobject)) { // keep tracking info for users with suspended enrolments
7562 if ($forumobject->trackingtype != FORUM_TRACKING_OPTIONAL) {
7563 //tracking forced on or off in forum settings so dont provide a link here to change it
7564 //could add unclickable text like for forced subscription but not sure this justifies adding another menu item
7565 } else {
7566 if (forum_tp_is_tracked($forumobject)) {
7567 $linktext = get_string('notrackforum', 'forum');
7568 } else {
7569 $linktext = get_string('trackforum', 'forum');
7571 $url = new moodle_url('/mod/forum/settracking.php', array('id'=>$forumobject->id));
7572 $forumnode->add($linktext, $url, navigation_node::TYPE_SETTING);
7576 if (!isloggedin() && $PAGE->course->id == SITEID) {
7577 $userid = guest_user()->id;
7578 } else {
7579 $userid = $USER->id;
7582 $hascourseaccess = ($PAGE->course->id == SITEID) || can_access_course($PAGE->course, $userid);
7583 $enablerssfeeds = !empty($CFG->enablerssfeeds) && !empty($CFG->forum_enablerssfeeds);
7585 if ($enablerssfeeds && $forumobject->rsstype && $forumobject->rssarticles && $hascourseaccess) {
7587 if (!function_exists('rss_get_url')) {
7588 require_once("$CFG->libdir/rsslib.php");
7591 if ($forumobject->rsstype == 1) {
7592 $string = get_string('rsssubscriberssdiscussions','forum');
7593 } else {
7594 $string = get_string('rsssubscriberssposts','forum');
7597 $url = new moodle_url(rss_get_url($PAGE->cm->context->id, $userid, "mod_forum", $forumobject->id));
7598 $forumnode->add($string, $url, settings_navigation::TYPE_SETTING, null, null, new pix_icon('i/rss', ''));
7603 * Abstract class used by forum subscriber selection controls
7604 * @package mod-forum
7605 * @copyright 2009 Sam Hemelryk
7606 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7608 abstract class forum_subscriber_selector_base extends user_selector_base {
7611 * The id of the forum this selector is being used for
7612 * @var int
7614 protected $forumid = null;
7616 * The context of the forum this selector is being used for
7617 * @var object
7619 protected $context = null;
7621 * The id of the current group
7622 * @var int
7624 protected $currentgroup = null;
7627 * Constructor method
7628 * @param string $name
7629 * @param array $options
7631 public function __construct($name, $options) {
7632 $options['accesscontext'] = $options['context'];
7633 parent::__construct($name, $options);
7634 if (isset($options['context'])) {
7635 $this->context = $options['context'];
7637 if (isset($options['currentgroup'])) {
7638 $this->currentgroup = $options['currentgroup'];
7640 if (isset($options['forumid'])) {
7641 $this->forumid = $options['forumid'];
7646 * Returns an array of options to seralise and store for searches
7648 * @return array
7650 protected function get_options() {
7651 global $CFG;
7652 $options = parent::get_options();
7653 $options['file'] = substr(__FILE__, strlen($CFG->dirroot.'/'));
7654 $options['context'] = $this->context;
7655 $options['currentgroup'] = $this->currentgroup;
7656 $options['forumid'] = $this->forumid;
7657 return $options;
7663 * A user selector control for potential subscribers to the selected forum
7664 * @package mod-forum
7665 * @copyright 2009 Sam Hemelryk
7666 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7668 class forum_potential_subscriber_selector extends forum_subscriber_selector_base {
7671 * If set to true EVERYONE in this course is force subscribed to this forum
7672 * @var bool
7674 protected $forcesubscribed = false;
7676 * Can be used to store existing subscribers so that they can be removed from
7677 * the potential subscribers list
7679 protected $existingsubscribers = array();
7682 * Constructor method
7683 * @param string $name
7684 * @param array $options
7686 public function __construct($name, $options) {
7687 parent::__construct($name, $options);
7688 if (isset($options['forcesubscribed'])) {
7689 $this->forcesubscribed=true;
7694 * Returns an arary of options for this control
7695 * @return array
7697 protected function get_options() {
7698 $options = parent::get_options();
7699 if ($this->forcesubscribed===true) {
7700 $options['forcesubscribed']=1;
7702 return $options;
7706 * Finds all potential users
7708 * Potential users are determined by checking for users with a capability
7709 * determined in {@see forum_get_potential_subscribers()}
7711 * @param string $search
7712 * @return array
7714 public function find_users($search) {
7715 global $DB;
7717 $availableusers = forum_get_potential_subscribers($this->context, $this->currentgroup, $this->required_fields_sql('u'), 'u.firstname ASC, u.lastname ASC');
7719 if (empty($availableusers)) {
7720 $availableusers = array();
7721 } else if ($search) {
7722 $search = strtolower($search);
7723 foreach ($availableusers as $key=>$user) {
7724 if (stripos($user->firstname, $search) === false && stripos($user->lastname, $search) === false) {
7725 unset($availableusers[$key]);
7730 // Unset any existing subscribers
7731 if (count($this->existingsubscribers)>0 && !$this->forcesubscribed) {
7732 foreach ($this->existingsubscribers as $group) {
7733 foreach ($group as $user) {
7734 if (array_key_exists($user->id, $availableusers)) {
7735 unset($availableusers[$user->id]);
7741 if ($this->forcesubscribed) {
7742 return array(get_string("existingsubscribers", 'forum') => $availableusers);
7743 } else {
7744 return array(get_string("potentialsubscribers", 'forum') => $availableusers);
7749 * Sets the existing subscribers
7750 * @param array $users
7752 public function set_existing_subscribers(array $users) {
7753 $this->existingsubscribers = $users;
7757 * Sets this forum as force subscribed or not
7759 public function set_force_subscribed($setting=true) {
7760 $this->forcesubscribed = true;
7765 * User selector control for removing subscribed users
7766 * @package mod-forum
7767 * @copyright 2009 Sam Hemelryk
7768 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7770 class forum_existing_subscriber_selector extends forum_subscriber_selector_base {
7773 * Finds all subscribed users
7775 * @param string $search
7776 * @return array
7778 public function find_users($search) {
7779 global $DB;
7780 list($wherecondition, $params) = $this->search_sql($search, 'u');
7781 $params['forumid'] = $this->forumid;
7783 // only active enrolled or everybody on the frontpage
7784 list($esql, $eparams) = get_enrolled_sql($this->context, '', $this->currentgroup, true);
7785 $params = array_merge($params, $eparams);
7787 $fields = $this->required_fields_sql('u');
7789 $subscribers = $DB->get_records_sql("SELECT $fields
7790 FROM {user} u
7791 JOIN ($esql) je ON je.id = u.id
7792 JOIN {forum_subscriptions} s ON s.userid = u.id
7793 WHERE $wherecondition AND s.forum = :forumid
7794 ORDER BY u.lastname ASC, u.firstname ASC", $params);
7796 return array(get_string("existingsubscribers", 'forum') => $subscribers);
7802 * Adds information about unread messages, that is only required for the course view page (and
7803 * similar), to the course-module object.
7804 * @param cm_info $cm Course-module object
7806 function forum_cm_info_view(cm_info $cm) {
7807 global $CFG;
7809 // Get tracking status (once per request)
7810 static $initialised;
7811 static $usetracking, $strunreadpostsone;
7812 if (!isset($initialised)) {
7813 if ($usetracking = forum_tp_can_track_forums()) {
7814 $strunreadpostsone = get_string('unreadpostsone', 'forum');
7816 $initialised = true;
7819 if ($usetracking) {
7820 if ($unread = forum_tp_count_forum_unread_posts($cm, $cm->get_course())) {
7821 $out = '<span class="unread"> <a href="' . $cm->get_url() . '">';
7822 if ($unread == 1) {
7823 $out .= $strunreadpostsone;
7824 } else {
7825 $out .= get_string('unreadpostsnumber', 'forum', $unread);
7827 $out .= '</a></span>';
7828 $cm->set_after_link($out);
7834 * Return a list of page types
7835 * @param string $pagetype current page type
7836 * @param stdClass $parentcontext Block's parent context
7837 * @param stdClass $currentcontext Current context of block
7839 function forum_page_type_list($pagetype, $parentcontext, $currentcontext) {
7840 $forum_pagetype = array(
7841 'mod-forum-*'=>get_string('page-mod-forum-x', 'forum'),
7842 'mod-forum-view'=>get_string('page-mod-forum-view', 'forum'),
7843 'mod-forum-discuss'=>get_string('page-mod-forum-discuss', 'forum')
7845 return $forum_pagetype;
7849 * Gets all of the courses where the provided user has posted in a forum.
7851 * @global moodle_database $DB The database connection
7852 * @param stdClass $user The user who's posts we are looking for
7853 * @param bool $discussionsonly If true only look for discussions started by the user
7854 * @param bool $includecontexts If set to trye contexts for the courses will be preloaded
7855 * @param int $limitfrom The offset of records to return
7856 * @param int $limitnum The number of records to return
7857 * @return array An array of courses
7859 function forum_get_courses_user_posted_in($user, $discussionsonly = false, $includecontexts = true, $limitfrom = null, $limitnum = null) {
7860 global $DB;
7862 // If we are only after discussions we need only look at the forum_discussions
7863 // table and join to the userid there. If we are looking for posts then we need
7864 // to join to the forum_posts table.
7865 if (!$discussionsonly) {
7866 $joinsql = 'JOIN {forum_discussions} fd ON fd.course = c.id
7867 JOIN {forum_posts} fp ON fp.discussion = fd.id';
7868 $wheresql = 'fp.userid = :userid';
7869 $params = array('userid' => $user->id);
7870 } else {
7871 $joinsql = 'JOIN {forum_discussions} fd ON fd.course = c.id';
7872 $wheresql = 'fd.userid = :userid';
7873 $params = array('userid' => $user->id);
7876 // Join to the context table so that we can preload contexts if required.
7877 if ($includecontexts) {
7878 list($ctxselect, $ctxjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
7879 } else {
7880 $ctxselect = '';
7881 $ctxjoin = '';
7884 // Now we need to get all of the courses to search.
7885 // All courses where the user has posted within a forum will be returned.
7886 $sql = "SELECT DISTINCT c.* $ctxselect
7887 FROM {course} c
7888 $joinsql
7889 $ctxjoin
7890 WHERE $wheresql";
7891 $courses = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
7892 if ($includecontexts) {
7893 array_map('context_instance_preload', $courses);
7895 return $courses;
7899 * Gets all of the forums a user has posted in for one or more courses.
7901 * @global moodle_database $DB
7902 * @param stdClass $user
7903 * @param array $courseids An array of courseids to search or if not provided
7904 * all courses the user has posted within
7905 * @param bool $discussionsonly If true then only forums where the user has started
7906 * a discussion will be returned.
7907 * @param int $limitfrom The offset of records to return
7908 * @param int $limitnum The number of records to return
7909 * @return array An array of forums the user has posted within in the provided courses
7911 function forum_get_forums_user_posted_in($user, array $courseids = null, $discussionsonly = false, $limitfrom = null, $limitnum = null) {
7912 global $DB;
7914 $where = array("m.name = 'forum'");
7915 $params = array();
7916 if (!is_null($courseids)) {
7917 list($coursewhere, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED, 'courseid');
7918 $where[] = 'f.course '.$coursewhere;
7920 if (!$discussionsonly) {
7921 $joinsql = 'JOIN {forum_discussions} fd ON fd.forum = f.id
7922 JOIN {forum_posts} fp ON fp.discussion = fd.id';
7923 $where[] = 'fp.userid = :userid';
7924 } else {
7925 $joinsql = 'JOIN {forum_discussions} fd ON fd.forum = f.id';
7926 $where[] = 'fd.userid = :userid';
7928 $params['userid'] = $user->id;
7929 $wheresql = join(' AND ', $where);
7931 $sql = "SELECT DISTINCT f.*, cm.id AS cmid
7932 FROM {forum} f
7933 JOIN {course_modules} cm ON cm.instance = f.id
7934 JOIN {modules} m ON m.id = cm.module
7935 $joinsql
7936 WHERE $wheresql";
7937 $courseforums = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
7938 return $courseforums;
7942 * Returns posts made by the selected user in the requested courses.
7944 * This method can be used to return all of the posts made by the requested user
7945 * within the given courses.
7946 * For each course the access of the current user and requested user is checked
7947 * and then for each post access to the post and forum is checked as well.
7949 * This function is safe to use with usercapabilities.
7951 * @global moodle_database $DB
7952 * @param stdClass $user The user whose posts we want to get
7953 * @param array $courses The courses to search
7954 * @param bool $musthaveaccess If set to true errors will be thrown if the user
7955 * cannot access one or more of the courses to search
7956 * @param bool $discussionsonly If set to true only discussion starting posts
7957 * will be returned.
7958 * @param int $limitfrom The offset of records to return
7959 * @param int $limitnum The number of records to return
7960 * @return stdClass An object the following properties
7961 * ->totalcount: the total number of posts made by the requested user
7962 * that the current user can see.
7963 * ->courses: An array of courses the current user can see that the
7964 * requested user has posted in.
7965 * ->forums: An array of forums relating to the posts returned in the
7966 * property below.
7967 * ->posts: An array containing the posts to show for this request.
7969 function forum_get_posts_by_user($user, array $courses, $musthaveaccess = false, $discussionsonly = false, $limitfrom = 0, $limitnum = 50) {
7970 global $DB, $USER, $CFG;
7972 $return = new stdClass;
7973 $return->totalcount = 0; // The total number of posts that the current user is able to view
7974 $return->courses = array(); // The courses the current user can access
7975 $return->forums = array(); // The forums that the current user can access that contain posts
7976 $return->posts = array(); // The posts to display
7978 // First up a small sanity check. If there are no courses to check we can
7979 // return immediately, there is obviously nothing to search.
7980 if (empty($courses)) {
7981 return $return;
7984 // A couple of quick setups
7985 $isloggedin = isloggedin();
7986 $isguestuser = $isloggedin && isguestuser();
7987 $iscurrentuser = $isloggedin && $USER->id == $user->id;
7989 // Checkout whether or not the current user has capabilities over the requested
7990 // user and if so they have the capabilities required to view the requested
7991 // users content.
7992 $usercontext = get_context_instance(CONTEXT_USER, $user->id, MUST_EXIST);
7993 $hascapsonuser = !$iscurrentuser && $DB->record_exists('role_assignments', array('userid' => $USER->id, 'contextid' => $usercontext->id));
7994 $hascapsonuser = $hascapsonuser && has_all_capabilities(array('moodle/user:viewdetails', 'moodle/user:readuserposts'), $usercontext);
7996 // Before we actually search each course we need to check the user's access to the
7997 // course. If the user doesn't have the appropraite access then we either throw an
7998 // error if a particular course was requested or we just skip over the course.
7999 foreach ($courses as $course) {
8000 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id, MUST_EXIST);
8001 if ($iscurrentuser || $hascapsonuser) {
8002 // If it is the current user, or the current user has capabilities to the
8003 // requested user then all we need to do is check the requested users
8004 // current access to the course.
8005 // Note: There is no need to check group access or anything of the like
8006 // as either the current user is the requested user, or has granted
8007 // capabilities on the requested user. Either way they can see what the
8008 // requested user posted, although its VERY unlikely in the `parent` situation
8009 // that the current user will be able to view the posts in context.
8010 if (!is_viewing($coursecontext, $user) && !is_enrolled($coursecontext, $user)) {
8011 // Need to have full access to a course to see the rest of own info
8012 if ($musthaveaccess) {
8013 print_error('errorenrolmentrequired', 'forum');
8015 continue;
8017 } else {
8018 // Check whether the current user is enrolled or has access to view the course
8019 // if they don't we immediately have a problem.
8020 if (!can_access_course($course)) {
8021 if ($musthaveaccess) {
8022 print_error('errorenrolmentrequired', 'forum');
8024 continue;
8027 // Check whether the requested user is enrolled or has access to view the course
8028 // if they don't we immediately have a problem.
8029 if (!can_access_course($course, $user)) {
8030 if ($musthaveaccess) {
8031 print_error('notenrolled', 'forum');
8033 continue;
8036 // If groups are in use and enforced throughout the course then make sure
8037 // we can meet in at least one course level group.
8038 // Note that we check if either the current user or the requested user have
8039 // the capability to access all groups. This is because with that capability
8040 // a user in group A could post in the group B forum. Grrrr.
8041 if (groups_get_course_groupmode($course) == SEPARATEGROUPS && $course->groupmodeforce
8042 && !has_capability('moodle/site:accessallgroups', $coursecontext) && !has_capability('moodle/site:accessallgroups', $coursecontext, $user->id)) {
8043 // If its the guest user to bad... the guest user cannot access groups
8044 if (!$isloggedin or $isguestuser) {
8045 // do not use require_login() here because we might have already used require_login($course)
8046 if ($musthaveaccess) {
8047 redirect(get_login_url());
8049 continue;
8051 // Get the groups of the current user
8052 $mygroups = array_keys(groups_get_all_groups($course->id, $USER->id, $course->defaultgroupingid, 'g.id, g.name'));
8053 // Get the groups the requested user is a member of
8054 $usergroups = array_keys(groups_get_all_groups($course->id, $user->id, $course->defaultgroupingid, 'g.id, g.name'));
8055 // Check whether they are members of the same group. If they are great.
8056 $intersect = array_intersect($mygroups, $usergroups);
8057 if (empty($intersect)) {
8058 // But they're not... if it was a specific course throw an error otherwise
8059 // just skip this course so that it is not searched.
8060 if ($musthaveaccess) {
8061 print_error("groupnotamember", '', $CFG->wwwroot."/course/view.php?id=$course->id");
8063 continue;
8067 // Woo hoo we got this far which means the current user can search this
8068 // this course for the requested user. Although this is only the course accessibility
8069 // handling that is complete, the forum accessibility tests are yet to come.
8070 $return->courses[$course->id] = $course;
8072 // No longer beed $courses array - lose it not it may be big
8073 unset($courses);
8075 // Make sure that we have some courses to search
8076 if (empty($return->courses)) {
8077 // If we don't have any courses to search then the reality is that the current
8078 // user doesn't have access to any courses is which the requested user has posted.
8079 // Although we do know at this point that the requested user has posts.
8080 if ($musthaveaccess) {
8081 print_error('permissiondenied');
8082 } else {
8083 return $return;
8087 // Next step: Collect all of the forums that we will want to search.
8088 // It is important to note that this step isn't actually about searching, it is
8089 // about determining which forums we can search by testing accessibility.
8090 $forums = forum_get_forums_user_posted_in($user, array_keys($return->courses), $discussionsonly);
8092 // Will be used to build the where conditions for the search
8093 $forumsearchwhere = array();
8094 // Will be used to store the where condition params for the search
8095 $forumsearchparams = array();
8096 // Will record forums where the user can freely access everything
8097 $forumsearchfullaccess = array();
8098 // DB caching friendly
8099 $now = round(time(), -2);
8100 // For each course to search we want to find the forums the user has posted in
8101 // and providing the current user can access the forum create a search condition
8102 // for the forum to get the requested users posts.
8103 foreach ($return->courses as $course) {
8104 // Now we need to get the forums
8105 $modinfo = get_fast_modinfo($course);
8106 if (empty($modinfo->instances['forum'])) {
8107 // hmmm, no forums? well at least its easy... skip!
8108 continue;
8110 // Iterate
8111 foreach ($modinfo->get_instances_of('forum') as $forumid => $cm) {
8112 if (!$cm->uservisible or !isset($forums[$forumid])) {
8113 continue;
8115 // Get the forum in question
8116 $forum = $forums[$forumid];
8117 // This is needed for functionality later on in the forum code....
8118 $forum->cm = $cm;
8120 // Check that either the current user can view the forum, or that the
8121 // current user has capabilities over the requested user and the requested
8122 // user can view the discussion
8123 if (!has_capability('mod/forum:viewdiscussion', $cm->context) && !($hascapsonuser && has_capability('mod/forum:viewdiscussion', $cm->context, $user->id))) {
8124 continue;
8127 // This will contain forum specific where clauses
8128 $forumsearchselect = array();
8129 if (!$iscurrentuser && !$hascapsonuser) {
8130 // Make sure we check group access
8131 if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $cm->context)) {
8132 $groups = $modinfo->get_groups($cm->groupingid);
8133 $groups[] = -1;
8134 list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
8135 $forumsearchparams = array_merge($forumsearchparams, $groupid_params);
8136 $forumsearchselect[] = "d.groupid $groupid_sql";
8139 // hidden timed discussions
8140 if (!empty($CFG->forum_enabletimedposts) && !has_capability('mod/forum:viewhiddentimedposts', $cm->context)) {
8141 $forumsearchselect[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
8142 $forumsearchparams['userid'.$forumid] = $user->id;
8143 $forumsearchparams['timestart'.$forumid] = $now;
8144 $forumsearchparams['timeend'.$forumid] = $now;
8147 // qanda access
8148 if ($forum->type == 'qanda' && !has_capability('mod/forum:viewqandawithoutposting', $cm->context)) {
8149 // We need to check whether the user has posted in the qanda forum.
8150 $discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $user->id);
8151 if (!empty($discussionspostedin)) {
8152 $forumonlydiscussions = array(); // Holds discussion ids for the discussions the user is allowed to see in this forum.
8153 foreach ($discussionspostedin as $d) {
8154 $forumonlydiscussions[] = $d->id;
8156 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forumonlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
8157 $forumsearchparams = array_merge($forumsearchparams, $discussionid_params);
8158 $forumsearchselect[] = "(d.id $discussionid_sql OR p.parent = 0)";
8159 } else {
8160 $forumsearchselect[] = "p.parent = 0";
8165 if (count($forumsearchselect) > 0) {
8166 $forumsearchwhere[] = "(d.forum = :forum{$forumid} AND ".implode(" AND ", $forumsearchselect).")";
8167 $forumsearchparams['forum'.$forumid] = $forumid;
8168 } else {
8169 $forumsearchfullaccess[] = $forumid;
8171 } else {
8172 // The current user/parent can see all of their own posts
8173 $forumsearchfullaccess[] = $forumid;
8178 // If we dont have any search conditions, and we don't have any forums where
8179 // the user has full access then we just return the default.
8180 if (empty($forumsearchwhere) && empty($forumsearchfullaccess)) {
8181 return $return;
8184 // Prepare a where condition for the full access forums.
8185 if (count($forumsearchfullaccess) > 0) {
8186 list($fullidsql, $fullidparams) = $DB->get_in_or_equal($forumsearchfullaccess, SQL_PARAMS_NAMED, 'fula');
8187 $forumsearchparams = array_merge($forumsearchparams, $fullidparams);
8188 $forumsearchwhere[] = "(d.forum $fullidsql)";
8191 // Prepare SQL to both count and search
8192 $userfields = user_picture::fields('u', null, 'userid');
8193 $countsql = 'SELECT COUNT(*) ';
8194 $selectsql = 'SELECT p.*, d.forum, d.name AS discussionname, '.$userfields.' ';
8195 $wheresql = implode(" OR ", $forumsearchwhere);
8197 if ($discussionsonly) {
8198 if ($wheresql == '') {
8199 $wheresql = 'p.parent = 0';
8200 } else {
8201 $wheresql = 'p.parent = 0 AND ('.$wheresql.')';
8205 $sql = "FROM {forum_posts} p
8206 JOIN {forum_discussions} d ON d.id = p.discussion
8207 JOIN {user} u ON u.id = p.userid
8208 WHERE ($wheresql)
8209 AND p.userid = :userid ";
8210 $orderby = "ORDER BY p.modified DESC";
8211 $forumsearchparams['userid'] = $user->id;
8213 // Set the total number posts made by the requested user that the current user can see
8214 $return->totalcount = $DB->count_records_sql($countsql.$sql, $forumsearchparams);
8215 // Set the collection of posts that has been requested
8216 $return->posts = $DB->get_records_sql($selectsql.$sql.$orderby, $forumsearchparams, $limitfrom, $limitnum);
8218 // We need to build an array of forums for which posts will be displayed.
8219 // We do this here to save the caller needing to retrieve them themselves before
8220 // printing these forums posts. Given we have the forums already there is
8221 // practically no overhead here.
8222 foreach ($return->posts as $post) {
8223 if (!array_key_exists($post->forum, $return->forums)) {
8224 $return->forums[$post->forum] = $forums[$post->forum];
8228 return $return;