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
 * Date condition.
19
 *
20
 * @package   availability_relativedate
21
 * @copyright 2022 eWallah.net
22
 * @author    Renaat Debleu <info@eWallah.net>
23
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 */
25
 
26
namespace availability_relativedate;
27
 
28
use context_course;
29
use core_availability\info;
30
use stdClass;
31
 
32
/**
33
 * relativedate from course start condition.
34
 *
35
 * @package   availability_relativedate
36
 * @copyright 2022 eWallah.net
37
 * @author    Renaat Debleu <info@eWallah.net>
38
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39
 */
40
class condition extends \core_availability\condition {
41
    /** @var int relativenumber (how many relative) for condition. */
42
    private $relativenumber;
43
 
44
    /** @var int relativedwm (what does the date relates to) for condition.
45
     *
46
     * 0 => minutes
47
     * 1 => hours
48
     * 2 => days
49
     * 3 => weeks
50
     * 4 => months
51
     */
52
    private $relativedwm;
53
 
54
    /** @var int relativestart (what date relates to) for condition.
55
     *
56
     * 1 => After Course start date
57
     * 2 => Before Course end date
58
     * 3 => After User enrolment date
59
     * 4 => After Enrolment method end date
60
     * 5 => After Course End date
61
     * 6 => Before Course start date
62
     * 7 => After completion of an activity
63
 
64
     */
65
    private $relativestart;
66
 
67
    /**
68
     * @var int Course module id of the activity used by type 6
69
     */
70
    private $relativecoursemodule;
71
 
72
    /**
73
     * Constructor.
74
     *
75
     * @param stdClass $structure Data structure from JSON decode.
76
     */
77
    public function __construct($structure) {
78
        $this->relativenumber = property_exists($structure, 'n') ? (int)$structure->n : 1;
79
        $this->relativedwm = property_exists($structure, 'd') ? (int)$structure->d : 2;
80
        $this->relativestart = property_exists($structure, 's') ? (int)$structure->s : 1;
81
        $this->relativecoursemodule = property_exists($structure, 'm') ? (int)$structure->m : 0;
82
    }
83
 
84
    /**
85
     * Saves the data.
86
     *
87
     * @return object data structure.
88
     */
89
    public function save() {
90
        return (object)[
91
            'type' => 'relativedate',
92
            'n' => intval($this->relativenumber),
93
            'd' => intval($this->relativedwm),
94
            's' => intval($this->relativestart),
95
            'm' => intval($this->relativecoursemodule),
96
        ];
97
    }
98
 
99
    /**
100
     * Determines whether this particular item is currently available.
101
     *
102
     * @param bool $not
103
     * @param info $info
104
     * @param bool $grabthelot
105
     * @param int $userid If set, specifies a different user ID to check availability for
106
     * @return bool True if this item is available to the user, false otherwise
107
     */
108
    public function is_available($not, info $info, $grabthelot, $userid) {
109
        $calc = $this->calc($info->get_course(), $userid);
110
        if ($calc === 0) {
111
            // Always not available if for some reason the value could not be calculated.
112
            return false;
113
        }
114
        $allow = time() >= $calc;
115
        if ($not) {
116
            $allow = !$allow;
117
        }
118
        return $allow;
119
    }
120
 
121
    /**
122
     * Obtains a string describing this restriction (whether or not it actually applies).
123
     *
124
     * @param bool $full Set true if this is the 'full information' view
125
     * @param bool $not Set true if we are inverting the condition
126
     * @param info $info Item we're checking
127
     * @return string Information string (for admin) about all restrictions on this item
128
     */
129
    public function get_description($full, $not, info $info): string {
130
        global $USER;
131
        $course = $info->get_course();
132
        $capability = has_capability('moodle/course:manageactivities', context_course::instance($course->id));
133
        $relative = (int)$this->relativestart;
134
        if ($relative === 2 || $relative === 5) {
135
            if ((!isset($course->enddate) || (int)$course->enddate === 0) && $capability) {
136
                return get_string('noenddate', 'availability_relativedate');
137
            }
138
        }
139
        if ($relative === 2 || $relative === 6) {
140
            $frut = $not ? 'from' : 'until';
141
        } else {
142
            $frut = $not ? 'until' : 'from';
143
        }
144
        $calc = $this->calc($course, $USER->id);
145
        if ($calc === 0) {
146
            return '(' . trim($this->get_debug_string()) . ')';
147
        }
148
        $a = new stdClass();
149
        $a->rnumber = userdate($calc, get_string('strftimedatetime', 'langconfig'));
150
        $a->rtime = ($capability && $full) ? '(' . trim($this->get_debug_string()) . ')' : '';
151
        $a->rela = '';
152
        return trim(get_string($frut, 'availability_relativedate', $a));
153
    }
154
 
155
    /**
156
     * Obtains a representation of the options of this condition as a string for debugging.
157
     *
158
     * @return string Text representation of parameters
159
     */
160
    protected function get_debug_string() {
161
        $modname = '';
162
        if ((int)$this->relativestart === 7) {
163
            $modname = ' ';
164
            if ($this->relativecoursemodule != -1 && get_coursemodule_from_id('', $this->relativecoursemodule)) {
165
                $modname .= \core_availability\condition::description_cm_name($this->relativecoursemodule);
166
            } else {
167
                $modname .= \html_writer::span(get_string('missing', 'availability_relativedate'), 'alert alert-danger');
168
            }
169
        }
170
        return ' ' . $this->relativenumber . ' ' . self::options_dwm($this->relativenumber)[$this->relativedwm] . ' ' .
171
               self::options_start($this->relativestart) . $modname;
172
    }
173
 
174
    /**
175
     * Obtains a the options for days week months.
176
     *
177
     * @param int $i index
178
     * @return string
179
     */
180
    public static function options_start(int $i) {
181
        switch ($i) {
182
            case 1:
183
                return get_string('datestart', 'availability_relativedate');
184
            case 2:
185
                return get_string('dateend', 'availability_relativedate');
186
            case 3:
187
                return get_string('dateenrol', 'availability_relativedate');
188
            case 4:
189
                return get_string('dateendenrol', 'availability_relativedate');
190
            case 5:
191
                return get_string('dateendafter', 'availability_relativedate');
192
            case 6:
193
                return get_string('datestartbefore', 'availability_relativedate');
194
            case 7:
195
                return get_string('datecompletion', 'availability_relativedate');
196
        }
197
        return '';
198
    }
199
 
200
    /**
201
     * Obtains a the options for hours days weeks months.
202
     *
203
     * @param int $number
204
     * @return array
205
     */
206
    public static function options_dwm($number = 2) {
207
        $s = $number === 1 ? '' : 's';
208
        return [
209
 
210
            1 => get_string('hour' . $s, 'availability_relativedate'),
211
            2 => get_string('day' . $s, 'availability_relativedate'),
212
            3 => get_string('week' . $s, 'availability_relativedate'),
213
            4 => get_string('month' . $s, 'availability_relativedate'),
214
        ];
215
    }
216
 
217
    /**
218
     * Obtains a the options for hour day week month.
219
     *
220
     * @param int $i
221
     * @return string
222
     */
223
    public static function option_dwm(int $i): string {
224
        switch ($i) {
225
            case 0:
226
                return 'minute';
227
            case 1:
228
                return 'hour';
229
            case 2:
230
                return 'day';
231
            case 3:
232
                return 'week';
233
            case 4:
234
                return 'month';
235
        }
236
        return '';
237
    }
238
 
239
    /**
240
     * Perform the calculation.
241
     *
242
     * @param stdClass $course
243
     * @param int $userid
244
     * @return int relative date.
245
     */
246
    private function calc($course, $userid): int {
247
        $x = $this->relativenumber . ' ' . $this->option_dwm($this->relativedwm);
248
        switch ($this->relativestart) {
249
            case 6:
250
                // Before course start date.
251
                return $this->fixdate("-$x", $course->startdate);
252
            case 2:
253
                // Before course end date.
254
                return $this->fixdate("-$x", $course->enddate);
255
            case 5:
256
                // After course end date.
257
                return $this->fixdate("+$x", $course->enddate);
258
            case 3:
259
                // After latest enrolment start date.
260
                $sql = 'SELECT ue.timestart
261
                        FROM {user_enrolments} ue
262
                        JOIN {enrol} e on ue.enrolid = e.id
263
                        WHERE e.courseid = :courseid AND ue.userid = :userid AND ue.timestart > 0
264
                        ORDER by ue.timestart DESC';
265
                $lowest = $this->getlowest($sql, ['courseid' => $course->id, 'userid' => $userid]);
266
                if ($lowest === 0) {
267
                    // A teacher or admin without restriction - or a student with no limit set?
268
                    $sql = 'SELECT ue.timecreated
269
                            FROM {user_enrolments} ue
270
                            JOIN {enrol} e on (e.id = ue.enrolid AND e.courseid = :courseid)
271
                            WHERE ue.userid = :userid
272
                            ORDER by ue.timecreated DESC';
273
                    $lowest = $this->getlowest($sql, ['courseid' => $course->id, 'userid' => $userid]);
274
                }
275
                return $this->fixdate("+$x", $lowest);
276
            case 4:
277
                // After latest enrolment end date.
278
                $sql = 'SELECT e.enrolenddate
279
                        FROM {user_enrolments} ue
280
                        JOIN {enrol} e on ue.enrolid = e.id
281
                        WHERE e.courseid = :courseid AND ue.userid = :userid
282
                        ORDER by e.enrolenddate DESC';
283
                $lowest = $this->getlowest($sql, ['courseid' => $course->id, 'userid' => $userid]);
284
                return $this->fixdate("+$x", $lowest);
285
            case 7:
286
                // Since completion of a module.
287
 
288
                if ($this->relativecoursemodule < 1) {
289
                    return 0;
290
                }
291
 
292
                $cm = new stdClass();
293
                $cm->id = $this->relativecoursemodule;
294
                $cm->course = $course->id;
295
                try {
296
                    $completion = new \completion_info($course);
297
                    $data = $completion->get_data($cm, false, $userid);
298
                    return $this->fixdate("+$x", $data->timemodified);
299
                } catch (\Exception $e) {
300
                    return 0;
301
                }
302
        }
303
        // After course start date.
304
        return $this->fixdate("+$x", $course->startdate);
305
    }
306
 
307
    /**
308
     * Get the record with the lowest value.
309
     *
310
     * @param string $sql
311
     * @param array $parameters
312
     * @return int lowest value.
313
     */
314
    private function getlowest($sql, $parameters): int {
315
        global $DB;
316
        if ($lowestrec = $DB->get_record_sql($sql, $parameters, IGNORE_MULTIPLE)) {
317
            $recs = array_values(get_object_vars($lowestrec));
318
            foreach ($recs as $value) {
319
                return $value;
320
            }
321
        }
322
        return 0;
323
    }
324
 
325
 
326
    /**
327
     * Keep the original hour.
328
     *
329
     * @param string $calc
330
     * @param int $newdate
331
     * @return int relative date.
332
     */
333
    private function fixdate($calc, $newdate): int {
334
        if ($newdate > 0) {
335
            $olddate = strtotime($calc, $newdate);
336
            if ($this->relativedwm > 1) {
337
                $arr1 = getdate($olddate);
338
                $arr2 = getdate($newdate);
339
                return mktime($arr2['hours'], $arr2['minutes'], $arr2['seconds'], $arr1['mon'], $arr1['mday'], $arr1['year']);
340
            }
341
            return $olddate;
342
        }
343
        return 0;
344
    }
345
 
346
    /**
347
     * Used in course/lib.php because we need to disable the completion JS if
348
     * a completion value affects a conditional activity.
349
     * @param int|stdClass $course Moodle course object
350
     * @param int $cmid Course-module id
351
     * @return bool True if this is used in a condition, false otherwise
352
     */
353
    public static function completion_value_used($course, $cmid): bool {
354
        global $DB;
355
        $courseobj = (is_object($course)) ? $course : get_course($course);
356
        $modinfo = get_fast_modinfo($courseobj);
357
        foreach ($modinfo->cms as $othercm) {
358
            if (is_null($othercm->availability)) {
359
                continue;
360
            }
361
            $ci = new \core_availability\info_module($othercm);
362
            $tree = $ci->get_availability_tree();
363
            foreach ($tree->get_all_children('availability_relativedate\condition') as $cond) {
364
                if ((int)$cond->relativestart === 7 && (int)$cond->relativecoursemodule === (int)$cmid) {
365
                    return true;
366
                }
367
            }
368
        }
369
        // Availability of sections (get_section_info_all) is always null.
370
        $sqllike = $DB->sql_like('availability', ':availability');
371
        $params = ['course' => $courseobj->id, 'availability' => '%"s":7,"m":' . $cmid . '%'];
372
        return count($DB->get_records_sql("SELECT id FROM {course_sections} WHERE course = :course AND $sqllike", $params)) > 0;
373
    }
374
 
375
    /**
376
     * Helper for updating ids, implemented for course modules and sections
377
     *
378
     * @param string $table
379
     * @param int $oldid
380
     * @param int $newid
381
     * @return bool
382
     */
383
    public function update_dependency_id($table, $oldid, $newid) {
384
        if (
385
            ($table === 'course_modules' || $table === 'course_sections') &&
386
            (int)$this->relativestart === 7 &&
387
            (int)$this->relativecoursemodule === (int)$oldid
388
        ) {
389
            $this->relativecoursemodule = $newid;
390
            return true;
391
        }
392
        return false;
393
    }
394
 
395
    /**
396
     * Updates this node after restore, returning true if anything changed.
397
     *
398
     * @param string $restoreid Restore ID
399
     * @param int $courseid ID of target course
400
     * @param \base_logger $logger Logger for any warnings
401
     * @param string $name Name of this item (for use in warning messages)
402
     * @return bool True if there was any change
403
     */
404
    public function update_after_restore($restoreid, $courseid, \base_logger $logger, $name): bool {
405
        global $DB;
406
        $rec = \restore_dbops::get_backup_ids_record($restoreid, 'course_module', $this->relativecoursemodule);
407
        if (!$rec || !$rec->newitemid) {
408
            // If we are on the same course (e.g. duplicate) then we can just use the existing one.
409
            if (!$DB->record_exists('course_modules', ['id' => $this->relativecoursemodule, 'course' => $courseid])) {
410
                $this->relativecoursemodule = 0;
411
                $logger->process(
412
                    "Restored item ($name has availability condition on module that was not restored",
413
                    \backup::LOG_WARNING
414
                );
415
            }
416
        } else {
417
            $this->relativecoursemodule = (int)$rec->newitemid;
418
        }
419
        return true;
420
    }
421
}