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