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
 * Enrol coursecompleted plugin
19
 *
20
 * @package   enrol_coursecompleted
21
 * @copyright 2017 eWallah (www.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
/**
27
 * Enrol coursecompleted plugin
28
 *
29
 * @package   enrol_coursecompleted
30
 * @copyright 2017 eWallah (www.eWallah.net)
31
 * @author    Renaat Debleu <info@eWallah.net>
32
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
33
 */
34
class enrol_coursecompleted_plugin extends enrol_plugin {
35
    /** @var bool singleinstance. */
36
    private $singleinstance = false;
37
 
38
    /**
39
     * Returns localised name of enrol instance
40
     *
41
     * @param object $instance (null is accepted too)
42
     * @return string
43
     */
44
    public function get_instance_name($instance) {
45
        global $DB;
46
        if ($short = $DB->get_field('course', 'shortname', ['id' => $instance->customint1])) {
47
            $coursename = format_string($short, true, ['context' => \context_course::instance($instance->customint1)]);
48
            return get_string('aftercourse', 'enrol_coursecompleted', $coursename);
49
        }
50
        return get_string('coursedeleted', '', $instance->customint1);
51
    }
52
 
53
    /**
54
     * Returns optional enrolment information icons.
55
     *
56
     * @param array $instances all enrol instances of this type in one course
57
     * @return array of pix_icon
58
     */
59
    public function get_info_icons(array $instances) {
60
        global $DB;
61
        $arr = [];
62
        foreach ($instances as $instance) {
63
            if ($fullname = $DB->get_field('course', 'fullname', ['id' => $instance->customint1])) {
64
                $context = \context_course::instance($instance->customint1);
65
                $name = format_string($fullname, true, ['context' => $context]);
66
                $arr[] = new pix_icon('icon', get_string('aftercourse', 'enrol_coursecompleted', $name), 'enrol_coursecompleted');
67
            }
68
        }
69
        return $arr;
70
    }
71
 
72
    /**
73
     * Returns optional enrolment instance description text.
74
     *
75
     * @param object $instance
76
     * @return string short html text
77
     */
78
    public function get_description_text($instance) {
79
        return 'Enrolment by completion of course with id ' . $instance->customint1;
80
    }
81
 
82
    /**
83
     * Add information for people who want to enrol.
84
     *
85
     * @param stdClass $instance
86
     * @return string html text, usually a form in a text box
87
     */
88
    public function enrol_page_hook(stdClass $instance) {
89
        global $OUTPUT;
90
        $data = [];
91
        if ($this->get_config('svglearnpath')) {
92
            $items = $this->build_course_path($instance);
93
            $i = 1;
94
            foreach ($items as $item) {
95
                $course = get_course($item);
96
                $context = \context_course::instance($item);
97
                $data[] =
98
                    [
99
                        'first' => ($i === 1),
100
                        'course' => $item == $instance->courseid,
101
                        'title' => format_string($course->fullname, true, ['context' => $context]),
102
                        'href' => new moodle_url('/course/view.php', ['id' => $item]),
103
                        'seqnumber' => $i,
104
                    ];
105
                $i++;
106
            }
107
        }
108
        $course = get_course($instance->customint1);
109
        $context = \context_course::instance($instance->customint1);
110
        $rdata =
111
            [
112
                'coursetitle' => format_string($course->fullname, true, ['context' => $context]),
113
                'courseurl' => new moodle_url('/course/view.php', ['id' => $instance->customint1]),
114
                'hasdata' => count($data) > 1,
115
                'items' => $data,
116
            ];
117
        $str = $OUTPUT->render_from_template('enrol_coursecompleted/learnpath', $rdata);
118
        return $OUTPUT->box($str);
119
    }
120
 
121
    /**
122
     * Gets an array of the user enrolment actions
123
     *
124
     * @param \course_enrolment_manager $manager
125
     * @param stdClass $ue A user enrolment object
126
     * @return array An array of user_enrolment_actions
127
     */
128
    public function get_user_enrolment_actions(\course_enrolment_manager $manager, $ue) {
129
        global $DB;
130
        $actions = parent::get_user_enrolment_actions($manager, $ue);
131
        $id = $ue->enrolmentinstance->customint1;
132
        if ($DB->record_exists('course', ['id' => $id])) {
133
            $context = \context_course::instance($id);
134
            if (has_capability('report/completion:view', $context)) {
135
                $actions[] = new user_enrolment_action(
136
                    new pix_icon('a/search', ''),
137
                    get_string('pluginname', 'report_completion'),
138
                    new moodle_url('/report/completion/index.php', ['course' => $id]),
139
                    ['class' => 'originlink', 'rel' => $ue->id]
140
                );
141
            }
142
        }
143
        return $actions;
144
    }
145
 
146
    /**
147
     * Returns edit icons for the page with list of instances
148
     * @param stdClass $instance
149
     * @return array
150
     */
151
    public function get_action_icons(stdClass $instance) {
152
        global $OUTPUT;
153
        if ($instance->enrol !== 'coursecompleted') {
154
            throw new coding_exception('invalid enrol instance!');
155
        }
156
        $context = \context_course::instance($instance->courseid);
157
        $icons = [];
158
        if (has_capability('enrol/coursecompleted:enrolpast', $context)) {
159
            $managelink = new moodle_url("/enrol/coursecompleted/manage.php", ['enrolid' => $instance->id]);
160
            $icon = new pix_icon('t/enrolusers', get_string('enrolusers', 'enrol_manual'), 'core', ['class' => 'iconsmall']);
161
            $icons[] = $OUTPUT->action_icon($managelink, $icon);
162
        }
163
        return array_merge(parent::get_action_icons($instance), $icons);
164
    }
165
 
166
    /**
167
     * Restore instance and map settings.
168
     *
169
     * @param \restore_enrolments_structure_step $step
170
     * @param stdClass $data
171
     * @param stdClass $course
172
     * @param int $oldid
173
     */
174
    public function restore_instance(\restore_enrolments_structure_step $step, stdClass $data, $course, $oldid) {
175
        global $DB;
176
        if ($step->get_task()->get_target() == backup::TARGET_NEW_COURSE) {
177
            $merge = false;
178
        } else {
179
            $merge = [
180
                'courseid' => $course->id,
181
                'enrol' => 'coursecompleted',
182
                'roleid' => $data->roleid,
183
                'customint1' => $data->customint1,
184
                'customint2' => $data->customint2,
185
                'customint3' => $data->customint3,
186
            ];
187
        }
188
        if ($merge && $instances = $DB->get_records('enrol', $merge, 'id')) {
189
            $instance = reset($instances);
190
            $instanceid = $instance->id;
191
        } else {
192
            $instanceid = $this->add_instance($course, (array)$data);
193
        }
194
        $step->set_mapping('enrol', $oldid, $instanceid);
195
    }
196
 
197
    /**
198
     * Enrol user into course via enrol instance.
199
     *
200
     * @param stdClass $instance
201
     * @param int $userid
202
     * @param int $roleid optional role id
203
     * @param int $timestart 0 means unknown
204
     * @param int $timeend 0 means forever
205
     * @param int $status default to ENROL_USER_ACTIVE for new enrolments, no change by default in updates
206
     * @param bool $recovergrades restore grade history
207
     * @return void
208
     */
209
    public function enrol_user(
210
        stdClass $instance,
211
        $userid,
212
        $roleid = null,
213
        $timestart = 0,
214
        $timeend = 0,
215
        $status = null,
216
        $recovergrades = null
217
    ) {
218
        global $CFG, $DB;
219
        // We need to keep the role of the user.
220
        if (isset($instance->roleid)) {
221
            $roleid = $instance->roleid;
222
        }
223
        if (isset($instance->enrolstartdate)) {
224
            $timestart = $instance->enrolstartdate;
225
        }
226
        if (isset($instance->enrolenddate)) {
227
            $timeend = $instance->enrolenddate;
228
        }
229
        if (isset($instance->enrolperiod) && $instance->enrolperiod > 0) {
230
            $timeend = max(time(), $timestart) + $instance->enrolperiod;
231
        }
232
 
233
        if ($DB->record_exists('role', ['id' => $roleid])) {
234
            $context = \context_course::instance($instance->courseid, MUST_EXIST);
235
            parent::enrol_user($instance, $userid, $roleid, $timestart, $timeend, $status, $recovergrades);
236
            role_assign($roleid, $userid, $context->id, 'enrol_coursecompleted', $instance->id);
237
 
238
            // Send welcome message if needed.
239
            if ($instance->customint2 > 0) {
240
                // There is a course welcome message to be sent.
241
                $adhock = new \enrol_coursecompleted\task\send_welcome();
242
                $adhock->set_custom_data(
243
                    [
244
                        'userid' => $userid,
245
                        'enrolid' => $instance->id,
246
                        'courseid' => $instance->courseid,
247
                        'completedid' => $instance->customint1,
248
                    ]
249
                );
250
                $adhock->set_component('enrol_coursecompleted');
251
                \core\task\manager::queue_adhoc_task($adhock);
252
            }
253
 
254
            // Keep the user in a group when needed.
255
            if ($instance->customint3 > 0) {
256
                require_once($CFG->dirroot . '/group/lib.php');
257
                $groups = array_values(groups_get_user_groups($instance->customint1, $userid));
258
                foreach ($groups as $group) {
259
                    $subs = array_values($group);
260
                    foreach ($subs as $sub) {
261
                        $groupnamea = groups_get_group_name($sub);
262
                        $groupnameb = groups_get_group_by_name($instance->courseid, $groupnamea);
263
                        if ($groupnameb) {
264
                            groups_add_member($groupnameb, $userid);
265
                        }
266
                    }
267
                }
268
            }
269
            mark_user_dirty($userid);
270
        } else {
271
            debugging('Role does not exist', DEBUG_DEVELOPER);
272
        }
273
    }
274
 
275
    /**
276
     * Restore user enrolment.
277
     *
278
     * @param \restore_enrolments_structure_step $step
279
     * @param stdClass $data
280
     * @param stdClass $instance
281
     * @param int $userid
282
     * @param int $oldstatus
283
     */
284
    public function restore_user_enrolment(\restore_enrolments_structure_step $step, $data, $instance, $userid, $oldstatus) {
285
        if ($step->get_task()->get_target() == backup::TARGET_NEW_COURSE) {
286
            $this->enrol_user($instance, $userid);
287
        }
288
    }
289
 
290
    /**
291
     * Is it possible to add enrol instance via standard UI?
292
     *
293
     * @param int $courseid id of the course to add the instance to
294
     * @return boolean
295
     */
296
    public function can_add_instance($courseid) {
297
        return has_capability('enrol/coursecompleted:manage', context_course::instance($courseid));
298
    }
299
 
300
    /**
301
     * Is it possible to delete enrol instance via standard UI?
302
     *
303
     * @param object $instance
304
     * @return bool
305
     */
306
    public function can_delete_instance($instance) {
307
        return has_capability('enrol/coursecompleted:manage', context_course::instance($instance->courseid));
308
    }
309
 
310
    /**
311
     * Is it possible to hide/show enrol instance via standard UI?
312
     *
313
     * @param stdClass $instance
314
     * @return bool
315
     */
316
    public function can_hide_show_instance($instance): bool {
317
        return has_capability('enrol/coursecompleted:manage', context_course::instance($instance->courseid));
318
    }
319
 
320
    /**
321
     * Does this plugin allow manual unenrolment of all users?
322
     *
323
     * @param stdClass $instance course enrol instance
324
     * @return bool - true means user with 'enrol/xxx:unenrol' may unenrol others freely
325
     */
326
    public function allow_unenrol(stdClass $instance): bool {
327
        return has_capability('enrol/coursecompleted:manage', context_course::instance($instance->courseid));
328
    }
329
 
330
    /**
331
     * Does this plugin allow manual changes in user_enrolments table?
332
     *
333
     * @param stdClass $instance course enrol instance
334
     * @return bool - true means it is possible to change enrol period and status in user_enrolments table
335
     */
336
    public function allow_manage(stdClass $instance): bool {
337
        return has_capability('enrol/coursecompleted:manage', context_course::instance($instance->courseid));
338
    }
339
 
340
    /**
341
     * Does this plugin shows enrol me?
342
     *
343
     * @param stdClass $instance course enrol instance
344
     * @return bool - true means it is possible to enrol.
345
     */
346
    public function show_enrolme_link(stdClass $instance): bool {
347
        return ($instance->status == ENROL_INSTANCE_ENABLED);
348
    }
349
 
350
    /**
351
     * Execute synchronisation.
352
     * @param progress_trace $trace
353
     * @return int exit code, 0 means ok
354
     */
355
    public function sync(progress_trace $trace): int {
356
        $this->process_expirations($trace);
357
        return 0;
358
    }
359
 
360
    /**
361
     * We are a good plugin and don't invent our own UI/validation code path.
362
     *
363
     * @return bool
364
     */
365
    public function use_standard_editing_ui(): bool {
366
        return true;
367
    }
368
 
369
    /**
370
     * Add elements to the edit instance form.
371
     *
372
     * @param stdClass $instance
373
     * @param \MoodleQuickForm $mform
374
     * @param context $context
375
     * @return bool
376
     */
377
    public function edit_instance_form($instance, \MoodleQuickForm $mform, $context) {
378
        $options = [ENROL_INSTANCE_ENABLED => get_string('yes'), ENROL_INSTANCE_DISABLED => get_string('no')];
379
        $mform->addElement('select', 'status', get_string('enabled', 'admin'), $options);
380
        $mform->setDefault('status', $this->get_config('status'));
381
 
382
        $role = ($instance && isset($instance->roleid)) ? $instance->roleid : $this->get_config('roleid');
383
        $roles = get_default_enrol_roles($context, $role);
384
        $mform->addElement('select', 'roleid', get_string('assignrole', 'enrol_fee'), $roles);
385
        $mform->setDefault('roleid', $this->get_config('roleid'));
386
 
387
        $arr = ['optional' => true, 'defaultunit' => 86400];
388
        $mform->addElement('duration', 'enrolperiod', get_string('enrolperiod', 'enrol_coursecompleted'), $arr);
389
        $mform->setDefault('enrolperiod', $this->get_config('enrolperiod'));
390
        $mform->addHelpButton('enrolperiod', 'enrolperiod', 'enrol_coursecompleted');
391
 
392
        $start = ($instance && isset($instance->customint1)) ? get_course($instance->customint1)->startdate : time();
393
        $arr = ['optional' => true, 'defaulttime' => $start];
394
        $mform->addElement('date_time_selector', 'enrolstartdate', get_string('enrolstartdate', 'enrol_coursecompleted'), $arr);
395
        $mform->addHelpButton('enrolstartdate', 'enrolstartdate', 'enrol_coursecompleted');
396
 
397
        $duration = intval(get_config('moodlecourse', 'courseduration')) ?? YEARSECS;
398
        $arr['defaulttime'] = $start + $duration;
399
        $mform->addElement('date_time_selector', 'enrolenddate', get_string('enrolenddate', 'enrol_coursecompleted'), $arr);
400
        $mform->addHelpButton('enrolenddate', 'enrolenddate', 'enrol_coursecompleted');
401
 
402
        $conditions = ['onlywithcompletion' => true, 'multiple' => false, 'includefrontpage' => false];
403
        $mform->addElement('course', 'customint1', get_string('course'), $conditions);
404
        $mform->addRule('customint1', get_string('required'), 'required', null, 'client');
405
        $mform->addHelpButton('customint1', 'compcourse', 'enrol_coursecompleted');
406
 
407
        $mform->addElement('advcheckbox', 'customint3', get_string('groups'), get_string('group', 'enrol_coursecompleted'));
408
        $mform->addHelpButton('customint3', 'group', 'enrol_coursecompleted');
409
        $mform->setDefault('customint3', $this->get_config('keepgroup'));
410
 
411
        $mform->addElement(
412
            'advcheckbox',
413
            'customint2',
414
            get_string('categoryemail', 'admin'),
415
            get_string('welcome', 'enrol_coursecompleted')
416
        );
417
        $mform->addHelpButton('customint2', 'welcome', 'enrol_coursecompleted');
418
        $mform->setDefault('customint2', $this->get_config('welcome'));
419
 
420
        $arr = ['cols' => '60', 'rows' => '8'];
421
        $mform->addElement('textarea', 'customtext1', get_string('customwelcome', 'enrol_coursecompleted'), $arr);
422
        $mform->addHelpButton('customtext1', 'customwelcome', 'enrol_coursecompleted');
423
        $mform->disabledIf('customtext1', 'customint2', 'notchecked');
424
    }
425
 
426
    /**
427
     * Add new instance of enrol plugin.
428
     * @param object $course
429
     * @param array $fields
430
     * @return int id of new instance, null if can not be created
431
     */
432
    public function add_instance($course, array $fields = null): int {
433
        if ($fields) {
434
            if (!isset($fields['customint2'])) {
435
                $fields['customint2'] = $this->get_config('welcome', 1);
436
            }
437
            if (!isset($fields['customint3'])) {
438
                $fields['customint3'] = $this->get_config('keepgroup', 1);
439
            }
440
        }
441
        return parent::add_instance($course, $fields);
442
    }
443
 
444
    /**
445
     * Perform custom validation of the data used to edit the instance.
446
     *
447
     * @param array $data array of ("fieldname"=>value) of submitted data
448
     * @param array $files array of uploaded files "element_name"=>tmp_file_path
449
     * @param object $instance The instance loaded from the DB
450
     * @param context $context The context of the instance we are editing
451
     * @return array of "element_name"=>"error_description" if there are errors,
452
     *         or an empty array if everything is OK.
453
     */
454
    public function edit_instance_validation($data, $files, $instance, $context): array {
455
        global $DB;
456
        $errors = [];
457
        if ($data['status'] == ENROL_INSTANCE_ENABLED) {
458
            if (!empty($data['enrolenddate']) && $data['enrolenddate'] < $data['enrolstartdate']) {
459
                $errors['enrolenddate'] = get_string('enrolenddaterror', 'enrol_fee');
460
            }
461
            if (
462
                empty($data['customint1']) ||
463
                $data['customint1'] === 1 ||
464
                !$DB->record_exists('course', ['id' => $data['customint1']])
465
            ) {
466
                $errors['customint'] = get_string('error_nonexistingcourse', 'tool_generator');
467
            }
468
        }
469
        return $errors;
470
    }
471
 
472
    /**
473
     * Build (possible) coursepath
474
     *
475
     * @param stdClass $instance
476
     * @return array $items
477
     */
478
    public function build_course_path(stdClass $instance): array {
479
        $parents = $this->search_parents($instance->customint1);
480
        $children = $this->search_children($instance->courseid);
481
        return array_unique(array_merge($parents, $children));
482
    }
483
 
484
    /**
485
     * Search parents
486
     *
487
     * @param int $id
488
     * @param int $level
489
     * @return array items
490
     */
491
    private function search_parents($id, $level = 1): array {
492
        global $DB;
493
        $arr = [$id];
494
        if ($level < 5) {
495
            $level++;
496
            $params = ['enrol' => 'coursecompleted', 'courseid' => $id];
497
            if ($parent = $DB->get_field('enrol', 'customint1', $params, IGNORE_MULTIPLE)) {
498
                $arr = array_merge($this->search_parents($parent, $level), $arr);
499
            }
500
        }
501
        return $arr;
502
    }
503
 
504
    /**
505
     * Search children
506
     *
507
     * @param int $id
508
     * @param int $level
509
     * @return array items
510
     */
511
    private function search_children($id, $level = 1): array {
512
        global $DB;
513
        $arr = [$id];
514
        if ($level < 5) {
515
            $level++;
516
            $params = ['enrol' => 'coursecompleted', 'customint1' => $id];
517
            if ($child = $DB->get_field('enrol', 'courseid', $params, IGNORE_MULTIPLE)) {
518
                $arr = array_merge($arr, $this->search_children($child, $level));
519
            }
520
        }
521
        return $arr;
522
    }
523
 
524
    /**
525
     * Returns true if the plugin has one or more bulk operations that can be performed on
526
     * user enrolments.
527
     *
528
     * @param \course_enrolment_manager $manager
529
     * @return bool
530
     */
531
    public function has_bulk_operations(\course_enrolment_manager $manager): bool {
532
        if ($this->singleinstance == false) {
533
            $instances = array_values($manager->get_enrolment_instances(false));
534
            $i = 0;
535
            foreach ($instances as $instance) {
536
                if ($instance->enrol == 'coursecompleted') {
537
                    $i++;
538
                }
539
            }
540
            $this->singleinstance = (bool)($i === 1);
541
        }
542
        return $this->singleinstance;
543
    }
544
 
545
    /**
546
     * The enrol plugin has bulk operations that can be performed.
547
     * @param \course_enrolment_manager $manager
548
     * @return array
549
     */
550
    public function get_bulk_operations(\course_enrolment_manager $manager): array {
551
        $context = $manager->get_context();
552
        $bulkoperations = [];
553
        if ($this->has_bulk_operations($manager)) {
554
            if (has_capability("enrol/coursecompleted:manage", $context)) {
555
                $bulkoperations['editselectedusers'] = new \enrol_coursecompleted\bulkedit($manager, $this);
556
            }
557
            if (has_capability("enrol/coursecompleted:unenrol", $context)) {
558
                $bulkoperations['deleteselectedusers'] = new \enrol_coursecompleted\bulkdelete($manager, $this);
559
            }
560
        }
561
        return $bulkoperations;
562
    }
563
 
564
    /**
565
     * Enrol all users who completed in the past.
566
     * @param int $courseid
567
     */
568
    public static function enrol_past(int $courseid) {
569
        global $DB;
570
        $params = [
571
            'enrol' => 'coursecompleted',
572
            'status' => ENROL_INSTANCE_ENABLED,
573
            'customint1' => $courseid,
574
        ];
575
 
576
        if ($enrols = $DB->get_records('enrol', $params)) {
577
            $plugin = \enrol_get_plugin('coursecompleted');
578
            foreach ($enrols as $enrol) {
579
                $candidates = self::get_candidates($enrol->customint1);
580
                foreach ($candidates as $canid) {
581
                    $plugin->enrol_user($enrol, $canid);
582
                }
583
            }
584
        }
585
    }
586
 
587
    /**
588
     * Get all candidates for an enrolment.
589
     * @param int $courseid
590
     * @return array
591
     */
592
    public static function get_candidates(int $courseid): array {
593
        global $DB;
594
        $condition = 'course = ? AND timecompleted > 0';
595
        $candidates = [];
596
        if ($return = $DB->get_fieldset_select('course_completions', 'userid', $condition, [$courseid])) {
597
            $candidates = $return;
598
        }
599
        return $candidates;
600
    }
601
}