Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
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
/**
18
 * A scheduled task for forum cron.
19
 *
20
 * @package    mod_forum
21
 * @copyright  2014 Dan Poltawski <dan@moodle.com>
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
namespace mod_forum\task;
25
 
26
defined('MOODLE_INTERNAL') || die();
27
 
28
require_once($CFG->dirroot . '/mod/forum/lib.php');
29
 
30
/**
31
 * The main scheduled task for the forum.
32
 *
33
 * @package    mod_forum
34
 * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
35
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36
 */
37
class cron_task extends \core\task\scheduled_task {
38
 
39
    // Use the logging trait to get some nice, juicy, logging.
40
    use \core\task\logging_trait;
41
 
42
    /**
43
     * @var The list of courses which contain posts to be sent.
44
     */
45
    protected $courses = [];
46
 
47
    /**
48
     * @var The list of forums which contain posts to be sent.
49
     */
50
    protected $forums = [];
51
 
52
    /**
53
     * @var The list of discussions which contain posts to be sent.
54
     */
55
    protected $discussions = [];
56
 
57
    /**
58
     * @var The list of posts to be sent.
59
     */
60
    protected $posts = [];
61
 
62
    /**
63
     * @var The list of post authors.
64
     */
65
    protected $users = [];
66
 
67
    /**
68
     * @var The list of subscribed users.
69
     */
70
    protected $subscribedusers = [];
71
 
72
    /**
73
     * @var The list of digest users.
74
     */
75
    protected $digestusers = [];
76
 
77
    /**
78
     * @var The list of adhoc data for sending.
79
     */
80
    protected $adhocdata = [];
81
 
82
    /**
83
     * Get a descriptive name for this task (shown to admins).
84
     *
85
     * @return string
86
     */
87
    public function get_name() {
88
        return get_string('crontask', 'mod_forum');
89
    }
90
 
91
    /**
92
     * Execute the scheduled task.
93
     */
94
    public function execute() {
95
        global $CFG, $DB;
96
 
97
        $timenow = time();
98
 
99
        // Delete any really old posts in the digest queue.
1441 ariadna 100
        $weekago = $timenow - WEEKSECS;
1 efrain 101
        $this->log_start("Removing old digest records from 7 days ago.");
1441 ariadna 102
 
103
        $selectsqlcondition = "timemodified < :weekago
104
            AND discussionid IN (SELECT id FROM {forum_discussions} WHERE timestart < :holdperiod)";
105
 
106
        $params = [
107
            'weekago' => $weekago,
108
            'holdperiod' => $weekago,
109
        ];
110
 
111
        $DB->delete_records_select('forum_queue', $selectsqlcondition, $params);
1 efrain 112
        $this->log_finish("Removed all old digest records.");
113
 
114
        $endtime   = $timenow - $CFG->maxeditingtime;
115
        $starttime = $endtime - (2 * DAYSECS);
116
        $this->log_start("Fetching unmailed posts.");
117
        if (!$posts = $this->get_unmailed_posts($starttime, $endtime, $timenow)) {
118
            $this->log_finish("No posts found.", 1);
119
            return false;
120
        }
121
        $this->log_finish("Done");
122
 
123
        // Process post data and turn into adhoc tasks.
124
        $this->process_post_data($posts);
125
 
126
        // Mark posts as read.
127
        list($in, $params) = $DB->get_in_or_equal(array_keys($posts));
128
        $DB->set_field_select('forum_posts', 'mailed', 1, "id {$in}", $params);
129
    }
130
 
131
    /**
132
     * Process all posts and convert to appropriated hoc tasks.
133
     *
134
     * @param   \stdClass[] $posts
135
     */
136
    protected function process_post_data($posts) {
137
        $discussionids = [];
138
        $forumids = [];
139
        $courseids = [];
140
 
141
        $this->log_start("Processing post information");
142
 
143
        $start = microtime(true);
144
        foreach ($posts as $id => $post) {
145
            $discussionids[$post->discussion] = true;
146
            $forumids[$post->forum] = true;
147
            $courseids[$post->course] = true;
148
            $this->add_data_for_post($post);
149
            $this->posts[$id] = $post;
150
        }
151
        $this->log_finish(sprintf("Processed %s posts", count($this->posts)));
152
 
153
        if (empty($this->posts)) {
154
            $this->log("No posts found. Returning early.");
155
            return;
156
        }
157
 
158
        // Please note, this order is intentional.
159
        // The forum cache makes use of the course.
160
        $this->log_start("Filling caches");
161
 
162
        $start = microtime(true);
163
        $this->log_start("Filling course cache", 1);
164
        $this->fill_course_cache(array_keys($courseids));
165
        $this->log_finish("Done", 1);
166
 
167
        $this->log_start("Filling forum cache", 1);
168
        $this->fill_forum_cache(array_keys($forumids));
169
        $this->log_finish("Done", 1);
170
 
171
        $this->log_start("Filling discussion cache", 1);
172
        $this->fill_discussion_cache(array_keys($discussionids));
173
        $this->log_finish("Done", 1);
174
 
175
        $this->log_start("Filling user subscription cache", 1);
176
        $this->fill_user_subscription_cache();
177
        $this->log_finish("Done", 1);
178
 
179
        $this->log_start("Filling digest cache", 1);
180
        $this->fill_digest_cache();
181
        $this->log_finish("Done", 1);
182
 
183
        $this->log_finish("All caches filled");
184
 
185
        $this->log_start("Queueing user tasks.");
186
        $this->queue_user_tasks();
187
        $this->log_finish("All tasks queued.");
188
    }
189
 
190
    /**
191
     * Fill the course cache.
192
     *
193
     * @param   int[]       $courseids
194
     */
195
    protected function fill_course_cache($courseids) {
196
        global $DB;
197
 
198
        list($in, $params) = $DB->get_in_or_equal($courseids);
199
        $this->courses = $DB->get_records_select('course', "id $in", $params);
200
    }
201
 
202
    /**
203
     * Fill the forum cache.
204
     *
205
     * @param   int[]       $forumids
206
     */
207
    protected function fill_forum_cache($forumids) {
208
        global $DB;
209
 
210
        $requiredfields = [
211
                'id',
212
                'course',
213
                'forcesubscribe',
214
                'type',
215
            ];
216
        list($in, $params) = $DB->get_in_or_equal($forumids);
217
        $this->forums = $DB->get_records_select('forum', "id $in", $params, '', implode(', ', $requiredfields));
218
        foreach ($this->forums as $id => $forum) {
219
            \mod_forum\subscriptions::fill_subscription_cache($id);
220
            \mod_forum\subscriptions::fill_discussion_subscription_cache($id);
221
        }
222
    }
223
 
224
    /**
225
     * Fill the discussion cache.
226
     *
227
     * @param   int[]       $discussionids
228
     */
229
    protected function fill_discussion_cache($discussionids) {
230
        global $DB;
231
 
232
        if (empty($discussionids)) {
233
            $this->discussions = [];
234
        } else {
235
 
236
            $requiredfields = [
237
                    'id',
238
                    'groupid',
239
                    'firstpost',
240
                    'timestart',
241
                    'timeend',
242
                ];
243
 
244
            list($in, $params) = $DB->get_in_or_equal($discussionids);
245
            $this->discussions = $DB->get_records_select(
246
                    'forum_discussions', "id $in", $params, '', implode(', ', $requiredfields));
247
        }
248
    }
249
 
250
    /**
251
     * Fill the cache of user digest preferences.
252
     */
253
    protected function fill_digest_cache() {
254
        global $DB;
255
 
256
        if (empty($this->users)) {
257
            return;
258
        }
259
        // Get the list of forum subscriptions for per-user per-forum maildigest settings.
260
        list($in, $params) = $DB->get_in_or_equal(array_keys($this->users));
261
        $digestspreferences = $DB->get_recordset_select(
262
                'forum_digests', "userid $in", $params, '', 'id, userid, forum, maildigest');
263
        foreach ($digestspreferences as $digestpreference) {
264
            if (!isset($this->digestusers[$digestpreference->forum])) {
265
                $this->digestusers[$digestpreference->forum] = [];
266
            }
267
            $this->digestusers[$digestpreference->forum][$digestpreference->userid] = $digestpreference->maildigest;
268
        }
269
        $digestspreferences->close();
270
    }
271
 
272
    /**
273
     * Add dsta for the current forum post to the structure of adhoc data.
274
     *
275
     * @param   \stdClass   $post
276
     */
277
    protected function add_data_for_post($post) {
278
        if (!isset($this->adhocdata[$post->course])) {
279
            $this->adhocdata[$post->course] = [];
280
        }
281
 
282
        if (!isset($this->adhocdata[$post->course][$post->forum])) {
283
            $this->adhocdata[$post->course][$post->forum] = [];
284
        }
285
 
286
        if (!isset($this->adhocdata[$post->course][$post->forum][$post->discussion])) {
287
            $this->adhocdata[$post->course][$post->forum][$post->discussion] = [];
288
        }
289
 
290
        $this->adhocdata[$post->course][$post->forum][$post->discussion][$post->id] = $post->id;
291
    }
292
 
293
    /**
294
     * Fill the cache of user subscriptions.
295
     */
296
    protected function fill_user_subscription_cache() {
297
        foreach ($this->forums as $forum) {
298
            $cm = get_fast_modinfo($this->courses[$forum->course])->instances['forum'][$forum->id];
299
            $modcontext = \context_module::instance($cm->id);
300
 
301
            $this->subscribedusers[$forum->id] = [];
302
            if ($users = \mod_forum\subscriptions::fetch_subscribed_users($forum, 0, $modcontext, 'u.id, u.maildigest', true)) {
303
                foreach ($users as $user) {
304
                    // This user is subscribed to this forum.
305
                    $this->subscribedusers[$forum->id][$user->id] = $user->id;
306
                    if (!isset($this->users[$user->id])) {
307
                        // Store minimal user info.
308
                        $this->users[$user->id] = $user;
309
                    }
310
                }
311
                // Release memory.
312
                unset($users);
313
            }
314
        }
315
    }
316
 
317
    /**
318
     * Queue the user tasks.
319
     */
320
    protected function queue_user_tasks() {
321
        global $CFG, $DB;
322
 
323
        $timenow = time();
324
        $sitetimezone = \core_date::get_server_timezone();
325
        $counts = [
326
            'digests' => 0,
327
            'individuals' => 0,
328
            'users' => 0,
329
            'ignored' => 0,
330
            'messages' => 0,
331
        ];
332
        $this->log("Processing " . count($this->users) . " users", 1);
333
        foreach ($this->users as $user) {
334
            $usercounts = [
335
                'digests' => 0,
336
                'messages' => 0,
337
            ];
338
 
339
            $send = false;
340
            // Setup this user so that the capabilities are cached, and environment matches receiving user.
341
            \core\cron::setup_user($user);
342
 
343
            list($individualpostdata, $digestpostdata) = $this->fetch_posts_for_user($user);
344
 
345
            if (!empty($digestpostdata)) {
346
                // Insert all of the records for the digest.
347
                $DB->insert_records('forum_queue', $digestpostdata);
348
                $servermidnight = usergetmidnight($timenow, $sitetimezone);
349
                $digesttime = $servermidnight + ($CFG->digestmailtime * 3600);
350
 
351
                if ($digesttime < $timenow) {
352
                    // Digest time is in the past. Get a new time for tomorrow.
353
                    $servermidnight = usergetmidnight($timenow + DAYSECS, $sitetimezone);
354
                    $digesttime = $servermidnight + ($CFG->digestmailtime * 3600);
355
                }
356
 
357
                $task = new \mod_forum\task\send_user_digests();
358
                $task->set_userid($user->id);
359
                $task->set_component('mod_forum');
360
                $task->set_custom_data(['servermidnight' => $servermidnight]);
361
                $task->set_next_run_time($digesttime);
362
                \core\task\manager::reschedule_or_queue_adhoc_task($task);
363
                $usercounts['digests']++;
364
                $send = true;
365
            }
366
 
367
            if (!empty($individualpostdata)) {
368
                $usercounts['messages'] += count($individualpostdata);
369
 
370
                $task = new \mod_forum\task\send_user_notifications();
371
                $task->set_userid($user->id);
372
                $task->set_custom_data($individualpostdata);
373
                $task->set_component('mod_forum');
374
                \core\task\manager::queue_adhoc_task($task);
375
                $counts['individuals']++;
376
                $send = true;
377
            }
378
 
379
            if ($send) {
380
                $counts['users']++;
381
                $counts['messages'] += $usercounts['messages'];
382
                $counts['digests'] += $usercounts['digests'];
383
            } else {
384
                $counts['ignored']++;
385
            }
386
 
387
            $this->log(sprintf("Queued %d digests and %d messages for %s",
388
                    $usercounts['digests'],
389
                    $usercounts['messages'],
390
                    $user->id
391
                ), 2);
392
        }
393
        $this->log(
394
            sprintf(
395
                "Queued %d digests, and %d individual tasks for %d post mails. " .
396
                "Unique users: %d (%d ignored)",
397
                $counts['digests'],
398
                $counts['individuals'],
399
                $counts['messages'],
400
                $counts['users'],
401
                $counts['ignored']
402
            ), 1);
403
    }
404
 
405
    /**
406
     * Fetch posts for this user.
407
     *
408
     * @param   \stdClass   $user The user to fetch posts for.
409
     */
410
    protected function fetch_posts_for_user($user) {
411
        // We maintain a mapping of user groups for each forum.
412
        $usergroups = [];
413
        $digeststructure = [];
414
 
415
        $poststructure = $this->adhocdata;
416
        $poststosend = [];
417
        foreach ($poststructure as $courseid => $forumids) {
418
            $course = $this->courses[$courseid];
419
            foreach ($forumids as $forumid => $discussionids) {
420
                $forum = $this->forums[$forumid];
421
                $maildigest = forum_get_user_maildigest_bulk($this->digestusers, $user, $forumid);
422
 
423
                if (!isset($this->subscribedusers[$forumid][$user->id])) {
424
                    // This user has no subscription of any kind to this forum.
425
                    // Do not send them any posts at all.
426
                    unset($poststructure[$courseid][$forumid]);
427
                    continue;
428
                }
429
 
430
                $subscriptiontime = \mod_forum\subscriptions::fetch_discussion_subscription($forum->id, $user->id);
431
 
432
                $cm = get_fast_modinfo($course)->instances['forum'][$forumid];
433
                foreach ($discussionids as $discussionid => $postids) {
434
                    $discussion = $this->discussions[$discussionid];
435
                    if (!\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussionid, $cm)) {
436
                        // The user does not subscribe to this forum as a whole, or to this specific discussion.
437
                        unset($poststructure[$courseid][$forumid][$discussionid]);
438
                        continue;
439
                    }
440
 
441
                    if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {
442
                        // This discussion has a groupmode set (SEPARATEGROUPS or VISIBLEGROUPS).
443
                        // Check whether the user can view it based on their groups.
444
                        if (!isset($usergroups[$forum->id])) {
445
                            $usergroups[$forum->id] = groups_get_all_groups($courseid, $user->id, $cm->groupingid);
446
                        }
447
 
448
                        if (!isset($usergroups[$forum->id][$discussion->groupid])) {
449
                            // This user is not a member of this group, or the group no longer exists.
450
 
451
                            $modcontext = \context_module::instance($cm->id);
452
                            if (!has_capability('moodle/site:accessallgroups', $modcontext, $user)) {
453
                                // This user does not have the accessallgroups and is not a member of the group.
454
                                // Do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS.
455
                                unset($poststructure[$courseid][$forumid][$discussionid]);
456
                                continue;
457
                            }
458
                        }
459
                    }
460
 
461
                    foreach ($postids as $postid) {
462
                        $post = $this->posts[$postid];
463
                        if ($subscriptiontime) {
464
                            // Skip posts if the user subscribed to the discussion after it was created.
465
                            $subscribedafter = isset($subscriptiontime[$post->discussion]);
466
                            $subscribedafter = $subscribedafter && ($subscriptiontime[$post->discussion] > $post->created);
467
                            if ($subscribedafter) {
468
                                // The user subscribed to the discussion/forum after this post was created.
469
                                unset($poststructure[$courseid][$forumid][$discussionid][$postid]);
470
                                continue;
471
                            }
472
                        }
473
 
474
                        if ($maildigest > 0) {
475
                            // This user wants the mails to be in digest form.
476
                            $digeststructure[] = (object) [
477
                                'userid' => $user->id,
478
                                'discussionid' => $discussion->id,
479
                                'postid' => $post->id,
480
                                'timemodified' => $post->created,
481
                            ];
482
                            unset($poststructure[$courseid][$forumid][$discussionid][$postid]);
483
                            continue;
484
                        } else {
485
                            // Add this post to the list of postids to be sent.
486
                            $poststosend[] = $postid;
487
                        }
488
                    }
489
                }
490
 
491
                if (empty($poststructure[$courseid][$forumid])) {
492
                    // This user is not subscribed to any discussions in this forum at all.
493
                    unset($poststructure[$courseid][$forumid]);
494
                    continue;
495
                }
496
            }
497
            if (empty($poststructure[$courseid])) {
498
                // This user is not subscribed to any forums in this course.
499
                unset($poststructure[$courseid]);
500
            }
501
        }
502
 
503
        return [$poststosend, $digeststructure];
504
    }
