lib.php 24 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Self enrolment plugin.
 *
20
 * @package    enrol_self
Petr Skoda's avatar
Petr Skoda committed
21
22
 * @copyright  2010 Petr Skoda  {@link http://skodak.org}
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
24
25
26
27
28
29
30
31
 */

/**
 * Self enrolment plugin implementation.
 * @author Petr Skoda
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class enrol_self_plugin extends enrol_plugin {

32
33
34
    protected $lasternoller = null;
    protected $lasternollerinstanceid = 0;

35
36
37
38
39
40
41
42
43
44
45
46
    /**
     * Returns optional enrolment information icons.
     *
     * This is used in course list for quick overview of enrolment options.
     *
     * We are not using single instance parameter because sometimes
     * we might want to prevent icon repetition when multiple instances
     * of one type exist. One instance may also produce several icons.
     *
     * @param array $instances all enrol instances of this type in one course
     * @return array of pix_icon
     */
47
    public function get_info_icons(array $instances) {
48
49
50
        $key = false;
        $nokey = false;
        foreach ($instances as $instance) {
51
52
53
54
            if (!$instance->customint6) {
                // New enrols not allowed.
                continue;
            }
55
56
57
58
59
60
61
62
63
64
65
66
67
68
            if ($instance->password or $instance->customint1) {
                $key = true;
            } else {
                $nokey = true;
            }
        }
        $icons = array();
        if ($nokey) {
            $icons[] = new pix_icon('withoutkey', get_string('pluginname', 'enrol_self'), 'enrol_self');
        }
        if ($key) {
            $icons[] = new pix_icon('withkey', get_string('pluginname', 'enrol_self'), 'enrol_self');
        }
        return $icons;
69
70
    }

71
72
73
    /**
     * Returns localised name of enrol instance
     *
74
     * @param stdClass $instance (null is accepted too)
75
76
77
78
79
80
81
     * @return string
     */
    public function get_instance_name($instance) {
        global $DB;

        if (empty($instance->name)) {
            if (!empty($instance->roleid) and $role = $DB->get_record('role', array('id'=>$instance->roleid))) {
82
                $role = ' (' . role_get_name($role, context_course::instance($instance->courseid, IGNORE_MISSING)) . ')';
83
84
85
86
87
88
89
90
91
92
93
            } else {
                $role = '';
            }
            $enrol = $this->get_name();
            return get_string('pluginname', 'enrol_'.$enrol) . $role;
        } else {
            return format_string($instance->name);
        }
    }

    public function roles_protected() {
94
        // Users may tweak the roles later.
95
96
97
98
        return false;
    }

    public function allow_unenrol(stdClass $instance) {
99
        // Users with unenrol cap may unenrol other users manually manually.
100
101
102
103
        return true;
    }

    public function allow_manage(stdClass $instance) {
104
        // Users with manage cap may tweak period and status.
105
106
107
        return true;
    }

108
    public function show_enrolme_link(stdClass $instance) {
109

110
        if (true !== $this->can_self_enrol($instance, false)) {
111
112
113
            return false;
        }

114
        return true;
115
116
    }

117
    /**
Petr Skoda's avatar
Petr Skoda committed
118
     * Sets up navigation entries.
119
     *
120
121
     * @param stdClass $instancesnode
     * @param stdClass $instance
Petr Skoda's avatar
Petr Skoda committed
122
     * @return void
123
124
125
126
127
128
     */
    public function add_course_navigation($instancesnode, stdClass $instance) {
        if ($instance->enrol !== 'self') {
             throw new coding_exception('Invalid enrol instance type!');
        }

129
        $context = context_course::instance($instance->courseid);
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
        if (has_capability('enrol/self:config', $context)) {
            $managelink = new moodle_url('/enrol/self/edit.php', array('courseid'=>$instance->courseid, 'id'=>$instance->id));
            $instancesnode->add($this->get_instance_name($instance), $managelink, navigation_node::TYPE_SETTING);
        }
    }

    /**
     * Returns edit icons for the page with list of instances
     * @param stdClass $instance
     * @return array
     */
    public function get_action_icons(stdClass $instance) {
        global $OUTPUT;

        if ($instance->enrol !== 'self') {
            throw new coding_exception('invalid enrol instance!');
        }
147
        $context = context_course::instance($instance->courseid);
148
149
150
151
152

        $icons = array();

        if (has_capability('enrol/self:config', $context)) {
            $editlink = new moodle_url("/enrol/self/edit.php", array('courseid'=>$instance->courseid, 'id'=>$instance->id));
153
            $icons[] = $OUTPUT->action_icon($editlink, new pix_icon('t/edit', get_string('edit'), 'core',
154
                array('class' => 'iconsmall')));
155
156
157
158
159
        }

        return $icons;
    }

160
161
162
163
164
    /**
     * Returns link to page which may be used to add new instance of enrolment plugin in course.
     * @param int $courseid
     * @return moodle_url page url
     */
165
    public function get_newinstance_link($courseid) {
166
        $context = context_course::instance($courseid, MUST_EXIST);
167

Sun Zhigang's avatar
Sun Zhigang committed
168
        if (!has_capability('moodle/course:enrolconfig', $context) or !has_capability('enrol/self:config', $context)) {
169
170
            return NULL;
        }
171
        // Multiple instances supported - different roles with different password.
172
        return new moodle_url('/enrol/self/edit.php', array('courseid'=>$courseid));
173
174
    }

175
176
177
178
179
180
181
182
    /**
     * Self enrol user to course
     *
     * @param stdClass $instance enrolment instance
     * @param stdClass $data data needed for enrolment.
     * @return bool|array true if enroled else eddor code and messege
     */
    public function enrol_self(stdClass $instance, $data = null) {
183
        global $DB, $USER, $CFG;
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199

        // Don't enrol user if password is not passed when required.
        if ($instance->password && !isset($data->enrolpassword)) {
            return;
        }

        $timestart = time();
        if ($instance->enrolperiod) {
            $timeend = $timestart + $instance->enrolperiod;
        } else {
            $timeend = 0;
        }

        $this->enrol_user($instance, $USER->id, $instance->roleid, $timestart, $timeend);

        if ($instance->password and $instance->customint1 and $data->enrolpassword !== $instance->password) {
200
            // It must be a group enrolment, let's assign group too.
201
202
203
204
205
206
            $groups = $DB->get_records('groups', array('courseid'=>$instance->courseid), 'id', 'id, enrolmentkey');
            foreach ($groups as $group) {
                if (empty($group->enrolmentkey)) {
                    continue;
                }
                if ($group->enrolmentkey === $data->enrolpassword) {
207
208
                    // Add user to group.
                    require_once($CFG->dirroot.'/group/lib.php');
209
210
211
212
213
214
215
216
217
218
219
                    groups_add_member($group->id, $USER->id);
                    break;
                }
            }
        }
        // Send welcome message.
        if ($instance->customint4) {
            $this->email_welcome_message($instance, $USER);
        }
    }

220
221
222
223
224
225
226
227
    /**
     * Creates course enrol form, checks if form submitted
     * and enrols user if necessary. It can also redirect.
     *
     * @param stdClass $instance
     * @return string html text, usually a form in a text box
     */
    public function enrol_page_hook(stdClass $instance) {
228
229
230
231
232
233
        global $CFG, $OUTPUT, $USER;

        require_once("$CFG->dirroot/enrol/self/locallib.php");

        $enrolstatus = $this->can_self_enrol($instance);

234
235
236
237
238
239
240
241
        // Don't show enrolment instance form, if user can't enrol using it.
        if (true === $enrolstatus) {
            $form = new enrol_self_enrol_form(NULL, $instance);
            $instanceid = optional_param('instance', 0, PARAM_INT);
            if ($instance->id == $instanceid) {
                if ($data = $form->get_data()) {
                    $this->enrol_self($instance, $data);
                }
242
243
            }

244
245
246
247
248
            ob_start();
            $form->display();
            $output = ob_get_clean();
            return $OUTPUT->box($output);
        }
249
250
251
252
253
254
    }

    /**
     * Checks if user can self enrol.
     *
     * @param stdClass $instance enrolment instance
255
256
257
     * @param bool $checkuserenrolment if true will check if user enrolment is inactive.
     *             used by navigation to improve performance.
     * @return bool|string true if successful, else error message or false.
258
     */
259
260
    public function can_self_enrol(stdClass $instance, $checkuserenrolment = true) {
        global $DB, $USER, $CFG;
261

262
263
264
265
266
267
268
269
270
271
272
273
274
        if ($checkuserenrolment) {
            if (isguestuser()) {
                // Can not enrol guest.
                return get_string('canntenrol', 'enrol_self');
            }
            // Check if user is already enroled.
            if ($DB->get_record('user_enrolments', array('userid' => $USER->id, 'enrolid' => $instance->id))) {
                return get_string('canntenrol', 'enrol_self');
            }
        }

        if ($instance->status != ENROL_INSTANCE_ENABLED) {
            return get_string('canntenrol', 'enrol_self');
275
276
        }

277
        if ($instance->enrolstartdate != 0 and $instance->enrolstartdate > time()) {
278
            return get_string('canntenrol', 'enrol_self');
279
280
        }

281
        if ($instance->enrolenddate != 0 and $instance->enrolenddate < time()) {
282
            return get_string('canntenrol', 'enrol_self');
283
284
        }

285
286
        if (!$instance->customint6) {
            // New enrols not allowed.
287
            return get_string('canntenrol', 'enrol_self');
288
289
290
        }

        if ($DB->record_exists('user_enrolments', array('userid' => $USER->id, 'enrolid' => $instance->id))) {
291
            return get_string('canntenrol', 'enrol_self');
292
293
294
295
296
297
298
        }

        if ($instance->customint3 > 0) {
            // Max enrol limit specified.
            $count = $DB->count_records('user_enrolments', array('enrolid' => $instance->id));
            if ($count >= $instance->customint3) {
                // Bad luck, no more self enrolments here.
299
                return get_string('maxenrolledreached', 'enrol_self');
300
            }
301
302
        }

303
304
305
        if ($instance->customint5) {
            require_once("$CFG->dirroot/cohort/lib.php");
            if (!cohort_is_member($instance->customint5, $USER->id)) {
306
                $cohort = $DB->get_record('cohort', array('id' => $instance->customint5));
307
308
309
                if (!$cohort) {
                    return null;
                }
310
                $a = format_string($cohort->name, true, array('context' => context::instance_by_id($cohort->contextid)));
311
                return markdown_to_html(get_string('cohortnonmemberinfo', 'enrol_self', $a));
312
313
314
            }
        }

315
316
        return true;
    }
317

318
319
320
321
322
323
324
325
    /**
     * Return information for enrolment instance containing list of parameters required
     * for enrolment, name of enrolment plugin etc.
     *
     * @param stdClass $instance enrolment instance
     * @return stdClass instance info.
     */
    public function get_enrol_info(stdClass $instance) {
326

327
328
329
330
331
        $instanceinfo = new stdClass();
        $instanceinfo->id = $instance->id;
        $instanceinfo->courseid = $instance->courseid;
        $instanceinfo->type = $this->get_name();
        $instanceinfo->name = $this->get_instance_name($instance);
332
        $instanceinfo->status = $this->can_self_enrol($instance);
333

334
335
336
        if ($instance->password) {
            $instanceinfo->requiredparam = new stdClass();
            $instanceinfo->requiredparam->enrolpassword = get_string('password', 'enrol_self');
337
338
        }

339
340
341
342
        // If enrolment is possible and password is required then return ws function name to get more information.
        if ((true === $instanceinfo->status) && $instance->password) {
            $instanceinfo->wsfunction = 'enrol_self_get_instance_info';
        }
343
        return $instanceinfo;
344
345
346
347
    }

    /**
     * Add new instance of enrol plugin with default settings.
348
     * @param stdClass $course
349
350
351
     * @return int id of new instance
     */
    public function add_default_instance($course) {
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
        $fields = $this->get_instance_defaults();

        if ($this->get_config('requirepassword')) {
            $fields['password'] = generate_password(20);
        }

        return $this->add_instance($course, $fields);
    }

    /**
     * Returns defaults for new instances.
     * @return array
     */
    public function get_instance_defaults() {
        $expirynotify = $this->get_config('expirynotify');
367
368
369
370
371
372
        if ($expirynotify == 2) {
            $expirynotify = 1;
            $notifyall = 1;
        } else {
            $notifyall = 0;
        }
373

374
375
376
377
378
379
380
381
382
383
384
385
        $fields = array();
        $fields['status']          = $this->get_config('status');
        $fields['roleid']          = $this->get_config('roleid');
        $fields['enrolperiod']     = $this->get_config('enrolperiod');
        $fields['expirynotify']    = $expirynotify;
        $fields['notifyall']       = $notifyall;
        $fields['expirythreshold'] = $this->get_config('expirythreshold');
        $fields['customint1']      = $this->get_config('groupkey');
        $fields['customint2']      = $this->get_config('longtimenosee');
        $fields['customint3']      = $this->get_config('maxenrolled');
        $fields['customint4']      = $this->get_config('sendcoursewelcomemessage');
        $fields['customint5']      = 0;
386
        $fields['customint6']      = $this->get_config('newenrols');
387
388

        return $fields;
389
390
391
    }

    /**
392
     * Send welcome email to specified user.
393
     *
394
395
     * @param stdClass $instance
     * @param stdClass $user user record
396
397
398
399
400
     * @return void
     */
    protected function email_welcome_message($instance, $user) {
        global $CFG, $DB;

401
        $course = $DB->get_record('course', array('id'=>$instance->courseid), '*', MUST_EXIST);
402
        $context = context_course::instance($course->id);
403

404
        $a = new stdClass();
405
        $a->coursename = format_string($course->fullname, true, array('context'=>$context));
406
407
408
409
410
411
        $a->profileurl = "$CFG->wwwroot/user/view.php?id=$user->id&course=$course->id";

        if (trim($instance->customtext1) !== '') {
            $message = $instance->customtext1;
            $message = str_replace('{$a->coursename}', $a->coursename, $message);
            $message = str_replace('{$a->profileurl}', $a->profileurl, $message);
412
413
414
415
416
417
418
419
420
            if (strpos($message, '<') === false) {
                // Plain text only.
                $messagetext = $message;
                $messagehtml = text_to_html($messagetext, null, false, true);
            } else {
                // This is most probably the tag/newline soup known as FORMAT_MOODLE.
                $messagehtml = format_text($message, FORMAT_MOODLE, array('context'=>$context, 'para'=>false, 'newlines'=>true, 'filter'=>true));
                $messagetext = html_to_text($messagehtml);
            }
421
        } else {
422
423
            $messagetext = get_string('welcometocoursetext', 'enrol_self', $a);
            $messagehtml = text_to_html($messagetext, null, false, true);
424
425
        }

426
        $subject = get_string('welcometocourse', 'enrol_self', format_string($course->fullname, true, array('context'=>$context)));
427

428
        $rusers = array();
429
430
        if (!empty($CFG->coursecontact)) {
            $croles = explode(',', $CFG->coursecontact);
431
432
            list($sort, $sortparams) = users_order_by_sql('u');
            $rusers = get_role_users($croles, $context, true, '', 'r.sortorder ASC, ' . $sort, null, '', '', '', '', $sortparams);
433
434
435
436
        }
        if ($rusers) {
            $contact = reset($rusers);
        } else {
437
            $contact = core_user::get_support_user();
438
439
        }

440
        // Directly emailing welcome message rather than using messaging.
441
        email_to_user($user, $contact, $subject, $messagetext, $messagehtml);
442
    }
443
444

    /**
445
     * Enrol self cron support.
446
447
448
     * @return void
     */
    public function cron() {
449
450
451
        $trace = new text_progress_trace();
        $this->sync($trace, null);
        $this->send_expiry_notifications($trace);
452
453
454
455
456
    }

    /**
     * Sync all meta course links.
     *
457
     * @param progress_trace $trace
458
459
460
     * @param int $courseid one course, empty mean all
     * @return int 0 means ok, 1 means error, 2 means plugin disabled
     */
461
    public function sync(progress_trace $trace, $courseid = null) {
462
463
464
        global $DB;

        if (!enrol_is_enabled('self')) {
465
            $trace->finished();
466
            return 2;
467
468
        }

469
        // Unfortunately this may take a long time, execution can be interrupted safely here.
470
        core_php_time_limit::raise();
471
472
        raise_memory_limit(MEMORY_HUGE);

473
        $trace->output('Verifying self-enrolments...');
474

475
476
477
478
479
480
        $params = array('now'=>time(), 'useractive'=>ENROL_USER_ACTIVE, 'courselevel'=>CONTEXT_COURSE);
        $coursesql = "";
        if ($courseid) {
            $coursesql = "AND e.courseid = :courseid";
            $params['courseid'] = $courseid;
        }
481

482
483
        // Note: the logic of self enrolment guarantees that user logged in at least once (=== u.lastaccess set)
        //       and that user accessed course at least once too (=== user_lastaccess record exists).
484

485
        // First deal with users that did not log in for a really long time - they do not have user_lastaccess records.
486
487
488
489
        $sql = "SELECT e.*, ue.userid
                  FROM {user_enrolments} ue
                  JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'self' AND e.customint2 > 0)
                  JOIN {user} u ON u.id = ue.userid
490
491
492
                 WHERE :now - u.lastaccess > e.customint2
                       $coursesql";
        $rs = $DB->get_recordset_sql($sql, $params);
493
494
495
        foreach ($rs as $instance) {
            $userid = $instance->userid;
            unset($instance->userid);
496
            $this->unenrol_user($instance, $userid);
497
498
            $days = $instance->customint2 / 60*60*24;
            $trace->output("unenrolling user $userid from course $instance->courseid as they have did not log in for at least $days days", 1);
499
500
501
        }
        $rs->close();

502
        // Now unenrol from course user did not visit for a long time.
503
504
505
506
        $sql = "SELECT e.*, ue.userid
                  FROM {user_enrolments} ue
                  JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'self' AND e.customint2 > 0)
                  JOIN {user_lastaccess} ul ON (ul.userid = ue.userid AND ul.courseid = e.courseid)
