Merge branch 'wip-MDL-31948-master' of git://github.com/phalacee/moodle
[moodle.git] / mod / forum / lib.php
blob10bd968a6c95c35ea9df5cc48649ce6a561e7bdd
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 * @param stdClass $forum add forum instance
56 * @param mod_forum_mod_form $mform
57 * @return int intance id
59 function forum_add_instance($forum, $mform = null) {
60 global $CFG, $DB;
62 $forum->timemodified = time();
64 if (empty($forum->assessed)) {
65 $forum->assessed = 0;
68 if (empty($forum->ratingtime) or empty($forum->assessed)) {
69 $forum->assesstimestart = 0;
70 $forum->assesstimefinish = 0;
73 $forum->id = $DB->insert_record('forum', $forum);
74 $modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule);
76 if ($forum->type == 'single') { // Create related discussion.
77 $discussion = new stdClass();
78 $discussion->course = $forum->course;
79 $discussion->forum = $forum->id;
80 $discussion->name = $forum->name;
81 $discussion->assessed = $forum->assessed;
82 $discussion->message = $forum->intro;
83 $discussion->messageformat = $forum->introformat;
84 $discussion->messagetrust = trusttext_trusted(get_context_instance(CONTEXT_COURSE, $forum->course));
85 $discussion->mailnow = false;
86 $discussion->groupid = -1;
88 $message = '';
90 $discussion->id = forum_add_discussion($discussion, null, $message);
92 if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
93 // ugly hack - we need to copy the files somehow
94 $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
95 $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
97 $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
98 $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
102 if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
103 /// all users should be subscribed initially
104 /// Note: forum_get_potential_subscribers should take the forum context,
105 /// but that does not exist yet, becuase the forum is only half build at this
106 /// stage. However, because the forum is brand new, we know that there are
107 /// no role assignments or overrides in the forum context, so using the
108 /// course context gives the same list of users.
109 $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
110 foreach ($users as $user) {
111 forum_subscribe($user->id, $forum->id);
115 forum_grade_item_update($forum);
117 return $forum->id;
122 * Given an object containing all the necessary data,
123 * (defined by the form in mod_form.php) this function
124 * will update an existing instance with new data.
126 * @global object
127 * @param object $forum forum instance (with magic quotes)
128 * @return bool success
130 function forum_update_instance($forum, $mform) {
131 global $DB, $OUTPUT, $USER;
133 $forum->timemodified = time();
134 $forum->id = $forum->instance;
136 if (empty($forum->assessed)) {
137 $forum->assessed = 0;
140 if (empty($forum->ratingtime) or empty($forum->assessed)) {
141 $forum->assesstimestart = 0;
142 $forum->assesstimefinish = 0;
145 $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
147 // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
148 // if scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
149 // 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
150 if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
151 forum_update_grades($forum); // recalculate grades for the forum
154 if ($forum->type == 'single') { // Update related discussion and post.
155 $discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC');
156 if (!empty($discussions)) {
157 if (count($discussions) > 1) {
158 echo $OUTPUT->notification(get_string('warnformorepost', 'forum'));
160 $discussion = array_pop($discussions);
161 } else {
162 // try to recover by creating initial discussion - MDL-16262
163 $discussion = new stdClass();
164 $discussion->course = $forum->course;
165 $discussion->forum = $forum->id;
166 $discussion->name = $forum->name;
167 $discussion->assessed = $forum->assessed;
168 $discussion->message = $forum->intro;
169 $discussion->messageformat = $forum->introformat;
170 $discussion->messagetrust = true;
171 $discussion->mailnow = false;
172 $discussion->groupid = -1;
174 $message = '';
176 forum_add_discussion($discussion, null, $message);
178 if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
179 print_error('cannotadd', 'forum');
182 if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
183 print_error('cannotfindfirstpost', 'forum');
186 $cm = get_coursemodule_from_instance('forum', $forum->id);
187 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
189 if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
190 // ugly hack - we need to copy the files somehow
191 $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
192 $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
194 $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
197 $post->subject = $forum->name;
198 $post->message = $forum->intro;
199 $post->messageformat = $forum->introformat;
200 $post->messagetrust = trusttext_trusted($modcontext);
201 $post->modified = $forum->timemodified;
202 $post->userid = $USER->id; // MDL-18599, so that current teacher can take ownership of activities
204 $DB->update_record('forum_posts', $post);
205 $discussion->name = $forum->name;
206 $DB->update_record('forum_discussions', $discussion);
209 $DB->update_record('forum', $forum);
211 forum_grade_item_update($forum);
213 return true;
218 * Given an ID of an instance of this module,
219 * this function will permanently delete the instance
220 * and any data that depends on it.
222 * @global object
223 * @param int $id forum instance id
224 * @return bool success
226 function forum_delete_instance($id) {
227 global $DB;
229 if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
230 return false;
232 if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
233 return false;
235 if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
236 return false;
239 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
241 // now get rid of all files
242 $fs = get_file_storage();
243 $fs->delete_area_files($context->id);
245 $result = true;
247 if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
248 foreach ($discussions as $discussion) {
249 if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
250 $result = false;
255 if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
256 $result = false;
259 forum_tp_delete_read_records(-1, -1, -1, $forum->id);
261 if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
262 $result = false;
265 forum_grade_item_delete($forum);
267 return $result;
272 * Indicates API features that the forum supports.
274 * @uses FEATURE_GROUPS
275 * @uses FEATURE_GROUPINGS
276 * @uses FEATURE_GROUPMEMBERSONLY
277 * @uses FEATURE_MOD_INTRO
278 * @uses FEATURE_COMPLETION_TRACKS_VIEWS
279 * @uses FEATURE_COMPLETION_HAS_RULES
280 * @uses FEATURE_GRADE_HAS_GRADE
281 * @uses FEATURE_GRADE_OUTCOMES
282 * @param string $feature
283 * @return mixed True if yes (some features may use other values)
285 function forum_supports($feature) {
286 switch($feature) {
287 case FEATURE_GROUPS: return true;
288 case FEATURE_GROUPINGS: return true;
289 case FEATURE_GROUPMEMBERSONLY: return true;
290 case FEATURE_MOD_INTRO: return true;
291 case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
292 case FEATURE_COMPLETION_HAS_RULES: return true;
293 case FEATURE_GRADE_HAS_GRADE: return true;
294 case FEATURE_GRADE_OUTCOMES: return true;
295 case FEATURE_RATE: return true;
296 case FEATURE_BACKUP_MOODLE2: return true;
297 case FEATURE_SHOW_DESCRIPTION: return true;
299 default: return null;
305 * Obtains the automatic completion state for this forum based on any conditions
306 * in forum settings.
308 * @global object
309 * @global object
310 * @param object $course Course
311 * @param object $cm Course-module
312 * @param int $userid User ID
313 * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
314 * @return bool True if completed, false if not. (If no conditions, then return
315 * value depends on comparison type)
317 function forum_get_completion_state($course,$cm,$userid,$type) {
318 global $CFG,$DB;
320 // Get forum details
321 if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
322 throw new Exception("Can't find forum {$cm->instance}");
325 $result=$type; // Default return value
327 $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
328 $postcountsql="
329 SELECT
330 COUNT(1)
331 FROM
332 {forum_posts} fp
333 INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
334 WHERE
335 fp.userid=:userid AND fd.forum=:forumid";
337 if ($forum->completiondiscussions) {
338 $value = $forum->completiondiscussions <=
339 $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
340 if ($type == COMPLETION_AND) {
341 $result = $result && $value;
342 } else {
343 $result = $result || $value;
346 if ($forum->completionreplies) {
347 $value = $forum->completionreplies <=
348 $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
349 if ($type==COMPLETION_AND) {
350 $result = $result && $value;
351 } else {
352 $result = $result || $value;
355 if ($forum->completionposts) {
356 $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
357 if ($type == COMPLETION_AND) {
358 $result = $result && $value;
359 } else {
360 $result = $result || $value;
364 return $result;
368 * Create a message-id string to use in the custom headers of forum notification emails
370 * message-id is used by email clients to identify emails and to nest conversations
372 * @param int $postid The ID of the forum post we are notifying the user about
373 * @param int $usertoid The ID of the user being notified
374 * @param string $hostname The server's hostname
375 * @return string A unique message-id
377 function forum_get_email_message_id($postid, $usertoid, $hostname) {
378 return '<'.hash('sha256',$postid.'to'.$usertoid.'@'.$hostname).'>';
382 * Function to be run periodically according to the moodle cron
383 * Finds all posts that have yet to be mailed out, and mails them
384 * out to all subscribers
386 * @global object
387 * @global object
388 * @global object
389 * @uses CONTEXT_MODULE
390 * @uses CONTEXT_COURSE
391 * @uses SITEID
392 * @uses FORMAT_PLAIN
393 * @return void
395 function forum_cron() {
396 global $CFG, $USER, $DB;
398 $site = get_site();
400 // all users that are subscribed to any post that needs sending
401 $users = array();
403 // status arrays
404 $mailcount = array();
405 $errorcount = array();
407 // caches
408 $discussions = array();
409 $forums = array();
410 $courses = array();
411 $coursemodules = array();
412 $subscribedusers = array();
415 // Posts older than 2 days will not be mailed. This is to avoid the problem where
416 // cron has not been running for a long time, and then suddenly people are flooded
417 // with mail from the past few weeks or months
418 $timenow = time();
419 $endtime = $timenow - $CFG->maxeditingtime;
420 $starttime = $endtime - 48 * 3600; // Two days earlier
422 if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
423 // Mark them all now as being mailed. It's unlikely but possible there
424 // might be an error later so that a post is NOT actually mailed out,
425 // but since mail isn't crucial, we can accept this risk. Doing it now
426 // prevents the risk of duplicated mails, which is a worse problem.
428 if (!forum_mark_old_posts_as_mailed($endtime)) {
429 mtrace('Errors occurred while trying to mark some posts as being mailed.');
430 return false; // Don't continue trying to mail them, in case we are in a cron loop
433 // checking post validity, and adding users to loop through later
434 foreach ($posts as $pid => $post) {
436 $discussionid = $post->discussion;
437 if (!isset($discussions[$discussionid])) {
438 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
439 $discussions[$discussionid] = $discussion;
440 } else {
441 mtrace('Could not find discussion '.$discussionid);
442 unset($posts[$pid]);
443 continue;
446 $forumid = $discussions[$discussionid]->forum;
447 if (!isset($forums[$forumid])) {
448 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
449 $forums[$forumid] = $forum;
450 } else {
451 mtrace('Could not find forum '.$forumid);
452 unset($posts[$pid]);
453 continue;
456 $courseid = $forums[$forumid]->course;
457 if (!isset($courses[$courseid])) {
458 if ($course = $DB->get_record('course', array('id' => $courseid))) {
459 $courses[$courseid] = $course;
460 } else {
461 mtrace('Could not find course '.$courseid);
462 unset($posts[$pid]);
463 continue;
466 if (!isset($coursemodules[$forumid])) {
467 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
468 $coursemodules[$forumid] = $cm;
469 } else {
470 mtrace('Could not find course module for forum '.$forumid);
471 unset($posts[$pid]);
472 continue;
477 // caching subscribed users of each forum
478 if (!isset($subscribedusers[$forumid])) {
479 $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
480 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext, "u.*")) {
481 foreach ($subusers as $postuser) {
482 unset($postuser->description); // not necessary
483 // this user is subscribed to this forum
484 $subscribedusers[$forumid][$postuser->id] = $postuser->id;
485 // this user is a user we have to process later
486 $users[$postuser->id] = $postuser;
488 unset($subusers); // release memory
492 $mailcount[$pid] = 0;
493 $errorcount[$pid] = 0;
497 if ($users && $posts) {
499 $urlinfo = parse_url($CFG->wwwroot);
500 $hostname = $urlinfo['host'];
502 foreach ($users as $userto) {
504 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
506 // set this so that the capabilities are cached, and environment matches receiving user
507 cron_setup_user($userto);
509 mtrace('Processing user '.$userto->id);
511 // init caches
512 $userto->viewfullnames = array();
513 $userto->canpost = array();
514 $userto->markposts = array();
516 // reset the caches
517 foreach ($coursemodules as $forumid=>$unused) {
518 $coursemodules[$forumid]->cache = new stdClass();
519 $coursemodules[$forumid]->cache->caps = array();
520 unset($coursemodules[$forumid]->uservisible);
523 foreach ($posts as $pid => $post) {
525 // Set up the environment for the post, discussion, forum, course
526 $discussion = $discussions[$post->discussion];
527 $forum = $forums[$discussion->forum];
528 $course = $courses[$forum->course];
529 $cm =& $coursemodules[$forum->id];
531 // Do some checks to see if we can bail out now
532 // Only active enrolled users are in the list of subscribers
533 if (!isset($subscribedusers[$forum->id][$userto->id])) {
534 continue; // user does not subscribe to this forum
537 // Don't send email if the forum is Q&A and the user has not posted
538 // Initial topics are still mailed
539 if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id) && $pid != $discussion->firstpost) {
540 mtrace('Did not email '.$userto->id.' because user has not posted in discussion');
541 continue;
544 // Get info about the sending user
545 if (array_key_exists($post->userid, $users)) { // we might know him/her already
546 $userfrom = $users[$post->userid];
547 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
548 unset($userfrom->description); // not necessary
549 $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
550 } else {
551 mtrace('Could not find user '.$post->userid);
552 continue;
555 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
557 // setup global $COURSE properly - needed for roles and languages
558 cron_setup_user($userto, $course);
560 // Fill caches
561 if (!isset($userto->viewfullnames[$forum->id])) {
562 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
563 $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
565 if (!isset($userto->canpost[$discussion->id])) {
566 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
567 $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
569 if (!isset($userfrom->groups[$forum->id])) {
570 if (!isset($userfrom->groups)) {
571 $userfrom->groups = array();
572 $users[$userfrom->id]->groups = array();
574 $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
575 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
578 // Make sure groups allow this user to see this email
579 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used
580 if (!groups_group_exists($discussion->groupid)) { // Can't find group
581 continue; // Be safe and don't send it to anyone
584 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
585 // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
586 continue;
590 // Make sure we're allowed to see it...
591 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
592 mtrace('user '.$userto->id. ' can not see '.$post->id);
593 continue;
596 // OK so we need to send the email.
598 // Does the user want this post in a digest? If so postpone it for now.
599 if ($userto->maildigest > 0) {
600 // This user wants the mails to be in digest form
601 $queue = new stdClass();
602 $queue->userid = $userto->id;
603 $queue->discussionid = $discussion->id;
604 $queue->postid = $post->id;
605 $queue->timemodified = $post->created;
606 $DB->insert_record('forum_queue', $queue);
607 continue;
611 // Prepare to actually send the post now, and build up the content
613 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
615 $userfrom->customheaders = array ( // Headers to make emails easier to track
616 'Precedence: Bulk',
617 'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
618 'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
619 'Message-ID: '.forum_get_email_message_id($post->id, $userto->id, $hostname),
620 'X-Course-Id: '.$course->id,
621 'X-Course-Name: '.format_string($course->fullname, true)
624 if ($post->parent) { // This post is a reply, so add headers for threading (see MDL-22551)
625 $userfrom->customheaders[] = 'In-Reply-To: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
626 $userfrom->customheaders[] = 'References: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
629 $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
631 $postsubject = "$shortname: ".format_string($post->subject,true);
632 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
633 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
635 // Send the post now!
637 mtrace('Sending ', '');
639 $eventdata = new stdClass();
640 $eventdata->component = 'mod_forum';
641 $eventdata->name = 'posts';
642 $eventdata->userfrom = $userfrom;
643 $eventdata->userto = $userto;
644 $eventdata->subject = $postsubject;
645 $eventdata->fullmessage = $posttext;
646 $eventdata->fullmessageformat = FORMAT_PLAIN;
647 $eventdata->fullmessagehtml = $posthtml;
648 $eventdata->notification = 1;
650 $smallmessagestrings = new stdClass();
651 $smallmessagestrings->user = fullname($userfrom);
652 $smallmessagestrings->forumname = "$shortname: ".format_string($forum->name,true).": ".$discussion->name;
653 $smallmessagestrings->message = $post->message;
654 //make sure strings are in message recipients language
655 $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
657 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
658 $eventdata->contexturlname = $discussion->name;
660 $mailresult = message_send($eventdata);
661 if (!$mailresult){
662 mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
663 " ($userto->email) .. not trying again.");
664 add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
665 substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
666 $errorcount[$post->id]++;
667 } else {
668 $mailcount[$post->id]++;
670 // Mark post as read if forum_usermarksread is set off
671 if (!$CFG->forum_usermarksread) {
672 $userto->markposts[$post->id] = $post->id;
676 mtrace('post '.$post->id. ': '.$post->subject);
679 // mark processed posts as read
680 forum_tp_mark_posts_read($userto, $userto->markposts);
684 if ($posts) {
685 foreach ($posts as $post) {
686 mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
687 if ($errorcount[$post->id]) {
688 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
693 // release some memory
694 unset($subscribedusers);
695 unset($mailcount);
696 unset($errorcount);
698 cron_setup_user();
700 $sitetimezone = $CFG->timezone;
702 // Now see if there are any digest mails waiting to be sent, and if we should send them
704 mtrace('Starting digest processing...');
706 @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
708 if (!isset($CFG->digestmailtimelast)) { // To catch the first time
709 set_config('digestmailtimelast', 0);
712 $timenow = time();
713 $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
715 // Delete any really old ones (normally there shouldn't be any)
716 $weekago = $timenow - (7 * 24 * 3600);
717 $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
718 mtrace ('Cleaned old digest records');
720 if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
722 mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
724 $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
726 if ($digestposts_rs->valid()) {
728 // We have work to do
729 $usermailcount = 0;
731 //caches - reuse the those filled before too
732 $discussionposts = array();
733 $userdiscussions = array();
735 foreach ($digestposts_rs as $digestpost) {
736 if (!isset($users[$digestpost->userid])) {
737 if ($user = $DB->get_record('user', array('id' => $digestpost->userid))) {
738 $users[$digestpost->userid] = $user;
739 } else {
740 continue;
743 $postuser = $users[$digestpost->userid];
745 if (!isset($posts[$digestpost->postid])) {
746 if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
747 $posts[$digestpost->postid] = $post;
748 } else {
749 continue;
752 $discussionid = $digestpost->discussionid;
753 if (!isset($discussions[$discussionid])) {
754 if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
755 $discussions[$discussionid] = $discussion;
756 } else {
757 continue;
760 $forumid = $discussions[$discussionid]->forum;
761 if (!isset($forums[$forumid])) {
762 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
763 $forums[$forumid] = $forum;
764 } else {
765 continue;
769 $courseid = $forums[$forumid]->course;
770 if (!isset($courses[$courseid])) {
771 if ($course = $DB->get_record('course', array('id' => $courseid))) {
772 $courses[$courseid] = $course;
773 } else {
774 continue;
778 if (!isset($coursemodules[$forumid])) {
779 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
780 $coursemodules[$forumid] = $cm;
781 } else {
782 continue;
785 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
786 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
788 $digestposts_rs->close(); /// Finished iteration, let's close the resultset
790 // Data collected, start sending out emails to each user
791 foreach ($userdiscussions as $userid => $thesediscussions) {
793 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
795 cron_setup_user();
797 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
799 // First of all delete all the queue entries for this user
800 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
801 $userto = $users[$userid];
803 // Override the language and timezone of the "current" user, so that
804 // mail is customised for the receiver.
805 cron_setup_user($userto);
807 // init caches
808 $userto->viewfullnames = array();
809 $userto->canpost = array();
810 $userto->markposts = array();
812 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
814 $headerdata = new stdClass();
815 $headerdata->sitename = format_string($site->fullname, true);
816 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
818 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
819 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
821 $posthtml = "<head>";
822 /* foreach ($CFG->stylesheets as $stylesheet) {
823 //TODO: MDL-21120
824 $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
826 $posthtml .= "</head>\n<body id=\"email\">\n";
827 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
829 foreach ($thesediscussions as $discussionid) {
831 @set_time_limit(120); // to be reset for each post
833 $discussion = $discussions[$discussionid];
834 $forum = $forums[$discussion->forum];
835 $course = $courses[$forum->course];
836 $cm = $coursemodules[$forum->id];
838 //override language
839 cron_setup_user($userto, $course);
841 // Fill caches
842 if (!isset($userto->viewfullnames[$forum->id])) {
843 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
844 $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
846 if (!isset($userto->canpost[$discussion->id])) {
847 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
848 $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
851 $strforums = get_string('forums', 'forum');
852 $canunsubscribe = ! forum_is_forcesubscribed($forum);
853 $canreply = $userto->canpost[$discussion->id];
854 $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
856 $posttext .= "\n \n";
857 $posttext .= '=====================================================================';
858 $posttext .= "\n \n";
859 $posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
860 if ($discussion->name != $forum->name) {
861 $posttext .= " -> ".format_string($discussion->name,true);
863 $posttext .= "\n";
865 $posthtml .= "<p><font face=\"sans-serif\">".
866 "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
867 "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
868 "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
869 if ($discussion->name == $forum->name) {
870 $posthtml .= "</font></p>";
871 } else {
872 $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
874 $posthtml .= '<p>';
876 $postsarray = $discussionposts[$discussionid];
877 sort($postsarray);
879 foreach ($postsarray as $postid) {
880 $post = $posts[$postid];
882 if (array_key_exists($post->userid, $users)) { // we might know him/her already
883 $userfrom = $users[$post->userid];
884 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
885 $users[$userfrom->id] = $userfrom; // fetch only once, we can add it to user list, it will be skipped anyway
886 } else {
887 mtrace('Could not find user '.$post->userid);
888 continue;
891 if (!isset($userfrom->groups[$forum->id])) {
892 if (!isset($userfrom->groups)) {
893 $userfrom->groups = array();
894 $users[$userfrom->id]->groups = array();
896 $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
897 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
900 $userfrom->customheaders = array ("Precedence: Bulk");
902 if ($userto->maildigest == 2) {
903 // Subjects only
904 $by = new stdClass();
905 $by->name = fullname($userfrom);
906 $by->date = userdate($post->modified);
907 $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
908 $posttext .= "\n---------------------------------------------------------------------";
910 $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
911 $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>';
913 } else {
914 // The full treatment
915 $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
916 $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
918 // Create an array of postid's for this user to mark as read.
919 if (!$CFG->forum_usermarksread) {
920 $userto->markposts[$post->id] = $post->id;
924 if ($canunsubscribe) {
925 $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>";
926 } else {
927 $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
929 $posthtml .= '<hr size="1" noshade="noshade" /></p>';
931 $posthtml .= '</body>';
933 if (empty($userto->mailformat) || $userto->mailformat != 1) {
934 // This user DOESN'T want to receive HTML
935 $posthtml = '';
938 $attachment = $attachname='';
939 $usetrueaddress = true;
940 //directly email forum digests rather than sending them via messaging
941 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname, $usetrueaddress, $CFG->forum_replytouser);
943 if (!$mailresult) {
944 mtrace("ERROR!");
945 echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
946 add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
947 } else {
948 mtrace("success.");
949 $usermailcount++;
951 // Mark post as read if forum_usermarksread is set off
952 forum_tp_mark_posts_read($userto, $userto->markposts);
956 /// We have finishied all digest emails, update $CFG->digestmailtimelast
957 set_config('digestmailtimelast', $timenow);
960 cron_setup_user();
962 if (!empty($usermailcount)) {
963 mtrace(get_string('digestsentusers', 'forum', $usermailcount));
966 if (!empty($CFG->forum_lastreadclean)) {
967 $timenow = time();
968 if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
969 set_config('forum_lastreadclean', $timenow);
970 mtrace('Removing old forum read tracking info...');
971 forum_tp_clean_read_records();
973 } else {
974 set_config('forum_lastreadclean', time());
978 return true;
982 * Builds and returns the body of the email notification in plain text.
984 * @global object
985 * @global object
986 * @uses CONTEXT_MODULE
987 * @param object $course
988 * @param object $cm
989 * @param object $forum
990 * @param object $discussion
991 * @param object $post
992 * @param object $userfrom
993 * @param object $userto
994 * @param boolean $bare
995 * @return string The email body in plain text format.
997 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
998 global $CFG, $USER;
1000 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
1002 if (!isset($userto->viewfullnames[$forum->id])) {
1003 $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
1004 } else {
1005 $viewfullnames = $userto->viewfullnames[$forum->id];
1008 if (!isset($userto->canpost[$discussion->id])) {
1009 $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
1010 } else {
1011 $canreply = $userto->canpost[$discussion->id];
1014 $by = New stdClass;
1015 $by->name = fullname($userfrom, $viewfullnames);
1016 $by->date = userdate($post->modified, "", $userto->timezone);
1018 $strbynameondate = get_string('bynameondate', 'forum', $by);
1020 $strforums = get_string('forums', 'forum');
1022 $canunsubscribe = ! forum_is_forcesubscribed($forum);
1024 $posttext = '';
1026 if (!$bare) {
1027 $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
1028 $posttext = "$shortname -> $strforums -> ".format_string($forum->name,true);
1030 if ($discussion->name != $forum->name) {
1031 $posttext .= " -> ".format_string($discussion->name,true);
1035 // add absolute file links
1036 $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
1038 $posttext .= "\n---------------------------------------------------------------------\n";
1039 $posttext .= format_string($post->subject,true);
1040 if ($bare) {
1041 $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1043 $posttext .= "\n".$strbynameondate."\n";
1044 $posttext .= "---------------------------------------------------------------------\n";
1045 $posttext .= format_text_email($post->message, $post->messageformat);
1046 $posttext .= "\n\n";
1047 $posttext .= forum_print_attachments($post, $cm, "text");
1049 if (!$bare && $canreply) {
1050 $posttext .= "---------------------------------------------------------------------\n";
1051 $posttext .= get_string("postmailinfo", "forum", $shortname)."\n";
1052 $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1054 if (!$bare && $canunsubscribe) {
1055 $posttext .= "\n---------------------------------------------------------------------\n";
1056 $posttext .= get_string("unsubscribe", "forum");
1057 $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1060 return $posttext;
1064 * Builds and returns the body of the email notification in html format.
1066 * @global object
1067 * @param object $course
1068 * @param object $cm
1069 * @param object $forum
1070 * @param object $discussion
1071 * @param object $post
1072 * @param object $userfrom
1073 * @param object $userto
1074 * @return string The email text in HTML format
1076 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1077 global $CFG;
1079 if ($userto->mailformat != 1) { // Needs to be HTML
1080 return '';
1083 if (!isset($userto->canpost[$discussion->id])) {
1084 $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course);
1085 } else {
1086 $canreply = $userto->canpost[$discussion->id];
1089 $strforums = get_string('forums', 'forum');
1090 $canunsubscribe = ! forum_is_forcesubscribed($forum);
1091 $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
1093 $posthtml = '<head>';
1094 /* foreach ($CFG->stylesheets as $stylesheet) {
1095 //TODO: MDL-21120
1096 $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1098 $posthtml .= '</head>';
1099 $posthtml .= "\n<body id=\"email\">\n\n";
1101 $posthtml .= '<div class="navbar">'.
1102 '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$shortname.'</a> &raquo; '.
1103 '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1104 '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1105 if ($discussion->name == $forum->name) {
1106 $posthtml .= '</div>';
1107 } else {
1108 $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1109 format_string($discussion->name,true).'</a></div>';
1111 $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1113 if ($canunsubscribe) {
1114 $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
1115 <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1116 <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
1119 $posthtml .= '</body>';
1121 return $posthtml;
1127 * @param object $course
1128 * @param object $user
1129 * @param object $mod TODO this is not used in this function, refactor
1130 * @param object $forum
1131 * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1133 function forum_user_outline($course, $user, $mod, $forum) {
1134 global $CFG;
1135 require_once("$CFG->libdir/gradelib.php");
1136 $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1137 if (empty($grades->items[0]->grades)) {
1138 $grade = false;
1139 } else {
1140 $grade = reset($grades->items[0]->grades);
1143 $count = forum_count_user_posts($forum->id, $user->id);
1145 if ($count && $count->postcount > 0) {
1146 $result = new stdClass();
1147 $result->info = get_string("numposts", "forum", $count->postcount);
1148 $result->time = $count->lastpost;
1149 if ($grade) {
1150 $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1152 return $result;
1153 } else if ($grade) {
1154 $result = new stdClass();
1155 $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1157 //datesubmitted == time created. dategraded == time modified or time overridden
1158 //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
1159 //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
1160 if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1161 $result->time = $grade->dategraded;
1162 } else {
1163 $result->time = $grade->datesubmitted;
1166 return $result;
1168 return NULL;
1173 * @global object
1174 * @global object
1175 * @param object $coure
1176 * @param object $user
1177 * @param object $mod
1178 * @param object $forum
1180 function forum_user_complete($course, $user, $mod, $forum) {
1181 global $CFG,$USER, $OUTPUT;
1182 require_once("$CFG->libdir/gradelib.php");
1184 $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1185 if (!empty($grades->items[0]->grades)) {
1186 $grade = reset($grades->items[0]->grades);
1187 echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1188 if ($grade->str_feedback) {
1189 echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1193 if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1195 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1196 print_error('invalidcoursemodule');
1198 $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1200 foreach ($posts as $post) {
1201 if (!isset($discussions[$post->discussion])) {
1202 continue;
1204 $discussion = $discussions[$post->discussion];
1206 forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1208 } else {
1209 echo "<p>".get_string("noposts", "forum")."</p>";
1219 * @global object
1220 * @global object
1221 * @global object
1222 * @param array $courses
1223 * @param array $htmlarray
1225 function forum_print_overview($courses,&$htmlarray) {
1226 global $USER, $CFG, $DB, $SESSION;
1228 if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1229 return array();
1232 if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1233 return;
1237 // get all forum logs in ONE query (much better!)
1238 $params = array();
1239 $sql = "SELECT instance,cmid,l.course,COUNT(l.id) as count FROM {log} l "
1240 ." JOIN {course_modules} cm ON cm.id = cmid "
1241 ." WHERE (";
1242 foreach ($courses as $course) {
1243 $sql .= '(l.course = ? AND l.time > ?) OR ';
1244 $params[] = $course->id;
1245 $params[] = $course->lastaccess;
1247 $sql = substr($sql,0,-3); // take off the last OR
1249 $sql .= ") AND l.module = 'forum' AND action = 'add post' "
1250 ." AND userid != ? GROUP BY cmid,l.course,instance";
1252 $params[] = $USER->id;
1254 if (!$new = $DB->get_records_sql($sql, $params)) {
1255 $new = array(); // avoid warnings
1258 // also get all forum tracking stuff ONCE.
1259 $trackingforums = array();
1260 foreach ($forums as $forum) {
1261 if (forum_tp_can_track_forums($forum)) {
1262 $trackingforums[$forum->id] = $forum;
1266 if (count($trackingforums) > 0) {
1267 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1268 $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1269 ' FROM {forum_posts} p '.
1270 ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1271 ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1272 $params = array($USER->id);
1274 foreach ($trackingforums as $track) {
1275 $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1276 $params[] = $track->id;
1277 if (isset($SESSION->currentgroup[$track->course])) {
1278 $groupid = $SESSION->currentgroup[$track->course];
1279 } else {
1280 // get first groupid
1281 $groupids = groups_get_all_groups($track->course, $USER->id);
1282 if ($groupids) {
1283 reset($groupids);
1284 $groupid = key($groupids);
1285 $SESSION->currentgroup[$track->course] = $groupid;
1286 } else {
1287 $groupid = 0;
1289 unset($groupids);
1291 $params[] = $groupid;
1293 $sql = substr($sql,0,-3); // take off the last OR
1294 $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1295 $params[] = $cutoffdate;
1297 if (!$unread = $DB->get_records_sql($sql, $params)) {
1298 $unread = array();
1300 } else {
1301 $unread = array();
1304 if (empty($unread) and empty($new)) {
1305 return;
1308 $strforum = get_string('modulename','forum');
1309 $strnumunread = get_string('overviewnumunread','forum');
1310 $strnumpostssince = get_string('overviewnumpostssince','forum');
1312 foreach ($forums as $forum) {
1313 $str = '';
1314 $count = 0;
1315 $thisunread = 0;
1316 $showunread = false;
1317 // either we have something from logs, or trackposts, or nothing.
1318 if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1319 $count = $new[$forum->id]->count;
1321 if (array_key_exists($forum->id,$unread)) {
1322 $thisunread = $unread[$forum->id]->count;
1323 $showunread = true;
1325 if ($count > 0 || $thisunread > 0) {
1326 $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1327 $forum->name.'</a></div>';
1328 $str .= '<div class="info"><span class="postsincelogin">';
1329 $str .= $count.' '.$strnumpostssince."</span>";
1330 if (!empty($showunread)) {
1331 $str .= '<div class="unreadposts">'.$thisunread .' '.$strnumunread.'</div>';
1333 $str .= '</div></div>';
1335 if (!empty($str)) {
1336 if (!array_key_exists($forum->course,$htmlarray)) {
1337 $htmlarray[$forum->course] = array();
1339 if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1340 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1342 $htmlarray[$forum->course]['forum'] .= $str;
1348 * Given a course and a date, prints a summary of all the new
1349 * messages posted in the course since that date
1351 * @global object
1352 * @global object
1353 * @global object
1354 * @uses CONTEXT_MODULE
1355 * @uses VISIBLEGROUPS
1356 * @param object $course
1357 * @param bool $viewfullnames capability
1358 * @param int $timestart
1359 * @return bool success
1361 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1362 global $CFG, $USER, $DB, $OUTPUT;
1364 // do not use log table if possible, it may be huge and is expensive to join with other tables
1366 if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1367 d.timestart, d.timeend, d.userid AS duserid,
1368 u.firstname, u.lastname, u.email, u.picture
1369 FROM {forum_posts} p
1370 JOIN {forum_discussions} d ON d.id = p.discussion
1371 JOIN {forum} f ON f.id = d.forum
1372 JOIN {user} u ON u.id = p.userid
1373 WHERE p.created > ? AND f.course = ?
1374 ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1375 return false;
1378 $modinfo = get_fast_modinfo($course);
1380 $groupmodes = array();
1381 $cms = array();
1383 $strftimerecent = get_string('strftimerecent');
1385 $printposts = array();
1386 foreach ($posts as $post) {
1387 if (!isset($modinfo->instances['forum'][$post->forum])) {
1388 // not visible
1389 continue;
1391 $cm = $modinfo->instances['forum'][$post->forum];
1392 if (!$cm->uservisible) {
1393 continue;
1395 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1397 if (!has_capability('mod/forum:viewdiscussion', $context)) {
1398 continue;
1401 if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1402 and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1403 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1404 continue;
1408 $groupmode = groups_get_activity_groupmode($cm, $course);
1410 if ($groupmode) {
1411 if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1412 // oki (Open discussions have groupid -1)
1413 } else {
1414 // separate mode
1415 if (isguestuser()) {
1416 // shortcut
1417 continue;
1420 if (is_null($modinfo->groups)) {
1421 $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1424 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1425 continue;
1430 $printposts[] = $post;
1432 unset($posts);
1434 if (!$printposts) {
1435 return false;
1438 echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1439 echo "\n<ul class='unlist'>\n";
1441 foreach ($printposts as $post) {
1442 $subjectclass = empty($post->parent) ? ' bold' : '';
1444 echo '<li><div class="head">'.
1445 '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1446 '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1447 '</div>';
1448 echo '<div class="info'.$subjectclass.'">';
1449 if (empty($post->parent)) {
1450 echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1451 } else {
1452 echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1454 $post->subject = break_up_long_words(format_string($post->subject, true));
1455 echo $post->subject;
1456 echo "</a>\"</div></li>\n";
1459 echo "</ul>\n";
1461 return true;
1465 * Return grade for given user or all users.
1467 * @global object
1468 * @global object
1469 * @param object $forum
1470 * @param int $userid optional user id, 0 means all users
1471 * @return array array of grades, false if none
1473 function forum_get_user_grades($forum, $userid = 0) {
1474 global $CFG;
1476 require_once($CFG->dirroot.'/rating/lib.php');
1478 $ratingoptions = new stdClass;
1479 $ratingoptions->component = 'mod_forum';
1480 $ratingoptions->ratingarea = 'post';
1482 //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1483 $ratingoptions->modulename = 'forum';
1484 $ratingoptions->moduleid = $forum->id;
1485 $ratingoptions->userid = $userid;
1486 $ratingoptions->aggregationmethod = $forum->assessed;
1487 $ratingoptions->scaleid = $forum->scale;
1488 $ratingoptions->itemtable = 'forum_posts';
1489 $ratingoptions->itemtableusercolumn = 'userid';
1491 $rm = new rating_manager();
1492 return $rm->get_user_grades($ratingoptions);
1496 * Update activity grades
1498 * @category grade
1499 * @param object $forum
1500 * @param int $userid specific user only, 0 means all
1501 * @param boolean $nullifnone return null if grade does not exist
1502 * @return void
1504 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1505 global $CFG, $DB;
1506 require_once($CFG->libdir.'/gradelib.php');
1508 if (!$forum->assessed) {
1509 forum_grade_item_update($forum);
1511 } else if ($grades = forum_get_user_grades($forum, $userid)) {
1512 forum_grade_item_update($forum, $grades);
1514 } else if ($userid and $nullifnone) {
1515 $grade = new stdClass();
1516 $grade->userid = $userid;
1517 $grade->rawgrade = NULL;
1518 forum_grade_item_update($forum, $grade);
1520 } else {
1521 forum_grade_item_update($forum);
1526 * Update all grades in gradebook.
1527 * @global object
1529 function forum_upgrade_grades() {
1530 global $DB;
1532 $sql = "SELECT COUNT('x')
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 $count = $DB->count_records_sql($sql);
1537 $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1538 FROM {forum} f, {course_modules} cm, {modules} m
1539 WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1540 $rs = $DB->get_recordset_sql($sql);
1541 if ($rs->valid()) {
1542 $pbar = new progress_bar('forumupgradegrades', 500, true);
1543 $i=0;
1544 foreach ($rs as $forum) {
1545 $i++;
1546 upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1547 forum_update_grades($forum, 0, false);
1548 $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1551 $rs->close();
1555 * Create/update grade item for given forum
1557 * @category grade
1558 * @uses GRADE_TYPE_NONE
1559 * @uses GRADE_TYPE_VALUE
1560 * @uses GRADE_TYPE_SCALE
1561 * @param stdClass $forum Forum object with extra cmidnumber
1562 * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
1563 * @return int 0 if ok
1565 function forum_grade_item_update($forum, $grades=NULL) {
1566 global $CFG;
1567 if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1568 require_once($CFG->libdir.'/gradelib.php');
1571 $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1573 if (!$forum->assessed or $forum->scale == 0) {
1574 $params['gradetype'] = GRADE_TYPE_NONE;
1576 } else if ($forum->scale > 0) {
1577 $params['gradetype'] = GRADE_TYPE_VALUE;
1578 $params['grademax'] = $forum->scale;
1579 $params['grademin'] = 0;
1581 } else if ($forum->scale < 0) {
1582 $params['gradetype'] = GRADE_TYPE_SCALE;
1583 $params['scaleid'] = -$forum->scale;
1586 if ($grades === 'reset') {
1587 $params['reset'] = true;
1588 $grades = NULL;
1591 return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1595 * Delete grade item for given forum
1597 * @category grade
1598 * @param stdClass $forum Forum object
1599 * @return grade_item
1601 function forum_grade_item_delete($forum) {
1602 global $CFG;
1603 require_once($CFG->libdir.'/gradelib.php');
1605 return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1610 * This function returns if a scale is being used by one forum
1612 * @global object
1613 * @param int $forumid
1614 * @param int $scaleid negative number
1615 * @return bool
1617 function forum_scale_used ($forumid,$scaleid) {
1618 global $DB;
1619 $return = false;
1621 $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1623 if (!empty($rec) && !empty($scaleid)) {
1624 $return = true;
1627 return $return;
1631 * Checks if scale is being used by any instance of forum
1633 * This is used to find out if scale used anywhere
1635 * @global object
1636 * @param $scaleid int
1637 * @return boolean True if the scale is used by any forum
1639 function forum_scale_used_anywhere($scaleid) {
1640 global $DB;
1641 if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1642 return true;
1643 } else {
1644 return false;
1648 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1651 * Gets a post with all info ready for forum_print_post
1652 * Most of these joins are just to get the forum id
1654 * @global object
1655 * @global object
1656 * @param int $postid
1657 * @return mixed array of posts or false
1659 function forum_get_post_full($postid) {
1660 global $CFG, $DB;
1662 return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1663 FROM {forum_posts} p
1664 JOIN {forum_discussions} d ON p.discussion = d.id
1665 LEFT JOIN {user} u ON p.userid = u.id
1666 WHERE p.id = ?", array($postid));
1670 * Gets posts with all info ready for forum_print_post
1671 * We pass forumid in because we always know it so no need to make a
1672 * complicated join to find it out.
1674 * @global object
1675 * @global object
1676 * @return mixed array of posts or false
1678 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1679 global $CFG, $DB;
1681 return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1682 FROM {forum_posts} p
1683 LEFT JOIN {user} u ON p.userid = u.id
1684 WHERE p.discussion = ?
1685 AND p.parent > 0 $sort", array($discussion));
1689 * Gets all posts in discussion including top parent.
1691 * @global object
1692 * @global object
1693 * @global object
1694 * @param int $discussionid
1695 * @param string $sort
1696 * @param bool $tracking does user track the forum?
1697 * @return array of posts
1699 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1700 global $CFG, $DB, $USER;
1702 $tr_sel = "";
1703 $tr_join = "";
1704 $params = array();
1706 if ($tracking) {
1707 $now = time();
1708 $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1709 $tr_sel = ", fr.id AS postread";
1710 $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1711 $params[] = $USER->id;
1714 $params[] = $discussionid;
1715 if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1716 FROM {forum_posts} p
1717 LEFT JOIN {user} u ON p.userid = u.id
1718 $tr_join
1719 WHERE p.discussion = ?
1720 ORDER BY $sort", $params)) {
1721 return array();
1724 foreach ($posts as $pid=>$p) {
1725 if ($tracking) {
1726 if (forum_tp_is_post_old($p)) {
1727 $posts[$pid]->postread = true;
1730 if (!$p->parent) {
1731 continue;
1733 if (!isset($posts[$p->parent])) {
1734 continue; // parent does not exist??
1736 if (!isset($posts[$p->parent]->children)) {
1737 $posts[$p->parent]->children = array();
1739 $posts[$p->parent]->children[$pid] =& $posts[$pid];
1742 return $posts;
1746 * Gets posts with all info ready for forum_print_post
1747 * We pass forumid in because we always know it so no need to make a
1748 * complicated join to find it out.
1750 * @global object
1751 * @global object
1752 * @param int $parent
1753 * @param int $forumid
1754 * @return array
1756 function forum_get_child_posts($parent, $forumid) {
1757 global $CFG, $DB;
1759 return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1760 FROM {forum_posts} p
1761 LEFT JOIN {user} u ON p.userid = u.id
1762 WHERE p.parent = ?
1763 ORDER BY p.created ASC", array($parent));
1767 * An array of forum objects that the user is allowed to read/search through.
1769 * @global object
1770 * @global object
1771 * @global object
1772 * @param int $userid
1773 * @param int $courseid if 0, we look for forums throughout the whole site.
1774 * @return array of forum objects, or false if no matches
1775 * Forum objects have the following attributes:
1776 * id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1777 * viewhiddentimedposts
1779 function forum_get_readable_forums($userid, $courseid=0) {
1781 global $CFG, $DB, $USER;
1782 require_once($CFG->dirroot.'/course/lib.php');
1784 if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1785 print_error('notinstalled', 'forum');
1788 if ($courseid) {
1789 $courses = $DB->get_records('course', array('id' => $courseid));
1790 } else {
1791 // If no course is specified, then the user can see SITE + his courses.
1792 $courses1 = $DB->get_records('course', array('id' => SITEID));
1793 $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
1794 $courses = array_merge($courses1, $courses2);
1796 if (!$courses) {
1797 return array();
1800 $readableforums = array();
1802 foreach ($courses as $course) {
1804 $modinfo = get_fast_modinfo($course);
1805 if (is_null($modinfo->groups)) {
1806 $modinfo->groups = groups_get_user_groups($course->id, $userid);
1809 if (empty($modinfo->instances['forum'])) {
1810 // hmm, no forums?
1811 continue;
1814 $courseforums = $DB->get_records('forum', array('course' => $course->id));
1816 foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1817 if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1818 continue;
1820 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1821 $forum = $courseforums[$forumid];
1822 $forum->context = $context;
1823 $forum->cm = $cm;
1825 if (!has_capability('mod/forum:viewdiscussion', $context)) {
1826 continue;
1829 /// group access
1830 if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1831 if (is_null($modinfo->groups)) {
1832 $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1834 if (isset($modinfo->groups[$cm->groupingid])) {
1835 $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1836 $forum->onlygroups[] = -1;
1837 } else {
1838 $forum->onlygroups = array(-1);
1842 /// hidden timed discussions
1843 $forum->viewhiddentimedposts = true;
1844 if (!empty($CFG->forum_enabletimedposts)) {
1845 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1846 $forum->viewhiddentimedposts = false;
1850 /// qanda access
1851 if ($forum->type == 'qanda'
1852 && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1854 // We need to check whether the user has posted in the qanda forum.
1855 $forum->onlydiscussions = array(); // Holds discussion ids for the discussions
1856 // the user is allowed to see in this forum.
1857 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1858 foreach ($discussionspostedin as $d) {
1859 $forum->onlydiscussions[] = $d->id;
1864 $readableforums[$forum->id] = $forum;
1867 unset($modinfo);
1869 } // End foreach $courses
1871 return $readableforums;
1875 * Returns a list of posts found using an array of search terms.
1877 * @global object
1878 * @global object
1879 * @global object
1880 * @param array $searchterms array of search terms, e.g. word +word -word
1881 * @param int $courseid if 0, we search through the whole site
1882 * @param int $limitfrom
1883 * @param int $limitnum
1884 * @param int &$totalcount
1885 * @param string $extrasql
1886 * @return array|bool Array of posts found or false
1888 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
1889 &$totalcount, $extrasql='') {
1890 global $CFG, $DB, $USER;
1891 require_once($CFG->libdir.'/searchlib.php');
1893 $forums = forum_get_readable_forums($USER->id, $courseid);
1895 if (count($forums) == 0) {
1896 $totalcount = 0;
1897 return false;
1900 $now = round(time(), -2); // db friendly
1902 $fullaccess = array();
1903 $where = array();
1904 $params = array();
1906 foreach ($forums as $forumid => $forum) {
1907 $select = array();
1909 if (!$forum->viewhiddentimedposts) {
1910 $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
1911 $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
1914 $cm = $forum->cm;
1915 $context = $forum->context;
1917 if ($forum->type == 'qanda'
1918 && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1919 if (!empty($forum->onlydiscussions)) {
1920 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
1921 $params = array_merge($params, $discussionid_params);
1922 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
1923 } else {
1924 $select[] = "p.parent = 0";
1928 if (!empty($forum->onlygroups)) {
1929 list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
1930 $params = array_merge($params, $groupid_params);
1931 $select[] = "d.groupid $groupid_sql";
1934 if ($select) {
1935 $selects = implode(" AND ", $select);
1936 $where[] = "(d.forum = :forum{$forumid} AND $selects)";
1937 $params['forum'.$forumid] = $forumid;
1938 } else {
1939 $fullaccess[] = $forumid;
1943 if ($fullaccess) {
1944 list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
1945 $params = array_merge($params, $fullid_params);
1946 $where[] = "(d.forum $fullid_sql)";
1949 $selectdiscussion = "(".implode(" OR ", $where).")";
1951 $messagesearch = '';
1952 $searchstring = '';
1954 // Need to concat these back together for parser to work.
1955 foreach($searchterms as $searchterm){
1956 if ($searchstring != '') {
1957 $searchstring .= ' ';
1959 $searchstring .= $searchterm;
1962 // We need to allow quoted strings for the search. The quotes *should* be stripped
1963 // by the parser, but this should be examined carefully for security implications.
1964 $searchstring = str_replace("\\\"","\"",$searchstring);
1965 $parser = new search_parser();
1966 $lexer = new search_lexer($parser);
1968 if ($lexer->parse($searchstring)) {
1969 $parsearray = $parser->get_parsed_array();
1970 // Experimental feature under 1.8! MDL-8830
1971 // Use alternative text searches if defined
1972 // This feature only works under mysql until properly implemented for other DBs
1973 // Requires manual creation of text index for forum_posts before enabling it:
1974 // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
1975 // Experimental feature under 1.8! MDL-8830
1976 if (!empty($CFG->forum_usetextsearches)) {
1977 list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
1978 'p.userid', 'u.id', 'u.firstname',
1979 'u.lastname', 'p.modified', 'd.forum');
1980 } else {
1981 list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
1982 'p.userid', 'u.id', 'u.firstname',
1983 'u.lastname', 'p.modified', 'd.forum');
1985 $params = array_merge($params, $msparams);
1988 $fromsql = "{forum_posts} p,
1989 {forum_discussions} d,
1990 {user} u";
1992 $selectsql = " $messagesearch
1993 AND p.discussion = d.id
1994 AND p.userid = u.id
1995 AND $selectdiscussion
1996 $extrasql";
1998 $countsql = "SELECT COUNT(*)
1999 FROM $fromsql
2000 WHERE $selectsql";
2002 $searchsql = "SELECT p.*,
2003 d.forum,
2004 u.firstname,
2005 u.lastname,
2006 u.email,
2007 u.picture,
2008 u.imagealt,
2009 u.email
2010 FROM $fromsql
2011 WHERE $selectsql
2012 ORDER BY p.modified DESC";
2014 $totalcount = $DB->count_records_sql($countsql, $params);
2016 return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2020 * Returns a list of ratings for a particular post - sorted.
2022 * TODO: Check if this function is actually used anywhere.
2023 * Up until the fix for MDL-27471 this function wasn't even returning.
2025 * @param stdClass $context
2026 * @param int $postid
2027 * @param string $sort
2028 * @return array Array of ratings or false
2030 function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
2031 $options = new stdClass;
2032 $options->context = $context;
2033 $options->component = 'mod_forum';
2034 $options->ratingarea = 'post';
2035 $options->itemid = $postid;
2036 $options->sort = "ORDER BY $sort";
2038 $rm = new rating_manager();
2039 return $rm->get_all_ratings_for_item($options);
2043 * Returns a list of all new posts that have not been mailed yet
2045 * @param int $starttime posts created after this time
2046 * @param int $endtime posts created before this
2047 * @param int $now used for timed discussions only
2048 * @return array
2050 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2051 global $CFG, $DB;
2053 $params = array($starttime, $endtime);
2054 if (!empty($CFG->forum_enabletimedposts)) {
2055 if (empty($now)) {
2056 $now = time();
2058 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2059 $params[] = $now;
2060 $params[] = $now;
2061 } else {
2062 $timedsql = "";
2065 return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2066 FROM {forum_posts} p
2067 JOIN {forum_discussions} d ON d.id = p.discussion
2068 WHERE p.mailed = 0
2069 AND p.created >= ?
2070 AND (p.created < ? OR p.mailnow = 1)
2071 $timedsql
2072 ORDER BY p.modified ASC", $params);
2076 * Marks posts before a certain time as being mailed already
2078 * @global object
2079 * @global object
2080 * @param int $endtime
2081 * @param int $now Defaults to time()
2082 * @return bool
2084 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2085 global $CFG, $DB;
2086 if (empty($now)) {
2087 $now = time();
2090 if (empty($CFG->forum_enabletimedposts)) {
2091 return $DB->execute("UPDATE {forum_posts}
2092 SET mailed = '1'
2093 WHERE (created < ? OR mailnow = 1)
2094 AND mailed = 0", array($endtime));
2096 } else {
2097 return $DB->execute("UPDATE {forum_posts}
2098 SET mailed = '1'
2099 WHERE discussion NOT IN (SELECT d.id
2100 FROM {forum_discussions} d
2101 WHERE d.timestart > ?)
2102 AND (created < ? OR mailnow = 1)
2103 AND mailed = 0", array($now, $endtime));
2108 * Get all the posts for a user in a forum suitable for forum_print_post
2110 * @global object
2111 * @global object
2112 * @uses CONTEXT_MODULE
2113 * @return array
2115 function forum_get_user_posts($forumid, $userid) {
2116 global $CFG, $DB;
2118 $timedsql = "";
2119 $params = array($forumid, $userid);
2121 if (!empty($CFG->forum_enabletimedposts)) {
2122 $cm = get_coursemodule_from_instance('forum', $forumid);
2123 if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2124 $now = time();
2125 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2126 $params[] = $now;
2127 $params[] = $now;
2131 return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2132 FROM {forum} f
2133 JOIN {forum_discussions} d ON d.forum = f.id
2134 JOIN {forum_posts} p ON p.discussion = d.id
2135 JOIN {user} u ON u.id = p.userid
2136 WHERE f.id = ?
2137 AND p.userid = ?
2138 $timedsql
2139 ORDER BY p.modified ASC", $params);
2143 * Get all the discussions user participated in
2145 * @global object
2146 * @global object
2147 * @uses CONTEXT_MODULE
2148 * @param int $forumid
2149 * @param int $userid
2150 * @return array Array or false
2152 function forum_get_user_involved_discussions($forumid, $userid) {
2153 global $CFG, $DB;
2155 $timedsql = "";
2156 $params = array($forumid, $userid);
2157 if (!empty($CFG->forum_enabletimedposts)) {
2158 $cm = get_coursemodule_from_instance('forum', $forumid);
2159 if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2160 $now = time();
2161 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2162 $params[] = $now;
2163 $params[] = $now;
2167 return $DB->get_records_sql("SELECT DISTINCT d.*
2168 FROM {forum} f
2169 JOIN {forum_discussions} d ON d.forum = f.id
2170 JOIN {forum_posts} p ON p.discussion = d.id
2171 WHERE f.id = ?
2172 AND p.userid = ?
2173 $timedsql", $params);
2177 * Get all the posts for a user in a forum suitable for forum_print_post
2179 * @global object
2180 * @global object
2181 * @param int $forumid
2182 * @param int $userid
2183 * @return array of counts or false
2185 function forum_count_user_posts($forumid, $userid) {
2186 global $CFG, $DB;
2188 $timedsql = "";
2189 $params = array($forumid, $userid);
2190 if (!empty($CFG->forum_enabletimedposts)) {
2191 $cm = get_coursemodule_from_instance('forum', $forumid);
2192 if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2193 $now = time();
2194 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2195 $params[] = $now;
2196 $params[] = $now;
2200 return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2201 FROM {forum} f
2202 JOIN {forum_discussions} d ON d.forum = f.id
2203 JOIN {forum_posts} p ON p.discussion = d.id
2204 JOIN {user} u ON u.id = p.userid
2205 WHERE f.id = ?
2206 AND p.userid = ?
2207 $timedsql", $params);
2211 * Given a log entry, return the forum post details for it.
2213 * @global object
2214 * @global object
2215 * @param object $log
2216 * @return array|null
2218 function forum_get_post_from_log($log) {
2219 global $CFG, $DB;
2221 if ($log->action == "add post") {
2223 return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2224 u.firstname, u.lastname, u.email, u.picture
2225 FROM {forum_discussions} d,
2226 {forum_posts} p,
2227 {forum} f,
2228 {user} u
2229 WHERE p.id = ?
2230 AND d.id = p.discussion
2231 AND p.userid = u.id
2232 AND u.deleted <> '1'
2233 AND f.id = d.forum", array($log->info));
2236 } else if ($log->action == "add discussion") {
2238 return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
2239 u.firstname, u.lastname, u.email, u.picture
2240 FROM {forum_discussions} d,
2241 {forum_posts} p,
2242 {forum} f,
2243 {user} u
2244 WHERE d.id = ?
2245 AND d.firstpost = p.id
2246 AND p.userid = u.id
2247 AND u.deleted <> '1'
2248 AND f.id = d.forum", array($log->info));
2250 return NULL;
2254 * Given a discussion id, return the first post from the discussion
2256 * @global object
2257 * @global object
2258 * @param int $dicsussionid
2259 * @return array
2261 function forum_get_firstpost_from_discussion($discussionid) {
2262 global $CFG, $DB;
2264 return $DB->get_record_sql("SELECT p.*
2265 FROM {forum_discussions} d,
2266 {forum_posts} p
2267 WHERE d.id = ?
2268 AND d.firstpost = p.id ", array($discussionid));
2272 * Returns an array of counts of replies to each discussion
2274 * @global object
2275 * @global object
2276 * @param int $forumid
2277 * @param string $forumsort
2278 * @param int $limit
2279 * @param int $page
2280 * @param int $perpage
2281 * @return array
2283 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2284 global $CFG, $DB;
2286 if ($limit > 0) {
2287 $limitfrom = 0;
2288 $limitnum = $limit;
2289 } else if ($page != -1) {
2290 $limitfrom = $page*$perpage;
2291 $limitnum = $perpage;
2292 } else {
2293 $limitfrom = 0;
2294 $limitnum = 0;
2297 if ($forumsort == "") {
2298 $orderby = "";
2299 $groupby = "";
2301 } else {
2302 $orderby = "ORDER BY $forumsort";
2303 $groupby = ", ".strtolower($forumsort);
2304 $groupby = str_replace('desc', '', $groupby);
2305 $groupby = str_replace('asc', '', $groupby);
2308 if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2309 $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2310 FROM {forum_posts} p
2311 JOIN {forum_discussions} d ON p.discussion = d.id
2312 WHERE p.parent > 0 AND d.forum = ?
2313 GROUP BY p.discussion";
2314 return $DB->get_records_sql($sql, array($forumid));
2316 } else {
2317 $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2318 FROM {forum_posts} p
2319 JOIN {forum_discussions} d ON p.discussion = d.id
2320 WHERE d.forum = ?
2321 GROUP BY p.discussion $groupby
2322 $orderby";
2323 return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2328 * @global object
2329 * @global object
2330 * @global object
2331 * @staticvar array $cache
2332 * @param object $forum
2333 * @param object $cm
2334 * @param object $course
2335 * @return mixed
2337 function forum_count_discussions($forum, $cm, $course) {
2338 global $CFG, $DB, $USER;
2340 static $cache = array();
2342 $now = round(time(), -2); // db cache friendliness
2344 $params = array($course->id);
2346 if (!isset($cache[$course->id])) {
2347 if (!empty($CFG->forum_enabletimedposts)) {
2348 $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2349 $params[] = $now;
2350 $params[] = $now;
2351 } else {
2352 $timedsql = "";
2355 $sql = "SELECT f.id, COUNT(d.id) as dcount
2356 FROM {forum} f
2357 JOIN {forum_discussions} d ON d.forum = f.id
2358 WHERE f.course = ?
2359 $timedsql
2360 GROUP BY f.id";
2362 if ($counts = $DB->get_records_sql($sql, $params)) {
2363 foreach ($counts as $count) {
2364 $counts[$count->id] = $count->dcount;
2366 $cache[$course->id] = $counts;
2367 } else {
2368 $cache[$course->id] = array();
2372 if (empty($cache[$course->id][$forum->id])) {
2373 return 0;
2376 $groupmode = groups_get_activity_groupmode($cm, $course);
2378 if ($groupmode != SEPARATEGROUPS) {
2379 return $cache[$course->id][$forum->id];
2382 if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
2383 return $cache[$course->id][$forum->id];
2386 require_once($CFG->dirroot.'/course/lib.php');
2388 $modinfo = get_fast_modinfo($course);
2389 if (is_null($modinfo->groups)) {
2390 $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2393 if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2394 $mygroups = $modinfo->groups[$cm->groupingid];
2395 } else {
2396 $mygroups = false; // Will be set below
2399 // add all groups posts
2400 if (empty($mygroups)) {
2401 $mygroups = array(-1=>-1);
2402 } else {
2403 $mygroups[-1] = -1;
2406 list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2407 $params[] = $forum->id;
2409 if (!empty($CFG->forum_enabletimedposts)) {
2410 $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2411 $params[] = $now;
2412 $params[] = $now;
2413 } else {
2414 $timedsql = "";
2417 $sql = "SELECT COUNT(d.id)
2418 FROM {forum_discussions} d
2419 WHERE d.groupid $mygroups_sql AND d.forum = ?
2420 $timedsql";
2422 return $DB->get_field_sql($sql, $params);
2426 * How many posts by other users are unrated by a given user in the given discussion?
2428 * TODO: Is this function still used anywhere?
2430 * @param int $discussionid
2431 * @param int $userid
2432 * @return mixed
2434 function forum_count_unrated_posts($discussionid, $userid) {
2435 global $CFG, $DB;
2437 $sql = "SELECT COUNT(*) as num
2438 FROM {forum_posts}
2439 WHERE parent > 0
2440 AND discussion = :discussionid
2441 AND userid <> :userid";
2442 $params = array('discussionid' => $discussionid, 'userid' => $userid);
2443 $posts = $DB->get_record_sql($sql, $params);
2444 if ($posts) {
2445 $sql = "SELECT count(*) as num
2446 FROM {forum_posts} p,
2447 {rating} r
2448 WHERE p.discussion = :discussionid AND
2449 p.id = r.itemid AND
2450 r.userid = userid AND
2451 r.component = 'mod_forum' AND
2452 r.ratingarea = 'post'";
2453 $rated = $DB->get_record_sql($sql, $params);
2454 if ($rated) {
2455 if ($posts->num > $rated->num) {
2456 return $posts->num - $rated->num;
2457 } else {
2458 return 0; // Just in case there was a counting error
2460 } else {
2461 return $posts->num;
2463 } else {
2464 return 0;
2469 * Get all discussions in a forum
2471 * @global object
2472 * @global object
2473 * @global object
2474 * @uses CONTEXT_MODULE
2475 * @uses VISIBLEGROUPS
2476 * @param object $cm
2477 * @param string $forumsort
2478 * @param bool $fullpost
2479 * @param int $unused
2480 * @param int $limit
2481 * @param bool $userlastmodified
2482 * @param int $page
2483 * @param int $perpage
2484 * @return array
2486 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2487 global $CFG, $DB, $USER;
2489 $timelimit = '';
2491 $now = round(time(), -2);
2492 $params = array($cm->instance);
2494 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2496 if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2497 return array();
2500 if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2502 if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2503 $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2504 $params[] = $now;
2505 $params[] = $now;
2506 if (isloggedin()) {
2507 $timelimit .= " OR d.userid = ?";
2508 $params[] = $USER->id;
2510 $timelimit .= ")";
2514 if ($limit > 0) {
2515 $limitfrom = 0;
2516 $limitnum = $limit;
2517 } else if ($page != -1) {
2518 $limitfrom = $page*$perpage;
2519 $limitnum = $perpage;
2520 } else {
2521 $limitfrom = 0;
2522 $limitnum = 0;
2525 $groupmode = groups_get_activity_groupmode($cm);
2526 $currentgroup = groups_get_activity_group($cm);
2528 if ($groupmode) {
2529 if (empty($modcontext)) {
2530 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2533 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2534 if ($currentgroup) {
2535 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2536 $params[] = $currentgroup;
2537 } else {
2538 $groupselect = "";
2541 } else {
2542 //seprate groups without access all
2543 if ($currentgroup) {
2544 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2545 $params[] = $currentgroup;
2546 } else {
2547 $groupselect = "AND d.groupid = -1";
2550 } else {
2551 $groupselect = "";
2555 if (empty($forumsort)) {
2556 $forumsort = "d.timemodified DESC";
2558 if (empty($fullpost)) {
2559 $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2560 } else {
2561 $postdata = "p.*";
2564 if (empty($userlastmodified)) { // We don't need to know this
2565 $umfields = "";
2566 $umtable = "";
2567 } else {
2568 $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
2569 $umtable = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2572 $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2573 u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
2574 FROM {forum_discussions} d
2575 JOIN {forum_posts} p ON p.discussion = d.id
2576 JOIN {user} u ON p.userid = u.id
2577 $umtable
2578 WHERE d.forum = ? AND p.parent = 0
2579 $timelimit $groupselect
2580 ORDER BY $forumsort";
2581 return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2586 * @global object
2587 * @global object
2588 * @global object
2589 * @uses CONTEXT_MODULE
2590 * @uses VISIBLEGROUPS
2591 * @param object $cm
2592 * @return array
2594 function forum_get_discussions_unread($cm) {
2595 global $CFG, $DB, $USER;
2597 $now = round(time(), -2);
2598 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2600 $params = array();
2601 $groupmode = groups_get_activity_groupmode($cm);
2602 $currentgroup = groups_get_activity_group($cm);
2604 if ($groupmode) {
2605 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2607 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2608 if ($currentgroup) {
2609 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2610 $params['currentgroup'] = $currentgroup;
2611 } else {
2612 $groupselect = "";
2615 } else {
2616 //separate groups without access all
2617 if ($currentgroup) {
2618 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2619 $params['currentgroup'] = $currentgroup;
2620 } else {
2621 $groupselect = "AND d.groupid = -1";
2624 } else {
2625 $groupselect = "";
2628 if (!empty($CFG->forum_enabletimedposts)) {
2629 $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2630 $params['now1'] = $now;
2631 $params['now2'] = $now;
2632 } else {
2633 $timedsql = "";
2636 $sql = "SELECT d.id, COUNT(p.id) AS unread
2637 FROM {forum_discussions} d
2638 JOIN {forum_posts} p ON p.discussion = d.id
2639 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2640 WHERE d.forum = {$cm->instance}
2641 AND p.modified >= :cutoffdate AND r.id is NULL
2642 $groupselect
2643 $timedsql
2644 GROUP BY d.id";
2645 $params['cutoffdate'] = $cutoffdate;
2647 if ($unreads = $DB->get_records_sql($sql, $params)) {
2648 foreach ($unreads as $unread) {
2649 $unreads[$unread->id] = $unread->unread;
2651 return $unreads;
2652 } else {
2653 return array();
2658 * @global object
2659 * @global object
2660 * @global object
2661 * @uses CONEXT_MODULE
2662 * @uses VISIBLEGROUPS
2663 * @param object $cm
2664 * @return array
2666 function forum_get_discussions_count($cm) {
2667 global $CFG, $DB, $USER;
2669 $now = round(time(), -2);
2670 $params = array($cm->instance);
2671 $groupmode = groups_get_activity_groupmode($cm);
2672 $currentgroup = groups_get_activity_group($cm);
2674 if ($groupmode) {
2675 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2677 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2678 if ($currentgroup) {
2679 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2680 $params[] = $currentgroup;
2681 } else {
2682 $groupselect = "";
2685 } else {
2686 //seprate groups without access all
2687 if ($currentgroup) {
2688 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2689 $params[] = $currentgroup;
2690 } else {
2691 $groupselect = "AND d.groupid = -1";
2694 } else {
2695 $groupselect = "";
2698 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2700 $timelimit = "";
2702 if (!empty($CFG->forum_enabletimedposts)) {
2704 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2706 if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2707 $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2708 $params[] = $now;
2709 $params[] = $now;
2710 if (isloggedin()) {
2711 $timelimit .= " OR d.userid = ?";
2712 $params[] = $USER->id;
2714 $timelimit .= ")";
2718 $sql = "SELECT COUNT(d.id)
2719 FROM {forum_discussions} d
2720 JOIN {forum_posts} p ON p.discussion = d.id
2721 WHERE d.forum = ? AND p.parent = 0
2722 $groupselect $timelimit";
2724 return $DB->get_field_sql($sql, $params);
2729 * Get all discussions started by a particular user in a course (or group)
2730 * This function no longer used ...
2732 * @todo Remove this function if no longer used
2733 * @global object
2734 * @global object
2735 * @param int $courseid
2736 * @param int $userid
2737 * @param int $groupid
2738 * @return array
2740 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2741 global $CFG, $DB;
2742 $params = array($courseid, $userid);
2743 if ($groupid) {
2744 $groupselect = " AND d.groupid = ? ";
2745 $params[] = $groupid;
2746 } else {
2747 $groupselect = "";
2750 return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
2751 f.type as forumtype, f.name as forumname, f.id as forumid
2752 FROM {forum_discussions} d,
2753 {forum_posts} p,
2754 {user} u,
2755 {forum} f
2756 WHERE d.course = ?
2757 AND p.discussion = d.id
2758 AND p.parent = 0
2759 AND p.userid = u.id
2760 AND u.id = ?
2761 AND d.forum = f.id $groupselect
2762 ORDER BY p.created DESC", $params);
2766 * Get the list of potential subscribers to a forum.
2768 * @param object $forumcontext the forum context.
2769 * @param integer $groupid the id of a group, or 0 for all groups.
2770 * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2771 * @param string $sort sort order. As for get_users_by_capability.
2772 * @return array list of users.
2774 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
2775 global $DB;
2777 // only active enrolled users or everybody on the frontpage
2778 list($esql, $params) = get_enrolled_sql($forumcontext, '', $groupid, true);
2780 $sql = "SELECT $fields
2781 FROM {user} u
2782 JOIN ($esql) je ON je.id = u.id";
2783 if ($sort) {
2784 $sql = "$sql ORDER BY $sort";
2785 } else {
2786 $sql = "$sql ORDER BY u.lastname ASC, u.firstname ASC";
2789 return $DB->get_records_sql($sql, $params);
2793 * Returns list of user objects that are subscribed to this forum
2795 * @global object
2796 * @global object
2797 * @param object $course the course
2798 * @param forum $forum the forum
2799 * @param integer $groupid group id, or 0 for all.
2800 * @param object $context the forum context, to save re-fetching it where possible.
2801 * @param string $fields requested user fields (with "u." table prefix)
2802 * @return array list of users.
2804 function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
2805 global $CFG, $DB;
2807 if (empty($fields)) {
2808 $fields ="u.id,
2809 u.username,
2810 u.firstname,
2811 u.lastname,
2812 u.maildisplay,
2813 u.mailformat,
2814 u.maildigest,
2815 u.imagealt,
2816 u.email,
2817 u.emailstop,
2818 u.city,
2819 u.country,
2820 u.lastaccess,
2821 u.lastlogin,
2822 u.picture,
2823 u.timezone,
2824 u.theme,
2825 u.lang,
2826 u.trackforums,
2827 u.mnethostid";
2830 if (empty($context)) {
2831 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2832 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
2835 if (forum_is_forcesubscribed($forum)) {
2836 $results = forum_get_potential_subscribers($context, $groupid, $fields, "u.email ASC");
2838 } else {
2839 // only active enrolled users or everybody on the frontpage
2840 list($esql, $params) = get_enrolled_sql($context, '', $groupid, true);
2841 $params['forumid'] = $forum->id;
2842 $results = $DB->get_records_sql("SELECT $fields
2843 FROM {user} u
2844 JOIN ($esql) je ON je.id = u.id
2845 JOIN {forum_subscriptions} s ON s.userid = u.id
2846 WHERE s.forum = :forumid
2847 ORDER BY u.email ASC", $params);
2850 // Guest user should never be subscribed to a forum.
2851 unset($results[$CFG->siteguest]);
2853 return $results;
2858 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2862 * @global object
2863 * @global object
2864 * @param int $courseid
2865 * @param string $type
2867 function forum_get_course_forum($courseid, $type) {
2868 // How to set up special 1-per-course forums
2869 global $CFG, $DB, $OUTPUT;
2871 if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
2872 // There should always only be ONE, but with the right combination of
2873 // errors there might be more. In this case, just return the oldest one (lowest ID).
2874 foreach ($forums as $forum) {
2875 return $forum; // ie the first one
2879 // Doesn't exist, so create one now.
2880 $forum = new stdClass();
2881 $forum->course = $courseid;
2882 $forum->type = "$type";
2883 switch ($forum->type) {
2884 case "news":
2885 $forum->name = get_string("namenews", "forum");
2886 $forum->intro = get_string("intronews", "forum");
2887 $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
2888 $forum->assessed = 0;
2889 if ($courseid == SITEID) {
2890 $forum->name = get_string("sitenews");
2891 $forum->forcesubscribe = 0;
2893 break;
2894 case "social":
2895 $forum->name = get_string("namesocial", "forum");
2896 $forum->intro = get_string("introsocial", "forum");
2897 $forum->assessed = 0;
2898 $forum->forcesubscribe = 0;
2899 break;
2900 case "blog":
2901 $forum->name = get_string('blogforum', 'forum');
2902 $forum->intro = get_string('introblog', 'forum');
2903 $forum->assessed = 0;
2904 $forum->forcesubscribe = 0;
2905 break;
2906 default:
2907 echo $OUTPUT->notification("That forum type doesn't exist!");
2908 return false;
2909 break;
2912 $forum->timemodified = time();
2913 $forum->id = $DB->insert_record("forum", $forum);
2915 if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
2916 echo $OUTPUT->notification("Could not find forum module!!");
2917 return false;
2919 $mod = new stdClass();
2920 $mod->course = $courseid;
2921 $mod->module = $module->id;
2922 $mod->instance = $forum->id;
2923 $mod->section = 0;
2924 if (! $mod->coursemodule = add_course_module($mod) ) { // assumes course/lib.php is loaded
2925 echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
2926 return false;
2928 if (! $sectionid = add_mod_to_section($mod) ) { // assumes course/lib.php is loaded
2929 echo $OUTPUT->notification("Could not add the new course module to that section");
2930 return false;
2932 $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule));
2934 include_once("$CFG->dirroot/course/lib.php");
2935 rebuild_course_cache($courseid);
2937 return $DB->get_record("forum", array("id" => "$forum->id"));
2942 * Given the data about a posting, builds up the HTML to display it and
2943 * returns the HTML in a string. This is designed for sending via HTML email.
2945 * @global object
2946 * @param object $course
2947 * @param object $cm
2948 * @param object $forum
2949 * @param object $discussion
2950 * @param object $post
2951 * @param object $userform
2952 * @param object $userto
2953 * @param bool $ownpost
2954 * @param bool $reply
2955 * @param bool $link
2956 * @param bool $rate
2957 * @param string $footer
2958 * @return string
2960 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
2961 $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
2963 global $CFG, $OUTPUT;
2965 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2967 if (!isset($userto->viewfullnames[$forum->id])) {
2968 $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
2969 } else {
2970 $viewfullnames = $userto->viewfullnames[$forum->id];
2973 // add absolute file links
2974 $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
2976 // format the post body
2977 $options = new stdClass();
2978 $options->para = true;
2979 $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
2981 $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
2983 $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
2984 $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
2985 $output .= '</td>';
2987 if ($post->parent) {
2988 $output .= '<td class="topic">';
2989 } else {
2990 $output .= '<td class="topic starter">';
2992 $output .= '<div class="subject">'.format_string($post->subject).'</div>';
2994 $fullname = fullname($userfrom, $viewfullnames);
2995 $by = new stdClass();
2996 $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
2997 $by->date = userdate($post->modified, '', $userto->timezone);
2998 $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3000 $output .= '</td></tr>';
3002 $output .= '<tr><td class="left side" valign="top">';
3004 if (isset($userfrom->groups)) {
3005 $groups = $userfrom->groups[$forum->id];
3006 } else {
3007 $groups = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3010 if ($groups) {
3011 $output .= print_group_picture($groups, $course->id, false, true, true);
3012 } else {
3013 $output .= '&nbsp;';
3016 $output .= '</td><td class="content">';
3018 $attachments = forum_print_attachments($post, $cm, 'html');
3019 if ($attachments !== '') {
3020 $output .= '<div class="attachments">';
3021 $output .= $attachments;
3022 $output .= '</div>';
3025 $output .= $formattedtext;
3027 // Commands
3028 $commands = array();
3030 if ($post->parent) {
3031 $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3032 $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3035 if ($reply) {
3036 $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3037 get_string('reply', 'forum').'</a>';
3040 $output .= '<div class="commands">';
3041 $output .= implode(' | ', $commands);
3042 $output .= '</div>';
3044 // Context link to post if required
3045 if ($link) {
3046 $output .= '<div class="link">';
3047 $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3048 get_string('postincontext', 'forum').'</a>';
3049 $output .= '</div>';
3052 if ($footer) {
3053 $output .= '<div class="footer">'.$footer.'</div>';
3055 $output .= '</td></tr></table>'."\n\n";
3057 return $output;
3061 * Print a forum post
3063 * @global object
3064 * @global object
3065 * @uses FORUM_MODE_THREADED
3066 * @uses PORTFOLIO_FORMAT_PLAINHTML
3067 * @uses PORTFOLIO_FORMAT_FILE
3068 * @uses PORTFOLIO_FORMAT_RICHHTML
3069 * @uses PORTFOLIO_ADD_TEXT_LINK
3070 * @uses CONTEXT_MODULE
3071 * @param object $post The post to print.
3072 * @param object $discussion
3073 * @param object $forum
3074 * @param object $cm
3075 * @param object $course
3076 * @param boolean $ownpost Whether this post belongs to the current user.
3077 * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3078 * @param boolean $link Just print a shortened version of the post as a link to the full post.
3079 * @param string $footer Extra stuff to print after the message.
3080 * @param string $highlight Space-separated list of terms to highlight.
3081 * @param int $post_read true, false or -99. If we already know whether this user
3082 * has read this post, pass that in, otherwise, pass in -99, and this
3083 * function will work it out.
3084 * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3085 * the current user can't see this post, if this argument is true
3086 * (the default) then print a dummy 'you can't see this post' post.
3087 * If false, don't output anything at all.
3088 * @param bool|null $istracked
3089 * @return void
3091 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3092 $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3093 global $USER, $CFG, $OUTPUT;
3095 require_once($CFG->libdir . '/filelib.php');
3097 // String cache
3098 static $str;
3100 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3102 $post->course = $course->id;
3103 $post->forum = $forum->id;
3104 $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3106 // caching
3107 if (!isset($cm->cache)) {
3108 $cm->cache = new stdClass;
3111 if (!isset($cm->cache->caps)) {
3112 $cm->cache->caps = array();
3113 $cm->cache->caps['mod/forum:viewdiscussion'] = has_capability('mod/forum:viewdiscussion', $modcontext);
3114 $cm->cache->caps['moodle/site:viewfullnames'] = has_capability('moodle/site:viewfullnames', $modcontext);
3115 $cm->cache->caps['mod/forum:editanypost'] = has_capability('mod/forum:editanypost', $modcontext);
3116 $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3117 $cm->cache->caps['mod/forum:deleteownpost'] = has_capability('mod/forum:deleteownpost', $modcontext);
3118 $cm->cache->caps['mod/forum:deleteanypost'] = has_capability('mod/forum:deleteanypost', $modcontext);
3119 $cm->cache->caps['mod/forum:viewanyrating'] = has_capability('mod/forum:viewanyrating', $modcontext);
3120 $cm->cache->caps['mod/forum:exportpost'] = has_capability('mod/forum:exportpost', $modcontext);
3121 $cm->cache->caps['mod/forum:exportownpost'] = has_capability('mod/forum:exportownpost', $modcontext);
3124 if (!isset($cm->uservisible)) {
3125 $cm->uservisible = coursemodule_visible_for_user($cm);
3128 if ($istracked && is_null($postisread)) {
3129 $postisread = forum_tp_is_post_read($USER->id, $post);
3132 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3133 $output = '';
3134 if (!$dummyifcantsee) {
3135 if ($return) {
3136 return $output;
3138 echo $output;
3139 return;
3141 $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3142 $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'));
3143 $output .= html_writer::start_tag('div', array('class'=>'row header'));
3144 $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3145 if ($post->parent) {
3146 $output .= html_writer::start_tag('div', array('class'=>'topic'));
3147 } else {
3148 $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3150 $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class'=>'subject')); // Subject
3151 $output .= html_writer::tag('div', get_string('forumauthorhidden','forum'), array('class'=>'author')); // author
3152 $output .= html_writer::end_tag('div');
3153 $output .= html_writer::end_tag('div'); // row
3154 $output .= html_writer::start_tag('div', array('class'=>'row'));
3155 $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3156 $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3157 $output .= html_writer::end_tag('div'); // row
3158 $output .= html_writer::end_tag('div'); // forumpost
3160 if ($return) {
3161 return $output;
3163 echo $output;
3164 return;
3167 if (empty($str)) {
3168 $str = new stdClass;
3169 $str->edit = get_string('edit', 'forum');
3170 $str->delete = get_string('delete', 'forum');
3171 $str->reply = get_string('reply', 'forum');
3172 $str->parent = get_string('parent', 'forum');
3173 $str->pruneheading = get_string('pruneheading', 'forum');
3174 $str->prune = get_string('prune', 'forum');
3175 $str->displaymode = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3176 $str->markread = get_string('markread', 'forum');
3177 $str->markunread = get_string('markunread', 'forum');
3180 $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3182 // Build an object that represents the posting user
3183 $postuser = new stdClass;
3184 $postuser->id = $post->userid;
3185 $postuser->firstname = $post->firstname;
3186 $postuser->lastname = $post->lastname;
3187 $postuser->imagealt = $post->imagealt;
3188 $postuser->picture = $post->picture;
3189 $postuser->email = $post->email;
3190 // Some handy things for later on
3191 $postuser->fullname = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3192 $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3194 // Prepare the groups the posting user belongs to
3195 if (isset($cm->cache->usersgroups)) {
3196 $groups = array();
3197 if (isset($cm->cache->usersgroups[$post->userid])) {
3198 foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3199 $groups[$gid] = $cm->cache->groups[$gid];
3202 } else {
3203 $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3206 // Prepare the attachements for the post, files then images
3207 list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3209 // Determine if we need to shorten this post
3210 $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3213 // Prepare an array of commands
3214 $commands = array();
3216 // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3217 // Don't display the mark read / unread controls in this case.
3218 if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3219 $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3220 $text = $str->markunread;
3221 if (!$postisread) {
3222 $url->param('mark', 'read');
3223 $text = $str->markread;
3225 if ($str->displaymode == FORUM_MODE_THREADED) {
3226 $url->param('parent', $post->parent);
3227 } else {
3228 $url->set_anchor('p'.$post->id);
3230 $commands[] = array('url'=>$url, 'text'=>$text);
3233 // Zoom in to the parent specifically
3234 if ($post->parent) {
3235 $url = new moodle_url($discussionlink);
3236 if ($str->displaymode == FORUM_MODE_THREADED) {
3237 $url->param('parent', $post->parent);
3238 } else {
3239 $url->set_anchor('p'.$post->parent);
3241 $commands[] = array('url'=>$url, 'text'=>$str->parent);
3244 // Hack for allow to edit news posts those are not displayed yet until they are displayed
3245 $age = time() - $post->created;
3246 if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3247 $age = 0;
3249 if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3250 $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3253 if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3254 $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3257 if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3258 $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3261 if ($reply) {
3262 $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('reply'=>$post->id)), 'text'=>$str->reply);
3265 if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
3266 $p = array('postid' => $post->id);
3267 require_once($CFG->libdir.'/portfoliolib.php');
3268 $button = new portfolio_add_button();
3269 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), '/mod/forum/locallib.php');
3270 if (empty($attachments)) {
3271 $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3272 } else {
3273 $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3276 $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3277 if (!empty($porfoliohtml)) {
3278 $commands[] = $porfoliohtml;
3281 // Finished building commands
3284 // Begin output
3286 $output = '';
3288 if ($istracked) {
3289 if ($postisread) {
3290 $forumpostclass = ' read';
3291 } else {
3292 $forumpostclass = ' unread';
3293 $output .= html_writer::tag('a', '', array('name'=>'unread'));
3295 } else {
3296 // ignore trackign status if not tracked or tracked param missing
3297 $forumpostclass = '';
3300 $topicclass = '';
3301 if (empty($post->parent)) {
3302 $topicclass = ' firstpost starter';
3305 $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3306 $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass));
3307 $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3308 $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3309 $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3310 $output .= html_writer::end_tag('div');
3313 $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3315 $postsubject = $post->subject;
3316 if (empty($post->subjectnoformat)) {
3317 $postsubject = format_string($postsubject);
3319 $output .= html_writer::tag('div', $postsubject, array('class'=>'subject'));
3321 $by = new stdClass();
3322 $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3323 $by->date = userdate($post->modified);
3324 $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author'));
3326 $output .= html_writer::end_tag('div'); //topic
3327 $output .= html_writer::end_tag('div'); //row
3329 $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3330 $output .= html_writer::start_tag('div', array('class'=>'left'));
3332 $groupoutput = '';
3333 if ($groups) {
3334 $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3336 if (empty($groupoutput)) {
3337 $groupoutput = '&nbsp;';
3339 $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3341 $output .= html_writer::end_tag('div'); //left side
3342 $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3343 $output .= html_writer::start_tag('div', array('class'=>'content'));
3344 if (!empty($attachments)) {
3345 $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3348 $options = new stdClass;
3349 $options->para = false;
3350 $options->trusted = $post->messagetrust;
3351 $options->context = $modcontext;
3352 if ($shortenpost) {
3353 // Prepare shortened version
3354 $postclass = 'shortenedpost';
3355 $postcontent = format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3356 $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
3357 $postcontent .= html_writer::tag('span', '('.get_string('numwords', 'moodle', count_words(strip_tags($post->message))).')...', array('class'=>'post-word-count'));
3358 } else {
3359 // Prepare whole post
3360 $postclass = 'fullpost';
3361 $postcontent = format_text($post->message, $post->messageformat, $options, $course->id);
3362 if (!empty($highlight)) {
3363 $postcontent = highlight($highlight, $postcontent);
3365 $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
3367 // Output the post content
3368 $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
3369 $output .= html_writer::end_tag('div'); // Content
3370 $output .= html_writer::end_tag('div'); // Content mask
3371 $output .= html_writer::end_tag('div'); // Row
3373 $output .= html_writer::start_tag('div', array('class'=>'row side'));
3374 $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
3375 $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
3377 // Output ratings
3378 if (!empty($post->rating)) {
3379 $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
3382 // Output the commands
3383 $commandhtml = array();
3384 foreach ($commands as $command) {
3385 if (is_array($command)) {
3386 $commandhtml[] = html_writer::link($command['url'], $command['text']);
3387 } else {
3388 $commandhtml[] = $command;
3391 $output .= html_writer::tag('div', implode(' | ', $commandhtml), array('class'=>'commands'));
3393 // Output link to post if required
3394 if ($link) {
3395 if ($post->replies == 1) {
3396 $replystring = get_string('repliesone', 'forum', $post->replies);
3397 } else {
3398 $replystring = get_string('repliesmany', 'forum', $post->replies);
3401 $output .= html_writer::start_tag('div', array('class'=>'link'));
3402 $output .= html_writer::link($discussionlink, get_string('discussthistopic', 'forum'));
3403 $output .= '&nbsp;('.$replystring.')';
3404 $output .= html_writer::end_tag('div'); // link
3407 // Output footer if required
3408 if ($footer) {
3409 $output .= html_writer::tag('div', $footer, array('class'=>'footer'));
3412 // Close remaining open divs
3413 $output .= html_writer::end_tag('div'); // content
3414 $output .= html_writer::end_tag('div'); // row
3415 $output .= html_writer::end_tag('div'); // forumpost
3417 // Mark the forum post as read if required
3418 if ($istracked && !$CFG->forum_usermarksread && !$postisread) {
3419 forum_tp_mark_post_read($USER->id, $post, $forum->id);
3422 if ($return) {
3423 return $output;
3425 echo $output;
3426 return;
3430 * Return rating related permissions
3432 * @param string $options the context id
3433 * @return array an associative array of the user's rating permissions
3435 function forum_rating_permissions($contextid, $component, $ratingarea) {
3436 $context = get_context_instance_by_id($contextid, MUST_EXIST);
3437 if ($component != 'mod_forum' || $ratingarea != 'post') {
3438 // We don't know about this component/ratingarea so just return null to get the
3439 // default restrictive permissions.
3440 return null;
3442 return array(
3443 'view' => has_capability('mod/forum:viewrating', $context),
3444 'viewany' => has_capability('mod/forum:viewanyrating', $context),
3445 'viewall' => has_capability('mod/forum:viewallratings', $context),
3446 'rate' => has_capability('mod/forum:rate', $context)
3451 * Validates a submitted rating
3452 * @param array $params submitted data
3453 * context => object the context in which the rated items exists [required]
3454 * component => The component for this module - should always be mod_forum [required]
3455 * ratingarea => object the context in which the rated items exists [required]
3456 * itemid => int the ID of the object being rated [required]
3457 * scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
3458 * rating => int the submitted rating [required]
3459 * rateduserid => int the id of the user whose items have been rated. NOT the user who submitted the ratings. 0 to update all. [required]
3460 * aggregation => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [required]
3461 * @return boolean true if the rating is valid. Will throw rating_exception if not
3463 function forum_rating_validate($params) {
3464 global $DB, $USER;
3466 // Check the component is mod_forum
3467 if ($params['component'] != 'mod_forum') {
3468 throw new rating_exception('invalidcomponent');
3471 // Check the ratingarea is post (the only rating area in forum)
3472 if ($params['ratingarea'] != 'post') {
3473 throw new rating_exception('invalidratingarea');
3476 // Check the rateduserid is not the current user .. you can't rate your own posts
3477 if ($params['rateduserid'] == $USER->id) {
3478 throw new rating_exception('nopermissiontorate');
3481 // Fetch all the related records ... we need to do this anyway to call forum_user_can_see_post
3482 $post = $DB->get_record('forum_posts', array('id' => $params['itemid'], 'userid' => $params['rateduserid']), '*', MUST_EXIST);
3483 $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion), '*', MUST_EXIST);
3484 $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
3485 $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
3486 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id , false, MUST_EXIST);
3487 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
3489 // Make sure the context provided is the context of the forum
3490 if ($context->id != $params['context']->id) {
3491 throw new rating_exception('invalidcontext');
3494 if ($forum->scale != $params['scaleid']) {
3495 //the scale being submitted doesnt match the one in the database
3496 throw new rating_exception('invalidscaleid');
3499 // check the item we're rating was created in the assessable time window
3500 if (!empty($forum->assesstimestart) && !empty($forum->assesstimefinish)) {
3501 if ($post->created < $forum->assesstimestart || $post->created > $forum->assesstimefinish) {
3502 throw new rating_exception('notavailable');
3506 //check that the submitted rating is valid for the scale
3508 // lower limit
3509 if ($params['rating'] < 0 && $params['rating'] != RATING_UNSET_RATING) {
3510 throw new rating_exception('invalidnum');
3513 // upper limit
3514 if ($forum->scale < 0) {
3515 //its a custom scale
3516 $scalerecord = $DB->get_record('scale', array('id' => -$forum->scale));
3517 if ($scalerecord) {
3518 $scalearray = explode(',', $scalerecord->scale);
3519 if ($params['rating'] > count($scalearray)) {
3520 throw new rating_exception('invalidnum');
3522 } else {
3523 throw new rating_exception('invalidscaleid');
3525 } else if ($params['rating'] > $forum->scale) {
3526 //if its numeric and submitted rating is above maximum
3527 throw new rating_exception('invalidnum');
3530 // Make sure groups allow this user to see the item they're rating
3531 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used
3532 if (!groups_group_exists($discussion->groupid)) { // Can't find group
3533 throw new rating_exception('cannotfindgroup');//something is wrong
3536 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
3537 // do not allow rating of posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
3538 throw new rating_exception('notmemberofgroup');
3542 // perform some final capability checks
3543 if (!forum_user_can_see_post($forum, $discussion, $post, $USER, $cm)) {
3544 throw new rating_exception('nopermissiontorate');
3547 return true;
3552 * This function prints the overview of a discussion in the forum listing.
3553 * It needs some discussion information and some post information, these
3554 * happen to be combined for efficiency in the $post parameter by the function
3555 * that calls this one: forum_print_latest_discussions()
3557 * @global object
3558 * @global object
3559 * @param object $post The post object (passed by reference for speed).
3560 * @param object $forum The forum object.
3561 * @param int $group Current group.
3562 * @param string $datestring Format to use for the dates.
3563 * @param boolean $cantrack Is tracking enabled for this forum.
3564 * @param boolean $forumtracked Is the user tracking this forum.
3565 * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
3567 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
3568 $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) {
3570 global $USER, $CFG, $OUTPUT;
3572 static $rowcount;
3573 static $strmarkalldread;
3575 if (empty($modcontext)) {
3576 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3577 print_error('invalidcoursemodule');
3579 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3582 if (!isset($rowcount)) {
3583 $rowcount = 0;
3584 $strmarkalldread = get_string('markalldread', 'forum');
3585 } else {
3586 $rowcount = ($rowcount + 1) % 2;
3589 $post->subject = format_string($post->subject,true);
3591 echo "\n\n";
3592 echo '<tr class="discussion r'.$rowcount.'">';
3594 // Topic
3595 echo '<td class="topic starter">';
3596 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.$post->subject.'</a>';
3597 echo "</td>\n";
3599 // Picture
3600 $postuser = new stdClass();
3601 $postuser->id = $post->userid;
3602 $postuser->firstname = $post->firstname;
3603 $postuser->lastname = $post->lastname;
3604 $postuser->imagealt = $post->imagealt;
3605 $postuser->picture = $post->picture;
3606 $postuser->email = $post->email;
3608 echo '<td class="picture">';
3609 echo $OUTPUT->user_picture($postuser, array('courseid'=>$forum->course));
3610 echo "</td>\n";
3612 // User name
3613 $fullname = fullname($post, has_capability('moodle/site:viewfullnames', $modcontext));
3614 echo '<td class="author">';
3615 echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->userid.'&amp;course='.$forum->course.'">'.$fullname.'</a>';
3616 echo "</td>\n";
3618 // Group picture
3619 if ($group !== -1) { // Groups are active - group is a group data object or NULL
3620 echo '<td class="picture group">';
3621 if (!empty($group->picture) and empty($group->hidepicture)) {
3622 print_group_picture($group, $forum->course, false, false, true);
3623 } else if (isset($group->id)) {
3624 if($canviewparticipants) {
3625 echo '<a href="'.$CFG->wwwroot.'/user/index.php?id='.$forum->course.'&amp;group='.$group->id.'">'.$group->name.'</a>';
3626 } else {
3627 echo $group->name;
3630 echo "</td>\n";
3633 if (has_capability('mod/forum:viewdiscussion', $modcontext)) { // Show the column with replies
3634 echo '<td class="replies">';
3635 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3636 echo $post->replies.'</a>';
3637 echo "</td>\n";
3639 if ($cantrack) {
3640 echo '<td class="replies">';
3641 if ($forumtracked) {
3642 if ($post->unread > 0) {
3643 echo '<span class="unread">';
3644 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#unread">';
3645 echo $post->unread;
3646 echo '</a>';
3647 echo '<a title="'.$strmarkalldread.'" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
3648 $forum->id.'&amp;d='.$post->discussion.'&amp;mark=read&amp;returnpage=view.php">' .
3649 '<img src="'.$OUTPUT->pix_url('t/clear') . '" class="iconsmall" alt="'.$strmarkalldread.'" /></a>';
3650 echo '</span>';
3651 } else {
3652 echo '<span class="read">';
3653 echo $post->unread;
3654 echo '</span>';
3656 } else {
3657 echo '<span class="read">';
3658 echo '-';
3659 echo '</span>';
3661 echo "</td>\n";
3665 echo '<td class="lastpost">';
3666 $usedate = (empty($post->timemodified)) ? $post->modified : $post->timemodified; // Just in case
3667 $parenturl = (empty($post->lastpostid)) ? '' : '&amp;parent='.$post->lastpostid;
3668 $usermodified = new stdClass();
3669 $usermodified->id = $post->usermodified;
3670 $usermodified->firstname = $post->umfirstname;
3671 $usermodified->lastname = $post->umlastname;
3672 echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->usermodified.'&amp;course='.$forum->course.'">'.
3673 fullname($usermodified).'</a><br />';
3674 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.$parenturl.'">'.
3675 userdate($usedate, $datestring).'</a>';
3676 echo "</td>\n";
3678 echo "</tr>\n\n";
3684 * Given a post object that we already know has a long message
3685 * this function truncates the message nicely to the first
3686 * sane place between $CFG->forum_longpost and $CFG->forum_shortpost
3688 * @global object
3689 * @param string $message
3690 * @return string
3692 function forum_shorten_post($message) {
3694 global $CFG;
3696 $i = 0;
3697 $tag = false;
3698 $length = strlen($message);
3699 $count = 0;
3700 $stopzone = false;
3701 $truncate = 0;
3703 for ($i=0; $i<$length; $i++) {
3704 $char = $message[$i];
3706 switch ($char) {
3707 case "<":
3708 $tag = true;
3709 break;
3710 case ">":
3711 $tag = false;
3712 break;
3713 default:
3714 if (!$tag) {
3715 if ($stopzone) {
3716 if ($char == ".") {
3717 $truncate = $i+1;
3718 break 2;
3721 $count++;
3723 break;
3725 if (!$stopzone) {
3726 if ($count > $CFG->forum_shortpost) {
3727 $stopzone = true;
3732 if (!$truncate) {
3733 $truncate = $i;
3736 return substr($message, 0, $truncate);
3740 * Print the drop down that allows the user to select how they want to have
3741 * the discussion displayed.
3743 * @param int $id forum id if $forumtype is 'single',
3744 * discussion id for any other forum type
3745 * @param mixed $mode forum layout mode
3746 * @param string $forumtype optional
3748 function forum_print_mode_form($id, $mode, $forumtype='') {
3749 global $OUTPUT;
3750 if ($forumtype == 'single') {
3751 $select = new single_select(new moodle_url("/mod/forum/view.php", array('f'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode");
3752 $select->class = "forummode";
3753 } else {
3754 $select = new single_select(new moodle_url("/mod/forum/discuss.php", array('d'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode");
3756 echo $OUTPUT->render($select);
3760 * @global object
3761 * @param object $course
3762 * @param string $search
3763 * @return string
3765 function forum_search_form($course, $search='') {
3766 global $CFG, $OUTPUT;
3768 $output = '<div class="forumsearch">';
3769 $output .= '<form action="'.$CFG->wwwroot.'/mod/forum/search.php" style="display:inline">';
3770 $output .= '<fieldset class="invisiblefieldset">';
3771 $output .= $OUTPUT->help_icon('search');
3772 $output .= '<label class="accesshide" for="search" >'.get_string('search', 'forum').'</label>';
3773 $output .= '<input id="search" name="search" type="text" size="18" value="'.s($search, true).'" alt="search" />';
3774 $output .= '<label class="accesshide" for="searchforums" >'.get_string('searchforums', 'forum').'</label>';
3775 $output .= '<input id="searchforums" value="'.get_string('searchforums', 'forum').'" type="submit" />';
3776 $output .= '<input name="id" type="hidden" value="'.$course->id.'" />';
3777 $output .= '</fieldset>';
3778 $output .= '</form>';
3779 $output .= '</div>';
3781 return $output;
3786 * @global object
3787 * @global object
3789 function forum_set_return() {
3790 global $CFG, $SESSION;
3792 if (! isset($SESSION->fromdiscussion)) {
3793 if (!empty($_SERVER['HTTP_REFERER'])) {
3794 $referer = $_SERVER['HTTP_REFERER'];
3795 } else {
3796 $referer = "";
3798 // If the referer is NOT a login screen then save it.
3799 if (! strncasecmp("$CFG->wwwroot/login", $referer, 300)) {
3800 $SESSION->fromdiscussion = $_SERVER["HTTP_REFERER"];
3807 * @global object
3808 * @param string $default
3809 * @return string
3811 function forum_go_back_to($default) {
3812 global $SESSION;
3814 if (!empty($SESSION->fromdiscussion)) {
3815 $returnto = $SESSION->fromdiscussion;
3816 unset($SESSION->fromdiscussion);
3817 return $returnto;
3818 } else {
3819 return $default;
3824 * Given a discussion object that is being moved to $forumto,
3825 * this function checks all posts in that discussion
3826 * for attachments, and if any are found, these are
3827 * moved to the new forum directory.
3829 * @global object
3830 * @param object $discussion
3831 * @param int $forumfrom source forum id
3832 * @param int $forumto target forum id
3833 * @return bool success
3835 function forum_move_attachments($discussion, $forumfrom, $forumto) {
3836 global $DB;
3838 $fs = get_file_storage();
3840 $newcm = get_coursemodule_from_instance('forum', $forumto);
3841 $oldcm = get_coursemodule_from_instance('forum', $forumfrom);
3843 $newcontext = get_context_instance(CONTEXT_MODULE, $newcm->id);
3844 $oldcontext = get_context_instance(CONTEXT_MODULE, $oldcm->id);
3846 // loop through all posts, better not use attachment flag ;-)
3847 if ($posts = $DB->get_records('forum_posts', array('discussion'=>$discussion->id), '', 'id, attachment')) {
3848 foreach ($posts as $post) {
3849 $fs->move_area_files_to_new_context($oldcontext->id,
3850 $newcontext->id, 'mod_forum', 'post', $post->id);
3851 $attachmentsmoved = $fs->move_area_files_to_new_context($oldcontext->id,
3852 $newcontext->id, 'mod_forum', 'attachment', $post->id);
3853 if ($attachmentsmoved > 0 && $post->attachment != '1') {
3854 // Weird - let's fix it
3855 $post->attachment = '1';
3856 $DB->update_record('forum_posts', $post);
3857 } else if ($attachmentsmoved == 0 && $post->attachment != '') {
3858 // Weird - let's fix it
3859 $post->attachment = '';
3860 $DB->update_record('forum_posts', $post);
3865 return true;
3869 * Returns attachments as formated text/html optionally with separate images
3871 * @global object
3872 * @global object
3873 * @global object
3874 * @param object $post
3875 * @param object $cm
3876 * @param string $type html/text/separateimages
3877 * @return mixed string or array of (html text withouth images and image HTML)
3879 function forum_print_attachments($post, $cm, $type) {
3880 global $CFG, $DB, $USER, $OUTPUT;
3882 if (empty($post->attachment)) {
3883 return $type !== 'separateimages' ? '' : array('', '');
3886 if (!in_array($type, array('separateimages', 'html', 'text'))) {
3887 return $type !== 'separateimages' ? '' : array('', '');
3890 if (!$context = get_context_instance(CONTEXT_MODULE, $cm->id)) {
3891 return $type !== 'separateimages' ? '' : array('', '');
3893 $strattachment = get_string('attachment', 'forum');
3895 $fs = get_file_storage();
3897 $imagereturn = '';
3898 $output = '';
3900 $canexport = !empty($CFG->enableportfolios) && (has_capability('mod/forum:exportpost', $context) || ($post->userid == $USER->id && has_capability('mod/forum:exportownpost', $context)));
3902 if ($canexport) {
3903 require_once($CFG->libdir.'/portfoliolib.php');
3906 $files = $fs->get_area_files($context->id, 'mod_forum', 'attachment', $post->id, "timemodified", false);
3907 if ($files) {
3908 if ($canexport) {
3909 $button = new portfolio_add_button();
3911 foreach ($files as $file) {
3912 $filename = $file->get_filename();
3913 $mimetype = $file->get_mimetype();
3914 $iconimage = '<img src="'.$OUTPUT->pix_url(file_mimetype_icon($mimetype)).'" class="icon" alt="'.$mimetype.'" />';
3915 $path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$context->id.'/mod_forum/attachment/'.$post->id.'/'.$filename);
3917 if ($type == 'html') {
3918 $output .= "<a href=\"$path\">$iconimage</a> ";
3919 $output .= "<a href=\"$path\">".s($filename)."</a>";
3920 if ($canexport) {
3921 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php');
3922 $button->set_format_by_file($file);
3923 $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
3925 $output .= "<br />";
3927 } else if ($type == 'text') {
3928 $output .= "$strattachment ".s($filename).":\n$path\n";
3930 } else { //'returnimages'
3931 if (in_array($mimetype, array('image/gif', 'image/jpeg', 'image/png'))) {
3932 // Image attachments don't get printed as links
3933 $imagereturn .= "<br /><img src=\"$path\" alt=\"\" />";
3934 if ($canexport) {
3935 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php');
3936 $button->set_format_by_file($file);
3937 $imagereturn .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
3939 } else {
3940 $output .= "<a href=\"$path\">$iconimage</a> ";
3941 $output .= format_text("<a href=\"$path\">".s($filename)."</a>", FORMAT_HTML, array('context'=>$context));
3942 if ($canexport) {
3943 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), '/mod/forum/locallib.php');
3944 $button->set_format_by_file($file);
3945 $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
3947 $output .= '<br />';
3953 if ($type !== 'separateimages') {
3954 return $output;
3956 } else {
3957 return array($output, $imagereturn);
3962 * Lists all browsable file areas
3964 * @package mod_forum
3965 * @category files
3966 * @param stdClass $course course object
3967 * @param stdClass $cm course module object
3968 * @param stdClass $context context object
3969 * @return array
3971 function forum_get_file_areas($course, $cm, $context) {
3972 $areas = array();
3973 return $areas;
3977 * File browsing support for forum module.
3979 * @package mod_forum
3980 * @category files
3981 * @param stdClass $browser file browser object
3982 * @param stdClass $areas file areas
3983 * @param stdClass $course course object
3984 * @param stdClass $cm course module
3985 * @param stdClass $context context module
3986 * @param string $filearea file area
3987 * @param int $itemid item ID
3988 * @param string $filepath file path
3989 * @param string $filename file name
3990 * @return file_info instance or null if not found
3992 function forum_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
3993 global $CFG, $DB;
3995 if ($context->contextlevel != CONTEXT_MODULE) {
3996 return null;
3999 $fileareas = array('attachment', 'post');
4000 if (!in_array($filearea, $fileareas)) {
4001 return null;
4004 if (!$post = $DB->get_record('forum_posts', array('id' => $itemid))) {
4005 return null;
4008 if (!$discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion))) {
4009 return null;
4012 if (!$forum = $DB->get_record('forum', array('id' => $cm->instance))) {
4013 return null;
4016 $fs = get_file_storage();
4017 $filepath = is_null($filepath) ? '/' : $filepath;
4018 $filename = is_null($filename) ? '.' : $filename;
4019 if (!($storedfile = $fs->get_file($context->id, 'mod_forum', $filearea, $itemid, $filepath, $filename))) {
4020 return null;
4023 // Make sure groups allow this user to see this file
4024 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used
4025 if (!groups_group_exists($discussion->groupid)) { // Can't find group
4026 return null; // Be safe and don't send it to anyone
4029 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
4030 // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
4031 return null;
4035 // Make sure we're allowed to see it...
4036 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
4037 return null;
4040 $urlbase = $CFG->wwwroot.'/pluginfile.php';
4041 return new file_info_stored($browser, $context, $storedfile, $urlbase, $filearea, $itemid, true, true, false);
4045 * Serves the forum attachments. Implements needed access control ;-)
4047 * @package mod_forum
4048 * @category files
4049 * @param stdClass $course course object
4050 * @param stdClass $cm course module object
4051 * @param stdClass $context context object
4052 * @param string $filearea file area
4053 * @param array $args extra arguments
4054 * @param bool $forcedownload whether or not force download
4055 * @param array $options additional options affecting the file serving
4056 * @return bool false if file not found, does not return if found - justsend the file
4058 function forum_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
4059 global $CFG, $DB;
4061 if ($context->contextlevel != CONTEXT_MODULE) {
4062 return false;
4065 require_course_login($course, true, $cm);
4067 $fileareas = array('attachment', 'post');
4068 if (!in_array($filearea, $fileareas)) {
4069 return false;
4072 $postid = (int)array_shift($args);
4074 if (!$post = $DB->get_record('forum_posts', array('id'=>$postid))) {
4075 return false;
4078 if (!$discussion = $DB->get_record('forum_discussions', array('id'=>$post->discussion))) {
4079 return false;
4082 if (!$forum = $DB->get_record('forum', array('id'=>$cm->instance))) {
4083 return false;
4086 $fs = get_file_storage();
4087 $relativepath = implode('/', $args);
4088 $fullpath = "/$context->id/mod_forum/$filearea/$postid/$relativepath";
4089 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
4090 return false;
4093 // Make sure groups allow this user to see this file
4094 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used
4095 if (!groups_group_exists($discussion->groupid)) { // Can't find group
4096 return false; // Be safe and don't send it to anyone
4099 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
4100 // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
4101 return false;
4105 // Make sure we're allowed to see it...
4106 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
4107 return false;
4110 // finally send the file
4111 send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security!
4115 * If successful, this function returns the name of the file
4117 * @global object
4118 * @param object $post is a full post record, including course and forum
4119 * @param object $forum
4120 * @param object $cm
4121 * @param mixed $mform
4122 * @param string $message
4123 * @return bool
4125 function forum_add_attachment($post, $forum, $cm, $mform=null, &$message=null) {
4126 global $DB;
4128 if (empty($mform)) {
4129 return false;
4132 if (empty($post->attachments)) {
4133 return true; // Nothing to do
4136 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4138 $info = file_get_draft_area_info($post->attachments);
4139 $present = ($info['filecount']>0) ? '1' : '';
4140 file_save_draft_area_files($post->attachments, $context->id, 'mod_forum', 'attachment', $post->id);
4142 $DB->set_field('forum_posts', 'attachment', $present, array('id'=>$post->id));
4144 return true;
4148 * Add a new post in an existing discussion.
4150 * @global object
4151 * @global object
4152 * @global object
4153 * @param object $post
4154 * @param mixed $mform
4155 * @param string $message
4156 * @return int
4158 function forum_add_new_post($post, $mform, &$message) {
4159 global $USER, $CFG, $DB;
4161 $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion));
4162 $forum = $DB->get_record('forum', array('id' => $discussion->forum));
4163 $cm = get_coursemodule_from_instance('forum', $forum->id);
4164 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4166 $post->created = $post->modified = time();
4167 $post->mailed = "0";
4168 $post->userid = $USER->id;
4169 $post->attachment = "";
4171 $post->id = $DB->insert_record("forum_posts", $post);
4172 $post->message = file_save_draft_area_files($post->itemid, $context->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
4173 $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
4174 forum_add_attachment($post, $forum, $cm, $mform, $message);
4176 // Update discussion modified date
4177 $DB->set_field("forum_discussions", "timemodified", $post->modified, array("id" => $post->discussion));
4178 $DB->set_field("forum_discussions", "usermodified", $post->userid, array("id" => $post->discussion));
4180 if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
4181 forum_tp_mark_post_read($post->userid, $post, $post->forum);
4184 return $post->id;
4188 * Update a post
4190 * @global object
4191 * @global object
4192 * @global object
4193 * @param object $post
4194 * @param mixed $mform
4195 * @param string $message
4196 * @return bool
4198 function forum_update_post($post, $mform, &$message) {
4199 global $USER, $CFG, $DB;
4201 $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion));
4202 $forum = $DB->get_record('forum', array('id' => $discussion->forum));
4203 $cm = get_coursemodule_from_instance('forum', $forum->id);
4204 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4206 $post->modified = time();
4208 $DB->update_record('forum_posts', $post);
4210 $discussion->timemodified = $post->modified; // last modified tracking
4211 $discussion->usermodified = $post->userid; // last modified tracking
4213 if (!$post->parent) { // Post is a discussion starter - update discussion title and times too
4214 $discussion->name = $post->subject;
4215 $discussion->timestart = $post->timestart;
4216 $discussion->timeend = $post->timeend;
4218 $post->message = file_save_draft_area_files($post->itemid, $context->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
4219 $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
4221 $DB->update_record('forum_discussions', $discussion);
4223 forum_add_attachment($post, $forum, $cm, $mform, $message);
4225 if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
4226 forum_tp_mark_post_read($post->userid, $post, $post->forum);
4229 return true;
4233 * Given an object containing all the necessary data,
4234 * create a new discussion and return the id
4236 * @global object
4237 * @global object
4238 * @global object
4239 * @param object $post
4240 * @param mixed $mform
4241 * @param string $message
4242 * @param int $userid
4243 * @return object
4245 function forum_add_discussion($discussion, $mform=null, &$message=null, $userid=null) {
4246 global $USER, $CFG, $DB;
4248 $timenow = time();
4250 if (is_null($userid)) {
4251 $userid = $USER->id;
4254 // The first post is stored as a real post, and linked
4255 // to from the discuss entry.
4257 $forum = $DB->get_record('forum', array('id'=>$discussion->forum));
4258 $cm = get_coursemodule_from_instance('forum', $forum->id);
4260 $post = new stdClass();
4261 $post->discussion = 0;
4262 $post->parent = 0;
4263 $post->userid = $userid;
4264 $post->created = $timenow;
4265 $post->modified = $timenow;
4266 $post->mailed = 0;
4267 $post->subject = $discussion->name;
4268 $post->message = $discussion->message;
4269 $post->messageformat = $discussion->messageformat;
4270 $post->messagetrust = $discussion->messagetrust;
4271 $post->attachments = isset($discussion->attachments) ? $discussion->attachments : null;
4272 $post->forum = $forum->id; // speedup
4273 $post->course = $forum->course; // speedup
4274 $post->mailnow = $discussion->mailnow;
4276 $post->id = $DB->insert_record("forum_posts", $post);
4278 // TODO: Fix the calling code so that there always is a $cm when this function is called
4279 if (!empty($cm->id) && !empty($discussion->itemid)) { // In "single simple discussions" this may not exist yet
4280 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4281 $text = file_save_draft_area_files($discussion->itemid, $context->id, 'mod_forum', 'post', $post->id, array('subdirs'=>true), $post->message);
4282 $DB->set_field('forum_posts', 'message', $text, array('id'=>$post->id));
4285 // Now do the main entry for the discussion, linking to this first post
4287 $discussion->firstpost = $post->id;
4288 $discussion->timemodified = $timenow;
4289 $discussion->usermodified = $post->userid;
4290 $discussion->userid = $userid;
4292 $post->discussion = $DB->insert_record("forum_discussions", $discussion);
4294 // Finally, set the pointer on the post.
4295 $DB->set_field("forum_posts", "discussion", $post->discussion, array("id"=>$post->id));
4297 if (!empty($cm->id)) {
4298 forum_add_attachment($post, $forum, $cm, $mform, $message);
4301 if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
4302 forum_tp_mark_post_read($post->userid, $post, $post->forum);
4305 return $post->discussion;
4310 * Deletes a discussion and handles all associated cleanup.
4312 * @global object
4313 * @param object $discussion Discussion to delete
4314 * @param bool $fulldelete True when deleting entire forum
4315 * @param object $course Course
4316 * @param object $cm Course-module
4317 * @param object $forum Forum
4318 * @return bool
4320 function forum_delete_discussion($discussion, $fulldelete, $course, $cm, $forum) {
4321 global $DB, $CFG;
4322 require_once($CFG->libdir.'/completionlib.php');
4324 $result = true;
4326 if ($posts = $DB->get_records("forum_posts", array("discussion" => $discussion->id))) {
4327 foreach ($posts as $post) {
4328 $post->course = $discussion->course;
4329 $post->forum = $discussion->forum;
4330 if (!forum_delete_post($post, 'ignore', $course, $cm, $forum, $fulldelete)) {
4331 $result = false;
4336 forum_tp_delete_read_records(-1, -1, $discussion->id);
4338 if (!$DB->delete_records("forum_discussions", array("id"=>$discussion->id))) {
4339 $result = false;
4342 // Update completion state if we are tracking completion based on number of posts
4343 // But don't bother when deleting whole thing
4344 if (!$fulldelete) {
4345 $completion = new completion_info($course);
4346 if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC &&
4347 ($forum->completiondiscussions || $forum->completionreplies || $forum->completionposts)) {
4348 $completion->update_state($cm, COMPLETION_INCOMPLETE, $discussion->userid);
4352 return $result;
4357 * Deletes a single forum post.
4359 * @global object
4360 * @param object $post Forum post object
4361 * @param mixed $children Whether to delete children. If false, returns false
4362 * if there are any children (without deleting the post). If true,
4363 * recursively deletes all children. If set to special value 'ignore', deletes
4364 * post regardless of children (this is for use only when deleting all posts
4365 * in a disussion).
4366 * @param object $course Course
4367 * @param object $cm Course-module
4368 * @param object $forum Forum
4369 * @param bool $skipcompletion True to skip updating completion state if it
4370 * would otherwise be updated, i.e. when deleting entire forum anyway.
4371 * @return bool
4373 function forum_delete_post($post, $children, $course, $cm, $forum, $skipcompletion=false) {
4374 global $DB, $CFG;
4375 require_once($CFG->libdir.'/completionlib.php');
4377 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4379 if ($children !== 'ignore' && ($childposts = $DB->get_records('forum_posts', array('parent'=>$post->id)))) {
4380 if ($children) {
4381 foreach ($childposts as $childpost) {
4382 forum_delete_post($childpost, true, $course, $cm, $forum, $skipcompletion);
4384 } else {
4385 return false;
4389 //delete ratings
4390 require_once($CFG->dirroot.'/rating/lib.php');
4391 $delopt = new stdClass;
4392 $delopt->contextid = $context->id;
4393 $delopt->component = 'mod_forum';
4394 $delopt->ratingarea = 'post';
4395 $delopt->itemid = $post->id;
4396 $rm = new rating_manager();
4397 $rm->delete_ratings($delopt);
4399 //delete attachments
4400 $fs = get_file_storage();
4401 $fs->delete_area_files($context->id, 'mod_forum', 'attachment', $post->id);
4402 $fs->delete_area_files($context->id, 'mod_forum', 'post', $post->id);
4404 if ($DB->delete_records("forum_posts", array("id" => $post->id))) {
4406 forum_tp_delete_read_records(-1, $post->id);
4408 // Just in case we are deleting the last post
4409 forum_discussion_update_last_post($post->discussion);
4411 // Update completion state if we are tracking completion based on number of posts
4412 // But don't bother when deleting whole thing
4414 if (!$skipcompletion) {
4415 $completion = new completion_info($course);
4416 if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC &&
4417 ($forum->completiondiscussions || $forum->completionreplies || $forum->completionposts)) {
4418 $completion->update_state($cm, COMPLETION_INCOMPLETE, $post->userid);
4422 return true;
4424 return false;
4428 * @global object
4429 * @param object $post
4430 * @param bool $children
4431 * @return int
4433 function forum_count_replies($post, $children=true) {
4434 global $DB;
4435 $count = 0;
4437 if ($children) {
4438 if ($childposts = $DB->get_records('forum_posts', array('parent' => $post->id))) {
4439 foreach ($childposts as $childpost) {
4440 $count ++; // For this child
4441 $count += forum_count_replies($childpost, true);
4444 } else {
4445 $count += $DB->count_records('forum_posts', array('parent' => $post->id));
4448 return $count;
4453 * @global object
4454 * @param int $forumid
4455 * @param mixed $value
4456 * @return bool
4458 function forum_forcesubscribe($forumid, $value=1) {
4459 global $DB;
4460 return $DB->set_field("forum", "forcesubscribe", $value, array("id" => $forumid));
4464 * @global object
4465 * @param object $forum
4466 * @return bool
4468 function forum_is_forcesubscribed($forum) {
4469 global $DB;
4470 if (isset($forum->forcesubscribe)) { // then we use that
4471 return ($forum->forcesubscribe == FORUM_FORCESUBSCRIBE);
4472 } else { // Check the database
4473 return ($DB->get_field('forum', 'forcesubscribe', array('id' => $forum)) == FORUM_FORCESUBSCRIBE);
4477 function forum_get_forcesubscribed($forum) {
4478 global $DB;
4479 if (isset($forum->forcesubscribe)) { // then we use that
4480 return $forum->forcesubscribe;
4481 } else { // Check the database
4482 return $DB->get_field('forum', 'forcesubscribe', array('id' => $forum));
4487 * @global object
4488 * @param int $userid
4489 * @param object $forum
4490 * @return bool
4492 function forum_is_subscribed($userid, $forum) {
4493 global $DB;
4494 if (is_numeric($forum)) {
4495 $forum = $DB->get_record('forum', array('id' => $forum));
4497 if (forum_is_forcesubscribed($forum)) {
4498 return true;
4500 return $DB->record_exists("forum_subscriptions", array("userid" => $userid, "forum" => $forum->id));
4503 function forum_get_subscribed_forums($course) {
4504 global $USER, $CFG, $DB;
4505 $sql = "SELECT f.id
4506 FROM {forum} f
4507 LEFT JOIN {forum_subscriptions} fs ON (fs.forum = f.id AND fs.userid = ?)
4508 WHERE f.course = ?
4509 AND f.forcesubscribe <> ".FORUM_DISALLOWSUBSCRIBE."
4510 AND (f.forcesubscribe = ".FORUM_FORCESUBSCRIBE." OR fs.id IS NOT NULL)";
4511 if ($subscribed = $DB->get_records_sql($sql, array($USER->id, $course->id))) {
4512 foreach ($subscribed as $s) {
4513 $subscribed[$s->id] = $s->id;
4515 return $subscribed;
4516 } else {
4517 return array();
4522 * Adds user to the subscriber list
4524 * @global object
4525 * @param int $userid
4526 * @param int $forumid
4528 function forum_subscribe($userid, $forumid) {
4529 global $DB;
4531 if ($DB->record_exists("forum_subscriptions", array("userid"=>$userid, "forum"=>$forumid))) {
4532 return true;
4535 $sub = new stdClass();
4536 $sub->userid = $userid;
4537 $sub->forum = $forumid;
4539 return $DB->insert_record("forum_subscriptions", $sub);
4543 * Removes user from the subscriber list
4545 * @global object
4546 * @param int $userid
4547 * @param int $forumid
4549 function forum_unsubscribe($userid, $forumid) {
4550 global $DB;
4551 return $DB->delete_records("forum_subscriptions", array("userid"=>$userid, "forum"=>$forumid));
4555 * Given a new post, subscribes or unsubscribes as appropriate.
4556 * Returns some text which describes what happened.
4558 * @global objec
4559 * @param object $post
4560 * @param object $forum
4562 function forum_post_subscription($post, $forum) {
4564 global $USER;
4566 $action = '';
4567 $subscribed = forum_is_subscribed($USER->id, $forum);
4569 if ($forum->forcesubscribe == FORUM_FORCESUBSCRIBE) { // database ignored
4570 return "";
4572 } elseif (($forum->forcesubscribe == FORUM_DISALLOWSUBSCRIBE)
4573 && !has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_COURSE, $forum->course), $USER->id)) {
4574 if ($subscribed) {
4575 $action = 'unsubscribe'; // sanity check, following MDL-14558
4576 } else {
4577 return "";
4580 } else { // go with the user's choice
4581 if (isset($post->subscribe)) {
4582 // no change
4583 if ((!empty($post->subscribe) && $subscribed)
4584 || (empty($post->subscribe) && !$subscribed)) {
4585 return "";
4587 } elseif (!empty($post->subscribe) && !$subscribed) {
4588 $action = 'subscribe';
4590 } elseif (empty($post->subscribe) && $subscribed) {
4591 $action = 'unsubscribe';
4596 $info = new stdClass();
4597 $info->name = fullname($USER);
4598 $info->forum = format_string($forum->name);
4600 switch ($action) {
4601 case 'subscribe':
4602 forum_subscribe($USER->id, $post->forum);
4603 return "<p>".get_string("nowsubscribed", "forum", $info)."</p>";
4604 case 'unsubscribe':
4605 forum_unsubscribe($USER->id, $post->forum);
4606 return "<p>".get_string("nownotsubscribed", "forum", $info)."</p>";
4611 * Generate and return the subscribe or unsubscribe link for a forum.
4613 * @param object $forum the forum. Fields used are $forum->id and $forum->forcesubscribe.
4614 * @param object $context the context object for this forum.
4615 * @param array $messages text used for the link in its various states
4616 * (subscribed, unsubscribed, forcesubscribed or cantsubscribe).
4617 * Any strings not passed in are taken from the $defaultmessages array
4618 * at the top of the function.
4619 * @param bool $cantaccessagroup
4620 * @param bool $fakelink
4621 * @param bool $backtoindex
4622 * @param array $subscribed_forums
4623 * @return string
4625 function forum_get_subscribe_link($forum, $context, $messages = array(), $cantaccessagroup = false, $fakelink=true, $backtoindex=false, $subscribed_forums=null) {
4626 global $CFG, $USER, $PAGE, $OUTPUT;
4627 $defaultmessages = array(
4628 'subscribed' => get_string('unsubscribe', 'forum'),
4629 'unsubscribed' => get_string('subscribe', 'forum'),
4630 'cantaccessgroup' => get_string('no'),
4631 'forcesubscribed' => get_string('everyoneissubscribed', 'forum'),
4632 'cantsubscribe' => get_string('disallowsubscribe','forum')
4634 $messages = $messages + $defaultmessages;
4636 if (forum_is_forcesubscribed($forum)) {
4637 return $messages['forcesubscribed'];
4638 } else if ($forum->forcesubscribe == FORUM_DISALLOWSUBSCRIBE && !has_capability('mod/forum:managesubscriptions', $context)) {
4639 return $messages['cantsubscribe'];
4640 } else if ($cantaccessagroup) {
4641 return $messages['cantaccessgroup'];
4642 } else {
4643 if (!is_enrolled($context, $USER, '', true)) {
4644 return '';
4646 if (is_null($subscribed_forums)) {
4647 $subscribed = forum_is_subscribed($USER->id, $forum);
4648 } else {
4649 $subscribed = !empty($subscribed_forums[$forum->id]);
4651 if ($subscribed) {
4652 $linktext = $messages['subscribed'];
4653 $linktitle = get_string('subscribestop', 'forum');
4654 } else {
4655 $linktext = $messages['unsubscribed'];
4656 $linktitle = get_string('subscribestart', 'forum');
4659 $options = array();
4660 if ($backtoindex) {
4661 $backtoindexlink = '&amp;backtoindex=1';
4662 $options['backtoindex'] = 1;
4663 } else {
4664 $backtoindexlink = '';
4666 $link = '';
4668 if ($fakelink) {
4669 $PAGE->requires->js('/mod/forum/forum.js');
4670 $PAGE->requires->js_function_call('forum_produce_subscribe_link', array($forum->id, $backtoindexlink, $linktext, $linktitle));
4671 $link = "<noscript>";
4673 $options['id'] = $forum->id;
4674 $options['sesskey'] = sesskey();
4675 $url = new moodle_url('/mod/forum/subscribe.php', $options);
4676 $link .= $OUTPUT->single_button($url, $linktext, 'get', array('title'=>$linktitle));
4677 if ($fakelink) {
4678 $link .= '</noscript>';
4681 return $link;
4687 * Generate and return the track or no track link for a forum.
4689 * @global object
4690 * @global object
4691 * @global object
4692 * @param object $forum the forum. Fields used are $forum->id and $forum->forcesubscribe.
4693 * @param array $messages
4694 * @param bool $fakelink
4695 * @return string
4697 function forum_get_tracking_link($forum, $messages=array(), $fakelink=true) {
4698 global $CFG, $USER, $PAGE, $OUTPUT;
4700 static $strnotrackforum, $strtrackforum;
4702 if (isset($messages['trackforum'])) {
4703 $strtrackforum = $messages['trackforum'];
4705 if (isset($messages['notrackforum'])) {
4706 $strnotrackforum = $messages['notrackforum'];
4708 if (empty($strtrackforum)) {
4709 $strtrackforum = get_string('trackforum', 'forum');
4711 if (empty($strnotrackforum)) {
4712 $strnotrackforum = get_string('notrackforum', 'forum');
4715 if (forum_tp_is_tracked($forum)) {
4716 $linktitle = $strnotrackforum;
4717 $linktext = $strnotrackforum;
4718 } else {
4719 $linktitle = $strtrackforum;
4720 $linktext = $strtrackforum;
4723 $link = '';
4724 if ($fakelink) {
4725 $PAGE->requires->js('/mod/forum/forum.js');
4726 $PAGE->requires->js_function_call('forum_produce_tracking_link', Array($forum->id, $linktext, $linktitle));
4727 // use <noscript> to print button in case javascript is not enabled
4728 $link .= '<noscript>';
4730 $url = new moodle_url('/mod/forum/settracking.php', array('id'=>$forum->id));
4731 $link .= $OUTPUT->single_button($url, $linktext, 'get', array('title'=>$linktitle));
4733 if ($fakelink) {
4734 $link .= '</noscript>';
4737 return $link;
4743 * Returns true if user created new discussion already
4745 * @global object
4746 * @global object
4747 * @param int $forumid
4748 * @param int $userid
4749 * @return bool
4751 function forum_user_has_posted_discussion($forumid, $userid) {
4752 global $CFG, $DB;
4754 $sql = "SELECT 'x'
4755 FROM {forum_discussions} d, {forum_posts} p
4756 WHERE d.forum = ? AND p.discussion = d.id AND p.parent = 0 and p.userid = ?";
4758 return $DB->record_exists_sql($sql, array($forumid, $userid));
4762 * @global object
4763 * @global object
4764 * @param int $forumid
4765 * @param int $userid
4766 * @return array
4768 function forum_discussions_user_has_posted_in($forumid, $userid) {
4769 global $CFG, $DB;
4771 $haspostedsql = "SELECT d.id AS id,
4773 FROM {forum_posts} p,
4774 {forum_discussions} d
4775 WHERE p.discussion = d.id
4776 AND d.forum = ?
4777 AND p.userid = ?";
4779 return $DB->get_records_sql($haspostedsql, array($forumid, $userid));
4783 * @global object
4784 * @global object
4785 * @param int $forumid
4786 * @param int $did
4787 * @param int $userid
4788 * @return bool
4790 function forum_user_has_posted($forumid, $did, $userid) {
4791 global $DB;
4793 if (empty($did)) {
4794 // posted in any forum discussion?
4795 $sql = "SELECT 'x'
4796 FROM {forum_posts} p
4797 JOIN {forum_discussions} d ON d.id = p.discussion
4798 WHERE p.userid = :userid AND d.forum = :forumid";
4799 return $DB->record_exists_sql($sql, array('forumid'=>$forumid,'userid'=>$userid));
4800 } else {
4801 return $DB->record_exists('forum_posts', array('discussion'=>$did,'userid'=>$userid));
4806 * Returns creation time of the first user's post in given discussion
4807 * @global object $DB
4808 * @param int $did Discussion id
4809 * @param int $userid User id
4810 * @return int|bool post creation time stamp or return false
4812 function forum_get_user_posted_time($did, $userid) {
4813 global $DB;
4815 $posttime = $DB->get_field('forum_posts', 'MIN(created)', array('userid'=>$userid, 'discussion'=>$did));
4816 if (empty($posttime)) {
4817 return false;
4819 return $posttime;
4823 * @global object
4824 * @param object $forum
4825 * @param object $currentgroup
4826 * @param int $unused
4827 * @param object $cm
4828 * @param object $context
4829 * @return bool
4831 function forum_user_can_post_discussion($forum, $currentgroup=null, $unused=-1, $cm=NULL, $context=NULL) {
4832 // $forum is an object
4833 global $USER;
4835 // shortcut - guest and not-logged-in users can not post
4836 if (isguestuser() or !isloggedin()) {
4837 return false;
4840 if (!$cm) {
4841 debugging('missing cm', DEBUG_DEVELOPER);
4842 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
4843 print_error('invalidcoursemodule');
4847 if (!$context) {
4848 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4851 if ($currentgroup === null) {
4852 $currentgroup = groups_get_activity_group($cm);
4855 $groupmode = groups_get_activity_groupmode($cm);
4857 if ($forum->type == 'news') {
4858 $capname = 'mod/forum:addnews';
4859 } else if ($forum->type == 'qanda') {
4860 $capname = 'mod/forum:addquestion';
4861 } else {
4862 $capname = 'mod/forum:startdiscussion';
4865 if (!has_capability($capname, $context)) {
4866 return false;
4869 if ($forum->type == 'single') {
4870 return false;
4873 if ($forum->type == 'eachuser') {
4874 if (forum_user_has_posted_discussion($forum->id, $USER->id)) {
4875 return false;
4879 if (!$groupmode or has_capability('moodle/site:accessallgroups', $context)) {
4880 return true;
4883 if ($currentgroup) {
4884 return groups_is_member($currentgroup);
4885 } else {
4886 // no group membership and no accessallgroups means no new discussions
4887 // reverted to 1.7 behaviour in 1.9+, buggy in 1.8.0-1.9.0
4888 return false;
4893 * This function checks whether the user can reply to posts in a forum
4894 * discussion. Use forum_user_can_post_discussion() to check whether the user
4895 * can start discussions.
4897 * @global object
4898 * @global object
4899 * @uses DEBUG_DEVELOPER
4900 * @uses CONTEXT_MODULE
4901 * @uses VISIBLEGROUPS
4902 * @param object $forum forum object
4903 * @param object $discussion
4904 * @param object $user
4905 * @param object $cm
4906 * @param object $course
4907 * @param object $context
4908 * @return bool
4910 function forum_user_can_post($forum, $discussion, $user=NULL, $cm=NULL, $course=NULL, $context=NULL) {
4911 global $USER, $DB;
4912 if (empty($user)) {
4913 $user = $USER;
4916 // shortcut - guest and not-logged-in users can not post
4917 if (isguestuser($user) or empty($user->id)) {
4918 return false;
4921 if (!isset($discussion->groupid)) {
4922 debugging('incorrect discussion parameter', DEBUG_DEVELOPER);
4923 return false;
4926 if (!$cm) {
4927 debugging('missing cm', DEBUG_DEVELOPER);
4928 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
4929 print_error('invalidcoursemodule');
4933 if (!$course) {
4934 debugging('missing course', DEBUG_DEVELOPER);
4935 if (!$course = $DB->get_record('course', array('id' => $forum->course))) {
4936 print_error('invalidcourseid');
4940 if (!$context) {
4941 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
4944 // normal users with temporary guest access can not post, suspended users can not post either
4945 if (!is_viewing($context, $user->id) and !is_enrolled($context, $user->id, '', true)) {
4946 return false;
4949 if ($forum->type == 'news') {
4950 $capname = 'mod/forum:replynews';
4951 } else {
4952 $capname = 'mod/forum:replypost';
4955 if (!has_capability($capname, $context, $user->id)) {
4956 return false;
4959 if (!$groupmode = groups_get_activity_groupmode($cm, $course)) {
4960 return true;
4963 if (has_capability('moodle/site:accessallgroups', $context)) {
4964 return true;
4967 if ($groupmode == VISIBLEGROUPS) {
4968 if ($discussion->groupid == -1) {
4969 // allow students to reply to all participants discussions - this was not possible in Moodle <1.8
4970 return true;
4972 return groups_is_member($discussion->groupid);
4974 } else {
4975 //separate groups
4976 if ($discussion->groupid == -1) {
4977 return false;
4979 return groups_is_member($discussion->groupid);
4985 * checks to see if a user can view a particular post
4987 * @global object
4988 * @global object
4989 * @uses CONTEXT_MODULE
4990 * @uses SEPARATEGROUPS
4991 * @param object $post
4992 * @param object $course
4993 * @param object $cm
4994 * @param object $forum
4995 * @param object $discussion
4996 * @param object $user
4998 function forum_user_can_view_post($post, $course, $cm, $forum, $discussion, $user=NULL){
5000 global $CFG, $USER;
5002 if (!$user){
5003 $user = $USER;
5006 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
5007 if (!has_capability('mod/forum:viewdiscussion', $modcontext)) {
5008 return false;
5011 // If it's a grouped discussion, make sure the user is a member
5012 if ($discussion->groupid > 0) {
5013 $groupmode = groups_get_activity_groupmode($cm);
5014 if ($groupmode == SEPARATEGROUPS) {
5015 return groups_is_member($discussion->groupid) || has_capability('moodle/site:accessallgroups', $modcontext);
5018 return true;
5023 * @global object
5024 * @global object
5025 * @uses DEBUG_DEVELOPER
5026 * @param object $forum
5027 * @param object $discussion
5028 * @param object $context
5029 * @param object $user
5030 * @return bool
5032 function forum_user_can_see_discussion($forum, $discussion, $context, $user=NULL) {
5033 global $USER, $DB;
5035 if (empty($user) || empty($user->id)) {
5036 $user = $USER;
5039 // retrieve objects (yuk)
5040 if (is_numeric($forum)) {
5041 debugging('missing full forum', DEBUG_DEVELOPER);
5042 if (!$forum = $DB->get_record('forum',array('id'=>$forum))) {
5043 return false;
5046 if (is_numeric($discussion)) {
5047 debugging('missing full discussion', DEBUG_DEVELOPER);
5048 if (!$discussion = $DB->get_record('forum_discussions',array('id'=>$discussion))) {
5049 return false;
5053 if (!has_capability('mod/forum:viewdiscussion', $context)) {
5054 return false;
5057 if ($forum->type == 'qanda' &&
5058 !forum_user_has_posted($forum->id, $discussion->id, $user->id) &&
5059 !has_capability('mod/forum:viewqandawithoutposting', $context)) {
5060 return false;
5062 return true;
5067 * @global object
5068 * @global object
5069 * @param object $forum
5070 * @param object $discussion
5071 * @param object $post
5072 * @param object $user
5073 * @param object $cm
5074 * @return bool
5076 function forum_user_can_see_post($forum, $discussion, $post, $user=NULL, $cm=NULL) {
5077 global $CFG, $USER, $DB;
5079 // retrieve objects (yuk)
5080 if (is_numeric($forum)) {
5081 debugging('missing full forum', DEBUG_DEVELOPER);
5082 if (!$forum = $DB->get_record('forum',array('id'=>$forum))) {
5083 return false;
5087 if (is_numeric($discussion)) {
5088 debugging('missing full discussion', DEBUG_DEVELOPER);
5089 if (!$discussion = $DB->get_record('forum_discussions',array('id'=>$discussion))) {
5090 return false;
5093 if (is_numeric($post)) {
5094 debugging('missing full post', DEBUG_DEVELOPER);
5095 if (!$post = $DB->get_record('forum_posts',array('id'=>$post))) {
5096 return false;
5099 if (!isset($post->id) && isset($post->parent)) {
5100 $post->id = $post->parent;
5103 if (!$cm) {
5104 debugging('missing cm', DEBUG_DEVELOPER);
5105 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5106 print_error('invalidcoursemodule');
5110 if (empty($user) || empty($user->id)) {
5111 $user = $USER;
5114 $canviewdiscussion = !empty($cm->cache->caps['mod/forum:viewdiscussion']) || has_capability('mod/forum:viewdiscussion', get_context_instance(CONTEXT_MODULE, $cm->id), $user->id);
5115 if (!$canviewdiscussion && !has_all_capabilities(array('moodle/user:viewdetails', 'moodle/user:readuserposts'), get_context_instance(CONTEXT_USER, $post->userid))) {
5116 return false;
5119 if (isset($cm->uservisible)) {
5120 if (!$cm->uservisible) {
5121 return false;
5123 } else {
5124 if (!coursemodule_visible_for_user($cm, $user->id)) {
5125 return false;
5129 if ($forum->type == 'qanda') {
5130 $firstpost = forum_get_firstpost_from_discussion($discussion->id);
5131 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
5132 $userfirstpost = forum_get_user_posted_time($discussion->id, $user->id);
5134 return (($userfirstpost !== false && (time() - $userfirstpost >= $CFG->maxeditingtime)) ||
5135 $firstpost->id == $post->id || $post->userid == $user->id || $firstpost->userid == $user->id ||
5136 has_capability('mod/forum:viewqandawithoutposting', $modcontext, $user->id, false));
5138 return true;
5143 * Prints the discussion view screen for a forum.
5145 * @global object
5146 * @global object
5147 * @param object $course The current course object.
5148 * @param object $forum Forum to be printed.
5149 * @param int $maxdiscussions .
5150 * @param string $displayformat The display format to use (optional).
5151 * @param string $sort Sort arguments for database query (optional).
5152 * @param int $groupmode Group mode of the forum (optional).
5153 * @param void $unused (originally current group)
5154 * @param int $page Page mode, page to display (optional).
5155 * @param int $perpage The maximum number of discussions per page(optional)
5158 function forum_print_latest_discussions($course, $forum, $maxdiscussions=-1, $displayformat='plain', $sort='',
5159 $currentgroup=-1, $groupmode=-1, $page=-1, $perpage=100, $cm=NULL) {
5160 global $CFG, $USER, $OUTPUT;
5162 if (!$cm) {
5163 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5164 print_error('invalidcoursemodule');
5167 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
5169 if (empty($sort)) {
5170 $sort = "d.timemodified DESC";
5173 $olddiscussionlink = false;
5175 // Sort out some defaults
5176 if ($perpage <= 0) {
5177 $perpage = 0;
5178 $page = -1;
5181 if ($maxdiscussions == 0) {
5182 // all discussions - backwards compatibility
5183 $page = -1;
5184 $perpage = 0;
5185 if ($displayformat == 'plain') {
5186 $displayformat = 'header'; // Abbreviate display by default
5189 } else if ($maxdiscussions > 0) {
5190 $page = -1;
5191 $perpage = $maxdiscussions;
5194 $fullpost = false;
5195 if ($displayformat == 'plain') {
5196 $fullpost = true;
5200 // Decide if current user is allowed to see ALL the current discussions or not
5202 // First check the group stuff
5203 if ($currentgroup == -1 or $groupmode == -1) {
5204 $groupmode = groups_get_activity_groupmode($cm, $course);
5205 $currentgroup = groups_get_activity_group($cm);
5208 $groups = array(); //cache
5210 // If the user can post discussions, then this is a good place to put the
5211 // button for it. We do not show the button if we are showing site news
5212 // and the current user is a guest.
5214 $canstart = forum_user_can_post_discussion($forum, $currentgroup, $groupmode, $cm, $context);
5215 if (!$canstart and $forum->type !== 'news') {
5216 if (isguestuser() or !isloggedin()) {
5217 $canstart = true;
5219 if (!is_enrolled($context) and !is_viewing($context)) {
5220 // allow guests and not-logged-in to see the button - they are prompted to log in after clicking the link
5221 // normal users with temporary guest access see this button too, they are asked to enrol instead
5222 // do not show the button to users with suspended enrolments here
5223 $canstart = enrol_selfenrol_available($course->id);
5227 if ($canstart) {
5228 echo '<div class="singlebutton forumaddnew">';
5229 echo "<form id=\"newdiscussionform\" method=\"get\" action=\"$CFG->wwwroot/mod/forum/post.php\">";
5230 echo '<div>';
5231 echo "<input type=\"hidden\" name=\"forum\" value=\"$forum->id\" />";
5232 switch ($forum->type) {
5233 case 'news':
5234 case 'blog':
5235 $buttonadd = get_string('addanewtopic', 'forum');
5236 break;
5237 case 'qanda':
5238 $buttonadd = get_string('addanewquestion', 'forum');
5239 break;
5240 default:
5241 $buttonadd = get_string('addanewdiscussion', 'forum');
5242 break;
5244 echo '<input type="submit" value="'.$buttonadd.'" />';
5245 echo '</div>';
5246 echo '</form>';
5247 echo "</div>\n";
5249 } else if (isguestuser() or !isloggedin() or $forum->type == 'news') {
5250 // no button and no info
5252 } else if ($groupmode and has_capability('mod/forum:startdiscussion', $context)) {
5253 // inform users why they can not post new discussion
5254 if ($currentgroup) {
5255 echo $OUTPUT->notification(get_string('cannotadddiscussion', 'forum'));
5256 } else {
5257 echo $OUTPUT->notification(get_string('cannotadddiscussionall', 'forum'));
5261 // Get all the recent discussions we're allowed to see
5263 $getuserlastmodified = ($displayformat == 'header');
5265 if (! $discussions = forum_get_discussions($cm, $sort, $fullpost, null, $maxdiscussions, $getuserlastmodified, $page, $perpage) ) {
5266 echo '<div class="forumnodiscuss">';
5267 if ($forum->type == 'news') {
5268 echo '('.get_string('nonews', 'forum').')';
5269 } else if ($forum->type == 'qanda') {
5270 echo '('.get_string('noquestions','forum').')';
5271 } else {
5272 echo '('.get_string('nodiscussions', 'forum').')';
5274 echo "</div>\n";
5275 return;
5278 // If we want paging
5279 if ($page != -1) {
5280 ///Get the number of discussions found
5281 $numdiscussions = forum_get_discussions_count($cm);
5283 ///Show the paging bar
5284 echo $OUTPUT->paging_bar($numdiscussions, $page, $perpage, "view.php?f=$forum->id");
5285 if ($numdiscussions > 1000) {
5286 // saves some memory on sites with very large forums
5287 $replies = forum_count_discussion_replies($forum->id, $sort, $maxdiscussions, $page, $perpage);
5288 } else {
5289 $replies = forum_count_discussion_replies($forum->id);
5292 } else {
5293 $replies = forum_count_discussion_replies($forum->id);
5295 if ($maxdiscussions > 0 and $maxdiscussions <= count($discussions)) {
5296 $olddiscussionlink = true;
5300 $canviewparticipants = has_capability('moodle/course:viewparticipants',$context);
5302 $strdatestring = get_string('strftimerecentfull');
5304 // Check if the forum is tracked.
5305 if ($cantrack = forum_tp_can_track_forums($forum)) {
5306 $forumtracked = forum_tp_is_tracked($forum);
5307 } else {
5308 $forumtracked = false;
5311 if ($forumtracked) {
5312 $unreads = forum_get_discussions_unread($cm);
5313 } else {
5314 $unreads = array();
5317 if ($displayformat == 'header') {
5318 echo '<table cellspacing="0" class="forumheaderlist">';
5319 echo '<thead>';
5320 echo '<tr>';
5321 echo '<th class="header topic" scope="col">'.get_string('discussion', 'forum').'</th>';
5322 echo '<th class="header author" colspan="2" scope="col">'.get_string('startedby', 'forum').'</th>';
5323 if ($groupmode > 0) {
5324 echo '<th class="header group" scope="col">'.get_string('group').'</th>';
5326 if (has_capability('mod/forum:viewdiscussion', $context)) {
5327 echo '<th class="header replies" scope="col">'.get_string('replies', 'forum').'</th>';
5328 // If the forum can be tracked, display the unread column.
5329 if ($cantrack) {
5330 echo '<th class="header replies" scope="col">'.get_string('unread', 'forum');
5331 if ($forumtracked) {
5332 echo '&nbsp;<a title="'.get_string('markallread', 'forum').
5333 '" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
5334 $forum->id.'&amp;mark=read&amp;returnpage=view.php">'.
5335 '<img src="'.$OUTPUT->pix_url('t/clear') . '" class="iconsmall" alt="'.get_string('markallread', 'forum').'" /></a>';
5337 echo '</th>';
5340 echo '<th class="header lastpost" scope="col">'.get_string('lastpost', 'forum').'</th>';
5341 echo '</tr>';
5342 echo '</thead>';
5343 echo '<tbody>';
5346 foreach ($discussions as $discussion) {
5347 if (!empty($replies[$discussion->discussion])) {
5348 $discussion->replies = $replies[$discussion->discussion]->replies;
5349 $discussion->lastpostid = $replies[$discussion->discussion]->lastpostid;
5350 } else {
5351 $discussion->replies = 0;
5354 // SPECIAL CASE: The front page can display a news item post to non-logged in users.
5355 // All posts are read in this case.
5356 if (!$forumtracked) {
5357 $discussion->unread = '-';
5358 } else if (empty($USER)) {
5359 $discussion->unread = 0;
5360 } else {
5361 if (empty($unreads[$discussion->discussion])) {
5362 $discussion->unread = 0;
5363 } else {
5364 $discussion->unread = $unreads[$discussion->discussion];
5368 if (isloggedin()) {
5369 $ownpost = ($discussion->userid == $USER->id);
5370 } else {
5371 $ownpost=false;
5373 // Use discussion name instead of subject of first post
5374 $discussion->subject = $discussion->name;
5376 switch ($displayformat) {
5377 case 'header':
5378 if ($groupmode > 0) {
5379 if (isset($groups[$discussion->groupid])) {
5380 $group = $groups[$discussion->groupid];
5381 } else {
5382 $group = $groups[$discussion->groupid] = groups_get_group($discussion->groupid);
5384 } else {
5385 $group = -1;
5387 forum_print_discussion_header($discussion, $forum, $group, $strdatestring, $cantrack, $forumtracked,
5388 $canviewparticipants, $context);
5389 break;
5390 default:
5391 $link = false;
5393 if ($discussion->replies) {
5394 $link = true;
5395 } else {
5396 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
5397 $link = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
5400 $discussion->forum = $forum->id;
5402 forum_print_post($discussion, $discussion, $forum, $cm, $course, $ownpost, 0, $link, false,
5403 '', null, true, $forumtracked);
5404 break;
5408 if ($displayformat == "header") {
5409 echo '</tbody>';
5410 echo '</table>';
5413 if ($olddiscussionlink) {
5414 if ($forum->type == 'news') {
5415 $strolder = get_string('oldertopics', 'forum');
5416 } else {
5417 $strolder = get_string('olderdiscussions', 'forum');
5419 echo '<div class="forumolddiscuss">';
5420 echo '<a href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'&amp;showall=1">';
5421 echo $strolder.'</a> ...</div>';
5424 if ($page != -1) { ///Show the paging bar
5425 echo $OUTPUT->paging_bar($numdiscussions, $page, $perpage, "view.php?f=$forum->id");
5431 * Prints a forum discussion
5433 * @uses CONTEXT_MODULE
5434 * @uses FORUM_MODE_FLATNEWEST
5435 * @uses FORUM_MODE_FLATOLDEST
5436 * @uses FORUM_MODE_THREADED
5437 * @uses FORUM_MODE_NESTED
5438 * @param stdClass $course
5439 * @param stdClass $cm
5440 * @param stdClass $forum
5441 * @param stdClass $discussion
5442 * @param stdClass $post
5443 * @param int $mode
5444 * @param mixed $canreply
5445 * @param bool $canrate
5447 function forum_print_discussion($course, $cm, $forum, $discussion, $post, $mode, $canreply=NULL, $canrate=false) {
5448 global $USER, $CFG;
5450 require_once($CFG->dirroot.'/rating/lib.php');
5452 $ownpost = (isloggedin() && $USER->id == $post->userid);
5454 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
5455 if ($canreply === NULL) {
5456 $reply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
5457 } else {
5458 $reply = $canreply;
5461 // $cm holds general cache for forum functions
5462 $cm->cache = new stdClass;
5463 $cm->cache->groups = groups_get_all_groups($course->id, 0, $cm->groupingid);
5464 $cm->cache->usersgroups = array();
5466 $posters = array();
5468 // preload all posts - TODO: improve...
5469 if ($mode == FORUM_MODE_FLATNEWEST) {
5470 $sort = "p.created DESC";
5471 } else {
5472 $sort = "p.created ASC";
5475 $forumtracked = forum_tp_is_tracked($forum);
5476 $posts = forum_get_all_discussion_posts($discussion->id, $sort, $forumtracked);
5477 $post = $posts[$post->id];
5479 foreach ($posts as $pid=>$p) {
5480 $posters[$p->userid] = $p->userid;
5483 // preload all groups of ppl that posted in this discussion
5484 if ($postersgroups = groups_get_all_groups($course->id, $posters, $cm->groupingid, 'gm.id, gm.groupid, gm.userid')) {
5485 foreach($postersgroups as $pg) {
5486 if (!isset($cm->cache->usersgroups[$pg->userid])) {
5487 $cm->cache->usersgroups[$pg->userid] = array();
5489 $cm->cache->usersgroups[$pg->userid][$pg->groupid] = $pg->groupid;
5491 unset($postersgroups);
5494 //load ratings
5495 if ($forum->assessed != RATING_AGGREGATE_NONE) {
5496 $ratingoptions = new stdClass;
5497 $ratingoptions->context = $modcontext;
5498 $ratingoptions->component = 'mod_forum';
5499 $ratingoptions->ratingarea = 'post';
5500 $ratingoptions->items = $posts;
5501 $ratingoptions->aggregate = $forum->assessed;//the aggregation method
5502 $ratingoptions->scaleid = $forum->scale;
5503 $ratingoptions->userid = $USER->id;
5504 if ($forum->type == 'single' or !$discussion->id) {
5505 $ratingoptions->returnurl = "$CFG->wwwroot/mod/forum/view.php?id=$cm->id";
5506 } else {
5507 $ratingoptions->returnurl = "$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id";
5509 $ratingoptions->assesstimestart = $forum->assesstimestart;
5510 $ratingoptions->assesstimefinish = $forum->assesstimefinish;
5512 $rm = new rating_manager();
5513 $posts = $rm->get_ratings($ratingoptions);
5517 $post->forum = $forum->id; // Add the forum id to the post object, later used by forum_print_post
5518 $post->forumtype = $forum->type;
5520 $post->subject = format_string($post->subject);
5522 $postread = !empty($post->postread);
5524 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, false,
5525 '', '', $postread, true, $forumtracked);
5527 switch ($mode) {
5528 case FORUM_MODE_FLATOLDEST :
5529 case FORUM_MODE_FLATNEWEST :
5530 default:
5531 forum_print_posts_flat($course, $cm, $forum, $discussion, $post, $mode, $reply, $forumtracked, $posts);
5532 break;
5534 case FORUM_MODE_THREADED :
5535 forum_print_posts_threaded($course, $cm, $forum, $discussion, $post, 0, $reply, $forumtracked, $posts);
5536 break;
5538 case FORUM_MODE_NESTED :
5539 forum_print_posts_nested($course, $cm, $forum, $discussion, $post, $reply, $forumtracked, $posts);
5540 break;
5546 * @global object
5547 * @global object
5548 * @uses FORUM_MODE_FLATNEWEST
5549 * @param object $course
5550 * @param object $cm
5551 * @param object $forum
5552 * @param object $discussion
5553 * @param object $post
5554 * @param object $mode
5555 * @param bool $reply
5556 * @param bool $forumtracked
5557 * @param array $posts
5558 * @return void
5560 function forum_print_posts_flat($course, &$cm, $forum, $discussion, $post, $mode, $reply, $forumtracked, $posts) {
5561 global $USER, $CFG;
5563 $link = false;
5565 if ($mode == FORUM_MODE_FLATNEWEST) {
5566 $sort = "ORDER BY created DESC";
5567 } else {
5568 $sort = "ORDER BY created ASC";
5571 foreach ($posts as $post) {
5572 if (!$post->parent) {
5573 continue;
5575 $post->subject = format_string($post->subject);
5576 $ownpost = ($USER->id == $post->userid);
5578 $postread = !empty($post->postread);
5580 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
5581 '', '', $postread, true, $forumtracked);
5586 * @todo Document this function
5588 * @global object
5589 * @global object
5590 * @uses CONTEXT_MODULE
5591 * @return void
5593 function forum_print_posts_threaded($course, &$cm, $forum, $discussion, $parent, $depth, $reply, $forumtracked, $posts) {
5594 global $USER, $CFG;
5596 $link = false;
5598 if (!empty($posts[$parent->id]->children)) {
5599 $posts = $posts[$parent->id]->children;
5601 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
5602 $canviewfullnames = has_capability('moodle/site:viewfullnames', $modcontext);
5604 foreach ($posts as $post) {
5606 echo '<div class="indent">';
5607 if ($depth > 0) {
5608 $ownpost = ($USER->id == $post->userid);
5609 $post->subject = format_string($post->subject);
5611 $postread = !empty($post->postread);
5613 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
5614 '', '', $postread, true, $forumtracked);
5615 } else {
5616 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
5617 echo "</div>\n";
5618 continue;
5620 $by = new stdClass();
5621 $by->name = fullname($post, $canviewfullnames);
5622 $by->date = userdate($post->modified);
5624 if ($forumtracked) {
5625 if (!empty($post->postread)) {
5626 $style = '<span class="forumthread read">';
5627 } else {
5628 $style = '<span class="forumthread unread">';
5630 } else {
5631 $style = '<span class="forumthread">';
5633 echo $style."<a name=\"$post->id\"></a>".
5634 "<a href=\"discuss.php?d=$post->discussion&amp;parent=$post->id\">".format_string($post->subject,true)."</a> ";
5635 print_string("bynameondate", "forum", $by);
5636 echo "</span>";
5639 forum_print_posts_threaded($course, $cm, $forum, $discussion, $post, $depth-1, $reply, $forumtracked, $posts);
5640 echo "</div>\n";
5646 * @todo Document this function
5647 * @global object
5648 * @global object
5649 * @return void
5651 function forum_print_posts_nested($course, &$cm, $forum, $discussion, $parent, $reply, $forumtracked, $posts) {
5652 global $USER, $CFG;
5654 $link = false;
5656 if (!empty($posts[$parent->id]->children)) {
5657 $posts = $posts[$parent->id]->children;
5659 foreach ($posts as $post) {
5661 echo '<div class="indent">';
5662 if (!isloggedin()) {
5663 $ownpost = false;
5664 } else {
5665 $ownpost = ($USER->id == $post->userid);
5668 $post->subject = format_string($post->subject);
5669 $postread = !empty($post->postread);
5671 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
5672 '', '', $postread, true, $forumtracked);
5673 forum_print_posts_nested($course, $cm, $forum, $discussion, $post, $reply, $forumtracked, $posts);
5674 echo "</div>\n";
5680 * Returns all forum posts since a given time in specified forum.
5682 * @todo Document this functions args
5683 * @global object
5684 * @global object
5685 * @global object
5686 * @global object
5688 function forum_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
5689 global $CFG, $COURSE, $USER, $DB;
5691 if ($COURSE->id == $courseid) {
5692 $course = $COURSE;
5693 } else {
5694 $course = $DB->get_record('course', array('id' => $courseid));
5697 $modinfo = get_fast_modinfo($course);
5699 $cm = $modinfo->cms[$cmid];
5700 $params = array($timestart, $cm->instance);
5702 if ($userid) {
5703 $userselect = "AND u.id = ?";
5704 $params[] = $userid;
5705 } else {
5706 $userselect = "";
5709 if ($groupid) {
5710 $groupselect = "AND gm.groupid = ?";
5711 $groupjoin = "JOIN {groups_members} gm ON gm.userid=u.id";
5712 $params[] = $groupid;
5713 } else {
5714 $groupselect = "";
5715 $groupjoin = "";
5718 if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
5719 d.timestart, d.timeend, d.userid AS duserid,
5720 u.firstname, u.lastname, u.email, u.picture, u.imagealt, u.email
5721 FROM {forum_posts} p
5722 JOIN {forum_discussions} d ON d.id = p.discussion
5723 JOIN {forum} f ON f.id = d.forum
5724 JOIN {user} u ON u.id = p.userid
5725 $groupjoin
5726 WHERE p.created > ? AND f.id = ?
5727 $userselect $groupselect
5728 ORDER BY p.id ASC", $params)) { // order by initial posting date
5729 return;
5732 $groupmode = groups_get_activity_groupmode($cm, $course);
5733 $cm_context = get_context_instance(CONTEXT_MODULE, $cm->id);
5734 $viewhiddentimed = has_capability('mod/forum:viewhiddentimedposts', $cm_context);
5735 $accessallgroups = has_capability('moodle/site:accessallgroups', $cm_context);
5737 if (is_null($modinfo->groups)) {
5738 $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
5741 $printposts = array();
5742 foreach ($posts as $post) {
5744 if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
5745 and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
5746 if (!$viewhiddentimed) {
5747 continue;
5751 if ($groupmode) {
5752 if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or $accessallgroups) {
5753 // oki (Open discussions have groupid -1)
5754 } else {
5755 // separate mode
5756 if (isguestuser()) {
5757 // shortcut
5758 continue;
5761 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
5762 continue;
5767 $printposts[] = $post;
5770 if (!$printposts) {
5771 return;
5774 $aname = format_string($cm->name,true);
5776 foreach ($printposts as $post) {
5777 $tmpactivity = new stdClass();
5779 $tmpactivity->type = 'forum';
5780 $tmpactivity->cmid = $cm->id;
5781 $tmpactivity->name = $aname;
5782 $tmpactivity->sectionnum = $cm->sectionnum;
5783 $tmpactivity->timestamp = $post->modified;
5785 $tmpactivity->content = new stdClass();
5786 $tmpactivity->content->id = $post->id;
5787 $tmpactivity->content->discussion = $post->discussion;
5788 $tmpactivity->content->subject = format_string($post->subject);
5789 $tmpactivity->content->parent = $post->parent;
5791 $tmpactivity->user = new stdClass();
5792 $tmpactivity->user->id = $post->userid;
5793 $tmpactivity->user->firstname = $post->firstname;
5794 $tmpactivity->user->lastname = $post->lastname;
5795 $tmpactivity->user->picture = $post->picture;
5796 $tmpactivity->user->imagealt = $post->imagealt;
5797 $tmpactivity->user->email = $post->email;
5799 $activities[$index++] = $tmpactivity;
5802 return;
5806 * @todo Document this function
5807 * @global object
5809 function forum_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
5810 global $CFG, $OUTPUT;
5812 if ($activity->content->parent) {
5813 $class = 'reply';
5814 } else {
5815 $class = 'discussion';
5818 echo '<table border="0" cellpadding="3" cellspacing="0" class="forum-recent">';
5820 echo "<tr><td class=\"userpicture\" valign=\"top\">";
5821 echo $OUTPUT->user_picture($activity->user, array('courseid'=>$courseid));
5822 echo "</td><td class=\"$class\">";
5824 echo '<div class="title">';
5825 if ($detail) {
5826 $aname = s($activity->name);
5827 echo "<img src=\"" . $OUTPUT->pix_url('icon', $activity->type) . "\" ".
5828 "class=\"icon\" alt=\"{$aname}\" />";
5830 echo "<a href=\"$CFG->wwwroot/mod/forum/discuss.php?d={$activity->content->discussion}"
5831 ."#p{$activity->content->id}\">{$activity->content->subject}</a>";
5832 echo '</div>';
5834 echo '<div class="user">';
5835 $fullname = fullname($activity->user, $viewfullnames);
5836 echo "<a href=\"$CFG->wwwroot/user/view.php?id={$activity->user->id}&amp;course=$courseid\">"
5837 ."{$fullname}</a> - ".userdate($activity->timestamp);
5838 echo '</div>';
5839 echo "</td></tr></table>";
5841 return;
5845 * recursively sets the discussion field to $discussionid on $postid and all its children
5846 * used when pruning a post
5848 * @global object
5849 * @param int $postid
5850 * @param int $discussionid
5851 * @return bool
5853 function forum_change_discussionid($postid, $discussionid) {
5854 global $DB;
5855 $DB->set_field('forum_posts', 'discussion', $discussionid, array('id' => $postid));
5856 if ($posts = $DB->get_records('forum_posts', array('parent' => $postid))) {
5857 foreach ($posts as $post) {
5858 forum_change_discussionid($post->id, $discussionid);
5861 return true;
5865 * Prints the editing button on subscribers page
5867 * @global object
5868 * @global object
5869 * @param int $courseid
5870 * @param int $forumid
5871 * @return string
5873 function forum_update_subscriptions_button($courseid, $forumid) {
5874 global $CFG, $USER;
5876 if (!empty($USER->subscriptionsediting)) {
5877 $string = get_string('turneditingoff');
5878 $edit = "off";
5879 } else {
5880 $string = get_string('turneditingon');
5881 $edit = "on";
5884 return "<form method=\"get\" action=\"$CFG->wwwroot/mod/forum/subscribers.php\">".
5885 "<input type=\"hidden\" name=\"id\" value=\"$forumid\" />".
5886 "<input type=\"hidden\" name=\"edit\" value=\"$edit\" />".
5887 "<input type=\"submit\" value=\"$string\" /></form>";
5891 * This function gets run whenever user is enrolled into course
5893 * @param stdClass $cp
5894 * @return void
5896 function forum_user_enrolled($cp) {
5897 global $DB;
5899 // NOTE: this has to be as fast as possible - we do not want to slow down enrolments!
5900 // Originally there used to be 'mod/forum:initialsubscriptions' which was
5901 // introduced because we did not have enrolment information in earlier versions...
5903 $sql = "SELECT f.id
5904 FROM {forum} f
5905 LEFT JOIN {forum_subscriptions} fs ON (fs.forum = f.id AND fs.userid = :userid)
5906 WHERE f.course = :courseid AND f.forcesubscribe = :initial AND fs.id IS NULL";
5907 $params = array('courseid'=>$cp->courseid, 'userid'=>$cp->userid, 'initial'=>FORUM_INITIALSUBSCRIBE);
5909 $forums = $DB->get_records_sql($sql, $params);
5910 foreach ($forums as $forum) {
5911 forum_subscribe($cp->userid, $forum->id);
5916 * This function gets run whenever user is unenrolled from course
5918 * @param stdClass $cp
5919 * @return void
5921 function forum_user_unenrolled($cp) {
5922 global $DB;
5924 // NOTE: this has to be as fast as possible!
5926 if ($cp->lastenrol) {
5927 $params = array('userid'=>$cp->userid, 'courseid'=>$cp->courseid);
5928 $forumselect = "IN (SELECT f.id FROM {forum} f WHERE f.course = :courseid)";
5930 $DB->delete_records_select('forum_subscriptions', "userid = :userid AND forum $forumselect", $params);
5931 $DB->delete_records_select('forum_track_prefs', "userid = :userid AND forumid $forumselect", $params);
5932 $DB->delete_records_select('forum_read', "userid = :userid AND forumid $forumselect", $params);
5936 // Functions to do with read tracking.
5939 * Mark posts as read.
5941 * @global object
5942 * @global object
5943 * @param object $user object
5944 * @param array $postids array of post ids
5945 * @return boolean success
5947 function forum_tp_mark_posts_read($user, $postids) {
5948 global $CFG, $DB;
5950 if (!forum_tp_can_track_forums(false, $user)) {
5951 return true;
5954 $status = true;
5956 $now = time();
5957 $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
5959 if (empty($postids)) {
5960 return true;
5962 } else if (count($postids) > 200) {
5963 while ($part = array_splice($postids, 0, 200)) {
5964 $status = forum_tp_mark_posts_read($user, $part) && $status;
5966 return $status;
5969 list($usql, $params) = $DB->get_in_or_equal($postids);
5970 $params[] = $user->id;
5972 $sql = "SELECT id
5973 FROM {forum_read}
5974 WHERE postid $usql AND userid = ?";
5975 if ($existing = $DB->get_records_sql($sql, $params)) {
5976 $existing = array_keys($existing);
5977 } else {
5978 $existing = array();
5981 $new = array_diff($postids, $existing);
5983 if ($new) {
5984 list($usql, $new_params) = $DB->get_in_or_equal($new);
5985 $params = array($user->id, $now, $now, $user->id);
5986 $params = array_merge($params, $new_params);
5987 $params[] = $cutoffdate;
5989 $sql = "INSERT INTO {forum_read} (userid, postid, discussionid, forumid, firstread, lastread)
5991 SELECT ?, p.id, p.discussion, d.forum, ?, ?
5992 FROM {forum_posts} p
5993 JOIN {forum_discussions} d ON d.id = p.discussion
5994 JOIN {forum} f ON f.id = d.forum
5995 LEFT JOIN {forum_track_prefs} tf ON (tf.userid = ? AND tf.forumid = f.id)
5996 WHERE p.id $usql
5997 AND p.modified >= ?
5998 AND (f.trackingtype = ".FORUM_TRACKING_ON."
5999 OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND tf.id IS NULL))";
6000 $status = $DB->execute($sql, $params) && $status;
6003 if ($existing) {
6004 list($usql, $new_params) = $DB->get_in_or_equal($existing);
6005 $params = array($now, $user->id);
6006 $params = array_merge($params, $new_params);
6008 $sql = "UPDATE {forum_read}
6009 SET lastread = ?
6010 WHERE userid = ? AND postid $usql";
6011 $status = $DB->execute($sql, $params) && $status;
6014 return $status;
6018 * Mark post as read.
6019 * @global object
6020 * @global object
6021 * @param int $userid
6022 * @param int $postid
6024 function forum_tp_add_read_record($userid, $postid) {
6025 global $CFG, $DB;
6027 $now = time();
6028 $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
6030 if (!$DB->record_exists('forum_read', array('userid' => $userid, 'postid' => $postid))) {
6031 $sql = "INSERT INTO {forum_read} (userid, postid, discussionid, forumid, firstread, lastread)
6033 SELECT ?, p.id, p.discussion, d.forum, ?, ?
6034 FROM {forum_posts} p
6035 JOIN {forum_discussions} d ON d.id = p.discussion
6036 WHERE p.id = ? AND p.modified >= ?";
6037 return $DB->execute($sql, array($userid, $now, $now, $postid, $cutoffdate));
6039 } else {
6040 $sql = "UPDATE {forum_read}
6041 SET lastread = ?
6042 WHERE userid = ? AND postid = ?";
6043 return $DB->execute($sql, array($now, $userid, $userid));
6048 * Returns all records in the 'forum_read' table matching the passed keys, indexed
6049 * by userid.
6051 * @global object
6052 * @param int $userid
6053 * @param int $postid
6054 * @param int $discussionid
6055 * @param int $forumid
6056 * @return array
6058 function forum_tp_get_read_records($userid=-1, $postid=-1, $discussionid=-1, $forumid=-1) {
6059 global $DB;
6060 $select = '';
6061 $params = array();
6063 if ($userid > -1) {
6064 if ($select != '') $select .= ' AND ';
6065 $select .= 'userid = ?';
6066 $params[] = $userid;
6068 if ($postid > -1) {
6069 if ($select != '') $select .= ' AND ';
6070 $select .= 'postid = ?';
6071 $params[] = $postid;
6073 if ($discussionid > -1) {
6074 if ($select != '') $select .= ' AND ';
6075 $select .= 'discussionid = ?';
6076 $params[] = $discussionid;
6078 if ($forumid > -1) {
6079 if ($select != '') $select .= ' AND ';
6080 $select .= 'forumid = ?';
6081 $params[] = $forumid;
6084 return $DB->get_records_select('forum_read', $select, $params);
6088 * Returns all read records for the provided user and discussion, indexed by postid.
6090 * @global object
6091 * @param inti $userid
6092 * @param int $discussionid
6094 function forum_tp_get_discussion_read_records($userid, $discussionid) {
6095 global $DB;
6096 $select = 'userid = ? AND discussionid = ?';
6097 $fields = 'postid, firstread, lastread';
6098 return $DB->get_records_select('forum_read', $select, array($userid, $discussionid), '', $fields);
6102 * If its an old post, do nothing. If the record exists, the maintenance will clear it up later.
6104 * @return bool
6106 function forum_tp_mark_post_read($userid, $post, $forumid) {
6107 if (!forum_tp_is_post_old($post)) {
6108 return forum_tp_add_read_record($userid, $post->id);
6109 } else {
6110 return true;
6115 * Marks a whole forum as read, for a given user
6117 * @global object
6118 * @global object
6119 * @param object $user
6120 * @param int $forumid
6121 * @param int|bool $groupid
6122 * @return bool
6124 function forum_tp_mark_forum_read($user, $forumid, $groupid=false) {
6125 global $CFG, $DB;
6127 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6129 $groupsel = "";
6130 $params = array($user->id, $forumid, $cutoffdate);
6132 if ($groupid !== false) {
6133 $groupsel = " AND (d.groupid = ? OR d.groupid = -1)";
6134 $params[] = $groupid;
6137 $sql = "SELECT p.id
6138 FROM {forum_posts} p
6139 LEFT JOIN {forum_discussions} d ON d.id = p.discussion
6140 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6141 WHERE d.forum = ?
6142 AND p.modified >= ? AND r.id is NULL
6143 $groupsel";
6145 if ($posts = $DB->get_records_sql($sql, $params)) {
6146 $postids = array_keys($posts);
6147 return forum_tp_mark_posts_read($user, $postids);
6150 return true;
6154 * Marks a whole discussion as read, for a given user
6156 * @global object
6157 * @global object
6158 * @param object $user
6159 * @param int $discussionid
6160 * @return bool
6162 function forum_tp_mark_discussion_read($user, $discussionid) {
6163 global $CFG, $DB;
6165 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6167 $sql = "SELECT p.id
6168 FROM {forum_posts} p
6169 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6170 WHERE p.discussion = ?
6171 AND p.modified >= ? AND r.id is NULL";
6173 if ($posts = $DB->get_records_sql($sql, array($user->id, $discussionid, $cutoffdate))) {
6174 $postids = array_keys($posts);
6175 return forum_tp_mark_posts_read($user, $postids);
6178 return true;
6182 * @global object
6183 * @param int $userid
6184 * @param object $post
6186 function forum_tp_is_post_read($userid, $post) {
6187 global $DB;
6188 return (forum_tp_is_post_old($post) ||
6189 $DB->record_exists('forum_read', array('userid' => $userid, 'postid' => $post->id)));
6193 * @global object
6194 * @param object $post
6195 * @param int $time Defautls to time()
6197 function forum_tp_is_post_old($post, $time=null) {
6198 global $CFG;
6200 if (is_null($time)) {
6201 $time = time();
6203 return ($post->modified < ($time - ($CFG->forum_oldpostdays * 24 * 3600)));
6207 * Returns the count of records for the provided user and discussion.
6209 * @global object
6210 * @global object
6211 * @param int $userid
6212 * @param int $discussionid
6213 * @return bool
6215 function forum_tp_count_discussion_read_records($userid, $discussionid) {
6216 global $CFG, $DB;
6218 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
6220 $sql = 'SELECT COUNT(DISTINCT p.id) '.
6221 'FROM {forum_discussions} d '.
6222 'LEFT JOIN {forum_read} r ON d.id = r.discussionid AND r.userid = ? '.
6223 'LEFT JOIN {forum_posts} p ON p.discussion = d.id '.
6224 'AND (p.modified < ? OR p.id = r.postid) '.
6225 'WHERE d.id = ? ';
6227 return ($DB->count_records_sql($sql, array($userid, $cutoffdate, $discussionid)));
6231 * Returns the count of records for the provided user and discussion.
6233 * @global object
6234 * @global object
6235 * @param int $userid
6236 * @param int $discussionid
6237 * @return int
6239 function forum_tp_count_discussion_unread_posts($userid, $discussionid) {
6240 global $CFG, $DB;
6242 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
6244 $sql = 'SELECT COUNT(p.id) '.
6245 'FROM {forum_posts} p '.
6246 'LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? '.
6247 'WHERE p.discussion = ? '.
6248 'AND p.modified >= ? AND r.id is NULL';
6250 return $DB->count_records_sql($sql, array($userid, $cutoffdate, $discussionid));
6254 * Returns the count of posts for the provided forum and [optionally] group.
6255 * @global object
6256 * @global object
6257 * @param int $forumid
6258 * @param int|bool $groupid
6259 * @return int
6261 function forum_tp_count_forum_posts($forumid, $groupid=false) {
6262 global $CFG, $DB;
6263 $params = array($forumid);
6264 $sql = 'SELECT COUNT(*) '.
6265 'FROM {forum_posts} fp,{forum_discussions} fd '.
6266 'WHERE fd.forum = ? AND fp.discussion = fd.id';
6267 if ($groupid !== false) {
6268 $sql .= ' AND (fd.groupid = ? OR fd.groupid = -1)';
6269 $params[] = $groupid;
6271 $count = $DB->count_records_sql($sql, $params);
6274 return $count;
6278 * Returns the count of records for the provided user and forum and [optionally] group.
6279 * @global object
6280 * @global object
6281 * @param int $userid
6282 * @param int $forumid
6283 * @param int|bool $groupid
6284 * @return int
6286 function forum_tp_count_forum_read_records($userid, $forumid, $groupid=false) {
6287 global $CFG, $DB;
6289 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6291 $groupsel = '';
6292 $params = array($userid, $forumid, $cutoffdate);
6293 if ($groupid !== false) {
6294 $groupsel = "AND (d.groupid = ? OR d.groupid = -1)";
6295 $params[] = $groupid;
6298 $sql = "SELECT COUNT(p.id)
6299 FROM {forum_posts} p
6300 JOIN {forum_discussions} d ON d.id = p.discussion
6301 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid= ?)
6302 WHERE d.forum = ?
6303 AND (p.modified < $cutoffdate OR (p.modified >= ? AND r.id IS NOT NULL))
6304 $groupsel";
6306 return $DB->get_field_sql($sql, $params);
6310 * Returns the count of records for the provided user and course.
6311 * Please note that group access is ignored!
6313 * @global object
6314 * @global object
6315 * @param int $userid
6316 * @param int $courseid
6317 * @return array
6319 function forum_tp_get_course_unread_posts($userid, $courseid) {
6320 global $CFG, $DB;
6322 $now = round(time(), -2); // db cache friendliness
6323 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
6324 $params = array($userid, $userid, $courseid, $cutoffdate);
6326 if (!empty($CFG->forum_enabletimedposts)) {
6327 $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
6328 $params[] = $now;
6329 $params[] = $now;
6330 } else {
6331 $timedsql = "";
6334 $sql = "SELECT f.id, COUNT(p.id) AS unread
6335 FROM {forum_posts} p
6336 JOIN {forum_discussions} d ON d.id = p.discussion
6337 JOIN {forum} f ON f.id = d.forum
6338 JOIN {course} c ON c.id = f.course
6339 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6340 LEFT JOIN {forum_track_prefs} tf ON (tf.userid = ? AND tf.forumid = f.id)
6341 WHERE f.course = ?
6342 AND p.modified >= ? AND r.id is NULL
6343 AND (f.trackingtype = ".FORUM_TRACKING_ON."
6344 OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND tf.id IS NULL))
6345 $timedsql
6346 GROUP BY f.id";
6348 if ($return = $DB->get_records_sql($sql, $params)) {
6349 return $return;
6352 return array();
6356 * Returns the count of records for the provided user and forum and [optionally] group.
6358 * @global object
6359 * @global object
6360 * @global object
6361 * @param object $cm
6362 * @param object $course
6363 * @return int
6365 function forum_tp_count_forum_unread_posts($cm, $course) {
6366 global $CFG, $USER, $DB;
6368 static $readcache = array();
6370 $forumid = $cm->instance;
6372 if (!isset($readcache[$course->id])) {
6373 $readcache[$course->id] = array();
6374 if ($counts = forum_tp_get_course_unread_posts($USER->id, $course->id)) {
6375 foreach ($counts as $count) {
6376 $readcache[$course->id][$count->id] = $count->unread;
6381 if (empty($readcache[$course->id][$forumid])) {
6382 // no need to check group mode ;-)
6383 return 0;
6386 $groupmode = groups_get_activity_groupmode($cm, $course);
6388 if ($groupmode != SEPARATEGROUPS) {
6389 return $readcache[$course->id][$forumid];
6392 if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
6393 return $readcache[$course->id][$forumid];
6396 require_once($CFG->dirroot.'/course/lib.php');
6398 $modinfo = get_fast_modinfo($course);
6399 if (is_null($modinfo->groups)) {
6400 $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
6403 $mygroups = $modinfo->groups[$cm->groupingid];
6405 // add all groups posts
6406 if (empty($mygroups)) {
6407 $mygroups = array(-1=>-1);
6408 } else {
6409 $mygroups[-1] = -1;
6412 list ($groups_sql, $groups_params) = $DB->get_in_or_equal($mygroups);
6414 $now = round(time(), -2); // db cache friendliness
6415 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
6416 $params = array($USER->id, $forumid, $cutoffdate);
6418 if (!empty($CFG->forum_enabletimedposts)) {
6419 $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
6420 $params[] = $now;
6421 $params[] = $now;
6422 } else {
6423 $timedsql = "";
6426 $params = array_merge($params, $groups_params);
6428 $sql = "SELECT COUNT(p.id)
6429 FROM {forum_posts} p
6430 JOIN {forum_discussions} d ON p.discussion = d.id
6431 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6432 WHERE d.forum = ?
6433 AND p.modified >= ? AND r.id is NULL
6434 $timedsql
6435 AND d.groupid $groups_sql";
6437 return $DB->get_field_sql($sql, $params);
6441 * Deletes read records for the specified index. At least one parameter must be specified.
6443 * @global object
6444 * @param int $userid
6445 * @param int $postid
6446 * @param int $discussionid
6447 * @param int $forumid
6448 * @return bool
6450 function forum_tp_delete_read_records($userid=-1, $postid=-1, $discussionid=-1, $forumid=-1) {
6451 global $DB;
6452 $params = array();
6454 $select = '';
6455 if ($userid > -1) {
6456 if ($select != '') $select .= ' AND ';
6457 $select .= 'userid = ?';
6458 $params[] = $userid;
6460 if ($postid > -1) {
6461 if ($select != '') $select .= ' AND ';
6462 $select .= 'postid = ?';
6463 $params[] = $postid;
6465 if ($discussionid > -1) {
6466 if ($select != '') $select .= ' AND ';
6467 $select .= 'discussionid = ?';
6468 $params[] = $discussionid;
6470 if ($forumid > -1) {
6471 if ($select != '') $select .= ' AND ';
6472 $select .= 'forumid = ?';
6473 $params[] = $forumid;
6475 if ($select == '') {
6476 return false;
6478 else {
6479 return $DB->delete_records_select('forum_read', $select, $params);
6483 * Get a list of forums not tracked by the user.
6485 * @global object
6486 * @global object
6487 * @param int $userid The id of the user to use.
6488 * @param int $courseid The id of the course being checked.
6489 * @return mixed An array indexed by forum id, or false.
6491 function forum_tp_get_untracked_forums($userid, $courseid) {
6492 global $CFG, $DB;
6494 $sql = "SELECT f.id
6495 FROM {forum} f
6496 LEFT JOIN {forum_track_prefs} ft ON (ft.forumid = f.id AND ft.userid = ?)
6497 WHERE f.course = ?
6498 AND (f.trackingtype = ".FORUM_TRACKING_OFF."
6499 OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND ft.id IS NOT NULL))";
6501 if ($forums = $DB->get_records_sql($sql, array($userid, $courseid))) {
6502 foreach ($forums as $forum) {
6503 $forums[$forum->id] = $forum;
6505 return $forums;
6507 } else {
6508 return array();
6513 * Determine if a user can track forums and optionally a particular forum.
6514 * Checks the site settings, the user settings and the forum settings (if
6515 * requested).
6517 * @global object
6518 * @global object
6519 * @global object
6520 * @param mixed $forum The forum object to test, or the int id (optional).
6521 * @param mixed $userid The user object to check for (optional).
6522 * @return boolean
6524 function forum_tp_can_track_forums($forum=false, $user=false) {
6525 global $USER, $CFG, $DB;
6527 // if possible, avoid expensive
6528 // queries
6529 if (empty($CFG->forum_trackreadposts)) {
6530 return false;
6533 if ($user === false) {
6534 $user = $USER;
6537 if (isguestuser($user) or empty($user->id)) {
6538 return false;
6541 if ($forum === false) {
6542 // general abitily to track forums
6543 return (bool)$user->trackforums;
6547 // Work toward always passing an object...
6548 if (is_numeric($forum)) {
6549 debugging('Better use proper forum object.', DEBUG_DEVELOPER);
6550 $forum = $DB->get_record('forum', array('id' => $forum), '', 'id,trackingtype');
6553 $forumallows = ($forum->trackingtype == FORUM_TRACKING_OPTIONAL);
6554 $forumforced = ($forum->trackingtype == FORUM_TRACKING_ON);
6556 return ($forumforced || $forumallows) && !empty($user->trackforums);
6560 * Tells whether a specific forum is tracked by the user. A user can optionally
6561 * be specified. If not specified, the current user is assumed.
6563 * @global object
6564 * @global object
6565 * @global object
6566 * @param mixed $forum If int, the id of the forum being checked; if object, the forum object
6567 * @param int $userid The id of the user being checked (optional).
6568 * @return boolean
6570 function forum_tp_is_tracked($forum, $user=false) {
6571 global $USER, $CFG, $DB;
6573 if ($user === false) {
6574 $user = $USER;
6577 if (isguestuser($user) or empty($user->id)) {
6578 return false;
6581 // Work toward always passing an object...
6582 if (is_numeric($forum)) {
6583 debugging('Better use proper forum object.', DEBUG_DEVELOPER);
6584 $forum = $DB->get_record('forum', array('id' => $forum));
6587 if (!forum_tp_can_track_forums($forum, $user)) {
6588 return false;
6591 $forumallows = ($forum->trackingtype == FORUM_TRACKING_OPTIONAL);
6592 $forumforced = ($forum->trackingtype == FORUM_TRACKING_ON);
6594 return $forumforced ||
6595 ($forumallows && $DB->get_record('forum_track_prefs', array('userid' => $user->id, 'forumid' => $forum->id)) === false);
6599 * @global object
6600 * @global object
6601 * @param int $forumid
6602 * @param int $userid
6604 function forum_tp_start_tracking($forumid, $userid=false) {
6605 global $USER, $DB;
6607 if ($userid === false) {
6608 $userid = $USER->id;
6611 return $DB->delete_records('forum_track_prefs', array('userid' => $userid, 'forumid' => $forumid));
6615 * @global object
6616 * @global object
6617 * @param int $forumid
6618 * @param int $userid
6620 function forum_tp_stop_tracking($forumid, $userid=false) {
6621 global $USER, $DB;
6623 if ($userid === false) {
6624 $userid = $USER->id;
6627 if (!$DB->record_exists('forum_track_prefs', array('userid' => $userid, 'forumid' => $forumid))) {
6628 $track_prefs = new stdClass();
6629 $track_prefs->userid = $userid;
6630 $track_prefs->forumid = $forumid;
6631 $DB->insert_record('forum_track_prefs', $track_prefs);
6634 return forum_tp_delete_read_records($userid, -1, -1, $forumid);
6639 * Clean old records from the forum_read table.
6640 * @global object
6641 * @global object
6642 * @return void
6644 function forum_tp_clean_read_records() {
6645 global $CFG, $DB;
6647 if (!isset($CFG->forum_oldpostdays)) {
6648 return;
6650 // Look for records older than the cutoffdate that are still in the forum_read table.
6651 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6653 //first get the oldest tracking present - we need tis to speedup the next delete query
6654 $sql = "SELECT MIN(fp.modified) AS first
6655 FROM {forum_posts} fp
6656 JOIN {forum_read} fr ON fr.postid=fp.id";
6657 if (!$first = $DB->get_field_sql($sql)) {
6658 // nothing to delete;
6659 return;
6662 // now delete old tracking info
6663 $sql = "DELETE
6664 FROM {forum_read}
6665 WHERE postid IN (SELECT fp.id
6666 FROM {forum_posts} fp
6667 WHERE fp.modified >= ? AND fp.modified < ?)";
6668 $DB->execute($sql, array($first, $cutoffdate));
6672 * Sets the last post for a given discussion
6674 * @global object
6675 * @global object
6676 * @param into $discussionid
6677 * @return bool|int
6679 function forum_discussion_update_last_post($discussionid) {
6680 global $CFG, $DB;
6682 // Check the given discussion exists
6683 if (!$DB->record_exists('forum_discussions', array('id' => $discussionid))) {
6684 return false;
6687 // Use SQL to find the last post for this discussion
6688 $sql = "SELECT id, userid, modified
6689 FROM {forum_posts}
6690 WHERE discussion=?
6691 ORDER BY modified DESC";
6693 // Lets go find the last post
6694 if (($lastposts = $DB->get_records_sql($sql, array($discussionid), 0, 1))) {
6695 $lastpost = reset($lastposts);
6696 $discussionobject = new stdClass();
6697 $discussionobject->id = $discussionid;
6698 $discussionobject->usermodified = $lastpost->userid;
6699 $discussionobject->timemodified = $lastpost->modified;
6700 $DB->update_record('forum_discussions', $discussionobject);
6701 return $lastpost->id;
6704 // To get here either we couldn't find a post for the discussion (weird)
6705 // or we couldn't update the discussion record (weird x2)
6706 return false;
6711 * @return array
6713 function forum_get_view_actions() {
6714 return array('view discussion', 'search', 'forum', 'forums', 'subscribers', 'view forum');
6718 * @return array
6720 function forum_get_post_actions() {
6721 return array('add discussion','add post','delete discussion','delete post','move discussion','prune post','update post');
6725 * this function returns all the separate forum ids, given a courseid
6727 * @global object
6728 * @global object
6729 * @param int $courseid
6730 * @return array
6732 function forum_get_separate_modules($courseid) {
6734 global $CFG,$DB;
6735 $forummodule = $DB->get_record("modules", array("name" => "forum"));
6737 $sql = 'SELECT f.id, f.id FROM {forum} f, {course_modules} cm WHERE
6738 f.id = cm.instance AND cm.module =? AND cm.visible = 1 AND cm.course = ?
6739 AND cm.groupmode ='.SEPARATEGROUPS;
6741 return $DB->get_records_sql($sql, array($forummodule->id, $courseid));
6746 * @global object
6747 * @global object
6748 * @global object
6749 * @param object $forum
6750 * @param object $cm
6751 * @return bool
6753 function forum_check_throttling($forum, $cm=null) {
6754 global $USER, $CFG, $DB, $OUTPUT;
6756 if (is_numeric($forum)) {
6757 $forum = $DB->get_record('forum',array('id'=>$forum));
6759 if (!is_object($forum)) {
6760 return false; // this is broken.
6763 if (empty($forum->blockafter)) {
6764 return true;
6767 if (empty($forum->blockperiod)) {
6768 return true;
6771 if (!$cm) {
6772 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
6773 print_error('invalidcoursemodule');
6777 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
6778 if(has_capability('mod/forum:postwithoutthrottling', $modcontext)) {
6779 return true;
6782 // get the number of posts in the last period we care about
6783 $timenow = time();
6784 $timeafter = $timenow - $forum->blockperiod;
6786 $numposts = $DB->count_records_sql('SELECT COUNT(p.id) FROM {forum_posts} p'
6787 .' JOIN {forum_discussions} d'
6788 .' ON p.discussion = d.id WHERE d.forum = ?'
6789 .' AND p.userid = ? AND p.created > ?', array($forum->id, $USER->id, $timeafter));
6791 $a = new stdClass();
6792 $a->blockafter = $forum->blockafter;
6793 $a->numposts = $numposts;
6794 $a->blockperiod = get_string('secondstotime'.$forum->blockperiod);
6796 if ($forum->blockafter <= $numposts) {
6797 print_error('forumblockingtoomanyposts', 'error', $CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id, $a);
6799 if ($forum->warnafter <= $numposts) {
6800 echo $OUTPUT->notification(get_string('forumblockingalmosttoomanyposts','forum',$a));
6808 * Removes all grades from gradebook
6810 * @global object
6811 * @global object
6812 * @param int $courseid
6813 * @param string $type optional
6815 function forum_reset_gradebook($courseid, $type='') {
6816 global $CFG, $DB;
6818 $wheresql = '';
6819 $params = array($courseid);
6820 if ($type) {
6821 $wheresql = "AND f.type=?";
6822 $params[] = $type;
6825 $sql = "SELECT f.*, cm.idnumber as cmidnumber, f.course as courseid
6826 FROM {forum} f, {course_modules} cm, {modules} m
6827 WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id AND f.course=? $wheresql";
6829 if ($forums = $DB->get_records_sql($sql, $params)) {
6830 foreach ($forums as $forum) {
6831 forum_grade_item_update($forum, 'reset');
6837 * This function is used by the reset_course_userdata function in moodlelib.
6838 * This function will remove all posts from the specified forum
6839 * and clean up any related data.
6841 * @global object
6842 * @global object
6843 * @param $data the data submitted from the reset course.
6844 * @return array status array
6846 function forum_reset_userdata($data) {
6847 global $CFG, $DB;
6848 require_once($CFG->dirroot.'/rating/lib.php');
6850 $componentstr = get_string('modulenameplural', 'forum');
6851 $status = array();
6853 $params = array($data->courseid);
6855 $removeposts = false;
6856 $typesql = "";
6857 if (!empty($data->reset_forum_all)) {
6858 $removeposts = true;
6859 $typesstr = get_string('resetforumsall', 'forum');
6860 $types = array();
6861 } else if (!empty($data->reset_forum_types)){
6862 $removeposts = true;
6863 $typesql = "";
6864 $types = array();
6865 $forum_types_all = forum_get_forum_types_all();
6866 foreach ($data->reset_forum_types as $type) {
6867 if (!array_key_exists($type, $forum_types_all)) {
6868 continue;
6870 $typesql .= " AND f.type=?";
6871 $types[] = $forum_types_all[$type];
6872 $params[] = $type;
6874 $typesstr = get_string('resetforums', 'forum').': '.implode(', ', $types);
6876 $alldiscussionssql = "SELECT fd.id
6877 FROM {forum_discussions} fd, {forum} f
6878 WHERE f.course=? AND f.id=fd.forum";
6880 $allforumssql = "SELECT f.id
6881 FROM {forum} f
6882 WHERE f.course=?";
6884 $allpostssql = "SELECT fp.id
6885 FROM {forum_posts} fp, {forum_discussions} fd, {forum} f
6886 WHERE f.course=? AND f.id=fd.forum AND fd.id=fp.discussion";
6888 $forumssql = $forums = $rm = null;
6890 if( $removeposts || !empty($data->reset_forum_ratings) ) {
6891 $forumssql = "$allforumssql $typesql";
6892 $forums = $forums = $DB->get_records_sql($forumssql, $params);
6893 $rm = new rating_manager();;
6894 $ratingdeloptions = new stdClass;
6895 $ratingdeloptions->component = 'mod_forum';
6896 $ratingdeloptions->ratingarea = 'post';
6899 if ($removeposts) {
6900 $discussionssql = "$alldiscussionssql $typesql";
6901 $postssql = "$allpostssql $typesql";
6903 // now get rid of all attachments
6904 $fs = get_file_storage();
6905 if ($forums) {
6906 foreach ($forums as $forumid=>$unused) {
6907 if (!$cm = get_coursemodule_from_instance('forum', $forumid)) {
6908 continue;
6910 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
6911 $fs->delete_area_files($context->id, 'mod_forum', 'attachment');
6912 $fs->delete_area_files($context->id, 'mod_forum', 'post');
6914 //remove ratings
6915 $ratingdeloptions->contextid = $context->id;
6916 $rm->delete_ratings($ratingdeloptions);
6920 // first delete all read flags
6921 $DB->delete_records_select('forum_read', "forumid IN ($forumssql)", $params);
6923 // remove tracking prefs
6924 $DB->delete_records_select('forum_track_prefs', "forumid IN ($forumssql)", $params);
6926 // remove posts from queue
6927 $DB->delete_records_select('forum_queue', "discussionid IN ($discussionssql)", $params);
6929 // all posts - initial posts must be kept in single simple discussion forums
6930 $DB->delete_records_select('forum_posts', "discussion IN ($discussionssql) AND parent <> 0", $params); // first all children
6931 $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
6933 // finally all discussions except single simple forums
6934 $DB->delete_records_select('forum_discussions', "forum IN ($forumssql AND f.type <> 'single')", $params);
6936 // remove all grades from gradebook
6937 if (empty($data->reset_gradebook_grades)) {
6938 if (empty($types)) {
6939 forum_reset_gradebook($data->courseid);
6940 } else {
6941 foreach ($types as $type) {
6942 forum_reset_gradebook($data->courseid, $type);
6947 $status[] = array('component'=>$componentstr, 'item'=>$typesstr, 'error'=>false);
6950 // remove all ratings in this course's forums
6951 if (!empty($data->reset_forum_ratings)) {
6952 if ($forums) {
6953 foreach ($forums as $forumid=>$unused) {
6954 if (!$cm = get_coursemodule_from_instance('forum', $forumid)) {
6955 continue;
6957 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
6959 //remove ratings
6960 $ratingdeloptions->contextid = $context->id;
6961 $rm->delete_ratings($ratingdeloptions);
6965 // remove all grades from gradebook
6966 if (empty($data->reset_gradebook_grades)) {
6967 forum_reset_gradebook($data->courseid);
6971 // remove all subscriptions unconditionally - even for users still enrolled in course
6972 if (!empty($data->reset_forum_subscriptions)) {
6973 $DB->delete_records_select('forum_subscriptions', "forum IN ($allforumssql)", $params);
6974 $status[] = array('component'=>$componentstr, 'item'=>get_string('resetsubscriptions','forum'), 'error'=>false);
6977 // remove all tracking prefs unconditionally - even for users still enrolled in course
6978 if (!empty($data->reset_forum_track_prefs)) {
6979 $DB->delete_records_select('forum_track_prefs', "forumid IN ($allforumssql)", $params);
6980 $status[] = array('component'=>$componentstr, 'item'=>get_string('resettrackprefs','forum'), 'error'=>false);
6983 /// updating dates - shift may be negative too
6984 if ($data->timeshift) {
6985 shift_course_mod_dates('forum', array('assesstimestart', 'assesstimefinish'), $data->timeshift, $data->courseid);
6986 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
6989 return $status;
6993 * Called by course/reset.php
6995 * @param $mform form passed by reference
6997 function forum_reset_course_form_definition(&$mform) {
6998 $mform->addElement('header', 'forumheader', get_string('modulenameplural', 'forum'));
7000 $mform->addElement('checkbox', 'reset_forum_all', get_string('resetforumsall','forum'));
7002 $mform->addElement('select', 'reset_forum_types', get_string('resetforums', 'forum'), forum_get_forum_types_all(), array('multiple' => 'multiple'));
7003 $mform->setAdvanced('reset_forum_types');
7004 $mform->disabledIf('reset_forum_types', 'reset_forum_all', 'checked');
7006 $mform->addElement('checkbox', 'reset_forum_subscriptions', get_string('resetsubscriptions','forum'));
7007 $mform->setAdvanced('reset_forum_subscriptions');
7009 $mform->addElement('checkbox', 'reset_forum_track_prefs', get_string('resettrackprefs','forum'));
7010 $mform->setAdvanced('reset_forum_track_prefs');
7011 $mform->disabledIf('reset_forum_track_prefs', 'reset_forum_all', 'checked');
7013 $mform->addElement('checkbox', 'reset_forum_ratings', get_string('deleteallratings'));
7014 $mform->disabledIf('reset_forum_ratings', 'reset_forum_all', 'checked');
7018 * Course reset form defaults.
7019 * @return array
7021 function forum_reset_course_form_defaults($course) {
7022 return array('reset_forum_all'=>1, 'reset_forum_subscriptions'=>0, 'reset_forum_track_prefs'=>0, 'reset_forum_ratings'=>1);
7026 * Converts a forum to use the Roles System
7028 * @global object
7029 * @global object
7030 * @param object $forum a forum object with the same attributes as a record
7031 * from the forum database table
7032 * @param int $forummodid the id of the forum module, from the modules table
7033 * @param array $teacherroles array of roles that have archetype teacher
7034 * @param array $studentroles array of roles that have archetype student
7035 * @param array $guestroles array of roles that have archetype guest
7036 * @param int $cmid the course_module id for this forum instance
7037 * @return boolean forum was converted or not
7039 function forum_convert_to_roles($forum, $forummodid, $teacherroles=array(),
7040 $studentroles=array(), $guestroles=array(), $cmid=NULL) {
7042 global $CFG, $DB, $OUTPUT;
7044 if (!isset($forum->open) && !isset($forum->assesspublic)) {
7045 // We assume that this forum has already been converted to use the
7046 // Roles System. Columns forum.open and forum.assesspublic get dropped
7047 // once the forum module has been upgraded to use Roles.
7048 return false;
7051 if ($forum->type == 'teacher') {
7053 // Teacher forums should be converted to normal forums that
7054 // use the Roles System to implement the old behavior.
7055 // Note:
7056 // Seems that teacher forums were never backed up in 1.6 since they
7057 // didn't have an entry in the course_modules table.
7058 require_once($CFG->dirroot.'/course/lib.php');
7060 if ($DB->count_records('forum_discussions', array('forum' => $forum->id)) == 0) {
7061 // Delete empty teacher forums.
7062 $DB->delete_records('forum', array('id' => $forum->id));
7063 } else {
7064 // Create a course module for the forum and assign it to
7065 // section 0 in the course.
7066 $mod = new stdClass();
7067 $mod->course = $forum->course;
7068 $mod->module = $forummodid;
7069 $mod->instance = $forum->id;
7070 $mod->section = 0;
7071 $mod->visible = 0; // Hide the forum
7072 $mod->visibleold = 0; // Hide the forum
7073 $mod->groupmode = 0;
7075 if (!$cmid = add_course_module($mod)) {
7076 print_error('cannotcreateinstanceforteacher', 'forum');
7077 } else {
7078 $mod->coursemodule = $cmid;
7079 if (!$sectionid = add_mod_to_section($mod)) {
7080 print_error('cannotaddteacherforumto', 'forum');
7081 } else {
7082 $DB->set_field('course_modules', 'section', $sectionid, array('id' => $cmid));
7086 // Change the forum type to general.
7087 $forum->type = 'general';
7088 $DB->update_record('forum', $forum);
7090 $context = get_context_instance(CONTEXT_MODULE, $cmid);
7092 // Create overrides for default student and guest roles (prevent).
7093 foreach ($studentroles as $studentrole) {
7094 assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
7095 assign_capability('mod/forum:viewhiddentimedposts', CAP_PREVENT, $studentrole->id, $context->id);
7096 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
7097 assign_capability('mod/forum:replypost', CAP_PREVENT, $studentrole->id, $context->id);
7098 assign_capability('mod/forum:viewrating', CAP_PREVENT, $studentrole->id, $context->id);
7099 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $studentrole->id, $context->id);
7100 assign_capability('mod/forum:rate', CAP_PREVENT, $studentrole->id, $context->id);
7101 assign_capability('mod/forum:createattachment', CAP_PREVENT, $studentrole->id, $context->id);
7102 assign_capability('mod/forum:deleteownpost', CAP_PREVENT, $studentrole->id, $context->id);
7103 assign_capability('mod/forum:deleteanypost', CAP_PREVENT, $studentrole->id, $context->id);
7104 assign_capability('mod/forum:splitdiscussions', CAP_PREVENT, $studentrole->id, $context->id);
7105 assign_capability('mod/forum:movediscussions', CAP_PREVENT, $studentrole->id, $context->id);
7106 assign_capability('mod/forum:editanypost', CAP_PREVENT, $studentrole->id, $context->id);
7107 assign_capability('mod/forum:viewqandawithoutposting', CAP_PREVENT, $studentrole->id, $context->id);
7108 assign_capability('mod/forum:viewsubscribers', CAP_PREVENT, $studentrole->id, $context->id);
7109 assign_capability('mod/forum:managesubscriptions', CAP_PREVENT, $studentrole->id, $context->id);
7110 assign_capability('mod/forum:postwithoutthrottling', CAP_PREVENT, $studentrole->id, $context->id);
7112 foreach ($guestroles as $guestrole) {
7113 assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $guestrole->id, $context->id);
7114 assign_capability('mod/forum:viewhiddentimedposts', CAP_PREVENT, $guestrole->id, $context->id);
7115 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $guestrole->id, $context->id);
7116 assign_capability('mod/forum:replypost', CAP_PREVENT, $guestrole->id, $context->id);
7117 assign_capability('mod/forum:viewrating', CAP_PREVENT, $guestrole->id, $context->id);
7118 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $guestrole->id, $context->id);
7119 assign_capability('mod/forum:rate', CAP_PREVENT, $guestrole->id, $context->id);
7120 assign_capability('mod/forum:createattachment', CAP_PREVENT, $guestrole->id, $context->id);
7121 assign_capability('mod/forum:deleteownpost', CAP_PREVENT, $guestrole->id, $context->id);
7122 assign_capability('mod/forum:deleteanypost', CAP_PREVENT, $guestrole->id, $context->id);
7123 assign_capability('mod/forum:splitdiscussions', CAP_PREVENT, $guestrole->id, $context->id);
7124 assign_capability('mod/forum:movediscussions', CAP_PREVENT, $guestrole->id, $context->id);
7125 assign_capability('mod/forum:editanypost', CAP_PREVENT, $guestrole->id, $context->id);
7126 assign_capability('mod/forum:viewqandawithoutposting', CAP_PREVENT, $guestrole->id, $context->id);
7127 assign_capability('mod/forum:viewsubscribers', CAP_PREVENT, $guestrole->id, $context->id);
7128 assign_capability('mod/forum:managesubscriptions', CAP_PREVENT, $guestrole->id, $context->id);
7129 assign_capability('mod/forum:postwithoutthrottling', CAP_PREVENT, $guestrole->id, $context->id);
7132 } else {
7133 // Non-teacher forum.
7135 if (empty($cmid)) {
7136 // We were not given the course_module id. Try to find it.
7137 if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
7138 echo $OUTPUT->notification('Could not get the course module for the forum');
7139 return false;
7140 } else {
7141 $cmid = $cm->id;
7144 $context = get_context_instance(CONTEXT_MODULE, $cmid);
7146 // $forum->open defines what students can do:
7147 // 0 = No discussions, no replies
7148 // 1 = No discussions, but replies are allowed
7149 // 2 = Discussions and replies are allowed
7150 switch ($forum->open) {
7151 case 0:
7152 foreach ($studentroles as $studentrole) {
7153 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
7154 assign_capability('mod/forum:replypost', CAP_PREVENT, $studentrole->id, $context->id);
7156 break;
7157 case 1:
7158 foreach ($studentroles as $studentrole) {
7159 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
7160 assign_capability('mod/forum:replypost', CAP_ALLOW, $studentrole->id, $context->id);
7162 break;
7163 case 2:
7164 foreach ($studentroles as $studentrole) {
7165 assign_capability('mod/forum:startdiscussion', CAP_ALLOW, $studentrole->id, $context->id);
7166 assign_capability('mod/forum:replypost', CAP_ALLOW, $studentrole->id, $context->id);
7168 break;
7171 // $forum->assessed defines whether forum rating is turned
7172 // on (1 or 2) and who can rate posts:
7173 // 1 = Everyone can rate posts
7174 // 2 = Only teachers can rate posts
7175 switch ($forum->assessed) {
7176 case 1:
7177 foreach ($studentroles as $studentrole) {
7178 assign_capability('mod/forum:rate', CAP_ALLOW, $studentrole->id, $context->id);
7180 foreach ($teacherroles as $teacherrole) {
7181 assign_capability('mod/forum:rate', CAP_ALLOW, $teacherrole->id, $context->id);
7183 break;
7184 case 2:
7185 foreach ($studentroles as $studentrole) {
7186 assign_capability('mod/forum:rate', CAP_PREVENT, $studentrole->id, $context->id);
7188 foreach ($teacherroles as $teacherrole) {
7189 assign_capability('mod/forum:rate', CAP_ALLOW, $teacherrole->id, $context->id);
7191 break;
7194 // $forum->assesspublic defines whether students can see
7195 // everybody's ratings:
7196 // 0 = Students can only see their own ratings
7197 // 1 = Students can see everyone's ratings
7198 switch ($forum->assesspublic) {
7199 case 0:
7200 foreach ($studentroles as $studentrole) {
7201 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $studentrole->id, $context->id);
7203 foreach ($teacherroles as $teacherrole) {
7204 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $teacherrole->id, $context->id);
7206 break;
7207 case 1:
7208 foreach ($studentroles as $studentrole) {
7209 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $studentrole->id, $context->id);
7211 foreach ($teacherroles as $teacherrole) {
7212 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $teacherrole->id, $context->id);
7214 break;
7217 if (empty($cm)) {
7218 $cm = $DB->get_record('course_modules', array('id' => $cmid));
7221 // $cm->groupmode:
7222 // 0 - No groups
7223 // 1 - Separate groups
7224 // 2 - Visible groups
7225 switch ($cm->groupmode) {
7226 case 0:
7227 break;
7228 case 1:
7229 foreach ($studentroles as $studentrole) {
7230 assign_capability('moodle/site:accessallgroups', CAP_PREVENT, $studentrole->id, $context->id);
7232 foreach ($teacherroles as $teacherrole) {
7233 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
7235 break;
7236 case 2:
7237 foreach ($studentroles as $studentrole) {
7238 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $studentrole->id, $context->id);
7240 foreach ($teacherroles as $teacherrole) {
7241 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
7243 break;
7246 return true;
7250 * Returns array of forum layout modes
7252 * @return array
7254 function forum_get_layout_modes() {
7255 return array (FORUM_MODE_FLATOLDEST => get_string('modeflatoldestfirst', 'forum'),
7256 FORUM_MODE_FLATNEWEST => get_string('modeflatnewestfirst', 'forum'),
7257 FORUM_MODE_THREADED => get_string('modethreaded', 'forum'),
7258 FORUM_MODE_NESTED => get_string('modenested', 'forum'));
7262 * Returns array of forum types chooseable on the forum editing form
7264 * @return array
7266 function forum_get_forum_types() {
7267 return array ('general' => get_string('generalforum', 'forum'),
7268 'eachuser' => get_string('eachuserforum', 'forum'),
7269 'single' => get_string('singleforum', 'forum'),
7270 'qanda' => get_string('qandaforum', 'forum'),
7271 'blog' => get_string('blogforum', 'forum'));
7275 * Returns array of all forum layout modes
7277 * @return array
7279 function forum_get_forum_types_all() {
7280 return array ('news' => get_string('namenews','forum'),
7281 'social' => get_string('namesocial','forum'),
7282 'general' => get_string('generalforum', 'forum'),
7283 'eachuser' => get_string('eachuserforum', 'forum'),
7284 'single' => get_string('singleforum', 'forum'),
7285 'qanda' => get_string('qandaforum', 'forum'),
7286 'blog' => get_string('blogforum', 'forum'));
7290 * Returns array of forum open modes
7292 * @return array
7294 function forum_get_open_modes() {
7295 return array ('2' => get_string('openmode2', 'forum'),
7296 '1' => get_string('openmode1', 'forum'),
7297 '0' => get_string('openmode0', 'forum') );
7301 * Returns all other caps used in module
7303 * @return array
7305 function forum_get_extra_capabilities() {
7306 return array('moodle/site:accessallgroups', 'moodle/site:viewfullnames', 'moodle/site:trustcontent', 'moodle/rating:view', 'moodle/rating:viewany', 'moodle/rating:viewall', 'moodle/rating:rate');
7311 * This function is used to extend the global navigation by add forum nodes if there
7312 * is relevant content.
7314 * @param navigation_node $navref
7315 * @param stdClass $course
7316 * @param stdClass $module
7317 * @param stdClass $cm
7319 /*************************************************
7320 function forum_extend_navigation($navref, $course, $module, $cm) {
7321 global $CFG, $OUTPUT, $USER;
7323 $limit = 5;
7325 $discussions = forum_get_discussions($cm,"d.timemodified DESC", false, -1, $limit);
7326 $discussioncount = forum_get_discussions_count($cm);
7327 if (!is_array($discussions) || count($discussions)==0) {
7328 return;
7330 $discussionnode = $navref->add(get_string('discussions', 'forum').' ('.$discussioncount.')');
7331 $discussionnode->mainnavonly = true;
7332 $discussionnode->display = false; // Do not display on navigation (only on navbar)
7334 foreach ($discussions as $discussion) {
7335 $icon = new pix_icon('i/feedback', '');
7336 $url = new moodle_url('/mod/forum/discuss.php', array('d'=>$discussion->discussion));
7337 $discussionnode->add($discussion->subject, $url, navigation_node::TYPE_SETTING, null, null, $icon);
7340 if ($discussioncount > count($discussions)) {
7341 if (!empty($navref->action)) {
7342 $url = $navref->action;
7343 } else {
7344 $url = new moodle_url('/mod/forum/view.php', array('id'=>$cm->id));
7346 $discussionnode->add(get_string('viewalldiscussions', 'forum'), $url, navigation_node::TYPE_SETTING, null, null, $icon);
7349 $index = 0;
7350 $recentposts = array();
7351 $lastlogin = time() - COURSE_MAX_RECENT_PERIOD;
7352 if (!isguestuser() and !empty($USER->lastcourseaccess[$course->id])) {
7353 if ($USER->lastcourseaccess[$course->id] > $lastlogin) {
7354 $lastlogin = $USER->lastcourseaccess[$course->id];
7357 forum_get_recent_mod_activity($recentposts, $index, $lastlogin, $course->id, $cm->id);
7359 if (is_array($recentposts) && count($recentposts)>0) {
7360 $recentnode = $navref->add(get_string('recentactivity').' ('.count($recentposts).')');
7361 $recentnode->mainnavonly = true;
7362 $recentnode->display = false;
7363 foreach ($recentposts as $post) {
7364 $icon = new pix_icon('i/feedback', '');
7365 $url = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->content->discussion));
7366 $title = $post->content->subject."\n".userdate($post->timestamp, get_string('strftimerecent', 'langconfig'))."\n".$post->user->firstname.' '.$post->user->lastname;
7367 $recentnode->add($title, $url, navigation_node::TYPE_SETTING, null, null, $icon);
7371 *************************/
7374 * Adds module specific settings to the settings block
7376 * @param settings_navigation $settings The settings navigation object
7377 * @param navigation_node $forumnode The node to add module settings to
7379 function forum_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $forumnode) {
7380 global $USER, $PAGE, $CFG, $DB, $OUTPUT;
7382 $forumobject = $DB->get_record("forum", array("id" => $PAGE->cm->instance));
7383 if (empty($PAGE->cm->context)) {
7384 $PAGE->cm->context = get_context_instance(CONTEXT_MODULE, $PAGE->cm->instance);
7387 // for some actions you need to be enrolled, beiing admin is not enough sometimes here
7388 $enrolled = is_enrolled($PAGE->cm->context, $USER, '', false);
7389 $activeenrolled = is_enrolled($PAGE->cm->context, $USER, '', true);
7391 $canmanage = has_capability('mod/forum:managesubscriptions', $PAGE->cm->context);
7392 $subscriptionmode = forum_get_forcesubscribed($forumobject);
7393 $cansubscribe = ($activeenrolled && $subscriptionmode != FORUM_FORCESUBSCRIBE && ($subscriptionmode != FORUM_DISALLOWSUBSCRIBE || $canmanage));
7395 if ($canmanage) {
7396 $mode = $forumnode->add(get_string('subscriptionmode', 'forum'), null, navigation_node::TYPE_CONTAINER);
7398 $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);
7399 $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);
7400 $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);
7401 $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);
7403 switch ($subscriptionmode) {
7404 case FORUM_CHOOSESUBSCRIBE : // 0
7405 $allowchoice->action = null;
7406 $allowchoice->add_class('activesetting');
7407 break;
7408 case FORUM_FORCESUBSCRIBE : // 1
7409 $forceforever->action = null;
7410 $forceforever->add_class('activesetting');
7411 break;
7412 case FORUM_INITIALSUBSCRIBE : // 2
7413 $forceinitially->action = null;
7414 $forceinitially->add_class('activesetting');
7415 break;
7416 case FORUM_DISALLOWSUBSCRIBE : // 3
7417 $disallowchoice->action = null;
7418 $disallowchoice->add_class('activesetting');
7419 break;
7422 } else if ($activeenrolled) {
7424 switch ($subscriptionmode) {
7425 case FORUM_CHOOSESUBSCRIBE : // 0
7426 $notenode = $forumnode->add(get_string('subscriptionoptional', 'forum'));
7427 break;
7428 case FORUM_FORCESUBSCRIBE : // 1
7429 $notenode = $forumnode->add(get_string('subscriptionforced', 'forum'));
7430 break;
7431 case FORUM_INITIALSUBSCRIBE : // 2
7432 $notenode = $forumnode->add(get_string('subscriptionauto', 'forum'));
7433 break;
7434 case FORUM_DISALLOWSUBSCRIBE : // 3
7435 $notenode = $forumnode->add(get_string('subscriptiondisabled', 'forum'));
7436 break;
7440 if ($cansubscribe) {
7441 if (forum_is_subscribed($USER->id, $forumobject)) {
7442 $linktext = get_string('unsubscribe', 'forum');
7443 } else {
7444 $linktext = get_string('subscribe', 'forum');
7446 $url = new moodle_url('/mod/forum/subscribe.php', array('id'=>$forumobject->id, 'sesskey'=>sesskey()));
7447 $forumnode->add($linktext, $url, navigation_node::TYPE_SETTING);
7450 if (has_capability('mod/forum:viewsubscribers', $PAGE->cm->context)){
7451 $url = new moodle_url('/mod/forum/subscribers.php', array('id'=>$forumobject->id));
7452 $forumnode->add(get_string('showsubscribers', 'forum'), $url, navigation_node::TYPE_SETTING);
7455 if ($enrolled && forum_tp_can_track_forums($forumobject)) { // keep tracking info for users with suspended enrolments
7456 if ($forumobject->trackingtype != FORUM_TRACKING_OPTIONAL) {
7457 //tracking forced on or off in forum settings so dont provide a link here to change it
7458 //could add unclickable text like for forced subscription but not sure this justifies adding another menu item
7459 } else {
7460 if (forum_tp_is_tracked($forumobject)) {
7461 $linktext = get_string('notrackforum', 'forum');
7462 } else {
7463 $linktext = get_string('trackforum', 'forum');
7465 $url = new moodle_url('/mod/forum/settracking.php', array('id'=>$forumobject->id));
7466 $forumnode->add($linktext, $url, navigation_node::TYPE_SETTING);
7470 if (!isloggedin() && $PAGE->course->id == SITEID) {
7471 $userid = guest_user()->id;
7472 } else {
7473 $userid = $USER->id;
7476 $hascourseaccess = ($PAGE->course->id == SITEID) || can_access_course($PAGE->course, $userid);
7477 $enablerssfeeds = !empty($CFG->enablerssfeeds) && !empty($CFG->forum_enablerssfeeds);
7479 if ($enablerssfeeds && $forumobject->rsstype && $forumobject->rssarticles && $hascourseaccess) {
7481 if (!function_exists('rss_get_url')) {
7482 require_once("$CFG->libdir/rsslib.php");
7485 if ($forumobject->rsstype == 1) {
7486 $string = get_string('rsssubscriberssdiscussions','forum');
7487 } else {
7488 $string = get_string('rsssubscriberssposts','forum');
7491 $url = new moodle_url(rss_get_url($PAGE->cm->context->id, $userid, "mod_forum", $forumobject->id));
7492 $forumnode->add($string, $url, settings_navigation::TYPE_SETTING, null, null, new pix_icon('i/rss', ''));
7497 * Abstract class used by forum subscriber selection controls
7498 * @package mod-forum
7499 * @copyright 2009 Sam Hemelryk
7500 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7502 abstract class forum_subscriber_selector_base extends user_selector_base {
7505 * The id of the forum this selector is being used for
7506 * @var int
7508 protected $forumid = null;
7510 * The context of the forum this selector is being used for
7511 * @var object
7513 protected $context = null;
7515 * The id of the current group
7516 * @var int
7518 protected $currentgroup = null;
7521 * Constructor method
7522 * @param string $name
7523 * @param array $options
7525 public function __construct($name, $options) {
7526 $options['accesscontext'] = $options['context'];
7527 parent::__construct($name, $options);
7528 if (isset($options['context'])) {
7529 $this->context = $options['context'];
7531 if (isset($options['currentgroup'])) {
7532 $this->currentgroup = $options['currentgroup'];
7534 if (isset($options['forumid'])) {
7535 $this->forumid = $options['forumid'];
7540 * Returns an array of options to seralise and store for searches
7542 * @return array
7544 protected function get_options() {
7545 global $CFG;
7546 $options = parent::get_options();
7547 $options['file'] = substr(__FILE__, strlen($CFG->dirroot.'/'));
7548 $options['context'] = $this->context;
7549 $options['currentgroup'] = $this->currentgroup;
7550 $options['forumid'] = $this->forumid;
7551 return $options;
7557 * A user selector control for potential subscribers to the selected forum
7558 * @package mod-forum
7559 * @copyright 2009 Sam Hemelryk
7560 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7562 class forum_potential_subscriber_selector extends forum_subscriber_selector_base {
7565 * If set to true EVERYONE in this course is force subscribed to this forum
7566 * @var bool
7568 protected $forcesubscribed = false;
7570 * Can be used to store existing subscribers so that they can be removed from
7571 * the potential subscribers list
7573 protected $existingsubscribers = array();
7576 * Constructor method
7577 * @param string $name
7578 * @param array $options
7580 public function __construct($name, $options) {
7581 parent::__construct($name, $options);
7582 if (isset($options['forcesubscribed'])) {
7583 $this->forcesubscribed=true;
7588 * Returns an arary of options for this control
7589 * @return array
7591 protected function get_options() {
7592 $options = parent::get_options();
7593 if ($this->forcesubscribed===true) {
7594 $options['forcesubscribed']=1;
7596 return $options;
7600 * Finds all potential users
7602 * Potential users are determined by checking for users with a capability
7603 * determined in {@see forum_get_potential_subscribers()}
7605 * @param string $search
7606 * @return array
7608 public function find_users($search) {
7609 global $DB;
7611 $availableusers = forum_get_potential_subscribers($this->context, $this->currentgroup, $this->required_fields_sql('u'), 'u.firstname ASC, u.lastname ASC');
7613 if (empty($availableusers)) {
7614 $availableusers = array();
7615 } else if ($search) {
7616 $search = strtolower($search);
7617 foreach ($availableusers as $key=>$user) {
7618 if (stripos($user->firstname, $search) === false && stripos($user->lastname, $search) === false) {
7619 unset($availableusers[$key]);
7624 // Unset any existing subscribers
7625 if (count($this->existingsubscribers)>0 && !$this->forcesubscribed) {
7626 foreach ($this->existingsubscribers as $group) {
7627 foreach ($group as $user) {
7628 if (array_key_exists($user->id, $availableusers)) {
7629 unset($availableusers[$user->id]);
7635 if ($this->forcesubscribed) {
7636 return array(get_string("existingsubscribers", 'forum') => $availableusers);
7637 } else {
7638 return array(get_string("potentialsubscribers", 'forum') => $availableusers);
7643 * Sets the existing subscribers
7644 * @param array $users
7646 public function set_existing_subscribers(array $users) {
7647 $this->existingsubscribers = $users;
7651 * Sets this forum as force subscribed or not
7653 public function set_force_subscribed($setting=true) {
7654 $this->forcesubscribed = true;
7659 * User selector control for removing subscribed users
7660 * @package mod-forum
7661 * @copyright 2009 Sam Hemelryk
7662 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7664 class forum_existing_subscriber_selector extends forum_subscriber_selector_base {
7667 * Finds all subscribed users
7669 * @param string $search
7670 * @return array
7672 public function find_users($search) {
7673 global $DB;
7674 list($wherecondition, $params) = $this->search_sql($search, 'u');
7675 $params['forumid'] = $this->forumid;
7677 // only active enrolled or everybody on the frontpage
7678 list($esql, $eparams) = get_enrolled_sql($this->context, '', $this->currentgroup, true);
7679 $params = array_merge($params, $eparams);
7681 $fields = $this->required_fields_sql('u');
7683 $subscribers = $DB->get_records_sql("SELECT $fields
7684 FROM {user} u
7685 JOIN ($esql) je ON je.id = u.id
7686 JOIN {forum_subscriptions} s ON s.userid = u.id
7687 WHERE $wherecondition AND s.forum = :forumid
7688 ORDER BY u.lastname ASC, u.firstname ASC", $params);
7690 return array(get_string("existingsubscribers", 'forum') => $subscribers);
7696 * Adds information about unread messages, that is only required for the course view page (and
7697 * similar), to the course-module object.
7698 * @param cm_info $cm Course-module object
7700 function forum_cm_info_view(cm_info $cm) {
7701 global $CFG;
7703 // Get tracking status (once per request)
7704 static $initialised;
7705 static $usetracking, $strunreadpostsone;
7706 if (!isset($initialised)) {
7707 if ($usetracking = forum_tp_can_track_forums()) {
7708 $strunreadpostsone = get_string('unreadpostsone', 'forum');
7710 $initialised = true;
7713 if ($usetracking) {
7714 if ($unread = forum_tp_count_forum_unread_posts($cm, $cm->get_course())) {
7715 $out = '<span class="unread"> <a href="' . $cm->get_url() . '">';
7716 if ($unread == 1) {
7717 $out .= $strunreadpostsone;
7718 } else {
7719 $out .= get_string('unreadpostsnumber', 'forum', $unread);
7721 $out .= '</a></span>';
7722 $cm->set_after_link($out);
7728 * Return a list of page types
7729 * @param string $pagetype current page type
7730 * @param stdClass $parentcontext Block's parent context
7731 * @param stdClass $currentcontext Current context of block
7733 function forum_page_type_list($pagetype, $parentcontext, $currentcontext) {
7734 $forum_pagetype = array(
7735 'mod-forum-*'=>get_string('page-mod-forum-x', 'forum'),
7736 'mod-forum-view'=>get_string('page-mod-forum-view', 'forum'),
7737 'mod-forum-discuss'=>get_string('page-mod-forum-discuss', 'forum')
7739 return $forum_pagetype;
7743 * Gets all of the courses where the provided user has posted in a forum.
7745 * @global moodle_database $DB The database connection
7746 * @param stdClass $user The user who's posts we are looking for
7747 * @param bool $discussionsonly If true only look for discussions started by the user
7748 * @param bool $includecontexts If set to trye contexts for the courses will be preloaded
7749 * @param int $limitfrom The offset of records to return
7750 * @param int $limitnum The number of records to return
7751 * @return array An array of courses
7753 function forum_get_courses_user_posted_in($user, $discussionsonly = false, $includecontexts = true, $limitfrom = null, $limitnum = null) {
7754 global $DB;
7756 // If we are only after discussions we need only look at the forum_discussions
7757 // table and join to the userid there. If we are looking for posts then we need
7758 // to join to the forum_posts table.
7759 if (!$discussionsonly) {
7760 $joinsql = 'JOIN {forum_discussions} fd ON fd.course = c.id
7761 JOIN {forum_posts} fp ON fp.discussion = fd.id';
7762 $wheresql = 'fp.userid = :userid';
7763 $params = array('userid' => $user->id);
7764 } else {
7765 $joinsql = 'JOIN {forum_discussions} fd ON fd.course = c.id';
7766 $wheresql = 'fd.userid = :userid';
7767 $params = array('userid' => $user->id);
7770 // Join to the context table so that we can preload contexts if required.
7771 if ($includecontexts) {
7772 list($ctxselect, $ctxjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
7773 } else {
7774 $ctxselect = '';
7775 $ctxjoin = '';
7778 // Now we need to get all of the courses to search.
7779 // All courses where the user has posted within a forum will be returned.
7780 $sql = "SELECT DISTINCT c.* $ctxselect
7781 FROM {course} c
7782 $joinsql
7783 $ctxjoin
7784 WHERE $wheresql";
7785 $courses = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
7786 if ($includecontexts) {
7787 array_map('context_instance_preload', $courses);
7789 return $courses;
7793 * Gets all of the forums a user has posted in for one or more courses.
7795 * @global moodle_database $DB
7796 * @param stdClass $user
7797 * @param array $courseids An array of courseids to search or if not provided
7798 * all courses the user has posted within
7799 * @param bool $discussionsonly If true then only forums where the user has started
7800 * a discussion will be returned.
7801 * @param int $limitfrom The offset of records to return
7802 * @param int $limitnum The number of records to return
7803 * @return array An array of forums the user has posted within in the provided courses
7805 function forum_get_forums_user_posted_in($user, array $courseids = null, $discussionsonly = false, $limitfrom = null, $limitnum = null) {
7806 global $DB;
7808 $where = array("m.name = 'forum'");
7809 $params = array();
7810 if (!is_null($courseids)) {
7811 list($coursewhere, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED, 'courseid');
7812 $where[] = 'f.course '.$coursewhere;
7814 if (!$discussionsonly) {
7815 $joinsql = 'JOIN {forum_discussions} fd ON fd.forum = f.id
7816 JOIN {forum_posts} fp ON fp.discussion = fd.id';
7817 $where[] = 'fp.userid = :userid';
7818 } else {
7819 $joinsql = 'JOIN {forum_discussions} fd ON fd.forum = f.id';
7820 $where[] = 'fd.userid = :userid';
7822 $params['userid'] = $user->id;
7823 $wheresql = join(' AND ', $where);
7825 $sql = "SELECT DISTINCT f.*, cm.id AS cmid
7826 FROM {forum} f
7827 JOIN {course_modules} cm ON cm.instance = f.id
7828 JOIN {modules} m ON m.id = cm.module
7829 $joinsql
7830 WHERE $wheresql";
7831 $courseforums = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
7832 return $courseforums;
7836 * Returns posts made by the selected user in the requested courses.
7838 * This method can be used to return all of the posts made by the requested user
7839 * within the given courses.
7840 * For each course the access of the current user and requested user is checked
7841 * and then for each post access to the post and forum is checked as well.
7843 * This function is safe to use with usercapabilities.
7845 * @global moodle_database $DB
7846 * @param stdClass $user The user whose posts we want to get
7847 * @param array $courses The courses to search
7848 * @param bool $musthaveaccess If set to true errors will be thrown if the user
7849 * cannot access one or more of the courses to search
7850 * @param bool $discussionsonly If set to true only discussion starting posts
7851 * will be returned.
7852 * @param int $limitfrom The offset of records to return
7853 * @param int $limitnum The number of records to return
7854 * @return stdClass An object the following properties
7855 * ->totalcount: the total number of posts made by the requested user
7856 * that the current user can see.
7857 * ->courses: An array of courses the current user can see that the
7858 * requested user has posted in.
7859 * ->forums: An array of forums relating to the posts returned in the
7860 * property below.
7861 * ->posts: An array containing the posts to show for this request.
7863 function forum_get_posts_by_user($user, array $courses, $musthaveaccess = false, $discussionsonly = false, $limitfrom = 0, $limitnum = 50) {
7864 global $DB, $USER, $CFG;
7866 $return = new stdClass;
7867 $return->totalcount = 0; // The total number of posts that the current user is able to view
7868 $return->courses = array(); // The courses the current user can access
7869 $return->forums = array(); // The forums that the current user can access that contain posts
7870 $return->posts = array(); // The posts to display
7872 // First up a small sanity check. If there are no courses to check we can
7873 // return immediately, there is obviously nothing to search.
7874 if (empty($courses)) {
7875 return $return;
7878 // A couple of quick setups
7879 $isloggedin = isloggedin();
7880 $isguestuser = $isloggedin && isguestuser();
7881 $iscurrentuser = $isloggedin && $USER->id == $user->id;
7883 // Checkout whether or not the current user has capabilities over the requested
7884 // user and if so they have the capabilities required to view the requested
7885 // users content.
7886 $usercontext = get_context_instance(CONTEXT_USER, $user->id, MUST_EXIST);
7887 $hascapsonuser = !$iscurrentuser && $DB->record_exists('role_assignments', array('userid' => $USER->id, 'contextid' => $usercontext->id));
7888 $hascapsonuser = $hascapsonuser && has_all_capabilities(array('moodle/user:viewdetails', 'moodle/user:readuserposts'), $usercontext);
7890 // Before we actually search each course we need to check the user's access to the
7891 // course. If the user doesn't have the appropraite access then we either throw an
7892 // error if a particular course was requested or we just skip over the course.
7893 foreach ($courses as $course) {
7894 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id, MUST_EXIST);
7895 if ($iscurrentuser || $hascapsonuser) {
7896 // If it is the current user, or the current user has capabilities to the
7897 // requested user then all we need to do is check the requested users
7898 // current access to the course.
7899 // Note: There is no need to check group access or anything of the like
7900 // as either the current user is the requested user, or has granted
7901 // capabilities on the requested user. Either way they can see what the
7902 // requested user posted, although its VERY unlikely in the `parent` situation
7903 // that the current user will be able to view the posts in context.
7904 if (!is_viewing($coursecontext, $user) && !is_enrolled($coursecontext, $user)) {
7905 // Need to have full access to a course to see the rest of own info
7906 if ($musthaveaccess) {
7907 print_error('errorenrolmentrequired', 'forum');
7909 continue;
7911 } else {
7912 // Check whether the current user is enrolled or has access to view the course
7913 // if they don't we immediately have a problem.
7914 if (!can_access_course($course)) {
7915 if ($musthaveaccess) {
7916 print_error('errorenrolmentrequired', 'forum');
7918 continue;
7921 // Check whether the requested user is enrolled or has access to view the course
7922 // if they don't we immediately have a problem.
7923 if (!can_access_course($course, $user)) {
7924 if ($musthaveaccess) {
7925 print_error('notenrolled', 'forum');
7927 continue;
7930 // If groups are in use and enforced throughout the course then make sure
7931 // we can meet in at least one course level group.
7932 // Note that we check if either the current user or the requested user have
7933 // the capability to access all groups. This is because with that capability
7934 // a user in group A could post in the group B forum. Grrrr.
7935 if (groups_get_course_groupmode($course) == SEPARATEGROUPS && $course->groupmodeforce
7936 && !has_capability('moodle/site:accessallgroups', $coursecontext) && !has_capability('moodle/site:accessallgroups', $coursecontext, $user->id)) {
7937 // If its the guest user to bad... the guest user cannot access groups
7938 if (!$isloggedin or $isguestuser) {
7939 // do not use require_login() here because we might have already used require_login($course)
7940 if ($musthaveaccess) {
7941 redirect(get_login_url());
7943 continue;
7945 // Get the groups of the current user
7946 $mygroups = array_keys(groups_get_all_groups($course->id, $USER->id, $course->defaultgroupingid, 'g.id, g.name'));
7947 // Get the groups the requested user is a member of
7948 $usergroups = array_keys(groups_get_all_groups($course->id, $user->id, $course->defaultgroupingid, 'g.id, g.name'));
7949 // Check whether they are members of the same group. If they are great.
7950 $intersect = array_intersect($mygroups, $usergroups);
7951 if (empty($intersect)) {
7952 // But they're not... if it was a specific course throw an error otherwise
7953 // just skip this course so that it is not searched.
7954 if ($musthaveaccess) {
7955 print_error("groupnotamember", '', $CFG->wwwroot."/course/view.php?id=$course->id");
7957 continue;
7961 // Woo hoo we got this far which means the current user can search this
7962 // this course for the requested user. Although this is only the course accessibility
7963 // handling that is complete, the forum accessibility tests are yet to come.
7964 $return->courses[$course->id] = $course;
7966 // No longer beed $courses array - lose it not it may be big
7967 unset($courses);
7969 // Make sure that we have some courses to search
7970 if (empty($return->courses)) {
7971 // If we don't have any courses to search then the reality is that the current
7972 // user doesn't have access to any courses is which the requested user has posted.
7973 // Although we do know at this point that the requested user has posts.
7974 if ($musthaveaccess) {
7975 print_error('permissiondenied');
7976 } else {
7977 return $return;
7981 // Next step: Collect all of the forums that we will want to search.
7982 // It is important to note that this step isn't actually about searching, it is
7983 // about determining which forums we can search by testing accessibility.
7984 $forums = forum_get_forums_user_posted_in($user, array_keys($return->courses), $discussionsonly);
7986 // Will be used to build the where conditions for the search
7987 $forumsearchwhere = array();
7988 // Will be used to store the where condition params for the search
7989 $forumsearchparams = array();
7990 // Will record forums where the user can freely access everything
7991 $forumsearchfullaccess = array();
7992 // DB caching friendly
7993 $now = round(time(), -2);
7994 // For each course to search we want to find the forums the user has posted in
7995 // and providing the current user can access the forum create a search condition
7996 // for the forum to get the requested users posts.
7997 foreach ($return->courses as $course) {
7998 // Now we need to get the forums
7999 $modinfo = get_fast_modinfo($course);
8000 if (empty($modinfo->instances['forum'])) {
8001 // hmmm, no forums? well at least its easy... skip!
8002 continue;
8004 // Iterate
8005 foreach ($modinfo->get_instances_of('forum') as $forumid => $cm) {
8006 if (!$cm->uservisible or !isset($forums[$forumid])) {
8007 continue;
8009 // Get the forum in question
8010 $forum = $forums[$forumid];
8011 // This is needed for functionality later on in the forum code....
8012 $forum->cm = $cm;
8014 // Check that either the current user can view the forum, or that the
8015 // current user has capabilities over the requested user and the requested
8016 // user can view the discussion
8017 if (!has_capability('mod/forum:viewdiscussion', $cm->context) && !($hascapsonuser && has_capability('mod/forum:viewdiscussion', $cm->context, $user->id))) {
8018 continue;
8021 // This will contain forum specific where clauses
8022 $forumsearchselect = array();
8023 if (!$iscurrentuser && !$hascapsonuser) {
8024 // Make sure we check group access
8025 if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $cm->context)) {
8026 $groups = $modinfo->get_groups($cm->groupingid);
8027 $groups[] = -1;
8028 list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
8029 $forumsearchparams = array_merge($forumsearchparams, $groupid_params);
8030 $forumsearchselect[] = "d.groupid $groupid_sql";
8033 // hidden timed discussions
8034 if (!empty($CFG->forum_enabletimedposts) && !has_capability('mod/forum:viewhiddentimedposts', $cm->context)) {
8035 $forumsearchselect[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
8036 $forumsearchparams['userid'.$forumid] = $user->id;
8037 $forumsearchparams['timestart'.$forumid] = $now;
8038 $forumsearchparams['timeend'.$forumid] = $now;
8041 // qanda access
8042 if ($forum->type == 'qanda' && !has_capability('mod/forum:viewqandawithoutposting', $cm->context)) {
8043 // We need to check whether the user has posted in the qanda forum.
8044 $discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $user->id);
8045 if (!empty($discussionspostedin)) {
8046 $forumonlydiscussions = array(); // Holds discussion ids for the discussions the user is allowed to see in this forum.
8047 foreach ($discussionspostedin as $d) {
8048 $forumonlydiscussions[] = $d->id;
8050 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forumonlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
8051 $forumsearchparams = array_merge($forumsearchparams, $discussionid_params);
8052 $forumsearchselect[] = "(d.id $discussionid_sql OR p.parent = 0)";
8053 } else {
8054 $forumsearchselect[] = "p.parent = 0";
8059 if (count($forumsearchselect) > 0) {
8060 $forumsearchwhere[] = "(d.forum = :forum{$forumid} AND ".implode(" AND ", $forumsearchselect).")";
8061 $forumsearchparams['forum'.$forumid] = $forumid;
8062 } else {
8063 $forumsearchfullaccess[] = $forumid;
8065 } else {
8066 // The current user/parent can see all of their own posts
8067 $forumsearchfullaccess[] = $forumid;
8072 // If we dont have any search conditions, and we don't have any forums where
8073 // the user has full access then we just return the default.
8074 if (empty($forumsearchwhere) && empty($forumsearchfullaccess)) {
8075 return $return;
8078 // Prepare a where condition for the full access forums.
8079 if (count($forumsearchfullaccess) > 0) {
8080 list($fullidsql, $fullidparams) = $DB->get_in_or_equal($forumsearchfullaccess, SQL_PARAMS_NAMED, 'fula');
8081 $forumsearchparams = array_merge($forumsearchparams, $fullidparams);
8082 $forumsearchwhere[] = "(d.forum $fullidsql)";
8085 // Prepare SQL to both count and search
8086 $userfields = user_picture::fields('u', null, 'userid');
8087 $countsql = 'SELECT COUNT(*) ';
8088 $selectsql = 'SELECT p.*, d.forum, d.name AS discussionname, '.$userfields.' ';
8089 $wheresql = implode(" OR ", $forumsearchwhere);
8091 if ($discussionsonly) {
8092 if ($wheresql == '') {
8093 $wheresql = 'p.parent = 0';
8094 } else {
8095 $wheresql = 'p.parent = 0 AND ('.$wheresql.')';
8099 $sql = "FROM {forum_posts} p
8100 JOIN {forum_discussions} d ON d.id = p.discussion
8101 JOIN {user} u ON u.id = p.userid
8102 WHERE ($wheresql)
8103 AND p.userid = :userid ";
8104 $orderby = "ORDER BY p.modified DESC";
8105 $forumsearchparams['userid'] = $user->id;
8107 // Set the total number posts made by the requested user that the current user can see
8108 $return->totalcount = $DB->count_records_sql($countsql.$sql, $forumsearchparams);
8109 // Set the collection of posts that has been requested
8110 $return->posts = $DB->get_records_sql($selectsql.$sql.$orderby, $forumsearchparams, $limitfrom, $limitnum);
8112 // We need to build an array of forums for which posts will be displayed.
8113 // We do this here to save the caller needing to retrieve them themselves before
8114 // printing these forums posts. Given we have the forums already there is
8115 // practically no overhead here.
8116 foreach ($return->posts as $post) {
8117 if (!array_key_exists($post->forum, $return->forums)) {
8118 $return->forums[$post->forum] = $forums[$post->forum];
8122 return $return;