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
 * Event vault class
19
 *
20
 * @package    core_calendar
21
 * @copyright  2017 Ryan Wyllie <ryan@moodle.com>
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
namespace core_calendar\local\event\data_access;
26
 
27
defined('MOODLE_INTERNAL') || die();
28
 
29
use core_calendar\local\event\entities\action_event_interface;
30
use core_calendar\local\event\entities\event_interface;
31
use core_calendar\local\event\exceptions\limit_invalid_parameter_exception;
32
use core_calendar\local\event\factories\action_factory_interface;
33
use core_calendar\local\event\factories\event_factory_interface;
34
use core_calendar\local\event\strategies\raw_event_retrieval_strategy_interface;
35
 
36
/**
37
 * Event vault class.
38
 *
39
 * This class will handle interacting with the database layer to retrieve
40
 * the records. This is required to house the complex logic required for
41
 * pagination because it's not a one-to-one mapping between database records
42
 * and users.
43
 *
44
 * This is a repository. It's called a vault to reduce confusion because
45
 * Moodle has already taken the name repository. Vault is cooler anyway.
46
 *
47
 * @copyright 2017 Ryan Wyllie <ryan@moodle.com>
48
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
49
 */
50
class event_vault implements event_vault_interface {
51
 
52
    /**
53
     * @var event_factory_interface $factory Factory for creating events.
54
     */
55
    protected $factory;
56
 
57
    /**
58
     * @var raw_event_retrieval_strategy_interface $retrievalstrategy Strategy for getting events from the DB.
59
     */
60
    protected $retrievalstrategy;
61
 
62
    /**
63
     * Create an event vault.
64
     *
65
     * @param event_factory_interface $factory An event factory
66
     * @param raw_event_retrieval_strategy_interface $retrievalstrategy
67
     */
68
    public function __construct(
69
        event_factory_interface $factory,
70
        raw_event_retrieval_strategy_interface $retrievalstrategy
71
    ) {
72
        $this->factory = $factory;
73
        $this->retrievalstrategy = $retrievalstrategy;
74
    }
75
 
76
    public function get_event_by_id($id) {
77
        global $DB;
78
 
79
        if ($record = $DB->get_record('event', ['id' => $id])) {
80
            return $this->transform_from_database_record($record);
81
        } else {
82
            return false;
83
        }
84
    }
85
 
86
    public function get_events(
87
        $timestartfrom = null,
88
        $timestartto = null,
89
        $timesortfrom = null,
90
        $timesortto = null,
91
        event_interface $timestartafterevent = null,
92
        event_interface $timesortafterevent = null,
93
        $limitnum = 20,
94
        $type = null,
95
        array $usersfilter = null,
96
        array $groupsfilter = null,
97
        array $coursesfilter = null,
98
        array $categoriesfilter = null,
99
        $withduration = true,
100
        $ignorehidden = true,
101
        callable $filter = null,
102
        ?string $searchvalue = null
103
    ) {
104
 
105
        $fromquery = function($field, $timefrom, $lastseenmethod, $afterevent, $withduration) {
106
            if (!$timefrom) {
107
                return false;
108
            }
109
 
110
            return $this->timefield_pagination_from(
111
                $field,
112
                $timefrom,
113
                $afterevent ? $afterevent->get_times()->{$lastseenmethod}()->getTimestamp() : null,
114
                $afterevent ? $afterevent->get_id() : null,
115
                $withduration
116
            );
117
        };
118
 
119
        $toquery = function($field, $timeto, $lastseenmethod, $afterevent) {
120
            if (!$timeto) {
121
                return false;
122
            }
123
 
124
            return $this->timefield_pagination_to(
125
                $field,
126
                $timeto,
127
                $afterevent ? $afterevent->get_times()->{$lastseenmethod}()->getTimestamp() : null,
128
                $afterevent ? $afterevent->get_id() : null
129
            );
130
        };
131
 
132
        $timesortfromquery = $fromquery('timesort', $timesortfrom, 'get_sort_time', $timesortafterevent, $withduration);
133
        $timesorttoquery = $toquery('timesort', $timesortto, 'get_sort_time', $timesortafterevent);
134
        $timestartfromquery = $fromquery('timestart', $timestartfrom, 'get_start_time', $timestartafterevent, $withduration);
135
        $timestarttoquery = $toquery('timestart', $timestartto, 'get_start_time', $timestartafterevent);
136
 
137
        if (($timesortto && !$timesorttoquery) || ($timestartto && !$timestarttoquery)) {
138
            return [];
139
        }
140
 
141
        $searchquery = $this->generate_search_subquery($searchvalue);
142
 
143
        $params = array_merge(
144
            $type ? ['type' => $type] : [],
145
            $timesortfromquery ? $timesortfromquery['params'] : [],
146
            $timesorttoquery ? $timesorttoquery['params'] : [],
147
            $timestartfromquery ? $timestartfromquery['params'] : [],
148
            $timestarttoquery ? $timestarttoquery['params'] : [],
149
            $searchquery ? $searchquery['params'] : [],
150
        );
151
 
152
        $where = array_merge(
153
            $type ? ['type = :type'] : [],
154
            $timesortfromquery ? $timesortfromquery['where'] : [],
155
            $timesorttoquery ? $timesorttoquery['where'] : [],
156
            $timestartfromquery ? $timestartfromquery['where'] : [],
157
            $timestarttoquery ? $timestarttoquery['where'] : [],
158
            $searchquery ? [$searchquery['where']] : [],
159
        );
160
 
161
        $offset = 0;
162
        $events = [];
163
 
164
        while ($records = array_values($this->retrievalstrategy->get_raw_events(
165
            $usersfilter,
166
            $groupsfilter,
167
            $coursesfilter,
168
            $categoriesfilter,
169
            $where,
170
            $params,
171
            "COALESCE(e.timesort, e.timestart) ASC, e.id ASC",
172
            $offset,
173
            $limitnum,
174
            $ignorehidden
175
        ))) {
176
            foreach ($records as $record) {
177
                if ($event = $this->transform_from_database_record($record)) {
178
                    $filtertest = $filter ? $filter($event) : true;
179
 
180
                    if ($event && $filtertest) {
181
                        $events[] = $event;
182
                    }
183
 
184
                    if (count($events) == $limitnum) {
185
                        // We've got all of the events so break both loops.
186
                        break 2;
187
                    }
188
                }
189
            }
190
 
191
            if (!$limitnum) {
192
                break;
193
            } else {
194
                $offset += $limitnum;
195
            }
196
        }
197
 
198
        return $events;
199
    }
200
 
201
    public function get_action_events_by_timesort(
202
        \stdClass $user,
203
        $timesortfrom = null,
204
        $timesortto = null,
205
        event_interface $afterevent = null,
206
        $limitnum = 20,
207
        $limittononsuspendedevents = false,
208
        ?string $searchvalue = null
209
    ) {
210
        $courseids = array_map(function($course) {
211
            return $course->id;
212
        }, enrol_get_all_users_courses($user->id, $limittononsuspendedevents));
213
 
214
        $groupids = array_reduce($courseids, function($carry, $courseid) use ($user) {
215
            $groupings = groups_get_user_groups($courseid, $user->id);
216
            // Grouping 0 is all groups.
217
            return array_merge($carry, $groupings[0]);
218
        }, []);
219
 
220
        // Always include the site events.
221
        $courseids = $courseids ? array_merge($courseids, [SITEID]) : $courseids;
222
 
223
        return $this->get_events(
224
            null,
225
            null,
226
            $timesortfrom,
227
            $timesortto,
228
            null,
229
            $afterevent,
230
            $limitnum,
231
            CALENDAR_EVENT_TYPE_ACTION,
232
            [$user->id],
233
            $groupids ? $groupids : null,
234
            $courseids ? $courseids : null,
235
            null, // All categories.
236
            true,
237
            true,
238
            function ($event) {
239
                return $event instanceof action_event_interface;
240
            },
241
            $searchvalue
242
        );
243
    }
244
 
245
    public function get_action_events_by_course(
246
        \stdClass $user,
247
        \stdClass $course,
248
        $timesortfrom = null,
249
        $timesortto = null,
250
        event_interface $afterevent = null,
251
        $limitnum = 20,
252
        ?string $searchvalue = null
253
    ) {
254
        $groupings = groups_get_user_groups($course->id, $user->id);
255
        return array_values(
256
            $this->get_events(
257
                null,
258
                null,
259
                $timesortfrom,
260
                $timesortto,
261
                null,
262
                $afterevent,
263
                $limitnum,
264
                CALENDAR_EVENT_TYPE_ACTION,
265
                [$user->id],
266
                $groupings[0] ? $groupings[0] : null,
267
                [$course->id],
268
                [],
269
                true,
270
                true,
271
                function ($event) use ($course) {
272
                    return $event instanceof action_event_interface && $event->get_course()->get('id') == $course->id;
273
                },
274
                $searchvalue
275
            )
276
        );
277
    }
278
 
279
    /**
280
     * Generates SQL subquery and parameters for 'from' pagination.
281
     *
282
     * @param string    $field
283
     * @param int       $timefrom
284
     * @param int|null  $lastseentime
285
     * @param int|null  $lastseenid
286
     * @param bool      $withduration
287
     * @return array
288
     */
289
    protected function timefield_pagination_from(
290
        $field,
291
        $timefrom,
292
        $lastseentime = null,
293
        $lastseenid = null,
294
        $withduration = true
295
    ) {
296
        $where = '';
297
        $params = [];
298
 
299
        if ($lastseentime && $lastseentime >= $timefrom) {
300
            $where = '((timesort = :timefrom1 AND e.id > :timefromid) OR timesort > :timefrom2)';
301
            if ($field === 'timestart') {
302
                $where = '((timestart = :timefrom1 AND e.id > :timefromid) OR timestart > :timefrom2' .
303
                       ($withduration ? ' OR timestart + timeduration > :timefrom3' : '') . ')';
304
            }
305
            $params['timefromid'] = $lastseenid;
306
            $params['timefrom1'] = $lastseentime;
307
            $params['timefrom2'] = $lastseentime;
308
            $params['timefrom3'] = $lastseentime;
309
        } else {
310
            $where = 'timesort >= :timefrom';
311
            if ($field === 'timestart') {
312
                $where = '(timestart >= :timefrom' .
313
                       ($withduration ? ' OR timestart + timeduration > :timefrom2' : '') . ')';
314
            }
315
 
316
            $params['timefrom'] = $timefrom;
317
            $params['timefrom2'] = $timefrom;
318
        }
319
 
320
        return ['where' => [$where], 'params' => $params];
321
    }
322
 
323
    /**
324
     * Generates SQL subquery and parameters for 'to' pagination.
325
     *
326
     * @param string   $field
327
     * @param int      $timeto
328
     * @param int|null $lastseentime
329
     * @param int|null $lastseenid
330
     * @return array|bool
331
     */
332
    protected function timefield_pagination_to(
333
        $field,
334
        $timeto,
335
        $lastseentime = null,
336
        $lastseenid = null
337
    ) {
338
        $where = [];
339
        $params = [];
340
 
341
        if ($lastseentime && $lastseentime > $timeto) {
342
            // The last seen event from this set is after the time sort range which
343
            // means all events in this range have been seen, so we can just return
344
            // early here.
345
            return false;
346
        } else if ($lastseentime && $lastseentime == $timeto) {
347
            $where[] = '((timesort = :timeto1 AND e.id > :timetoid) OR timesort < :timeto2)';
348
            if ($field === 'timestart') {
349
                $where[] = '((timestart = :timeto1 AND e.id > :timetoid) OR timestart < :timeto2)';
350
            }
351
            $params['timetoid'] = $lastseenid;
352
            $params['timeto1'] = $timeto;
353
            $params['timeto2'] = $timeto;
354
        } else {
355
            $where[] = ($field === 'timestart' ? 'timestart' : 'timesort') . ' <= :timeto';
356
            $params['timeto'] = $timeto;
357
        }
358
 
359
        return ['where' => $where, 'params' => $params];
360
    }
361
 
362
    /**
363
     * Create an event from a database record.
364
     *
365
     * @param \stdClass $record The database record
366
     * @return event_interface|null
367
     */
368
    protected function transform_from_database_record(\stdClass $record) {
369
        return $this->factory->create_instance($record);
370
    }
371
 
372
    /**
373
     * Fetches records from DB.
374
     *
375
     * @param int    $userid
376
     * @param array|null $whereconditions
377
     * @param array  $whereparams
378
     * @param string $ordersql
379
     * @param int    $offset
380
     * @param int    $limitnum
381
     * @return array
382
     */
383
    protected function get_from_db(
384
        $userid,
385
        $whereconditions,
386
        $whereparams,
387
        $ordersql,
388
        $offset,
389
        $limitnum
390
    ) {
391
        return array_values(
392
            $this->retrievalstrategy->get_raw_events(
393
                [$userid],
394
                null,
395
                null,
396
                null,
397
                $whereconditions,
398
                $whereparams,
399
                $ordersql,
400
                $offset,
401
                $limitnum
402
            )
403
        );
404
    }
405
 
406
    /**
407
     * Generates SQL subquery and parameters for event searching.
408
     *
409
     * @param string|null $searchvalue Search value.
410
     * @return array|null
411
     */
412
    protected function generate_search_subquery(?string $searchvalue): ?array {
413
        global $CFG, $DB;
414
        if (!$searchvalue) {
415
            return null;
416
        }
417
 
418
        $parts = preg_split('/\s+/', $searchvalue);
419
        $wherecoursenameconditions = [];
420
        $whereactivitynameconditions = [];
421
        foreach ($parts as $index => $part) {
422
            // Course name searching.
423
            $wherecoursenameconditions[] = $DB->sql_like('c.fullname', ':cfullname' . $index, false);
424
            $params['cfullname'. $index] = '%' . $DB->sql_like_escape($part) . '%';
425
 
426
            // Activity name searching.
427
            $whereactivitynameconditions[] = $DB->sql_like('e.name', ':eventname' . $index, false);
428
            $params['eventname'. $index] = '%' . $DB->sql_like_escape($part) . '%';
429
        }
430
 
431
        // Activity type searching.
432
        $whereconditions[] = $DB->sql_like('e.modulename', ':modulename', false);
433
        $params['modulename'] = '%' . $DB->sql_like_escape($searchvalue) . '%';
434
 
435
        // Activity type searching (localised type name).
436
        require_once($CFG->dirroot . '/course/lib.php');
437
        // Search in modules' singular and plural names.
438
        $modules = array_keys(array_merge(
439
            preg_grep('/' . $searchvalue . '/i', get_module_types_names()) ?: [],
440
            preg_grep('/' . $searchvalue . '/i', get_module_types_names(true)) ?: [],
441
        ));
442
        if ($modules) {
443
            [$insql, $inparams] = $DB->get_in_or_equal($modules, SQL_PARAMS_NAMED, 'exactmodulename');
444
            $whereconditions[] = 'e.modulename ' . $insql;
445
            $params += $inparams;
446
        }
447
 
448
        $whereclause = '(';
449
        $whereclause .= implode(' OR ', $whereconditions);
450
        $whereclause .= ' OR (' . implode(' AND ', $wherecoursenameconditions) . ')';
451
        $whereclause .= ' OR (' . implode(' AND ', $whereactivitynameconditions) . ')';
452
        $whereclause .= ')';
453
 
454
        return ['where' => $whereclause, 'params' => $params];
455
    }
456
}