507
508
509
                 WHERE :now - ul.timeaccess > e.customint2
                       $coursesql";
        $rs = $DB->get_recordset_sql($sql, $params);
510
511
512
        foreach ($rs as $instance) {
            $userid = $instance->userid;
            unset($instance->userid);
513
514
            $this->unenrol_user($instance, $userid);
                $days = $instance->customint2 / 60*60*24;
515
            $trace->output("unenrolling user $userid from course $instance->courseid as they have did not access course for at least $days days", 1);
516
517
518
        }
        $rs->close();

519
520
        $trace->output('...user self-enrolment updates finished.');
        $trace->finished();
521

522
523
        $this->process_expirations($trace, $courseid);

524
        return 0;
525
    }
526

527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
    /**
     * Returns the user who is responsible for self enrolments in given instance.
     *
     * Usually it is the first editing teacher - the person with "highest authority"
     * as defined by sort_by_roleassignment_authority() having 'enrol/self:manage'
     * capability.
     *
     * @param int $instanceid enrolment instance id
     * @return stdClass user record
     */
    protected function get_enroller($instanceid) {
        global $DB;

        if ($this->lasternollerinstanceid == $instanceid and $this->lasternoller) {
            return $this->lasternoller;
        }

        $instance = $DB->get_record('enrol', array('id'=>$instanceid, 'enrol'=>$this->get_name()), '*', MUST_EXIST);
        $context = context_course::instance($instance->courseid);

        if ($users = get_enrolled_users($context, 'enrol/self:manage')) {
            $users = sort_by_roleassignment_authority($users, $context);
            $this->lasternoller = reset($users);
            unset($users);
        } else {
            $this->lasternoller = parent::get_enroller($instanceid);
        }

        $this->lasternollerinstanceid = $instanceid;

        return $this->lasternoller;
    }

    /**
561
     * Gets an array of the user enrolment actions.
562
563
564
565
566
567
568
569
570
571
572
573
     *
     * @param course_enrolment_manager $manager
     * @param stdClass $ue A user enrolment object
     * @return array An array of user_enrolment_actions
     */
    public function get_user_enrolment_actions(course_enrolment_manager $manager, $ue) {
        $actions = array();
        $context = $manager->get_context();
        $instance = $ue->enrolmentinstance;
        $params = $manager->get_moodlepage()->url->params();
        $params['ue'] = $ue->id;
        if ($this->allow_unenrol($instance) && has_capability("enrol/self:unenrol", $context)) {
574
            $url = new moodle_url('/enrol/unenroluser.php', $params);
575
576
577
            $actions[] = new user_enrolment_action(new pix_icon('t/delete', ''), get_string('unenrol', 'enrol'), $url, array('class'=>'unenrollink', 'rel'=>$ue->id));
        }
        if ($this->allow_manage($instance) && has_capability("enrol/self:manage", $context)) {
578
            $url = new moodle_url('/enrol/editenrolment.php', $params);
579
580
581
582
            $actions[] = new user_enrolment_action(new pix_icon('t/edit', ''), get_string('edit'), $url, array('class'=>'editenrollink', 'rel'=>$ue->id));
        }
        return $actions;
    }
