Proyectos de Subversion Moodle

Rev

| 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.
100
        $weekago = $timenow - (7 * 24 * 3600);
101
        $this->log_start("Removing old digest records from 7 days ago.");
102
        $DB->delete_records_select('forum_queue', "timemodified < ?", array($weekago));
103
        $this->log_finish("Removed all old digest records.");
104
 
105
        $endtime   = $timenow - $CFG->maxeditingtime;
106
        $starttime = $endtime - (2 * DAYSECS);
107
        $this->log_start("Fetching unmailed posts.");
108
        if (!$posts = $this->get_unmailed_posts($starttime, $endtime, $timenow)) {
109
            $this->log_finish("No posts found.", 1);
110
            return false;
111
        }
112
        $this->log_finish("Done");
113
 
114
        // Process post data and turn into adhoc tasks.
115
        $this->process_post_data($posts);
116
 
117
        // Mark posts as read.
118
        list($in, $params) = $DB->get_in_or_equal(array_keys($posts));
119
        $DB->set_field_select('forum_posts', 'mailed', 1, "id {$in}", $params);
120
    }
121
 
122
    /**
123
     * Process all posts and convert to appropriated hoc tasks.
124
     *
125
     * @param   \stdClass[] $posts
126
     */
127
    protected function process_post_data($posts) {
128
        $discussionids = [];
129
        $forumids = [];
130
        $courseids = [];
131
 
132
        $this->log_start("Processing post information");
133
 
134
        $start = microtime(true);
135
        foreach ($posts as $id => $post) {
136
            $discussionids[$post->discussion] = true;
137
            $forumids[$post->forum] = true;
138
            $courseids[$post->course] = true;
139
            $this->add_data_for_post($post);
140
            $this->posts[$id] = $post;
141
        }
142
        $this->log_finish(sprintf("Processed %s posts", count($this->posts)));
143
 
144
        if (empty($this->posts)) {
145
            $this->log("No posts found. Returning early.");
146
            return;
147
        }
148
 
149
        // Please note, this order is intentional.
150
        // The forum cache makes use of the course.
151
        $this->log_start("Filling caches");
152
 
153
        $start = microtime(true);
154
        $this->log_start("Filling course cache", 1);
155
        $this->fill_course_cache(array_keys($courseids));
156
        $this->log_finish("Done", 1);
157
 
158
        $this->log_start("Filling forum cache", 1);
159
        $this->fill_forum_cache(array_keys($forumids));
160
        $this->log_finish("Done", 1);
161
 
162
        $this->log_start("Filling discussion cache", 1);
163
        $this->fill_discussion_cache(array_keys($discussionids));
164
        $this->log_finish("Done", 1);
165
 
166
        $this->log_start("Filling user subscription cache", 1);
167
        $this->fill_user_subscription_cache();
168
        $this->log_finish("Done", 1);
169
 
170
        $this->log_start("Filling digest cache", 1);
171
        $this->fill_digest_cache();
172
        $this->log_finish("Done", 1);
173
 
174
        $this->log_finish("All caches filled");
175
 
176
        $this->log_start("Queueing user tasks.");
177
        $this->queue_user_tasks();
178
        $this->log_finish("All tasks queued.");
179
    }
180
 
181
    /**
182
     * Fill the course cache.
183
     *
184
     * @param   int[]       $courseids
185
     */
186
    protected function fill_course_cache($courseids) {
187
        global $DB;
188
 
189
        list($in, $params) = $DB->get_in_or_equal($courseids);
190
        $this->courses = $DB->get_records_select('course', "id $in", $params);
191
    }
192
 
193
    /**
194
     * Fill the forum cache.
195
     *
196
     * @param   int[]       $forumids
197
     */
198
    protected function fill_forum_cache($forumids) {
199
        global $DB;
200
 
201
        $requiredfields = [
202
                'id',
203
                'course',
204
                'forcesubscribe',
205
                'type',
206
            ];
207
        list($in, $params) = $DB->get_in_or_equal($forumids);
208
        $this->forums = $DB->get_records_select('forum', "id $in", $params, '', implode(', ', $requiredfields));
209
        foreach ($this->forums as $id => $forum) {
210
            \mod_forum\subscriptions::fill_subscription_cache($id);
211
            \mod_forum\subscriptions::fill_discussion_subscription_cache($id);
212
        }
213
    }
