MDL-32125 mod_forum: updating subscription mode not reflected
[moodle.git] / mod / forum / lib.php
CommitLineData
df1ba0f4 1<?php
df1ba0f4 2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
17/**
bc196cbe
PS
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
df1ba0f4 22 */
23
bc196cbe
PS
24defined('MOODLE_INTERNAL') || die();
25
df1ba0f4 26/** Include required files */
f1e0649c 27require_once($CFG->libdir.'/filelib.php');
3b120e46 28require_once($CFG->libdir.'/eventslib.php');
c23c9009 29require_once($CFG->dirroot.'/user/selector/lib.php');
dff8438e 30require_once($CFG->dirroot.'/mod/forum/post_form.php');
7f6689e4 31
501cdbd8 32/// CONSTANTS ///////////////////////////////////////////////////////////
f93f848a 33
d3583b41 34define('FORUM_MODE_FLATOLDEST', 1);
35define('FORUM_MODE_FLATNEWEST', -1);
36define('FORUM_MODE_THREADED', 2);
37define('FORUM_MODE_NESTED', 3);
2e2e71a8 38
afef965e 39define('FORUM_CHOOSESUBSCRIBE', 0);
d3583b41 40define('FORUM_FORCESUBSCRIBE', 1);
41define('FORUM_INITIALSUBSCRIBE', 2);
098d27d4 42define('FORUM_DISALLOWSUBSCRIBE',3);
709f0ec8 43
eaf50aef 44define('FORUM_TRACKING_OFF', 0);
45define('FORUM_TRACKING_OPTIONAL', 1);
46define('FORUM_TRACKING_ON', 2);
47
103f34d8
PS
48if (!defined('FORUM_CRON_USER_CACHE')) {
49 /** Defines how many full user records are cached in forum cron. */
50 define('FORUM_CRON_USER_CACHE', 5000);
51}
52
caadf009 53/// STANDARD FUNCTIONS ///////////////////////////////////////////////////////////
54
0a4ac01b 55/**
56 * Given an object containing all the necessary data,
7cac0c4b 57 * (defined by the form in mod_form.php) this function
0a4ac01b 58 * will create a new instance and return the id number
59 * of the new instance.
df1ba0f4 60 *
6b04fdc0
PS
61 * @param stdClass $forum add forum instance
62 * @param mod_forum_mod_form $mform
6b7de0bb 63 * @return int intance id
3a5e1d06 64 */
6b04fdc0 65function forum_add_instance($forum, $mform = null) {
c18269c7 66 global $CFG, $DB;
caadf009 67
68 $forum->timemodified = time();
69
353228d8 70 if (empty($forum->assessed)) {
f2f56406 71 $forum->assessed = 0;
72 }
2b63df96 73
353228d8 74 if (empty($forum->ratingtime) or empty($forum->assessed)) {
98914efd 75 $forum->assesstimestart = 0;
76 $forum->assesstimefinish = 0;
77 }
caadf009 78
a8f3a651 79 $forum->id = $DB->insert_record('forum', $forum);
42776c94 80 $modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule);
cb9a975f 81
d3583b41 82 if ($forum->type == 'single') { // Create related discussion.
39790bd8 83 $discussion = new stdClass();
e2d7687f 84 $discussion->course = $forum->course;
85 $discussion->forum = $forum->id;
86 $discussion->name = $forum->name;
e2d7687f 87 $discussion->assessed = $forum->assessed;
6606c00f
MD
88 $discussion->message = $forum->intro;
89 $discussion->messageformat = $forum->introformat;
90 $discussion->messagetrust = trusttext_trusted(get_context_instance(CONTEXT_COURSE, $forum->course));
e2d7687f 91 $discussion->mailnow = false;
92 $discussion->groupid = -1;
caadf009 93
dbf4d660 94 $message = '';
95
42776c94
PS
96 $discussion->id = forum_add_discussion($discussion, null, $message);
97
98 if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
99 // ugly hack - we need to copy the files somehow
100 $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
101 $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
102
dff8438e
MG
103 $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id,
104 mod_forum_post_form::attachment_options($forum), $post->message);
42776c94 105 $DB->set_field('forum_posts', 'message', $post->message, array('id'=>$post->id));
caadf009 106 }
107 }
8f0cd6ef 108
3c268f25 109 if ($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) {
8d8d0bfa 110 /// all users should be subscribed initially
111 /// Note: forum_get_potential_subscribers should take the forum context,
112 /// but that does not exist yet, becuase the forum is only half build at this
113 /// stage. However, because the forum is brand new, we know that there are
114 /// no role assignments or overrides in the forum context, so using the
115 /// course context gives the same list of users.
42776c94 116 $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
709f0ec8 117 foreach ($users as $user) {
118 forum_subscribe($user->id, $forum->id);
119 }
120 }
caadf009 121
612607bd 122 forum_grade_item_update($forum);
353228d8 123
caadf009 124 return $forum->id;
125}
126
127
13bbe067 128/**
0a4ac01b 129 * Given an object containing all the necessary data,
7cac0c4b 130 * (defined by the form in mod_form.php) this function
0a4ac01b 131 * will update an existing instance with new data.
df1ba0f4 132 *
133 * @global object
90f4745c 134 * @param object $forum forum instance (with magic quotes)
6b7de0bb 135 * @return bool success
90f4745c 136 */
42776c94 137function forum_update_instance($forum, $mform) {
862f5a49 138 global $DB, $OUTPUT, $USER;
c18269c7 139
caadf009 140 $forum->timemodified = time();
353228d8 141 $forum->id = $forum->instance;
caadf009 142
f0da6b85 143 if (empty($forum->assessed)) {
f2f56406 144 $forum->assessed = 0;
145 }
2b63df96 146
353228d8 147 if (empty($forum->ratingtime) or empty($forum->assessed)) {
98914efd 148 $forum->assesstimestart = 0;
149 $forum->assesstimefinish = 0;
150 }
151
c18269c7 152 $oldforum = $DB->get_record('forum', array('id'=>$forum->id));
13bbe067 153
154 // MDL-3942 - if the aggregation type or scale (i.e. max grade) changes then recalculate the grades for the entire forum
155 // if scale changes - do we need to recheck the ratings, if ratings higher than scale how do we want to respond?
156 // 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
157 if (($oldforum->assessed<>$forum->assessed) or ($oldforum->scale<>$forum->scale)) {
158 forum_update_grades($forum); // recalculate grades for the forum
159 }
160
d3583b41 161 if ($forum->type == 'single') { // Update related discussion and post.
35b81f27
RT
162 $discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id), 'timemodified ASC');
163 if (!empty($discussions)) {
164 if (count($discussions) > 1) {
165 echo $OUTPUT->notification(get_string('warnformorepost', 'forum'));
166 }
167 $discussion = array_pop($discussions);
168 } else {
169 // try to recover by creating initial discussion - MDL-16262
170 $discussion = new stdClass();
171 $discussion->course = $forum->course;
172 $discussion->forum = $forum->id;
173 $discussion->name = $forum->name;
174 $discussion->assessed = $forum->assessed;
175 $discussion->message = $forum->intro;
176 $discussion->messageformat = $forum->introformat;
177 $discussion->messagetrust = true;
178 $discussion->mailnow = false;
179 $discussion->groupid = -1;
180
181 $message = '';
182
183 forum_add_discussion($discussion, null, $message);
184
185 if (! $discussion = $DB->get_record('forum_discussions', array('forum'=>$forum->id))) {
186 print_error('cannotadd', 'forum');
caadf009 187 }
188 }
c18269c7 189 if (! $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost))) {
12e57b92 190 print_error('cannotfindfirstpost', 'forum');
caadf009 191 }
192
6606c00f 193 $cm = get_coursemodule_from_instance('forum', $forum->id);
42776c94
PS
194 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
195
196 if ($mform and $draftid = file_get_submitted_draft_itemid('introeditor')) {
197 // ugly hack - we need to copy the files somehow
198 $discussion = $DB->get_record('forum_discussions', array('id'=>$discussion->id), '*', MUST_EXIST);
199 $post = $DB->get_record('forum_posts', array('id'=>$discussion->firstpost), '*', MUST_EXIST);
200
dff8438e
MG
201 $post->message = file_save_draft_area_files($draftid, $modcontext->id, 'mod_forum', 'post', $post->id,
202 mod_forum_post_form::editor_options(), $post->message);
42776c94 203 }
6606c00f
MD
204
205 $post->subject = $forum->name;
206 $post->message = $forum->intro;
207 $post->messageformat = $forum->introformat;
208 $post->messagetrust = trusttext_trusted($modcontext);
209 $post->modified = $forum->timemodified;
210 $post->userid = $USER->id; // MDL-18599, so that current teacher can take ownership of activities
caadf009 211
a8d6ef8c 212 $DB->update_record('forum_posts', $post);
caadf009 213 $discussion->name = $forum->name;
a8d6ef8c 214 $DB->update_record('forum_discussions', $discussion);
caadf009 215 }
216
a8d6ef8c 217 $DB->update_record('forum', $forum);
353228d8 218
b4271570
KG
219 $modcontext = get_context_instance(CONTEXT_MODULE, $forum->coursemodule);
220 if (($forum->forcesubscribe == FORUM_INITIALSUBSCRIBE) && ($oldforum->forcesubscribe <> $forum->forcesubscribe)) {
221 $users = forum_get_potential_subscribers($modcontext, 0, 'u.id, u.email', '');
222 foreach ($users as $user) {
223 forum_subscribe($user->id, $forum->id);
224 }
225 }
353228d8 226 forum_grade_item_update($forum);
227
228 return true;
caadf009 229}
230
231
0a4ac01b 232/**
233 * Given an ID of an instance of this module,
234 * this function will permanently delete the instance
235 * and any data that depends on it.
df1ba0f4 236 *
237 * @global object
238 * @param int $id forum instance id
90f4745c 239 * @return bool success
0a4ac01b 240 */
caadf009 241function forum_delete_instance($id) {
c18269c7 242 global $DB;
caadf009 243
c18269c7 244 if (!$forum = $DB->get_record('forum', array('id'=>$id))) {
caadf009 245 return false;
246 }
455a8fed 247 if (!$cm = get_coursemodule_from_instance('forum', $forum->id)) {
248 return false;
249 }
250 if (!$course = $DB->get_record('course', array('id'=>$cm->course))) {
251 return false;
252 }
253
254 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
caadf009 255
fa686bc4 256 // now get rid of all files
257 $fs = get_file_storage();
455a8fed 258 $fs->delete_area_files($context->id);
fa686bc4 259
caadf009 260 $result = true;
261
c18269c7 262 if ($discussions = $DB->get_records('forum_discussions', array('forum'=>$forum->id))) {
caadf009 263 foreach ($discussions as $discussion) {
455a8fed 264 if (!forum_delete_discussion($discussion, true, $course, $cm, $forum)) {
caadf009 265 $result = false;
266 }
267 }
268 }
269
c18269c7 270 if (!$DB->delete_records('forum_subscriptions', array('forum'=>$forum->id))) {
caadf009 271 $result = false;
272 }
273
f37da850 274 forum_tp_delete_read_records(-1, -1, -1, $forum->id);
275
415f8c3e 276 if (!$DB->delete_records('forum', array('id'=>$forum->id))) {
caadf009 277 $result = false;
278 }
279
353228d8 280 forum_grade_item_delete($forum);
281
caadf009 282 return $result;
283}
284
285
4e781c7b 286/**
287 * Indicates API features that the forum supports.
288 *
df1ba0f4 289 * @uses FEATURE_GROUPS
290 * @uses FEATURE_GROUPINGS
291 * @uses FEATURE_GROUPMEMBERSONLY
292 * @uses FEATURE_MOD_INTRO
293 * @uses FEATURE_COMPLETION_TRACKS_VIEWS
294 * @uses FEATURE_COMPLETION_HAS_RULES
295 * @uses FEATURE_GRADE_HAS_GRADE
296 * @uses FEATURE_GRADE_OUTCOMES
4e781c7b 297 * @param string $feature
298 * @return mixed True if yes (some features may use other values)
299 */
300function forum_supports($feature) {
301 switch($feature) {
42f103be 302 case FEATURE_GROUPS: return true;
303 case FEATURE_GROUPINGS: return true;
304 case FEATURE_GROUPMEMBERSONLY: return true;
dc5c2bd9 305 case FEATURE_MOD_INTRO: return true;
4e781c7b 306 case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
42f103be 307 case FEATURE_COMPLETION_HAS_RULES: return true;
308 case FEATURE_GRADE_HAS_GRADE: return true;
309 case FEATURE_GRADE_OUTCOMES: return true;
4bfdcfcf
EL
310 case FEATURE_RATE: return true;
311 case FEATURE_BACKUP_MOODLE2: return true;
3e4c2435 312 case FEATURE_SHOW_DESCRIPTION: return true;
42f103be 313
49f6e5f4 314 default: return null;
4e781c7b 315 }
316}
317
318
319/**
320 * Obtains the automatic completion state for this forum based on any conditions
321 * in forum settings.
322 *
df1ba0f4 323 * @global object
324 * @global object
4e781c7b 325 * @param object $course Course
326 * @param object $cm Course-module
327 * @param int $userid User ID
328 * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
329 * @return bool True if completed, false if not. (If no conditions, then return
330 * value depends on comparison type)
331 */
332function forum_get_completion_state($course,$cm,$userid,$type) {
333 global $CFG,$DB;
334
335 // Get forum details
6093af9b 336 if (!($forum=$DB->get_record('forum',array('id'=>$cm->instance)))) {
4e781c7b 337 throw new Exception("Can't find forum {$cm->instance}");
338 }
339
340 $result=$type; // Default return value
341
342 $postcountparams=array('userid'=>$userid,'forumid'=>$forum->id);
343 $postcountsql="
5e7f2b0b 344SELECT
345 COUNT(1)
346FROM
347 {forum_posts} fp
49f6e5f4 348 INNER JOIN {forum_discussions} fd ON fp.discussion=fd.id
4e781c7b 349WHERE
350 fp.userid=:userid AND fd.forum=:forumid";
351
6093af9b 352 if ($forum->completiondiscussions) {
4e781c7b 353 $value = $forum->completiondiscussions <=
6093af9b 354 $DB->count_records('forum_discussions',array('forum'=>$forum->id,'userid'=>$userid));
355 if ($type == COMPLETION_AND) {
356 $result = $result && $value;
4e781c7b 357 } else {
6093af9b 358 $result = $result || $value;
4e781c7b 359 }
360 }
6093af9b 361 if ($forum->completionreplies) {
5e7f2b0b 362 $value = $forum->completionreplies <=
6093af9b 363 $DB->get_field_sql( $postcountsql.' AND fp.parent<>0',$postcountparams);
364 if ($type==COMPLETION_AND) {
365 $result = $result && $value;
4e781c7b 366 } else {
6093af9b 367 $result = $result || $value;
4e781c7b 368 }
369 }
6093af9b 370 if ($forum->completionposts) {
4e781c7b 371 $value = $forum->completionposts <= $DB->get_field_sql($postcountsql,$postcountparams);
6093af9b 372 if ($type == COMPLETION_AND) {
373 $result = $result && $value;
4e781c7b 374 } else {
6093af9b 375 $result = $result || $value;
4e781c7b 376 }
377 }
378
5e7f2b0b 379 return $result;
4e781c7b 380}
381
8260050d
AD
382/**
383 * Create a message-id string to use in the custom headers of forum notification emails
384 *
385 * message-id is used by email clients to identify emails and to nest conversations
386 *
387 * @param int $postid The ID of the forum post we are notifying the user about
388 * @param int $usertoid The ID of the user being notified
389 * @param string $hostname The server's hostname
390 * @return string A unique message-id
391 */
1376b0dd 392function forum_get_email_message_id($postid, $usertoid, $hostname) {
476f72e9 393 return '<'.hash('sha256',$postid.'to'.$usertoid).'@'.$hostname.'>';
1376b0dd 394}
4e781c7b 395
103f34d8
PS
396/**
397 * Removes properties from user record that are not necessary
398 * for sending post notifications.
399 * @param stdClass $user
400 * @return void, $user parameter is modified
401 */
402function forum_cron_minimise_user_record(stdClass $user) {
403
404 // We store large amount of users in one huge array,
405 // make sure we do not store info there we do not actually need
406 // in mail generation code or messaging.
407
408 unset($user->institution);
409 unset($user->department);
410 unset($user->address);
411 unset($user->city);
412 unset($user->url);
413 unset($user->currentlogin);
414 unset($user->description);
415 unset($user->descriptionformat);
416}
417
0a4ac01b 418/**
419 * Function to be run periodically according to the moodle cron
420 * Finds all posts that have yet to be mailed out, and mails them
421 * out to all subscribers
df1ba0f4 422 *
423 * @global object
424 * @global object
425 * @global object
426 * @uses CONTEXT_MODULE
427 * @uses CONTEXT_COURSE
428 * @uses SITEID
429 * @uses FORMAT_PLAIN
90f4745c 430 * @return void
431 */
0fa18d5a 432function forum_cron() {
4e445355 433 global $CFG, $USER, $DB;
857b798b 434
a974c799 435 $site = get_site();
436
103f34d8
PS
437 // All users that are subscribed to any post that needs sending,
438 // please increase $CFG->extramemorylimit on large sites that
439 // send notifications to a large number of users.
a974c799 440 $users = array();
103f34d8 441 $userscount = 0; // Cached user counter - count($users) in PHP is horribly slow!!!
a974c799 442
443 // status arrays
444 $mailcount = array();
445 $errorcount = array();
446
447 // caches
448 $discussions = array();
449 $forums = array();
450 $courses = array();
451 $coursemodules = array();
a974c799 452 $subscribedusers = array();
aaf7a9dc 453
ec2137ba 454
0a4ac01b 455 // Posts older than 2 days will not be mailed. This is to avoid the problem where
456 // cron has not been running for a long time, and then suddenly people are flooded
457 // with mail from the past few weeks or months
3ecca1ee 458 $timenow = time();
459 $endtime = $timenow - $CFG->maxeditingtime;
0a4ac01b 460 $starttime = $endtime - 48 * 3600; // Two days earlier
3ecca1ee 461
90f4745c 462 if ($posts = forum_get_unmailed_posts($starttime, $endtime, $timenow)) {
0a4ac01b 463 // Mark them all now as being mailed. It's unlikely but possible there
464 // might be an error later so that a post is NOT actually mailed out,
465 // but since mail isn't crucial, we can accept this risk. Doing it now
466 // prevents the risk of duplicated mails, which is a worse problem.
16b4e5b6 467
5fac3a5e 468 if (!forum_mark_old_posts_as_mailed($endtime)) {
469 mtrace('Errors occurred while trying to mark some posts as being mailed.');
470 return false; // Don't continue trying to mail them, in case we are in a cron loop
471 }
472
473 // checking post validity, and adding users to loop through later
474 foreach ($posts as $pid => $post) {
475
a974c799 476 $discussionid = $post->discussion;
477 if (!isset($discussions[$discussionid])) {
4e445355 478 if ($discussion = $DB->get_record('forum_discussions', array('id'=> $post->discussion))) {
a974c799 479 $discussions[$discussionid] = $discussion;
480 } else {
481 mtrace('Could not find discussion '.$discussionid);
482 unset($posts[$pid]);
483 continue;
484 }
5fac3a5e 485 }
a974c799 486 $forumid = $discussions[$discussionid]->forum;
487 if (!isset($forums[$forumid])) {
4e445355 488 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
a974c799 489 $forums[$forumid] = $forum;
490 } else {
491 mtrace('Could not find forum '.$forumid);
492 unset($posts[$pid]);
493 continue;
494 }
5fac3a5e 495 }
a974c799 496 $courseid = $forums[$forumid]->course;
497 if (!isset($courses[$courseid])) {
4e445355 498 if ($course = $DB->get_record('course', array('id' => $courseid))) {
a974c799 499 $courses[$courseid] = $course;
500 } else {
501 mtrace('Could not find course '.$courseid);
502 unset($posts[$pid]);
503 continue;
504 }
5fac3a5e 505 }
a974c799 506 if (!isset($coursemodules[$forumid])) {
507 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
508 $coursemodules[$forumid] = $cm;
509 } else {
9c670df6 510 mtrace('Could not find course module for forum '.$forumid);
a974c799 511 unset($posts[$pid]);
512 continue;
513 }
514 }
515
516
5fac3a5e 517 // caching subscribed users of each forum
a974c799 518 if (!isset($subscribedusers[$forumid])) {
48dcaf58 519 $modcontext = get_context_instance(CONTEXT_MODULE, $coursemodules[$forumid]->id);
c35ae136 520 if ($subusers = forum_subscribed_users($courses[$courseid], $forums[$forumid], 0, $modcontext, "u.*")) {
704ca25c 521 foreach ($subusers as $postuser) {
522 // this user is subscribed to this forum
a5cef9c8 523 $subscribedusers[$forumid][$postuser->id] = $postuser->id;
103f34d8
PS
524 $userscount++;
525 if ($userscount > FORUM_CRON_USER_CACHE) {
526 // Store minimal user info.
527 $minuser = new stdClass();
528 $minuser->id = $postuser->id;
529 $users[$postuser->id] = $minuser;
530 } else {
531 // Cache full user record.
532 forum_cron_minimise_user_record($postuser);
533 $users[$postuser->id] = $postuser;
534 }
704ca25c 535 }
103f34d8
PS
536 // Release memory.
537 unset($subusers);
538 unset($postuser);
a974c799 539 }
5fac3a5e 540 }
a974c799 541
5fac3a5e 542 $mailcount[$pid] = 0;
543 $errorcount[$pid] = 0;
a974c799 544 }
5fac3a5e 545 }
caadf009 546
4dad2828 547 if ($users && $posts) {
edffca15 548
857b798b 549 $urlinfo = parse_url($CFG->wwwroot);
550 $hostname = $urlinfo['host'];
551
5fac3a5e 552 foreach ($users as $userto) {
a974c799 553
554 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
555
16b4e5b6 556 mtrace('Processing user '.$userto->id);
caadf009 557
103f34d8
PS
558 // Init user caches - we keep the cache for one cycle only,
559 // otherwise it could consume too much memory.
560 if (isset($userto->username)) {
561 $userto = clone($userto);
562 } else {
563 $userto = $DB->get_record('user', array('id' => $userto->id));
564 forum_cron_minimise_user_record($userto);
565 }
df1c2c71 566 $userto->viewfullnames = array();
567 $userto->canpost = array();
90f4745c 568 $userto->markposts = array();
669f2499 569
103f34d8
PS
570 // set this so that the capabilities are cached, and environment matches receiving user
571 cron_setup_user($userto);
572
9db5d080 573 // reset the caches
574 foreach ($coursemodules as $forumid=>$unused) {
39790bd8 575 $coursemodules[$forumid]->cache = new stdClass();
9db5d080 576 $coursemodules[$forumid]->cache->caps = array();
577 unset($coursemodules[$forumid]->uservisible);
578 }
579
4dad2828 580 foreach ($posts as $pid => $post) {
caadf009 581
4dad2828 582 // Set up the environment for the post, discussion, forum, course
a974c799 583 $discussion = $discussions[$post->discussion];
584 $forum = $forums[$discussion->forum];
585 $course = $courses[$forum->course];
90f4745c 586 $cm =& $coursemodules[$forum->id];
4dad2828 587
588 // Do some checks to see if we can bail out now
72650054 589 // Only active enrolled users are in the list of subscribers
a5cef9c8 590 if (!isset($subscribedusers[$forum->id][$userto->id])) {
a974c799 591 continue; // user does not subscribe to this forum
4dad2828 592 }
4dad2828 593
67fc4f00 594 // Don't send email if the forum is Q&A and the user has not posted
1e966b8a
CF
595 // Initial topics are still mailed
596 if ($forum->type == 'qanda' && !forum_get_user_posted_time($discussion->id, $userto->id) && $pid != $discussion->firstpost) {
67fc4f00
DC
597 mtrace('Did not email '.$userto->id.' because user has not posted in discussion');
598 continue;
599 }
90f4745c 600
a5cef9c8 601 // Get info about the sending user
602 if (array_key_exists($post->userid, $users)) { // we might know him/her already
603 $userfrom = $users[$post->userid];
103f34d8
PS
604 if (!isset($userfrom->idnumber)) {
605 // Minimalised user info, fetch full record.
606 $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
607 forum_cron_minimise_user_record($userfrom);
608 }
609
4e445355 610 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
103f34d8
PS
611 forum_cron_minimise_user_record($userfrom);
612 // Fetch only once if possible, we can add it to user list, it will be skipped anyway.
613 if ($userscount <= FORUM_CRON_USER_CACHE) {
614 $userscount++;
615 $users[$userfrom->id] = $userfrom;
616 }
617
a5cef9c8 618 } else {
619 mtrace('Could not find user '.$post->userid);
620 continue;
621 }
622
505ab5aa
AD
623 //if we want to check that userto and userfrom are not the same person this is probably the spot to do it
624
a974c799 625 // setup global $COURSE properly - needed for roles and languages
e8b7114d 626 cron_setup_user($userto, $course);
4dad2828 627
df1c2c71 628 // Fill caches
629 if (!isset($userto->viewfullnames[$forum->id])) {
630 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
631 $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
632 }
8b79a625 633 if (!isset($userto->canpost[$discussion->id])) {
df1c2c71 634 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
8b79a625 635 $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
df1c2c71 636 }
637 if (!isset($userfrom->groups[$forum->id])) {
638 if (!isset($userfrom->groups)) {
639 $userfrom->groups = array();
103f34d8
PS
640 if (isset($users[$userfrom->id])) {
641 $users[$userfrom->id]->groups = array();
642 }
df1c2c71 643 }
644 $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
103f34d8
PS
645 if (isset($users[$userfrom->id])) {
646 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
647 }
df1c2c71 648 }
9f2ded76 649
a974c799 650 // Make sure groups allow this user to see this email
df1c2c71 651 if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) { // Groups are being used
9f2ded76 652 if (!groups_group_exists($discussion->groupid)) { // Can't find group
38bd362a 653 continue; // Be safe and don't send it to anyone
5fac3a5e 654 }
918e9805 655
2c386f82 656 if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $modcontext)) {
38bd362a 657 // do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
658 continue;
9197e147 659 }
4dad2828 660 }
2b63df96 661
4dad2828 662 // Make sure we're allowed to see it...
9f2ded76 663 if (!forum_user_can_see_post($forum, $discussion, $post, NULL, $cm)) {
4dad2828 664 mtrace('user '.$userto->id. ' can not see '.$post->id);
665 continue;
666 }
667
668 // OK so we need to send the email.
669
670 // Does the user want this post in a digest? If so postpone it for now.
671 if ($userto->maildigest > 0) {
672 // This user wants the mails to be in digest form
39790bd8 673 $queue = new stdClass();
a974c799 674 $queue->userid = $userto->id;
4dad2828 675 $queue->discussionid = $discussion->id;
a974c799 676 $queue->postid = $post->id;
90f4745c 677 $queue->timemodified = $post->created;
fc29e51b 678 $DB->insert_record('forum_queue', $queue);
4dad2828 679 continue;
680 }
65b0e537 681
4dad2828 682
683 // Prepare to actually send the post now, and build up the content
684
a974c799 685 $cleanforumname = str_replace('"', "'", strip_tags(format_string($forum->name)));
4dad2828 686
687 $userfrom->customheaders = array ( // Headers to make emails easier to track
688 'Precedence: Bulk',
689 'List-Id: "'.$cleanforumname.'" <moodleforum'.$forum->id.'@'.$hostname.'>',
690 'List-Help: '.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id,
1376b0dd 691 'Message-ID: '.forum_get_email_message_id($post->id, $userto->id, $hostname),
4dad2828 692 'X-Course-Id: '.$course->id,
a974c799 693 'X-Course-Name: '.format_string($course->fullname, true)
4dad2828 694 );
a974c799 695
c1b47d94 696 if ($post->parent) { // This post is a reply, so add headers for threading (see MDL-22551)
1376b0dd
AD
697 $userfrom->customheaders[] = 'In-Reply-To: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
698 $userfrom->customheaders[] = 'References: '.forum_get_email_message_id($post->parent, $userto->id, $hostname);
c1b47d94 699 }
4dad2828 700
8ebbb06a
SH
701 $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
702
93f11331 703 $postsubject = html_to_text("$shortname: ".format_string($post->subject, true));
0faf6791 704 $posttext = forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
705 $posthtml = forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto);
4dad2828 706
707 // Send the post now!
708
709 mtrace('Sending ', '');
5e7f2b0b 710
39790bd8 711 $eventdata = new stdClass();
505ab5aa 712 $eventdata->component = 'mod_forum';
120b3758 713 $eventdata->name = 'posts';
3b120e46 714 $eventdata->userfrom = $userfrom;
715 $eventdata->userto = $userto;
716 $eventdata->subject = $postsubject;
717 $eventdata->fullmessage = $posttext;
718 $eventdata->fullmessageformat = FORMAT_PLAIN;
719 $eventdata->fullmessagehtml = $posthtml;
14a0e7dd 720 $eventdata->notification = 1;
6ee2611c
AD
721
722 $smallmessagestrings = new stdClass();
723 $smallmessagestrings->user = fullname($userfrom);
8ebbb06a 724 $smallmessagestrings->forumname = "$shortname: ".format_string($forum->name,true).": ".$discussion->name;
6ee2611c 725 $smallmessagestrings->message = $post->message;
4ffa1463
AD
726 //make sure strings are in message recipients language
727 $eventdata->smallmessage = get_string_manager()->get_string('smallmessage', 'forum', $smallmessagestrings, $userto->lang);
14a0e7dd
AD
728
729 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
730 $eventdata->contexturlname = $discussion->name;
3b120e46 731
505ab5aa
AD
732 $mailresult = message_send($eventdata);
733 if (!$mailresult){
734 mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
4dad2828 735 " ($userto->email) .. not trying again.");
736 add_to_log($course->id, 'forum', 'mail error', "discuss.php?d=$discussion->id#p$post->id",
737 substr(format_string($post->subject,true),0,30), $cm->id, $userto->id);
738 $errorcount[$post->id]++;
4dad2828 739 } else {
740 $mailcount[$post->id]++;
741
0a4ac01b 742 // Mark post as read if forum_usermarksread is set off
90f4745c 743 if (!$CFG->forum_usermarksread) {
744 $userto->markposts[$post->id] = $post->id;
caadf009 745 }
aaf7a9dc 746 }
4dad2828 747
748 mtrace('post '.$post->id. ': '.$post->subject);
aaf7a9dc 749 }
90f4745c 750
751 // mark processed posts as read
752 forum_tp_mark_posts_read($userto, $userto->markposts);
103f34d8 753 unset($userto);
5fac3a5e 754 }
755 }
756
757 if ($posts) {
758 foreach ($posts as $post) {
16b4e5b6 759 mtrace($mailcount[$post->id]." users were sent post $post->id, '$post->subject'");
5fac3a5e 760 if ($errorcount[$post->id]) {
4e445355 761 $DB->set_field("forum_posts", "mailed", "2", array("id" => "$post->id"));
a974c799 762 }
aaf7a9dc 763 }
764 }
765
8cb121cc 766 // release some memory
767 unset($subscribedusers);
768 unset($mailcount);
769 unset($errorcount);
770
e8b7114d 771 cron_setup_user();
ad9ff3d3 772
9152fc99 773 $sitetimezone = $CFG->timezone;
944a2b28 774
0a4ac01b 775 // Now see if there are any digest mails waiting to be sent, and if we should send them
aaf7a9dc 776
f3c3a4d3 777 mtrace('Starting digest processing...');
778
910b6fa7 779 @set_time_limit(300); // terminate if not able to fetch all digests in 5 minutes
780
8f0cd6ef 781 if (!isset($CFG->digestmailtimelast)) { // To catch the first time
ca8e8a10 782 set_config('digestmailtimelast', 0);
783 }
784
785 $timenow = time();
944a2b28 786 $digesttime = usergetmidnight($timenow, $sitetimezone) + ($CFG->digestmailtime * 3600);
ca8e8a10 787
f3c3a4d3 788 // Delete any really old ones (normally there shouldn't be any)
789 $weekago = $timenow - (7 * 24 * 3600);
2ac897cd 790 $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
910b6fa7 791 mtrace ('Cleaned old digest records');
9f2ded76 792
ca8e8a10 793 if ($CFG->digestmailtimelast < $digesttime and $timenow > $digesttime) {
b140ae85 794
b140ae85 795 mtrace('Sending forum digests: '.userdate($timenow, '', $sitetimezone));
796
4e445355 797 $digestposts_rs = $DB->get_recordset_select('forum_queue', "timemodified < ?", array($digesttime));
910b6fa7 798
4e445355 799 if ($digestposts_rs->valid()) {
8ad64455 800
aaf7a9dc 801 // We have work to do
802 $usermailcount = 0;
aaf7a9dc 803
a974c799 804 //caches - reuse the those filled before too
aaf7a9dc 805 $discussionposts = array();
806 $userdiscussions = array();
a974c799 807
4e445355 808 foreach ($digestposts_rs as $digestpost) {
a974c799 809 if (!isset($posts[$digestpost->postid])) {
4e445355 810 if ($post = $DB->get_record('forum_posts', array('id' => $digestpost->postid))) {
a974c799 811 $posts[$digestpost->postid] = $post;
812 } else {
813 continue;
814 }
815 }
816 $discussionid = $digestpost->discussionid;
817 if (!isset($discussions[$discussionid])) {
4e445355 818 if ($discussion = $DB->get_record('forum_discussions', array('id' => $discussionid))) {
a974c799 819 $discussions[$discussionid] = $discussion;
820 } else {
821 continue;
822 }
aaf7a9dc 823 }
a974c799 824 $forumid = $discussions[$discussionid]->forum;
825 if (!isset($forums[$forumid])) {
4e445355 826 if ($forum = $DB->get_record('forum', array('id' => $forumid))) {
a974c799 827 $forums[$forumid] = $forum;
828 } else {
829 continue;
830 }
831 }
832
833 $courseid = $forums[$forumid]->course;
834 if (!isset($courses[$courseid])) {
4e445355 835 if ($course = $DB->get_record('course', array('id' => $courseid))) {
a974c799 836 $courses[$courseid] = $course;
837 } else {
838 continue;
839 }
aaf7a9dc 840 }
a974c799 841
842 if (!isset($coursemodules[$forumid])) {
843 if ($cm = get_coursemodule_from_instance('forum', $forumid, $courseid)) {
844 $coursemodules[$forumid] = $cm;
845 } else {
846 continue;
847 }
aaf7a9dc 848 }
849 $userdiscussions[$digestpost->userid][$digestpost->discussionid] = $digestpost->discussionid;
850 $discussionposts[$digestpost->discussionid][$digestpost->postid] = $digestpost->postid;
851 }
4e445355 852 $digestposts_rs->close(); /// Finished iteration, let's close the resultset
aaf7a9dc 853
854 // Data collected, start sending out emails to each user
a974c799 855 foreach ($userdiscussions as $userid => $thesediscussions) {
aaf7a9dc 856
a974c799 857 @set_time_limit(120); // terminate if processing of any account takes longer than 2 minutes
aaf7a9dc 858
e8b7114d 859 cron_setup_user();
90f4745c 860
a974c799 861 mtrace(get_string('processingdigest', 'forum', $userid), '... ');
aaf7a9dc 862
863 // First of all delete all the queue entries for this user
4e445355 864 $DB->delete_records_select('forum_queue', "userid = ? AND timemodified < ?", array($userid, $digesttime));
aaf7a9dc 865
103f34d8
PS
866 // Init user caches - we keep the cache for one cycle only,
867 // otherwise it would unnecessarily consume memory.
868 if (array_key_exists($userid, $users) and isset($users[$userid]->username)) {
869 $userto = clone($users[$userid]);
870 } else {
871 $userto = $DB->get_record('user', array('id' => $userid));
872 forum_cron_minimise_user_record($userto);
873 }
df1c2c71 874 $userto->viewfullnames = array();
875 $userto->canpost = array();
90f4745c 876 $userto->markposts = array();
df1c2c71 877
103f34d8
PS
878 // Override the language and timezone of the "current" user, so that
879 // mail is customised for the receiver.
880 cron_setup_user($userto);
881
a974c799 882 $postsubject = get_string('digestmailsubject', 'forum', format_string($site->shortname, true));
aaf7a9dc 883
39790bd8 884 $headerdata = new stdClass();
a974c799 885 $headerdata->sitename = format_string($site->fullname, true);
839f2456 886 $headerdata->userprefs = $CFG->wwwroot.'/user/edit.php?id='.$userid.'&amp;course='.$site->id;
aaf7a9dc 887
888 $posttext = get_string('digestmailheader', 'forum', $headerdata)."\n\n";
889 $headerdata->userprefs = '<a target="_blank" href="'.$headerdata->userprefs.'">'.get_string('digestmailprefs', 'forum').'</a>';
9c674431 890
78c0d909 891 $posthtml = "<head>";
78946b9b
PS
892/* foreach ($CFG->stylesheets as $stylesheet) {
893 //TODO: MDL-21120
78c0d909 894 $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
78946b9b 895 }*/
449b1c1a 896 $posthtml .= "</head>\n<body id=\"email\">\n";
a0330747 897 $posthtml .= '<p>'.get_string('digestmailheader', 'forum', $headerdata).'</p><br /><hr size="1" noshade="noshade" />';
aaf7a9dc 898
a974c799 899 foreach ($thesediscussions as $discussionid) {
aaf7a9dc 900
0a4ac01b 901 @set_time_limit(120); // to be reset for each post
a974c799 902
903 $discussion = $discussions[$discussionid];
904 $forum = $forums[$discussion->forum];
905 $course = $courses[$forum->course];
906 $cm = $coursemodules[$forum->id];
65b0e537 907
9152fc99 908 //override language
e8b7114d 909 cron_setup_user($userto, $course);
9152fc99 910
df1c2c71 911 // Fill caches
912 if (!isset($userto->viewfullnames[$forum->id])) {
913 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
914 $userto->viewfullnames[$forum->id] = has_capability('moodle/site:viewfullnames', $modcontext);
915 }
8b79a625 916 if (!isset($userto->canpost[$discussion->id])) {
df1c2c71 917 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
8b79a625 918 $userto->canpost[$discussion->id] = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
df1c2c71 919 }
caadf009 920
de85c320 921 $strforums = get_string('forums', 'forum');
922 $canunsubscribe = ! forum_is_forcesubscribed($forum);
8b79a625 923 $canreply = $userto->canpost[$discussion->id];
8ebbb06a 924 $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
de85c320 925
aaf7a9dc 926 $posttext .= "\n \n";
927 $posttext .= '=====================================================================';
928 $posttext .= "\n \n";
8ebbb06a 929 $posttext .= "$shortname -> $strforums -> ".format_string($forum->name,true);
aaf7a9dc 930 if ($discussion->name != $forum->name) {
c78ac798 931 $posttext .= " -> ".format_string($discussion->name,true);
caadf009 932 }
aaf7a9dc 933 $posttext .= "\n";
65b0e537 934
aaf7a9dc 935 $posthtml .= "<p><font face=\"sans-serif\">".
8ebbb06a 936 "<a target=\"_blank\" href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$shortname</a> -> ".
aaf7a9dc 937 "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/index.php?id=$course->id\">$strforums</a> -> ".
3849dae8 938 "<a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
aaf7a9dc 939 if ($discussion->name == $forum->name) {
940 $posthtml .= "</font></p>";
caadf009 941 } else {
c78ac798 942 $posthtml .= " -> <a target=\"_blank\" href=\"$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a></font></p>";
caadf009 943 }
aaf7a9dc 944 $posthtml .= '<p>';
945
e1c6dde1 946 $postsarray = $discussionposts[$discussionid];
947 sort($postsarray);
948
857b798b 949 foreach ($postsarray as $postid) {
a974c799 950 $post = $posts[$postid];
951
952 if (array_key_exists($post->userid, $users)) { // we might know him/her already
953 $userfrom = $users[$post->userid];
103f34d8
PS
954 if (!isset($userfrom->idnumber)) {
955 $userfrom = $DB->get_record('user', array('id' => $userfrom->id));
956 forum_cron_minimise_user_record($userfrom);
957 }
958
4e445355 959 } else if ($userfrom = $DB->get_record('user', array('id' => $post->userid))) {
103f34d8
PS
960 forum_cron_minimise_user_record($userfrom);
961 if ($userscount <= FORUM_CRON_USER_CACHE) {
962 $userscount++;
963 $users[$userfrom->id] = $userfrom;
964 }
965
df1c2c71 966 } else {
a974c799 967 mtrace('Could not find user '.$post->userid);
aaf7a9dc 968 continue;
969 }
970
df1c2c71 971 if (!isset($userfrom->groups[$forum->id])) {
972 if (!isset($userfrom->groups)) {
973 $userfrom->groups = array();
103f34d8
PS
974 if (isset($users[$userfrom->id])) {
975 $users[$userfrom->id]->groups = array();
976 }
df1c2c71 977 }
978 $userfrom->groups[$forum->id] = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
103f34d8
PS
979 if (isset($users[$userfrom->id])) {
980 $users[$userfrom->id]->groups[$forum->id] = $userfrom->groups[$forum->id];
981 }
df1c2c71 982 }
983
857b798b 984 $userfrom->customheaders = array ("Precedence: Bulk");
985
986 if ($userto->maildigest == 2) {
aaf7a9dc 987 // Subjects only
39790bd8 988 $by = new stdClass();
aaf7a9dc 989 $by->name = fullname($userfrom);
990 $by->date = userdate($post->modified);
17dc3f3c 991 $posttext .= "\n".format_string($post->subject,true).' '.get_string("bynameondate", "forum", $by);
aaf7a9dc 992 $posttext .= "\n---------------------------------------------------------------------";
993
839f2456 994 $by->name = "<a target=\"_blank\" href=\"$CFG->wwwroot/user/view.php?id=$userfrom->id&amp;course=$course->id\">$by->name</a>";
0be4d8bf 995 $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>';
857b798b 996
997 } else {
aaf7a9dc 998 // The full treatment
0faf6791 999 $posttext .= forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, true);
1000 $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
df1c2c71 1001
0a4ac01b 1002 // Create an array of postid's for this user to mark as read.
90f4745c 1003 if (!$CFG->forum_usermarksread) {
1004 $userto->markposts[$post->id] = $post->id;
f37da850 1005 }
aaf7a9dc 1006 }
1007 }
1008 if ($canunsubscribe) {
ff9b4ea4 1009 $posthtml .= "\n<div class='mdl-right'><font size=\"1\"><a href=\"$CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\">".get_string("unsubscribe", "forum")."</a></font></div>";
857b798b 1010 } else {
ff9b4ea4 1011 $posthtml .= "\n<div class='mdl-right'><font size=\"1\">".get_string("everyoneissubscribed", "forum")."</font></div>";
aaf7a9dc 1012 }
1013 $posthtml .= '<hr size="1" noshade="noshade" /></p>';
caadf009 1014 }
a0330747 1015 $posthtml .= '</body>';
caadf009 1016
6d2e6936 1017 if (empty($userto->mailformat) || $userto->mailformat != 1) {
379a42cb 1018 // This user DOESN'T want to receive HTML
1019 $posthtml = '';
1020 }
5e7f2b0b 1021
b58bd1a2
AD
1022 $attachment = $attachname='';
1023 $usetrueaddress = true;
02d8fb14
DP
1024 // Directly email forum digests rather than sending them via messaging, use the
1025 // site shortname as 'from name', the noreply address will be used by email_to_user.
eac02096 1026 $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname, $usetrueaddress, $CFG->forum_replytouser);
b58bd1a2
AD
1027
1028 if (!$mailresult) {
b140ae85 1029 mtrace("ERROR!");
aaf7a9dc 1030 echo "Error: mod/forum/cron.php: Could not send out digest mail to user $userto->id ($userto->email)... not trying again.\n";
1031 add_to_log($course->id, 'forum', 'mail digest error', '', '', $cm->id, $userto->id);
1032 } else {
b140ae85 1033 mtrace("success.");
aaf7a9dc 1034 $usermailcount++;
e3ff14ca 1035
90f4745c 1036 // Mark post as read if forum_usermarksread is set off
1037 forum_tp_mark_posts_read($userto, $userto->markposts);
3d94772d 1038 }
caadf009 1039 }
caadf009 1040 }
226a1d9d 1041 /// We have finishied all digest emails, update $CFG->digestmailtimelast
1042 set_config('digestmailtimelast', $timenow);
caadf009 1043 }
1044
e8b7114d 1045 cron_setup_user();
de85c320 1046
a974c799 1047 if (!empty($usermailcount)) {
b140ae85 1048 mtrace(get_string('digestsentusers', 'forum', $usermailcount));
aaf7a9dc 1049 }
1050
8ad64455 1051 if (!empty($CFG->forum_lastreadclean)) {
f37da850 1052 $timenow = time();
8ad64455 1053 if ($CFG->forum_lastreadclean + (24*3600) < $timenow) {
1054 set_config('forum_lastreadclean', $timenow);
8cb121cc 1055 mtrace('Removing old forum read tracking info...');
f37da850 1056 forum_tp_clean_read_records();
1057 }
1058 } else {
8ad64455 1059 set_config('forum_lastreadclean', time());
f37da850 1060 }
1061
1062
caadf009 1063 return true;
1064}
1065
0a4ac01b 1066/**
1670305d 1067 * Builds and returns the body of the email notification in plain text.
1068 *
df1ba0f4 1069 * @global object
1070 * @global object
1071 * @uses CONTEXT_MODULE
1670305d 1072 * @param object $course
df1ba0f4 1073 * @param object $cm
1670305d 1074 * @param object $forum
1075 * @param object $discussion
1076 * @param object $post
1077 * @param object $userfrom
1078 * @param object $userto
1079 * @param boolean $bare
1080 * @return string The email body in plain text format.
0a4ac01b 1081 */
0faf6791 1082function forum_make_mail_text($course, $cm, $forum, $discussion, $post, $userfrom, $userto, $bare = false) {
15f81ee3 1083 global $CFG, $USER;
2b63df96 1084
18ff4d42
PS
1085 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
1086
df1c2c71 1087 if (!isset($userto->viewfullnames[$forum->id])) {
df1c2c71 1088 $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
1089 } else {
1090 $viewfullnames = $userto->viewfullnames[$forum->id];
1091 }
1092
8b79a625 1093 if (!isset($userto->canpost[$discussion->id])) {
8b79a625 1094 $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course, $modcontext);
df1c2c71 1095 } else {
8b79a625 1096 $canreply = $userto->canpost[$discussion->id];
0fa18d5a 1097 }
2b63df96 1098
aaf7a9dc 1099 $by = New stdClass;
df1c2c71 1100 $by->name = fullname($userfrom, $viewfullnames);
aaf7a9dc 1101 $by->date = userdate($post->modified, "", $userto->timezone);
1102
1103 $strbynameondate = get_string('bynameondate', 'forum', $by);
1104
64762ddc 1105 $strforums = get_string('forums', 'forum');
1106
a9900c73 1107 $canunsubscribe = ! forum_is_forcesubscribed($forum);
aaf7a9dc 1108
1109 $posttext = '';
1110
0fa18d5a 1111 if (!$bare) {
8ebbb06a
SH
1112 $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
1113 $posttext = "$shortname -> $strforums -> ".format_string($forum->name,true);
aaf7a9dc 1114
1115 if ($discussion->name != $forum->name) {
c78ac798 1116 $posttext .= " -> ".format_string($discussion->name,true);
aaf7a9dc 1117 }
1118 }
1119
18ff4d42
PS
1120 // add absolute file links
1121 $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
1122
aaf7a9dc 1123 $posttext .= "\n---------------------------------------------------------------------\n";
17dc3f3c 1124 $posttext .= format_string($post->subject,true);
0fa18d5a 1125 if ($bare) {
0be4d8bf 1126 $posttext .= " ($CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id#p$post->id)";
aaf7a9dc 1127 }
1128 $posttext .= "\n".$strbynameondate."\n";
1129 $posttext .= "---------------------------------------------------------------------\n";
e2d7687f 1130 $posttext .= format_text_email($post->message, $post->messageformat);
aaf7a9dc 1131 $posttext .= "\n\n";
0faf6791 1132 $posttext .= forum_print_attachments($post, $cm, "text");
1133
aaf7a9dc 1134 if (!$bare && $canreply) {
1135 $posttext .= "---------------------------------------------------------------------\n";
8ebbb06a 1136 $posttext .= get_string("postmailinfo", "forum", $shortname)."\n";
aaf7a9dc 1137 $posttext .= "$CFG->wwwroot/mod/forum/post.php?reply=$post->id\n";
1138 }
1139 if (!$bare && $canunsubscribe) {
1140 $posttext .= "\n---------------------------------------------------------------------\n";
1141 $posttext .= get_string("unsubscribe", "forum");
1142 $posttext .= ": $CFG->wwwroot/mod/forum/subscribe.php?id=$forum->id\n";
1143 }
1144
1145 return $posttext;
1146}
1147
0a4ac01b 1148/**
1670305d 1149 * Builds and returns the body of the email notification in html format.
1150 *
df1ba0f4 1151 * @global object
1670305d 1152 * @param object $course
df1ba0f4 1153 * @param object $cm
1670305d 1154 * @param object $forum
1155 * @param object $discussion
1156 * @param object $post
1157 * @param object $userfrom
1158 * @param object $userto
1159 * @return string The email text in HTML format
0a4ac01b 1160 */
0faf6791 1161function forum_make_mail_html($course, $cm, $forum, $discussion, $post, $userfrom, $userto) {
aaf7a9dc 1162 global $CFG;
1163
a0288610 1164 if ($userto->mailformat != 1) { // Needs to be HTML
1165 return '';
1166 }
aaf7a9dc 1167
8b79a625 1168 if (!isset($userto->canpost[$discussion->id])) {
11ec4ed5 1169 $canreply = forum_user_can_post($forum, $discussion, $userto, $cm, $course);
df1c2c71 1170 } else {
8b79a625 1171 $canreply = $userto->canpost[$discussion->id];
9f2ded76 1172 }
1173
a0288610 1174 $strforums = get_string('forums', 'forum');
a9900c73 1175 $canunsubscribe = ! forum_is_forcesubscribed($forum);
8ebbb06a 1176 $shortname = format_string($course->shortname, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
aaf7a9dc 1177
a0288610 1178 $posthtml = '<head>';
78946b9b
PS
1179/* foreach ($CFG->stylesheets as $stylesheet) {
1180 //TODO: MDL-21120
a0288610 1181 $posthtml .= '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'" />'."\n";
78946b9b 1182 }*/
a0288610 1183 $posthtml .= '</head>';
f2379d2d 1184 $posthtml .= "\n<body id=\"email\">\n\n";
aaf7a9dc 1185
f2379d2d 1186 $posthtml .= '<div class="navbar">'.
8ebbb06a 1187 '<a target="_blank" href="'.$CFG->wwwroot.'/course/view.php?id='.$course->id.'">'.$shortname.'</a> &raquo; '.
a0288610 1188 '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/index.php?id='.$course->id.'">'.$strforums.'</a> &raquo; '.
1189 '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.format_string($forum->name,true).'</a>';
1190 if ($discussion->name == $forum->name) {
1191 $posthtml .= '</div>';
aaf7a9dc 1192 } else {
a0288610 1193 $posthtml .= ' &raquo; <a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$discussion->id.'">'.
1194 format_string($discussion->name,true).'</a></div>';
aaf7a9dc 1195 }
0faf6791 1196 $posthtml .= forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto, false, $canreply, true, false);
a0288610 1197
1198 if ($canunsubscribe) {
85db96c5 1199 $posthtml .= '<hr /><div class="mdl-align unsubscribelink">
739da48c 1200 <a href="'.$CFG->wwwroot.'/mod/forum/subscribe.php?id='.$forum->id.'">'.get_string('unsubscribe', 'forum').'</a>&nbsp;
1201 <a href="'.$CFG->wwwroot.'/mod/forum/unsubscribeall.php">'.get_string('unsubscribeall', 'forum').'</a></div>';
a0288610 1202 }
1203
f2379d2d 1204 $posthtml .= '</body>';
1205
a0288610 1206 return $posthtml;
aaf7a9dc 1207}
1670305d 1208
1209
0a4ac01b 1210/**
13bbe067 1211 *
1670305d 1212 * @param object $course
1213 * @param object $user
1214 * @param object $mod TODO this is not used in this function, refactor
1215 * @param object $forum
1216 * @return object A standard object with 2 variables: info (number of posts for this user) and time (last modified)
0a4ac01b 1217 */
caadf009 1218function forum_user_outline($course, $user, $mod, $forum) {
1a96363a
NC
1219 global $CFG;
1220 require_once("$CFG->libdir/gradelib.php");
1221 $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1222 if (empty($grades->items[0]->grades)) {
1223 $grade = false;
1224 } else {
1225 $grade = reset($grades->items[0]->grades);
1226 }
1227
1228 $count = forum_count_user_posts($forum->id, $user->id);
1229
1230 if ($count && $count->postcount > 0) {
39790bd8 1231 $result = new stdClass();
1a96363a
NC
1232 $result->info = get_string("numposts", "forum", $count->postcount);
1233 $result->time = $count->lastpost;
1234 if ($grade) {
1235 $result->info .= ', ' . get_string('grade') . ': ' . $grade->str_long_grade;
90f4745c 1236 }
1a96363a
NC
1237 return $result;
1238 } else if ($grade) {
39790bd8 1239 $result = new stdClass();
1a96363a 1240 $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
4433f871
AD
1241
1242 //datesubmitted == time created. dategraded == time modified or time overridden
1243 //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
94a74f54 1244 //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
4433f871
AD
1245 if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
1246 $result->time = $grade->dategraded;
1247 } else {
1248 $result->time = $grade->datesubmitted;
1249 }
1250
1a96363a 1251 return $result;
caadf009 1252 }
1253 return NULL;
1254}
1255
1256
0a4ac01b 1257/**
df1ba0f4 1258 * @global object
1259 * @global object
1260 * @param object $coure
1261 * @param object $user
1262 * @param object $mod
1263 * @param object $forum
0a4ac01b 1264 */
caadf009 1265function forum_user_complete($course, $user, $mod, $forum) {
1a96363a
NC
1266 global $CFG,$USER, $OUTPUT;
1267 require_once("$CFG->libdir/gradelib.php");
1268
1269 $grades = grade_get_grades($course->id, 'mod', 'forum', $forum->id, $user->id);
1270 if (!empty($grades->items[0]->grades)) {
1271 $grade = reset($grades->items[0]->grades);
1272 echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
1273 if ($grade->str_feedback) {
1274 echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
1275 }
1276 }
caadf009 1277
1f48942e 1278 if ($posts = forum_get_user_posts($forum->id, $user->id)) {
e3ff14ca 1279
65bcf17b 1280 if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $course->id)) {
12e57b92 1281 print_error('invalidcoursemodule');
65bcf17b 1282 }
90f4745c 1283 $discussions = forum_get_user_involved_discussions($forum->id, $user->id);
65bcf17b 1284
1285 foreach ($posts as $post) {
5e7f2b0b 1286 if (!isset($discussions[$post->discussion])) {
90f4745c 1287 continue;
1288 }
7a8d8f22 1289 $discussion = $discussions[$post->discussion];
5e7f2b0b 1290
63e87951 1291 forum_print_post($post, $discussion, $forum, $cm, $course, false, false, false);
5e7f2b0b 1292 }
caadf009 1293 } else {
41905731 1294 echo "<p>".get_string("noposts", "forum")."</p>";
caadf009 1295 }
caadf009 1296}
1297
c38965fb 1298
1299
1300
5e7f2b0b 1301
1302
0a4ac01b 1303/**
df1ba0f4 1304 * @global object
1305 * @global object
1306 * @global object
1307 * @param array $courses
1308 * @param array $htmlarray
0a4ac01b 1309 */
185cfb09 1310function forum_print_overview($courses,&$htmlarray) {
261c6ef0 1311 global $USER, $CFG, $DB, $SESSION;
493f0820 1312
185cfb09 1313 if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1314 return array();
1315 }
f8716988 1316
185cfb09 1317 if (!$forums = get_all_instances_in_courses('forum',$courses)) {
f8716988 1318 return;
1319 }
185cfb09 1320
e8790310
DM
1321 // Courses to search for new posts
1322 $coursessqls = array();
4e445355 1323 $params = array();
185cfb09 1324 foreach ($courses as $course) {
e8790310
DM
1325
1326 // If the user has never entered into the course all posts are pending
1327 if ($course->lastaccess == 0) {
1328 $coursessqls[] = '(f.course = ?)';
1329 $params[] = $course->id;
1330
1331 // Only posts created after the course last access
1332 } else {
1333 $coursessqls[] = '(f.course = ? AND p.created > ?)';
1334 $params[] = $course->id;
1335 $params[] = $course->lastaccess;
1336 }
185cfb09 1337 }
4e445355 1338 $params[] = $USER->id;
e8790310
DM
1339 $coursessql = implode(' OR ', $coursessqls);
1340
1341 $sql = "SELECT f.id, COUNT(*) as count "
1342 .'FROM {forum} f '
1343 .'JOIN {forum_discussions} d ON d.forum = f.id '
1344 .'JOIN {forum_posts} p ON p.discussion = d.id '
1345 ."WHERE ($coursessql) "
1346 .'AND p.userid != ? '
1347 .'GROUP BY f.id';
2b63df96 1348
4e445355 1349 if (!$new = $DB->get_records_sql($sql, $params)) {
185cfb09 1350 $new = array(); // avoid warnings
1351 }
2b63df96 1352
185cfb09 1353 // also get all forum tracking stuff ONCE.
1354 $trackingforums = array();
1355 foreach ($forums as $forum) {
1356 if (forum_tp_can_track_forums($forum)) {
1357 $trackingforums[$forum->id] = $forum;
1358 }
1359 }
2b63df96 1360
185cfb09 1361 if (count($trackingforums) > 0) {
1362 $cutoffdate = isset($CFG->forum_oldpostdays) ? (time() - ($CFG->forum_oldpostdays*24*60*60)) : 0;
1363 $sql = 'SELECT d.forum,d.course,COUNT(p.id) AS count '.
4e445355 1364 ' FROM {forum_posts} p '.
1365 ' JOIN {forum_discussions} d ON p.discussion = d.id '.
1366 ' LEFT JOIN {forum_read} r ON r.postid = p.id AND r.userid = ? WHERE (';
1367 $params = array($USER->id);
1368
d3553951 1369 foreach ($trackingforums as $track) {
4e445355 1370 $sql .= '(d.forum = ? AND (d.groupid = -1 OR d.groupid = 0 OR d.groupid = ?)) OR ';
1371 $params[] = $track->id;
261c6ef0 1372 if (isset($SESSION->currentgroup[$track->course])) {
1373 $groupid = $SESSION->currentgroup[$track->course];
1374 } else {
0209f964
PS
1375 // get first groupid
1376 $groupids = groups_get_all_groups($track->course, $USER->id);
1377 if ($groupids) {
1378 reset($groupids);
1379 $groupid = key($groupids);
261c6ef0 1380 $SESSION->currentgroup[$track->course] = $groupid;
1381 } else {
1382 $groupid = 0;
1383 }
0209f964 1384 unset($groupids);
261c6ef0 1385 }
1386 $params[] = $groupid;
185cfb09 1387 }
1388 $sql = substr($sql,0,-3); // take off the last OR
4e445355 1389 $sql .= ') AND p.modified >= ? AND r.id is NULL GROUP BY d.forum,d.course';
1390 $params[] = $cutoffdate;
185cfb09 1391
4e445355 1392 if (!$unread = $DB->get_records_sql($sql, $params)) {
185cfb09 1393 $unread = array();
1394 }
1395 } else {
1396 $unread = array();
95d71ad3 1397 }
185cfb09 1398
9cba7a8c 1399 if (empty($unread) and empty($new)) {
1400 return;
1401 }
1402
1403 $strforum = get_string('modulename','forum');
9cba7a8c 1404
f8716988 1405 foreach ($forums as $forum) {
185cfb09 1406 $str = '';
f8716988 1407 $count = 0;
185cfb09 1408 $thisunread = 0;
f8716988 1409 $showunread = false;
1410 // either we have something from logs, or trackposts, or nothing.
1411 if (array_key_exists($forum->id, $new) && !empty($new[$forum->id])) {
1412 $count = $new[$forum->id]->count;
90558ec4 1413 }
185cfb09 1414 if (array_key_exists($forum->id,$unread)) {
1415 $thisunread = $unread[$forum->id]->count;
f8716988 1416 $showunread = true;
0d6b9d4f 1417 }
185cfb09 1418 if ($count > 0 || $thisunread > 0) {
e23800b7 1419 $str .= '<div class="overview forum"><div class="name">'.$strforum.': <a title="'.$strforum.'" href="'.$CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id.'">'.
1420 $forum->name.'</a></div>';
6d641063 1421 $str .= '<div class="info"><span class="postsincelogin">';
54294601 1422 $str .= get_string('overviewnumpostssince', 'forum', $count)."</span>";
f8716988 1423 if (!empty($showunread)) {
54294601 1424 $str .= '<div class="unreadposts">'.get_string('overviewnumunread', 'forum', $thisunread).'</div>';
f8716988 1425 }
e23800b7 1426 $str .= '</div></div>';
f8716988 1427 }
2b63df96 1428 if (!empty($str)) {
185cfb09 1429 if (!array_key_exists($forum->course,$htmlarray)) {
1430 $htmlarray[$forum->course] = array();
1431 }
1432 if (!array_key_exists('forum',$htmlarray[$forum->course])) {
1433 $htmlarray[$forum->course]['forum'] = ''; // initialize, avoid warnings
1434 }
1435 $htmlarray[$forum->course]['forum'] .= $str;
1436 }
2b63df96 1437 }
0d6b9d4f 1438}
1439
0a4ac01b 1440/**
1441 * Given a course and a date, prints a summary of all the new
1442 * messages posted in the course since that date
df1ba0f4 1443 *
1444 * @global object
1445 * @global object
1446 * @global object
1447 * @uses CONTEXT_MODULE
1448 * @uses VISIBLEGROUPS
90f4745c 1449 * @param object $course
1450 * @param bool $viewfullnames capability
1451 * @param int $timestart
1452 * @return bool success
0a4ac01b 1453 */
dd97c328 1454function forum_print_recent_activity($course, $viewfullnames, $timestart) {
cb860491 1455 global $CFG, $USER, $DB, $OUTPUT;
caadf009 1456
dd97c328 1457 // do not use log table if possible, it may be huge and is expensive to join with other tables
caadf009 1458
4e445355 1459 if (!$posts = $DB->get_records_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
1460 d.timestart, d.timeend, d.userid AS duserid,
1461 u.firstname, u.lastname, u.email, u.picture
1462 FROM {forum_posts} p
1463 JOIN {forum_discussions} d ON d.id = p.discussion
1464 JOIN {forum} f ON f.id = d.forum
1465 JOIN {user} u ON u.id = p.userid
1466 WHERE p.created > ? AND f.course = ?
1467 ORDER BY p.id ASC", array($timestart, $course->id))) { // order by initial posting date
dd97c328 1468 return false;
1b5910c4 1469 }
1470
f20edd52 1471 $modinfo = get_fast_modinfo($course);
dcde9f02 1472
dd97c328 1473 $groupmodes = array();
1474 $cms = array();
d05956ac 1475
dd97c328 1476 $strftimerecent = get_string('strftimerecent');
d05956ac 1477
dd97c328 1478 $printposts = array();
1479 foreach ($posts as $post) {
1480 if (!isset($modinfo->instances['forum'][$post->forum])) {
1481 // not visible
1482 continue;
1483 }
1484 $cm = $modinfo->instances['forum'][$post->forum];
1485 if (!$cm->uservisible) {
1486 continue;
1487 }
6b7de0bb 1488 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1489
1490 if (!has_capability('mod/forum:viewdiscussion', $context)) {
1491 continue;
1492 }
583b57b4 1493
dd97c328 1494 if (!empty($CFG->forum_enabletimedposts) and $USER->id != $post->duserid
1495 and (($post->timestart > 0 and $post->timestart > time()) or ($post->timeend > 0 and $post->timeend < time()))) {
6b7de0bb 1496 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
dd97c328 1497 continue;
ac1d9a22 1498 }
dd97c328 1499 }
583b57b4 1500
dd97c328 1501 $groupmode = groups_get_activity_groupmode($cm, $course);
b91d6dcd 1502
dd97c328 1503 if ($groupmode) {
6b7de0bb 1504 if ($post->groupid == -1 or $groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $context)) {
dd97c328 1505 // oki (Open discussions have groupid -1)
1506 } else {
1507 // separate mode
1508 if (isguestuser()) {
1509 // shortcut
1510 continue;
1511 }
2b63df96 1512
dd97c328 1513 if (is_null($modinfo->groups)) {
1514 $modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
1b5910c4 1515 }
ac1d9a22 1516
dd97c328 1517 if (!array_key_exists($post->groupid, $modinfo->groups[0])) {
1518 continue;
1519 }
ac1d9a22 1520 }
dd97c328 1521 }
8f7dc7f1 1522
dd97c328 1523 $printposts[] = $post;
1524 }
1525 unset($posts);
8f7dc7f1 1526
dd97c328 1527 if (!$printposts) {
1528 return false;
1529 }
1530
cb860491 1531 echo $OUTPUT->heading(get_string('newforumposts', 'forum').':', 3);
dd97c328 1532 echo "\n<ul class='unlist'>\n";
1533
1534 foreach ($printposts as $post) {
1535 $subjectclass = empty($post->parent) ? ' bold' : '';
1536
1537 echo '<li><div class="head">'.
1538 '<div class="date">'.userdate($post->modified, $strftimerecent).'</div>'.
1539 '<div class="name">'.fullname($post, $viewfullnames).'</div>'.
1540 '</div>';
1541 echo '<div class="info'.$subjectclass.'">';
1542 if (empty($post->parent)) {
1543 echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'">';
1544 } else {
1545 echo '"<a href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.$post->discussion.'&amp;parent='.$post->parent.'#p'.$post->id.'">';
caadf009 1546 }
dd97c328 1547 $post->subject = break_up_long_words(format_string($post->subject, true));
1548 echo $post->subject;
1549 echo "</a>\"</div></li>\n";
caadf009 1550 }
dd97c328 1551
1306c5ea 1552 echo "</ul>\n";
dd97c328 1553
1554 return true;
caadf009 1555}
1556
353228d8 1557/**
1558 * Return grade for given user or all users.
1559 *
df1ba0f4 1560 * @global object
1561 * @global object
1562 * @param object $forum
353228d8 1563 * @param int $userid optional user id, 0 means all users
1564 * @return array array of grades, false if none
1565 */
2b04c41c 1566function forum_get_user_grades($forum, $userid = 0) {
63e87951 1567 global $CFG;
df997f84 1568
63e87951 1569 require_once($CFG->dirroot.'/rating/lib.php');
df997f84 1570
2b04c41c
SH
1571 $ratingoptions = new stdClass;
1572 $ratingoptions->component = 'mod_forum';
1573 $ratingoptions->ratingarea = 'post';
353228d8 1574
63e87951
AD
1575 //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
1576 $ratingoptions->modulename = 'forum';
1577 $ratingoptions->moduleid = $forum->id;
63e87951
AD
1578 $ratingoptions->userid = $userid;
1579 $ratingoptions->aggregationmethod = $forum->assessed;
1580 $ratingoptions->scaleid = $forum->scale;
1581 $ratingoptions->itemtable = 'forum_posts';
1582 $ratingoptions->itemtableusercolumn = 'userid';
353228d8 1583
2b04c41c 1584 $rm = new rating_manager();
63e87951 1585 return $rm->get_user_grades($ratingoptions);
353228d8 1586}
caadf009 1587
0a4ac01b 1588/**
775f811a 1589 * Update activity grades
353228d8 1590 *
a153c9f2 1591 * @category grade
775f811a 1592 * @param object $forum
1593 * @param int $userid specific user only, 0 means all
6b7de0bb 1594 * @param boolean $nullifnone return null if grade does not exist
90f4745c 1595 * @return void
0a4ac01b 1596 */
775f811a 1597function forum_update_grades($forum, $userid=0, $nullifnone=true) {
4e445355 1598 global $CFG, $DB;
775f811a 1599 require_once($CFG->libdir.'/gradelib.php');
caadf009 1600
775f811a 1601 if (!$forum->assessed) {
1602 forum_grade_item_update($forum);
02ebf404 1603
775f811a 1604 } else if ($grades = forum_get_user_grades($forum, $userid)) {
1605 forum_grade_item_update($forum, $grades);
eafb9d9e 1606
775f811a 1607 } else if ($userid and $nullifnone) {
39790bd8 1608 $grade = new stdClass();
775f811a 1609 $grade->userid = $userid;
1610 $grade->rawgrade = NULL;
1611 forum_grade_item_update($forum, $grade);
02ebf404 1612
353228d8 1613 } else {
775f811a 1614 forum_grade_item_update($forum);
1615 }
1616}
1617
1618/**
1619 * Update all grades in gradebook.
df1ba0f4 1620 * @global object
775f811a 1621 */
1622function forum_upgrade_grades() {
1623 global $DB;
1624
1625 $sql = "SELECT COUNT('x')
1626 FROM {forum} f, {course_modules} cm, {modules} m
1627 WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
1628 $count = $DB->count_records_sql($sql);
1629
1630 $sql = "SELECT f.*, cm.idnumber AS cmidnumber, f.course AS courseid
1631 FROM {forum} f, {course_modules} cm, {modules} m
1632 WHERE m.name='forum' AND m.id=cm.module AND cm.instance=f.id";
56d1c48f
EL
1633 $rs = $DB->get_recordset_sql($sql);
1634 if ($rs->valid()) {
775f811a 1635 $pbar = new progress_bar('forumupgradegrades', 500, true);
1636 $i=0;
1637 foreach ($rs as $forum) {
1638 $i++;
1639 upgrade_set_timeout(60*5); // set up timeout, may also abort execution
1640 forum_update_grades($forum, 0, false);
1641 $pbar->update($i, $count, "Updating Forum grades ($i/$count).");
caadf009 1642 }
353228d8 1643 }
56d1c48f 1644 $rs->close();
353228d8 1645}
1646
1647/**
612607bd 1648 * Create/update grade item for given forum
353228d8 1649 *
a153c9f2 1650 * @category grade
df1ba0f4 1651 * @uses GRADE_TYPE_NONE
1652 * @uses GRADE_TYPE_VALUE
1653 * @uses GRADE_TYPE_SCALE
a153c9f2
AD
1654 * @param stdClass $forum Forum object with extra cmidnumber
1655 * @param mixed $grades Optional array/object of grade(s); 'reset' means reset grades in gradebook
612607bd 1656 * @return int 0 if ok
353228d8 1657 */
0b5a80a1 1658function forum_grade_item_update($forum, $grades=NULL) {
612607bd 1659 global $CFG;
1660 if (!function_exists('grade_update')) { //workaround for buggy PHP versions
1661 require_once($CFG->libdir.'/gradelib.php');
353228d8 1662 }
1663
612607bd 1664 $params = array('itemname'=>$forum->name, 'idnumber'=>$forum->cmidnumber);
353228d8 1665
5980d52f 1666 if (!$forum->assessed or $forum->scale == 0) {
612607bd 1667 $params['gradetype'] = GRADE_TYPE_NONE;
353228d8 1668
1669 } else if ($forum->scale > 0) {
1670 $params['gradetype'] = GRADE_TYPE_VALUE;
1671 $params['grademax'] = $forum->scale;
1672 $params['grademin'] = 0;
1673
1674 } else if ($forum->scale < 0) {
1675 $params['gradetype'] = GRADE_TYPE_SCALE;
1676 $params['scaleid'] = -$forum->scale;
1677 }
1678
0b5a80a1 1679 if ($grades === 'reset') {
1680 $params['reset'] = true;
1681 $grades = NULL;
1682 }
1683
1684 return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, $grades, $params);
353228d8 1685}
1686
1687/**
1688 * Delete grade item for given forum
1689 *
a153c9f2
AD
1690 * @category grade
1691 * @param stdClass $forum Forum object
1692 * @return grade_item
353228d8 1693 */
1694function forum_grade_item_delete($forum) {
612607bd 1695 global $CFG;
1696 require_once($CFG->libdir.'/gradelib.php');
1697
b67ec72f 1698 return grade_update('mod/forum', $forum->course, 'mod', 'forum', $forum->id, 0, NULL, array('deleted'=>1));
caadf009 1699}
1700
353228d8 1701
0a4ac01b 1702/**
90f4745c 1703 * This function returns if a scale is being used by one forum
df1ba0f4 1704 *
1705 * @global object
90f4745c 1706 * @param int $forumid
1707 * @param int $scaleid negative number
1708 * @return bool
0a4ac01b 1709 */
0f1a97c2 1710function forum_scale_used ($forumid,$scaleid) {
4e445355 1711 global $DB;
0f1a97c2 1712 $return = false;
65b0e537 1713
4e445355 1714 $rec = $DB->get_record("forum",array("id" => "$forumid","scale" => "-$scaleid"));
65b0e537 1715
fa22fd5f 1716 if (!empty($rec) && !empty($scaleid)) {
0f1a97c2 1717 $return = true;
1718 }
65b0e537 1719
0f1a97c2 1720 return $return;
1721}
1722
85c9ebb9 1723/**
1724 * Checks if scale is being used by any instance of forum
1725 *
1726 * This is used to find out if scale used anywhere
df1ba0f4 1727 *
1728 * @global object
85c9ebb9 1729 * @param $scaleid int
1730 * @return boolean True if the scale is used by any forum
1731 */
1732function forum_scale_used_anywhere($scaleid) {
4e445355 1733 global $DB;
1734 if ($scaleid and $DB->record_exists('forum', array('scale' => -$scaleid))) {
85c9ebb9 1735 return true;
1736 } else {
1737 return false;
1738 }
1739}
1740
0a4ac01b 1741// SQL FUNCTIONS ///////////////////////////////////////////////////////////
9fa49e22 1742
0a4ac01b 1743/**
1744 * Gets a post with all info ready for forum_print_post
1745 * Most of these joins are just to get the forum id
df1ba0f4 1746 *
1747 * @global object
1748 * @global object
90f4745c 1749 * @param int $postid
1750 * @return mixed array of posts or false
0a4ac01b 1751 */
1f48942e 1752function forum_get_post_full($postid) {
4e445355 1753 global $CFG, $DB;
1f48942e 1754
4e445355 1755 return $DB->get_record_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1756 FROM {forum_posts} p
1757 JOIN {forum_discussions} d ON p.discussion = d.id
1758 LEFT JOIN {user} u ON p.userid = u.id
1759 WHERE p.id = ?", array($postid));
1f48942e 1760}
1761
0a4ac01b 1762/**
1763 * Gets posts with all info ready for forum_print_post
1764 * We pass forumid in because we always know it so no need to make a
1765 * complicated join to find it out.
df1ba0f4 1766 *
1767 * @global object
1768 * @global object
90f4745c 1769 * @return mixed array of posts or false
0a4ac01b 1770 */
77efef3e 1771function forum_get_discussion_posts($discussion, $sort, $forumid) {
4e445355 1772 global $CFG, $DB;
1f48942e 1773
4e445355 1774 return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1775 FROM {forum_posts} p
1776 LEFT JOIN {user} u ON p.userid = u.id
1777 WHERE p.discussion = ?
1778 AND p.parent > 0 $sort", array($discussion));
1f48942e 1779}
1780
65bcf17b 1781/**
1782 * Gets all posts in discussion including top parent.
df1ba0f4 1783 *
1784 * @global object
1785 * @global object
1786 * @global object
90f4745c 1787 * @param int $discussionid
1788 * @param string $sort
1789 * @param bool $tracking does user track the forum?
1790 * @return array of posts
65bcf17b 1791 */
90f4745c 1792function forum_get_all_discussion_posts($discussionid, $sort, $tracking=false) {
4e445355 1793 global $CFG, $DB, $USER;
65bcf17b 1794
3c2bf848 1795 $tr_sel = "";
1796 $tr_join = "";
4e445355 1797 $params = array();
3c2bf848 1798
90f4745c 1799 if ($tracking) {
1800 $now = time();
1801 $cutoffdate = $now - ($CFG->forum_oldpostdays * 24 * 3600);
1802 $tr_sel = ", fr.id AS postread";
4e445355 1803 $tr_join = "LEFT JOIN {forum_read} fr ON (fr.postid = p.id AND fr.userid = ?)";
1804 $params[] = $USER->id;
90f4745c 1805 }
1806
4e445355 1807 $params[] = $discussionid;
1808 if (!$posts = $DB->get_records_sql("SELECT p.*, u.firstname, u.lastname, u.email, u.picture, u.imagealt $tr_sel
1809 FROM {forum_posts} p
1810 LEFT JOIN {user} u ON p.userid = u.id
90f4745c 1811 $tr_join
4e445355 1812 WHERE p.discussion = ?
1813 ORDER BY $sort", $params)) {
65bcf17b 1814 return array();
1815 }
1816
1817 foreach ($posts as $pid=>$p) {
90f4745c 1818 if ($tracking) {
1819 if (forum_tp_is_post_old($p)) {
6b7de0bb 1820 $posts[$pid]->postread = true;
90f4745c 1821 }
1822 }
65bcf17b 1823 if (!$p->parent) {
1824 continue;
1825 }
1826 if (!isset($posts[$p->parent])) {
1827 continue; // parent does not exist??
1828 }
1829 if (!isset($posts[$p->parent]->children)) {
1830 $posts[$p->parent]->children = array();
1831 }
1832 $posts[$p->parent]->children[$pid] =& $posts[$pid];
1833 }
1834
1835 return $posts;
1836}
1837
0a4ac01b 1838/**
1839 * Gets posts with all info ready for forum_print_post
1840 * We pass forumid in because we always know it so no need to make a
1841 * complicated join to find it out.
df1ba0f4 1842 *
1843 * @global object
1844 * @global object
1845 * @param int $parent
1846 * @param int $forumid
1847 * @return array
0a4ac01b 1848 */
77efef3e 1849function forum_get_child_posts($parent, $forumid) {
4e445355 1850 global $CFG, $DB;
1f48942e 1851
4e445355 1852 return $DB->get_records_sql("SELECT p.*, $forumid AS forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
1853 FROM {forum_posts} p
1854 LEFT JOIN {user} u ON p.userid = u.id
1855 WHERE p.parent = ?
1856 ORDER BY p.created ASC", array($parent));
1f48942e 1857}
1858
42fb3c85 1859/**
1860 * An array of forum objects that the user is allowed to read/search through.
df1ba0f4 1861 *
1862 * @global object
1863 * @global object
1864 * @global object
1865 * @param int $userid
1866 * @param int $courseid if 0, we look for forums throughout the whole site.
42fb3c85 1867 * @return array of forum objects, or false if no matches
1868 * Forum objects have the following attributes:
1869 * id, type, course, cmid, cmvisible, cmgroupmode, accessallgroups,
1870 * viewhiddentimedposts
1871 */
1872function forum_get_readable_forums($userid, $courseid=0) {
2b63df96 1873
4e445355 1874 global $CFG, $DB, $USER;
6b7de0bb 1875 require_once($CFG->dirroot.'/course/lib.php');
2b63df96 1876
4e445355 1877 if (!$forummod = $DB->get_record('modules', array('name' => 'forum'))) {
12e57b92 1878 print_error('notinstalled', 'forum');
42fb3c85 1879 }
2b63df96 1880
42fb3c85 1881 if ($courseid) {
4e445355 1882 $courses = $DB->get_records('course', array('id' => $courseid));
42fb3c85 1883 } else {
65bcf17b 1884 // If no course is specified, then the user can see SITE + his courses.
4e445355 1885 $courses1 = $DB->get_records('course', array('id' => SITEID));
b1d5d015 1886 $courses2 = enrol_get_users_courses($userid, true, array('modinfo'));
6155150c 1887 $courses = array_merge($courses1, $courses2);
42fb3c85 1888 }
1889 if (!$courses) {
6b7de0bb 1890 return array();
42fb3c85 1891 }
1892
1893 $readableforums = array();
2b63df96 1894
6527b5c2 1895 foreach ($courses as $course) {
1896
f20edd52 1897 $modinfo = get_fast_modinfo($course);
6b7de0bb 1898 if (is_null($modinfo->groups)) {
1899 $modinfo->groups = groups_get_user_groups($course->id, $userid);
42fb3c85 1900 }
2b63df96 1901
6b7de0bb 1902 if (empty($modinfo->instances['forum'])) {
1903 // hmm, no forums?
1904 continue;
1905 }
2b63df96 1906
4e445355 1907 $courseforums = $DB->get_records('forum', array('course' => $course->id));
2b63df96 1908
6b7de0bb 1909 foreach ($modinfo->instances['forum'] as $forumid => $cm) {
1910 if (!$cm->uservisible or !isset($courseforums[$forumid])) {
1911 continue;
1912 }
1913 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1914 $forum = $courseforums[$forumid];
2e945910
AB
1915 $forum->context = $context;
1916 $forum->cm = $cm;
d50704bf 1917
6b7de0bb 1918 if (!has_capability('mod/forum:viewdiscussion', $context)) {
1919 continue;
1920 }
6527b5c2 1921
6b7de0bb 1922 /// group access
1923 if (groups_get_activity_groupmode($cm, $course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
1924 if (is_null($modinfo->groups)) {
1925 $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
1926 }
98da6021 1927 if (isset($modinfo->groups[$cm->groupingid])) {
6b7de0bb 1928 $forum->onlygroups = $modinfo->groups[$cm->groupingid];
1929 $forum->onlygroups[] = -1;
1930 } else {
1931 $forum->onlygroups = array(-1);
1932 }
1933 }
2b63df96 1934
6b7de0bb 1935 /// hidden timed discussions
1936 $forum->viewhiddentimedposts = true;
1937 if (!empty($CFG->forum_enabletimedposts)) {
1938 if (!has_capability('mod/forum:viewhiddentimedposts', $context)) {
1939 $forum->viewhiddentimedposts = false;
1940 }
1941 }
d50704bf 1942
6b7de0bb 1943 /// qanda access
1944 if ($forum->type == 'qanda'
1945 && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
2b63df96 1946
6b7de0bb 1947 // We need to check whether the user has posted in the qanda forum.
1948 $forum->onlydiscussions = array(); // Holds discussion ids for the discussions
1949 // the user is allowed to see in this forum.
1950 if ($discussionspostedin = forum_discussions_user_has_posted_in($forum->id, $USER->id)) {
1951 foreach ($discussionspostedin as $d) {
1952 $forum->onlydiscussions[] = $d->id;
d50704bf 1953 }
42fb3c85 1954 }
1955 }
6b7de0bb 1956
1957 $readableforums[$forum->id] = $forum;
42fb3c85 1958 }
6b7de0bb 1959
1960 unset($modinfo);
1961
42fb3c85 1962 } // End foreach $courses
2b63df96 1963
42fb3c85 1964 return $readableforums;
1965}
1966
bbbf2d40 1967/**
1968 * Returns a list of posts found using an array of search terms.
df1ba0f4 1969 *
1970 * @global object
1971 * @global object
1972 * @global object
1973 * @param array $searchterms array of search terms, e.g. word +word -word
1974 * @param int $courseid if 0, we search through the whole site
1975 * @param int $limitfrom
1976 * @param int $limitnum
1977 * @param int &$totalcount
1978 * @param string $extrasql
1979 * @return array|bool Array of posts found or false
42fb3c85 1980 */
2b63df96 1981function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=50,
42fb3c85 1982 &$totalcount, $extrasql='') {
4e445355 1983 global $CFG, $DB, $USER;
42fb3c85 1984 require_once($CFG->libdir.'/searchlib.php');
1985
1986 $forums = forum_get_readable_forums($USER->id, $courseid);
2b63df96 1987
67875aa1 1988 if (count($forums) == 0) {
6b7de0bb 1989 $totalcount = 0;
67875aa1 1990 return false;
1991 }
42fb3c85 1992
6b7de0bb 1993 $now = round(time(), -2); // db friendly
1994
1995 $fullaccess = array();
1996 $where = array();
4e445355 1997 $params = array();
6b7de0bb 1998
1999 foreach ($forums as $forumid => $forum) {
2000 $select = array();
2001
2002 if (!$forum->viewhiddentimedposts) {
b1d5d015
PS
2003 $select[] = "(d.userid = :userid{$forumid} OR (d.timestart < :timestart{$forumid} AND (d.timeend = 0 OR d.timeend > :timeend{$forumid})))";
2004 $params = array_merge($params, array('userid'.$forumid=>$USER->id, 'timestart'.$forumid=>$now, 'timeend'.$forumid=>$now));
42fb3c85 2005 }
6b7de0bb 2006
2e945910
AB
2007 $cm = $forum->cm;
2008 $context = $forum->context;
ad9c22aa 2009
2010 if ($forum->type == 'qanda'
2011 && !has_capability('mod/forum:viewqandawithoutposting', $context)) {
6b7de0bb 2012 if (!empty($forum->onlydiscussions)) {
cf717dc2 2013 list($discussionid_sql, $discussionid_params) = $DB->get_in_or_equal($forum->onlydiscussions, SQL_PARAMS_NAMED, 'qanda'.$forumid.'_');
4e445355 2014 $params = array_merge($params, $discussionid_params);
2015 $select[] = "(d.id $discussionid_sql OR p.parent = 0)";
d50704bf 2016 } else {
6b7de0bb 2017 $select[] = "p.parent = 0";
d50704bf 2018 }
2019 }
6b7de0bb 2020
2021 if (!empty($forum->onlygroups)) {
cf717dc2 2022 list($groupid_sql, $groupid_params) = $DB->get_in_or_equal($forum->onlygroups, SQL_PARAMS_NAMED, 'grps'.$forumid.'_');
4e445355 2023 $params = array_merge($params, $groupid_params);
2024 $select[] = "d.groupid $groupid_sql";
42fb3c85 2025 }
6b7de0bb 2026
2027 if ($select) {
2028 $selects = implode(" AND ", $select);
b1d5d015
PS
2029 $where[] = "(d.forum = :forum{$forumid} AND $selects)";
2030 $params['forum'.$forumid] = $forumid;
6b7de0bb 2031 } else {
2032 $fullaccess[] = $forumid;
2033 }
2034 }
2035
2036 if ($fullaccess) {
cf717dc2 2037 list($fullid_sql, $fullid_params) = $DB->get_in_or_equal($fullaccess, SQL_PARAMS_NAMED, 'fula');
4e445355 2038 $params = array_merge($params, $fullid_params);
2039 $where[] = "(d.forum $fullid_sql)";
42fb3c85 2040 }
42fb3c85 2041
6b7de0bb 2042 $selectdiscussion = "(".implode(" OR ", $where).")";
42fb3c85 2043
42fb3c85 2044 $messagesearch = '';
2045 $searchstring = '';
2b63df96 2046
42fb3c85 2047 // Need to concat these back together for parser to work.
2048 foreach($searchterms as $searchterm){
2049 if ($searchstring != '') {
2050 $searchstring .= ' ';
2051 }
2052 $searchstring .= $searchterm;
2053 }
2054
2055 // We need to allow quoted strings for the search. The quotes *should* be stripped
2056 // by the parser, but this should be examined carefully for security implications.
2057 $searchstring = str_replace("\\\"","\"",$searchstring);
2058 $parser = new search_parser();
2059 $lexer = new search_lexer($parser);
2060
2061 if ($lexer->parse($searchstring)) {
2062 $parsearray = $parser->get_parsed_array();
0a4ac01b 2063 // Experimental feature under 1.8! MDL-8830
2064 // Use alternative text searches if defined
2065 // This feature only works under mysql until properly implemented for other DBs
2066 // Requires manual creation of text index for forum_posts before enabling it:
2067 // CREATE FULLTEXT INDEX foru_post_tix ON [prefix]forum_posts (subject, message)
2068 // Experimental feature under 1.8! MDL-8830
532daab4 2069 if (!empty($CFG->forum_usetextsearches)) {
004efe66 2070 list($messagesearch, $msparams) = search_generate_text_SQL($parsearray, 'p.message', 'p.subject',
532daab4 2071 'p.userid', 'u.id', 'u.firstname',
2072 'u.lastname', 'p.modified', 'd.forum');
2073 } else {
004efe66 2074 list($messagesearch, $msparams) = search_generate_SQL($parsearray, 'p.message', 'p.subject',
532daab4 2075 'p.userid', 'u.id', 'u.firstname',
2076 'u.lastname', 'p.modified', 'd.forum');
2077 }
004efe66 2078 $params = array_merge($params, $msparams);
42fb3c85 2079 }
2080
4e445355 2081 $fromsql = "{forum_posts} p,
2082 {forum_discussions} d,
2083 {user} u";
42fb3c85 2084
2085 $selectsql = " $messagesearch
2086 AND p.discussion = d.id
2087 AND p.userid = u.id
2088 AND $selectdiscussion
2089 $extrasql";
2090
2091 $countsql = "SELECT COUNT(*)
2092 FROM $fromsql
2093 WHERE $selectsql";
2094
7f094149 2095 $searchsql = "SELECT p.*,
42fb3c85 2096 d.forum,
2097 u.firstname,
2098 u.lastname,
2099 u.email,
8ba59d07 2100 u.picture,
25e77f4f 2101 u.imagealt
42fb3c85 2102 FROM $fromsql
2103 WHERE $selectsql
2104 ORDER BY p.modified DESC";
2105
4e445355 2106 $totalcount = $DB->count_records_sql($countsql, $params);
d50704bf 2107
4e445355 2108 return $DB->get_records_sql($searchsql, $params, $limitfrom, $limitnum);
42fb3c85 2109}
2110
0a4ac01b 2111/**
2112 * Returns a list of ratings for a particular post - sorted.
df1ba0f4 2113 *
2b04c41c
SH
2114 * TODO: Check if this function is actually used anywhere.
2115 * Up until the fix for MDL-27471 this function wasn't even returning.
2116 *
2117 * @param stdClass $context
90f4745c 2118 * @param int $postid
2119 * @param string $sort
df1ba0f4 2120 * @return array Array of ratings or false
0a4ac01b 2121 */
2b04c41c
SH
2122function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
2123 $options = new stdClass;
2124 $options->context = $context;
2125 $options->component = 'mod_forum';
2126 $options->ratingarea = 'post';
63e87951
AD
2127 $options->itemid = $postid;
2128 $options->sort = "ORDER BY $sort";
2129
0bb69ab3 2130 $rm = new rating_manager();
2b04c41c 2131 return $rm->get_all_ratings_for_item($options);
9fa49e22 2132}
2133
0a4ac01b 2134/**
2135 * Returns a list of all new posts that have not been mailed yet
df1ba0f4 2136 *
df1ba0f4 2137 * @param int $starttime posts created after this time
2138 * @param int $endtime posts created before this
2139 * @param int $now used for timed discussions only
2140 * @return array
0a4ac01b 2141 */
90f4745c 2142function forum_get_unmailed_posts($starttime, $endtime, $now=null) {
4e445355 2143 global $CFG, $DB;
90f4745c 2144
4e445355 2145 $params = array($starttime, $endtime);
90f4745c 2146 if (!empty($CFG->forum_enabletimedposts)) {
2147 if (empty($now)) {
2148 $now = time();
2149 }
4e445355 2150 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2151 $params[] = $now;
2152 $params[] = $now;
90f4745c 2153 } else {
2154 $timedsql = "";
2155 }
2156
4e445355 2157 return $DB->get_records_sql("SELECT p.*, d.course, d.forum
2158 FROM {forum_posts} p
2159 JOIN {forum_discussions} d ON d.id = p.discussion
65b0e537 2160 WHERE p.mailed = 0
4e445355 2161 AND p.created >= ?
2162 AND (p.created < ? OR p.mailnow = 1)
90f4745c 2163 $timedsql
4e445355 2164 ORDER BY p.modified ASC", $params);
1f48942e 2165}
2166
0a4ac01b 2167/**
2168 * Marks posts before a certain time as being mailed already
df1ba0f4 2169 *
2170 * @global object
2171 * @global object
2172 * @param int $endtime
2173 * @param int $now Defaults to time()
2174 * @return bool
0a4ac01b 2175 */
90f4745c 2176function forum_mark_old_posts_as_mailed($endtime, $now=null) {
4e445355 2177 global $CFG, $DB;
90f4745c 2178 if (empty($now)) {
2179 $now = time();
2180 }
2181
2182 if (empty($CFG->forum_enabletimedposts)) {
4e445355 2183 return $DB->execute("UPDATE {forum_posts}
90f4745c 2184 SET mailed = '1'
4e445355 2185 WHERE (created < ? OR mailnow = 1)
3342790c 2186 AND mailed = 0", array($endtime));
90f4745c 2187
0f620d4b 2188 } else {
4e445355 2189 return $DB->execute("UPDATE {forum_posts}
90f4745c 2190 SET mailed = '1'
2191 WHERE discussion NOT IN (SELECT d.id
4e445355 2192 FROM {forum_discussions} d
2193 WHERE d.timestart > ?)
2194 AND (created < ? OR mailnow = 1)
3342790c 2195 AND mailed = 0", array($now, $endtime));
0f620d4b 2196 }
3ecca1ee 2197}
2198
0a4ac01b 2199/**
2200 * Get all the posts for a user in a forum suitable for forum_print_post
df1ba0f4 2201 *
2202 * @global object
2203 * @global object
2204 * @uses CONTEXT_MODULE
2205 * @return array
0a4ac01b 2206 */
1f48942e 2207function forum_get_user_posts($forumid, $userid) {
4e445355 2208 global $CFG, $DB;
1f48942e 2209
90f4745c 2210 $timedsql = "";
4e445355 2211 $params = array($forumid, $userid);
2212
90f4745c 2213 if (!empty($CFG->forum_enabletimedposts)) {
2214 $cm = get_coursemodule_from_instance('forum', $forumid);
2215 if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2216 $now = time();
4e445355 2217 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2218 $params[] = $now;
2219 $params[] = $now;
6b7de0bb 2220 }
90f4745c 2221 }
2222
4e445355 2223 return $DB->get_records_sql("SELECT p.*, d.forum, u.firstname, u.lastname, u.email, u.picture, u.imagealt
2224 FROM {forum} f
2225 JOIN {forum_discussions} d ON d.forum = f.id
2226 JOIN {forum_posts} p ON p.discussion = d.id
2227 JOIN {user} u ON u.id = p.userid
2228 WHERE f.id = ?
2229 AND p.userid = ?
90f4745c 2230 $timedsql
4e445355 2231 ORDER BY p.modified ASC", $params);
1f48942e 2232}
2233
90f4745c 2234/**
2235 * Get all the discussions user participated in
df1ba0f4 2236 *
2237 * @global object
2238 * @global object
2239 * @uses CONTEXT_MODULE
90f4745c 2240 * @param int $forumid
2241 * @param int $userid
df1ba0f4 2242 * @return array Array or false
90f4745c 2243 */
2244function forum_get_user_involved_discussions($forumid, $userid) {
4e445355 2245 global $CFG, $DB;
90f4745c 2246
2247 $timedsql = "";
4e445355 2248 $params = array($forumid, $userid);
90f4745c 2249 if (!empty($CFG->forum_enabletimedposts)) {
2250 $cm = get_coursemodule_from_instance('forum', $forumid);
2251 if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2252 $now = time();
4e445355 2253 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2254 $params[] = $now;
2255 $params[] = $now;
6b7de0bb 2256 }
90f4745c 2257 }
2258
4e445355 2259 return $DB->get_records_sql("SELECT DISTINCT d.*
2260 FROM {forum} f
2261 JOIN {forum_discussions} d ON d.forum = f.id
2262 JOIN {forum_posts} p ON p.discussion = d.id
2263 WHERE f.id = ?
2264 AND p.userid = ?
2265 $timedsql", $params);
90f4745c 2266}
2267
2268/**
2269 * Get all the posts for a user in a forum suitable for forum_print_post
df1ba0f4 2270 *
2271 * @global object
2272 * @global object
90f4745c 2273 * @param int $forumid
2274 * @param int $userid
2275 * @return array of counts or false
2276 */
2277function forum_count_user_posts($forumid, $userid) {
4e445355 2278 global $CFG, $DB;
90f4745c 2279
2280 $timedsql = "";
4e445355 2281 $params = array($forumid, $userid);
90f4745c 2282 if (!empty($CFG->forum_enabletimedposts)) {
2283 $cm = get_coursemodule_from_instance('forum', $forumid);
2284 if (!has_capability('mod/forum:viewhiddentimedposts' , get_context_instance(CONTEXT_MODULE, $cm->id))) {
2285 $now = time();
4e445355 2286 $timedsql = "AND (d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?))";
2287 $params[] = $now;
2288 $params[] = $now;
6b7de0bb 2289 }
90f4745c 2290 }
2291
4e445355 2292 return $DB->get_record_sql("SELECT COUNT(p.id) AS postcount, MAX(p.modified) AS lastpost
2293 FROM {forum} f
2294 JOIN {forum_discussions} d ON d.forum = f.id
2295 JOIN {forum_posts} p ON p.discussion = d.id
2296 JOIN {user} u ON u.id = p.userid
2297 WHERE f.id = ?
2298 AND p.userid = ?
2299 $timedsql", $params);
90f4745c 2300}
2301
0a4ac01b 2302/**
2303 * Given a log entry, return the forum post details for it.
df1ba0f4 2304 *
2305 * @global object
2306 * @global object
2307 * @param object $log
2308 * @return array|null
0a4ac01b 2309 */
1f48942e 2310function forum_get_post_from_log($log) {
4e445355 2311 global $CFG, $DB;
1f48942e 2312
2313 if ($log->action == "add post") {
2314
4e445355 2315 return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
8f7dc7f1 2316 u.firstname, u.lastname, u.email, u.picture
4e445355 2317 FROM {forum_discussions} d,
2318 {forum_posts} p,
2319 {forum} f,
2320 {user} u
2321 WHERE p.id = ?
65b0e537 2322 AND d.id = p.discussion
2323 AND p.userid = u.id
8f7dc7f1 2324 AND u.deleted <> '1'
4e445355 2325 AND f.id = d.forum", array($log->info));
1f48942e 2326
2327
2328 } else if ($log->action == "add discussion") {
2329
4e445355 2330 return $DB->get_record_sql("SELECT p.*, f.type AS forumtype, d.forum, d.groupid,
8f7dc7f1 2331 u.firstname, u.lastname, u.email, u.picture
4e445355 2332 FROM {forum_discussions} d,
2333 {forum_posts} p,
2334 {forum} f,
2335 {user} u
2336 WHERE d.id = ?
65b0e537 2337 AND d.firstpost = p.id
2338 AND p.userid = u.id
8f7dc7f1 2339 AND u.deleted <> '1'
4e445355 2340 AND f.id = d.forum", array($log->info));
1f48942e 2341 }
2342 return NULL;
2343}
2344
0a4ac01b 2345/**
2346 * Given a discussion id, return the first post from the discussion
df1ba0f4 2347 *
2348 * @global object
2349 * @global object
2350 * @param int $dicsussionid
2351 * @return array
0a4ac01b 2352 */
d05956ac 2353function forum_get_firstpost_from_discussion($discussionid) {
4e445355 2354 global $CFG, $DB;
d05956ac 2355
4e445355 2356 return $DB->get_record_sql("SELECT p.*
2357 FROM {forum_discussions} d,
2358 {forum_posts} p
2359 WHERE d.id = ?
2360 AND d.firstpost = p.id ", array($discussionid));
d05956ac 2361}
2362
0a4ac01b 2363/**
90f4745c 2364 * Returns an array of counts of replies to each discussion
df1ba0f4 2365 *
2366 * @global object
2367 * @global object
2368 * @param int $forumid
2369 * @param string $forumsort
2370 * @param int $limit
2371 * @param int $page
2372 * @param int $perpage
2373 * @return array
0a4ac01b 2374 */
90f4745c 2375function forum_count_discussion_replies($forumid, $forumsort="", $limit=-1, $page=-1, $perpage=0) {
4e445355 2376 global $CFG, $DB;
1f48942e 2377
90f4745c 2378 if ($limit > 0) {
2379 $limitfrom = 0;
2380 $limitnum = $limit;
2381 } else if ($page != -1) {
2382 $limitfrom = $page*$perpage;
2383 $limitnum = $perpage;
2384 } else {
2385 $limitfrom = 0;
2386 $limitnum = 0;
2387 }
2388
2389 if ($forumsort == "") {
2390 $orderby = "";
2391 $groupby = "";
2392
2393 } else {
2394 $orderby = "ORDER BY $forumsort";
2395 $groupby = ", ".strtolower($forumsort);
2396 $groupby = str_replace('desc', '', $groupby);
2397 $groupby = str_replace('asc', '', $groupby);
2398 }
2399
bfeb10b7 2400 if (($limitfrom == 0 and $limitnum == 0) or $forumsort == "") {
2401 $sql = "SELECT p.discussion, COUNT(p.id) AS replies, MAX(p.id) AS lastpostid
4e445355 2402 FROM {forum_posts} p
2403 JOIN {forum_discussions} d ON p.discussion = d.id
2404 WHERE p.parent > 0 AND d.forum = ?
bfeb10b7 2405 GROUP BY p.discussion";
4e445355 2406 return $DB->get_records_sql($sql, array($forumid));
bfeb10b7 2407
90f4745c 2408 } else {
bfeb10b7 2409 $sql = "SELECT p.discussion, (COUNT(p.id) - 1) AS replies, MAX(p.id) AS lastpostid
4e445355 2410 FROM {forum_posts} p
2411 JOIN {forum_discussions} d ON p.discussion = d.id
2412 WHERE d.forum = ?
bfeb10b7 2413 GROUP BY p.discussion $groupby
2414 $orderby";
4e445355 2415 return $DB->get_records_sql("SELECT * FROM ($sql) sq", array($forumid), $limitfrom, $limitnum);
90f4745c 2416 }
2417}
2418
df1ba0f4 2419/**
2420 * @global object
2421 * @global object
2422 * @global object
2423 * @staticvar array $cache
2424 * @param object $forum
2425 * @param object $cm
2426 * @param object $course
2427 * @return mixed
2428 */
90f4745c 2429function forum_count_discussions($forum, $cm, $course) {
4e445355 2430 global $CFG, $DB, $USER;
90f4745c 2431
2432 static $cache = array();
2433
2434 $now = round(time(), -2); // db cache friendliness
2435
4e445355 2436 $params = array($course->id);
2437
90f4745c 2438 if (!isset($cache[$course->id])) {
2439 if (!empty($CFG->forum_enabletimedposts)) {
4e445355 2440 $timedsql = "AND d.timestart < ? AND (d.timeend = 0 OR d.timeend > ?)";
2441 $params[] = $now;
2442 $params[] = $now;
90f4745c 2443 } else {
2444 $timedsql = "";
2445 }
2446
2447 $sql = "SELECT f.id, COUNT(d.id) as dcount
4e445355 2448 FROM {forum} f
2449 JOIN {forum_discussions} d ON d.forum = f.id
2450 WHERE f.course = ?
90f4745c 2451 $timedsql
2452 GROUP BY f.id";
a48e8c4b 2453
4e445355 2454 if ($counts = $DB->get_records_sql($sql, $params)) {
90f4745c 2455 foreach ($counts as $count) {
2456 $counts[$count->id] = $count->dcount;
2457 }
2458 $cache[$course->id] = $counts;
2459 } else {
2460 $cache[$course->id] = array();
2461 }
a48e8c4b 2462 }
90f4745c 2463
2464 if (empty($cache[$course->id][$forum->id])) {
2465 return 0;
a48e8c4b 2466 }
90f4745c 2467
2468 $groupmode = groups_get_activity_groupmode($cm, $course);
2469
2470 if ($groupmode != SEPARATEGROUPS) {
2471 return $cache[$course->id][$forum->id];
1f48942e 2472 }
90f4745c 2473
948091f4 2474 if (has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
90f4745c 2475 return $cache[$course->id][$forum->id];
2476 }
2477
2478 require_once($CFG->dirroot.'/course/lib.php');
2479
f20edd52 2480 $modinfo = get_fast_modinfo($course);
90f4745c 2481 if (is_null($modinfo->groups)) {
2482 $modinfo->groups = groups_get_user_groups($course->id, $USER->id);
2483 }
2484
98da6021
PS
2485 if (array_key_exists($cm->groupingid, $modinfo->groups)) {
2486 $mygroups = $modinfo->groups[$cm->groupingid];
90f4745c 2487 } else {
98da6021 2488 $mygroups = false; // Will be set below
90f4745c 2489 }
2490
2491 // add all groups posts
2492 if (empty($mygroups)) {
2493 $mygroups = array(-1=>-1);
2494 } else {
2495 $mygroups[-1] = -1;
2496 }
4e445355 2497
2498 list($mygroups_sql, $params) = $DB->get_in_or_equal($mygroups);
2499 $params[] = $forum->id;
90f4745c 2500
2501 if (!empty($CFG->forum_enabletimedposts)) {
2502 $timedsql = "AND d.timestart < $now AND (d.timeend = 0 OR d.timeend > $now)";
4e445355 2503 $params[] = $now;
2504 $params[] = $now;
90f4745c 2505 } else {
2506 $timedsql = "";
2507 }
2508
2509 $sql = "SELECT COUNT(d.id)
4e445355 2510 FROM {forum_discussions} d
342650a7 2511 WHERE d.groupid $mygroups_sql AND d.forum = ?
90f4745c 2512 $timedsql";
2513
4e445355 2514 return $DB->get_field_sql($sql, $params);
1f48942e 2515}
2516
0a4ac01b 2517/**
63e87951 2518 * How many posts by other users are unrated by a given user in the given discussion?
df1ba0f4 2519 *
2b04c41c
SH
2520 * TODO: Is this function still used anywhere?
2521 *
df1ba0f4 2522 * @param int $discussionid
2523 * @param int $userid
2524 * @return mixed
0a4ac01b 2525 */
1f48942e 2526function forum_count_unrated_posts($discussionid, $userid) {
4e445355 2527 global $CFG, $DB;
2b04c41c
SH
2528
2529 $sql = "SELECT COUNT(*) as num
2530 FROM {forum_posts}
2531 WHERE parent > 0
2532 AND discussion = :discussionid
2533 AND userid <> :userid";
2534 $params = array('discussionid' => $discussionid, 'userid' => $userid);
2535 $posts = $DB->get_record_sql($sql, $params);
2536 if ($posts) {
2537 $sql = "SELECT count(*) as num
2538 FROM {forum_posts} p,
2539 {rating} r
2540 WHERE p.discussion = :discussionid AND
2541 p.id = r.itemid AND
2542 r.userid = userid AND
2543 r.component = 'mod_forum' AND
2544 r.ratingarea = 'post'";
2545 $rated = $DB->get_record_sql($sql, $params);
2546 if ($rated) {
2547 if ($posts->num > $rated->num) {
2548 return $posts->num - $rated->num;
1f48942e 2549 } else {
2550 return 0; // Just in case there was a counting error
2551 }
2552 } else {
2553 return $posts->num;
2554 }
2555 } else {
2556 return 0;
2557 }
2558}
2559
0a4ac01b 2560/**
2561 * Get all discussions in a forum
df1ba0f4 2562 *
2563 * @global object
2564 * @global object
2565 * @global object
2566 * @uses CONTEXT_MODULE
2567 * @uses VISIBLEGROUPS
2568 * @param object $cm
2569 * @param string $forumsort
2570 * @param bool $fullpost
2571 * @param int $unused
2572 * @param int $limit
2573 * @param bool $userlastmodified
2574 * @param int $page
2575 * @param int $perpage
2576 * @return array
0a4ac01b 2577 */
90f4745c 2578function forum_get_discussions($cm, $forumsort="d.timemodified DESC", $fullpost=true, $unused=-1, $limit=-1, $userlastmodified=false, $page=-1, $perpage=0) {
4e445355 2579 global $CFG, $DB, $USER;
0fcac008 2580
3d284127 2581 $timelimit = '';
2582
90f4745c 2583 $now = round(time(), -2);
4e445355 2584 $params = array($cm->instance);
90f4745c 2585
4436a63b 2586 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2b63df96 2587
4436a63b 2588 if (!has_capability('mod/forum:viewdiscussion', $modcontext)) { /// User must have perms to view discussions
2589 return array();
2590 }
2591
2592 if (!empty($CFG->forum_enabletimedposts)) { /// Users must fulfill timed posts
2b63df96 2593
0468976c 2594 if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
4e445355 2595 $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2596 $params[] = $now;
2597 $params[] = $now;
90f4745c 2598 if (isloggedin()) {
4e445355 2599 $timelimit .= " OR d.userid = ?";
2600 $params[] = $USER->id;
3d284127 2601 }
90f4745c 2602 $timelimit .= ")";
fbc21e82 2603 }
fbc21e82 2604 }
1f48942e 2605
90f4745c 2606 if ($limit > 0) {
2607 $limitfrom = 0;
2608 $limitnum = $limit;
2609 } else if ($page != -1) {
2610 $limitfrom = $page*$perpage;
2611 $limitnum = $perpage;
2612 } else {
2613 $limitfrom = 0;
2614 $limitnum = 0;
90ec387a 2615 }
8f0cd6ef 2616
90f4745c 2617 $groupmode = groups_get_activity_groupmode($cm);
2618 $currentgroup = groups_get_activity_group($cm);
353228d8 2619
fffa8b35 2620 if ($groupmode) {
2621 if (empty($modcontext)) {
2622 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2623 }
2624
2625 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2626 if ($currentgroup) {
4e445355 2627 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2628 $params[] = $currentgroup;
fffa8b35 2629 } else {
2630 $groupselect = "";
2631 }
2632
2633 } else {
2634 //seprate groups without access all
2635 if ($currentgroup) {
4e445355 2636 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2637 $params[] = $currentgroup;
fffa8b35 2638 } else {
2639 $groupselect = "AND d.groupid = -1";
2640 }
2641 }
353228d8 2642 } else {
02509fe6 2643 $groupselect = "";
2644 }
2862b309 2645
fffa8b35 2646
29507631 2647 if (empty($forumsort)) {
2648 $forumsort = "d.timemodified DESC";
2649 }
2ab968e9 2650 if (empty($fullpost)) {
b879effb 2651 $postdata = "p.id,p.subject,p.modified,p.discussion,p.userid";
2ab968e9 2652 } else {
2653 $postdata = "p.*";
2654 }
9197e147 2655
13597d01 2656 if (empty($userlastmodified)) { // We don't need to know this
fffa8b35 2657 $umfields = "";
2658 $umtable = "";
13597d01 2659 } else {
fffa8b35 2660 $umfields = ", um.firstname AS umfirstname, um.lastname AS umlastname";
4e445355 2661 $umtable = " LEFT JOIN {user} um ON (d.usermodified = um.id)";
90f4745c 2662 }
2663
2664 $sql = "SELECT $postdata, d.name, d.timemodified, d.usermodified, d.groupid, d.timestart, d.timeend,
2665 u.firstname, u.lastname, u.email, u.picture, u.imagealt $umfields
4e445355 2666 FROM {forum_discussions} d
2667 JOIN {forum_posts} p ON p.discussion = d.id
2668 JOIN {user} u ON p.userid = u.id
90f4745c 2669 $umtable
4e445355 2670 WHERE d.forum = ? AND p.parent = 0
90f4745c 2671 $timelimit $groupselect
2672 ORDER BY $forumsort";
4e445355 2673 return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
90f4745c 2674}
2675
df1ba0f4 2676/**
2677 *
2678 * @global object
2679 * @global object
2680 * @global object
2681 * @uses CONTEXT_MODULE
2682 * @uses VISIBLEGROUPS
2683 * @param object $cm
2684 * @return array
2685 */
bfeb10b7 2686function forum_get_discussions_unread($cm) {
4e445355 2687 global $CFG, $DB, $USER;
90f4745c 2688
2689 $now = round(time(), -2);
ee151230 2690 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
35716b86 2691
ee151230 2692 $params = array();
90f4745c 2693 $groupmode = groups_get_activity_groupmode($cm);
2694 $currentgroup = groups_get_activity_group($cm);
2695
2696 if ($groupmode) {
2697 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2698
2699 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2700 if ($currentgroup) {
ee151230
AD
2701 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2702 $params['currentgroup'] = $currentgroup;
90f4745c 2703 } else {
2704 $groupselect = "";
2705 }
2706
2707 } else {
ee151230 2708 //separate groups without access all
90f4745c 2709 if ($currentgroup) {
ee151230
AD
2710 $groupselect = "AND (d.groupid = :currentgroup OR d.groupid = -1)";
2711 $params['currentgroup'] = $currentgroup;
90f4745c 2712 } else {
2713 $groupselect = "AND d.groupid = -1";
2714 }
2715 }
2716 } else {
2717 $groupselect = "";
13597d01 2718 }
2719
90f4745c 2720 if (!empty($CFG->forum_enabletimedposts)) {
ee151230
AD
2721 $timedsql = "AND d.timestart < :now1 AND (d.timeend = 0 OR d.timeend > :now2)";
2722 $params['now1'] = $now;
2723 $params['now2'] = $now;
90f4745c 2724 } else {
2725 $timedsql = "";
2726 }
2727
2728 $sql = "SELECT d.id, COUNT(p.id) AS unread
4e445355 2729 FROM {forum_discussions} d
2730 JOIN {forum_posts} p ON p.discussion = d.id
2731 LEFT JOIN {forum_read} r ON (r.postid = p.id AND r.userid = $USER->id)
90f4745c 2732 WHERE d.forum = {$cm->instance}
ee151230 2733 AND p.modified >= :cutoffdate AND r.id is NULL
90f4745c 2734 $groupselect
4e445355 2735 $timedsql
bfeb10b7 2736 GROUP BY d.id";
ee151230
AD
2737 $params['cutoffdate'] = $cutoffdate;
2738
4e445355 2739 if ($unreads = $DB->get_records_sql($sql, $params)) {
90f4745c 2740 foreach ($unreads as $unread) {
2741 $unreads[$unread->id] = $unread->unread;
2742 }
2743 return $unreads;
2744 } else {
2745 return array();
2746 }
1f48942e 2747}
2748
df1ba0f4 2749/**
2750 * @global object
2751 * @global object
2752 * @global object
2753 * @uses CONEXT_MODULE
2754 * @uses VISIBLEGROUPS
2755 * @param object $cm
2756 * @return array
2757 */
90f4745c 2758function forum_get_discussions_count($cm) {
4e445355 2759 global $CFG, $DB, $USER;
90f4745c 2760
2761 $now = round(time(), -2);
4e445355 2762 $params = array($cm->instance);
90f4745c 2763 $groupmode = groups_get_activity_groupmode($cm);
2764 $currentgroup = groups_get_activity_group($cm);
2765
2766 if ($groupmode) {
2767 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2768
2769 if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
2770 if ($currentgroup) {
4e445355 2771 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2772 $params[] = $currentgroup;
90f4745c 2773 } else {
2774 $groupselect = "";
2775 }
2776
2777 } else {
2778 //seprate groups without access all
2779 if ($currentgroup) {
4e445355 2780 $groupselect = "AND (d.groupid = ? OR d.groupid = -1)";
2781 $params[] = $currentgroup;
90f4745c 2782 } else {
2783 $groupselect = "AND d.groupid = -1";
2784 }
2785 }
2786 } else {
2787 $groupselect = "";
2788 }
2789
2790 $cutoffdate = $now - ($CFG->forum_oldpostdays*24*60*60);
2791
2792 $timelimit = "";
2793
2794 if (!empty($CFG->forum_enabletimedposts)) {
2795
2796 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
2797
2798 if (!has_capability('mod/forum:viewhiddentimedposts', $modcontext)) {
4e445355 2799 $timelimit = " AND ((d.timestart <= ? AND (d.timeend = 0 OR d.timeend > ?))";
2800 $params[] = $now;
2801 $params[] = $now;
90f4745c 2802 if (isloggedin()) {
4e445355 2803 $timelimit .= " OR d.userid = ?";
2804 $params[] = $USER->id;
90f4745c 2805 }
2806 $timelimit .= ")";
2807 }
2808 }
2809
2810 $sql = "SELECT COUNT(d.id)
4e445355 2811 FROM {forum_discussions} d
2812 JOIN {forum_posts} p ON p.discussion = d.id
2813 WHERE d.forum = ? AND p.parent = 0
2814 $groupselect $timelimit";
90f4745c 2815
4e445355 2816 return $DB->get_field_sql($sql, $params);
90f4745c 2817}
1f48942e 2818
2819
0a4ac01b 2820/**
2821 * Get all discussions started by a particular user in a course (or group)
2822 * This function no longer used ...
df1ba0f4 2823 *
2824 * @todo Remove this function if no longer used
2825 * @global object
2826 * @global object
2827 * @param int $courseid
2828 * @param int $userid
2829 * @param int $groupid
2830 * @return array
0a4ac01b 2831 */
b656e2a9 2832function forum_get_user_discussions($courseid, $userid, $groupid=0) {
4e445355 2833 global $CFG, $DB;
2834 $params = array($courseid, $userid);
b656e2a9 2835 if ($groupid) {
4e445355 2836 $groupselect = " AND d.groupid = ? ";
2837 $params[] = $groupid;
b656e2a9 2838 } else {
2839 $groupselect = "";
2840 }
2841
4e445355 2842 return $DB->get_records_sql("SELECT p.*, d.groupid, u.firstname, u.lastname, u.email, u.picture, u.imagealt,
ebc3bd2b 2843 f.type as forumtype, f.name as forumname, f.id as forumid
4e445355 2844 FROM {forum_discussions} d,
2845 {forum_posts} p,
2846 {user} u,
2847 {forum} f
2848 WHERE d.course = ?
65b0e537 2849 AND p.discussion = d.id
2850 AND p.parent = 0
2851 AND p.userid = u.id
4e445355 2852 AND u.id = ?
b656e2a9 2853 AND d.forum = f.id $groupselect
4e445355 2854 ORDER BY p.created DESC", $params);
1f48942e 2855}
2856
0a4ac01b 2857/**
702dc57b 2858 * Get the list of potential subscribers to a forum.
8d8d0bfa 2859 *
2860 * @param object $forumcontext the forum context.
2861 * @param integer $groupid the id of a group, or 0 for all groups.
2862 * @param string $fields the list of fields to return for each user. As for get_users_by_capability.
2863 * @param string $sort sort order. As for get_users_by_capability.
2864 * @return array list of users.
0a4ac01b 2865 */
8d8d0bfa 2866function forum_get_potential_subscribers($forumcontext, $groupid, $fields, $sort) {
72650054
PS
2867 global $DB;
2868
bc196cbe
PS
2869 // only active enrolled users or everybody on the frontpage
2870 list($esql, $params) = get_enrolled_sql($forumcontext, '', $groupid, true);
72650054
PS
2871
2872 $sql = "SELECT $fields
2873 FROM {user} u
2874 JOIN ($esql) je ON je.id = u.id";
2875 if ($sort) {
2876 $sql = "$sql ORDER BY $sort";
2877 } else {
2878 $sql = "$sql ORDER BY u.lastname ASC, u.firstname ASC";
2879 }
2880
2881 return $DB->get_records_sql($sql, $params);
8d8d0bfa 2882}
0a4ac01b 2883
8d8d0bfa 2884/**
2885 * Returns list of user objects that are subscribed to this forum
2886 *
df1ba0f4 2887 * @global object
2888 * @global object
8d8d0bfa 2889 * @param object $course the course
2890 * @param forum $forum the forum
2891 * @param integer $groupid group id, or 0 for all.
2892 * @param object $context the forum context, to save re-fetching it where possible.
c35ae136 2893 * @param string $fields requested user fields (with "u." table prefix)
8d8d0bfa 2894 * @return array list of users.
2895 */
c35ae136 2896function forum_subscribed_users($course, $forum, $groupid=0, $context = null, $fields = null) {
4e445355 2897 global $CFG, $DB;
6673d7bd 2898
c35ae136
PS
2899 if (empty($fields)) {
2900 $fields ="u.id,
2901 u.username,
2902 u.firstname,
2903 u.lastname,
2904 u.maildisplay,
2905 u.mailformat,
2906 u.maildigest,
2907 u.imagealt,
2908 u.email,
d8aa5ec7 2909 u.emailstop,
c35ae136
PS
2910 u.city,
2911 u.country,
2912 u.lastaccess,
2913 u.lastlogin,
2914 u.picture,
2915 u.timezone,
2916 u.theme,
2917 u.lang,
2918 u.trackforums,
2919 u.mnethostid";
2920 }
4e445355 2921
72650054
PS
2922 if (empty($context)) {
2923 $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id);
2924 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
669f2499 2925 }
2926
72650054
PS
2927 if (forum_is_forcesubscribed($forum)) {
2928 $results = forum_get_potential_subscribers($context, $groupid, $fields, "u.email ASC");
df1c2c71 2929
72650054
PS
2930 } else {
2931 // only active enrolled users or everybody on the frontpage
2932 list($esql, $params) = get_enrolled_sql($context, '', $groupid, true);
2933 $params['forumid'] = $forum->id;
2934 $results = $DB->get_records_sql("SELECT $fields
2935 FROM {user} u
2936 JOIN ($esql) je ON je.id = u.id
2937 JOIN {forum_subscriptions} s ON s.userid = u.id
2938 WHERE s.forum = :forumid
2939 ORDER BY u.email ASC", $params);
669f2499 2940 }
2941
df1c2c71 2942 // Guest user should never be subscribed to a forum.
72650054 2943 unset($results[$CFG->siteguest]);
df1c2c71 2944
669f2499 2945 return $results;
1f48942e 2946}
9fa49e22 2947
067675c0 2948
2949
0a4ac01b 2950// OTHER FUNCTIONS ///////////////////////////////////////////////////////////
f93f848a 2951
2952
df1ba0f4 2953/**
2954 * @global object
2955 * @global object
2956 * @param int $courseid
2957 * @param string $type
2958 */
11b0c469 2959function forum_get_course_forum($courseid, $type) {
2960// How to set up special 1-per-course forums
9146b979 2961 global $CFG, $DB, $OUTPUT;
a6fcdf98 2962
4e445355 2963 if ($forums = $DB->get_records_select("forum", "course = ? AND type = ?", array($courseid, $type), "id ASC")) {
65b0e537 2964 // There should always only be ONE, but with the right combination of
29cbd93a 2965 // errors there might be more. In this case, just return the oldest one (lowest ID).
2966 foreach ($forums as $forum) {
2967 return $forum; // ie the first one
11b0c469 2968 }
8daaf761 2969 }
e6874d9f 2970
8daaf761 2971 // Doesn't exist, so create one now.
b85b25eb 2972 $forum = new stdClass();
8daaf761 2973 $forum->course = $courseid;
2974 $forum->type = "$type";
2975 switch ($forum->type) {
2976 case "news":
294ce987 2977 $forum->name = get_string("namenews", "forum");
2978 $forum->intro = get_string("intronews", "forum");
906fef94 2979 $forum->forcesubscribe = FORUM_FORCESUBSCRIBE;
8daaf761 2980 $forum->assessed = 0;
709f0ec8 2981 if ($courseid == SITEID) {
2982 $forum->name = get_string("sitenews");
2983 $forum->forcesubscribe = 0;
8f0cd6ef 2984 }
8daaf761 2985 break;
2986 case "social":
294ce987 2987 $forum->name = get_string("namesocial", "forum");
2988 $forum->intro = get_string("introsocial", "forum");
8daaf761 2989 $forum->assessed = 0;
2990 $forum->forcesubscribe = 0;
2991 break;
1c7b8b93
NC
2992 case "blog":
2993 $forum->name = get_string('blogforum', 'forum');
2994 $forum->intro = get_string('introblog', 'forum');
2995 $forum->assessed = 0;
2996 $forum->forcesubscribe = 0;
2997 break;
8daaf761 2998 default:
9146b979 2999 echo $OUTPUT->notification("That forum type doesn't exist!");
8daaf761 3000 return false;
3001 break;
3002 }
3003
3004 $forum->timemodified = time();
4e445355 3005 $forum->id = $DB->insert_record("forum", $forum);
8daaf761 3006
4e445355 3007 if (! $module = $DB->get_record("modules", array("name" => "forum"))) {
9146b979 3008 echo $OUTPUT->notification("Could not find forum module!!");
e1b5643f 3009 return false;
82aa0e8d 3010 }
39790bd8 3011 $mod = new stdClass();
e1b5643f 3012 $mod->course = $courseid;
3013 $mod->module = $module->id;
3014 $mod->instance = $forum->id;
3015 $mod->section = 0;
3016 if (! $mod->coursemodule = add_course_module($mod) ) { // assumes course/lib.php is loaded
11cd754e 3017 echo $OUTPUT->notification("Could not add a new course module to the course '" . $courseid . "'");
e1b5643f 3018 return false;
3019 }
3020 if (! $sectionid = add_mod_to_section($mod) ) { // assumes course/lib.php is loaded
9146b979 3021 echo $OUTPUT->notification("Could not add the new course module to that section");
e1b5643f 3022 return false;
3023 }
f685e830
PS
3024 $DB->set_field("course_modules", "section", $sectionid, array("id" => $mod->coursemodule));
3025
e1b5643f 3026 include_once("$CFG->dirroot/course/lib.php");
3027 rebuild_course_cache($courseid);
65b0e537 3028
4e445355 3029 return $DB->get_record("forum", array("id" => "$forum->id"));
82aa0e8d 3030}
3031
f93f848a 3032
0a4ac01b 3033/**
df1ba0f4 3034 * Given the data about a posting, builds up the HTML to display it and
3035 * returns the HTML in a string. This is designed for sending via HTML email.
3036 *
3037 * @global object
3038 * @param object $course
3039 * @param object $cm
3040 * @param object $forum
3041 * @param object $discussion
3042 * @param object $post
3043 * @param object $userform
3044 * @param object $userto
3045 * @param bool $ownpost
3046 * @param bool $reply
3047 * @param bool $link
3048 * @param bool $rate
3049 * @param string $footer
3050 * @return string
3051 */
0faf6791 3052function forum_make_mail_post($course, $cm, $forum, $discussion, $post, $userfrom, $userto,
11b0c469 3053 $ownpost=false, $reply=false, $link=false, $rate=false, $footer="") {
2b63df96 3054
59e28d8f 3055 global $CFG, $OUTPUT;
501cdbd8 3056
18ff4d42
PS
3057 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
3058
df1c2c71 3059 if (!isset($userto->viewfullnames[$forum->id])) {
df1c2c71 3060 $viewfullnames = has_capability('moodle/site:viewfullnames', $modcontext, $userto->id);
3061 } else {
3062 $viewfullnames = $userto->viewfullnames[$forum->id];
7613e6d7 3063 }
7613e6d7 3064
18ff4d42
PS
3065 // add absolute file links
3066 $post->message = file_rewrite_pluginfile_urls($post->message, 'pluginfile.php', $modcontext->id, 'mod_forum', 'post', $post->id);
3067
1306c5ea 3068 // format the post body
39790bd8 3069 $options = new stdClass();
1306c5ea 3070 $options->para = true;
e2d7687f 3071 $formattedtext = format_text($post->message, $post->messageformat, $options, $course->id);
0d851f90 3072
add3201e 3073 $output = '<table border="0" cellpadding="3" cellspacing="0" class="forumpost">';
501cdbd8 3074
add3201e 3075 $output .= '<tr class="header"><td width="35" valign="top" class="picture left">';
812dbaf7 3076 $output .= $OUTPUT->user_picture($userfrom, array('courseid'=>$course->id));
add3201e 3077 $output .= '</td>';
501cdbd8 3078
3079 if ($post->parent) {
add3201e 3080 $output .= '<td class="topic">';
501cdbd8 3081 } else {
add3201e 3082 $output .= '<td class="topic starter">';
501cdbd8 3083 }
add3201e 3084 $output .= '<div class="subject">'.format_string($post->subject).'</div>';
1b26d5e7 3085
df1c2c71 3086 $fullname = fullname($userfrom, $viewfullnames);
39790bd8 3087 $by = new stdClass();
df1c2c71 3088 $by->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$userfrom->id.'&amp;course='.$course->id.'">'.$fullname.'</a>';
3089 $by->date = userdate($post->modified, '', $userto->timezone);
add3201e 3090 $output .= '<div class="author">'.get_string('bynameondate', 'forum', $by).'</div>';
3091
3092 $output .= '</td></tr>';
3093
7b54f563 3094 $output .= '<tr><td class="left side" valign="top">';
df1c2c71 3095
3096 if (isset($userfrom->groups)) {
3097 $groups = $userfrom->groups[$forum->id];
3098 } else {
18ff4d42 3099 $groups = groups_get_all_groups($course->id, $userfrom->id, $cm->groupingid);
df1c2c71 3100 }
3101
3102 if ($groups) {
3103 $output .= print_group_picture($groups, $course->id, false, true, true);
add3201e 3104 } else {
3105 $output .= '&nbsp;';
3106 }
1b26d5e7 3107
add3201e 3108 $output .= '</td><td class="content">';
501cdbd8 3109
0faf6791 3110 $attachments = forum_print_attachments($post, $cm, 'html');
3111 if ($attachments !== '') {
add3201e 3112 $output .= '<div class="attachments">';
0faf6791 3113 $output .= $attachments;
3114 $output .= '</div>';
7f6689e4 3115 }
3116
0d851f90 3117 $output .= $formattedtext;
501cdbd8 3118
0a4ac01b 3119// Commands
add3201e 3120 $commands = array();
501cdbd8 3121
2e2e71a8 3122 if ($post->parent) {
add3201e 3123 $commands[] = '<a target="_blank" href="'.$CFG->wwwroot.'/mod/forum/discuss.php?d='.
3124 $post->discussion.'&amp;parent='.$post->parent.'">'.get_string('parent', 'forum').'</a>';
2e2e71a8 3125 }
ce45515e 3126
add3201e 3127 if ($reply) {