583

584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
    /**
     * Restore instance and map settings.
     *
     * @param restore_enrolments_structure_step $step
     * @param stdClass $data
     * @param stdClass $course
     * @param int $oldid
     */
    public function restore_instance(restore_enrolments_structure_step $step, stdClass $data, $course, $oldid) {
        global $DB;
        if ($step->get_task()->get_target() == backup::TARGET_NEW_COURSE) {
            $merge = false;
        } else {
            $merge = array(
                'courseid'   => $data->courseid,
                'enrol'      => $this->get_name(),
                'roleid'     => $data->roleid,
            );
        }
        if ($merge and $instances = $DB->get_records('enrol', $merge, 'id')) {
            $instance = reset($instances);
            $instanceid = $instance->id;
        } else {
            if (!empty($data->customint5)) {
                if ($step->get_task()->is_samesite()) {
                    // Keep cohort restriction unchanged - we are on the same site.
                } else {
                    // Use some id that can not exist in order to prevent self enrolment,
                    // because we do not know what cohort it is in this site.
                    $data->customint5 = -1;
                }
            }
            $instanceid = $this->add_instance($course, (array)$data);
        }
        $step->set_mapping('enrol', $oldid, $instanceid);
    }
620

621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
    /**
     * Restore user enrolment.
     *
     * @param restore_enrolments_structure_step $step
     * @param stdClass $data
     * @param stdClass $instance
     * @param int $oldinstancestatus
     * @param int $userid
     */
    public function restore_user_enrolment(restore_enrolments_structure_step $step, $data, $instance, $userid, $oldinstancestatus) {
        $this->enrol_user($instance, $userid, null, $data->timestart, $data->timeend, $data->status);
    }

    /**
     * Restore role assignment.
     *
     * @param stdClass $instance
     * @param int $roleid
     * @param int $userid
     * @param int $contextid
     */
    public function restore_role_assignment($instance, $roleid, $userid, $contextid) {
        // This is necessary only because we may migrate other types to this instance,
        // we do not use component in manual or self enrol.
        role_assign($roleid, $userid, $contextid, '', 0);
646
647
    }
}