214
 
215
    /**
216
     * Fill the discussion cache.
217
     *
218
     * @param   int[]       $discussionids
219
     */
220
    protected function fill_discussion_cache($discussionids) {
221
        global $DB;
222
 
223
        if (empty($discussionids)) {
224
            $this->discussions = [];
225
        } else {
226
 
227
            $requiredfields = [
228
                    'id',
229
                    'groupid',
230
                    'firstpost',
231
                    'timestart',
232
                    'timeend',
233
                ];
234
 
235
            list($in, $params) = $DB->get_in_or_equal($discussionids);
236
            $this->discussions = $DB->get_records_select(
237
                    'forum_discussions', "id $in", $params, '', implode(', ', $requiredfields));
238
        }
239
    }
240
 
241
    /**
242
     * Fill the cache of user digest preferences.
243
     */
244
    protected function fill_digest_cache() {
245
        global $DB;
246
 
247
        if (empty($this->users)) {
248
            return;
249
        }
250
        // Get the list of forum subscriptions for per-user per-forum maildigest settings.
251
        list($in, $params) = $DB->get_in_or_equal(array_keys($this->users));
252
        $digestspreferences = $DB->get_recordset_select(
253
                'forum_digests', "userid $in", $params, '', 'id, userid, forum, maildigest');
254
        foreach ($digestspreferences as $digestpreference) {
255
            if (!isset($this->digestusers[$digestpreference->forum])) {
256
                $this->digestusers[$digestpreference->forum] = [];
257
            }
258
            $this->digestusers[$digestpreference->forum][$digestpreference->userid] = $digestpreference->maildigest;
259
        }
260
        $digestspreferences->close();
261
    }
262
 
263
    /**
264
     * Add dsta for the current forum post to the structure of adhoc data.
265
     *
266
     * @param   \stdClass   $post
267
     */
268
    protected function add_data_for_post($post) {
269
        if (!isset($this->adhocdata[$post->course])) {
270
            $this->adhocdata[$post->course] = [];
271
        }
272
 
273
        if (!isset($this->adhocdata[$post->course][$post->forum])) {
274
            $this->adhocdata[$post->course][$post->forum] = [];
275
        }
276
 
277
        if (!isset($this->adhocdata[$post->course][$post->forum][$post->discussion])) {
278
            $this->adhocdata[$post->course][$post->forum][$post->discussion] = [];
279
        }
280
 
281
        $this->adhocdata[$post->course][$post->forum][$post->discussion][$post->id] = $post->id;
282
    }
283
 
284
    /**
285
     * Fill the cache of user subscriptions.
286
     */
287
    protected function fill_user_subscription_cache() {
288
        foreach ($this->forums as $forum) {
289
            $cm = get_fast_modinfo($this->courses[$forum->course])->instances['forum'][$forum->id];
290
            $modcontext = \context_module::instance($cm->id);
291
 
292
            $this->subscribedusers[$forum->id] = [];
293
            if ($users = \mod_forum\subscriptions::fetch_subscribed_users($forum, 0, $modcontext, 'u.id, u.maildigest', true)) {
294
                foreach ($users as $user) {
295
                    // This user is subscribed to this forum.
296
                    $this->subscribedusers[$forum->id][$user->id] = $user->id;
297
                    if (!isset($this->users[$user->id])) {
298
                        // Store minimal user info.
299
                        $this->users[$user->id] = $user;
300
                    }
301
                }
302
                // Release memory.
303
                unset($users);
304
            }
305
        }
306
    }
307
 
308
    /**
309
     * Queue the user tasks.
310
     */