505
 
506
    /**
507
     * Returns a list of all new posts that have not been mailed yet
508
     *
509
     * @param int $starttime posts created after this time
510
     * @param int $endtime posts created before this
511
     * @param int $now used for timed discussions only
512
     * @return array
513
     */
514
    protected function get_unmailed_posts($starttime, $endtime, $now = null) {
515
        global $CFG, $DB;
516
 
517
        $params = array();
518
        $params['mailed'] = FORUM_MAILED_PENDING;
519
        $params['ptimestart'] = $starttime;
520
        $params['ptimeend'] = $endtime;
521
        $params['mailnow'] = 1;
522
 
523
        if (!empty($CFG->forum_enabletimedposts)) {
524
            if (empty($now)) {
525
                $now = time();
526
            }
527
            $selectsql = "AND (p.created >= :ptimestart OR d.timestart >= :pptimestart)";
528
            $params['pptimestart'] = $starttime;
529
            $timedsql = "AND (d.timestart < :dtimestart AND (d.timeend = 0 OR d.timeend > :dtimeend))";
530
            $params['dtimestart'] = $now;
531
            $params['dtimeend'] = $now;
532
        } else {
533
            $timedsql = "";
534
            $selectsql = "AND p.created >= :ptimestart";
535
        }
536
 
537
        return $DB->get_records_sql(
538
               "SELECT
539
                    p.id,
540
                    p.discussion,
541
                    d.forum,
542
                    d.course,
543
                    p.created,
544
                    p.parent,
545
                    p.userid
546
                  FROM {forum_posts} p
547
                  JOIN {forum_discussions} d ON d.id = p.discussion
548
                 WHERE p.mailed = :mailed
549
                $selectsql
550
                   AND (p.created < :ptimeend OR p.mailnow = :mailnow)
551
                $timedsql
552
                 ORDER BY p.modified ASC",
553
             $params);
554
    }
555
}