MDL-4908 Forum: Add a digest option link to outgoing forum mails
[moodle.git] / mod / forum / lib.php
blob301f6c3de73cad598ec13983d8720cab7adf050f
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');
30 require_once($CFG->dirroot.'/mod/forum/post_form.php');
32 /// CONSTANTS ///////////////////////////////////////////////////////////
34 define('FORUM_MODE_FLATOLDEST', 1);
35 define('FORUM_MODE_FLATNEWEST', -1);
36 define('FORUM_MODE_THREADED', 2);
37 define('FORUM_MODE_NESTED', 3);
39 define('FORUM_CHOOSESUBSCRIBE', 0);
40 define('FORUM_FORCESUBSCRIBE', 1);
41 define('FORUM_INITIALSUBSCRIBE', 2);
42 define('FORUM_DISALLOWSUBSCRIBE',3);
44 define('FORUM_TRACKING_OFF', 0);
45 define('FORUM_TRACKING_OPTIONAL', 1);
46 define('FORUM_TRACKING_ON', 2);
48 define('FORUM_MAILED_PENDING', 0);
49 define('FORUM_MAILED_SUCCESS', 1);
50 define('FORUM_MAILED_ERROR', 2);
52 if (!defined('FORUM_CRON_USER_CACHE')) {
53 /** Defines how many full user records are cached in forum cron. */
54 define('FORUM_CRON_USER_CACHE', 5000);
57 /// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
59 /**
60 * Given an object containing all the necessary data,
61 * (defined by the form in mod_form.php) this function
62 * will create a new instance and return the id number
63 * of the new instance.
65 * @param stdClass $forum add forum instance
66 * @param mod_forum_mod_form $mform
67 * @return int intance id
69 function forum_add_instance($forum, $mform = null) {
70 global $CFG, $DB;
72 $forum->timemodified = time();
74 if (empty($forum->assessed)) {
75 $forum->assessed = 0;
78 if (empty($forum->ratingtime) or empty($forum->assessed)) {
79 $forum->assesstimestart = 0;
80 $forum->assesstimefinish = 0;
83 $forum->id = $DB->insert_record('forum', $forum);
84 $modcontext = context_module::instance($forum->coursemodule);
86 if ($forum->type == 'single') { // Create related discussion.
87 $discussion = new stdClass();
88 $discussion->course = $forum->course;
89 $discussion->forum = $forum->id;
90 $discussion->name = $forum->name;
91 $discussion->assessed = $forum->assessed;
92 $discussion->message = $forum->intro;
93 $discussion->messageformat = $forum->introformat;
94 $discussion->messagetrust = trusttext_trusted(context_course::instance($forum->course));
95 $discussion->mailnow = false;
96 $discussion->groupid = -1;
98 $message = '';
100 $discussion->id = forum_add_discussion($discussion, null, $message);
102 if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
103 // Ugly hack - we need to copy the files somehow.
104 $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
105 $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
107 $options = array('subdirs'=>true); // Use the same options as intro field!
108 $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
109 $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
113 if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
114 $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email');
115 foreach ($users as $user) {
116 forum_subscribe($user->id, $forum->id);
120 forum_grade_item_update($forum);
122 return $forum->id;
127 * Given an object containing all the necessary data,
128 * (defined by the form in mod_form.php) this function
129 * will update an existing instance with new data.
131 * @global object
132 * @param object $forum forum instance (with magic quotes)
133 * @return bool success
135 function forum_update_instance($forum, $mform) {
136 global $DB, $OUTPUT, $USER;
138 $forum->timemodified = time();
139 $forum->id = $forum->instance;
141 if (empty($forum->assessed)) {
142 $forum->assessed = 0;
145 if (empty($forum->ratingtime) or empty($forum->assessed)) {
146 $forum->assesstimestart = 0;
147 $forum->assesstimefinish = 0;
150 $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
152 // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
153 // if scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
154 // 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
155 if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
156 forum_update_grades($forum); // recalculate grades for the forum
159 if ($forum->type == 'single') { // Update related discussion and post.
160 $discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC');
161 if (!empty($discussions)) {
162 if (count($discussions) > 1) {
163 echo $OUTPUT->notification(get_string('warnformorepost', 'forum'));
165 $discussion = array_pop($discussions);
166 } else {
167 // try to recover by creating initial discussion - MDL-16262
168 $discussion = new stdClass();
169 $discussion->course = $forum->course;
170 $discussion->forum = $forum->id;
171 $discussion->name = $forum->name;
172 $discussion->assessed = $forum->assessed;
173 $discussion->message = $forum->intro;
174 $discussion->messageformat = $forum->introformat;
175 $discussion->messagetrust = true;
176 $discussion->mailnow = false;
177 $discussion->groupid = -1;
179 $message = '';
181 forum_add_discussion($discussion, null, $message);
183 if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
184 print_error('cannotadd', 'forum');
187 if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
188 print_error('cannotfindfirstpost', 'forum');
191 $cm = get_coursemodule_from_instance('forum', $forum->id);
192 $modcontext = context_module::instance($cm->id, MUST_EXIST);
194 $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
195 $post->subject = $forum->name;
196 $post->message = $forum->intro;
197 $post->messageformat = $forum->introformat;
198 $post->messagetrust = trusttext_trusted($modcontext);
199 $post->modified = $forum->timemodified;
200 $post->userid = $USER->id; // MDL-18599, so that current teacher can take ownership of activities.
202 if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
203 // Ugly hack - we need to copy the files somehow.
204 $options = array('subdirs'=>true); // Use the same options as intro field!
205 $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id, $options, $post->message);
208 $DB->update_record('forum_posts', $post);
209 $discussion->name = $forum->name;
210 $DB->update_record('forum_discussions', $discussion);
213 $DB->update_record('forum', $forum);
215 $modcontext = context_module::instance($forum->coursemodule);
216 if (($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) && ($oldforum->forcesubscribe <> $forum->forcesubscribe)) {
217 $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
218 foreach ($users as $user) {
219 forum_subscribe($user->id, $forum->id);
223 forum_grade_item_update($forum);
225 return true;
230 * Given an ID of an instance of this module,
231 * this function will permanently delete the instance
232 * and any data that depends on it.
234 * @global object
235 * @param int $id forum instance id
236 * @return bool success
238 function forum_delete_instance($id) {
239 global $DB;
241 if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
242 return false;
244 if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
245 return false;
247 if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
248 return false;
251 $context = context_module::instance($cm->id);
253 // now get rid of all files
254 $fs = get_file_storage();
255 $fs->delete_area_files($context->id);
257 $result = true;
259 if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
260 foreach ($discussions as $discussion) {
261 if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
262 $result = false;
267 if (!$DB->delete_records('forum_digests', array('forum' => $forum->id))) {
268 $result = false;
271 if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
272 $result = false;
275 forum_tp_delete_read_records(-1, -1, -1, $forum->id);
277 if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
278 $result = false;
281 forum_grade_item_delete($forum);
283 return $result;
288 * Indicates API features that the forum supports.
290 * @uses FEATURE_GROUPS
291 * @uses FEATURE_GROUPINGS
292 * @uses FEATURE_GROUPMEMBERSONLY
293 * @uses FEATURE_MOD_INTRO
294 * @uses FEATURE_COMPLETION_TRACKS_VIEWS
295 * @uses FEATURE_COMPLETION_HAS_RULES
296 * @uses FEATURE_GRADE_HAS_GRADE
297 * @uses FEATURE_GRADE_OUTCOMES
298 * @param string $feature
299 * @return mixed True if yes (some features may use other values)
301 function forum_supports($feature) {
302 switch($feature) {
303 case FEATURE_GROUPS: return true;
304 case FEATURE_GROUPINGS: return true;
305 case FEATURE_GROUPMEMBERSONLY: return true;
306 case FEATURE_MOD_INTRO: return true;
307 case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
308 case FEATURE_COMPLETION_HAS_RULES: return true;
309 case FEATURE_GRADE_HAS_GRADE: return true;
310 case FEATURE_GRADE_OUTCOMES: return true;
311 case FEATURE_RATE: return true;
312 case FEATURE_BACKUP_MOODLE2: return true;
313 case FEATURE_SHOW_DESCRIPTION: return true;
314 case FEATURE_PLAGIARISM: return true;
316 default: return null;
322 * Obtains the automatic completion state for this forum based on any conditions
323 * in forum settings.
325 * @global object
326 * @global object
327 * @param object $course Course
328 * @param object $cm Course-module
329 * @param int $userid User ID
330 * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
331 * @return bool True if completed, false if not. (If no conditions, then return
332 * value depends on comparison type)
334 function forum_get_completion_state($course,$cm,$userid,$type) {
335 global $CFG,$DB;
337 // Get forum details
338 if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
339 throw new Exception("Can't find forum {$cm->instance}");
342 $result=$type; // Default return value
344 $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
345 $postcountsql="
346 SELECT
347 COUNT(1)
348 FROM
349 {forum_posts} fp
350 INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
351 WHERE
352 fp.userid=:userid AND fd.forum=:forumid";
354 if ($forum->completiondiscussions) {
355 $value = $forum->completiondiscussions <=
356 $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
357 if ($type == COMPLETION_AND) {
358 $result = $result && $value;
359 } else {
360 $result = $result || $value;
363 if ($forum->completionreplies) {
364 $value = $forum->completionreplies <=
365 $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
366 if ($type==COMPLETION_AND) {
367 $result = $result && $value;
368 } else {
369 $result = $result || $value;
372 if ($forum->completionposts) {
373 $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
374 if ($type == COMPLETION_AND) {
375 $result = $result && $value;
376 } else {
377 $result = $result || $value;
381 return $result;
385 * Create a message-id string to use in the custom headers of forum notification emails
387 * message-id is used by email clients to identify emails and to nest conversations
389 * @param int $postid The ID of the forum post we are notifying the user about
390 * @param int $usertoid The ID of the user being notified
391 * @param string $hostname The server's hostname
392 * @return string A unique message-id
394 function forum_get_email_message_id($postid, $usertoid, $hostname) {
395 return '<'.hash('sha256',$postid.'to'.$usertoid).'@'.$hostname.'>';
399 * Removes properties from user record that are not necessary
400 * for sending post notifications.
401 * @param stdClass $user
402 * @return void, $user parameter is modified
404 function forum_cron_minimise_user_record(stdClass $user) {
406 // We store large amount of users in one huge array,
407 // make sure we do not store info there we do not actually need
408 // in mail generation code or messaging.
410 unset($user->institution);
411 unset($user->department);
412 unset($user->address);
413 unset($user->city);
414 unset($user->url);
415 unset($user->currentlogin);
416 unset($user->description);
417 unset($user->descriptionformat);
421 * Function to be run periodically according to the moodle cron
422 * Finds all posts that have yet to be mailed out, and mails them
423 * out to all subscribers
425 * @global object
426 * @global object
427 * @global object
428 * @uses CONTEXT_MODULE
429 * @uses CONTEXT_COURSE
430 * @uses SITEID
431 * @uses FORMAT_PLAIN
432 * @return void
434 function forum_cron() {
435 global $CFG, $USER, $DB;
437 $site = get_site();
439 // All users that are subscribed to any post that needs sending,
440 // please increase $CFG->extramemorylimit on large sites that
441 // send notifications to a large number of users.
442 $users = array();
443 $userscount = 0; // Cached user counter - count($users) in PHP is horribly slow!!!
445 // status arrays
446 $mailcount = array();
447 $errorcount = array();
449 // caches
450 $discussions = array();
451 $forums = array();
452 $courses = array();
453 $coursemodules = array();
454 $subscribedusers = array();
457 // Posts older than 2 days will not be mailed. This is to avoid the problem where
458 // cron has not been running for a long time, and then suddenly people are flooded
459 // with mail from the past few weeks or months
460 $timenow = time();
461 $endtime = $timenow - $CFG->maxeditingtime;
462 $starttime = $endtime - 48 * 3600; // Two days earlier
464 // Get the list of forum subscriptions for per-user per-forum maildigest settings.
465 $digestsset = $DB->get_recordset('forum_digests', null, '', 'id, userid, forum, maildigest');
466 $digests = array();
467 foreach ($digestsset as $thisrow) {
468 if (!isset($digests[$thisrow->forum])) {
469 $digests[$thisrow->forum] = array();
471 $digests[$thisrow->forum][$thisrow->userid] = $thisrow->maildigest;
473 $digestsset->close();
475 if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
476 // Mark them all now as being mailed. It's unlikely but possible there
477 // might be an error later so that a post is NOT actually mailed out,
478 // but since mail isn't crucial, we can accept this risk. Doing it now
479 // prevents the risk of duplicated mails, which is a worse problem.
481 if (!forum_mark_old_posts_as_mailed($endtime)) {
482 mtrace('Errors occurred while trying to mark some posts as being mailed.');
483 return false; // Don't continue trying to mail them, in case we are in a cron loop
486 // checking post validity, and adding users to loop through later
487 foreach ($posts as $pid => $post) {
489 $discussionid = $post->discussion;
490 if (!isset($discussions[$discussionid])) {
491 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
492 $discussions[$discussionid] = $discussion;
493 } else {
494 mtrace('Could not find discussion '.$discussionid);
495 unset($posts[$pid]);
496 continue;
499 $forumid = $discussions[$discussionid]->forum;
500 if (!isset($forums[$forumid])) {
501 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
502 $forums[$forumid] = $forum;
503 } else {
504 mtrace('Could not find forum '.$forumid);
505 unset($posts[$pid]);
506 continue;
509 $courseid = $forums[$forumid]->course;
510 if (!isset($courses[$courseid])) {
511 if ($course = $DB->get_record('course', array('id' => $courseid))) {
512 $courses[$courseid] = $course;
513 } else {
514 mtrace('Could not find course '.$courseid);
515 unset($posts[$pid]);
516 continue;
519 if (!isset($coursemodules[$forumid])) {
520 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
521 $coursemodules[$forumid] = $cm;
522 } else {
523 mtrace('Could not find course module for forum '.$forumid);
524 unset($posts[$pid]);
525 continue;
530 // caching subscribed users of each forum
531 if (!isset($subscribedusers[$forumid])) {
532 $modcontext = context_module::instance($coursemodules[$forumid]->id);
533 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext, "u.*")) {
534 foreach ($subusers as $postuser) {
535 // this user is subscribed to this forum
536 $subscribedusers[$forumid][$postuser->id] = $postuser->id;
537 $userscount++;
538 if ($userscount > FORUM_CRON_USER_CACHE) {
539 // Store minimal user info.
540 $minuser = new stdClass();
541 $minuser->id = $postuser->id;
542 $users[$postuser->id] = $minuser;
543 } else {
544 // Cache full user record.
545 forum_cron_minimise_user_record($postuser);
546 $users[$postuser->id] = $postuser;
549 // Release memory.
550 unset($subusers);
551 unset($postuser);
555 $mailcount[$pid] = 0;
556 $errorcount[$pid] = 0;
560 if ($users && $posts) {
562 $urlinfo = parse_url($CFG->wwwroot);
563 $hostname = $urlinfo['host'];
565 foreach ($users as $userto) {
567 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
569 mtrace('Processing user '.$userto->id);
571 // Init user caches - we keep the cache for one cycle only,
572 // otherwise it could consume too much memory.
573 if (isset($userto->username)) {
574 $userto = clone($userto);
575 } else {
576 $userto = $DB->get_record('user', array('id' => $userto->id));
577 forum_cron_minimise_user_record($userto);
579 $userto->viewfullnames = array();
580 $userto->canpost = array();
581 $userto->markposts = array();
583 // set this so that the capabilities are cached, and environment matches receiving user
584 cron_setup_user($userto);
586 // reset the caches
587 foreach ($coursemodules as $forumid=>$unused) {
588 $coursemodules[$forumid]->cache = new stdClass();
589 $coursemodules[$forumid]->cache->caps = array();
590 unset($coursemodules[$forumid]->uservisible);
593 foreach ($posts as $pid => $post) {
595 // Set up the environment for the post, discussion, forum, course
596 $discussion = $discussions[$post->discussion];
597 $forum = $forums[$discussion->forum];
598 $course = $courses[$forum->course];
599 $cm =& $coursemodules[$forum->id];
601 // Do some checks to see if we can bail out now
602 // Only active enrolled users are in the list of subscribers
603 if (!isset($subscribedusers[$forum->id][$userto->id])) {
604 continue; // user does not subscribe to this forum
607 // Don't send email if the forum is Q&A and the user has not posted
608 // Initial topics are still mailed
609 if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id) && $pid != $discussion->firstpost) {
610 mtrace('Did not email '.$userto->id.' because user has not posted in discussion');
611 continue;
614 // Get info about the sending user
615 if (array_key_exists($post->userid, $users)) { // we might know him/her already
616 $userfrom = $users[$post->userid];
617 if (!isset($userfrom->idnumber)) {
618 // Minimalised user info, fetch full record.
619 $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
620 forum_cron_minimise_user_record($userfrom);
623 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
624 forum_cron_minimise_user_record($userfrom);
625 // Fetch only once if possible, we can add it to user list, it will be skipped anyway.
626 if ($userscount <= FORUM_CRON_USER_CACHE) {
627 $userscount++;
628 $users[$userfrom->id] = $userfrom;
631 } else {
632 mtrace('Could not find user '.$post->userid);
633 continue;
636 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
638 // setup global $COURSE properly - needed for roles and languages
639 cron_setup_user($userto, $course);
641 // Fill caches
642 if (!isset($userto->viewfullnames[$forum->id])) {
643 $modcontext = context_module::instance($cm->id);
644 $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
646 if (!isset($userto->canpost[$discussion->id])) {
647 $modcontext = context_module::instance($cm->id);
648 $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
650 if (!isset($userfrom->groups[$forum->id])) {
651 if (!isset($userfrom->groups)) {
652 $userfrom->groups = array();
653 if (isset($users[$userfrom->id])) {
654 $users[$userfrom->id]->groups = array();
657 $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
658 if (isset($users[$userfrom->id])) {
659 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
663 // Make sure groups allow this user to see this email
664 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used
665 if (!groups_group_exists($discussion->groupid)) { // Can't find group
666 continue; // Be safe and don't send it to anyone
669 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
670 // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
671 continue;
675 // Make sure we're allowed to see it...
676 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
677 mtrace('user '.$userto->id. ' can not see '.$post->id);
678 continue;
681 // OK so we need to send the email.
683 // Does the user want this post in a digest? If so postpone it for now.
684 $maildigest = forum_get_user_maildigest_bulk($digests, $userto, $forum->id);
686 if ($maildigest > 0) {
687 // This user wants the mails to be in digest form
688 $queue = new stdClass();
689 $queue->userid = $userto->id;
690 $queue->discussionid = $discussion->id;
691 $queue->postid = $post->id;
692 $queue->timemodified = $post->created;
693 $DB->insert_record('forum_queue', $queue);
694 continue;
698 // Prepare to actually send the post now, and build up the content
700 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
702 $userfrom->customheaders = array ( // Headers to make emails easier to track
703 'Precedence: Bulk',
704 'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
705 'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
706 'Message-ID: '.forum_get_email_message_id($post->id, $userto->id, $hostname),
707 'X-Course-Id: '.$course->id,
708 'X-Course-Name: '.format_string($course->fullname, true)
711 if ($post->parent) { // This post is a reply, so add headers for threading (see MDL-22551)
712 $userfrom->customheaders[] = 'In-Reply-To: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
713 $userfrom->customheaders[] = 'References: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
716 $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
718 $postsubject = html_to_text("$shortname: ".format_string($post->subject, true));
719 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
720 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
722 // Send the post now!
724 mtrace('Sending ', '');
726 $eventdata = new stdClass();
727 $eventdata->component = 'mod_forum';
728 $eventdata->name = 'posts';
729 $eventdata->userfrom = $userfrom;
730 $eventdata->userto = $userto;
731 $eventdata->subject = $postsubject;
732 $eventdata->fullmessage = $posttext;
733 $eventdata->fullmessageformat = FORMAT_PLAIN;
734 $eventdata->fullmessagehtml = $posthtml;
735 $eventdata->notification = 1;
737 // If forum_replytouser is not set then send mail using the noreplyaddress.
738 if (empty($CFG->forum_replytouser)) {
739 // Clone userfrom as it is referenced by $users.
740 $cloneduserfrom = clone($userfrom);
741 $cloneduserfrom->email = $CFG->noreplyaddress;
742 $eventdata->userfrom = $cloneduserfrom;
745 $smallmessagestrings = new stdClass();
746 $smallmessagestrings->user = fullname($userfrom);
747 $smallmessagestrings->forumname = "$shortname: ".format_string($forum->name,true).": ".$discussion->name;
748 $smallmessagestrings->message = $post->message;
749 //make sure strings are in message recipients language
750 $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
752 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
753 $eventdata->contexturlname = $discussion->name;
755 $mailresult = message_send($eventdata);
756 if (!$mailresult){
757 mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
758 " ($userto->email) .. not trying again.");
759 add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
760 substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
761 $errorcount[$post->id]++;
762 } else {
763 $mailcount[$post->id]++;
765 // Mark post as read if forum_usermarksread is set off
766 if (!$CFG->forum_usermarksread) {
767 $userto->markposts[$post->id] = $post->id;
771 mtrace('post '.$post->id. ': '.$post->subject);
774 // mark processed posts as read
775 forum_tp_mark_posts_read($userto, $userto->markposts);
776 unset($userto);
780 if ($posts) {
781 foreach ($posts as $post) {
782 mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
783 if ($errorcount[$post->id]) {
784 $DB->set_field('forum_posts', 'mailed', FORUM_MAILED_ERROR, array('id' => $post->id));
789 // release some memory
790 unset($subscribedusers);
791 unset($mailcount);
792 unset($errorcount);
794 cron_setup_user();
796 $sitetimezone = $CFG->timezone;
798 // Now see if there are any digest mails waiting to be sent, and if we should send them
800 mtrace('Starting digest processing...');
802 @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
804 if (!isset($CFG->digestmailtimelast)) { // To catch the first time
805 set_config('digestmailtimelast', 0);
808 $timenow = time();
809 $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
811 // Delete any really old ones (normally there shouldn't be any)
812 $weekago = $timenow - (7 * 24 * 3600);
813 $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
814 mtrace ('Cleaned old digest records');
816 if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
818 mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
820 $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
822 if ($digestposts_rs->valid()) {
824 // We have work to do
825 $usermailcount = 0;
827 //caches - reuse the those filled before too
828 $discussionposts = array();
829 $userdiscussions = array();
831 foreach ($digestposts_rs as $digestpost) {
832 if (!isset($posts[$digestpost->postid])) {
833 if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
834 $posts[$digestpost->postid] = $post;
835 } else {
836 continue;
839 $discussionid = $digestpost->discussionid;
840 if (!isset($discussions[$discussionid])) {
841 if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
842 $discussions[$discussionid] = $discussion;
843 } else {
844 continue;
847 $forumid = $discussions[$discussionid]->forum;
848 if (!isset($forums[$forumid])) {
849 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
850 $forums[$forumid] = $forum;
851 } else {
852 continue;
856 $courseid = $forums[$forumid]->course;
857 if (!isset($courses[$courseid])) {
858 if ($course = $DB->get_record('course', array('id' => $courseid))) {
859 $courses[$courseid] = $course;
860 } else {
861 continue;
865 if (!isset($coursemodules[$forumid])) {
866 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
867 $coursemodules[$forumid] = $cm;
868 } else {
869 continue;
872 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
873 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
875 $digestposts_rs->close(); /// Finished iteration, let's close the resultset
877 // Data collected, start sending out emails to each user
878 foreach ($userdiscussions as $userid => $thesediscussions) {
880 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
882 cron_setup_user();
884 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
886 // First of all delete all the queue entries for this user
887 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
889 // Init user caches - we keep the cache for one cycle only,
890 // otherwise it would unnecessarily consume memory.
891 if (array_key_exists($userid, $users) and isset($users[$userid]->username)) {
892 $userto = clone($users[$userid]);
893 } else {
894 $userto = $DB->get_record('user', array('id' => $userid));
895 forum_cron_minimise_user_record($userto);
897 $userto->viewfullnames = array();
898 $userto->canpost = array();
899 $userto->markposts = array();
901 // Override the language and timezone of the "current" user, so that
902 // mail is customised for the receiver.
903 cron_setup_user($userto);
905 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
907 $headerdata = new stdClass();
908 $headerdata->sitename = format_string($site->fullname, true);
909 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
911 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
912 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
914 $posthtml = "<head>";
915 /* foreach ($CFG->stylesheets as $stylesheet) {
916 //TODO: MDL-21120
917 $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
919 $posthtml .= "</head>\n<body id=\"email\">\n";
920 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
922 foreach ($thesediscussions as $discussionid) {
924 @set_time_limit(120); // to be reset for each post
926 $discussion = $discussions[$discussionid];
927 $forum = $forums[$discussion->forum];
928 $course = $courses[$forum->course];
929 $cm = $coursemodules[$forum->id];
931 //override language
932 cron_setup_user($userto, $course);
934 // Fill caches
935 if (!isset($userto->viewfullnames[$forum->id])) {
936 $modcontext = context_module::instance($cm->id);
937 $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
939 if (!isset($userto->canpost[$discussion->id])) {
940 $modcontext = context_module::instance($cm->id);
941 $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
944 $strforums = get_string('forums', 'forum');
945 $canunsubscribe = ! forum_is_forcesubscribed($forum);
946 $canreply = $userto->canpost[$discussion->id];
947 $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
949 $posttext .= "\n \n";
950 $posttext .= '=====================================================================';
951 $posttext .= "\n \n";
952 $posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
953 if ($discussion->name != $forum->name) {
954 $posttext .= " -> ".format_string($discussion->name,true);
956 $posttext .= "\n";
957 $posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
958 $posttext .= "\n";
960 $posthtml .= "<p><font face=\"sans-serif\">".
961 "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
962 "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
963 "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
964 if ($discussion->name == $forum->name) {
965 $posthtml .= "</font></p>";
966 } else {
967 $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
969 $posthtml .= '<p>';
971 $postsarray = $discussionposts[$discussionid];
972 sort($postsarray);
974 foreach ($postsarray as $postid) {
975 $post = $posts[$postid];
977 if (array_key_exists($post->userid, $users)) { // we might know him/her already
978 $userfrom = $users[$post->userid];
979 if (!isset($userfrom->idnumber)) {
980 $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
981 forum_cron_minimise_user_record($userfrom);
984 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
985 forum_cron_minimise_user_record($userfrom);
986 if ($userscount <= FORUM_CRON_USER_CACHE) {
987 $userscount++;
988 $users[$userfrom->id] = $userfrom;
991 } else {
992 mtrace('Could not find user '.$post->userid);
993 continue;
996 if (!isset($userfrom->groups[$forum->id])) {
997 if (!isset($userfrom->groups)) {
998 $userfrom->groups = array();
999 if (isset($users[$userfrom->id])) {
1000 $users[$userfrom->id]->groups = array();
1003 $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
1004 if (isset($users[$userfrom->id])) {
1005 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
1009 $userfrom->customheaders = array ("Precedence: Bulk");
1011 $maildigest = forum_get_user_maildigest_bulk($digests, $userto, $forum->id);
1012 if ($maildigest == 2) {
1013 // Subjects and link only
1014 $posttext .= "\n";
1015 $posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
1016 $by = new stdClass();
1017 $by->name = fullname($userfrom);
1018 $by->date = userdate($post->modified);
1019 $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
1020 $posttext .= "\n---------------------------------------------------------------------";
1022 $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
1023 $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>';
1025 } else {
1026 // The full treatment
1027 $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
1028 $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1030 // Create an array of postid's for this user to mark as read.
1031 if (!$CFG->forum_usermarksread) {
1032 $userto->markposts[$post->id] = $post->id;
1036 $footerlinks = array();
1037 if ($canunsubscribe) {
1038 $footerlinks[] = "<a href=\"$CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\">" . get_string("unsubscribe", "forum") . "</a>";
1039 } else {
1040 $footerlinks[] = get_string("everyoneissubscribed", "forum");
1042 $footerlinks[] = "<a href='{$CFG->wwwroot}/mod/forum/index.php?id={$forum->course}'>" . get_string("digestmailpost", "forum") . '</a>';
1043 $posthtml .= "\n<div class='mdl-right'><font size=\"1\">" . implode('&nbsp;', $footerlinks) . '</font></div>';
1044 $posthtml .= '<hr size="1" noshade="noshade" /></p>';
1046 $posthtml .= '</body>';
1048 if (empty($userto->mailformat) || $userto->mailformat != 1) {
1049 // This user DOESN'T want to receive HTML
1050 $posthtml = '';
1053 $attachment = $attachname='';
1054 // Directly email forum digests rather than sending them via messaging, use the
1055 // site shortname as 'from name', the noreply address will be used by email_to_user.
1056 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname);
1058 if (!$mailresult) {
1059 mtrace("ERROR!");
1060 echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
1061 add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
1062 } else {
1063 mtrace("success.");
1064 $usermailcount++;
1066 // Mark post as read if forum_usermarksread is set off
1067 forum_tp_mark_posts_read($userto, $userto->markposts);
1071 /// We have finishied all digest emails, update $CFG->digestmailtimelast
1072 set_config('digestmailtimelast', $timenow);
1075 cron_setup_user();
1077 if (!empty($usermailcount)) {
1078 mtrace(get_string('digestsentusers', 'forum', $usermailcount));
1081 if (!empty($CFG->forum_lastreadclean)) {
1082 $timenow = time();
1083 if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
1084 set_config('forum_lastreadclean', $timenow);
1085 mtrace('Removing old forum read tracking info...');
1086 forum_tp_clean_read_records();
1088 } else {
1089 set_config('forum_lastreadclean', time());
1093 return true;
1097 * Builds and returns the body of the email notification in plain text.
1099 * @global object
1100 * @global object
1101 * @uses CONTEXT_MODULE
1102 * @param object $course
1103 * @param object $cm
1104 * @param object $forum
1105 * @param object $discussion
1106 * @param object $post
1107 * @param object $userfrom
1108 * @param object $userto
1109 * @param boolean $bare
1110 * @return string The email body in plain text format.
1112 function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
1113 global $CFG, $USER;
1115 $modcontext = context_module::instance($cm->id);
1117 if (!isset($userto->viewfullnames[$forum->id])) {
1118 $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
1119 } else {
1120 $viewfullnames = $userto->viewfullnames[$forum->id];
1123 if (!isset($userto->canpost[$discussion->id])) {
1124 $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
1125 } else {
1126 $canreply = $userto->canpost[$discussion->id];
1129 $by = New stdClass;
1130 $by->name = fullname($userfrom, $viewfullnames);
1131 $by->date = userdate($post->modified, "", $userto->timezone);
1133 $strbynameondate = get_string('bynameondate', 'forum', $by);
1135 $strforums = get_string('forums', 'forum');
1137 $canunsubscribe = ! forum_is_forcesubscribed($forum);
1139 $posttext = '';
1141 if (!$bare) {
1142 $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
1143 $posttext = "$shortname -> $strforums -> ".format_string($forum->name,true);
1145 if ($discussion->name != $forum->name) {
1146 $posttext .= " -> ".format_string($discussion->name,true);
1150 // add absolute file links
1151 $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
1153 $posttext .= "\n";
1154 $posttext .= $CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id;
1155 $posttext .= "\n---------------------------------------------------------------------\n";
1156 $posttext .= format_string($post->subject,true);
1157 if ($bare) {
1158 $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
1160 $posttext .= "\n".$strbynameondate."\n";
1161 $posttext .= "---------------------------------------------------------------------\n";
1162 $posttext .= format_text_email($post->message, $post->messageformat);
1163 $posttext .= "\n\n";
1164 $posttext .= forum_print_attachments($post, $cm, "text");
1166 if (!$bare && $canreply) {
1167 $posttext .= "---------------------------------------------------------------------\n";
1168 $posttext .= get_string("postmailinfo", "forum", $shortname)."\n";
1169 $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1171 if (!$bare && $canunsubscribe) {
1172 $posttext .= "\n---------------------------------------------------------------------\n";
1173 $posttext .= get_string("unsubscribe", "forum");
1174 $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1177 $posttext .= "\n---------------------------------------------------------------------\n";
1178 $posttext .= get_string("digestmailpost", "forum");
1179 $posttext .= ": {$CFG->wwwroot}/mod/forum/index.php?id={$forum->course}\n";
1181 return $posttext;
1185 * Builds and returns the body of the email notification in html format.
1187 * @global object
1188 * @param object $course
1189 * @param object $cm
1190 * @param object $forum
1191 * @param object $discussion
1192 * @param object $post
1193 * @param object $userfrom
1194 * @param object $userto
1195 * @return string The email text in HTML format
1197 function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
1198 global $CFG;
1200 if ($userto->mailformat != 1) { // Needs to be HTML
1201 return '';
1204 if (!isset($userto->canpost[$discussion->id])) {
1205 $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course);
1206 } else {
1207 $canreply = $userto->canpost[$discussion->id];
1210 $strforums = get_string('forums', 'forum');
1211 $canunsubscribe = ! forum_is_forcesubscribed($forum);
1212 $shortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
1214 $posthtml = '<head>';
1215 /* foreach ($CFG->stylesheets as $stylesheet) {
1216 //TODO: MDL-21120
1217 $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
1219 $posthtml .= '</head>';
1220 $posthtml .= "\n<body id=\"email\">\n\n";
1222 $posthtml .= '<div class="navbar">'.
1223 '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$shortname.'</a> &raquo; '.
1224 '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1225 '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1226 if ($discussion->name == $forum->name) {
1227 $posthtml .= '</div>';
1228 } else {
1229 $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1230 format_string($discussion->name,true).'</a></div>';
1232 $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
1234 $footerlinks = array();
1235 if ($canunsubscribe) {
1236 $footerlinks[] = '<a href="' . $CFG->wwwroot . '/mod/forum/subscribe.php?id=' . $forum->id . '">' . get_string('unsubscribe', 'forum') . '</a>';
1237 $footerlinks[] = '<a href="' . $CFG->wwwroot . '/mod/forum/unsubscribeall.php">' . get_string('unsubscribeall', 'forum') . '</a>';
1239 $footerlinks[] = "<a href='{$CFG->wwwroot}/mod/forum/index.php?id={$forum->course}'>" . get_string('digestmailpost', 'forum') . '</a>';
1240 $posthtml .= '<hr /><div class="mdl-align unsubscribelink">' . implode('&nbsp;', $footerlinks) . '</div>';
1242 $posthtml .= '</body>';
1244 return $posthtml;
1250 * @param object $course
1251 * @param object $user
1252 * @param object $mod TODO this is not used in this function, refactor
1253 * @param object $forum
1254 * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
1256 function forum_user_outline($course, $user, $mod, $forum) {
1257 global $CFG;
1258 require_once("$CFG->libdir/gradelib.php");
1259 $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1260 if (empty($grades->items[0]->grades)) {
1261 $grade = false;
1262 } else {
1263 $grade = reset($grades->items[0]->grades);
1266 $count = forum_count_user_posts($forum->id, $user->id);
1268 if ($count && $count->postcount > 0) {
1269 $result = new stdClass();
1270 $result->info = get_string("numposts", "forum", $count->postcount);
1271 $result->time = $count->lastpost;
1272 if ($grade) {
1273 $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
1275 return $result;
1276 } else if ($grade) {
1277 $result = new stdClass();
1278 $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
1280 //datesubmitted == time created. dategraded == time modified or time overridden
1281 //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
1282 //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
1283 if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1284 $result->time = $grade->dategraded;
1285 } else {
1286 $result->time = $grade->datesubmitted;
1289 return $result;
1291 return NULL;
1296 * @global object
1297 * @global object
1298 * @param object $coure
1299 * @param object $user
1300 * @param object $mod
1301 * @param object $forum
1303 function forum_user_complete($course, $user, $mod, $forum) {
1304 global $CFG,$USER, $OUTPUT;
1305 require_once("$CFG->libdir/gradelib.php");
1307 $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1308 if (!empty($grades->items[0]->grades)) {
1309 $grade = reset($grades->items[0]->grades);
1310 echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1311 if ($grade->str_feedback) {
1312 echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1316 if ($posts = forum_get_user_posts($forum->id, $user->id)) {
1318 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
1319 print_error('invalidcoursemodule');
1321 $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
1323 foreach ($posts as $post) {
1324 if (!isset($discussions[$post->discussion])) {
1325 continue;
1327 $discussion = $discussions[$post->discussion];
1329 forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
1331 } else {
1332 echo "<p>".get_string("noposts", "forum")."</p>";
1342 * @global object
1343 * @global object
1344 * @global object
1345 * @param array $courses
1346 * @param array $htmlarray
1348 function forum_print_overview($courses,&$htmlarray) {
1349 global $USER, $CFG, $DB, $SESSION;
1351 if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1352 return array();
1355 if (!$forums = get_all_instances_in_courses('forum',$courses)) {
1356 return;
1359 // Courses to search for new posts
1360 $coursessqls = array();
1361 $params = array();
1362 foreach ($courses as $course) {
1364 // If the user has never entered into the course all posts are pending
1365 if ($course->lastaccess == 0) {
1366 $coursessqls[] = '(f.course = ?)';
1367 $params[] = $course->id;
1369 // Only posts created after the course last access
1370 } else {
1371 $coursessqls[] = '(f.course = ? AND p.created > ?)';
1372 $params[] = $course->id;
1373 $params[] = $course->lastaccess;
1376 $params[] = $USER->id;
1377 $coursessql = implode(' OR ', $coursessqls);
1379 $sql = "SELECT f.id, COUNT(*) as count "
1380 .'FROM {forum} f '
1381 .'JOIN {forum_discussions} d ON d.forum = f.id '
1382 .'JOIN {forum_posts} p ON p.discussion = d.id '
1383 ."WHERE ($coursessql) "
1384 .'AND p.userid != ? '
1385 .'GROUP BY f.id';
1387 if (!$new = $DB->get_records_sql($sql, $params)) {
1388 $new = array(); // avoid warnings
1391 // also get all forum tracking stuff ONCE.
1392 $trackingforums = array();
1393 foreach ($forums as $forum) {
1394 if (forum_tp_can_track_forums($forum)) {
1395 $trackingforums[$forum->id] = $forum;
1399 if (count($trackingforums) > 0) {
1400 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1401 $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
1402 ' FROM {forum_posts} p '.
1403 ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1404 ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1405 $params = array($USER->id);
1407 foreach ($trackingforums as $track) {
1408 $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1409 $params[] = $track->id;
1410 if (isset($SESSION->currentgroup[$track->course])) {
1411 $groupid = $SESSION->currentgroup[$track->course];
1412 } else {
1413 // get first groupid
1414 $groupids = groups_get_all_groups($track->course, $USER->id);
1415 if ($groupids) {
1416 reset($groupids);
1417 $groupid = key($groupids);
1418 $SESSION->currentgroup[$track->course] = $groupid;
1419 } else {
1420 $groupid = 0;
1422 unset($groupids);
1424 $params[] = $groupid;
1426 $sql = substr($sql,0,-3); // take off the last OR
1427 $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1428 $params[] = $cutoffdate;
1430 if (!$unread = $DB->get_records_sql($sql, $params)) {
1431 $unread = array();
1433 } else {
1434 $unread = array();
1437 if (empty($unread) and empty($new)) {
1438 return;
1441 $strforum = get_string('modulename','forum');
1443 foreach ($forums as $forum) {
1444 $str = '';
1445 $count = 0;
1446 $thisunread = 0;
1447 $showunread = false;
1448 // either we have something from logs, or trackposts, or nothing.
1449 if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1450 $count = $new[$forum->id]->count;
1452 if (array_key_exists($forum->id,$unread)) {
1453 $thisunread = $unread[$forum->id]->count;
1454 $showunread = true;
1456 if ($count > 0 || $thisunread > 0) {
1457 $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1458 $forum->name.'</a></div>';
1459 $str .= '<div class="info"><span class="postsincelogin">';
1460 $str .= get_string('overviewnumpostssince', 'forum', $count)."</span>";
1461 if (!empty($showunread)) {
1462 $str .= '<div class="unreadposts">'.get_string('overviewnumunread', 'forum', $thisunread).'</div>';
1464 $str .= '</div></div>';
1466 if (!empty($str)) {
1467 if (!array_key_exists($forum->course,$htmlarray)) {
1468 $htmlarray[$forum->course] = array();
1470 if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1471 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1473 $htmlarray[$forum->course]['forum'] .= $str;
1479 * Given a course and a date, prints a summary of all the new
1480 * messages posted in the course since that date
1482 * @global object
1483 * @global object
1484 * @global object
1485 * @uses CONTEXT_MODULE
1486 * @uses VISIBLEGROUPS
1487 * @param object $course
1488 * @param bool $viewfullnames capability
1489 * @param int $timestart
1490 * @return bool success
1492 function forum_print_recent_activity($course, $viewfullnames, $timestart) {
1493 global $CFG, $USER, $DB, $OUTPUT;
1495 // do not use log table if possible, it may be huge and is expensive to join with other tables
1497 if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1498 d.timestart, d.timeend, d.userid AS duserid,
1499 u.firstname, u.lastname, u.email, u.picture
1500 FROM {forum_posts} p
1501 JOIN {forum_discussions} d ON d.id = p.discussion
1502 JOIN {forum} f ON f.id = d.forum
1503 JOIN {user} u ON u.id = p.userid
1504 WHERE p.created > ? AND f.course = ?
1505 ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
1506 return false;
1509 $modinfo = get_fast_modinfo($course);
1511 $groupmodes = array();
1512 $cms = array();
1514 $strftimerecent = get_string('strftimerecent');
1516 $printposts = array();
1517 foreach ($posts as $post) {
1518 if (!isset($modinfo->instances['forum'][$post->forum])) {
1519 // not visible
1520 continue;
1522 $cm = $modinfo->instances['forum'][$post->forum];
1523 if (!$cm->uservisible) {
1524 continue;
1526 $context = context_module::instance($cm->id);
1528 if (!has_capability('mod/forum:viewdiscussion', $context)) {
1529 continue;
1532 if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1533 and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
1534 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1535 continue;
1539 $groupmode = groups_get_activity_groupmode($cm, $course);
1541 if ($groupmode) {
1542 if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
1543 // oki (Open discussions have groupid -1)
1544 } else {
1545 // separate mode
1546 if (isguestuser()) {
1547 // shortcut
1548 continue;
1551 if (!in_array($post->groupid, $modinfo->get_groups($cm->groupingid))) {
1552 continue;
1557 $printposts[] = $post;
1559 unset($posts);
1561 if (!$printposts) {
1562 return false;
1565 echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
1566 echo "\n<ul class='unlist'>\n";
1568 foreach ($printposts as $post) {
1569 $subjectclass = empty($post->parent) ? ' bold' : '';
1571 echo '<li><div class="head">'.
1572 '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1573 '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1574 '</div>';
1575 echo '<div class="info'.$subjectclass.'">';
1576 if (empty($post->parent)) {
1577 echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1578 } else {
1579 echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
1581 $post->subject = break_up_long_words(format_string($post->subject, true));
1582 echo $post->subject;
1583 echo "</a>\"</div></li>\n";
1586 echo "</ul>\n";
1588 return true;
1592 * Return grade for given user or all users.
1594 * @global object
1595 * @global object
1596 * @param object $forum
1597 * @param int $userid optional user id, 0 means all users
1598 * @return array array of grades, false if none
1600 function forum_get_user_grades($forum, $userid = 0) {
1601 global $CFG;
1603 require_once($CFG->dirroot.'/rating/lib.php');
1605 $ratingoptions = new stdClass;
1606 $ratingoptions->component = 'mod_forum';
1607 $ratingoptions->ratingarea = 'post';
1609 //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1610 $ratingoptions->modulename = 'forum';
1611 $ratingoptions->moduleid = $forum->id;
1612 $ratingoptions->userid = $userid;
1613 $ratingoptions->aggregationmethod = $forum->assessed;
1614 $ratingoptions->scaleid = $forum->scale;
1615 $ratingoptions->itemtable = 'forum_posts';
1616 $ratingoptions->itemtableusercolumn = 'userid';
1618 $rm = new rating_manager();
1619 return $rm->get_user_grades($ratingoptions);
1623 * Update activity grades
1625 * @category grade
1626 * @param object $forum
1627 * @param int $userid specific user only, 0 means all
1628 * @param boolean $nullifnone return null if grade does not exist
1629 * @return void
1631 function forum_update_grades($forum, $userid=0, $nullifnone=true) {
1632 global $CFG, $DB;
1633 require_once($CFG->libdir.'/gradelib.php');
1635 if (!$forum->assessed) {
1636 forum_grade_item_update($forum);
1638 } else if ($grades = forum_get_user_grades($forum, $userid)) {
1639 forum_grade_item_update($forum, $grades);
1641 } else if ($userid and $nullifnone) {
1642 $grade = new stdClass();
1643 $grade->userid = $userid;
1644 $grade->rawgrade = NULL;
1645 forum_grade_item_update($forum, $grade);
1647 } else {
1648 forum_grade_item_update($forum);
1653 * Update all grades in gradebook.
1654 * @global object
1656 function forum_upgrade_grades() {
1657 global $DB;
1659 $sql = "SELECT COUNT('x')
1660 FROM {forum} f, {course_modules} cm, {modules} m
1661 WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1662 $count = $DB->count_records_sql($sql);
1664 $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1665 FROM {forum} f, {course_modules} cm, {modules} m
1666 WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1667 $rs = $DB->get_recordset_sql($sql);
1668 if ($rs->valid()) {
1669 $pbar = new progress_bar('forumupgradegrades', 500, true);
1670 $i=0;
1671 foreach ($rs as $forum) {
1672 $i++;
1673 upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1674 forum_update_grades($forum, 0, false);
1675 $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
1678 $rs->close();
1682 * Create/update grade item for given forum
1684 * @category grade
1685 * @uses GRADE_TYPE_NONE
1686 * @uses GRADE_TYPE_VALUE
1687 * @uses GRADE_TYPE_SCALE
1688 * @param stdClass $forum Forum object with extra cmidnumber
1689 * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
1690 * @return int 0 if ok
1692 function forum_grade_item_update($forum, $grades=NULL) {
1693 global $CFG;
1694 if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1695 require_once($CFG->libdir.'/gradelib.php');
1698 $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
1700 if (!$forum->assessed or $forum->scale == 0) {
1701 $params['gradetype'] = GRADE_TYPE_NONE;
1703 } else if ($forum->scale > 0) {
1704 $params['gradetype'] = GRADE_TYPE_VALUE;
1705 $params['grademax'] = $forum->scale;
1706 $params['grademin'] = 0;
1708 } else if ($forum->scale < 0) {
1709 $params['gradetype'] = GRADE_TYPE_SCALE;
1710 $params['scaleid'] = -$forum->scale;
1713 if ($grades === 'reset') {
1714 $params['reset'] = true;
1715 $grades = NULL;
1718 return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
1722 * Delete grade item for given forum
1724 * @category grade
1725 * @param stdClass $forum Forum object
1726 * @return grade_item
1728 function forum_grade_item_delete($forum) {
1729 global $CFG;
1730 require_once($CFG->libdir.'/gradelib.php');
1732 return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
1737 * This function returns if a scale is being used by one forum
1739 * @global object
1740 * @param int $forumid
1741 * @param int $scaleid negative number
1742 * @return bool
1744 function forum_scale_used ($forumid,$scaleid) {
1745 global $DB;
1746 $return = false;
1748 $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
1750 if (!empty($rec) && !empty($scaleid)) {
1751 $return = true;
1754 return $return;
1758 * Checks if scale is being used by any instance of forum
1760 * This is used to find out if scale used anywhere
1762 * @global object
1763 * @param $scaleid int
1764 * @return boolean True if the scale is used by any forum
1766 function forum_scale_used_anywhere($scaleid) {
1767 global $DB;
1768 if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
1769 return true;
1770 } else {
1771 return false;
1775 // SQL FUNCTIONS ///////////////////////////////////////////////////////////
1778 * Gets a post with all info ready for forum_print_post
1779 * Most of these joins are just to get the forum id
1781 * @global object
1782 * @global object
1783 * @param int $postid
1784 * @return mixed array of posts or false
1786 function forum_get_post_full($postid) {
1787 global $CFG, $DB;
1789 $allnames = get_all_user_name_fields(true, 'u');
1790 return $DB->get_record_sql("SELECT p.*, d.forum, $allnames, u.email, u.picture, u.imagealt
1791 FROM {forum_posts} p
1792 JOIN {forum_discussions} d ON p.discussion = d.id
1793 LEFT JOIN {user} u ON p.userid = u.id
1794 WHERE p.id = ?", array($postid));
1798 * Gets posts with all info ready for forum_print_post
1799 * We pass forumid in because we always know it so no need to make a
1800 * complicated join to find it out.
1802 * @global object
1803 * @global object
1804 * @return mixed array of posts or false
1806 function forum_get_discussion_posts($discussion, $sort, $forumid) {
1807 global $CFG, $DB;
1809 $allnames = get_all_user_name_fields(true, 'u');
1810 return $DB->get_records_sql("SELECT p.*, $forumid AS forum, $allnames, u.email, u.picture, u.imagealt
1811 FROM {forum_posts} p
1812 LEFT JOIN {user} u ON p.userid = u.id
1813 WHERE p.discussion = ?
1814 AND p.parent > 0 $sort", array($discussion));
1818 * Gets all posts in discussion including top parent.
1820 * @global object
1821 * @global object
1822 * @global object
1823 * @param int $discussionid
1824 * @param string $sort
1825 * @param bool $tracking does user track the forum?
1826 * @return array of posts
1828 function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
1829 global $CFG, $DB, $USER;
1831 $tr_sel = "";
1832 $tr_join = "";
1833 $params = array();
1835 if ($tracking) {
1836 $now = time();
1837 $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1838 $tr_sel = ", fr.id AS postread";
1839 $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1840 $params[] = $USER->id;
1843 $allnames = get_all_user_name_fields(true, 'u');
1844 $params[] = $discussionid;
1845 if (!$posts = $DB->get_records_sql("SELECT p.*, $allnames, u.email, u.picture, u.imagealt $tr_sel
1846 FROM {forum_posts} p
1847 LEFT JOIN {user} u ON p.userid = u.id
1848 $tr_join
1849 WHERE p.discussion = ?
1850 ORDER BY $sort", $params)) {
1851 return array();
1854 foreach ($posts as $pid=>$p) {
1855 if ($tracking) {
1856 if (forum_tp_is_post_old($p)) {
1857 $posts[$pid]->postread = true;
1860 if (!$p->parent) {
1861 continue;
1863 if (!isset($posts[$p->parent])) {
1864 continue; // parent does not exist??
1866 if (!isset($posts[$p->parent]->children)) {
1867 $posts[$p->parent]->children = array();
1869 $posts[$p->parent]->children[$pid] =& $posts[$pid];
1872 return $posts;
1876 * Gets posts with all info ready for forum_print_post
1877 * We pass forumid in because we always know it so no need to make a
1878 * complicated join to find it out.
1880 * @global object
1881 * @global object
1882 * @param int $parent
1883 * @param int $forumid
1884 * @return array
1886 function forum_get_child_posts($parent, $forumid) {
1887 global $CFG, $DB;
1889 $allnames = get_all_user_name_fields(true, 'u');
1890 return $DB->get_records_sql("SELECT p.*, $forumid AS forum, $allnames, u.email, u.picture, u.imagealt
1891 FROM {forum_posts} p
1892 LEFT JOIN {user} u ON p.userid = u.id
1893 WHERE p.parent = ?
1894 ORDER BY p.created ASC", array($parent));
1898 * An array of forum objects that the user is allowed to read/search through.
1900 * @global object
1901 * @global object
1902 * @global object
1903 * @param int $userid
1904 * @param int $courseid if 0, we look for forums throughout the whole site.
1905 * @return array of forum objects, or false if no matches
1906 * Forum objects have the following attributes:
1907 * id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1908 * viewhiddentimedposts
1910 function forum_get_readable_forums($userid, $courseid=0) {
1912 global $CFG, $DB, $USER;
1913 require_once($CFG->dirroot.'/course/lib.php');
1915 if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
1916 print_error('notinstalled', 'forum');
1919 if ($courseid) {
1920 $courses = $DB->get_records('course', array('id' => $courseid));
1921 } else {
1922 // If no course is specified, then the user can see SITE + his courses.
1923 $courses1 = $DB->get_records('course', array('id' => SITEID));
1924 $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
1925 $courses = array_merge($courses1, $courses2);
1927 if (!$courses) {
1928 return array();
1931 $readableforums = array();
1933 foreach ($courses as $course) {
1935 $modinfo = get_fast_modinfo($course);
1937 if (empty($modinfo->instances['forum'])) {
1938 // hmm, no forums?
1939 continue;
1942 $courseforums = $DB->get_records('forum', array('course' => $course->id));
1944 foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1945 if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1946 continue;
1948 $context = context_module::instance($cm->id);
1949 $forum = $courseforums[$forumid];
1950 $forum->context = $context;
1951 $forum->cm = $cm;
1953 if (!has_capability('mod/forum:viewdiscussion', $context)) {
1954 continue;
1957 /// group access
1958 if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1960 $forum->onlygroups = $modinfo->get_groups($cm->groupingid);
1961 $forum->onlygroups[] = -1;
1964 /// hidden timed discussions
1965 $forum->viewhiddentimedposts = true;
1966 if (!empty($CFG->forum_enabletimedposts)) {
1967 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1968 $forum->viewhiddentimedposts = false;
1972 /// qanda access
1973 if ($forum->type == 'qanda'
1974 && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
1976 // We need to check whether the user has posted in the qanda forum.
1977 $forum->onlydiscussions = array(); // Holds discussion ids for the discussions
1978 // the user is allowed to see in this forum.
1979 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1980 foreach ($discussionspostedin as $d) {
1981 $forum->onlydiscussions[] = $d->id;
1986 $readableforums[$forum->id] = $forum;
1989 unset($modinfo);
1991 } // End foreach $courses
1993 return $readableforums;
1997 * Returns a list of posts found using an array of search terms.
1999 * @global object
2000 * @global object
2001 * @global object
2002 * @param array $searchterms array of search terms, e.g. word +word -word
2003 * @param int $courseid if 0, we search through the whole site
2004 * @param int $limitfrom
2005 * @param int $limitnum
2006 * @param int &$totalcount
2007 * @param string $extrasql
2008 * @return array|bool Array of posts found or false
2010 function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
2011 &$totalcount, $extrasql='') {
2012 global $CFG, $DB, $USER;
2013 require_once($CFG->libdir.'/searchlib.php');
2015 $forums = forum_get_readable_forums($USER->id, $courseid);
2017 if (count($forums) == 0) {
2018 $totalcount = 0;
2019 return false;
2022 $now = round(time(), -2); // db friendly
2024 $fullaccess = array();
2025 $where = array();
2026 $params = array();
2028 foreach ($forums as $forumid => $forum) {
2029 $select = array();
2031 if (!$forum->viewhiddentimedposts) {
2032 $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
2033 $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
2036 $cm = $forum->cm;
2037 $context = $forum->context;
2039 if ($forum->type == 'qanda'
2040 && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
2041 if (!empty($forum->onlydiscussions)) {
2042 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
2043 $params = array_merge($params, $discussionid_params);
2044 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
2045 } else {
2046 $select[] = "p.parent = 0";
2050 if (!empty($forum->onlygroups)) {
2051 list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
2052 $params = array_merge($params, $groupid_params);
2053 $select[] = "d.groupid $groupid_sql";
2056 if ($select) {
2057 $selects = implode(" AND ", $select);
2058 $where[] = "(d.forum = :forum{$forumid} AND $selects)";
2059 $params['forum'.$forumid] = $forumid;
2060 } else {
2061 $fullaccess[] = $forumid;
2065 if ($fullaccess) {
2066 list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
2067 $params = array_merge($params, $fullid_params);
2068 $where[] = "(d.forum $fullid_sql)";
2071 $selectdiscussion = "(".implode(" OR ", $where).")";
2073 $messagesearch = '';
2074 $searchstring = '';
2076 // Need to concat these back together for parser to work.
2077 foreach($searchterms as $searchterm){
2078 if ($searchstring != '') {
2079 $searchstring .= ' ';
2081 $searchstring .= $searchterm;
2084 // We need to allow quoted strings for the search. The quotes *should* be stripped
2085 // by the parser, but this should be examined carefully for security implications.
2086 $searchstring = str_replace("\\\"","\"",$searchstring);
2087 $parser = new search_parser();
2088 $lexer = new search_lexer($parser);
2090 if ($lexer->parse($searchstring)) {
2091 $parsearray = $parser->get_parsed_array();
2092 // Experimental feature under 1.8! MDL-8830
2093 // Use alternative text searches if defined
2094 // This feature only works under mysql until properly implemented for other DBs
2095 // Requires manual creation of text index for forum_posts before enabling it:
2096 // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2097 // Experimental feature under 1.8! MDL-8830
2098 if (!empty($CFG->forum_usetextsearches)) {
2099 list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
2100 'p.userid', 'u.id', 'u.firstname',
2101 'u.lastname', 'p.modified', 'd.forum');
2102 } else {
2103 list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
2104 'p.userid', 'u.id', 'u.firstname',
2105 'u.lastname', 'p.modified', 'd.forum');
2107 $params = array_merge($params, $msparams);
2110 $fromsql = "{forum_posts} p,
2111 {forum_discussions} d,
2112 {user} u";
2114 $selectsql = " $messagesearch
2115 AND p.discussion = d.id
2116 AND p.userid = u.id
2117 AND $selectdiscussion
2118 $extrasql";
2120 $countsql = "SELECT COUNT(*)
2121 FROM $fromsql
2122 WHERE $selectsql";
2124 $allnames = get_all_user_name_fields(true, 'u');
2125 $searchsql = "SELECT p.*,
2126 d.forum,
2127 $allnames,
2128 u.email,
2129 u.picture,
2130 u.imagealt
2131 FROM $fromsql
2132 WHERE $selectsql
2133 ORDER BY p.modified DESC";
2135 $totalcount = $DB->count_records_sql($countsql, $params);
2137 return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
2141 * Returns a list of ratings for a particular post - sorted.
2143 * TODO: Check if this function is actually used anywhere.
2144 * Up until the fix for MDL-27471 this function wasn't even returning.
2146 * @param stdClass $context
2147 * @param int $postid
2148 * @param string $sort
2149 * @return array Array of ratings or false
2151 function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
2152 $options = new stdClass;
2153 $options->context = $context;
2154 $options->component = 'mod_forum';
2155 $options->ratingarea = 'post';
2156 $options->itemid = $postid;
2157 $options->sort = "ORDER BY $sort";
2159 $rm = new rating_manager();
2160 return $rm->get_all_ratings_for_item($options);
2164 * Returns a list of all new posts that have not been mailed yet
2166 * @param int $starttime posts created after this time
2167 * @param int $endtime posts created before this
2168 * @param int $now used for timed discussions only
2169 * @return array
2171 function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
2172 global $CFG, $DB;
2174 $params = array();
2175 $params['mailed'] = FORUM_MAILED_PENDING;
2176 $params['ptimestart'] = $starttime;
2177 $params['ptimeend'] = $endtime;
2178 $params['mailnow'] = 1;
2180 if (!empty($CFG->forum_enabletimedposts)) {
2181 if (empty($now)) {
2182 $now = time();
2184 $timedsql = "AND (d.timestart < :dtimestart AND (d.timeend = 0 OR d.timeend > :dtimeend))";
2185 $params['dtimestart'] = $now;
2186 $params['dtimeend'] = $now;
2187 } else {
2188 $timedsql = "";
2191 return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2192 FROM {forum_posts} p
2193 JOIN {forum_discussions} d ON d.id = p.discussion
2194 WHERE p.mailed = :mailed
2195 AND p.created >= :ptimestart
2196 AND (p.created < :ptimeend OR p.mailnow = :mailnow)
2197 $timedsql
2198 ORDER BY p.modified ASC", $params);
2202 * Marks posts before a certain time as being mailed already
2204 * @global object
2205 * @global object
2206 * @param int $endtime
2207 * @param int $now Defaults to time()
2208 * @return bool
2210 function forum_mark_old_posts_as_mailed($endtime, $now=null) {
2211 global $CFG, $DB;
2213 if (empty($now)) {
2214 $now = time();
2217 $params = array();
2218 $params['mailedsuccess'] = FORUM_MAILED_SUCCESS;
2219 $params['now'] = $now;
2220 $params['endtime'] = $endtime;
2221 $params['mailnow'] = 1;
2222 $params['mailedpending'] = FORUM_MAILED_PENDING;
2224 if (empty($CFG->forum_enabletimedposts)) {
2225 return $DB->execute("UPDATE {forum_posts}
2226 SET mailed = :mailedsuccess
2227 WHERE (created < :endtime OR mailnow = :mailnow)
2228 AND mailed = :mailedpending", $params);
2229 } else {
2230 return $DB->execute("UPDATE {forum_posts}
2231 SET mailed = :mailedsuccess
2232 WHERE discussion NOT IN (SELECT d.id
2233 FROM {forum_discussions} d
2234 WHERE d.timestart > :now)
2235 AND (created < :endtime OR mailnow = :mailnow)
2236 AND mailed = :mailedpending", $params);
2241 * Get all the posts for a user in a forum suitable for forum_print_post
2243 * @global object
2244 * @global object
2245 * @uses CONTEXT_MODULE
2246 * @return array
2248 function forum_get_user_posts($forumid, $userid) {
2249 global $CFG, $DB;
2251 $timedsql = "";
2252 $params = array($forumid, $userid);
2254 if (!empty($CFG->forum_enabletimedposts)) {
2255 $cm = get_coursemodule_from_instance('forum', $forumid);
2256 if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2257 $now = time();
2258 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2259 $params[] = $now;
2260 $params[] = $now;
2264 $allnames = get_all_user_name_fields(true, 'u');
2265 return $DB->get_records_sql("SELECT p.*, d.forum, $allnames, u.email, u.picture, u.imagealt
2266 FROM {forum} f
2267 JOIN {forum_discussions} d ON d.forum = f.id
2268 JOIN {forum_posts} p ON p.discussion = d.id
2269 JOIN {user} u ON u.id = p.userid
2270 WHERE f.id = ?
2271 AND p.userid = ?
2272 $timedsql
2273 ORDER BY p.modified ASC", $params);
2277 * Get all the discussions user participated in
2279 * @global object
2280 * @global object
2281 * @uses CONTEXT_MODULE
2282 * @param int $forumid
2283 * @param int $userid
2284 * @return array Array or false
2286 function forum_get_user_involved_discussions($forumid, $userid) {
2287 global $CFG, $DB;
2289 $timedsql = "";
2290 $params = array($forumid, $userid);
2291 if (!empty($CFG->forum_enabletimedposts)) {
2292 $cm = get_coursemodule_from_instance('forum', $forumid);
2293 if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2294 $now = time();
2295 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2296 $params[] = $now;
2297 $params[] = $now;
2301 return $DB->get_records_sql("SELECT DISTINCT d.*
2302 FROM {forum} f
2303 JOIN {forum_discussions} d ON d.forum = f.id
2304 JOIN {forum_posts} p ON p.discussion = d.id
2305 WHERE f.id = ?
2306 AND p.userid = ?
2307 $timedsql", $params);
2311 * Get all the posts for a user in a forum suitable for forum_print_post
2313 * @global object
2314 * @global object
2315 * @param int $forumid
2316 * @param int $userid
2317 * @return array of counts or false
2319 function forum_count_user_posts($forumid, $userid) {
2320 global $CFG, $DB;
2322 $timedsql = "";
2323 $params = array($forumid, $userid);
2324 if (!empty($CFG->forum_enabletimedposts)) {
2325 $cm = get_coursemodule_from_instance('forum', $forumid);
2326 if (!has_capability('mod/forum:viewhiddentimedposts' , context_module::instance($cm->id))) {
2327 $now = time();
2328 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2329 $params[] = $now;
2330 $params[] = $now;
2334 return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2335 FROM {forum} f
2336 JOIN {forum_discussions} d ON d.forum = f.id
2337 JOIN {forum_posts} p ON p.discussion = d.id
2338 JOIN {user} u ON u.id = p.userid
2339 WHERE f.id = ?
2340 AND p.userid = ?
2341 $timedsql", $params);
2345 * Given a log entry, return the forum post details for it.
2347 * @global object
2348 * @global object
2349 * @param object $log
2350 * @return array|null
2352 function forum_get_post_from_log($log) {
2353 global $CFG, $DB;
2355 $allnames = get_all_user_name_fields(true, 'u');
2356 if ($log->action == "add post") {
2358 return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid, $allnames, u.email, u.picture
2359 FROM {forum_discussions} d,
2360 {forum_posts} p,
2361 {forum} f,
2362 {user} u
2363 WHERE p.id = ?
2364 AND d.id = p.discussion
2365 AND p.userid = u.id
2366 AND u.deleted <> '1'
2367 AND f.id = d.forum", array($log->info));
2370 } else if ($log->action == "add discussion") {
2372 return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid, $allnames, u.email, u.picture
2373 FROM {forum_discussions} d,
2374 {forum_posts} p,
2375 {forum} f,
2376 {user} u
2377 WHERE d.id = ?
2378 AND d.firstpost = p.id
2379 AND p.userid = u.id
2380 AND u.deleted <> '1'
2381 AND f.id = d.forum", array($log->info));
2383 return NULL;
2387 * Given a discussion id, return the first post from the discussion
2389 * @global object
2390 * @global object
2391 * @param int $dicsussionid
2392 * @return array
2394 function forum_get_firstpost_from_discussion($discussionid) {
2395 global $CFG, $DB;
2397 return $DB->get_record_sql("SELECT p.*
2398 FROM {forum_discussions} d,
2399 {forum_posts} p
2400 WHERE d.id = ?
2401 AND d.firstpost = p.id ", array($discussionid));
2405 * Returns an array of counts of replies to each discussion
2407 * @global object
2408 * @global object
2409 * @param int $forumid
2410 * @param string $forumsort
2411 * @param int $limit
2412 * @param int $page
2413 * @param int $perpage
2414 * @return array
2416 function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
2417 global $CFG, $DB;
2419 if ($limit > 0) {
2420 $limitfrom = 0;
2421 $limitnum = $limit;
2422 } else if ($page != -1) {
2423 $limitfrom = $page*$perpage;
2424 $limitnum = $perpage;
2425 } else {
2426 $limitfrom = 0;
2427 $limitnum = 0;
2430 if ($forumsort == "") {
2431 $orderby = "";
2432 $groupby = "";
2434 } else {
2435 $orderby = "ORDER BY $forumsort";
2436 $groupby = ", ".strtolower($forumsort);
2437 $groupby = str_replace('desc', '', $groupby);
2438 $groupby = str_replace('asc', '', $groupby);
2441 if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2442 $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
2443 FROM {forum_posts} p
2444 JOIN {forum_discussions} d ON p.discussion = d.id
2445 WHERE p.parent > 0 AND d.forum = ?
2446 GROUP BY p.discussion";
2447 return $DB->get_records_sql($sql, array($forumid));
2449 } else {
2450 $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
2451 FROM {forum_posts} p
2452 JOIN {forum_discussions} d ON p.discussion = d.id
2453 WHERE d.forum = ?
2454 GROUP BY p.discussion $groupby
2455 $orderby";
2456 return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
2461 * @global object
2462 * @global object
2463 * @global object
2464 * @staticvar array $cache
2465 * @param object $forum
2466 * @param object $cm
2467 * @param object $course
2468 * @return mixed
2470 function forum_count_discussions($forum, $cm, $course) {
2471 global $CFG, $DB, $USER;
2473 static $cache = array();
2475 $now = round(time(), -2); // db cache friendliness
2477 $params = array($course->id);
2479 if (!isset($cache[$course->id])) {
2480 if (!empty($CFG->forum_enabletimedposts)) {
2481 $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2482 $params[] = $now;
2483 $params[] = $now;
2484 } else {
2485 $timedsql = "";
2488 $sql = "SELECT f.id, COUNT(d.id) as dcount
2489 FROM {forum} f
2490 JOIN {forum_discussions} d ON d.forum = f.id
2491 WHERE f.course = ?
2492 $timedsql
2493 GROUP BY f.id";
2495 if ($counts = $DB->get_records_sql($sql, $params)) {
2496 foreach ($counts as $count) {
2497 $counts[$count->id] = $count->dcount;
2499 $cache[$course->id] = $counts;
2500 } else {
2501 $cache[$course->id] = array();
2505 if (empty($cache[$course->id][$forum->id])) {
2506 return 0;
2509 $groupmode = groups_get_activity_groupmode($cm, $course);
2511 if ($groupmode != SEPARATEGROUPS) {
2512 return $cache[$course->id][$forum->id];
2515 if (has_capability('moodle/site:accessallgroups', context_module::instance($cm->id))) {
2516 return $cache[$course->id][$forum->id];
2519 require_once($CFG->dirroot.'/course/lib.php');
2521 $modinfo = get_fast_modinfo($course);
2523 $mygroups = $modinfo->get_groups($cm->groupingid);
2525 // add all groups posts
2526 $mygroups[-1] = -1;
2528 list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2529 $params[] = $forum->id;
2531 if (!empty($CFG->forum_enabletimedposts)) {
2532 $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
2533 $params[] = $now;
2534 $params[] = $now;
2535 } else {
2536 $timedsql = "";
2539 $sql = "SELECT COUNT(d.id)
2540 FROM {forum_discussions} d
2541 WHERE d.groupid $mygroups_sql AND d.forum = ?
2542 $timedsql";
2544 return $DB->get_field_sql($sql, $params);
2548 * How many posts by other users are unrated by a given user in the given discussion?
2550 * TODO: Is this function still used anywhere?
2552 * @param int $discussionid
2553 * @param int $userid
2554 * @return mixed
2556 function forum_count_unrated_posts($discussionid, $userid) {
2557 global $CFG, $DB;
2559 $sql = "SELECT COUNT(*) as num
2560 FROM {forum_posts}
2561 WHERE parent > 0
2562 AND discussion = :discussionid
2563 AND userid <> :userid";
2564 $params = array('discussionid' => $discussionid, 'userid' => $userid);
2565 $posts = $DB->get_record_sql($sql, $params);
2566 if ($posts) {
2567 $sql = "SELECT count(*) as num
2568 FROM {forum_posts} p,
2569 {rating} r
2570 WHERE p.discussion = :discussionid AND
2571 p.id = r.itemid AND
2572 r.userid = userid AND
2573 r.component = 'mod_forum' AND
2574 r.ratingarea = 'post'";
2575 $rated = $DB->get_record_sql($sql, $params);
2576 if ($rated) {
2577 if ($posts->num > $rated->num) {
2578 return $posts->num - $rated->num;
2579 } else {
2580 return 0; // Just in case there was a counting error
2582 } else {
2583 return $posts->num;
2585 } else {
2586 return 0;
2591 * Get all discussions in a forum
2593 * @global object
2594 * @global object
2595 * @global object
2596 * @uses CONTEXT_MODULE
2597 * @uses VISIBLEGROUPS
2598 * @param object $cm
2599 * @param string $forumsort
2600 * @param bool $fullpost
2601 * @param int $unused
2602 * @param int $limit
2603 * @param bool $userlastmodified
2604 * @param int $page
2605 * @param int $perpage
2606 * @return array
2608 function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
2609 global $CFG, $DB, $USER;
2611 $timelimit = '';
2613 $now = round(time(), -2);
2614 $params = array($cm->instance);
2616 $modcontext = context_module::instance($cm->id);
2618 if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2619 return array();
2622 if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2624 if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2625 $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2626 $params[] = $now;
2627 $params[] = $now;
2628 if (isloggedin()) {
2629 $timelimit .= " OR d.userid = ?";
2630 $params[] = $USER->id;
2632 $timelimit .= ")";
2636 if ($limit > 0) {
2637 $limitfrom = 0;
2638 $limitnum = $limit;
2639 } else if ($page != -1) {
2640 $limitfrom = $page*$perpage;
2641 $limitnum = $perpage;
2642 } else {
2643 $limitfrom = 0;
2644 $limitnum = 0;
2647 $groupmode = groups_get_activity_groupmode($cm);
2648 $currentgroup = groups_get_activity_group($cm);
2650 if ($groupmode) {
2651 if (empty($modcontext)) {
2652 $modcontext = context_module::instance($cm->id);
2655 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2656 if ($currentgroup) {
2657 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2658 $params[] = $currentgroup;
2659 } else {
2660 $groupselect = "";
2663 } else {
2664 //seprate groups without access all
2665 if ($currentgroup) {
2666 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2667 $params[] = $currentgroup;
2668 } else {
2669 $groupselect = "AND d.groupid = -1";
2672 } else {
2673 $groupselect = "";
2677 if (empty($forumsort)) {
2678 $forumsort = "d.timemodified DESC";
2680 if (empty($fullpost)) {
2681 $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2682 } else {
2683 $postdata = "p.*";
2686 if (empty($userlastmodified)) { // We don't need to know this
2687 $umfields = "";
2688 $umtable = "";
2689 } else {
2690 $umfields = '';
2691 $umnames = get_all_user_name_fields();
2692 foreach ($umnames as $umname) {
2693 $umfields .= ', um.' . $umname . ' AS um' . $umname;
2695 $umtable = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
2698 $allnames = get_all_user_name_fields(true, 'u');
2699 $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend, $allnames,
2700 u.email, u.picture, u.imagealt $umfields
2701 FROM {forum_discussions} d
2702 JOIN {forum_posts} p ON p.discussion = d.id
2703 JOIN {user} u ON p.userid = u.id
2704 $umtable
2705 WHERE d.forum = ? AND p.parent = 0
2706 $timelimit $groupselect
2707 ORDER BY $forumsort";
2708 return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2713 * @global object
2714 * @global object
2715 * @global object
2716 * @uses CONTEXT_MODULE
2717 * @uses VISIBLEGROUPS
2718 * @param object $cm
2719 * @return array
2721 function forum_get_discussions_unread($cm) {
2722 global $CFG, $DB, $USER;
2724 $now = round(time(), -2);
2725 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2727 $params = array();
2728 $groupmode = groups_get_activity_groupmode($cm);
2729 $currentgroup = groups_get_activity_group($cm);
2731 if ($groupmode) {
2732 $modcontext = context_module::instance($cm->id);
2734 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2735 if ($currentgroup) {
2736 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2737 $params['currentgroup'] = $currentgroup;
2738 } else {
2739 $groupselect = "";
2742 } else {
2743 //separate groups without access all
2744 if ($currentgroup) {
2745 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2746 $params['currentgroup'] = $currentgroup;
2747 } else {
2748 $groupselect = "AND d.groupid = -1";
2751 } else {
2752 $groupselect = "";
2755 if (!empty($CFG->forum_enabletimedposts)) {
2756 $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2757 $params['now1'] = $now;
2758 $params['now2'] = $now;
2759 } else {
2760 $timedsql = "";
2763 $sql = "SELECT d.id, COUNT(p.id) AS unread
2764 FROM {forum_discussions} d
2765 JOIN {forum_posts} p ON p.discussion = d.id
2766 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
2767 WHERE d.forum = {$cm->instance}
2768 AND p.modified >= :cutoffdate AND r.id is NULL
2769 $groupselect
2770 $timedsql
2771 GROUP BY d.id";
2772 $params['cutoffdate'] = $cutoffdate;
2774 if ($unreads = $DB->get_records_sql($sql, $params)) {
2775 foreach ($unreads as $unread) {
2776 $unreads[$unread->id] = $unread->unread;
2778 return $unreads;
2779 } else {
2780 return array();
2785 * @global object
2786 * @global object
2787 * @global object
2788 * @uses CONEXT_MODULE
2789 * @uses VISIBLEGROUPS
2790 * @param object $cm
2791 * @return array
2793 function forum_get_discussions_count($cm) {
2794 global $CFG, $DB, $USER;
2796 $now = round(time(), -2);
2797 $params = array($cm->instance);
2798 $groupmode = groups_get_activity_groupmode($cm);
2799 $currentgroup = groups_get_activity_group($cm);
2801 if ($groupmode) {
2802 $modcontext = context_module::instance($cm->id);
2804 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2805 if ($currentgroup) {
2806 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2807 $params[] = $currentgroup;
2808 } else {
2809 $groupselect = "";
2812 } else {
2813 //seprate groups without access all
2814 if ($currentgroup) {
2815 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2816 $params[] = $currentgroup;
2817 } else {
2818 $groupselect = "AND d.groupid = -1";
2821 } else {
2822 $groupselect = "";
2825 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2827 $timelimit = "";
2829 if (!empty($CFG->forum_enabletimedposts)) {
2831 $modcontext = context_module::instance($cm->id);
2833 if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
2834 $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2835 $params[] = $now;
2836 $params[] = $now;
2837 if (isloggedin()) {
2838 $timelimit .= " OR d.userid = ?";
2839 $params[] = $USER->id;
2841 $timelimit .= ")";
2845 $sql = "SELECT COUNT(d.id)
2846 FROM {forum_discussions} d
2847 JOIN {forum_posts} p ON p.discussion = d.id
2848 WHERE d.forum = ? AND p.parent = 0
2849 $groupselect $timelimit";
2851 return $DB->get_field_sql($sql, $params);
2856 * Get all discussions started by a particular user in a course (or group)
2857 * This function no longer used ...
2859 * @todo Remove this function if no longer used
2860 * @global object
2861 * @global object
2862 * @param int $courseid
2863 * @param int $userid
2864 * @param int $groupid
2865 * @return array
2867 function forum_get_user_discussions($courseid, $userid, $groupid=0) {
2868 global $CFG, $DB;
2869 $params = array($courseid, $userid);
2870 if ($groupid) {
2871 $groupselect = " AND d.groupid = ? ";
2872 $params[] = $groupid;
2873 } else {
2874 $groupselect = "";
2877 $allnames = get_all_user_name_fields(true, 'u');
2878 return $DB->get_records_sql("SELECT p.*, d.groupid, $allnames, u.email, u.picture, u.imagealt,
2879 f.type as forumtype, f.name as forumname, f.id as forumid
2880 FROM {forum_discussions} d,
2881 {forum_posts} p,
2882 {user} u,
2883 {forum} f
2884 WHERE d.course = ?
2885 AND p.discussion = d.id
2886 AND p.parent = 0
2887 AND p.userid = u.id
2888 AND u.id = ?
2889 AND d.forum = f.id $groupselect
2890 ORDER BY p.created DESC", $params);
2894 * Get the list of potential subscribers to a forum.
2896 * @param object $forumcontext the forum context.
2897 * @param integer $groupid the id of a group, or 0 for all groups.
2898 * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2899 * @param string $sort sort order. As for get_users_by_capability.
2900 * @return array list of users.
2902 function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort = '') {
2903 global $DB;
2905 // only active enrolled users or everybody on the frontpage
2906 list($esql, $params) = get_enrolled_sql($forumcontext, 'mod/forum:allowforcesubscribe', $groupid, true);
2907 if (!$sort) {
2908 list($sort, $sortparams) = users_order_by_sql('u');
2909 $params = array_merge($params, $sortparams);
2912 $sql = "SELECT $fields
2913 FROM {user} u
2914 JOIN ($esql) je ON je.id = u.id
2915 ORDER BY $sort";
2917 return $DB->get_records_sql($sql, $params);
2921 * Returns list of user objects that are subscribed to this forum
2923 * @global object
2924 * @global object
2925 * @param object $course the course
2926 * @param forum $forum the forum
2927 * @param integer $groupid group id, or 0 for all.
2928 * @param object $context the forum context, to save re-fetching it where possible.
2929 * @param string $fields requested user fields (with "u." table prefix)
2930 * @return array list of users.
2932 function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
2933 global $CFG, $DB;
2935 $allnames = get_all_user_name_fields(true, 'u');
2936 if (empty($fields)) {
2937 $fields ="u.id,
2938 u.username,
2939 $allnames,
2940 u.maildisplay,
2941 u.mailformat,
2942 u.maildigest,
2943 u.imagealt,
2944 u.email,
2945 u.emailstop,
2946 u.city,
2947 u.country,
2948 u.lastaccess,
2949 u.lastlogin,
2950 u.picture,
2951 u.timezone,
2952 u.theme,
2953 u.lang,
2954 u.trackforums,
2955 u.mnethostid";
2958 if (empty($context)) {
2959 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2960 $context = context_module::instance($cm->id);
2963 if (forum_is_forcesubscribed($forum)) {
2964 $results = forum_get_potential_subscribers($context, $groupid, $fields, "u.email ASC");
2966 } else {
2967 // only active enrolled users or everybody on the frontpage
2968 list($esql, $params) = get_enrolled_sql($context, '', $groupid, true);
2969 $params['forumid'] = $forum->id;
2970 $results = $DB->get_records_sql("SELECT $fields
2971 FROM {user} u
2972 JOIN ($esql) je ON je.id = u.id
2973 JOIN {forum_subscriptions} s ON s.userid = u.id
2974 WHERE s.forum = :forumid
2975 ORDER BY u.email ASC", $params);
2978 // Guest user should never be subscribed to a forum.
2979 unset($results[$CFG->siteguest]);
2981 return $results;
2986 // OTHER FUNCTIONS ///////////////////////////////////////////////////////////
2990 * @global object
2991 * @global object
2992 * @param int $courseid
2993 * @param string $type
2995 function forum_get_course_forum($courseid, $type) {
2996 // How to set up special 1-per-course forums
2997 global $CFG, $DB, $OUTPUT, $USER;
2999 if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
3000 // There should always only be ONE, but with the right combination of
3001 // errors there might be more. In this case, just return the oldest one (lowest ID).
3002 foreach ($forums as $forum) {
3003 return $forum; // ie the first one
3007 // Doesn't exist, so create one now.
3008 $forum = new stdClass();
3009 $forum->course = $courseid;
3010 $forum->type = "$type";
3011 if (!empty($USER->htmleditor)) {
3012 $forum->introformat = $USER->htmleditor;
3014 switch ($forum->type) {
3015 case "news":
3016 $forum->name = get_string("namenews", "forum");
3017 $forum->intro = get_string("intronews", "forum");
3018 $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
3019 $forum->assessed = 0;
3020 if ($courseid == SITEID) {
3021 $forum->name = get_string("sitenews");
3022 $forum->forcesubscribe = 0;
3024 break;
3025 case "social":
3026 $forum->name = get_string("namesocial", "forum");
3027 $forum->intro = get_string("introsocial", "forum");
3028 $forum->assessed = 0;
3029 $forum->forcesubscribe = 0;
3030 break;
3031 case "blog":
3032 $forum->name = get_string('blogforum', 'forum');
3033 $forum->intro = get_string('introblog', 'forum');
3034 $forum->assessed = 0;
3035 $forum->forcesubscribe = 0;
3036 break;
3037 default:
3038 echo $OUTPUT->notification("That forum type doesn't exist!");
3039 return false;
3040 break;
3043 $forum->timemodified = time();
3044 $forum->id = $DB->insert_record("forum", $forum);
3046 if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
3047 echo $OUTPUT->notification("Could not find forum module!!");
3048 return false;
3050 $mod = new stdClass();
3051 $mod->course = $courseid;
3052 $mod->module = $module->id;
3053 $mod->instance = $forum->id;
3054 $mod->section = 0;
3055 include_once("$CFG->dirroot/course/lib.php");
3056 if (! $mod->coursemodule = add_course_module($mod) ) {
3057 echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
3058 return false;
3060 $sectionid = course_add_cm_to_section($courseid, $mod->coursemodule, 0);
3061 return $DB->get_record("forum", array("id" => "$forum->id"));
3066 * Given the data about a posting, builds up the HTML to display it and
3067 * returns the HTML in a string. This is designed for sending via HTML email.
3069 * @global object
3070 * @param object $course
3071 * @param object $cm
3072 * @param object $forum
3073 * @param object $discussion
3074 * @param object $post
3075 * @param object $userform
3076 * @param object $userto
3077 * @param bool $ownpost
3078 * @param bool $reply
3079 * @param bool $link
3080 * @param bool $rate
3081 * @param string $footer
3082 * @return string
3084 function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
3085 $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
3087 global $CFG, $OUTPUT;
3089 $modcontext = context_module::instance($cm->id);
3091 if (!isset($userto->viewfullnames[$forum->id])) {
3092 $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3093 } else {
3094 $viewfullnames = $userto->viewfullnames[$forum->id];
3097 // add absolute file links
3098 $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3100 // format the post body
3101 $options = new stdClass();
3102 $options->para = true;
3103 $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
3105 $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
3107 $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
3108 $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
3109 $output .= '</td>';
3111 if ($post->parent) {
3112 $output .= '<td class="topic">';
3113 } else {
3114 $output .= '<td class="topic starter">';
3116 $output .= '<div class="subject">'.format_string($post->subject).'</div>';
3118 $fullname = fullname($userfrom, $viewfullnames);
3119 $by = new stdClass();
3120 $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3121 $by->date = userdate($post->modified, '', $userto->timezone);
3122 $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3124 $output .= '</td></tr>';
3126 $output .= '<tr><td class="left side" valign="top">';
3128 if (isset($userfrom->groups)) {
3129 $groups = $userfrom->groups[$forum->id];
3130 } else {
3131 $groups = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
3134 if ($groups) {
3135 $output .= print_group_picture($groups, $course->id, false, true, true);
3136 } else {
3137 $output .= '&nbsp;';
3140 $output .= '</td><td class="content">';
3142 $attachments = forum_print_attachments($post, $cm, 'html');
3143 if ($attachments !== '') {
3144 $output .= '<div class="attachments">';
3145 $output .= $attachments;
3146 $output .= '</div>';
3149 $output .= $formattedtext;
3151 // Commands
3152 $commands = array();
3154 if ($post->parent) {
3155 $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3156 $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
3159 if ($reply) {
3160 $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/post.php?reply='.$post->id.'">'.
3161 get_string('reply', 'forum').'</a>';
3164 $output .= '<div class="commands">';
3165 $output .= implode(' | ', $commands);
3166 $output .= '</div>';
3168 // Context link to post if required
3169 if ($link) {
3170 $output .= '<div class="link">';
3171 $output .= '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#p'.$post->id.'">'.
3172 get_string('postincontext', 'forum').'</a>';
3173 $output .= '</div>';
3176 if ($footer) {
3177 $output .= '<div class="footer">'.$footer.'</div>';
3179 $output .= '</td></tr></table>'."\n\n";
3181 return $output;
3185 * Print a forum post
3187 * @global object
3188 * @global object
3189 * @uses FORUM_MODE_THREADED
3190 * @uses PORTFOLIO_FORMAT_PLAINHTML
3191 * @uses PORTFOLIO_FORMAT_FILE
3192 * @uses PORTFOLIO_FORMAT_RICHHTML
3193 * @uses PORTFOLIO_ADD_TEXT_LINK
3194 * @uses CONTEXT_MODULE
3195 * @param object $post The post to print.
3196 * @param object $discussion
3197 * @param object $forum
3198 * @param object $cm
3199 * @param object $course
3200 * @param boolean $ownpost Whether this post belongs to the current user.
3201 * @param boolean $reply Whether to print a 'reply' link at the bottom of the message.
3202 * @param boolean $link Just print a shortened version of the post as a link to the full post.
3203 * @param string $footer Extra stuff to print after the message.
3204 * @param string $highlight Space-separated list of terms to highlight.
3205 * @param int $post_read true, false or -99. If we already know whether this user
3206 * has read this post, pass that in, otherwise, pass in -99, and this
3207 * function will work it out.
3208 * @param boolean $dummyifcantsee When forum_user_can_see_post says that
3209 * the current user can't see this post, if this argument is true
3210 * (the default) then print a dummy 'you can't see this post' post.
3211 * If false, don't output anything at all.
3212 * @param bool|null $istracked
3213 * @return void
3215 function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=false, $reply=false, $link=false,
3216 $footer="", $highlight="", $postisread=null, $dummyifcantsee=true, $istracked=null, $return=false) {
3217 global $USER, $CFG, $OUTPUT;
3219 require_once($CFG->libdir . '/filelib.php');
3221 // String cache
3222 static $str;
3224 $modcontext = context_module::instance($cm->id);
3226 $post->course = $course->id;
3227 $post->forum = $forum->id;
3228 $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3229 if (!empty($CFG->enableplagiarism)) {
3230 require_once($CFG->libdir.'/plagiarismlib.php');
3231 $post->message .= plagiarism_get_links(array('userid' => $post->userid,
3232 'content' => $post->message,
3233 'cmid' => $cm->id,
3234 'course' => $post->course,
3235 'forum' => $post->forum));
3238 // caching
3239 if (!isset($cm->cache)) {
3240 $cm->cache = new stdClass;
3243 if (!isset($cm->cache->caps)) {
3244 $cm->cache->caps = array();
3245 $cm->cache->caps['mod/forum:viewdiscussion'] = has_capability('mod/forum:viewdiscussion', $modcontext);
3246 $cm->cache->caps['moodle/site:viewfullnames'] = has_capability('moodle/site:viewfullnames', $modcontext);
3247 $cm->cache->caps['mod/forum:editanypost'] = has_capability('mod/forum:editanypost', $modcontext);
3248 $cm->cache->caps['mod/forum:splitdiscussions'] = has_capability('mod/forum:splitdiscussions', $modcontext);
3249 $cm->cache->caps['mod/forum:deleteownpost'] = has_capability('mod/forum:deleteownpost', $modcontext);
3250 $cm->cache->caps['mod/forum:deleteanypost'] = has_capability('mod/forum:deleteanypost', $modcontext);
3251 $cm->cache->caps['mod/forum:viewanyrating'] = has_capability('mod/forum:viewanyrating', $modcontext);
3252 $cm->cache->caps['mod/forum:exportpost'] = has_capability('mod/forum:exportpost', $modcontext);
3253 $cm->cache->caps['mod/forum:exportownpost'] = has_capability('mod/forum:exportownpost', $modcontext);
3256 if (!isset($cm->uservisible)) {
3257 $cm->uservisible = coursemodule_visible_for_user($cm);
3260 if ($istracked && is_null($postisread)) {
3261 $postisread = forum_tp_is_post_read($USER->id, $post);
3264 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
3265 $output = '';
3266 if (!$dummyifcantsee) {
3267 if ($return) {
3268 return $output;
3270 echo $output;
3271 return;
3273 $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3274 $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'));
3275 $output .= html_writer::start_tag('div', array('class'=>'row header'));
3276 $output .= html_writer::tag('div', '', array('class'=>'left picture')); // Picture
3277 if ($post->parent) {
3278 $output .= html_writer::start_tag('div', array('class'=>'topic'));
3279 } else {
3280 $output .= html_writer::start_tag('div', array('class'=>'topic starter'));
3282 $output .= html_writer::tag('div', get_string('forumsubjecthidden','forum'), array('class'=>'subject')); // Subject
3283 $output .= html_writer::tag('div', get_string('forumauthorhidden','forum'), array('class'=>'author')); // author
3284 $output .= html_writer::end_tag('div');
3285 $output .= html_writer::end_tag('div'); // row
3286 $output .= html_writer::start_tag('div', array('class'=>'row'));
3287 $output .= html_writer::tag('div', '&nbsp;', array('class'=>'left side')); // Groups
3288 $output .= html_writer::tag('div', get_string('forumbodyhidden','forum'), array('class'=>'content')); // Content
3289 $output .= html_writer::end_tag('div'); // row
3290 $output .= html_writer::end_tag('div'); // forumpost
3292 if ($return) {
3293 return $output;
3295 echo $output;
3296 return;
3299 if (empty($str)) {
3300 $str = new stdClass;
3301 $str->edit = get_string('edit', 'forum');
3302 $str->delete = get_string('delete', 'forum');
3303 $str->reply = get_string('reply', 'forum');
3304 $str->parent = get_string('parent', 'forum');
3305 $str->pruneheading = get_string('pruneheading', 'forum');
3306 $str->prune = get_string('prune', 'forum');
3307 $str->displaymode = get_user_preferences('forum_displaymode', $CFG->forum_displaymode);
3308 $str->markread = get_string('markread', 'forum');
3309 $str->markunread = get_string('markunread', 'forum');
3312 $discussionlink = new moodle_url('/mod/forum/discuss.php', array('d'=>$post->discussion));
3314 // Build an object that represents the posting user
3315 $postuser = new stdClass;
3316 $postuser->id = $post->userid;
3317 foreach (get_all_user_name_fields() as $addname) {
3318 $postuser->$addname = $post->$addname;
3320 $postuser->imagealt = $post->imagealt;
3321 $postuser->picture = $post->picture;
3322 $postuser->email = $post->email;
3323 // Some handy things for later on
3324 $postuser->fullname = fullname($postuser, $cm->cache->caps['moodle/site:viewfullnames']);
3325 $postuser->profilelink = new moodle_url('/user/view.php', array('id'=>$post->userid, 'course'=>$course->id));
3327 // Prepare the groups the posting user belongs to
3328 if (isset($cm->cache->usersgroups)) {
3329 $groups = array();
3330 if (isset($cm->cache->usersgroups[$post->userid])) {
3331 foreach ($cm->cache->usersgroups[$post->userid] as $gid) {
3332 $groups[$gid] = $cm->cache->groups[$gid];
3335 } else {
3336 $groups = groups_get_all_groups($course->id, $post->userid, $cm->groupingid);
3339 // Prepare the attachements for the post, files then images
3340 list($attachments, $attachedimages) = forum_print_attachments($post, $cm, 'separateimages');
3342 // Determine if we need to shorten this post
3343 $shortenpost = ($link && (strlen(strip_tags($post->message)) > $CFG->forum_longpost));
3346 // Prepare an array of commands
3347 $commands = array();
3349 // SPECIAL CASE: The front page can display a news item post to non-logged in users.
3350 // Don't display the mark read / unread controls in this case.
3351 if ($istracked && $CFG->forum_usermarksread && isloggedin()) {
3352 $url = new moodle_url($discussionlink, array('postid'=>$post->id, 'mark'=>'unread'));
3353 $text = $str->markunread;
3354 if (!$postisread) {
3355 $url->param('mark', 'read');
3356 $text = $str->markread;
3358 if ($str->displaymode == FORUM_MODE_THREADED) {
3359 $url->param('parent', $post->parent);
3360 } else {
3361 $url->set_anchor('p'.$post->id);
3363 $commands[] = array('url'=>$url, 'text'=>$text);
3366 // Zoom in to the parent specifically
3367 if ($post->parent) {
3368 $url = new moodle_url($discussionlink);
3369 if ($str->displaymode == FORUM_MODE_THREADED) {
3370 $url->param('parent', $post->parent);
3371 } else {
3372 $url->set_anchor('p'.$post->parent);
3374 $commands[] = array('url'=>$url, 'text'=>$str->parent);
3377 // Hack for allow to edit news posts those are not displayed yet until they are displayed
3378 $age = time() - $post->created;
3379 if (!$post->parent && $forum->type == 'news' && $discussion->timestart > time()) {
3380 $age = 0;
3383 if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
3384 if (has_capability('moodle/course:manageactivities', $modcontext)) {
3385 // The first post in single simple is the forum description.
3386 $commands[] = array('url'=>new moodle_url('/course/modedit.php', array('update'=>$cm->id, 'sesskey'=>sesskey(), 'return'=>1)), 'text'=>$str->edit);
3388 } else if (($ownpost && $age < $CFG->maxeditingtime) || $cm->cache->caps['mod/forum:editanypost']) {
3389 $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('edit'=>$post->id)), 'text'=>$str->edit);
3392 if ($cm->cache->caps['mod/forum:splitdiscussions'] && $post->parent && $forum->type != 'single') {
3393 $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('prune'=>$post->id)), 'text'=>$str->prune, 'title'=>$str->pruneheading);
3396 if ($forum->type == 'single' and $discussion->firstpost == $post->id) {
3397 // Do not allow deleting of first post in single simple type.
3398 } else if (($ownpost && $age < $CFG->maxeditingtime && $cm->cache->caps['mod/forum:deleteownpost']) || $cm->cache->caps['mod/forum:deleteanypost']) {
3399 $commands[] = array('url'=>new moodle_url('/mod/forum/post.php', array('delete'=>$post->id)), 'text'=>$str->delete);
3402 if ($reply) {
3403 $commands[] = array('url'=>new moodle_url('/mod/forum/post.php#mformforum', array('reply'=>$post->id)), 'text'=>$str->reply);
3406 if ($CFG->enableportfolios && ($cm->cache->caps['mod/forum:exportpost'] || ($ownpost && $cm->cache->caps['mod/forum:exportownpost']))) {
3407 $p = array('postid' => $post->id);
3408 require_once($CFG->libdir.'/portfoliolib.php');
3409 $button = new portfolio_add_button();
3410 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id), 'mod_forum');
3411 if (empty($attachments)) {
3412 $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
3413 } else {
3414 $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
3417 $porfoliohtml = $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
3418 if (!empty($porfoliohtml)) {
3419 $commands[] = $porfoliohtml;
3422 // Finished building commands
3425 // Begin output
3427 $output = '';
3429 if ($istracked) {
3430 if ($postisread) {
3431 $forumpostclass = ' read';
3432 } else {
3433 $forumpostclass = ' unread';
3434 $output .= html_writer::tag('a', '', array('name'=>'unread'));
3436 } else {
3437 // ignore trackign status if not tracked or tracked param missing
3438 $forumpostclass = '';
3441 $topicclass = '';
3442 if (empty($post->parent)) {
3443 $topicclass = ' firstpost starter';
3446 $output .= html_writer::tag('a', '', array('id'=>'p'.$post->id));
3447 $output .= html_writer::start_tag('div', array('class'=>'forumpost clearfix'.$forumpostclass.$topicclass));
3448 $output .= html_writer::start_tag('div', array('class'=>'row header clearfix'));
3449 $output .= html_writer::start_tag('div', array('class'=>'left picture'));
3450 $output .= $OUTPUT->user_picture($postuser, array('courseid'=>$course->id));
3451 $output .= html_writer::end_tag('div');
3454 $output .= html_writer::start_tag('div', array('class'=>'topic'.$topicclass));
3456 $postsubject = $post->subject;
3457 if (empty($post->subjectnoformat)) {
3458 $postsubject = format_string($postsubject);
3460 $output .= html_writer::tag('div', $postsubject, array('class'=>'subject'));
3462 $by = new stdClass();
3463 $by->name = html_writer::link($postuser->profilelink, $postuser->fullname);
3464 $by->date = userdate($post->modified);
3465 $output .= html_writer::tag('div', get_string('bynameondate', 'forum', $by), array('class'=>'author'));
3467 $output .= html_writer::end_tag('div'); //topic
3468 $output .= html_writer::end_tag('div'); //row
3470 $output .= html_writer::start_tag('div', array('class'=>'row maincontent clearfix'));
3471 $output .= html_writer::start_tag('div', array('class'=>'left'));
3473 $groupoutput = '';
3474 if ($groups) {
3475 $groupoutput = print_group_picture($groups, $course->id, false, true, true);
3477 if (empty($groupoutput)) {
3478 $groupoutput = '&nbsp;';
3480 $output .= html_writer::tag('div', $groupoutput, array('class'=>'grouppictures'));
3482 $output .= html_writer::end_tag('div'); //left side
3483 $output .= html_writer::start_tag('div', array('class'=>'no-overflow'));
3484 $output .= html_writer::start_tag('div', array('class'=>'content'));
3485 if (!empty($attachments)) {
3486 $output .= html_writer::tag('div', $attachments, array('class'=>'attachments'));
3489 $options = new stdClass;
3490 $options->para = false;
3491 $options->trusted = $post->messagetrust;
3492 $options->context = $modcontext;
3493 if ($shortenpost) {
3494 // Prepare shortened version
3495 $postclass = 'shortenedpost';
3496 $postcontent = format_text(forum_shorten_post($post->message), $post->messageformat, $options, $course->id);
3497 $postcontent .= html_writer::link($discussionlink, get_string('readtherest', 'forum'));
3498 $postcontent .= html_writer::tag('div', '('.get_string('numwords', 'moodle', count_words($post->message)).')',
3499 array('class'=>'post-word-count'));
3500 } else {
3501 // Prepare whole post
3502 $postclass = 'fullpost';
3503 $postcontent = format_text($post->message, $post->messageformat, $options, $course->id);
3504 if (!empty($highlight)) {
3505 $postcontent = highlight($highlight, $postcontent);
3507 if (!empty($forum->displaywordcount)) {
3508 $postcontent .= html_writer::tag('div', get_string('numwords', 'moodle', count_words($post->message)),
3509 array('class'=>'post-word-count'));
3511 $postcontent .= html_writer::tag('div', $attachedimages, array('class'=>'attachedimages'));
3514 // Output the post content
3515 $output .= html_writer::tag('div', $postcontent, array('class'=>'posting '.$postclass));
3516 $output .= html_writer::end_tag('div'); // Content
3517 $output .= html_writer::end_tag('div'); // Content mask
3518 $output .= html_writer::end_tag('div'); // Row
3520 $output .= html_writer::start_tag('div', array('class'=>'row side'));
3521 $output .= html_writer::tag('div','&nbsp;', array('class'=>'left'));
3522 $output .= html_writer::start_tag('div', array('class'=>'options clearfix'));
3524 // Output ratings
3525 if (!empty($post->rating)) {
3526 $output .= html_writer::tag('div', $OUTPUT->render($post->rating), array('class'=>'forum-post-rating'));
3529 // Output the commands
3530 $commandhtml = array();
3531 foreach ($commands as $command) {
3532 if (is_array($command)) {
3533 $commandhtml[] = html_writer::link($command['url'], $command['text']);
3534 } else {
3535 $commandhtml[] = $command;
3538 $output .= html_writer::tag('div', implode(' | ', $commandhtml), array('class'=>'commands'));
3540 // Output link to post if required
3541 if ($link) {
3542 if ($post->replies == 1) {
3543 $replystring = get_string('repliesone', 'forum', $post->replies);
3544 } else {
3545 $replystring = get_string('repliesmany', 'forum', $post->replies);
3548 $output .= html_writer::start_tag('div', array('class'=>'link'));
3549 $output .= html_writer::link($discussionlink, get_string('discussthistopic', 'forum'));
3550 $output .= '&nbsp;('.$replystring.')';
3551 $output .= html_writer::end_tag('div'); // link
3554 // Output footer if required
3555 if ($footer) {
3556 $output .= html_writer::tag('div', $footer, array('class'=>'footer'));
3559 // Close remaining open divs
3560 $output .= html_writer::end_tag('div'); // content
3561 $output .= html_writer::end_tag('div'); // row
3562 $output .= html_writer::end_tag('div'); // forumpost
3564 // Mark the forum post as read if required
3565 if ($istracked && !$CFG->forum_usermarksread && !$postisread) {
3566 forum_tp_mark_post_read($USER->id, $post, $forum->id);
3569 if ($return) {
3570 return $output;
3572 echo $output;
3573 return;
3577 * Return rating related permissions
3579 * @param string $options the context id
3580 * @return array an associative array of the user's rating permissions
3582 function forum_rating_permissions($contextid, $component, $ratingarea) {
3583 $context = context::instance_by_id($contextid, MUST_EXIST);
3584 if ($component != 'mod_forum' || $ratingarea != 'post') {
3585 // We don't know about this component/ratingarea so just return null to get the
3586 // default restrictive permissions.
3587 return null;
3589 return array(
3590 'view' => has_capability('mod/forum:viewrating', $context),
3591 'viewany' => has_capability('mod/forum:viewanyrating', $context),
3592 'viewall' => has_capability('mod/forum:viewallratings', $context),
3593 'rate' => has_capability('mod/forum:rate', $context)
3598 * Validates a submitted rating
3599 * @param array $params submitted data
3600 * context => object the context in which the rated items exists [required]
3601 * component => The component for this module - should always be mod_forum [required]
3602 * ratingarea => object the context in which the rated items exists [required]
3603 * itemid => int the ID of the object being rated [required]
3604 * scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
3605 * rating => int the submitted rating [required]
3606 * rateduserid => int the id of the user whose items have been rated. NOT the user who submitted the ratings. 0 to update all. [required]
3607 * aggregation => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [required]
3608 * @return boolean true if the rating is valid. Will throw rating_exception if not
3610 function forum_rating_validate($params) {
3611 global $DB, $USER;
3613 // Check the component is mod_forum
3614 if ($params['component'] != 'mod_forum') {
3615 throw new rating_exception('invalidcomponent');
3618 // Check the ratingarea is post (the only rating area in forum)
3619 if ($params['ratingarea'] != 'post') {
3620 throw new rating_exception('invalidratingarea');
3623 // Check the rateduserid is not the current user .. you can't rate your own posts
3624 if ($params['rateduserid'] == $USER->id) {
3625 throw new rating_exception('nopermissiontorate');
3628 // Fetch all the related records ... we need to do this anyway to call forum_user_can_see_post
3629 $post = $DB->get_record('forum_posts', array('id' => $params['itemid'], 'userid' => $params['rateduserid']), '*', MUST_EXIST);
3630 $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion), '*', MUST_EXIST);
3631 $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
3632 $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
3633 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id , false, MUST_EXIST);
3634 $context = context_module::instance($cm->id);
3636 // Make sure the context provided is the context of the forum
3637 if ($context->id != $params['context']->id) {
3638 throw new rating_exception('invalidcontext');
3641 if ($forum->scale != $params['scaleid']) {
3642 //the scale being submitted doesnt match the one in the database
3643 throw new rating_exception('invalidscaleid');
3646 // check the item we're rating was created in the assessable time window
3647 if (!empty($forum->assesstimestart) && !empty($forum->assesstimefinish)) {
3648 if ($post->created < $forum->assesstimestart || $post->created > $forum->assesstimefinish) {
3649 throw new rating_exception('notavailable');
3653 //check that the submitted rating is valid for the scale
3655 // lower limit
3656 if ($params['rating'] < 0 && $params['rating'] != RATING_UNSET_RATING) {
3657 throw new rating_exception('invalidnum');
3660 // upper limit
3661 if ($forum->scale < 0) {
3662 //its a custom scale
3663 $scalerecord = $DB->get_record('scale', array('id' => -$forum->scale));
3664 if ($scalerecord) {
3665 $scalearray = explode(',', $scalerecord->scale);
3666 if ($params['rating'] > count($scalearray)) {
3667 throw new rating_exception('invalidnum');
3669 } else {
3670 throw new rating_exception('invalidscaleid');
3672 } else if ($params['rating'] > $forum->scale) {
3673 //if its numeric and submitted rating is above maximum
3674 throw new rating_exception('invalidnum');
3677 // Make sure groups allow this user to see the item they're rating
3678 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used
3679 if (!groups_group_exists($discussion->groupid)) { // Can't find group
3680 throw new rating_exception('cannotfindgroup');//something is wrong
3683 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
3684 // do not allow rating of posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
3685 throw new rating_exception('notmemberofgroup');
3689 // perform some final capability checks
3690 if (!forum_user_can_see_post($forum, $discussion, $post, $USER, $cm)) {
3691 throw new rating_exception('nopermissiontorate');
3694 return true;
3699 * This function prints the overview of a discussion in the forum listing.
3700 * It needs some discussion information and some post information, these
3701 * happen to be combined for efficiency in the $post parameter by the function
3702 * that calls this one: forum_print_latest_discussions()
3704 * @global object
3705 * @global object
3706 * @param object $post The post object (passed by reference for speed).
3707 * @param object $forum The forum object.
3708 * @param int $group Current group.
3709 * @param string $datestring Format to use for the dates.
3710 * @param boolean $cantrack Is tracking enabled for this forum.
3711 * @param boolean $forumtracked Is the user tracking this forum.
3712 * @param boolean $canviewparticipants True if user has the viewparticipants permission for this course
3714 function forum_print_discussion_header(&$post, $forum, $group=-1, $datestring="",
3715 $cantrack=true, $forumtracked=true, $canviewparticipants=true, $modcontext=NULL) {
3717 global $USER, $CFG, $OUTPUT;
3719 static $rowcount;
3720 static $strmarkalldread;
3722 if (empty($modcontext)) {
3723 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
3724 print_error('invalidcoursemodule');
3726 $modcontext = context_module::instance($cm->id);
3729 if (!isset($rowcount)) {
3730 $rowcount = 0;
3731 $strmarkalldread = get_string('markalldread', 'forum');
3732 } else {
3733 $rowcount = ($rowcount + 1) % 2;
3736 $post->subject = format_string($post->subject,true);
3738 echo "\n\n";
3739 echo '<tr class="discussion r'.$rowcount.'">';
3741 // Topic
3742 echo '<td class="topic starter">';
3743 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">'.$post->subject.'</a>';
3744 echo "</td>\n";
3746 // Picture
3747 $postuser = new stdClass();
3748 $postuser->id = $post->userid;
3749 foreach (get_all_user_name_fields() as $addname) {
3750 $postuser->$addname = $post->$addname;
3752 $postuser->imagealt = $post->imagealt;
3753 $postuser->picture = $post->picture;
3754 $postuser->email = $post->email;
3756 echo '<td class="picture">';
3757 echo $OUTPUT->user_picture($postuser, array('courseid'=>$forum->course));
3758 echo "</td>\n";
3760 // User name
3761 $fullname = fullname($postuser, has_capability('moodle/site:viewfullnames', $modcontext));
3762 echo '<td class="author">';
3763 echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->userid.'&amp;course='.$forum->course.'">'.$fullname.'</a>';
3764 echo "</td>\n";
3766 // Group picture
3767 if ($group !== -1) { // Groups are active - group is a group data object or NULL
3768 echo '<td class="picture group">';
3769 if (!empty($group->picture) and empty($group->hidepicture)) {
3770 print_group_picture($group, $forum->course, false, false, true);
3771 } else if (isset($group->id)) {
3772 if($canviewparticipants) {
3773 echo '<a href="'.$CFG->wwwroot.'/user/index.php?id='.$forum->course.'&amp;group='.$group->id.'">'.$group->name.'</a>';
3774 } else {
3775 echo $group->name;
3778 echo "</td>\n";
3781 if (has_capability('mod/forum:viewdiscussion', $modcontext)) { // Show the column with replies
3782 echo '<td class="replies">';
3783 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
3784 echo $post->replies.'</a>';
3785 echo "</td>\n";
3787 if ($cantrack) {
3788 echo '<td class="replies">';
3789 if ($forumtracked) {
3790 if ($post->unread > 0) {
3791 echo '<span class="unread">';
3792 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'#unread">';
3793 echo $post->unread;
3794 echo '</a>';
3795 echo '<a title="'.$strmarkalldread.'" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
3796 $forum->id.'&amp;d='.$post->discussion.'&amp;mark=read&amp;returnpage=view.php">' .
3797 '<img src="'.$OUTPUT->pix_url('t/markasread') . '" class="iconsmall" alt="'.$strmarkalldread.'" /></a>';
3798 echo '</span>';
3799 } else {
3800 echo '<span class="read">';
3801 echo $post->unread;
3802 echo '</span>';
3804 } else {
3805 echo '<span class="read">';
3806 echo '-';
3807 echo '</span>';
3809 echo "</td>\n";
3813 echo '<td class="lastpost">';
3814 $usedate = (empty($post->timemodified)) ? $post->modified : $post->timemodified; // Just in case
3815 $parenturl = (empty($post->lastpostid)) ? '' : '&amp;parent='.$post->lastpostid;
3816 $usermodified = new stdClass();
3817 $usermodified->id = $post->usermodified;
3818 foreach (get_all_user_name_fields() as $addname) {
3819 $temp = 'um' . $addname;
3820 $usermodified->$addname = $post->$temp;
3822 echo '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$post->usermodified.'&amp;course='.$forum->course.'">'.
3823 fullname($usermodified).'</a><br />';
3824 echo '<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.$parenturl.'">'.
3825 userdate($usedate, $datestring).'</a>';
3826 echo "</td>\n";
3828 echo "</tr>\n\n";
3834 * Given a post object that we already know has a long message
3835 * this function truncates the message nicely to the first
3836 * sane place between $CFG->forum_longpost and $CFG->forum_shortpost
3838 * @global object
3839 * @param string $message
3840 * @return string
3842 function forum_shorten_post($message) {
3844 global $CFG;
3846 $i = 0;
3847 $tag = false;
3848 $length = strlen($message);
3849 $count = 0;
3850 $stopzone = false;
3851 $truncate = 0;
3853 for ($i=0; $i<$length; $i++) {
3854 $char = $message[$i];
3856 switch ($char) {
3857 case "<":
3858 $tag = true;
3859 break;
3860 case ">":
3861 $tag = false;
3862 break;
3863 default:
3864 if (!$tag) {
3865 if ($stopzone) {
3866 if ($char == ".") {
3867 $truncate = $i+1;
3868 break 2;
3871 $count++;
3873 break;
3875 if (!$stopzone) {
3876 if ($count > $CFG->forum_shortpost) {
3877 $stopzone = true;
3882 if (!$truncate) {
3883 $truncate = $i;
3886 return substr($message, 0, $truncate);
3890 * Print the drop down that allows the user to select how they want to have
3891 * the discussion displayed.
3893 * @param int $id forum id if $forumtype is 'single',
3894 * discussion id for any other forum type
3895 * @param mixed $mode forum layout mode
3896 * @param string $forumtype optional
3898 function forum_print_mode_form($id, $mode, $forumtype='') {
3899 global $OUTPUT;
3900 if ($forumtype == 'single') {
3901 $select = new single_select(new moodle_url("/mod/forum/view.php", array('f'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode");
3902 $select->set_label(get_string('displaymode', 'forum'), array('class' => 'accesshide'));
3903 $select->class = "forummode";
3904 } else {
3905 $select = new single_select(new moodle_url("/mod/forum/discuss.php", array('d'=>$id)), 'mode', forum_get_layout_modes(), $mode, null, "mode");
3906 $select->set_label(get_string('displaymode', 'forum'), array('class' => 'accesshide'));
3908 echo $OUTPUT->render($select);
3912 * @global object
3913 * @param object $course
3914 * @param string $search
3915 * @return string
3917 function forum_search_form($course, $search='') {
3918 global $CFG, $OUTPUT;
3920 $output = '<div class="forumsearch">';
3921 $output .= '<form action="'.$CFG->wwwroot.'/mod/forum/search.php" style="display:inline">';
3922 $output .= '<fieldset class="invisiblefieldset">';
3923 $output .= $OUTPUT->help_icon('search');
3924 $output .= '<label class="accesshide" for="search" >'.get_string('search', 'forum').'</label>';
3925 $output .= '<input id="search" name="search" type="text" size="18" value="'.s($search, true).'" alt="search" />';
3926 $output .= '<label class="accesshide" for="searchforums" >'.get_string('searchforums', 'forum').'</label>';
3927 $output .= '<input id="searchforums" value="'.get_string('searchforums', 'forum').'" type="submit" />';
3928 $output .= '<input name="id" type="hidden" value="'.$course->id.'" />';
3929 $output .= '</fieldset>';
3930 $output .= '</form>';
3931 $output .= '</div>';
3933 return $output;
3938 * @global object
3939 * @global object
3941 function forum_set_return() {
3942 global $CFG, $SESSION;
3944 if (! isset($SESSION->fromdiscussion)) {
3945 if (!empty($_SERVER['HTTP_REFERER'])) {
3946 $referer = $_SERVER['HTTP_REFERER'];
3947 } else {
3948 $referer = "";
3950 // If the referer is NOT a login screen then save it.
3951 if (! strncasecmp("$CFG->wwwroot/login", $referer, 300)) {
3952 $SESSION->fromdiscussion = $_SERVER["HTTP_REFERER"];
3959 * @global object
3960 * @param string $default
3961 * @return string
3963 function forum_go_back_to($default) {
3964 global $SESSION;
3966 if (!empty($SESSION->fromdiscussion)) {
3967 $returnto = $SESSION->fromdiscussion;
3968 unset($SESSION->fromdiscussion);
3969 return $returnto;
3970 } else {
3971 return $default;
3976 * Given a discussion object that is being moved to $forumto,
3977 * this function checks all posts in that discussion
3978 * for attachments, and if any are found, these are
3979 * moved to the new forum directory.
3981 * @global object
3982 * @param object $discussion
3983 * @param int $forumfrom source forum id
3984 * @param int $forumto target forum id
3985 * @return bool success
3987 function forum_move_attachments($discussion, $forumfrom, $forumto) {
3988 global $DB;
3990 $fs = get_file_storage();
3992 $newcm = get_coursemodule_from_instance('forum', $forumto);
3993 $oldcm = get_coursemodule_from_instance('forum', $forumfrom);
3995 $newcontext = context_module::instance($newcm->id);
3996 $oldcontext = context_module::instance($oldcm->id);
3998 // loop through all posts, better not use attachment flag ;-)
3999 if ($posts = $DB->get_records('forum_posts', array('discussion'=>$discussion->id), '', 'id, attachment')) {
4000 foreach ($posts as $post) {
4001 $fs->move_area_files_to_new_context($oldcontext->id,
4002 $newcontext->id, 'mod_forum', 'post', $post->id);
4003 $attachmentsmoved = $fs->move_area_files_to_new_context($oldcontext->id,
4004 $newcontext->id, 'mod_forum', 'attachment', $post->id);
4005 if ($attachmentsmoved > 0 && $post->attachment != '1') {
4006 // Weird - let's fix it
4007 $post->attachment = '1';
4008 $DB->update_record('forum_posts', $post);
4009 } else if ($attachmentsmoved == 0 && $post->attachment != '') {
4010 // Weird - let's fix it
4011 $post->attachment = '';
4012 $DB->update_record('forum_posts', $post);
4017 return true;
4021 * Returns attachments as formated text/html optionally with separate images
4023 * @global object
4024 * @global object
4025 * @global object
4026 * @param object $post
4027 * @param object $cm
4028 * @param string $type html/text/separateimages
4029 * @return mixed string or array of (html text withouth images and image HTML)
4031 function forum_print_attachments($post, $cm, $type) {
4032 global $CFG, $DB, $USER, $OUTPUT;
4034 if (empty($post->attachment)) {
4035 return $type !== 'separateimages' ? '' : array('', '');
4038 if (!in_array($type, array('separateimages', 'html', 'text'))) {
4039 return $type !== 'separateimages' ? '' : array('', '');
4042 if (!$context = context_module::instance($cm->id)) {
4043 return $type !== 'separateimages' ? '' : array('', '');
4045 $strattachment = get_string('attachment', 'forum');
4047 $fs = get_file_storage();
4049 $imagereturn = '';
4050 $output = '';
4052 $canexport = !empty($CFG->enableportfolios) && (has_capability('mod/forum:exportpost', $context) || ($post->userid == $USER->id && has_capability('mod/forum:exportownpost', $context)));
4054 if ($canexport) {
4055 require_once($CFG->libdir.'/portfoliolib.php');
4058 $files = $fs->get_area_files($context->id, 'mod_forum', 'attachment', $post->id, "timemodified", false);
4059 if ($files) {
4060 if ($canexport) {
4061 $button = new portfolio_add_button();
4063 foreach ($files as $file) {
4064 $filename = $file->get_filename();
4065 $mimetype = $file->get_mimetype();
4066 $iconimage = $OUTPUT->pix_icon(file_file_icon($file), get_mimetype_description($file), 'moodle', array('class' => 'icon'));
4067 $path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$context->id.'/mod_forum/attachment/'.$post->id.'/'.$filename);
4069 if ($type == 'html') {
4070 $output .= "<a href=\"$path\">$iconimage</a> ";
4071 $output .= "<a href=\"$path\">".s($filename)."</a>";
4072 if ($canexport) {
4073 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), 'mod_forum');
4074 $button->set_format_by_file($file);
4075 $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
4077 $output .= "<br />";
4079 } else if ($type == 'text') {
4080 $output .= "$strattachment ".s($filename).":\n$path\n";
4082 } else { //'returnimages'
4083 if (in_array($mimetype, array('image/gif', 'image/jpeg', 'image/png'))) {
4084 // Image attachments don't get printed as links
4085 $imagereturn .= "<br /><img src=\"$path\" alt=\"\" />";
4086 if ($canexport) {
4087 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), 'mod_forum');
4088 $button->set_format_by_file($file);
4089 $imagereturn .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
4091 } else {
4092 $output .= "<a href=\"$path\">$iconimage</a> ";
4093 $output .= format_text("<a href=\"$path\">".s($filename)."</a>", FORMAT_HTML, array('context'=>$context));
4094 if ($canexport) {
4095 $button->set_callback_options('forum_portfolio_caller', array('postid' => $post->id, 'attachment' => $file->get_id()), 'mod_forum');
4096 $button->set_format_by_file($file);
4097 $output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
4099 $output .= '<br />';
4103 if (!empty($CFG->enableplagiarism)) {
4104 require_once($CFG->libdir.'/plagiarismlib.php');
4105 $output .= plagiarism_get_links(array('userid' => $post->userid,
4106 'file' => $file,
4107 'cmid' => $cm->id,
4108 'course' => $post->course,
4109 'forum' => $post->forum));
4110 $output .= '<br />';
4115 if ($type !== 'separateimages') {
4116 return $output;
4118 } else {
4119 return array($output, $imagereturn);
4123 ////////////////////////////////////////////////////////////////////////////////
4124 // File API //
4125 ////////////////////////////////////////////////////////////////////////////////
4128 * Lists all browsable file areas
4130 * @package mod_forum
4131 * @category files
4132 * @param stdClass $course course object
4133 * @param stdClass $cm course module object
4134 * @param stdClass $context context object
4135 * @return array
4137 function forum_get_file_areas($course, $cm, $context) {
4138 return array(
4139 'attachment' => get_string('areaattachment', 'mod_forum'),
4140 'post' => get_string('areapost', 'mod_forum'),
4145 * File browsing support for forum module.
4147 * @package mod_forum
4148 * @category files
4149 * @param stdClass $browser file browser object
4150 * @param stdClass $areas file areas
4151 * @param stdClass $course course object
4152 * @param stdClass $cm course module
4153 * @param stdClass $context context module
4154 * @param string $filearea file area
4155 * @param int $itemid item ID
4156 * @param string $filepath file path
4157 * @param string $filename file name
4158 * @return file_info instance or null if not found
4160 function forum_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
4161 global $CFG, $DB, $USER;
4163 if ($context->contextlevel != CONTEXT_MODULE) {
4164 return null;
4167 // filearea must contain a real area
4168 if (!isset($areas[$filearea])) {
4169 return null;
4172 // Note that forum_user_can_see_post() additionally allows access for parent roles
4173 // and it explicitly checks qanda forum type, too. One day, when we stop requiring
4174 // course:managefiles, we will need to extend this.
4175 if (!has_capability('mod/forum:viewdiscussion', $context)) {
4176 return null;
4179 if (is_null($itemid)) {
4180 require_once($CFG->dirroot.'/mod/forum/locallib.php');
4181 return new forum_file_info_container($browser, $course, $cm, $context, $areas, $filearea);
4184 static $cached = array();
4185 // $cached will store last retrieved post, discussion and forum. To make sure that the cache
4186 // is cleared between unit tests we check if this is the same session
4187 if (!isset($cached['sesskey']) || $cached['sesskey'] != sesskey()) {
4188 $cached = array('sesskey' => sesskey());
4191 if (isset($cached['post']) && $cached['post']->id == $itemid) {
4192 $post = $cached['post'];
4193 } else if ($post = $DB->get_record('forum_posts', array('id' => $itemid))) {
4194 $cached['post'] = $post;
4195 } else {
4196 return null;
4199 if (isset($cached['discussion']) && $cached['discussion']->id == $post->discussion) {
4200 $discussion = $cached['discussion'];
4201 } else if ($discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion))) {
4202 $cached['discussion'] = $discussion;
4203 } else {
4204 return null;
4207 if (isset($cached['forum']) && $cached['forum']->id == $cm->instance) {
4208 $forum = $cached['forum'];
4209 } else if ($forum = $DB->get_record('forum', array('id' => $cm->instance))) {
4210 $cached['forum'] = $forum;
4211 } else {
4212 return null;
4215 $fs = get_file_storage();
4216 $filepath = is_null($filepath) ? '/' : $filepath;
4217 $filename = is_null($filename) ? '.' : $filename;
4218 if (!($storedfile = $fs->get_file($context->id, 'mod_forum', $filearea, $itemid, $filepath, $filename))) {
4219 return null;
4222 // Checks to see if the user can manage files or is the owner.
4223 // TODO MDL-33805 - Do not use userid here and move the capability check above.
4224 if (!has_capability('moodle/course:managefiles', $context) && $storedfile->get_userid() != $USER->id) {
4225 return null;
4227 // Make sure groups allow this user to see this file
4228 if ($discussion->groupid > 0 && !has_capability('moodle/site:accessallgroups', $context)) {
4229 $groupmode = groups_get_activity_groupmode($cm, $course);
4230 if ($groupmode == SEPARATEGROUPS && !groups_is_member($discussion->groupid)) {
4231 return null;
4235 // Make sure we're allowed to see it...
4236 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
4237 return null;
4240 $urlbase = $CFG->wwwroot.'/pluginfile.php';
4241 return new file_info_stored($browser, $context, $storedfile, $urlbase, $itemid, true, true, false, false);
4245 * Serves the forum attachments. Implements needed access control ;-)
4247 * @package mod_forum
4248 * @category files
4249 * @param stdClass $course course object
4250 * @param stdClass $cm course module object
4251 * @param stdClass $context context object
4252 * @param string $filearea file area
4253 * @param array $args extra arguments
4254 * @param bool $forcedownload whether or not force download
4255 * @param array $options additional options affecting the file serving
4256 * @return bool false if file not found, does not return if found - justsend the file
4258 function forum_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
4259 global $CFG, $DB;
4261 if ($context->contextlevel != CONTEXT_MODULE) {
4262 return false;
4265 require_course_login($course, true, $cm);
4267 $areas = forum_get_file_areas($course, $cm, $context);
4269 // filearea must contain a real area
4270 if (!isset($areas[$filearea])) {
4271 return false;
4274 $postid = (int)array_shift($args);
4276 if (!$post = $DB->get_record('forum_posts', array('id'=>$postid))) {
4277 return false;
4280 if (!$discussion = $DB->get_record('forum_discussions', array('id'=>$post->discussion))) {
4281 return false;
4284 if (!$forum = $DB->get_record('forum', array('id'=>$cm->instance))) {
4285 return false;
4288 $fs = get_file_storage();
4289 $relativepath = implode('/', $args);
4290 $fullpath = "/$context->id/mod_forum/$filearea/$postid/$relativepath";
4291 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
4292 return false;
4295 // Make sure groups allow this user to see this file
4296 if ($discussion->groupid > 0) {
4297 $groupmode = groups_get_activity_groupmode($cm, $course);
4298 if ($groupmode == SEPARATEGROUPS) {
4299 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
4300 return false;
4305 // Make sure we're allowed to see it...
4306 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
4307 return false;
4310 // finally send the file
4311 send_stored_file($file, 0, 0, true, $options); // download MUST be forced - security!
4315 * If successful, this function returns the name of the file
4317 * @global object
4318 * @param object $post is a full post record, including course and forum
4319 * @param object $forum
4320 * @param object $cm
4321 * @param mixed $mform
4322 * @param string $unused
4323 * @return bool
4325 function forum_add_attachment($post, $forum, $cm, $mform=null, $unused=null) {
4326 global $DB;
4328 if (empty($mform)) {
4329 return false;
4332 if (empty($post->attachments)) {
4333 return true; // Nothing to do
4336 $context = context_module::instance($cm->id);
4338 $info = file_get_draft_area_info($post->attachments);
4339 $present = ($info['filecount']>0) ? '1' : '';
4340 file_save_draft_area_files($post->attachments, $context->id, 'mod_forum', 'attachment', $post->id,
4341 mod_forum_post_form::attachment_options($forum));
4343 $DB->set_field('forum_posts', 'attachment', $present, array('id'=>$post->id));
4345 return true;
4349 * Add a new post in an existing discussion.
4351 * @global object
4352 * @global object
4353 * @global object
4354 * @param object $post
4355 * @param mixed $mform
4356 * @param string $message
4357 * @return int
4359 function forum_add_new_post($post, $mform, &$message) {
4360 global $USER, $CFG, $DB;
4362 $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion));
4363 $forum = $DB->get_record('forum', array('id' => $discussion->forum));
4364 $cm = get_coursemodule_from_instance('forum', $forum->id);
4365 $context = context_module::instance($cm->id);
4367 $post->created = $post->modified = time();
4368 $post->mailed = FORUM_MAILED_PENDING;
4369 $post->userid = $USER->id;
4370 $post->attachment = "";
4372 $post->id = $DB->insert_record("forum_posts", $post);
4373 $post->message = file_save_draft_area_files($post->itemid, $context->id, 'mod_forum', 'post', $post->id,
4374 mod_forum_post_form::editor_options(), $post->message);
4375 $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
4376 forum_add_attachment($post, $forum, $cm, $mform, $message);
4378 // Update discussion modified date
4379 $DB->set_field("forum_discussions", "timemodified", $post->modified, array("id" => $post->discussion));
4380 $DB->set_field("forum_discussions", "usermodified", $post->userid, array("id" => $post->discussion));
4382 if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
4383 forum_tp_mark_post_read($post->userid, $post, $post->forum);
4386 // Let Moodle know that assessable content is uploaded (eg for plagiarism detection)
4387 forum_trigger_content_uploaded_event($post, $cm, 'forum_add_new_post');
4389 return $post->id;
4393 * Update a post
4395 * @global object
4396 * @global object
4397 * @global object
4398 * @param object $post
4399 * @param mixed $mform
4400 * @param string $message
4401 * @return bool
4403 function forum_update_post($post, $mform, &$message) {
4404 global $USER, $CFG, $DB;
4406 $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion));
4407 $forum = $DB->get_record('forum', array('id' => $discussion->forum));
4408 $cm = get_coursemodule_from_instance('forum', $forum->id);
4409 $context = context_module::instance($cm->id);
4411 $post->modified = time();
4413 $DB->update_record('forum_posts', $post);
4415 $discussion->timemodified = $post->modified; // last modified tracking
4416 $discussion->usermodified = $post->userid; // last modified tracking
4418 if (!$post->parent) { // Post is a discussion starter - update discussion title and times too
4419 $discussion->name = $post->subject;
4420 $discussion->timestart = $post->timestart;
4421 $discussion->timeend = $post->timeend;
4423 $post->message = file_save_draft_area_files($post->itemid, $context->id, 'mod_forum', 'post', $post->id,
4424 mod_forum_post_form::editor_options(), $post->message);
4425 $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
4427 $DB->update_record('forum_discussions', $discussion);
4429 forum_add_attachment($post, $forum, $cm, $mform, $message);
4431 if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
4432 forum_tp_mark_post_read($post->userid, $post, $post->forum);
4435 // Let Moodle know that assessable content is uploaded (eg for plagiarism detection)
4436 forum_trigger_content_uploaded_event($post, $cm, 'forum_update_post');
4438 return true;
4442 * Given an object containing all the necessary data,
4443 * create a new discussion and return the id
4445 * @param object $post
4446 * @param mixed $mform
4447 * @param string $unused
4448 * @param int $userid
4449 * @return object
4451 function forum_add_discussion($discussion, $mform=null, $unused=null, $userid=null) {
4452 global $USER, $CFG, $DB;
4454 $timenow = time();
4456 if (is_null($userid)) {
4457 $userid = $USER->id;
4460 // The first post is stored as a real post, and linked
4461 // to from the discuss entry.
4463 $forum = $DB->get_record('forum', array('id'=>$discussion->forum));
4464 $cm = get_coursemodule_from_instance('forum', $forum->id);
4466 $post = new stdClass();
4467 $post->discussion = 0;
4468 $post->parent = 0;
4469 $post->userid = $userid;
4470 $post->created = $timenow;
4471 $post->modified = $timenow;
4472 $post->mailed = FORUM_MAILED_PENDING;
4473 $post->subject = $discussion->name;
4474 $post->message = $discussion->message;
4475 $post->messageformat = $discussion->messageformat;
4476 $post->messagetrust = $discussion->messagetrust;
4477 $post->attachments = isset($discussion->attachments) ? $discussion->attachments : null;
4478 $post->forum = $forum->id; // speedup
4479 $post->course = $forum->course; // speedup
4480 $post->mailnow = $discussion->mailnow;
4482 $post->id = $DB->insert_record("forum_posts", $post);
4484 // TODO: Fix the calling code so that there always is a $cm when this function is called
4485 if (!empty($cm->id) && !empty($discussion->itemid)) { // In "single simple discussions" this may not exist yet
4486 $context = context_module::instance($cm->id);
4487 $text = file_save_draft_area_files($discussion->itemid, $context->id, 'mod_forum', 'post', $post->id,
4488 mod_forum_post_form::editor_options(), $post->message);
4489 $DB->set_field('forum_posts', 'message', $text, array('id'=>$post->id));
4492 // Now do the main entry for the discussion, linking to this first post
4494 $discussion->firstpost = $post->id;
4495 $discussion->timemodified = $timenow;
4496 $discussion->usermodified = $post->userid;
4497 $discussion->userid = $userid;
4499 $post->discussion = $DB->insert_record("forum_discussions", $discussion);
4501 // Finally, set the pointer on the post.
4502 $DB->set_field("forum_posts", "discussion", $post->discussion, array("id"=>$post->id));
4504 if (!empty($cm->id)) {
4505 forum_add_attachment($post, $forum, $cm, $mform, $unused);
4508 if (forum_tp_can_track_forums($forum) && forum_tp_is_tracked($forum)) {
4509 forum_tp_mark_post_read($post->userid, $post, $post->forum);
4512 // Let Moodle know that assessable content is uploaded (eg for plagiarism detection)
4513 if (!empty($cm->id)) {
4514 forum_trigger_content_uploaded_event($post, $cm, 'forum_add_discussion');
4517 return $post->discussion;
4522 * Deletes a discussion and handles all associated cleanup.
4524 * @global object
4525 * @param object $discussion Discussion to delete
4526 * @param bool $fulldelete True when deleting entire forum
4527 * @param object $course Course
4528 * @param object $cm Course-module
4529 * @param object $forum Forum
4530 * @return bool
4532 function forum_delete_discussion($discussion, $fulldelete, $course, $cm, $forum) {
4533 global $DB, $CFG;
4534 require_once($CFG->libdir.'/completionlib.php');
4536 $result = true;
4538 if ($posts = $DB->get_records("forum_posts", array("discussion" => $discussion->id))) {
4539 foreach ($posts as $post) {
4540 $post->course = $discussion->course;
4541 $post->forum = $discussion->forum;
4542 if (!forum_delete_post($post, 'ignore', $course, $cm, $forum, $fulldelete)) {
4543 $result = false;
4548 forum_tp_delete_read_records(-1, -1, $discussion->id);
4550 if (!$DB->delete_records("forum_discussions", array("id"=>$discussion->id))) {
4551 $result = false;
4554 // Update completion state if we are tracking completion based on number of posts
4555 // But don't bother when deleting whole thing
4556 if (!$fulldelete) {
4557 $completion = new completion_info($course);
4558 if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC &&
4559 ($forum->completiondiscussions || $forum->completionreplies || $forum->completionposts)) {
4560 $completion->update_state($cm, COMPLETION_INCOMPLETE, $discussion->userid);
4564 return $result;
4569 * Deletes a single forum post.
4571 * @global object
4572 * @param object $post Forum post object
4573 * @param mixed $children Whether to delete children. If false, returns false
4574 * if there are any children (without deleting the post). If true,
4575 * recursively deletes all children. If set to special value 'ignore', deletes
4576 * post regardless of children (this is for use only when deleting all posts
4577 * in a disussion).
4578 * @param object $course Course
4579 * @param object $cm Course-module
4580 * @param object $forum Forum
4581 * @param bool $skipcompletion True to skip updating completion state if it
4582 * would otherwise be updated, i.e. when deleting entire forum anyway.
4583 * @return bool
4585 function forum_delete_post($post, $children, $course, $cm, $forum, $skipcompletion=false) {
4586 global $DB, $CFG;
4587 require_once($CFG->libdir.'/completionlib.php');
4589 $context = context_module::instance($cm->id);
4591 if ($children !== 'ignore' && ($childposts = $DB->get_records('forum_posts', array('parent'=>$post->id)))) {
4592 if ($children) {
4593 foreach ($childposts as $childpost) {
4594 forum_delete_post($childpost, true, $course, $cm, $forum, $skipcompletion);
4596 } else {
4597 return false;
4601 //delete ratings
4602 require_once($CFG->dirroot.'/rating/lib.php');
4603 $delopt = new stdClass;
4604 $delopt->contextid = $context->id;
4605 $delopt->component = 'mod_forum';
4606 $delopt->ratingarea = 'post';
4607 $delopt->itemid = $post->id;
4608 $rm = new rating_manager();
4609 $rm->delete_ratings($delopt);
4611 //delete attachments
4612 $fs = get_file_storage();
4613 $fs->delete_area_files($context->id, 'mod_forum', 'attachment', $post->id);
4614 $fs->delete_area_files($context->id, 'mod_forum', 'post', $post->id);
4616 if ($DB->delete_records("forum_posts", array("id" => $post->id))) {
4618 forum_tp_delete_read_records(-1, $post->id);
4620 // Just in case we are deleting the last post
4621 forum_discussion_update_last_post($post->discussion);
4623 // Update completion state if we are tracking completion based on number of posts
4624 // But don't bother when deleting whole thing
4626 if (!$skipcompletion) {
4627 $completion = new completion_info($course);
4628 if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC &&
4629 ($forum->completiondiscussions || $forum->completionreplies || $forum->completionposts)) {
4630 $completion->update_state($cm, COMPLETION_INCOMPLETE, $post->userid);
4634 return true;
4636 return false;
4640 * Sends post content to plagiarism plugin
4641 * @param object $post Forum post object
4642 * @param object $cm Course-module
4643 * @param string $name
4644 * @return bool
4646 function forum_trigger_content_uploaded_event($post, $cm, $name) {
4647 $context = context_module::instance($cm->id);
4648 $fs = get_file_storage();
4649 $files = $fs->get_area_files($context->id, 'mod_forum', 'attachment', $post->id, "timemodified", false);
4650 $eventdata = new stdClass();
4651 $eventdata->modulename = 'forum';
4652 $eventdata->name = $name;
4653 $eventdata->cmid = $cm->id;
4654 $eventdata->itemid = $post->id;
4655 $eventdata->courseid = $post->course;
4656 $eventdata->userid = $post->userid;
4657 $eventdata->content = $post->message;
4658 if ($files) {
4659 $eventdata->pathnamehashes = array_keys($files);
4661 events_trigger('assessable_content_uploaded', $eventdata);
4663 return true;
4667 * @global object
4668 * @param object $post
4669 * @param bool $children
4670 * @return int
4672 function forum_count_replies($post, $children=true) {
4673 global $DB;
4674 $count = 0;
4676 if ($children) {
4677 if ($childposts = $DB->get_records('forum_posts', array('parent' => $post->id))) {
4678 foreach ($childposts as $childpost) {
4679 $count ++; // For this child
4680 $count += forum_count_replies($childpost, true);
4683 } else {
4684 $count += $DB->count_records('forum_posts', array('parent' => $post->id));
4687 return $count;
4692 * @global object
4693 * @param int $forumid
4694 * @param mixed $value
4695 * @return bool
4697 function forum_forcesubscribe($forumid, $value=1) {
4698 global $DB;
4699 return $DB->set_field("forum", "forcesubscribe", $value, array("id" => $forumid));
4703 * @global object
4704 * @param object $forum
4705 * @return bool
4707 function forum_is_forcesubscribed($forum) {
4708 global $DB;
4709 if (isset($forum->forcesubscribe)) { // then we use that
4710 return ($forum->forcesubscribe == FORUM_FORCESUBSCRIBE);
4711 } else { // Check the database
4712 return ($DB->get_field('forum', 'forcesubscribe', array('id' => $forum)) == FORUM_FORCESUBSCRIBE);
4716 function forum_get_forcesubscribed($forum) {
4717 global $DB;
4718 if (isset($forum->forcesubscribe)) { // then we use that
4719 return $forum->forcesubscribe;
4720 } else { // Check the database
4721 return $DB->get_field('forum', 'forcesubscribe', array('id' => $forum));
4726 * @global object
4727 * @param int $userid
4728 * @param object $forum
4729 * @return bool
4731 function forum_is_subscribed($userid, $forum) {
4732 global $DB;
4733 if (is_numeric($forum)) {
4734 $forum = $DB->get_record('forum', array('id' => $forum));
4736 // If forum is force subscribed and has allowforcesubscribe, then user is subscribed.
4737 $cm = get_coursemodule_from_instance('forum', $forum->id);
4738 if (forum_is_forcesubscribed($forum) && $cm &&
4739 has_capability('mod/forum:allowforcesubscribe', context_module::instance($cm->id), $userid)) {
4740 return true;
4742 return $DB->record_exists("forum_subscriptions", array("userid" => $userid, "forum" => $forum->id));
4745 function forum_get_subscribed_forums($course) {
4746 global $USER, $CFG, $DB;
4747 $sql = "SELECT f.id
4748 FROM {forum} f
4749 LEFT JOIN {forum_subscriptions} fs ON (fs.forum = f.id AND fs.userid = ?)
4750 WHERE f.course = ?
4751 AND f.forcesubscribe <> ".FORUM_DISALLOWSUBSCRIBE."
4752 AND (f.forcesubscribe = ".FORUM_FORCESUBSCRIBE." OR fs.id IS NOT NULL)";
4753 if ($subscribed = $DB->get_records_sql($sql, array($USER->id, $course->id))) {
4754 foreach ($subscribed as $s) {
4755 $subscribed[$s->id] = $s->id;
4757 return $subscribed;
4758 } else {
4759 return array();
4764 * Returns an array of forums that the current user is subscribed to and is allowed to unsubscribe from
4766 * @return array An array of unsubscribable forums
4768 function forum_get_optional_subscribed_forums() {
4769 global $USER, $DB;
4771 // Get courses that $USER is enrolled in and can see
4772 $courses = enrol_get_my_courses();
4773 if (empty($courses)) {
4774 return array();
4777 $courseids = array();
4778 foreach($courses as $course) {
4779 $courseids[] = $course->id;
4781 list($coursesql, $courseparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED, 'c');
4783 // get all forums from the user's courses that they are subscribed to and which are not set to forced
4784 $sql = "SELECT f.id, cm.id as cm, cm.visible
4785 FROM {forum} f
4786 JOIN {course_modules} cm ON cm.instance = f.id
4787 JOIN {modules} m ON m.name = :modulename AND m.id = cm.module
4788 LEFT JOIN {forum_subscriptions} fs ON (fs.forum = f.id AND fs.userid = :userid)
4789 WHERE f.forcesubscribe <> :forcesubscribe AND fs.id IS NOT NULL
4790 AND cm.course $coursesql";
4791 $params = array_merge($courseparams, array('modulename'=>'forum', 'userid'=>$USER->id, 'forcesubscribe'=>FORUM_FORCESUBSCRIBE));
4792 if (!$forums = $DB->get_records_sql($sql, $params)) {
4793 return array();
4796 $unsubscribableforums = array(); // Array to return
4798 foreach($forums as $forum) {
4800 if (empty($forum->visible)) {
4801 // the forum is hidden
4802 $context = context_module::instance($forum->cm);
4803 if (!has_capability('moodle/course:viewhiddenactivities', $context)) {
4804 // the user can't see the hidden forum
4805 continue;
4809 // subscribe.php only requires 'mod/forum:managesubscriptions' when
4810 // unsubscribing a user other than yourself so we don't require it here either
4812 // A check for whether the forum has subscription set to forced is built into the SQL above
4814 $unsubscribableforums[] = $forum;
4817 return $unsubscribableforums;
4821 * Adds user to the subscriber list
4823 * @global object
4824 * @param int $userid
4825 * @param int $forumid
4827 function forum_subscribe($userid, $forumid) {
4828 global $DB;
4830 if ($DB->record_exists("forum_subscriptions", array("userid"=>$userid, "forum"=>$forumid))) {
4831 return true;
4834 $sub = new stdClass();
4835 $sub->userid = $userid;
4836 $sub->forum = $forumid;
4838 return $DB->insert_record("forum_subscriptions", $sub);
4842 * Removes user from the subscriber list
4844 * @global object
4845 * @param int $userid
4846 * @param int $forumid
4848 function forum_unsubscribe($userid, $forumid) {
4849 global $DB;
4850 return ($DB->delete_records('forum_digests', array('userid' => $userid, 'forum' => $forumid))
4851 && $DB->delete_records('forum_subscriptions', array('userid' => $userid, 'forum' => $forumid)));
4855 * Given a new post, subscribes or unsubscribes as appropriate.
4856 * Returns some text which describes what happened.
4858 * @global objec
4859 * @param object $post
4860 * @param object $forum
4862 function forum_post_subscription($post, $forum) {
4864 global $USER;
4866 $action = '';
4867 $subscribed = forum_is_subscribed($USER->id, $forum);
4869 if ($forum->forcesubscribe == FORUM_FORCESUBSCRIBE) { // database ignored
4870 return "";
4872 } elseif (($forum->forcesubscribe == FORUM_DISALLOWSUBSCRIBE)
4873 && !has_capability('moodle/course:manageactivities', context_course::instance($forum->course), $USER->id)) {
4874 if ($subscribed) {
4875 $action = 'unsubscribe'; // sanity check, following MDL-14558
4876 } else {
4877 return "";
4880 } else { // go with the user's choice
4881 if (isset($post->subscribe)) {
4882 // no change
4883 if ((!empty($post->subscribe) && $subscribed)
4884 || (empty($post->subscribe) && !$subscribed)) {
4885 return "";
4887 } elseif (!empty($post->subscribe) && !$subscribed) {
4888 $action = 'subscribe';
4890 } elseif (empty($post->subscribe) && $subscribed) {
4891 $action = 'unsubscribe';
4896 $info = new stdClass();
4897 $info->name = fullname($USER);
4898 $info->forum = format_string($forum->name);
4900 switch ($action) {
4901 case 'subscribe':
4902 forum_subscribe($USER->id, $post->forum);
4903 return "<p>".get_string("nowsubscribed", "forum", $info)."</p>";
4904 case 'unsubscribe':
4905 forum_unsubscribe($USER->id, $post->forum);
4906 return "<p>".get_string("nownotsubscribed", "forum", $info)."</p>";
4911 * Generate and return the subscribe or unsubscribe link for a forum.
4913 * @param object $forum the forum. Fields used are $forum->id and $forum->forcesubscribe.
4914 * @param object $context the context object for this forum.
4915 * @param array $messages text used for the link in its various states
4916 * (subscribed, unsubscribed, forcesubscribed or cantsubscribe).
4917 * Any strings not passed in are taken from the $defaultmessages array
4918 * at the top of the function.
4919 * @param bool $cantaccessagroup
4920 * @param bool $fakelink
4921 * @param bool $backtoindex
4922 * @param array $subscribed_forums
4923 * @return string
4925 function forum_get_subscribe_link($forum, $context, $messages = array(), $cantaccessagroup = false, $fakelink=true, $backtoindex=false, $subscribed_forums=null) {
4926 global $CFG, $USER, $PAGE, $OUTPUT;
4927 $defaultmessages = array(
4928 'subscribed' => get_string('unsubscribe', 'forum'),
4929 'unsubscribed' => get_string('subscribe', 'forum'),
4930 'cantaccessgroup' => get_string('no'),
4931 'forcesubscribed' => get_string('everyoneissubscribed', 'forum'),
4932 'cantsubscribe' => get_string('disallowsubscribe','forum')
4934 $messages = $messages + $defaultmessages;
4936 if (forum_is_forcesubscribed($forum)) {
4937 return $messages['forcesubscribed'];
4938 } else if ($forum->forcesubscribe == FORUM_DISALLOWSUBSCRIBE && !has_capability('mod/forum:managesubscriptions', $context)) {
4939 return $messages['cantsubscribe'];
4940 } else if ($cantaccessagroup) {
4941 return $messages['cantaccessgroup'];
4942 } else {
4943 if (!is_enrolled($context, $USER, '', true)) {
4944 return '';
4946 if (is_null($subscribed_forums)) {
4947 $subscribed = forum_is_subscribed($USER->id, $forum);
4948 } else {
4949 $subscribed = !empty($subscribed_forums[$forum->id]);
4951 if ($subscribed) {
4952 $linktext = $messages['subscribed'];
4953 $linktitle = get_string('subscribestop', 'forum');
4954 } else {
4955 $linktext = $messages['unsubscribed'];
4956 $linktitle = get_string('subscribestart', 'forum');
4959 $options = array();
4960 if ($backtoindex) {
4961 $backtoindexlink = '&amp;backtoindex=1';
4962 $options['backtoindex'] = 1;
4963 } else {
4964 $backtoindexlink = '';
4966 $link = '';
4968 if ($fakelink) {
4969 $PAGE->requires->js('/mod/forum/forum.js');
4970 $PAGE->requires->js_function_call('forum_produce_subscribe_link', array($forum->id, $backtoindexlink, $linktext, $linktitle));
4971 $link = "<noscript>";
4973 $options['id'] = $forum->id;
4974 $options['sesskey'] = sesskey();
4975 $url = new moodle_url('/mod/forum/subscribe.php', $options);
4976 $link .= $OUTPUT->single_button($url, $linktext, 'get', array('title'=>$linktitle));
4977 if ($fakelink) {
4978 $link .= '</noscript>';
4981 return $link;
4987 * Generate and return the track or no track link for a forum.
4989 * @global object
4990 * @global object
4991 * @global object
4992 * @param object $forum the forum. Fields used are $forum->id and $forum->forcesubscribe.
4993 * @param array $messages
4994 * @param bool $fakelink
4995 * @return string
4997 function forum_get_tracking_link($forum, $messages=array(), $fakelink=true) {
4998 global $CFG, $USER, $PAGE, $OUTPUT;
5000 static $strnotrackforum, $strtrackforum;
5002 if (isset($messages['trackforum'])) {
5003 $strtrackforum = $messages['trackforum'];
5005 if (isset($messages['notrackforum'])) {
5006 $strnotrackforum = $messages['notrackforum'];
5008 if (empty($strtrackforum)) {
5009 $strtrackforum = get_string('trackforum', 'forum');
5011 if (empty($strnotrackforum)) {
5012 $strnotrackforum = get_string('notrackforum', 'forum');
5015 if (forum_tp_is_tracked($forum)) {
5016 $linktitle = $strnotrackforum;
5017 $linktext = $strnotrackforum;
5018 } else {
5019 $linktitle = $strtrackforum;
5020 $linktext = $strtrackforum;
5023 $link = '';
5024 if ($fakelink) {
5025 $PAGE->requires->js('/mod/forum/forum.js');
5026 $PAGE->requires->js_function_call('forum_produce_tracking_link', Array($forum->id, $linktext, $linktitle));
5027 // use <noscript> to print button in case javascript is not enabled
5028 $link .= '<noscript>';
5030 $url = new moodle_url('/mod/forum/settracking.php', array('id'=>$forum->id));
5031 $link .= $OUTPUT->single_button($url, $linktext, 'get', array('title'=>$linktitle));
5033 if ($fakelink) {
5034 $link .= '</noscript>';
5037 return $link;
5043 * Returns true if user created new discussion already
5045 * @global object
5046 * @global object
5047 * @param int $forumid
5048 * @param int $userid
5049 * @return bool
5051 function forum_user_has_posted_discussion($forumid, $userid) {
5052 global $CFG, $DB;
5054 $sql = "SELECT 'x'
5055 FROM {forum_discussions} d, {forum_posts} p
5056 WHERE d.forum = ? AND p.discussion = d.id AND p.parent = 0 and p.userid = ?";
5058 return $DB->record_exists_sql($sql, array($forumid, $userid));
5062 * @global object
5063 * @global object
5064 * @param int $forumid
5065 * @param int $userid
5066 * @return array
5068 function forum_discussions_user_has_posted_in($forumid, $userid) {
5069 global $CFG, $DB;
5071 $haspostedsql = "SELECT d.id AS id,
5073 FROM {forum_posts} p,
5074 {forum_discussions} d
5075 WHERE p.discussion = d.id
5076 AND d.forum = ?
5077 AND p.userid = ?";
5079 return $DB->get_records_sql($haspostedsql, array($forumid, $userid));
5083 * @global object
5084 * @global object
5085 * @param int $forumid
5086 * @param int $did
5087 * @param int $userid
5088 * @return bool
5090 function forum_user_has_posted($forumid, $did, $userid) {
5091 global $DB;
5093 if (empty($did)) {
5094 // posted in any forum discussion?
5095 $sql = "SELECT 'x'
5096 FROM {forum_posts} p
5097 JOIN {forum_discussions} d ON d.id = p.discussion
5098 WHERE p.userid = :userid AND d.forum = :forumid";
5099 return $DB->record_exists_sql($sql, array('forumid'=>$forumid,'userid'=>$userid));
5100 } else {
5101 return $DB->record_exists('forum_posts', array('discussion'=>$did,'userid'=>$userid));
5106 * Returns creation time of the first user's post in given discussion
5107 * @global object $DB
5108 * @param int $did Discussion id
5109 * @param int $userid User id
5110 * @return int|bool post creation time stamp or return false
5112 function forum_get_user_posted_time($did, $userid) {
5113 global $DB;
5115 $posttime = $DB->get_field('forum_posts', 'MIN(created)', array('userid'=>$userid, 'discussion'=>$did));
5116 if (empty($posttime)) {
5117 return false;
5119 return $posttime;
5123 * @global object
5124 * @param object $forum
5125 * @param object $currentgroup
5126 * @param int $unused
5127 * @param object $cm
5128 * @param object $context
5129 * @return bool
5131 function forum_user_can_post_discussion($forum, $currentgroup=null, $unused=-1, $cm=NULL, $context=NULL) {
5132 // $forum is an object
5133 global $USER;
5135 // shortcut - guest and not-logged-in users can not post
5136 if (isguestuser() or !isloggedin()) {
5137 return false;
5140 if (!$cm) {
5141 debugging('missing cm', DEBUG_DEVELOPER);
5142 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5143 print_error('invalidcoursemodule');
5147 if (!$context) {
5148 $context = context_module::instance($cm->id);
5151 if ($currentgroup === null) {
5152 $currentgroup = groups_get_activity_group($cm);
5155 $groupmode = groups_get_activity_groupmode($cm);
5157 if ($forum->type == 'news') {
5158 $capname = 'mod/forum:addnews';
5159 } else if ($forum->type == 'qanda') {
5160 $capname = 'mod/forum:addquestion';
5161 } else {
5162 $capname = 'mod/forum:startdiscussion';
5165 if (!has_capability($capname, $context)) {
5166 return false;
5169 if ($forum->type == 'single') {
5170 return false;
5173 if ($forum->type == 'eachuser') {
5174 if (forum_user_has_posted_discussion($forum->id, $USER->id)) {
5175 return false;
5179 if (!$groupmode or has_capability('moodle/site:accessallgroups', $context)) {
5180 return true;
5183 if ($currentgroup) {
5184 return groups_is_member($currentgroup);
5185 } else {
5186 // no group membership and no accessallgroups means no new discussions
5187 // reverted to 1.7 behaviour in 1.9+, buggy in 1.8.0-1.9.0
5188 return false;
5193 * This function checks whether the user can reply to posts in a forum
5194 * discussion. Use forum_user_can_post_discussion() to check whether the user
5195 * can start discussions.
5197 * @global object
5198 * @global object
5199 * @uses DEBUG_DEVELOPER
5200 * @uses CONTEXT_MODULE
5201 * @uses VISIBLEGROUPS
5202 * @param object $forum forum object
5203 * @param object $discussion
5204 * @param object $user
5205 * @param object $cm
5206 * @param object $course
5207 * @param object $context
5208 * @return bool
5210 function forum_user_can_post($forum, $discussion, $user=NULL, $cm=NULL, $course=NULL, $context=NULL) {
5211 global $USER, $DB;
5212 if (empty($user)) {
5213 $user = $USER;
5216 // shortcut - guest and not-logged-in users can not post
5217 if (isguestuser($user) or empty($user->id)) {
5218 return false;
5221 if (!isset($discussion->groupid)) {
5222 debugging('incorrect discussion parameter', DEBUG_DEVELOPER);
5223 return false;
5226 if (!$cm) {
5227 debugging('missing cm', DEBUG_DEVELOPER);
5228 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5229 print_error('invalidcoursemodule');
5233 if (!$course) {
5234 debugging('missing course', DEBUG_DEVELOPER);
5235 if (!$course = $DB->get_record('course', array('id' => $forum->course))) {
5236 print_error('invalidcourseid');
5240 if (!$context) {
5241 $context = context_module::instance($cm->id);
5244 // normal users with temporary guest access can not post, suspended users can not post either
5245 if (!is_viewing($context, $user->id) and !is_enrolled($context, $user->id, '', true)) {
5246 return false;
5249 if ($forum->type == 'news') {
5250 $capname = 'mod/forum:replynews';
5251 } else {
5252 $capname = 'mod/forum:replypost';
5255 if (!has_capability($capname, $context, $user->id)) {
5256 return false;
5259 if (!$groupmode = groups_get_activity_groupmode($cm, $course)) {
5260 return true;
5263 if (has_capability('moodle/site:accessallgroups', $context)) {
5264 return true;
5267 if ($groupmode == VISIBLEGROUPS) {
5268 if ($discussion->groupid == -1) {
5269 // allow students to reply to all participants discussions - this was not possible in Moodle <1.8
5270 return true;
5272 return groups_is_member($discussion->groupid);
5274 } else {
5275 //separate groups
5276 if ($discussion->groupid == -1) {
5277 return false;
5279 return groups_is_member($discussion->groupid);
5284 * Checks to see if a user can view a particular post.
5286 * @deprecated since Moodle 2.4 use forum_user_can_see_post() instead
5288 * @param object $post
5289 * @param object $course
5290 * @param object $cm
5291 * @param object $forum
5292 * @param object $discussion
5293 * @param object $user
5294 * @return boolean
5296 function forum_user_can_view_post($post, $course, $cm, $forum, $discussion, $user=null){
5297 debugging('forum_user_can_view_post() is deprecated. Please use forum_user_can_see_post() instead.', DEBUG_DEVELOPER);
5298 return forum_user_can_see_post($forum, $discussion, $post, $user, $cm);
5302 * Check to ensure a user can view a timed discussion.
5304 * @param object $discussion
5305 * @param object $user
5306 * @param object $context
5307 * @return boolean returns true if they can view post, false otherwise
5309 function forum_user_can_see_timed_discussion($discussion, $user, $context) {
5310 global $CFG;
5312 // Check that the user can view a discussion that is normally hidden due to access times.
5313 if (!empty($CFG->forum_enabletimedposts)) {
5314 $time = time();
5315 if (($discussion->timestart != 0 && $discussion->timestart > $time)
5316 || ($discussion->timeend != 0 && $discussion->timeend < $time)) {
5317 if (!has_capability('mod/forum:viewhiddentimedposts', $context, $user->id)) {
5318 return false;
5323 return true;
5327 * Check to ensure a user can view a group discussion.
5329 * @param object $discussion
5330 * @param object $cm
5331 * @param object $context
5332 * @return boolean returns true if they can view post, false otherwise
5334 function forum_user_can_see_group_discussion($discussion, $cm, $context) {
5336 // If it's a grouped discussion, make sure the user is a member.
5337 if ($discussion->groupid > 0) {
5338 $groupmode = groups_get_activity_groupmode($cm);
5339 if ($groupmode == SEPARATEGROUPS) {
5340 return groups_is_member($discussion->groupid) || has_capability('moodle/site:accessallgroups', $context);
5344 return true;
5348 * @global object
5349 * @global object
5350 * @uses DEBUG_DEVELOPER
5351 * @param object $forum
5352 * @param object $discussion
5353 * @param object $context
5354 * @param object $user
5355 * @return bool
5357 function forum_user_can_see_discussion($forum, $discussion, $context, $user=NULL) {
5358 global $USER, $DB;
5360 if (empty($user) || empty($user->id)) {
5361 $user = $USER;
5364 // retrieve objects (yuk)
5365 if (is_numeric($forum)) {
5366 debugging('missing full forum', DEBUG_DEVELOPER);
5367 if (!$forum = $DB->get_record('forum',array('id'=>$forum))) {
5368 return false;
5371 if (is_numeric($discussion)) {
5372 debugging('missing full discussion', DEBUG_DEVELOPER);
5373 if (!$discussion = $DB->get_record('forum_discussions',array('id'=>$discussion))) {
5374 return false;
5377 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5378 print_error('invalidcoursemodule');
5381 if (!has_capability('mod/forum:viewdiscussion', $context)) {
5382 return false;
5385 if (!forum_user_can_see_timed_discussion($discussion, $user, $context)) {
5386 return false;
5389 if (!forum_user_can_see_group_discussion($discussion, $cm, $context)) {
5390 return false;
5393 if ($forum->type == 'qanda' &&
5394 !forum_user_has_posted($forum->id, $discussion->id, $user->id) &&
5395 !has_capability('mod/forum:viewqandawithoutposting', $context)) {
5396 return false;
5398 return true;
5402 * @global object
5403 * @global object
5404 * @param object $forum
5405 * @param object $discussion
5406 * @param object $post
5407 * @param object $user
5408 * @param object $cm
5409 * @return bool
5411 function forum_user_can_see_post($forum, $discussion, $post, $user=NULL, $cm=NULL) {
5412 global $CFG, $USER, $DB;
5414 // Context used throughout function.
5415 $modcontext = context_module::instance($cm->id);
5417 // retrieve objects (yuk)
5418 if (is_numeric($forum)) {
5419 debugging('missing full forum', DEBUG_DEVELOPER);
5420 if (!$forum = $DB->get_record('forum',array('id'=>$forum))) {
5421 return false;
5425 if (is_numeric($discussion)) {
5426 debugging('missing full discussion', DEBUG_DEVELOPER);
5427 if (!$discussion = $DB->get_record('forum_discussions',array('id'=>$discussion))) {
5428 return false;
5431 if (is_numeric($post)) {
5432 debugging('missing full post', DEBUG_DEVELOPER);
5433 if (!$post = $DB->get_record('forum_posts',array('id'=>$post))) {
5434 return false;
5438 if (!isset($post->id) && isset($post->parent)) {
5439 $post->id = $post->parent;
5442 if (!$cm) {
5443 debugging('missing cm', DEBUG_DEVELOPER);
5444 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5445 print_error('invalidcoursemodule');
5449 if (empty($user) || empty($user->id)) {
5450 $user = $USER;
5453 $canviewdiscussion = !empty($cm->cache->caps['mod/forum:viewdiscussion']) || has_capability('mod/forum:viewdiscussion', $modcontext, $user->id);
5454 if (!$canviewdiscussion && !has_all_capabilities(array('moodle/user:viewdetails', 'moodle/user:readuserposts'), context_user::instance($post->userid))) {
5455 return false;
5458 if (isset($cm->uservisible)) {
5459 if (!$cm->uservisible) {
5460 return false;
5462 } else {
5463 if (!coursemodule_visible_for_user($cm, $user->id)) {
5464 return false;
5468 if (!forum_user_can_see_timed_discussion($discussion, $user, $modcontext)) {
5469 return false;
5472 if (!forum_user_can_see_group_discussion($discussion, $cm, $modcontext)) {
5473 return false;
5476 if ($forum->type == 'qanda') {
5477 $firstpost = forum_get_firstpost_from_discussion($discussion->id);
5478 $userfirstpost = forum_get_user_posted_time($discussion->id, $user->id);
5480 return (($userfirstpost !== false && (time() - $userfirstpost >= $CFG->maxeditingtime)) ||
5481 $firstpost->id == $post->id || $post->userid == $user->id || $firstpost->userid == $user->id ||
5482 has_capability('mod/forum:viewqandawithoutposting', $modcontext, $user->id));
5484 return true;
5489 * Prints the discussion view screen for a forum.
5491 * @global object
5492 * @global object
5493 * @param object $course The current course object.
5494 * @param object $forum Forum to be printed.
5495 * @param int $maxdiscussions .
5496 * @param string $displayformat The display format to use (optional).
5497 * @param string $sort Sort arguments for database query (optional).
5498 * @param int $groupmode Group mode of the forum (optional).
5499 * @param void $unused (originally current group)
5500 * @param int $page Page mode, page to display (optional).
5501 * @param int $perpage The maximum number of discussions per page(optional)
5504 function forum_print_latest_discussions($course, $forum, $maxdiscussions=-1, $displayformat='plain', $sort='',
5505 $currentgroup=-1, $groupmode=-1, $page=-1, $perpage=100, $cm=NULL) {
5506 global $CFG, $USER, $OUTPUT;
5508 if (!$cm) {
5509 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
5510 print_error('invalidcoursemodule');
5513 $context = context_module::instance($cm->id);
5515 if (empty($sort)) {
5516 $sort = "d.timemodified DESC";
5519 $olddiscussionlink = false;
5521 // Sort out some defaults
5522 if ($perpage <= 0) {
5523 $perpage = 0;
5524 $page = -1;
5527 if ($maxdiscussions == 0) {
5528 // all discussions - backwards compatibility
5529 $page = -1;
5530 $perpage = 0;
5531 if ($displayformat == 'plain') {
5532 $displayformat = 'header'; // Abbreviate display by default
5535 } else if ($maxdiscussions > 0) {
5536 $page = -1;
5537 $perpage = $maxdiscussions;
5540 $fullpost = false;
5541 if ($displayformat == 'plain') {
5542 $fullpost = true;
5546 // Decide if current user is allowed to see ALL the current discussions or not
5548 // First check the group stuff
5549 if ($currentgroup == -1 or $groupmode == -1) {
5550 $groupmode = groups_get_activity_groupmode($cm, $course);
5551 $currentgroup = groups_get_activity_group($cm);
5554 $groups = array(); //cache
5556 // If the user can post discussions, then this is a good place to put the
5557 // button for it. We do not show the button if we are showing site news
5558 // and the current user is a guest.
5560 $canstart = forum_user_can_post_discussion($forum, $currentgroup, $groupmode, $cm, $context);
5561 if (!$canstart and $forum->type !== 'news') {
5562 if (isguestuser() or !isloggedin()) {
5563 $canstart = true;
5565 if (!is_enrolled($context) and !is_viewing($context)) {
5566 // allow guests and not-logged-in to see the button - they are prompted to log in after clicking the link
5567 // normal users with temporary guest access see this button too, they are asked to enrol instead
5568 // do not show the button to users with suspended enrolments here
5569 $canstart = enrol_selfenrol_available($course->id);
5573 if ($canstart) {
5574 echo '<div class="singlebutton forumaddnew">';
5575 echo "<form id=\"newdiscussionform\" method=\"get\" action=\"$CFG->wwwroot/mod/forum/post.php\">";
5576 echo '<div>';
5577 echo "<input type=\"hidden\" name=\"forum\" value=\"$forum->id\" />";
5578 switch ($forum->type) {
5579 case 'news':
5580 case 'blog':
5581 $buttonadd = get_string('addanewtopic', 'forum');
5582 break;
5583 case 'qanda':
5584 $buttonadd = get_string('addanewquestion', 'forum');
5585 break;
5586 default:
5587 $buttonadd = get_string('addanewdiscussion', 'forum');
5588 break;
5590 echo '<input type="submit" value="'.$buttonadd.'" />';
5591 echo '</div>';
5592 echo '</form>';
5593 echo "</div>\n";
5595 } else if (isguestuser() or !isloggedin() or $forum->type == 'news') {
5596 // no button and no info
5598 } else if ($groupmode and has_capability('mod/forum:startdiscussion', $context)) {
5599 // inform users why they can not post new discussion
5600 if ($currentgroup) {
5601 echo $OUTPUT->notification(get_string('cannotadddiscussion', 'forum'));
5602 } else {
5603 echo $OUTPUT->notification(get_string('cannotadddiscussionall', 'forum'));
5607 // Get all the recent discussions we're allowed to see
5609 $getuserlastmodified = ($displayformat == 'header');
5611 if (! $discussions = forum_get_discussions($cm, $sort, $fullpost, null, $maxdiscussions, $getuserlastmodified, $page, $perpage) ) {
5612 echo '<div class="forumnodiscuss">';
5613 if ($forum->type == 'news') {
5614 echo '('.get_string('nonews', 'forum').')';
5615 } else if ($forum->type == 'qanda') {
5616 echo '('.get_string('noquestions','forum').')';
5617 } else {
5618 echo '('.get_string('nodiscussions', 'forum').')';
5620 echo "</div>\n";
5621 return;
5624 // If we want paging
5625 if ($page != -1) {
5626 ///Get the number of discussions found
5627 $numdiscussions = forum_get_discussions_count($cm);
5629 ///Show the paging bar
5630 echo $OUTPUT->paging_bar($numdiscussions, $page, $perpage, "view.php?f=$forum->id");
5631 if ($numdiscussions > 1000) {
5632 // saves some memory on sites with very large forums
5633 $replies = forum_count_discussion_replies($forum->id, $sort, $maxdiscussions, $page, $perpage);
5634 } else {
5635 $replies = forum_count_discussion_replies($forum->id);
5638 } else {
5639 $replies = forum_count_discussion_replies($forum->id);
5641 if ($maxdiscussions > 0 and $maxdiscussions <= count($discussions)) {
5642 $olddiscussionlink = true;
5646 $canviewparticipants = has_capability('moodle/course:viewparticipants',$context);
5648 $strdatestring = get_string('strftimerecentfull');
5650 // Check if the forum is tracked.
5651 if ($cantrack = forum_tp_can_track_forums($forum)) {
5652 $forumtracked = forum_tp_is_tracked($forum);
5653 } else {
5654 $forumtracked = false;
5657 if ($forumtracked) {
5658 $unreads = forum_get_discussions_unread($cm);
5659 } else {
5660 $unreads = array();
5663 if ($displayformat == 'header') {
5664 echo '<table cellspacing="0" class="forumheaderlist">';
5665 echo '<thead>';
5666 echo '<tr>';
5667 echo '<th class="header topic" scope="col">'.get_string('discussion', 'forum').'</th>';
5668 echo '<th class="header author" colspan="2" scope="col">'.get_string('startedby', 'forum').'</th>';
5669 if ($groupmode > 0) {
5670 echo '<th class="header group" scope="col">'.get_string('group').'</th>';
5672 if (has_capability('mod/forum:viewdiscussion', $context)) {
5673 echo '<th class="header replies" scope="col">'.get_string('replies', 'forum').'</th>';
5674 // If the forum can be tracked, display the unread column.
5675 if ($cantrack) {
5676 echo '<th class="header replies" scope="col">'.get_string('unread', 'forum');
5677 if ($forumtracked) {
5678 echo '<a title="'.get_string('markallread', 'forum').
5679 '" href="'.$CFG->wwwroot.'/mod/forum/markposts.php?f='.
5680 $forum->id.'&amp;mark=read&amp;returnpage=view.php">'.
5681 '<img src="'.$OUTPUT->pix_url('t/markasread') . '" class="iconsmall" alt="'.get_string('markallread', 'forum').'" /></a>';
5683 echo '</th>';
5686 echo '<th class="header lastpost" scope="col">'.get_string('lastpost', 'forum').'</th>';
5687 echo '</tr>';
5688 echo '</thead>';
5689 echo '<tbody>';
5692 foreach ($discussions as $discussion) {
5693 if (!empty($replies[$discussion->discussion])) {
5694 $discussion->replies = $replies[$discussion->discussion]->replies;
5695 $discussion->lastpostid = $replies[$discussion->discussion]->lastpostid;
5696 } else {
5697 $discussion->replies = 0;
5700 // SPECIAL CASE: The front page can display a news item post to non-logged in users.
5701 // All posts are read in this case.
5702 if (!$forumtracked) {
5703 $discussion->unread = '-';
5704 } else if (empty($USER)) {
5705 $discussion->unread = 0;
5706 } else {
5707 if (empty($unreads[$discussion->discussion])) {
5708 $discussion->unread = 0;
5709 } else {
5710 $discussion->unread = $unreads[$discussion->discussion];
5714 if (isloggedin()) {
5715 $ownpost = ($discussion->userid == $USER->id);
5716 } else {
5717 $ownpost=false;
5719 // Use discussion name instead of subject of first post
5720 $discussion->subject = $discussion->name;
5722 switch ($displayformat) {
5723 case 'header':
5724 if ($groupmode > 0) {
5725 if (isset($groups[$discussion->groupid])) {
5726 $group = $groups[$discussion->groupid];
5727 } else {
5728 $group = $groups[$discussion->groupid] = groups_get_group($discussion->groupid);
5730 } else {
5731 $group = -1;
5733 forum_print_discussion_header($discussion, $forum, $group, $strdatestring, $cantrack, $forumtracked,
5734 $canviewparticipants, $context);
5735 break;
5736 default:
5737 $link = false;
5739 if ($discussion->replies) {
5740 $link = true;
5741 } else {
5742 $modcontext = context_module::instance($cm->id);
5743 $link = forum_user_can_see_discussion($forum, $discussion, $modcontext, $USER);
5746 $discussion->forum = $forum->id;
5748 forum_print_post($discussion, $discussion, $forum, $cm, $course, $ownpost, 0, $link, false,
5749 '', null, true, $forumtracked);
5750 break;
5754 if ($displayformat == "header") {
5755 echo '</tbody>';
5756 echo '</table>';
5759 if ($olddiscussionlink) {
5760 if ($forum->type == 'news') {
5761 $strolder = get_string('oldertopics', 'forum');
5762 } else {
5763 $strolder = get_string('olderdiscussions', 'forum');
5765 echo '<div class="forumolddiscuss">';
5766 echo '<a href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'&amp;showall=1">';
5767 echo $strolder.'</a> ...</div>';
5770 if ($page != -1) { ///Show the paging bar
5771 echo $OUTPUT->paging_bar($numdiscussions, $page, $perpage, "view.php?f=$forum->id");
5777 * Prints a forum discussion
5779 * @uses CONTEXT_MODULE
5780 * @uses FORUM_MODE_FLATNEWEST
5781 * @uses FORUM_MODE_FLATOLDEST
5782 * @uses FORUM_MODE_THREADED
5783 * @uses FORUM_MODE_NESTED
5784 * @param stdClass $course
5785 * @param stdClass $cm
5786 * @param stdClass $forum
5787 * @param stdClass $discussion
5788 * @param stdClass $post
5789 * @param int $mode
5790 * @param mixed $canreply
5791 * @param bool $canrate
5793 function forum_print_discussion($course, $cm, $forum, $discussion, $post, $mode, $canreply=NULL, $canrate=false) {
5794 global $USER, $CFG;
5796 require_once($CFG->dirroot.'/rating/lib.php');
5798 $ownpost = (isloggedin() && $USER->id == $post->userid);
5800 $modcontext = context_module::instance($cm->id);
5801 if ($canreply === NULL) {
5802 $reply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
5803 } else {
5804 $reply = $canreply;
5807 // $cm holds general cache for forum functions
5808 $cm->cache = new stdClass;
5809 $cm->cache->groups = groups_get_all_groups($course->id, 0, $cm->groupingid);
5810 $cm->cache->usersgroups = array();
5812 $posters = array();
5814 // preload all posts - TODO: improve...
5815 if ($mode == FORUM_MODE_FLATNEWEST) {
5816 $sort = "p.created DESC";
5817 } else {
5818 $sort = "p.created ASC";
5821 $forumtracked = forum_tp_is_tracked($forum);
5822 $posts = forum_get_all_discussion_posts($discussion->id, $sort, $forumtracked);
5823 $post = $posts[$post->id];
5825 foreach ($posts as $pid=>$p) {
5826 $posters[$p->userid] = $p->userid;
5829 // preload all groups of ppl that posted in this discussion
5830 if ($postersgroups = groups_get_all_groups($course->id, $posters, $cm->groupingid, 'gm.id, gm.groupid, gm.userid')) {
5831 foreach($postersgroups as $pg) {
5832 if (!isset($cm->cache->usersgroups[$pg->userid])) {
5833 $cm->cache->usersgroups[$pg->userid] = array();
5835 $cm->cache->usersgroups[$pg->userid][$pg->groupid] = $pg->groupid;
5837 unset($postersgroups);
5840 //load ratings
5841 if ($forum->assessed != RATING_AGGREGATE_NONE) {
5842 $ratingoptions = new stdClass;
5843 $ratingoptions->context = $modcontext;
5844 $ratingoptions->component = 'mod_forum';
5845 $ratingoptions->ratingarea = 'post';
5846 $ratingoptions->items = $posts;
5847 $ratingoptions->aggregate = $forum->assessed;//the aggregation method
5848 $ratingoptions->scaleid = $forum->scale;
5849 $ratingoptions->userid = $USER->id;
5850 if ($forum->type == 'single' or !$discussion->id) {
5851 $ratingoptions->returnurl = "$CFG->wwwroot/mod/forum/view.php?id=$cm->id";
5852 } else {
5853 $ratingoptions->returnurl = "$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id";
5855 $ratingoptions->assesstimestart = $forum->assesstimestart;
5856 $ratingoptions->assesstimefinish = $forum->assesstimefinish;
5858 $rm = new rating_manager();
5859 $posts = $rm->get_ratings($ratingoptions);
5863 $post->forum = $forum->id; // Add the forum id to the post object, later used by forum_print_post
5864 $post->forumtype = $forum->type;
5866 $post->subject = format_string($post->subject);
5868 $postread = !empty($post->postread);
5870 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, false,
5871 '', '', $postread, true, $forumtracked);
5873 switch ($mode) {
5874 case FORUM_MODE_FLATOLDEST :
5875 case FORUM_MODE_FLATNEWEST :
5876 default:
5877 forum_print_posts_flat($course, $cm, $forum, $discussion, $post, $mode, $reply, $forumtracked, $posts);
5878 break;
5880 case FORUM_MODE_THREADED :
5881 forum_print_posts_threaded($course, $cm, $forum, $discussion, $post, 0, $reply, $forumtracked, $posts);
5882 break;
5884 case FORUM_MODE_NESTED :
5885 forum_print_posts_nested($course, $cm, $forum, $discussion, $post, $reply, $forumtracked, $posts);
5886 break;
5892 * @global object
5893 * @global object
5894 * @uses FORUM_MODE_FLATNEWEST
5895 * @param object $course
5896 * @param object $cm
5897 * @param object $forum
5898 * @param object $discussion
5899 * @param object $post
5900 * @param object $mode
5901 * @param bool $reply
5902 * @param bool $forumtracked
5903 * @param array $posts
5904 * @return void
5906 function forum_print_posts_flat($course, &$cm, $forum, $discussion, $post, $mode, $reply, $forumtracked, $posts) {
5907 global $USER, $CFG;
5909 $link = false;
5911 if ($mode == FORUM_MODE_FLATNEWEST) {
5912 $sort = "ORDER BY created DESC";
5913 } else {
5914 $sort = "ORDER BY created ASC";
5917 foreach ($posts as $post) {
5918 if (!$post->parent) {
5919 continue;
5921 $post->subject = format_string($post->subject);
5922 $ownpost = ($USER->id == $post->userid);
5924 $postread = !empty($post->postread);
5926 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
5927 '', '', $postread, true, $forumtracked);
5932 * @todo Document this function
5934 * @global object
5935 * @global object
5936 * @uses CONTEXT_MODULE
5937 * @return void
5939 function forum_print_posts_threaded($course, &$cm, $forum, $discussion, $parent, $depth, $reply, $forumtracked, $posts) {
5940 global $USER, $CFG;
5942 $link = false;
5944 if (!empty($posts[$parent->id]->children)) {
5945 $posts = $posts[$parent->id]->children;
5947 $modcontext = context_module::instance($cm->id);
5948 $canviewfullnames = has_capability('moodle/site:viewfullnames', $modcontext);
5950 foreach ($posts as $post) {
5952 echo '<div class="indent">';
5953 if ($depth > 0) {
5954 $ownpost = ($USER->id == $post->userid);
5955 $post->subject = format_string($post->subject);
5957 $postread = !empty($post->postread);
5959 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
5960 '', '', $postread, true, $forumtracked);
5961 } else {
5962 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
5963 echo "</div>\n";
5964 continue;
5966 $by = new stdClass();
5967 $by->name = fullname($post, $canviewfullnames);
5968 $by->date = userdate($post->modified);
5970 if ($forumtracked) {
5971 if (!empty($post->postread)) {
5972 $style = '<span class="forumthread read">';
5973 } else {
5974 $style = '<span class="forumthread unread">';
5976 } else {
5977 $style = '<span class="forumthread">';
5979 echo $style."<a name=\"$post->id\"></a>".
5980 "<a href=\"discuss.php?d=$post->discussion&amp;parent=$post->id\">".format_string($post->subject,true)."</a> ";
5981 print_string("bynameondate", "forum", $by);
5982 echo "</span>";
5985 forum_print_posts_threaded($course, $cm, $forum, $discussion, $post, $depth-1, $reply, $forumtracked, $posts);
5986 echo "</div>\n";
5992 * @todo Document this function
5993 * @global object
5994 * @global object
5995 * @return void
5997 function forum_print_posts_nested($course, &$cm, $forum, $discussion, $parent, $reply, $forumtracked, $posts) {
5998 global $USER, $CFG;
6000 $link = false;
6002 if (!empty($posts[$parent->id]->children)) {
6003 $posts = $posts[$parent->id]->children;
6005 foreach ($posts as $post) {
6007 echo '<div class="indent">';
6008 if (!isloggedin()) {
6009 $ownpost = false;
6010 } else {
6011 $ownpost = ($USER->id == $post->userid);
6014 $post->subject = format_string($post->subject);
6015 $postread = !empty($post->postread);
6017 forum_print_post($post, $discussion, $forum, $cm, $course, $ownpost, $reply, $link,
6018 '', '', $postread, true, $forumtracked);
6019 forum_print_posts_nested($course, $cm, $forum, $discussion, $post, $reply, $forumtracked, $posts);
6020 echo "</div>\n";
6026 * Returns all forum posts since a given time in specified forum.
6028 * @todo Document this functions args
6029 * @global object
6030 * @global object
6031 * @global object
6032 * @global object
6034 function forum_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
6035 global $CFG, $COURSE, $USER, $DB;
6037 if ($COURSE->id == $courseid) {
6038 $course = $COURSE;
6039 } else {
6040 $course = $DB->get_record('course', array('id' => $courseid));
6043 $modinfo = get_fast_modinfo($course);
6045 $cm = $modinfo->cms[$cmid];
6046 $params = array($timestart, $cm->instance);
6048 if ($userid) {
6049 $userselect = "AND u.id = ?";
6050 $params[] = $userid;
6051 } else {
6052 $userselect = "";
6055 if ($groupid) {
6056 $groupselect = "AND d.groupid = ?";
6057 $params[] = $groupid;
6058 } else {
6059 $groupselect = "";
6062 $allnames = get_all_user_name_fields(true, 'u');
6063 if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
6064 d.timestart, d.timeend, d.userid AS duserid,
6065 $allnames, u.email, u.picture, u.imagealt, u.email
6066 FROM {forum_posts} p
6067 JOIN {forum_discussions} d ON d.id = p.discussion
6068 JOIN {forum} f ON f.id = d.forum
6069 JOIN {user} u ON u.id = p.userid
6070 WHERE p.created > ? AND f.id = ?
6071 $userselect $groupselect
6072 ORDER BY p.id ASC", $params)) { // order by initial posting date
6073 return;
6076 $groupmode = groups_get_activity_groupmode($cm, $course);
6077 $cm_context = context_module::instance($cm->id);
6078 $viewhiddentimed = has_capability('mod/forum:viewhiddentimedposts', $cm_context);
6079 $accessallgroups = has_capability('moodle/site:accessallgroups', $cm_context);
6081 $printposts = array();
6082 foreach ($posts as $post) {
6084 if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
6085 and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
6086 if (!$viewhiddentimed) {
6087 continue;
6091 if ($groupmode) {
6092 if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or $accessallgroups) {
6093 // oki (Open discussions have groupid -1)
6094 } else {
6095 // separate mode
6096 if (isguestuser()) {
6097 // shortcut
6098 continue;
6101 if (!in_array($post->groupid, $modinfo->get_groups($cm->groupingid))) {
6102 continue;
6107 $printposts[] = $post;
6110 if (!$printposts) {
6111 return;
6114 $aname = format_string($cm->name,true);
6116 foreach ($printposts as $post) {
6117 $tmpactivity = new stdClass();
6119 $tmpactivity->type = 'forum';
6120 $tmpactivity->cmid = $cm->id;
6121 $tmpactivity->name = $aname;
6122 $tmpactivity->sectionnum = $cm->sectionnum;
6123 $tmpactivity->timestamp = $post->modified;
6125 $tmpactivity->content = new stdClass();
6126 $tmpactivity->content->id = $post->id;
6127 $tmpactivity->content->discussion = $post->discussion;
6128 $tmpactivity->content->subject = format_string($post->subject);
6129 $tmpactivity->content->parent = $post->parent;
6131 $tmpactivity->user = new stdClass();
6132 $tmpactivity->user->id = $post->userid;
6133 $tmpactivity->user->picture = $post->picture;
6134 $tmpactivity->user->imagealt = $post->imagealt;
6135 $tmpactivity->user->email = $post->email;
6136 foreach (get_all_user_name_fields() as $addname) {
6137 $tmpactivity->user->$addname = $post->$addname;
6140 $activities[$index++] = $tmpactivity;
6143 return;
6147 * @todo Document this function
6148 * @global object
6150 function forum_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) {
6151 global $CFG, $OUTPUT;
6153 if ($activity->content->parent) {
6154 $class = 'reply';
6155 } else {
6156 $class = 'discussion';
6159 echo '<table border="0" cellpadding="3" cellspacing="0" class="forum-recent">';
6161 echo "<tr><td class=\"userpicture\" valign=\"top\">";
6162 echo $OUTPUT->user_picture($activity->user, array('courseid'=>$courseid));
6163 echo "</td><td class=\"$class\">";
6165 echo '<div class="title">';
6166 if ($detail) {
6167 $aname = s($activity->name);
6168 echo "<img src=\"" . $OUTPUT->pix_url('icon', $activity->type) . "\" ".
6169 "class=\"icon\" alt=\"{$aname}\" />";
6171 echo "<a href=\"$CFG->wwwroot/mod/forum/discuss.php?d={$activity->content->discussion}"
6172 ."#p{$activity->content->id}\">{$activity->content->subject}</a>";
6173 echo '</div>';
6175 echo '<div class="user">';
6176 $fullname = fullname($activity->user, $viewfullnames);
6177 echo "<a href=\"$CFG->wwwroot/user/view.php?id={$activity->user->id}&amp;course=$courseid\">"
6178 ."{$fullname}</a> - ".userdate($activity->timestamp);
6179 echo '</div>';
6180 echo "</td></tr></table>";
6182 return;
6186 * recursively sets the discussion field to $discussionid on $postid and all its children
6187 * used when pruning a post
6189 * @global object
6190 * @param int $postid
6191 * @param int $discussionid
6192 * @return bool
6194 function forum_change_discussionid($postid, $discussionid) {
6195 global $DB;
6196 $DB->set_field('forum_posts', 'discussion', $discussionid, array('id' => $postid));
6197 if ($posts = $DB->get_records('forum_posts', array('parent' => $postid))) {
6198 foreach ($posts as $post) {
6199 forum_change_discussionid($post->id, $discussionid);
6202 return true;
6206 * Prints the editing button on subscribers page
6208 * @global object
6209 * @global object
6210 * @param int $courseid
6211 * @param int $forumid
6212 * @return string
6214 function forum_update_subscriptions_button($courseid, $forumid) {
6215 global $CFG, $USER;
6217 if (!empty($USER->subscriptionsediting)) {
6218 $string = get_string('turneditingoff');
6219 $edit = "off";
6220 } else {
6221 $string = get_string('turneditingon');
6222 $edit = "on";
6225 return "<form method=\"get\" action=\"$CFG->wwwroot/mod/forum/subscribers.php\">".
6226 "<input type=\"hidden\" name=\"id\" value=\"$forumid\" />".
6227 "<input type=\"hidden\" name=\"edit\" value=\"$edit\" />".
6228 "<input type=\"submit\" value=\"$string\" /></form>";
6232 * This function gets run whenever user is enrolled into course
6234 * @deprecated deprecating this function as we will be using forum_user_role_assigned
6235 * @param stdClass $cp
6236 * @return void
6238 function forum_user_enrolled($cp) {
6239 global $DB;
6241 // NOTE: this has to be as fast as possible - we do not want to slow down enrolments!
6242 // Originally there used to be 'mod/forum:initialsubscriptions' which was
6243 // introduced because we did not have enrolment information in earlier versions...
6245 $sql = "SELECT f.id
6246 FROM {forum} f
6247 LEFT JOIN {forum_subscriptions} fs ON (fs.forum = f.id AND fs.userid = :userid)
6248 WHERE f.course = :courseid AND f.forcesubscribe = :initial AND fs.id IS NULL";
6249 $params = array('courseid'=>$cp->courseid, 'userid'=>$cp->userid, 'initial'=>FORUM_INITIALSUBSCRIBE);
6251 $forums = $DB->get_records_sql($sql, $params);
6252 foreach ($forums as $forum) {
6253 forum_subscribe($cp->userid, $forum->id);
6258 * This function gets run whenever user is assigned role in course
6260 * @param stdClass $cp
6261 * @return void
6263 function forum_user_role_assigned($cp) {
6264 global $DB;
6266 $context = context::instance_by_id($cp->contextid, MUST_EXIST);
6268 // If contextlevel is course then only subscribe user. Role assignment
6269 // at course level means user is enroled in course and can subscribe to forum.
6270 if ($context->contextlevel != CONTEXT_COURSE) {
6271 return;
6274 $sql = "SELECT f.id, cm.id AS cmid
6275 FROM {forum} f
6276 JOIN {course_modules} cm ON (cm.instance = f.id)
6277 JOIN {modules} m ON (m.id = cm.module)
6278 LEFT JOIN {forum_subscriptions} fs ON (fs.forum = f.id AND fs.userid = :userid)
6279 WHERE f.course = :courseid
6280 AND f.forcesubscribe = :initial
6281 AND m.name = 'forum'
6282 AND fs.id IS NULL";
6283 $params = array('courseid'=>$context->instanceid, 'userid'=>$cp->userid, 'initial'=>FORUM_INITIALSUBSCRIBE);
6285 $forums = $DB->get_records_sql($sql, $params);
6286 foreach ($forums as $forum) {
6287 // If user doesn't have allowforcesubscribe capability then don't subscribe.
6288 if (has_capability('mod/forum:allowforcesubscribe', context_module::instance($forum->cmid), $cp->userid)) {
6289 forum_subscribe($cp->userid, $forum->id);
6295 * This function gets run whenever user is unenrolled from course
6297 * @param stdClass $cp
6298 * @return void
6300 function forum_user_unenrolled($cp) {
6301 global $DB;
6303 // NOTE: this has to be as fast as possible!
6305 if ($cp->lastenrol) {
6306 $params = array('userid'=>$cp->userid, 'courseid'=>$cp->courseid);
6307 $forumselect = "IN (SELECT f.id FROM {forum} f WHERE f.course = :courseid)";
6309 $DB->delete_records_select('forum_digests', "userid = :userid AND forum $forumselect", $params);
6310 $DB->delete_records_select('forum_subscriptions', "userid = :userid AND forum $forumselect", $params);
6311 $DB->delete_records_select('forum_track_prefs', "userid = :userid AND forumid $forumselect", $params);
6312 $DB->delete_records_select('forum_read', "userid = :userid AND forumid $forumselect", $params);
6316 // Functions to do with read tracking.
6319 * Mark posts as read.
6321 * @global object
6322 * @global object
6323 * @param object $user object
6324 * @param array $postids array of post ids
6325 * @return boolean success
6327 function forum_tp_mark_posts_read($user, $postids) {
6328 global $CFG, $DB;
6330 if (!forum_tp_can_track_forums(false, $user)) {
6331 return true;
6334 $status = true;
6336 $now = time();
6337 $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
6339 if (empty($postids)) {
6340 return true;
6342 } else if (count($postids) > 200) {
6343 while ($part = array_splice($postids, 0, 200)) {
6344 $status = forum_tp_mark_posts_read($user, $part) && $status;
6346 return $status;
6349 list($usql, $params) = $DB->get_in_or_equal($postids);
6350 $params[] = $user->id;
6352 $sql = "SELECT id
6353 FROM {forum_read}
6354 WHERE postid $usql AND userid = ?";
6355 if ($existing = $DB->get_records_sql($sql, $params)) {
6356 $existing = array_keys($existing);
6357 } else {
6358 $existing = array();
6361 $new = array_diff($postids, $existing);
6363 if ($new) {
6364 list($usql, $new_params) = $DB->get_in_or_equal($new);
6365 $params = array($user->id, $now, $now, $user->id);
6366 $params = array_merge($params, $new_params);
6367 $params[] = $cutoffdate;
6369 $sql = "INSERT INTO {forum_read} (userid, postid, discussionid, forumid, firstread, lastread)
6371 SELECT ?, p.id, p.discussion, d.forum, ?, ?
6372 FROM {forum_posts} p
6373 JOIN {forum_discussions} d ON d.id = p.discussion
6374 JOIN {forum} f ON f.id = d.forum
6375 LEFT JOIN {forum_track_prefs} tf ON (tf.userid = ? AND tf.forumid = f.id)
6376 WHERE p.id $usql
6377 AND p.modified >= ?
6378 AND (f.trackingtype = ".FORUM_TRACKING_ON."
6379 OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND tf.id IS NULL))";
6380 $status = $DB->execute($sql, $params) && $status;
6383 if ($existing) {
6384 list($usql, $new_params) = $DB->get_in_or_equal($existing);
6385 $params = array($now, $user->id);
6386 $params = array_merge($params, $new_params);
6388 $sql = "UPDATE {forum_read}
6389 SET lastread = ?
6390 WHERE userid = ? AND postid $usql";
6391 $status = $DB->execute($sql, $params) && $status;
6394 return $status;
6398 * Mark post as read.
6399 * @global object
6400 * @global object
6401 * @param int $userid
6402 * @param int $postid
6404 function forum_tp_add_read_record($userid, $postid) {
6405 global $CFG, $DB;
6407 $now = time();
6408 $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
6410 if (!$DB->record_exists('forum_read', array('userid' => $userid, 'postid' => $postid))) {
6411 $sql = "INSERT INTO {forum_read} (userid, postid, discussionid, forumid, firstread, lastread)
6413 SELECT ?, p.id, p.discussion, d.forum, ?, ?
6414 FROM {forum_posts} p
6415 JOIN {forum_discussions} d ON d.id = p.discussion
6416 WHERE p.id = ? AND p.modified >= ?";
6417 return $DB->execute($sql, array($userid, $now, $now, $postid, $cutoffdate));
6419 } else {
6420 $sql = "UPDATE {forum_read}
6421 SET lastread = ?
6422 WHERE userid = ? AND postid = ?";
6423 return $DB->execute($sql, array($now, $userid, $userid));
6428 * Returns all records in the 'forum_read' table matching the passed keys, indexed
6429 * by userid.
6431 * @global object
6432 * @param int $userid
6433 * @param int $postid
6434 * @param int $discussionid
6435 * @param int $forumid
6436 * @return array
6438 function forum_tp_get_read_records($userid=-1, $postid=-1, $discussionid=-1, $forumid=-1) {
6439 global $DB;
6440 $select = '';
6441 $params = array();
6443 if ($userid > -1) {
6444 if ($select != '') $select .= ' AND ';
6445 $select .= 'userid = ?';
6446 $params[] = $userid;
6448 if ($postid > -1) {
6449 if ($select != '') $select .= ' AND ';
6450 $select .= 'postid = ?';
6451 $params[] = $postid;
6453 if ($discussionid > -1) {
6454 if ($select != '') $select .= ' AND ';
6455 $select .= 'discussionid = ?';
6456 $params[] = $discussionid;
6458 if ($forumid > -1) {
6459 if ($select != '') $select .= ' AND ';
6460 $select .= 'forumid = ?';
6461 $params[] = $forumid;
6464 return $DB->get_records_select('forum_read', $select, $params);
6468 * Returns all read records for the provided user and discussion, indexed by postid.
6470 * @global object
6471 * @param inti $userid
6472 * @param int $discussionid
6474 function forum_tp_get_discussion_read_records($userid, $discussionid) {
6475 global $DB;
6476 $select = 'userid = ? AND discussionid = ?';
6477 $fields = 'postid, firstread, lastread';
6478 return $DB->get_records_select('forum_read', $select, array($userid, $discussionid), '', $fields);
6482 * If its an old post, do nothing. If the record exists, the maintenance will clear it up later.
6484 * @return bool
6486 function forum_tp_mark_post_read($userid, $post, $forumid) {
6487 if (!forum_tp_is_post_old($post)) {
6488 return forum_tp_add_read_record($userid, $post->id);
6489 } else {
6490 return true;
6495 * Marks a whole forum as read, for a given user
6497 * @global object
6498 * @global object
6499 * @param object $user
6500 * @param int $forumid
6501 * @param int|bool $groupid
6502 * @return bool
6504 function forum_tp_mark_forum_read($user, $forumid, $groupid=false) {
6505 global $CFG, $DB;
6507 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6509 $groupsel = "";
6510 $params = array($user->id, $forumid, $cutoffdate);
6512 if ($groupid !== false) {
6513 $groupsel = " AND (d.groupid = ? OR d.groupid = -1)";
6514 $params[] = $groupid;
6517 $sql = "SELECT p.id
6518 FROM {forum_posts} p
6519 LEFT JOIN {forum_discussions} d ON d.id = p.discussion
6520 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6521 WHERE d.forum = ?
6522 AND p.modified >= ? AND r.id is NULL
6523 $groupsel";
6525 if ($posts = $DB->get_records_sql($sql, $params)) {
6526 $postids = array_keys($posts);
6527 return forum_tp_mark_posts_read($user, $postids);
6530 return true;
6534 * Marks a whole discussion as read, for a given user
6536 * @global object
6537 * @global object
6538 * @param object $user
6539 * @param int $discussionid
6540 * @return bool
6542 function forum_tp_mark_discussion_read($user, $discussionid) {
6543 global $CFG, $DB;
6545 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6547 $sql = "SELECT p.id
6548 FROM {forum_posts} p
6549 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6550 WHERE p.discussion = ?
6551 AND p.modified >= ? AND r.id is NULL";
6553 if ($posts = $DB->get_records_sql($sql, array($user->id, $discussionid, $cutoffdate))) {
6554 $postids = array_keys($posts);
6555 return forum_tp_mark_posts_read($user, $postids);
6558 return true;
6562 * @global object
6563 * @param int $userid
6564 * @param object $post
6566 function forum_tp_is_post_read($userid, $post) {
6567 global $DB;
6568 return (forum_tp_is_post_old($post) ||
6569 $DB->record_exists('forum_read', array('userid' => $userid, 'postid' => $post->id)));
6573 * @global object
6574 * @param object $post
6575 * @param int $time Defautls to time()
6577 function forum_tp_is_post_old($post, $time=null) {
6578 global $CFG;
6580 if (is_null($time)) {
6581 $time = time();
6583 return ($post->modified < ($time - ($CFG->forum_oldpostdays * 24 * 3600)));
6587 * Returns the count of records for the provided user and discussion.
6589 * @global object
6590 * @global object
6591 * @param int $userid
6592 * @param int $discussionid
6593 * @return bool
6595 function forum_tp_count_discussion_read_records($userid, $discussionid) {
6596 global $CFG, $DB;
6598 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
6600 $sql = 'SELECT COUNT(DISTINCT p.id) '.
6601 'FROM {forum_discussions} d '.
6602 'LEFT JOIN {forum_read} r ON d.id = r.discussionid AND r.userid = ? '.
6603 'LEFT JOIN {forum_posts} p ON p.discussion = d.id '.
6604 'AND (p.modified < ? OR p.id = r.postid) '.
6605 'WHERE d.id = ? ';
6607 return ($DB->count_records_sql($sql, array($userid, $cutoffdate, $discussionid)));
6611 * Returns the count of records for the provided user and discussion.
6613 * @global object
6614 * @global object
6615 * @param int $userid
6616 * @param int $discussionid
6617 * @return int
6619 function forum_tp_count_discussion_unread_posts($userid, $discussionid) {
6620 global $CFG, $DB;
6622 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
6624 $sql = 'SELECT COUNT(p.id) '.
6625 'FROM {forum_posts} p '.
6626 'LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? '.
6627 'WHERE p.discussion = ? '.
6628 'AND p.modified >= ? AND r.id is NULL';
6630 return $DB->count_records_sql($sql, array($userid, $discussionid, $cutoffdate));
6634 * Returns the count of posts for the provided forum and [optionally] group.
6635 * @global object
6636 * @global object
6637 * @param int $forumid
6638 * @param int|bool $groupid
6639 * @return int
6641 function forum_tp_count_forum_posts($forumid, $groupid=false) {
6642 global $CFG, $DB;
6643 $params = array($forumid);
6644 $sql = 'SELECT COUNT(*) '.
6645 'FROM {forum_posts} fp,{forum_discussions} fd '.
6646 'WHERE fd.forum = ? AND fp.discussion = fd.id';
6647 if ($groupid !== false) {
6648 $sql .= ' AND (fd.groupid = ? OR fd.groupid = -1)';
6649 $params[] = $groupid;
6651 $count = $DB->count_records_sql($sql, $params);
6654 return $count;
6658 * Returns the count of records for the provided user and forum and [optionally] group.
6659 * @global object
6660 * @global object
6661 * @param int $userid
6662 * @param int $forumid
6663 * @param int|bool $groupid
6664 * @return int
6666 function forum_tp_count_forum_read_records($userid, $forumid, $groupid=false) {
6667 global $CFG, $DB;
6669 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
6671 $groupsel = '';
6672 $params = array($userid, $forumid, $cutoffdate);
6673 if ($groupid !== false) {
6674 $groupsel = "AND (d.groupid = ? OR d.groupid = -1)";
6675 $params[] = $groupid;
6678 $sql = "SELECT COUNT(p.id)
6679 FROM {forum_posts} p
6680 JOIN {forum_discussions} d ON d.id = p.discussion
6681 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid= ?)
6682 WHERE d.forum = ?
6683 AND (p.modified < $cutoffdate OR (p.modified >= ? AND r.id IS NOT NULL))
6684 $groupsel";
6686 return $DB->get_field_sql($sql, $params);
6690 * Returns the count of records for the provided user and course.
6691 * Please note that group access is ignored!
6693 * @global object
6694 * @global object
6695 * @param int $userid
6696 * @param int $courseid
6697 * @return array
6699 function forum_tp_get_course_unread_posts($userid, $courseid) {
6700 global $CFG, $DB;
6702 $now = round(time(), -2); // db cache friendliness
6703 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
6704 $params = array($userid, $userid, $courseid, $cutoffdate);
6706 if (!empty($CFG->forum_enabletimedposts)) {
6707 $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
6708 $params[] = $now;
6709 $params[] = $now;
6710 } else {
6711 $timedsql = "";
6714 $sql = "SELECT f.id, COUNT(p.id) AS unread
6715 FROM {forum_posts} p
6716 JOIN {forum_discussions} d ON d.id = p.discussion
6717 JOIN {forum} f ON f.id = d.forum
6718 JOIN {course} c ON c.id = f.course
6719 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6720 LEFT JOIN {forum_track_prefs} tf ON (tf.userid = ? AND tf.forumid = f.id)
6721 WHERE f.course = ?
6722 AND p.modified >= ? AND r.id is NULL
6723 AND (f.trackingtype = ".FORUM_TRACKING_ON."
6724 OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND tf.id IS NULL))
6725 $timedsql
6726 GROUP BY f.id";
6728 if ($return = $DB->get_records_sql($sql, $params)) {
6729 return $return;
6732 return array();
6736 * Returns the count of records for the provided user and forum and [optionally] group.
6738 * @global object
6739 * @global object
6740 * @global object
6741 * @param object $cm
6742 * @param object $course
6743 * @return int
6745 function forum_tp_count_forum_unread_posts($cm, $course) {
6746 global $CFG, $USER, $DB;
6748 static $readcache = array();
6750 $forumid = $cm->instance;
6752 if (!isset($readcache[$course->id])) {
6753 $readcache[$course->id] = array();
6754 if ($counts = forum_tp_get_course_unread_posts($USER->id, $course->id)) {
6755 foreach ($counts as $count) {
6756 $readcache[$course->id][$count->id] = $count->unread;
6761 if (empty($readcache[$course->id][$forumid])) {
6762 // no need to check group mode ;-)
6763 return 0;
6766 $groupmode = groups_get_activity_groupmode($cm, $course);
6768 if ($groupmode != SEPARATEGROUPS) {
6769 return $readcache[$course->id][$forumid];
6772 if (has_capability('moodle/site:accessallgroups', context_module::instance($cm->id))) {
6773 return $readcache[$course->id][$forumid];
6776 require_once($CFG->dirroot.'/course/lib.php');
6778 $modinfo = get_fast_modinfo($course);
6780 $mygroups = $modinfo->get_groups($cm->groupingid);
6782 // add all groups posts
6783 $mygroups[-1] = -1;
6785 list ($groups_sql, $groups_params) = $DB->get_in_or_equal($mygroups);
6787 $now = round(time(), -2); // db cache friendliness
6788 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
6789 $params = array($USER->id, $forumid, $cutoffdate);
6791 if (!empty($CFG->forum_enabletimedposts)) {
6792 $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
6793 $params[] = $now;
6794 $params[] = $now;
6795 } else {
6796 $timedsql = "";
6799 $params = array_merge($params, $groups_params);
6801 $sql = "SELECT COUNT(p.id)
6802 FROM {forum_posts} p
6803 JOIN {forum_discussions} d ON p.discussion = d.id
6804 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = ?)
6805 WHERE d.forum = ?
6806 AND p.modified >= ? AND r.id is NULL
6807 $timedsql
6808 AND d.groupid $groups_sql";
6810 return $DB->get_field_sql($sql, $params);
6814 * Deletes read records for the specified index. At least one parameter must be specified.
6816 * @global object
6817 * @param int $userid
6818 * @param int $postid
6819 * @param int $discussionid
6820 * @param int $forumid
6821 * @return bool
6823 function forum_tp_delete_read_records($userid=-1, $postid=-1, $discussionid=-1, $forumid=-1) {
6824 global $DB;
6825 $params = array();
6827 $select = '';
6828 if ($userid > -1) {
6829 if ($select != '') $select .= ' AND ';
6830 $select .= 'userid = ?';
6831 $params[] = $userid;
6833 if ($postid > -1) {
6834 if ($select != '') $select .= ' AND ';
6835 $select .= 'postid = ?';
6836 $params[] = $postid;
6838 if ($discussionid > -1) {
6839 if ($select != '') $select .= ' AND ';
6840 $select .= 'discussionid = ?';
6841 $params[] = $discussionid;
6843 if ($forumid > -1) {
6844 if ($select != '') $select .= ' AND ';
6845 $select .= 'forumid = ?';
6846 $params[] = $forumid;
6848 if ($select == '') {
6849 return false;
6851 else {
6852 return $DB->delete_records_select('forum_read', $select, $params);
6856 * Get a list of forums not tracked by the user.
6858 * @global object
6859 * @global object
6860 * @param int $userid The id of the user to use.
6861 * @param int $courseid The id of the course being checked.
6862 * @return mixed An array indexed by forum id, or false.
6864 function forum_tp_get_untracked_forums($userid, $courseid) {
6865 global $CFG, $DB;
6867 $sql = "SELECT f.id
6868 FROM {forum} f
6869 LEFT JOIN {forum_track_prefs} ft ON (ft.forumid = f.id AND ft.userid = ?)
6870 WHERE f.course = ?
6871 AND (f.trackingtype = ".FORUM_TRACKING_OFF."
6872 OR (f.trackingtype = ".FORUM_TRACKING_OPTIONAL." AND ft.id IS NOT NULL))";
6874 if ($forums = $DB->get_records_sql($sql, array($userid, $courseid))) {
6875 foreach ($forums as $forum) {
6876 $forums[$forum->id] = $forum;
6878 return $forums;
6880 } else {
6881 return array();
6886 * Determine if a user can track forums and optionally a particular forum.
6887 * Checks the site settings, the user settings and the forum settings (if
6888 * requested).
6890 * @global object
6891 * @global object
6892 * @global object
6893 * @param mixed $forum The forum object to test, or the int id (optional).
6894 * @param mixed $userid The user object to check for (optional).
6895 * @return boolean
6897 function forum_tp_can_track_forums($forum=false, $user=false) {
6898 global $USER, $CFG, $DB;
6900 // if possible, avoid expensive
6901 // queries
6902 if (empty($CFG->forum_trackreadposts)) {
6903 return false;
6906 if ($user === false) {
6907 $user = $USER;
6910 if (isguestuser($user) or empty($user->id)) {
6911 return false;
6914 if ($forum === false) {
6915 // general abitily to track forums
6916 return (bool)$user->trackforums;
6920 // Work toward always passing an object...
6921 if (is_numeric($forum)) {
6922 debugging('Better use proper forum object.', DEBUG_DEVELOPER);
6923 $forum = $DB->get_record('forum', array('id' => $forum), '', 'id,trackingtype');
6926 $forumallows = ($forum->trackingtype == FORUM_TRACKING_OPTIONAL);
6927 $forumforced = ($forum->trackingtype == FORUM_TRACKING_ON);
6929 return ($forumforced || $forumallows) && !empty($user->trackforums);
6933 * Tells whether a specific forum is tracked by the user. A user can optionally
6934 * be specified. If not specified, the current user is assumed.
6936 * @global object
6937 * @global object
6938 * @global object
6939 * @param mixed $forum If int, the id of the forum being checked; if object, the forum object
6940 * @param int $userid The id of the user being checked (optional).
6941 * @return boolean
6943 function forum_tp_is_tracked($forum, $user=false) {
6944 global $USER, $CFG, $DB;
6946 if ($user === false) {
6947 $user = $USER;
6950 if (isguestuser($user) or empty($user->id)) {
6951 return false;
6954 // Work toward always passing an object...
6955 if (is_numeric($forum)) {
6956 debugging('Better use proper forum object.', DEBUG_DEVELOPER);
6957 $forum = $DB->get_record('forum', array('id' => $forum));
6960 if (!forum_tp_can_track_forums($forum, $user)) {
6961 return false;
6964 $forumallows = ($forum->trackingtype == FORUM_TRACKING_OPTIONAL);
6965 $forumforced = ($forum->trackingtype == FORUM_TRACKING_ON);
6967 return $forumforced ||
6968 ($forumallows && $DB->get_record('forum_track_prefs', array('userid' => $user->id, 'forumid' => $forum->id)) === false);
6972 * @global object
6973 * @global object
6974 * @param int $forumid
6975 * @param int $userid
6977 function forum_tp_start_tracking($forumid, $userid=false) {
6978 global $USER, $DB;
6980 if ($userid === false) {
6981 $userid = $USER->id;
6984 return $DB->delete_records('forum_track_prefs', array('userid' => $userid, 'forumid' => $forumid));
6988 * @global object
6989 * @global object
6990 * @param int $forumid
6991 * @param int $userid
6993 function forum_tp_stop_tracking($forumid, $userid=false) {
6994 global $USER, $DB;
6996 if ($userid === false) {
6997 $userid = $USER->id;
7000 if (!$DB->record_exists('forum_track_prefs', array('userid' => $userid, 'forumid' => $forumid))) {
7001 $track_prefs = new stdClass();
7002 $track_prefs->userid = $userid;
7003 $track_prefs->forumid = $forumid;
7004 $DB->insert_record('forum_track_prefs', $track_prefs);
7007 return forum_tp_delete_read_records($userid, -1, -1, $forumid);
7012 * Clean old records from the forum_read table.
7013 * @global object
7014 * @global object
7015 * @return void
7017 function forum_tp_clean_read_records() {
7018 global $CFG, $DB;
7020 if (!isset($CFG->forum_oldpostdays)) {
7021 return;
7023 // Look for records older than the cutoffdate that are still in the forum_read table.
7024 $cutoffdate = time() - ($CFG->forum_oldpostdays*24*60*60);
7026 //first get the oldest tracking present - we need tis to speedup the next delete query
7027 $sql = "SELECT MIN(fp.modified) AS first
7028 FROM {forum_posts} fp
7029 JOIN {forum_read} fr ON fr.postid=fp.id";
7030 if (!$first = $DB->get_field_sql($sql)) {
7031 // nothing to delete;
7032 return;
7035 // now delete old tracking info
7036 $sql = "DELETE
7037 FROM {forum_read}
7038 WHERE postid IN (SELECT fp.id
7039 FROM {forum_posts} fp
7040 WHERE fp.modified >= ? AND fp.modified < ?)";
7041 $DB->execute($sql, array($first, $cutoffdate));
7045 * Sets the last post for a given discussion
7047 * @global object
7048 * @global object
7049 * @param into $discussionid
7050 * @return bool|int
7052 function forum_discussion_update_last_post($discussionid) {
7053 global $CFG, $DB;
7055 // Check the given discussion exists
7056 if (!$DB->record_exists('forum_discussions', array('id' => $discussionid))) {
7057 return false;
7060 // Use SQL to find the last post for this discussion
7061 $sql = "SELECT id, userid, modified
7062 FROM {forum_posts}
7063 WHERE discussion=?
7064 ORDER BY modified DESC";
7066 // Lets go find the last post
7067 if (($lastposts = $DB->get_records_sql($sql, array($discussionid), 0, 1))) {
7068 $lastpost = reset($lastposts);
7069 $discussionobject = new stdClass();
7070 $discussionobject->id = $discussionid;
7071 $discussionobject->usermodified = $lastpost->userid;
7072 $discussionobject->timemodified = $lastpost->modified;
7073 $DB->update_record('forum_discussions', $discussionobject);
7074 return $lastpost->id;
7077 // To get here either we couldn't find a post for the discussion (weird)
7078 // or we couldn't update the discussion record (weird x2)
7079 return false;
7084 * @return array
7086 function forum_get_view_actions() {
7087 return array('view discussion', 'search', 'forum', 'forums', 'subscribers', 'view forum');
7091 * @return array
7093 function forum_get_post_actions() {
7094 return array('add discussion','add post','delete discussion','delete post','move discussion','prune post','update post');
7098 * Returns a warning object if a user has reached the number of posts equal to
7099 * the warning/blocking setting, or false if there is no warning to show.
7101 * @param int|stdClass $forum the forum id or the forum object
7102 * @param stdClass $cm the course module
7103 * @return stdClass|bool returns an object with the warning information, else
7104 * returns false if no warning is required.
7106 function forum_check_throttling($forum, $cm = null) {
7107 global $CFG, $DB, $USER;
7109 if (is_numeric($forum)) {
7110 $forum = $DB->get_record('forum', array('id' => $forum), '*', MUST_EXIST);
7113 if (!is_object($forum)) {
7114 return false; // This is broken.
7117 if (!$cm) {
7118 $cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course, false, MUST_EXIST);
7121 if (empty($forum->blockafter)) {
7122 return false;
7125 if (empty($forum->blockperiod)) {
7126 return false;
7129 $modcontext = context_module::instance($cm->id);
7130 if (has_capability('mod/forum:postwithoutthrottling', $modcontext)) {
7131 return false;
7134 // Get the number of posts in the last period we care about.
7135 $timenow = time();
7136 $timeafter = $timenow - $forum->blockperiod;
7137 $numposts = $DB->count_records_sql('SELECT COUNT(p.id) FROM {forum_posts} p
7138 JOIN {forum_discussions} d
7139 ON p.discussion = d.id WHERE d.forum = ?
7140 AND p.userid = ? AND p.created > ?', array($forum->id, $USER->id, $timeafter));
7142 $a = new stdClass();
7143 $a->blockafter = $forum->blockafter;
7144 $a->numposts = $numposts;
7145 $a->blockperiod = get_string('secondstotime'.$forum->blockperiod);
7147 if ($forum->blockafter <= $numposts) {
7148 $warning = new stdClass();
7149 $warning->canpost = false;
7150 $warning->errorcode = 'forumblockingtoomanyposts';
7151 $warning->module = 'error';
7152 $warning->additional = $a;
7153 $warning->link = $CFG->wwwroot . '/mod/forum/view.php?f=' . $forum->id;
7155 return $warning;
7158 if ($forum->warnafter <= $numposts) {
7159 $warning = new stdClass();
7160 $warning->canpost = true;
7161 $warning->errorcode = 'forumblockingalmosttoomanyposts';
7162 $warning->module = 'forum';
7163 $warning->additional = $a;
7164 $warning->link = null;
7166 return $warning;
7171 * Throws an error if the user is no longer allowed to post due to having reached
7172 * or exceeded the number of posts specified in 'Post threshold for blocking'
7173 * setting.
7175 * @since Moodle 2.5
7176 * @param stdClass $thresholdwarning the warning information returned
7177 * from the function forum_check_throttling.
7179 function forum_check_blocking_threshold($thresholdwarning) {
7180 if (!empty($thresholdwarning) && !$thresholdwarning->canpost) {
7181 print_error($thresholdwarning->errorcode,
7182 $thresholdwarning->module,
7183 $thresholdwarning->link,
7184 $thresholdwarning->additional);
7190 * Removes all grades from gradebook
7192 * @global object
7193 * @global object
7194 * @param int $courseid
7195 * @param string $type optional
7197 function forum_reset_gradebook($courseid, $type='') {
7198 global $CFG, $DB;
7200 $wheresql = '';
7201 $params = array($courseid);
7202 if ($type) {
7203 $wheresql = "AND f.type=?";
7204 $params[] = $type;
7207 $sql = "SELECT f.*, cm.idnumber as cmidnumber, f.course as courseid
7208 FROM {forum} f, {course_modules} cm, {modules} m
7209 WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id AND f.course=? $wheresql";
7211 if ($forums = $DB->get_records_sql($sql, $params)) {
7212 foreach ($forums as $forum) {
7213 forum_grade_item_update($forum, 'reset');
7219 * This function is used by the reset_course_userdata function in moodlelib.
7220 * This function will remove all posts from the specified forum
7221 * and clean up any related data.
7223 * @global object
7224 * @global object
7225 * @param $data the data submitted from the reset course.
7226 * @return array status array
7228 function forum_reset_userdata($data) {
7229 global $CFG, $DB;
7230 require_once($CFG->dirroot.'/rating/lib.php');
7232 $componentstr = get_string('modulenameplural', 'forum');
7233 $status = array();
7235 $params = array($data->courseid);
7237 $removeposts = false;
7238 $typesql = "";
7239 if (!empty($data->reset_forum_all)) {
7240 $removeposts = true;
7241 $typesstr = get_string('resetforumsall', 'forum');
7242 $types = array();
7243 } else if (!empty($data->reset_forum_types)){
7244 $removeposts = true;
7245 $typesql = "";
7246 $types = array();
7247 $forum_types_all = forum_get_forum_types_all();
7248 foreach ($data->reset_forum_types as $type) {
7249 if (!array_key_exists($type, $forum_types_all)) {
7250 continue;
7252 $typesql .= " AND f.type=?";
7253 $types[] = $forum_types_all[$type];
7254 $params[] = $type;
7256 $typesstr = get_string('resetforums', 'forum').': '.implode(', ', $types);
7258 $alldiscussionssql = "SELECT fd.id
7259 FROM {forum_discussions} fd, {forum} f
7260 WHERE f.course=? AND f.id=fd.forum";
7262 $allforumssql = "SELECT f.id
7263 FROM {forum} f
7264 WHERE f.course=?";
7266 $allpostssql = "SELECT fp.id
7267 FROM {forum_posts} fp, {forum_discussions} fd, {forum} f
7268 WHERE f.course=? AND f.id=fd.forum AND fd.id=fp.discussion";
7270 $forumssql = $forums = $rm = null;
7272 if( $removeposts || !empty($data->reset_forum_ratings) ) {
7273 $forumssql = "$allforumssql $typesql";
7274 $forums = $forums = $DB->get_records_sql($forumssql, $params);
7275 $rm = new rating_manager();
7276 $ratingdeloptions = new stdClass;
7277 $ratingdeloptions->component = 'mod_forum';
7278 $ratingdeloptions->ratingarea = 'post';
7281 if ($removeposts) {
7282 $discussionssql = "$alldiscussionssql $typesql";
7283 $postssql = "$allpostssql $typesql";
7285 // now get rid of all attachments
7286 $fs = get_file_storage();
7287 if ($forums) {
7288 foreach ($forums as $forumid=>$unused) {
7289 if (!$cm = get_coursemodule_from_instance('forum', $forumid)) {
7290 continue;
7292 $context = context_module::instance($cm->id);
7293 $fs->delete_area_files($context->id, 'mod_forum', 'attachment');
7294 $fs->delete_area_files($context->id, 'mod_forum', 'post');
7296 //remove ratings
7297 $ratingdeloptions->contextid = $context->id;
7298 $rm->delete_ratings($ratingdeloptions);
7302 // first delete all read flags
7303 $DB->delete_records_select('forum_read', "forumid IN ($forumssql)", $params);
7305 // remove tracking prefs
7306 $DB->delete_records_select('forum_track_prefs', "forumid IN ($forumssql)", $params);
7308 // remove posts from queue
7309 $DB->delete_records_select('forum_queue', "discussionid IN ($discussionssql)", $params);
7311 // all posts - initial posts must be kept in single simple discussion forums
7312 $DB->delete_records_select('forum_posts', "discussion IN ($discussionssql) AND parent <> 0", $params); // first all children
7313 $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
7315 // finally all discussions except single simple forums
7316 $DB->delete_records_select('forum_discussions', "forum IN ($forumssql AND f.type <> 'single')", $params);
7318 // remove all grades from gradebook
7319 if (empty($data->reset_gradebook_grades)) {
7320 if (empty($types)) {
7321 forum_reset_gradebook($data->courseid);
7322 } else {
7323 foreach ($types as $type) {
7324 forum_reset_gradebook($data->courseid, $type);
7329 $status[] = array('component'=>$componentstr, 'item'=>$typesstr, 'error'=>false);
7332 // remove all ratings in this course's forums
7333 if (!empty($data->reset_forum_ratings)) {
7334 if ($forums) {
7335 foreach ($forums as $forumid=>$unused) {
7336 if (!$cm = get_coursemodule_from_instance('forum', $forumid)) {
7337 continue;
7339 $context = context_module::instance($cm->id);
7341 //remove ratings
7342 $ratingdeloptions->contextid = $context->id;
7343 $rm->delete_ratings($ratingdeloptions);
7347 // remove all grades from gradebook
7348 if (empty($data->reset_gradebook_grades)) {
7349 forum_reset_gradebook($data->courseid);
7353 // remove all digest settings unconditionally - even for users still enrolled in course.
7354 if (!empty($data->reset_forum_digests)) {
7355 $DB->delete_records_select('forum_digests', "forum IN ($allforumssql)", $params);
7356 $status[] = array('component' => $componentstr, 'item' => get_string('resetdigests', 'forum'), 'error' => false);
7359 // remove all subscriptions unconditionally - even for users still enrolled in course
7360 if (!empty($data->reset_forum_subscriptions)) {
7361 $DB->delete_records_select('forum_subscriptions', "forum IN ($allforumssql)", $params);
7362 $status[] = array('component'=>$componentstr, 'item'=>get_string('resetsubscriptions','forum'), 'error'=>false);
7365 // remove all tracking prefs unconditionally - even for users still enrolled in course
7366 if (!empty($data->reset_forum_track_prefs)) {
7367 $DB->delete_records_select('forum_track_prefs', "forumid IN ($allforumssql)", $params);
7368 $status[] = array('component'=>$componentstr, 'item'=>get_string('resettrackprefs','forum'), 'error'=>false);
7371 /// updating dates - shift may be negative too
7372 if ($data->timeshift) {
7373 shift_course_mod_dates('forum', array('assesstimestart', 'assesstimefinish'), $data->timeshift, $data->courseid);
7374 $status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged'), 'error'=>false);
7377 return $status;
7381 * Called by course/reset.php
7383 * @param $mform form passed by reference
7385 function forum_reset_course_form_definition(&$mform) {
7386 $mform->addElement('header', 'forumheader', get_string('modulenameplural', 'forum'));
7388 $mform->addElement('checkbox', 'reset_forum_all', get_string('resetforumsall','forum'));
7390 $mform->addElement('select', 'reset_forum_types', get_string('resetforums', 'forum'), forum_get_forum_types_all(), array('multiple' => 'multiple'));
7391 $mform->setAdvanced('reset_forum_types');
7392 $mform->disabledIf('reset_forum_types', 'reset_forum_all', 'checked');
7394 $mform->addElement('checkbox', 'reset_forum_digests', get_string('resetdigests','forum'));
7395 $mform->setAdvanced('reset_forum_digests');
7397 $mform->addElement('checkbox', 'reset_forum_subscriptions', get_string('resetsubscriptions','forum'));
7398 $mform->setAdvanced('reset_forum_subscriptions');
7400 $mform->addElement('checkbox', 'reset_forum_track_prefs', get_string('resettrackprefs','forum'));
7401 $mform->setAdvanced('reset_forum_track_prefs');
7402 $mform->disabledIf('reset_forum_track_prefs', 'reset_forum_all', 'checked');
7404 $mform->addElement('checkbox', 'reset_forum_ratings', get_string('deleteallratings'));
7405 $mform->disabledIf('reset_forum_ratings', 'reset_forum_all', 'checked');
7409 * Course reset form defaults.
7410 * @return array
7412 function forum_reset_course_form_defaults($course) {
7413 return array('reset_forum_all'=>1, 'reset_forum_digests' => 0, 'reset_forum_subscriptions'=>0, 'reset_forum_track_prefs'=>0, 'reset_forum_ratings'=>1);
7417 * Converts a forum to use the Roles System
7419 * @global object
7420 * @global object
7421 * @param object $forum a forum object with the same attributes as a record
7422 * from the forum database table
7423 * @param int $forummodid the id of the forum module, from the modules table
7424 * @param array $teacherroles array of roles that have archetype teacher
7425 * @param array $studentroles array of roles that have archetype student
7426 * @param array $guestroles array of roles that have archetype guest
7427 * @param int $cmid the course_module id for this forum instance
7428 * @return boolean forum was converted or not
7430 function forum_convert_to_roles($forum, $forummodid, $teacherroles=array(),
7431 $studentroles=array(), $guestroles=array(), $cmid=NULL) {
7433 global $CFG, $DB, $OUTPUT;
7435 if (!isset($forum->open) && !isset($forum->assesspublic)) {
7436 // We assume that this forum has already been converted to use the
7437 // Roles System. Columns forum.open and forum.assesspublic get dropped
7438 // once the forum module has been upgraded to use Roles.
7439 return false;
7442 if ($forum->type == 'teacher') {
7444 // Teacher forums should be converted to normal forums that
7445 // use the Roles System to implement the old behavior.
7446 // Note:
7447 // Seems that teacher forums were never backed up in 1.6 since they
7448 // didn't have an entry in the course_modules table.
7449 require_once($CFG->dirroot.'/course/lib.php');
7451 if ($DB->count_records('forum_discussions', array('forum' => $forum->id)) == 0) {
7452 // Delete empty teacher forums.
7453 $DB->delete_records('forum', array('id' => $forum->id));
7454 } else {
7455 // Create a course module for the forum and assign it to
7456 // section 0 in the course.
7457 $mod = new stdClass();
7458 $mod->course = $forum->course;
7459 $mod->module = $forummodid;
7460 $mod->instance = $forum->id;
7461 $mod->section = 0;
7462 $mod->visible = 0; // Hide the forum
7463 $mod->visibleold = 0; // Hide the forum
7464 $mod->groupmode = 0;
7466 if (!$cmid = add_course_module($mod)) {
7467 print_error('cannotcreateinstanceforteacher', 'forum');
7468 } else {
7469 $sectionid = course_add_cm_to_section($forum->course, $mod->coursemodule, 0);
7472 // Change the forum type to general.
7473 $forum->type = 'general';
7474 $DB->update_record('forum', $forum);
7476 $context = context_module::instance($cmid);
7478 // Create overrides for default student and guest roles (prevent).
7479 foreach ($studentroles as $studentrole) {
7480 assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
7481 assign_capability('mod/forum:viewhiddentimedposts', CAP_PREVENT, $studentrole->id, $context->id);
7482 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
7483 assign_capability('mod/forum:replypost', CAP_PREVENT, $studentrole->id, $context->id);
7484 assign_capability('mod/forum:viewrating', CAP_PREVENT, $studentrole->id, $context->id);
7485 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $studentrole->id, $context->id);
7486 assign_capability('mod/forum:rate', CAP_PREVENT, $studentrole->id, $context->id);
7487 assign_capability('mod/forum:createattachment', CAP_PREVENT, $studentrole->id, $context->id);
7488 assign_capability('mod/forum:deleteownpost', CAP_PREVENT, $studentrole->id, $context->id);
7489 assign_capability('mod/forum:deleteanypost', CAP_PREVENT, $studentrole->id, $context->id);
7490 assign_capability('mod/forum:splitdiscussions', CAP_PREVENT, $studentrole->id, $context->id);
7491 assign_capability('mod/forum:movediscussions', CAP_PREVENT, $studentrole->id, $context->id);
7492 assign_capability('mod/forum:editanypost', CAP_PREVENT, $studentrole->id, $context->id);
7493 assign_capability('mod/forum:viewqandawithoutposting', CAP_PREVENT, $studentrole->id, $context->id);
7494 assign_capability('mod/forum:viewsubscribers', CAP_PREVENT, $studentrole->id, $context->id);
7495 assign_capability('mod/forum:managesubscriptions', CAP_PREVENT, $studentrole->id, $context->id);
7496 assign_capability('mod/forum:postwithoutthrottling', CAP_PREVENT, $studentrole->id, $context->id);
7498 foreach ($guestroles as $guestrole) {
7499 assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $guestrole->id, $context->id);
7500 assign_capability('mod/forum:viewhiddentimedposts', CAP_PREVENT, $guestrole->id, $context->id);
7501 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $guestrole->id, $context->id);
7502 assign_capability('mod/forum:replypost', CAP_PREVENT, $guestrole->id, $context->id);
7503 assign_capability('mod/forum:viewrating', CAP_PREVENT, $guestrole->id, $context->id);
7504 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $guestrole->id, $context->id);
7505 assign_capability('mod/forum:rate', CAP_PREVENT, $guestrole->id, $context->id);
7506 assign_capability('mod/forum:createattachment', CAP_PREVENT, $guestrole->id, $context->id);
7507 assign_capability('mod/forum:deleteownpost', CAP_PREVENT, $guestrole->id, $context->id);
7508 assign_capability('mod/forum:deleteanypost', CAP_PREVENT, $guestrole->id, $context->id);
7509 assign_capability('mod/forum:splitdiscussions', CAP_PREVENT, $guestrole->id, $context->id);
7510 assign_capability('mod/forum:movediscussions', CAP_PREVENT, $guestrole->id, $context->id);
7511 assign_capability('mod/forum:editanypost', CAP_PREVENT, $guestrole->id, $context->id);
7512 assign_capability('mod/forum:viewqandawithoutposting', CAP_PREVENT, $guestrole->id, $context->id);
7513 assign_capability('mod/forum:viewsubscribers', CAP_PREVENT, $guestrole->id, $context->id);
7514 assign_capability('mod/forum:managesubscriptions', CAP_PREVENT, $guestrole->id, $context->id);
7515 assign_capability('mod/forum:postwithoutthrottling', CAP_PREVENT, $guestrole->id, $context->id);
7518 } else {
7519 // Non-teacher forum.
7521 if (empty($cmid)) {
7522 // We were not given the course_module id. Try to find it.
7523 if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
7524 echo $OUTPUT->notification('Could not get the course module for the forum');
7525 return false;
7526 } else {
7527 $cmid = $cm->id;
7530 $context = context_module::instance($cmid);
7532 // $forum->open defines what students can do:
7533 // 0 = No discussions, no replies
7534 // 1 = No discussions, but replies are allowed
7535 // 2 = Discussions and replies are allowed
7536 switch ($forum->open) {
7537 case 0:
7538 foreach ($studentroles as $studentrole) {
7539 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
7540 assign_capability('mod/forum:replypost', CAP_PREVENT, $studentrole->id, $context->id);
7542 break;
7543 case 1:
7544 foreach ($studentroles as $studentrole) {
7545 assign_capability('mod/forum:startdiscussion', CAP_PREVENT, $studentrole->id, $context->id);
7546 assign_capability('mod/forum:replypost', CAP_ALLOW, $studentrole->id, $context->id);
7548 break;
7549 case 2:
7550 foreach ($studentroles as $studentrole) {
7551 assign_capability('mod/forum:startdiscussion', CAP_ALLOW, $studentrole->id, $context->id);
7552 assign_capability('mod/forum:replypost', CAP_ALLOW, $studentrole->id, $context->id);
7554 break;
7557 // $forum->assessed defines whether forum rating is turned
7558 // on (1 or 2) and who can rate posts:
7559 // 1 = Everyone can rate posts
7560 // 2 = Only teachers can rate posts
7561 switch ($forum->assessed) {
7562 case 1:
7563 foreach ($studentroles as $studentrole) {
7564 assign_capability('mod/forum:rate', CAP_ALLOW, $studentrole->id, $context->id);
7566 foreach ($teacherroles as $teacherrole) {
7567 assign_capability('mod/forum:rate', CAP_ALLOW, $teacherrole->id, $context->id);
7569 break;
7570 case 2:
7571 foreach ($studentroles as $studentrole) {
7572 assign_capability('mod/forum:rate', CAP_PREVENT, $studentrole->id, $context->id);
7574 foreach ($teacherroles as $teacherrole) {
7575 assign_capability('mod/forum:rate', CAP_ALLOW, $teacherrole->id, $context->id);
7577 break;
7580 // $forum->assesspublic defines whether students can see
7581 // everybody's ratings:
7582 // 0 = Students can only see their own ratings
7583 // 1 = Students can see everyone's ratings
7584 switch ($forum->assesspublic) {
7585 case 0:
7586 foreach ($studentroles as $studentrole) {
7587 assign_capability('mod/forum:viewanyrating', CAP_PREVENT, $studentrole->id, $context->id);
7589 foreach ($teacherroles as $teacherrole) {
7590 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $teacherrole->id, $context->id);
7592 break;
7593 case 1:
7594 foreach ($studentroles as $studentrole) {
7595 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $studentrole->id, $context->id);
7597 foreach ($teacherroles as $teacherrole) {
7598 assign_capability('mod/forum:viewanyrating', CAP_ALLOW, $teacherrole->id, $context->id);
7600 break;
7603 if (empty($cm)) {
7604 $cm = $DB->get_record('course_modules', array('id' => $cmid));
7607 // $cm->groupmode:
7608 // 0 - No groups
7609 // 1 - Separate groups
7610 // 2 - Visible groups
7611 switch ($cm->groupmode) {
7612 case 0:
7613 break;
7614 case 1:
7615 foreach ($studentroles as $studentrole) {
7616 assign_capability('moodle/site:accessallgroups', CAP_PREVENT, $studentrole->id, $context->id);
7618 foreach ($teacherroles as $teacherrole) {
7619 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
7621 break;
7622 case 2:
7623 foreach ($studentroles as $studentrole) {
7624 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $studentrole->id, $context->id);
7626 foreach ($teacherroles as $teacherrole) {
7627 assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $teacherrole->id, $context->id);
7629 break;
7632 return true;
7636 * Returns array of forum layout modes
7638 * @return array
7640 function forum_get_layout_modes() {
7641 return array (FORUM_MODE_FLATOLDEST => get_string('modeflatoldestfirst', 'forum'),
7642 FORUM_MODE_FLATNEWEST => get_string('modeflatnewestfirst', 'forum'),
7643 FORUM_MODE_THREADED => get_string('modethreaded', 'forum'),
7644 FORUM_MODE_NESTED => get_string('modenested', 'forum'));
7648 * Returns array of forum types chooseable on the forum editing form
7650 * @return array
7652 function forum_get_forum_types() {
7653 return array ('general' => get_string('generalforum', 'forum'),
7654 'eachuser' => get_string('eachuserforum', 'forum'),
7655 'single' => get_string('singleforum', 'forum'),
7656 'qanda' => get_string('qandaforum', 'forum'),
7657 'blog' => get_string('blogforum', 'forum'));
7661 * Returns array of all forum layout modes
7663 * @return array
7665 function forum_get_forum_types_all() {
7666 return array ('news' => get_string('namenews','forum'),
7667 'social' => get_string('namesocial','forum'),
7668 'general' => get_string('generalforum', 'forum'),
7669 'eachuser' => get_string('eachuserforum', 'forum'),
7670 'single' => get_string('singleforum', 'forum'),
7671 'qanda' => get_string('qandaforum', 'forum'),
7672 'blog' => get_string('blogforum', 'forum'));
7676 * Returns array of forum open modes
7678 * @return array
7680 function forum_get_open_modes() {
7681 return array ('2' => get_string('openmode2', 'forum'),
7682 '1' => get_string('openmode1', 'forum'),
7683 '0' => get_string('openmode0', 'forum') );
7687 * Returns all other caps used in module
7689 * @return array
7691 function forum_get_extra_capabilities() {
7692 return array('moodle/site:accessallgroups', 'moodle/site:viewfullnames', 'moodle/site:trustcontent', 'moodle/rating:view', 'moodle/rating:viewany', 'moodle/rating:viewall', 'moodle/rating:rate');
7696 * Adds module specific settings to the settings block
7698 * @param settings_navigation $settings The settings navigation object
7699 * @param navigation_node $forumnode The node to add module settings to
7701 function forum_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $forumnode) {
7702 global $USER, $PAGE, $CFG, $DB, $OUTPUT;
7704 $forumobject = $DB->get_record("forum", array("id" => $PAGE->cm->instance));
7705 if (empty($PAGE->cm->context)) {
7706 $PAGE->cm->context = context_module::instance($PAGE->cm->instance);
7709 // for some actions you need to be enrolled, beiing admin is not enough sometimes here
7710 $enrolled = is_enrolled($PAGE->cm->context, $USER, '', false);
7711 $activeenrolled = is_enrolled($PAGE->cm->context, $USER, '', true);
7713 $canmanage = has_capability('mod/forum:managesubscriptions', $PAGE->cm->context);
7714 $subscriptionmode = forum_get_forcesubscribed($forumobject);
7715 $cansubscribe = ($activeenrolled && $subscriptionmode != FORUM_FORCESUBSCRIBE && ($subscriptionmode != FORUM_DISALLOWSUBSCRIBE || $canmanage));
7717 if ($canmanage) {
7718 $mode = $forumnode->add(get_string('subscriptionmode', 'forum'), null, navigation_node::TYPE_CONTAINER);
7720 $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);
7721 $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);
7722 $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);
7723 $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);
7725 switch ($subscriptionmode) {
7726 case FORUM_CHOOSESUBSCRIBE : // 0
7727 $allowchoice->action = null;
7728 $allowchoice->add_class('activesetting');
7729 break;
7730 case FORUM_FORCESUBSCRIBE : // 1
7731 $forceforever->action = null;
7732 $forceforever->add_class('activesetting');
7733 break;
7734 case FORUM_INITIALSUBSCRIBE : // 2
7735 $forceinitially->action = null;
7736 $forceinitially->add_class('activesetting');
7737 break;
7738 case FORUM_DISALLOWSUBSCRIBE : // 3
7739 $disallowchoice->action = null;
7740 $disallowchoice->add_class('activesetting');
7741 break;
7744 } else if ($activeenrolled) {
7746 switch ($subscriptionmode) {
7747 case FORUM_CHOOSESUBSCRIBE : // 0
7748 $notenode = $forumnode->add(get_string('subscriptionoptional', 'forum'));
7749 break;
7750 case FORUM_FORCESUBSCRIBE : // 1
7751 $notenode = $forumnode->add(get_string('subscriptionforced', 'forum'));
7752 break;
7753 case FORUM_INITIALSUBSCRIBE : // 2
7754 $notenode = $forumnode->add(get_string('subscriptionauto', 'forum'));
7755 break;
7756 case FORUM_DISALLOWSUBSCRIBE : // 3
7757 $notenode = $forumnode->add(get_string('subscriptiondisabled', 'forum'));
7758 break;
7762 if ($cansubscribe) {
7763 if (forum_is_subscribed($USER->id, $forumobject)) {
7764 $linktext = get_string('unsubscribe', 'forum');
7765 } else {
7766 $linktext = get_string('subscribe', 'forum');
7768 $url = new moodle_url('/mod/forum/subscribe.php', array('id'=>$forumobject->id, 'sesskey'=>sesskey()));
7769 $forumnode->add($linktext, $url, navigation_node::TYPE_SETTING);
7772 if (has_capability('mod/forum:viewsubscribers', $PAGE->cm->context)){
7773 $url = new moodle_url('/mod/forum/subscribers.php', array('id'=>$forumobject->id));
7774 $forumnode->add(get_string('showsubscribers', 'forum'), $url, navigation_node::TYPE_SETTING);
7777 if ($enrolled && forum_tp_can_track_forums($forumobject)) { // keep tracking info for users with suspended enrolments
7778 if ($forumobject->trackingtype != FORUM_TRACKING_OPTIONAL) {
7779 //tracking forced on or off in forum settings so dont provide a link here to change it
7780 //could add unclickable text like for forced subscription but not sure this justifies adding another menu item
7781 } else {
7782 if (forum_tp_is_tracked($forumobject)) {
7783 $linktext = get_string('notrackforum', 'forum');
7784 } else {
7785 $linktext = get_string('trackforum', 'forum');
7787 $url = new moodle_url('/mod/forum/settracking.php', array('id'=>$forumobject->id));
7788 $forumnode->add($linktext, $url, navigation_node::TYPE_SETTING);
7792 if (!isloggedin() && $PAGE->course->id == SITEID) {
7793 $userid = guest_user()->id;
7794 } else {
7795 $userid = $USER->id;
7798 $hascourseaccess = ($PAGE->course->id == SITEID) || can_access_course($PAGE->course, $userid);
7799 $enablerssfeeds = !empty($CFG->enablerssfeeds) && !empty($CFG->forum_enablerssfeeds);
7801 if ($enablerssfeeds && $forumobject->rsstype && $forumobject->rssarticles && $hascourseaccess) {
7803 if (!function_exists('rss_get_url')) {
7804 require_once("$CFG->libdir/rsslib.php");
7807 if ($forumobject->rsstype == 1) {
7808 $string = get_string('rsssubscriberssdiscussions','forum');
7809 } else {
7810 $string = get_string('rsssubscriberssposts','forum');
7813 $url = new moodle_url(rss_get_url($PAGE->cm->context->id, $userid, "mod_forum", $forumobject->id));
7814 $forumnode->add($string, $url, settings_navigation::TYPE_SETTING, null, null, new pix_icon('i/rss', ''));
7819 * Abstract class used by forum subscriber selection controls
7820 * @package mod-forum
7821 * @copyright 2009 Sam Hemelryk
7822 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7824 abstract class forum_subscriber_selector_base extends user_selector_base {
7827 * The id of the forum this selector is being used for
7828 * @var int
7830 protected $forumid = null;
7832 * The context of the forum this selector is being used for
7833 * @var object
7835 protected $context = null;
7837 * The id of the current group
7838 * @var int
7840 protected $currentgroup = null;
7843 * Constructor method
7844 * @param string $name
7845 * @param array $options
7847 public function __construct($name, $options) {
7848 $options['accesscontext'] = $options['context'];
7849 parent::__construct($name, $options);
7850 if (isset($options['context'])) {
7851 $this->context = $options['context'];
7853 if (isset($options['currentgroup'])) {
7854 $this->currentgroup = $options['currentgroup'];
7856 if (isset($options['forumid'])) {
7857 $this->forumid = $options['forumid'];
7862 * Returns an array of options to seralise and store for searches
7864 * @return array
7866 protected function get_options() {
7867 global $CFG;
7868 $options = parent::get_options();
7869 $options['file'] = substr(__FILE__, strlen($CFG->dirroot.'/'));
7870 $options['context'] = $this->context;
7871 $options['currentgroup'] = $this->currentgroup;
7872 $options['forumid'] = $this->forumid;
7873 return $options;
7879 * A user selector control for potential subscribers to the selected forum
7880 * @package mod-forum
7881 * @copyright 2009 Sam Hemelryk
7882 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7884 class forum_potential_subscriber_selector extends forum_subscriber_selector_base {
7886 * If set to true EVERYONE in this course is force subscribed to this forum
7887 * @var bool
7889 protected $forcesubscribed = false;
7891 * Can be used to store existing subscribers so that they can be removed from
7892 * the potential subscribers list
7894 protected $existingsubscribers = array();
7897 * Constructor method
7898 * @param string $name
7899 * @param array $options
7901 public function __construct($name, $options) {
7902 parent::__construct($name, $options);
7903 if (isset($options['forcesubscribed'])) {
7904 $this->forcesubscribed=true;
7909 * Returns an arary of options for this control
7910 * @return array
7912 protected function get_options() {
7913 $options = parent::get_options();
7914 if ($this->forcesubscribed===true) {
7915 $options['forcesubscribed']=1;
7917 return $options;
7921 * Finds all potential users
7923 * Potential subscribers are all enroled users who are not already subscribed.
7925 * @param string $search
7926 * @return array
7928 public function find_users($search) {
7929 global $DB;
7931 $whereconditions = array();
7932 list($wherecondition, $params) = $this->search_sql($search, 'u');
7933 if ($wherecondition) {
7934 $whereconditions[] = $wherecondition;
7937 if (!$this->forcesubscribed) {
7938 $existingids = array();
7939 foreach ($this->existingsubscribers as $group) {
7940 foreach ($group as $user) {
7941 $existingids[$user->id] = 1;
7944 if ($existingids) {
7945 list($usertest, $userparams) = $DB->get_in_or_equal(
7946 array_keys($existingids), SQL_PARAMS_NAMED, 'existing', false);
7947 $whereconditions[] = 'u.id ' . $usertest;
7948 $params = array_merge($params, $userparams);
7952 if ($whereconditions) {
7953 $wherecondition = 'WHERE ' . implode(' AND ', $whereconditions);
7956 list($esql, $eparams) = get_enrolled_sql($this->context, '', $this->currentgroup, true);
7957 $params = array_merge($params, $eparams);
7959 $fields = 'SELECT ' . $this->required_fields_sql('u');
7960 $countfields = 'SELECT COUNT(u.id)';
7962 $sql = " FROM {user} u
7963 JOIN ($esql) je ON je.id = u.id
7964 $wherecondition";
7966 list($sort, $sortparams) = users_order_by_sql('u', $search, $this->accesscontext);
7967 $order = ' ORDER BY ' . $sort;
7969 // Check to see if there are too many to show sensibly.
7970 if (!$this->is_validating()) {
7971 $potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params);
7972 if ($potentialmemberscount > $this->maxusersperpage) {
7973 return $this->too_many_results($search, $potentialmemberscount);
7977 // If not, show them.
7978 $availableusers = $DB->get_records_sql($fields . $sql . $order, array_merge($params, $sortparams));
7980 if (empty($availableusers)) {
7981 return array();
7984 if ($this->forcesubscribed) {
7985 return array(get_string("existingsubscribers", 'forum') => $availableusers);
7986 } else {
7987 return array(get_string("potentialsubscribers", 'forum') => $availableusers);
7992 * Sets the existing subscribers
7993 * @param array $users
7995 public function set_existing_subscribers(array $users) {
7996 $this->existingsubscribers = $users;
8000 * Sets this forum as force subscribed or not
8002 public function set_force_subscribed($setting=true) {
8003 $this->forcesubscribed = true;
8008 * User selector control for removing subscribed users
8009 * @package mod-forum
8010 * @copyright 2009 Sam Hemelryk
8011 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8013 class forum_existing_subscriber_selector extends forum_subscriber_selector_base {
8016 * Finds all subscribed users
8018 * @param string $search
8019 * @return array
8021 public function find_users($search) {
8022 global $DB;
8023 list($wherecondition, $params) = $this->search_sql($search, 'u');
8024 $params['forumid'] = $this->forumid;
8026 // only active enrolled or everybody on the frontpage
8027 list($esql, $eparams) = get_enrolled_sql($this->context, '', $this->currentgroup, true);
8028 $fields = $this->required_fields_sql('u');
8029 list($sort, $sortparams) = users_order_by_sql('u', $search, $this->accesscontext);
8030 $params = array_merge($params, $eparams, $sortparams);
8032 $subscribers = $DB->get_records_sql("SELECT $fields
8033 FROM {user} u
8034 JOIN ($esql) je ON je.id = u.id
8035 JOIN {forum_subscriptions} s ON s.userid = u.id
8036 WHERE $wherecondition AND s.forum = :forumid
8037 ORDER BY $sort", $params);
8039 return array(get_string("existingsubscribers", 'forum') => $subscribers);
8045 * Adds information about unread messages, that is only required for the course view page (and
8046 * similar), to the course-module object.
8047 * @param cm_info $cm Course-module object
8049 function forum_cm_info_view(cm_info $cm) {
8050 global $CFG;
8052 // Get tracking status (once per request)
8053 static $initialised;
8054 static $usetracking, $strunreadpostsone;
8055 if (!isset($initialised)) {
8056 if ($usetracking = forum_tp_can_track_forums()) {
8057 $strunreadpostsone = get_string('unreadpostsone', 'forum');
8059 $initialised = true;
8062 if ($usetracking) {
8063 if ($unread = forum_tp_count_forum_unread_posts($cm, $cm->get_course())) {
8064 $out = '<span class="unread"> <a href="' . $cm->get_url() . '">';
8065 if ($unread == 1) {
8066 $out .= $strunreadpostsone;
8067 } else {
8068 $out .= get_string('unreadpostsnumber', 'forum', $unread);
8070 $out .= '</a></span>';
8071 $cm->set_after_link($out);
8077 * Return a list of page types
8078 * @param string $pagetype current page type
8079 * @param stdClass $parentcontext Block's parent context
8080 * @param stdClass $currentcontext Current context of block
8082 function forum_page_type_list($pagetype, $parentcontext, $currentcontext) {
8083 $forum_pagetype = array(
8084 'mod-forum-*'=>get_string('page-mod-forum-x', 'forum'),
8085 'mod-forum-view'=>get_string('page-mod-forum-view', 'forum'),
8086 'mod-forum-discuss'=>get_string('page-mod-forum-discuss', 'forum')
8088 return $forum_pagetype;
8092 * Gets all of the courses where the provided user has posted in a forum.
8094 * @global moodle_database $DB The database connection
8095 * @param stdClass $user The user who's posts we are looking for
8096 * @param bool $discussionsonly If true only look for discussions started by the user
8097 * @param bool $includecontexts If set to trye contexts for the courses will be preloaded
8098 * @param int $limitfrom The offset of records to return
8099 * @param int $limitnum The number of records to return
8100 * @return array An array of courses
8102 function forum_get_courses_user_posted_in($user, $discussionsonly = false, $includecontexts = true, $limitfrom = null, $limitnum = null) {
8103 global $DB;
8105 // If we are only after discussions we need only look at the forum_discussions
8106 // table and join to the userid there. If we are looking for posts then we need
8107 // to join to the forum_posts table.
8108 if (!$discussionsonly) {
8109 $joinsql = 'JOIN {forum_discussions} fd ON fd.course = c.id
8110 JOIN {forum_posts} fp ON fp.discussion = fd.id';
8111 $wheresql = 'fp.userid = :userid';
8112 $params = array('userid' => $user->id);
8113 } else {
8114 $joinsql = 'JOIN {forum_discussions} fd ON fd.course = c.id';
8115 $wheresql = 'fd.userid = :userid';
8116 $params = array('userid' => $user->id);
8119 // Join to the context table so that we can preload contexts if required.
8120 if ($includecontexts) {
8121 $ctxselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
8122 $ctxjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)";
8123 $params['contextlevel'] = CONTEXT_COURSE;
8124 } else {
8125 $ctxselect = '';
8126 $ctxjoin = '';
8129 // Now we need to get all of the courses to search.
8130 // All courses where the user has posted within a forum will be returned.
8131 $sql = "SELECT DISTINCT c.* $ctxselect
8132 FROM {course} c
8133 $joinsql
8134 $ctxjoin
8135 WHERE $wheresql";
8136 $courses = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
8137 if ($includecontexts) {
8138 array_map('context_instance_preload', $courses);
8140 return $courses;
8144 * Gets all of the forums a user has posted in for one or more courses.
8146 * @global moodle_database $DB
8147 * @param stdClass $user
8148 * @param array $courseids An array of courseids to search or if not provided
8149 * all courses the user has posted within
8150 * @param bool $discussionsonly If true then only forums where the user has started
8151 * a discussion will be returned.
8152 * @param int $limitfrom The offset of records to return
8153 * @param int $limitnum The number of records to return
8154 * @return array An array of forums the user has posted within in the provided courses
8156 function forum_get_forums_user_posted_in($user, array $courseids = null, $discussionsonly = false, $limitfrom = null, $limitnum = null) {
8157 global $DB;
8159 if (!is_null($courseids)) {
8160 list($coursewhere, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED, 'courseid');
8161 $coursewhere = ' AND f.course '.$coursewhere;
8162 } else {
8163 $coursewhere = '';
8164 $params = array();
8166 $params['userid'] = $user->id;
8167 $params['forum'] = 'forum';
8169 if ($discussionsonly) {
8170 $join = 'JOIN {forum_discussions} ff ON ff.forum = f.id';
8171 } else {
8172 $join = 'JOIN {forum_discussions} fd ON fd.forum = f.id
8173 JOIN {forum_posts} ff ON ff.discussion = fd.id';
8176 $sql = "SELECT f.*, cm.id AS cmid
8177 FROM {forum} f
8178 JOIN {course_modules} cm ON cm.instance = f.id
8179 JOIN {modules} m ON m.id = cm.module
8180 JOIN (
8181 SELECT f.id
8182 FROM {forum} f
8183 {$join}
8184 WHERE ff.userid = :userid
8185 GROUP BY f.id
8186 ) j ON j.id = f.id
8187 WHERE m.name = :forum
8188 {$coursewhere}";
8190 $courseforums = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
8191 return $courseforums;
8195 * Returns posts made by the selected user in the requested courses.
8197 * This method can be used to return all of the posts made by the requested user
8198 * within the given courses.
8199 * For each course the access of the current user and requested user is checked
8200 * and then for each post access to the post and forum is checked as well.
8202 * This function is safe to use with usercapabilities.
8204 * @global moodle_database $DB
8205 * @param stdClass $user The user whose posts we want to get
8206 * @param array $courses The courses to search
8207 * @param bool $musthaveaccess If set to true errors will be thrown if the user
8208 * cannot access one or more of the courses to search
8209 * @param bool $discussionsonly If set to true only discussion starting posts
8210 * will be returned.
8211 * @param int $limitfrom The offset of records to return
8212 * @param int $limitnum The number of records to return
8213 * @return stdClass An object the following properties
8214 * ->totalcount: the total number of posts made by the requested user
8215 * that the current user can see.
8216 * ->courses: An array of courses the current user can see that the
8217 * requested user has posted in.
8218 * ->forums: An array of forums relating to the posts returned in the
8219 * property below.
8220 * ->posts: An array containing the posts to show for this request.
8222 function forum_get_posts_by_user($user, array $courses, $musthaveaccess = false, $discussionsonly = false, $limitfrom = 0, $limitnum = 50) {
8223 global $DB, $USER, $CFG;
8225 $return = new stdClass;
8226 $return->totalcount = 0; // The total number of posts that the current user is able to view
8227 $return->courses = array(); // The courses the current user can access
8228 $return->forums = array(); // The forums that the current user can access that contain posts
8229 $return->posts = array(); // The posts to display
8231 // First up a small sanity check. If there are no courses to check we can
8232 // return immediately, there is obviously nothing to search.
8233 if (empty($courses)) {
8234 return $return;
8237 // A couple of quick setups
8238 $isloggedin = isloggedin();
8239 $isguestuser = $isloggedin && isguestuser();
8240 $iscurrentuser = $isloggedin && $USER->id == $user->id;
8242 // Checkout whether or not the current user has capabilities over the requested
8243 // user and if so they have the capabilities required to view the requested
8244 // users content.
8245 $usercontext = context_user::instance($user->id, MUST_EXIST);
8246 $hascapsonuser = !$iscurrentuser && $DB->record_exists('role_assignments', array('userid' => $USER->id, 'contextid' => $usercontext->id));
8247 $hascapsonuser = $hascapsonuser && has_all_capabilities(array('moodle/user:viewdetails', 'moodle/user:readuserposts'), $usercontext);
8249 // Before we actually search each course we need to check the user's access to the
8250 // course. If the user doesn't have the appropraite access then we either throw an
8251 // error if a particular course was requested or we just skip over the course.
8252 foreach ($courses as $course) {
8253 $coursecontext = context_course::instance($course->id, MUST_EXIST);
8254 if ($iscurrentuser || $hascapsonuser) {
8255 // If it is the current user, or the current user has capabilities to the
8256 // requested user then all we need to do is check the requested users
8257 // current access to the course.
8258 // Note: There is no need to check group access or anything of the like
8259 // as either the current user is the requested user, or has granted
8260 // capabilities on the requested user. Either way they can see what the
8261 // requested user posted, although its VERY unlikely in the `parent` situation
8262 // that the current user will be able to view the posts in context.
8263 if (!is_viewing($coursecontext, $user) && !is_enrolled($coursecontext, $user)) {
8264 // Need to have full access to a course to see the rest of own info
8265 if ($musthaveaccess) {
8266 print_error('errorenrolmentrequired', 'forum');
8268 continue;
8270 } else {
8271 // Check whether the current user is enrolled or has access to view the course
8272 // if they don't we immediately have a problem.
8273 if (!can_access_course($course)) {
8274 if ($musthaveaccess) {
8275 print_error('errorenrolmentrequired', 'forum');
8277 continue;
8280 // Check whether the requested user is enrolled or has access to view the course
8281 // if they don't we immediately have a problem.
8282 if (!can_access_course($course, $user)) {
8283 if ($musthaveaccess) {
8284 print_error('notenrolled', 'forum');
8286 continue;
8289 // If groups are in use and enforced throughout the course then make sure
8290 // we can meet in at least one course level group.
8291 // Note that we check if either the current user or the requested user have
8292 // the capability to access all groups. This is because with that capability
8293 // a user in group A could post in the group B forum. Grrrr.
8294 if (groups_get_course_groupmode($course) == SEPARATEGROUPS && $course->groupmodeforce
8295 && !has_capability('moodle/site:accessallgroups', $coursecontext) && !has_capability('moodle/site:accessallgroups', $coursecontext, $user->id)) {
8296 // If its the guest user to bad... the guest user cannot access groups
8297 if (!$isloggedin or $isguestuser) {
8298 // do not use require_login() here because we might have already used require_login($course)
8299 if ($musthaveaccess) {
8300 redirect(get_login_url());
8302 continue;
8304 // Get the groups of the current user
8305 $mygroups = array_keys(groups_get_all_groups($course->id, $USER->id, $course->defaultgroupingid, 'g.id, g.name'));
8306 // Get the groups the requested user is a member of
8307 $usergroups = array_keys(groups_get_all_groups($course->id, $user->id, $course->defaultgroupingid, 'g.id, g.name'));
8308 // Check whether they are members of the same group. If they are great.
8309 $intersect = array_intersect($mygroups, $usergroups);
8310 if (empty($intersect)) {
8311 // But they're not... if it was a specific course throw an error otherwise
8312 // just skip this course so that it is not searched.
8313 if ($musthaveaccess) {
8314 print_error("groupnotamember", '', $CFG->wwwroot."/course/view.php?id=$course->id");
8316 continue;
8320 // Woo hoo we got this far which means the current user can search this
8321 // this course for the requested user. Although this is only the course accessibility
8322 // handling that is complete, the forum accessibility tests are yet to come.
8323 $return->courses[$course->id] = $course;
8325 // No longer beed $courses array - lose it not it may be big
8326 unset($courses);
8328 // Make sure that we have some courses to search
8329 if (empty($return->courses)) {
8330 // If we don't have any courses to search then the reality is that the current
8331 // user doesn't have access to any courses is which the requested user has posted.
8332 // Although we do know at this point that the requested user has posts.
8333 if ($musthaveaccess) {
8334 print_error('permissiondenied');
8335 } else {
8336 return $return;
8340 // Next step: Collect all of the forums that we will want to search.
8341 // It is important to note that this step isn't actually about searching, it is
8342 // about determining which forums we can search by testing accessibility.
8343 $forums = forum_get_forums_user_posted_in($user, array_keys($return->courses), $discussionsonly);
8345 // Will be used to build the where conditions for the search
8346 $forumsearchwhere = array();
8347 // Will be used to store the where condition params for the search
8348 $forumsearchparams = array();
8349 // Will record forums where the user can freely access everything
8350 $forumsearchfullaccess = array();
8351 // DB caching friendly
8352 $now = round(time(), -2);
8353 // For each course to search we want to find the forums the user has posted in
8354 // and providing the current user can access the forum create a search condition
8355 // for the forum to get the requested users posts.
8356 foreach ($return->courses as $course) {
8357 // Now we need to get the forums
8358 $modinfo = get_fast_modinfo($course);
8359 if (empty($modinfo->instances['forum'])) {
8360 // hmmm, no forums? well at least its easy... skip!
8361 continue;
8363 // Iterate
8364 foreach ($modinfo->get_instances_of('forum') as $forumid => $cm) {
8365 if (!$cm->uservisible or !isset($forums[$forumid])) {
8366 continue;
8368 // Get the forum in question
8369 $forum = $forums[$forumid];
8370 // This is needed for functionality later on in the forum code....
8371 $forum->cm = $cm;
8373 // Check that either the current user can view the forum, or that the
8374 // current user has capabilities over the requested user and the requested
8375 // user can view the discussion
8376 if (!has_capability('mod/forum:viewdiscussion', $cm->context) && !($hascapsonuser && has_capability('mod/forum:viewdiscussion', $cm->context, $user->id))) {
8377 continue;
8380 // This will contain forum specific where clauses
8381 $forumsearchselect = array();
8382 if (!$iscurrentuser && !$hascapsonuser) {
8383 // Make sure we check group access
8384 if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $cm->context)) {
8385 $groups = $modinfo->get_groups($cm->groupingid);
8386 $groups[] = -1;
8387 list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
8388 $forumsearchparams = array_merge($forumsearchparams, $groupid_params);
8389 $forumsearchselect[] = "d.groupid $groupid_sql";
8392 // hidden timed discussions
8393 if (!empty($CFG->forum_enabletimedposts) && !has_capability('mod/forum:viewhiddentimedposts', $cm->context)) {
8394 $forumsearchselect[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
8395 $forumsearchparams['userid'.$forumid] = $user->id;
8396 $forumsearchparams['timestart'.$forumid] = $now;
8397 $forumsearchparams['timeend'.$forumid] = $now;
8400 // qanda access
8401 if ($forum->type == 'qanda' && !has_capability('mod/forum:viewqandawithoutposting', $cm->context)) {
8402 // We need to check whether the user has posted in the qanda forum.
8403 $discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $user->id);
8404 if (!empty($discussionspostedin)) {
8405 $forumonlydiscussions = array(); // Holds discussion ids for the discussions the user is allowed to see in this forum.
8406 foreach ($discussionspostedin as $d) {
8407 $forumonlydiscussions[] = $d->id;
8409 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forumonlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
8410 $forumsearchparams = array_merge($forumsearchparams, $discussionid_params);
8411 $forumsearchselect[] = "(d.id $discussionid_sql OR p.parent = 0)";
8412 } else {
8413 $forumsearchselect[] = "p.parent = 0";
8418 if (count($forumsearchselect) > 0) {
8419 $forumsearchwhere[] = "(d.forum = :forum{$forumid} AND ".implode(" AND ", $forumsearchselect).")";
8420 $forumsearchparams['forum'.$forumid] = $forumid;
8421 } else {
8422 $forumsearchfullaccess[] = $forumid;
8424 } else {
8425 // The current user/parent can see all of their own posts
8426 $forumsearchfullaccess[] = $forumid;
8431 // If we dont have any search conditions, and we don't have any forums where
8432 // the user has full access then we just return the default.
8433 if (empty($forumsearchwhere) && empty($forumsearchfullaccess)) {
8434 return $return;
8437 // Prepare a where condition for the full access forums.
8438 if (count($forumsearchfullaccess) > 0) {
8439 list($fullidsql, $fullidparams) = $DB->get_in_or_equal($forumsearchfullaccess, SQL_PARAMS_NAMED, 'fula');
8440 $forumsearchparams = array_merge($forumsearchparams, $fullidparams);
8441 $forumsearchwhere[] = "(d.forum $fullidsql)";
8444 // Prepare SQL to both count and search.
8445 // We alias user.id to useridx because we forum_posts already has a userid field and not aliasing this would break
8446 // oracle and mssql.
8447 $userfields = user_picture::fields('u', null, 'useridx');
8448 $countsql = 'SELECT COUNT(*) ';
8449 $selectsql = 'SELECT p.*, d.forum, d.name AS discussionname, '.$userfields.' ';
8450 $wheresql = implode(" OR ", $forumsearchwhere);
8452 if ($discussionsonly) {
8453 if ($wheresql == '') {
8454 $wheresql = 'p.parent = 0';
8455 } else {
8456 $wheresql = 'p.parent = 0 AND ('.$wheresql.')';
8460 $sql = "FROM {forum_posts} p
8461 JOIN {forum_discussions} d ON d.id = p.discussion
8462 JOIN {user} u ON u.id = p.userid
8463 WHERE ($wheresql)
8464 AND p.userid = :userid ";
8465 $orderby = "ORDER BY p.modified DESC";
8466 $forumsearchparams['userid'] = $user->id;
8468 // Set the total number posts made by the requested user that the current user can see
8469 $return->totalcount = $DB->count_records_sql($countsql.$sql, $forumsearchparams);
8470 // Set the collection of posts that has been requested
8471 $return->posts = $DB->get_records_sql($selectsql.$sql.$orderby, $forumsearchparams, $limitfrom, $limitnum);
8473 // We need to build an array of forums for which posts will be displayed.
8474 // We do this here to save the caller needing to retrieve them themselves before
8475 // printing these forums posts. Given we have the forums already there is
8476 // practically no overhead here.
8477 foreach ($return->posts as $post) {
8478 if (!array_key_exists($post->forum, $return->forums)) {
8479 $return->forums[$post->forum] = $forums[$post->forum];
8483 return $return;
8487 * Set the per-forum maildigest option for the specified user.
8489 * @param stdClass $forum The forum to set the option for.
8490 * @param int $maildigest The maildigest option.
8491 * @param stdClass $user The user object. This defaults to the global $USER object.
8492 * @throws invalid_digest_setting thrown if an invalid maildigest option is provided.
8494 function forum_set_user_maildigest($forum, $maildigest, $user = null) {
8495 global $DB, $USER;
8497 if (is_number($forum)) {
8498 $forum = $DB->get_record('forum', array('id' => $forum));
8501 if ($user === null) {
8502 $user = $USER;
8505 $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
8506 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id, false, MUST_EXIST);
8507 $context = context_module::instance($cm->id);
8509 // User must be allowed to see this forum.
8510 require_capability('mod/forum:viewdiscussion', $context, $user->id);
8512 // Validate the maildigest setting.
8513 $digestoptions = forum_get_user_digest_options($user);
8515 if (!isset($digestoptions[$maildigest])) {
8516 throw new moodle_exception('invaliddigestsetting', 'mod_forum');
8519 // Attempt to retrieve any existing forum digest record.
8520 $subscription = $DB->get_record('forum_digests', array(
8521 'userid' => $user->id,
8522 'forum' => $forum->id,
8525 // Create or Update the existing maildigest setting.
8526 if ($subscription) {
8527 if ($maildigest == -1) {
8528 $DB->delete_records('forum_digests', array('forum' => $forum->id, 'userid' => $user->id));
8529 } else if ($maildigest !== $subscription->maildigest) {
8530 // Only update the maildigest setting if it's changed.
8532 $subscription->maildigest = $maildigest;
8533 $DB->update_record('forum_digests', $subscription);
8535 } else {
8536 if ($maildigest != -1) {
8537 // Only insert the maildigest setting if it's non-default.
8539 $subscription = new stdClass();
8540 $subscription->forum = $forum->id;
8541 $subscription->userid = $user->id;
8542 $subscription->maildigest = $maildigest;
8543 $subscription->id = $DB->insert_record('forum_digests', $subscription);
8547 return true;
8551 * Determine the maildigest setting for the specified user against the
8552 * specified forum.
8554 * @param Array $digests An array of forums and user digest settings.
8555 * @param stdClass $user The user object containing the id and maildigest default.
8556 * @param int $forumid The ID of the forum to check.
8557 * @return int The calculated maildigest setting for this user and forum.
8559 function forum_get_user_maildigest_bulk($digests, $user, $forumid) {
8560 if (isset($digests[$forumid]) && isset($digests[$forumid][$user->id])) {
8561 $maildigest = $digests[$forumid][$user->id];
8562 if ($maildigest === -1) {
8563 $maildigest = $user->maildigest;
8565 } else {
8566 $maildigest = $user->maildigest;
8568 return $maildigest;
8572 * Retrieve the list of available user digest options.
8574 * @param stdClass $user The user object. This defaults to the global $USER object.
8575 * @return array The mapping of values to digest options.
8577 function forum_get_user_digest_options($user = null) {
8578 global $USER;
8580 // Revert to the global user object.
8581 if ($user === null) {
8582 $user = $USER;
8585 $digestoptions = array();
8586 $digestoptions['0'] = get_string('emaildigestoffshort', 'mod_forum');
8587 $digestoptions['1'] = get_string('emaildigestcompleteshort', 'mod_forum');
8588 $digestoptions['2'] = get_string('emaildigestsubjectsshort', 'mod_forum');
8590 // We need to add the default digest option at the end - it relies on
8591 // the contents of the existing values.
8592 $digestoptions['-1'] = get_string('emaildigestdefault', 'mod_forum',
8593 $digestoptions[$user->maildigest]);
8595 // Resort the options to be in a sensible order.
8596 ksort($digestoptions);
8598 return $digestoptions;