311
    protected function queue_user_tasks() {
312
        global $CFG, $DB;
313
 
314
        $timenow = time();
315
        $sitetimezone = \core_date::get_server_timezone();
316
        $counts = [
317
            'digests' => 0,
318
            'individuals' => 0,
319
            'users' => 0,
320
            'ignored' => 0,
321
            'messages' => 0,
322
        ];
323
        $this->log("Processing " . count($this->users) . " users", 1);
324
        foreach ($this->users as $user) {
325
            $usercounts = [
326
                'digests' => 0,
327
                'messages' => 0,
328
            ];
329
 
330
            $send = false;
331
            // Setup this user so that the capabilities are cached, and environment matches receiving user.
332
            \core\cron::setup_user($user);
333
 
334
            list($individualpostdata, $digestpostdata) = $this->fetch_posts_for_user($user);
335
 
336
            if (!empty($digestpostdata)) {
337
                // Insert all of the records for the digest.
338
                $DB->insert_records('forum_queue', $digestpostdata);
339
                $servermidnight = usergetmidnight($timenow, $sitetimezone);
340
                $digesttime = $servermidnight + ($CFG->digestmailtime * 3600);
341
 
342
                if ($digesttime < $timenow) {
343
                    // Digest time is in the past. Get a new time for tomorrow.
344
                    $servermidnight = usergetmidnight($timenow + DAYSECS, $sitetimezone);
345
                    $digesttime = $servermidnight + ($CFG->digestmailtime * 3600);
346
                }
347
 
348
                $task = new \mod_forum\task\send_user_digests();
349
                $task->set_userid($user->id);
350
                $task->set_component('mod_forum');
351
                $task->set_custom_data(['servermidnight' => $servermidnight]);
352
                $task->set_next_run_time($digesttime);
353
                \core\task\manager::reschedule_or_queue_adhoc_task($task);
354
                $usercounts['digests']++;
355
                $send = true;
356
            }
357
 
358
            if (!empty($individualpostdata)) {
359
                $usercounts['messages'] += count($individualpostdata);
360
 
361
                $task = new \mod_forum\task\send_user_notifications();
362
                $task->set_userid($user->id);
363
                $task->set_custom_data($individualpostdata);
364
                $task->set_component('mod_forum');
365
                \core\task\manager::queue_adhoc_task($task);
366
                $counts['individuals']++;
367
                $send = true;
368
            }
369
 
370
            if ($send) {
371
                $counts['users']++;
372
                $counts['messages'] += $usercounts['messages'];
373
                $counts['digests'] += $usercounts['digests'];
374
            } else {
375
                $counts['ignored']++;
376
            }
377
 
378
            $this->log(sprintf("Queued %d digests and %d messages for %s",
379
                    $usercounts['digests'],
380
                    $usercounts['messages'],
381
                    $user->id
382
                ), 2);
383
        }
384
        $this->log(
385
            sprintf(
386
                "Queued %d digests, and %d individual tasks for %d post mails. " .
387
                "Unique users: %d (%d ignored)",
388
                $counts['digests'],
389
                $counts['individuals'],
390
                $counts['messages'],
391
                $counts['users'],
392
                $counts['ignored']
393
            ), 1);
394
    }
395
 
396
    /**
397
     * Fetch posts for this user.
398
     *
399
     * @param   \stdClass   $user The user to fetch posts for.
400
     */
401
    protected function fetch_posts_for_user($user) {
402
        // We maintain a mapping of user groups for each forum.
403
        $usergroups = [];
404
        $digeststructure = [];
405
 
406
        $poststructure = $this->adhocdata;
407
        $poststosend = [];
408
        foreach ($poststructure as $courseid => $forumids) {
409
            $course = $this->courses[$courseid];
410
            foreach ($forumids as $forumid => $discussionids) {
411
                $forum = $this->forums[$forumid];
412
                $maildigest = forum_get_user_maildigest_bulk($this->digestusers, $user, $forumid);
413
 
414
                if (!isset($this->subscribedusers[$forumid][$user->id])) {
415
                    // This user has no subscription of any kind to this forum.
416
                    // Do not send them any posts at all.
417
                    unset($poststructure[$courseid][$forumid]);
418
                    continue;
419
                }
420
 
421
                $subscriptiontime = \mod_forum\subscriptions::fetch_discussion_subscription($forum->id, $user->id);
422
 
423
                $cm = get_fast_modinfo($course)->instances['forum'][$forumid];
424
                foreach ($discussionids as $discussionid => $postids) {
425
                    $discussion = $this->discussions[$discussionid];
426
                    if (!\mod_forum\subscriptions::is_subscribed($user->id, $forum, $discussionid, $cm)) {
427
                        // The user does not subscribe to this forum as a whole, or to this specific discussion.
428
                        unset($poststructure[$courseid][$forumid][$discussionid]);
429
                        continue;
430
                    }
431
 
432
                    if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {
433
                        // This discussion has a groupmode set (SEPARATEGROUPS or VISIBLEGROUPS).
434
                        // Check whether the user can view it based on their groups.
435
                        if (!isset($usergroups[$forum->id])) {
436
                            $usergroups[$forum->id] = groups_get_all_groups($courseid, $user->id, $cm->groupingid);
437
                        }
438
 
439
                        if (!isset($usergroups[$forum->id][$discussion->groupid])) {
440
                            // This user is not a member of this group, or the group no longer exists.
441
 
442
                            $modcontext = \context_module::instance($cm->id);
443
                            if (!has_capability('moodle/site:accessallgroups', $modcontext, $user)) {
444
                                // This user does not have the accessallgroups and is not a member of the group.
445
                                // Do not send posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS.
446
                                unset($poststructure[$courseid][$forumid][$discussionid]);
447
                                continue;
448
                            }
449
                        }
450
                    }
451
 
452
                    foreach ($postids as $postid) {
453
                        $post = $this->posts[$postid];
454
                        if ($subscriptiontime) {
455
                            // Skip posts if the user subscribed to the discussion after it was created.
456
                            $subscribedafter = isset($subscriptiontime[$post->discussion]);
457
                            $subscribedafter = $subscribedafter && ($subscriptiontime[$post->discussion] > $post->created);
458
                            if ($subscribedafter) {
459
                                // The user subscribed to the discussion/forum after this post was created.
460
                                unset($poststructure[$courseid][$forumid][$discussionid][$postid]);
461
                                continue;
462
                            }
463
                        }
464
 
465
                        if ($maildigest > 0) {
466
                            // This user wants the mails to be in digest form.
467
                            $digeststructure[] = (object) [
468
                                'userid' => $user->id,
469
                                'discussionid' => $discussion->id,
470
                                'postid' => $post->id,
471
                                'timemodified' => $post->created,
472
                            ];
473
                            unset($poststructure[$courseid][$forumid][$discussionid][$postid]);
474
                            continue;
475
                        } else {
476
                            // Add this post to the list of postids to be sent.
477
                            $poststosend[] = $postid;
478
                        }
479
                    }
480
                }
481
 
482
                if (empty($poststructure[$courseid][$forumid])) {
483
                    // This user is not subscribed to any discussions in this forum at all.
484
                    unset($poststructure[$courseid][$forumid]);
485
                    continue;
486
                }
487
            }
488
            if (empty($poststructure[$courseid])) {
489
                // This user is not subscribed to any forums in this course.
490
                unset($poststructure[$courseid]);
491
            }
492
        }
493
 
494
        return [$poststosend, $digeststructure];
495
    }
496
 
497
    /**
498
     * Returns a list of all new posts that have not been mailed yet
499
     *
500
     * @param int $starttime posts created after this time
501
     * @param int $endtime posts created before this
502
     * @param int $now used for timed discussions only
503
     * @return array
504
     */
505
    protected function get_unmailed_posts($starttime, $endtime, $now = null) {
506
        global $CFG, $DB;
507
 
508
        $params = array();
509
        $params['mailed'] = FORUM_MAILED_PENDING;
510
        $params['ptimestart'] = $starttime;
511
        $params['ptimeend'] = $endtime;
512
        $params['mailnow'] = 1;
513
 
514
        if (!empty($CFG->forum_enabletimedposts)) {
515
            if (empty($now)) {
516
                $now = time();
517
            }
518
            $selectsql = "AND (p.created >= :ptimestart OR d.timestart >= :pptimestart)";
519
            $params['pptimestart'] = $starttime;
520
            $timedsql = "AND (d.timestart < :dtimestart AND (d.timeend = 0 OR d.timeend > :dtimeend))";
521
            $params['dtimestart'] = $now;
522
            $params['dtimeend'] = $now;
523
        } else {
524
            $timedsql = "";
525
            $selectsql = "AND p.created >= :ptimestart";
526
        }
527
 
528
        return $DB->get_records_sql(
529
               "SELECT
530
                    p.id,
531
                    p.discussion,
532
                    d.forum,
533
                    d.course,
534
                    p.created,
535
                    p.parent,
536
                    p.userid
537
                  FROM {forum_posts} p
538
                  JOIN {forum_discussions} d ON d.id = p.discussion
539
                 WHERE p.mailed = :mailed
540
                $selectsql
541
                   AND (p.created < :ptimeend OR p.mailnow = :mailnow)
542
                $timedsql
543
                 ORDER BY p.modified ASC",
544
             $params);
545
    }
546
}