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
 * LDAP enrolment plugin implementation.
19
 *
20
 * This plugin synchronises enrolment and roles with a LDAP server.
21
 *
22
 * @package    enrol_ldap
23
 * @author     Iñaki Arenaza - based on code by Martin Dougiamas, Martin Langhoff and others
24
 * @copyright  1999 onwards Martin Dougiamas {@link http://moodle.com}
25
 * @copyright  2010 Iñaki Arenaza <iarenaza@eps.mondragon.edu>
26
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27
 */
28
 
29
defined('MOODLE_INTERNAL') || die();
30
 
31
class enrol_ldap_plugin extends enrol_plugin {
32
    protected $enrol_localcoursefield = 'idnumber';
33
    protected $enroltype = 'enrol_ldap';
34
    protected $errorlogtag = '[ENROL LDAP] ';
35
 
36
    /**
37
     * The object class to use when finding users.
38
     *
39
     * @var string $userobjectclass
40
     */
41
    protected $userobjectclass;
42
 
43
    /** @var LDAP\Connection LDAP connection. */
44
    protected $ldapconnection;
45
 
46
    /**
47
     * Constructor for the plugin. In addition to calling the parent
48
     * constructor, we define and 'fix' some settings depending on the
49
     * real settings the admin defined.
50
     */
51
    public function __construct() {
52
        global $CFG;
53
        require_once($CFG->libdir.'/ldaplib.php');
54
 
55
        // Do our own stuff to fix the config (it's easier to do it
56
        // here than using the admin settings infrastructure). We
57
        // don't call $this->set_config() for any of the 'fixups'
58
        // (except the objectclass, as it's critical) because the user
59
        // didn't specify any values and relied on the default values
60
        // defined for the user type she chose.
61
        $this->load_config();
62
 
63
        // Make sure we get sane defaults for critical values.
64
        $this->config->ldapencoding = $this->get_config('ldapencoding', 'utf-8');
65
        $this->config->user_type = $this->get_config('user_type', 'default');
66
 
67
        $ldap_usertypes = ldap_supported_usertypes();
68
        $this->config->user_type_name = $ldap_usertypes[$this->config->user_type];
69
        unset($ldap_usertypes);
70
 
71
        $default = ldap_getdefaults();
72
 
73
        // The objectclass in the defaults is for a user.
74
        // This will be required later, but enrol_ldap uses 'objectclass' for its group objectclass.
75
        // Save the normalised user objectclass for later.
76
        $this->userobjectclass = ldap_normalise_objectclass($default['objectclass'][$this->get_config('user_type')]);
77
 
78
        // Remove the objectclass default, as the values specified there are for users, and we are dealing with groups here.
79
        unset($default['objectclass']);
80
 
81
        // Use defaults if values not given. Dont use this->get_config()
82
        // here to be able to check for 0 and false values too.
83
        foreach ($default as $key => $value) {
84
            // Watch out - 0, false are correct values too, so we can't use $this->get_config()
85
            if (!isset($this->config->{$key}) or $this->config->{$key} == '') {
86
                $this->config->{$key} = $value[$this->config->user_type];
87
            }
88
        }
89
 
90
        // Normalise the objectclass used for groups.
91
        if (empty($this->config->objectclass)) {
92
            // No objectclass set yet - set a default class.
93
            $this->config->objectclass = ldap_normalise_objectclass(null, '*');
94
            $this->set_config('objectclass', $this->config->objectclass);
95
        } else {
96
            $objectclass = ldap_normalise_objectclass($this->config->objectclass);
97
            if ($objectclass !== $this->config->objectclass) {
98
                // The objectclass was changed during normalisation.
99
                // Save it in config, and update the local copy of config.
100
                $this->set_config('objectclass', $objectclass);
101
                $this->config->objectclass = $objectclass;
102
            }
103
        }
104
    }
105
 
106
    /**
107
     * Is it possible to delete enrol instance via standard UI?
108
     *
109
     * @param object $instance
110
     * @return bool
111
     */
112
    public function can_delete_instance($instance) {
113
        $context = context_course::instance($instance->courseid);
114
        if (!has_capability('enrol/ldap:manage', $context)) {
115
            return false;
116
        }
117
 
118
        if (!enrol_is_enabled('ldap')) {
119
            return true;
120
        }
121
 
122
        if (!$this->get_config('ldap_host') or !$this->get_config('objectclass') or !$this->get_config('course_idnumber')) {
123
            return true;
124
        }
125
 
126
        // TODO: connect to external system and make sure no users are to be enrolled in this course
127
        return false;
128
    }
129
 
130
    /**
131
     * Is it possible to hide/show enrol instance via standard UI?
132
     *
133
     * @param stdClass $instance
134
     * @return bool
135
     */
136
    public function can_hide_show_instance($instance) {
137
        $context = context_course::instance($instance->courseid);
138
        return has_capability('enrol/ldap:manage', $context);
139
    }
140
 
141
    /**
142
     * Forces synchronisation of user enrolments with LDAP server.
143
     * It creates courses if the plugin is configured to do so.
144
     *
145
     * @param object $user user record
146
     * @return void
147
     */
148
    public function sync_user_enrolments($user) {
149
        global $DB;
150
 
151
        // Do not try to print anything to the output because this method is called during interactive login.
152
        if (PHPUNIT_TEST) {
153
            $trace = new null_progress_trace();
154
        } else {
155
            $trace = new error_log_progress_trace($this->errorlogtag);
156
        }
157
 
158
        if (!$this->ldap_connect($trace)) {
159
            $trace->finished();
160
            return;
161
        }
162
 
163
        if (!is_object($user) or !property_exists($user, 'id')) {
164
            throw new coding_exception('Invalid $user parameter in sync_user_enrolments()');
165
        }
166
 
167
        if (!property_exists($user, 'idnumber')) {
168
            debugging('Invalid $user parameter in sync_user_enrolments(), missing idnumber');
169
            $user = $DB->get_record('user', array('id'=>$user->id));
170
        }
171
 
172
        // We may need a lot of memory here
173
        core_php_time_limit::raise();
174
        raise_memory_limit(MEMORY_HUGE);
175
 
176
        // Get enrolments for each type of role.
177
        $roles = get_all_roles();
178
        $enrolments = array();
179
        foreach($roles as $role) {
180
            // Get external enrolments according to LDAP server
181
            $enrolments[$role->id]['ext'] = $this->find_ext_enrolments($user->idnumber, $role);
182
 
183
            // Get the list of current user enrolments that come from LDAP
184
            $sql= "SELECT e.courseid, ue.status, e.id as enrolid, c.shortname
185
                     FROM {user} u
186
                     JOIN {role_assignments} ra ON (ra.userid = u.id AND ra.component = 'enrol_ldap' AND ra.roleid = :roleid)
187
                     JOIN {user_enrolments} ue ON (ue.userid = u.id AND ue.enrolid = ra.itemid)
188
                     JOIN {enrol} e ON (e.id = ue.enrolid)
189
                     JOIN {course} c ON (c.id = e.courseid)
190
                    WHERE u.deleted = 0 AND u.id = :userid";
191
            $params = array ('roleid'=>$role->id, 'userid'=>$user->id);
192
            $enrolments[$role->id]['current'] = $DB->get_records_sql($sql, $params);
193
        }
194
 
195
        $ignorehidden = $this->get_config('ignorehiddencourses');
196
        $courseidnumber = $this->get_config('course_idnumber');
197
        foreach($roles as $role) {
198
            foreach ($enrolments[$role->id]['ext'] as $enrol) {
199
                $course_ext_id = $enrol[$courseidnumber][0];
200
                if (empty($course_ext_id)) {
201
                    $trace->output(get_string('extcourseidinvalid', 'enrol_ldap'));
202
                    continue; // Next; skip this one!
203
                }
204
 
205
                // Create the course if required
206
                $course = $DB->get_record('course', array($this->enrol_localcoursefield=>$course_ext_id));
207
                if (empty($course)) { // Course doesn't exist
208
                    if ($this->get_config('autocreate')) { // Autocreate
209
                        $trace->output(get_string('createcourseextid', 'enrol_ldap', array('courseextid'=>$course_ext_id)));
210
                        if (!$newcourseid = $this->create_course($enrol, $trace)) {
211
                            continue;
212
                        }
213
                        $course = $DB->get_record('course', array('id'=>$newcourseid));
214
                    } else {
215
                        $trace->output(get_string('createnotcourseextid', 'enrol_ldap', array('courseextid'=>$course_ext_id)));
216
                        continue; // Next; skip this one!
217
                    }
218
                }
219
 
220
                // Deal with enrolment in the moodle db
221
                // Add necessary enrol instance if not present yet;
222
                $sql = "SELECT c.id, c.visible, e.id as enrolid
223
                          FROM {course} c
224
                          JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'ldap')
225
                         WHERE c.id = :courseid";
226
                $params = array('courseid'=>$course->id);
227
                if (!($course_instance = $DB->get_record_sql($sql, $params, IGNORE_MULTIPLE))) {
228
                    $course_instance = new stdClass();
229
                    $course_instance->id = $course->id;
230
                    $course_instance->visible = $course->visible;
231
                    $course_instance->enrolid = $this->add_instance($course_instance);
232
                }
233
 
234
                if (!$instance = $DB->get_record('enrol', array('id'=>$course_instance->enrolid))) {
235
                    continue; // Weird; skip this one.
236
                }
237
 
238
                if ($ignorehidden && !$course_instance->visible) {
239
                    continue;
240
                }
241
 
242
                if (empty($enrolments[$role->id]['current'][$course->id])) {
243
                    // Enrol the user in the given course, with that role.
244
                    $this->enrol_user($instance, $user->id, $role->id);
245
                    // Make sure we set the enrolment status to active. If the user wasn't
246
                    // previously enrolled to the course, enrol_user() sets it. But if we
247
                    // configured the plugin to suspend the user enrolments _AND_ remove
248
                    // the role assignments on external unenrol, then enrol_user() doesn't
249
                    // set it back to active on external re-enrolment. So set it
250
                    // unconditionnally to cover both cases.
251
                    $DB->set_field('user_enrolments', 'status', ENROL_USER_ACTIVE, array('enrolid'=>$instance->id, 'userid'=>$user->id));
252
                    $trace->output(get_string('enroluser', 'enrol_ldap',
253
                        array('user_username'=> $user->username,
254
                              'course_shortname'=>$course->shortname,
255
                              'course_id'=>$course->id)));
256
                } else {
257
                    if ($enrolments[$role->id]['current'][$course->id]->status == ENROL_USER_SUSPENDED) {
258
                        // Reenable enrolment that was previously disabled. Enrolment refreshed
259
                        $DB->set_field('user_enrolments', 'status', ENROL_USER_ACTIVE, array('enrolid'=>$instance->id, 'userid'=>$user->id));
260
                        $trace->output(get_string('enroluserenable', 'enrol_ldap',
261
                            array('user_username'=> $user->username,
262
                                  'course_shortname'=>$course->shortname,
263
                                  'course_id'=>$course->id)));
264
                    }
265
                }
266
 
267
                // Remove this course from the current courses, to be able to detect
268
                // which current courses should be unenroled from when we finish processing
269
                // external enrolments.
270
                unset($enrolments[$role->id]['current'][$course->id]);
271
            }
272
 
273
            // Deal with unenrolments.
274
            $transaction = $DB->start_delegated_transaction();
275
            foreach ($enrolments[$role->id]['current'] as $course) {
276
                $context = context_course::instance($course->courseid);
277
                $instance = $DB->get_record('enrol', array('id'=>$course->enrolid));
278
                switch ($this->get_config('unenrolaction')) {
279
                    case ENROL_EXT_REMOVED_UNENROL:
280
                        $this->unenrol_user($instance, $user->id);
281
                        $trace->output(get_string('extremovedunenrol', 'enrol_ldap',
282
                            array('user_username'=> $user->username,
283
                                  'course_shortname'=>$course->shortname,
284
                                  'course_id'=>$course->courseid)));
285
                        break;
286
                    case ENROL_EXT_REMOVED_KEEP:
287
                        // Keep - only adding enrolments
288
                        break;
289
                    case ENROL_EXT_REMOVED_SUSPEND:
290
                        if ($course->status != ENROL_USER_SUSPENDED) {
291
                            $DB->set_field('user_enrolments', 'status', ENROL_USER_SUSPENDED, array('enrolid'=>$instance->id, 'userid'=>$user->id));
292
                            $trace->output(get_string('extremovedsuspend', 'enrol_ldap',
293
                                array('user_username'=> $user->username,
294
                                      'course_shortname'=>$course->shortname,
295
                                      'course_id'=>$course->courseid)));
296
                        }
297
                        break;
298
                    case ENROL_EXT_REMOVED_SUSPENDNOROLES:
299
                        if ($course->status != ENROL_USER_SUSPENDED) {
300
                            $DB->set_field('user_enrolments', 'status', ENROL_USER_SUSPENDED, array('enrolid'=>$instance->id, 'userid'=>$user->id));
301
                        }
302
                        role_unassign_all(array('contextid'=>$context->id, 'userid'=>$user->id, 'component'=>'enrol_ldap', 'itemid'=>$instance->id));
303
                        $trace->output(get_string('extremovedsuspendnoroles', 'enrol_ldap',
304
                            array('user_username'=> $user->username,
305
                                  'course_shortname'=>$course->shortname,
306
                                  'course_id'=>$course->courseid)));
307
                        break;
308
                }
309
            }
310
            $transaction->allow_commit();
311
        }
312
 
313
        $this->ldap_close();
314
 
315
        $trace->finished();
316
    }
317
 
318
    /**
319
     * Forces synchronisation of all enrolments with LDAP server.
320
     * It creates courses if the plugin is configured to do so.
321
     *
322
     * @param progress_trace $trace
323
     * @param int|null $onecourse limit sync to one course->id, null if all courses
324
     * @return void
325
     */
326
    public function sync_enrolments(progress_trace $trace, $onecourse = null) {
327
        global $CFG, $DB;
328
 
329
        if (!$this->ldap_connect($trace)) {
330
            $trace->finished();
331
            return;
332
        }
333
 
334
        $ldap_pagedresults = ldap_paged_results_supported($this->get_config('ldap_version'), $this->ldapconnection);
335
 
336
        // we may need a lot of memory here
337
        core_php_time_limit::raise();
338
        raise_memory_limit(MEMORY_HUGE);
339
 
340
        $oneidnumber = null;
341
        if ($onecourse) {
342
            if (!$course = $DB->get_record('course', array('id'=>$onecourse), 'id,'.$this->enrol_localcoursefield)) {
343
                // Course does not exist, nothing to do.
344
                $trace->output("Requested course $onecourse does not exist, no sync performed.");
345
                $trace->finished();
346
                return;
347
            }
348
            if (empty($course->{$this->enrol_localcoursefield})) {
349
                $trace->output("Requested course $onecourse does not have {$this->enrol_localcoursefield}, no sync performed.");
350
                $trace->finished();
351
                return;
352
            }
353
            $oneidnumber = ldap_filter_addslashes(core_text::convert($course->idnumber, 'utf-8', $this->get_config('ldapencoding')));
354
        }
355
 
356
        // Get enrolments for each type of role.
357
        $roles = get_all_roles();
358
        $enrolments = array();
359
        foreach($roles as $role) {
360
            // Get all contexts
361
            $ldap_contexts = explode(';', $this->config->{'contexts_role'.$role->id});
362
 
363
            // Get all the fields we will want for the potential course creation
364
            // as they are light. Don't get membership -- potentially a lot of data.
365
            $ldap_fields_wanted = array('dn', $this->config->course_idnumber);
366
            if (!empty($this->config->course_fullname)) {
367
                array_push($ldap_fields_wanted, $this->config->course_fullname);
368
            }
369
            if (!empty($this->config->course_shortname)) {
370
                array_push($ldap_fields_wanted, $this->config->course_shortname);
371
            }
372
            if (!empty($this->config->course_summary)) {
373
                array_push($ldap_fields_wanted, $this->config->course_summary);
374
            }
375
            array_push($ldap_fields_wanted, $this->config->{'memberattribute_role'.$role->id});
376
 
377
            // Define the search pattern
378
            $ldap_search_pattern = $this->config->objectclass;
379
 
380
            if ($oneidnumber !== null) {
381
                $ldap_search_pattern = "(&$ldap_search_pattern({$this->config->course_idnumber}=$oneidnumber))";
382
            }
383
 
384
            $ldap_cookie = '';
385
            $servercontrols = array();
386
            foreach ($ldap_contexts as $ldap_context) {
387
                $ldap_context = trim($ldap_context);
388
                if (empty($ldap_context)) {
389
                    continue; // Next;
390
                }
391
 
392
                $flat_records = array();
393
                do {
394
                    if ($ldap_pagedresults) {
395
                        $servercontrols = array(array(
396
                            'oid' => LDAP_CONTROL_PAGEDRESULTS, 'value' => array(
397
                                'size' => $this->config->pagesize, 'cookie' => $ldap_cookie)));
398
                    }
399
 
400
                    if ($this->config->course_search_sub) {
401
                        // Use ldap_search to find first user from subtree
402
                        $ldap_result = @ldap_search($this->ldapconnection, $ldap_context,
403
                            $ldap_search_pattern, $ldap_fields_wanted,
404
                            0, -1, -1, LDAP_DEREF_NEVER, $servercontrols);
405
                    } else {
406
                        // Search only in this context
407
                        $ldap_result = @ldap_list($this->ldapconnection, $ldap_context,
408
                            $ldap_search_pattern, $ldap_fields_wanted,
409
                            0, -1, -1, LDAP_DEREF_NEVER, $servercontrols);
410
                    }
411
                    if (!$ldap_result) {
412
                        continue; // Next
413
                    }
414
 
415
                    if ($ldap_pagedresults) {
416
                        // Get next server cookie to know if we'll need to continue searching.
417
                        $ldap_cookie = '';
418
                        // Get next cookie from controls.
419
                        ldap_parse_result($this->ldapconnection, $ldap_result, $errcode, $matcheddn,
420
                            $errmsg, $referrals, $controls);
421
                        if (isset($controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'])) {
422
                            $ldap_cookie = $controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'];
423
                        }
424
                    }
425
 
426
                    // Check and push results
427
                    $records = ldap_get_entries($this->ldapconnection, $ldap_result);
428
 
429
                    // LDAP libraries return an odd array, really. fix it:
430
                    for ($c = 0; $c < $records['count']; $c++) {
431
                        array_push($flat_records, $records[$c]);
432
                    }
433
                    // Free some mem
434
                    unset($records);
435
                } while ($ldap_pagedresults && !empty($ldap_cookie));
436
 
437
                // If LDAP paged results were used, the current connection must be completely
438
                // closed and a new one created, to work without paged results from here on.
439
                if ($ldap_pagedresults) {
440
                    $this->ldap_close();
441
                    $this->ldap_connect($trace);
442
                }
443
 
444
                if (count($flat_records)) {
445
                    $ignorehidden = $this->get_config('ignorehiddencourses');
446
                    foreach($flat_records as $course) {
447
                        $course = array_change_key_case($course, CASE_LOWER);
448
                        $idnumber = $course[$this->config->course_idnumber][0];
449
                        $trace->output(get_string('synccourserole', 'enrol_ldap', array('idnumber'=>$idnumber, 'role_shortname'=>$role->shortname)));
450
 
451
                        // Does the course exist in moodle already?
452
                        $course_obj = $DB->get_record('course', array($this->enrol_localcoursefield=>$idnumber));
453
                        if (empty($course_obj)) { // Course doesn't exist
454
                            if ($this->get_config('autocreate')) { // Autocreate
455
                                $trace->output(get_string('createcourseextid', 'enrol_ldap', array('courseextid'=>$idnumber)));
456
                                if (!$newcourseid = $this->create_course($course, $trace)) {
457
                                    continue;
458
                                }
459
                                $course_obj = $DB->get_record('course', array('id'=>$newcourseid));
460
                            } else {
461
                                $trace->output(get_string('createnotcourseextid', 'enrol_ldap', array('courseextid'=>$idnumber)));
462
                                continue; // Next; skip this one!
463
                            }
464
                        } else {  // Check if course needs update & update as needed.
465
                            $this->update_course($course_obj, $course, $trace);
466
                        }
467
 
468
                        // Enrol & unenrol
469
 
470
                        // Pull the ldap membership into a nice array
471
                        // this is an odd array -- mix of hash and array --
472
                        $ldapmembers = array();
473
 
474
                        if (property_exists($this->config, 'memberattribute_role'.$role->id)
475
                            && !empty($this->config->{'memberattribute_role'.$role->id})
476
                            && !empty($course[$this->config->{'memberattribute_role'.$role->id}])) { // May have no membership!
477
 
478
                            $ldapmembers = $course[$this->config->{'memberattribute_role'.$role->id}];
479
                            unset($ldapmembers['count']); // Remove oddity ;)
480
 
481
                            // If we have enabled nested groups, we need to expand
482
                            // the groups to get the real user list. We need to do
483
                            // this before dealing with 'memberattribute_isdn'.
484
                            if ($this->config->nested_groups) {
485
                                $users = array();
486
                                foreach ($ldapmembers as $ldapmember) {
487
                                    $grpusers = $this->ldap_explode_group($ldapmember,
488
                                                                          $this->config->{'memberattribute_role'.$role->id});
489
 
490
                                    $users = array_merge($users, $grpusers);
491
                                }
492
                                $ldapmembers = array_unique($users); // There might be duplicates.
493
                            }
494
 
495
                            // Deal with the case where the member attribute holds distinguished names,
496
                            // but only if the user attribute is not a distinguished name itself.
497
                            if ($this->config->memberattribute_isdn
498
                                && ($this->config->idnumber_attribute !== 'dn')
499
                                && ($this->config->idnumber_attribute !== 'distinguishedname')) {
500
                                // We need to retrieve the idnumber for all the users in $ldapmembers,
501
                                // as the idnumber does not match their dn and we get dn's from membership.
502
                                $memberidnumbers = array();
503
                                foreach ($ldapmembers as $ldapmember) {
504
                                    $result = ldap_read($this->ldapconnection, $ldapmember, $this->userobjectclass,
505
                                                        array($this->config->idnumber_attribute));
506
                                    $entry = ldap_first_entry($this->ldapconnection, $result);
507
                                    $values = ldap_get_values($this->ldapconnection, $entry, $this->config->idnumber_attribute);
508
                                    array_push($memberidnumbers, $values[0]);
509
                                }
510
 
511
                                $ldapmembers = $memberidnumbers;
512
                            }
513
                        }
514
 
515
                        // Prune old ldap enrolments
516
                        // hopefully they'll fit in the max buffer size for the RDBMS
517
                        $sql= "SELECT u.id as userid, u.username, ue.status,
518
                                      ra.contextid, ra.itemid as instanceid
519
                                 FROM {user} u
520
                                 JOIN {role_assignments} ra ON (ra.userid = u.id AND ra.component = 'enrol_ldap' AND ra.roleid = :roleid)
521
                                 JOIN {user_enrolments} ue ON (ue.userid = u.id AND ue.enrolid = ra.itemid)
522
                                 JOIN {enrol} e ON (e.id = ue.enrolid)
523
                                WHERE u.deleted = 0 AND e.courseid = :courseid ";
524
                        $params = array('roleid'=>$role->id, 'courseid'=>$course_obj->id);
525
                        $context = context_course::instance($course_obj->id);
526
                        if (!empty($ldapmembers)) {
527
                            list($ldapml, $params2) = $DB->get_in_or_equal($ldapmembers, SQL_PARAMS_NAMED, 'm', false);
528
                            $sql .= "AND u.idnumber $ldapml";
529
                            $params = array_merge($params, $params2);
530
                            unset($params2);
531
                        } else {
532
                            $shortname = format_string($course_obj->shortname, true, array('context' => $context));
533
                            $trace->output(get_string('emptyenrolment', 'enrol_ldap',
534
                                         array('role_shortname'=> $role->shortname,
535
                                               'course_shortname' => $shortname)));
536
                        }
537
                        $todelete = $DB->get_records_sql($sql, $params);
538
 
539
                        if (!empty($todelete)) {
540
                            $transaction = $DB->start_delegated_transaction();
541
                            foreach ($todelete as $row) {
542
                                $instance = $DB->get_record('enrol', array('id'=>$row->instanceid));
543
                                switch ($this->get_config('unenrolaction')) {
544
                                case ENROL_EXT_REMOVED_UNENROL:
545
                                    $this->unenrol_user($instance, $row->userid);
546
                                    $trace->output(get_string('extremovedunenrol', 'enrol_ldap',
547
                                        array('user_username'=> $row->username,
548
                                              'course_shortname'=>$course_obj->shortname,
549
                                              'course_id'=>$course_obj->id)));
550
                                    break;
551
                                case ENROL_EXT_REMOVED_KEEP:
552
                                    // Keep - only adding enrolments
553
                                    break;
554
                                case ENROL_EXT_REMOVED_SUSPEND:
555
                                    if ($row->status != ENROL_USER_SUSPENDED) {
556
                                        $DB->set_field('user_enrolments', 'status', ENROL_USER_SUSPENDED, array('enrolid'=>$instance->id, 'userid'=>$row->userid));
557
                                        $trace->output(get_string('extremovedsuspend', 'enrol_ldap',
558
                                            array('user_username'=> $row->username,
559
                                                  'course_shortname'=>$course_obj->shortname,
560
                                                  'course_id'=>$course_obj->id)));
561
                                    }
562
                                    break;
563
                                case ENROL_EXT_REMOVED_SUSPENDNOROLES:
564
                                    if ($row->status != ENROL_USER_SUSPENDED) {
565
                                        $DB->set_field('user_enrolments', 'status', ENROL_USER_SUSPENDED, array('enrolid'=>$instance->id, 'userid'=>$row->userid));
566
                                    }
567
                                    role_unassign_all(array('contextid'=>$row->contextid, 'userid'=>$row->userid, 'component'=>'enrol_ldap', 'itemid'=>$instance->id));
568
                                    $trace->output(get_string('extremovedsuspendnoroles', 'enrol_ldap',
569
                                        array('user_username'=> $row->username,
570
                                              'course_shortname'=>$course_obj->shortname,
571
                                              'course_id'=>$course_obj->id)));
572
                                    break;
573
                                }
574
                            }
575
                            $transaction->allow_commit();
576
                        }
577
 
578
                        // Insert current enrolments
579
                        // bad we can't do INSERT IGNORE with postgres...
580
 
581
                        // Add necessary enrol instance if not present yet;
582
                        $sql = "SELECT c.id, c.visible, e.id as enrolid
583
                                  FROM {course} c
584
                                  JOIN {enrol} e ON (e.courseid = c.id AND e.enrol = 'ldap')
585
                                 WHERE c.id = :courseid";
586
                        $params = array('courseid'=>$course_obj->id);
587
                        if (!($course_instance = $DB->get_record_sql($sql, $params, IGNORE_MULTIPLE))) {
588
                            $course_instance = new stdClass();
589
                            $course_instance->id = $course_obj->id;
590
                            $course_instance->visible = $course_obj->visible;
591
                            $course_instance->enrolid = $this->add_instance($course_instance);
592
                        }
593
 
594
                        if (!$instance = $DB->get_record('enrol', array('id'=>$course_instance->enrolid))) {
595
                            continue; // Weird; skip this one.
596
                        }
597
 
598
                        if ($ignorehidden && !$course_instance->visible) {
599
                            continue;
600
                        }
601
 
602
                        $transaction = $DB->start_delegated_transaction();
603
                        foreach ($ldapmembers as $ldapmember) {
604
                            $sql = 'SELECT id,username,1 FROM {user} WHERE idnumber = ? AND deleted = 0';
605
                            $member = $DB->get_record_sql($sql, array($ldapmember));
606
                            if(empty($member) || empty($member->id)){
607
                                $trace->output(get_string('couldnotfinduser', 'enrol_ldap', $ldapmember));
608
                                continue;
609
                            }
610
 
611
                            $sql= "SELECT ue.status
612
                                     FROM {user_enrolments} ue
613
                                     JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'ldap')
614
                                    WHERE e.courseid = :courseid AND ue.userid = :userid";
615
                            $params = array('courseid'=>$course_obj->id, 'userid'=>$member->id);
616
                            $userenrolment = $DB->get_record_sql($sql, $params, IGNORE_MULTIPLE);
617
 
618
                            if (empty($userenrolment)) {
619
                                $this->enrol_user($instance, $member->id, $role->id);
620
                                // Make sure we set the enrolment status to active. If the user wasn't
621
                                // previously enrolled to the course, enrol_user() sets it. But if we
622
                                // configured the plugin to suspend the user enrolments _AND_ remove
623
                                // the role assignments on external unenrol, then enrol_user() doesn't
624
                                // set it back to active on external re-enrolment. So set it
625
                                // unconditionally to cover both cases.
626
                                $DB->set_field('user_enrolments', 'status', ENROL_USER_ACTIVE, array('enrolid'=>$instance->id, 'userid'=>$member->id));
627
                                $trace->output(get_string('enroluser', 'enrol_ldap',
628
                                    array('user_username'=> $member->username,
629
                                          'course_shortname'=>$course_obj->shortname,
630
                                          'course_id'=>$course_obj->id)));
631
 
632
                            } else {
633
                                if (!$DB->record_exists('role_assignments', array('roleid'=>$role->id, 'userid'=>$member->id, 'contextid'=>$context->id, 'component'=>'enrol_ldap', 'itemid'=>$instance->id))) {
634
                                    // This happens when reviving users or when user has multiple roles in one course.
635
                                    $context = context_course::instance($course_obj->id);
636
                                    role_assign($role->id, $member->id, $context->id, 'enrol_ldap', $instance->id);
637
                                    $trace->output("Assign role to user '$member->username' in course '$course_obj->shortname ($course_obj->id)'");
638
                                }
639
                                if ($userenrolment->status == ENROL_USER_SUSPENDED) {
640
                                    // Reenable enrolment that was previously disabled. Enrolment refreshed
641
                                    $DB->set_field('user_enrolments', 'status', ENROL_USER_ACTIVE, array('enrolid'=>$instance->id, 'userid'=>$member->id));
642
                                    $trace->output(get_string('enroluserenable', 'enrol_ldap',
643
                                        array('user_username'=> $member->username,
644
                                              'course_shortname'=>$course_obj->shortname,
645
                                              'course_id'=>$course_obj->id)));
646
                                }
647
                            }
648
                        }
649
                        $transaction->allow_commit();
650
                    }
651
                }
652
            }
653
        }
654
        @$this->ldap_close();
655
        $trace->finished();
656
    }
657
 
658
    /**
659
     * Connect to the LDAP server, using the plugin configured
660
     * settings. It's actually a wrapper around ldap_connect_moodle()
661
     *
662
     * @param progress_trace $trace
663
     * @return bool success
664
     */
665
    protected function ldap_connect(progress_trace $trace = null) {
666
        global $CFG;
667
        require_once($CFG->libdir.'/ldaplib.php');
668
 
669
        if (isset($this->ldapconnection)) {
670
            return true;
671
        }
672
 
673
        if ($ldapconnection = ldap_connect_moodle($this->get_config('host_url'), $this->get_config('ldap_version'),
674
                                                  $this->get_config('user_type'), $this->get_config('bind_dn'),
675
                                                  $this->get_config('bind_pw'), $this->get_config('opt_deref'),
676
                                                  $debuginfo, $this->get_config('start_tls'))) {
677
            $this->ldapconnection = $ldapconnection;
678
            return true;
679
        }
680
 
681
        if ($trace) {
682
            $trace->output($debuginfo);
683
        } else {
684
            error_log($this->errorlogtag.$debuginfo);
685
        }
686
 
687
        return false;
688
    }
689
 
690
    /**
691
     * Disconnects from a LDAP server
692
     *
693
     */
694
    protected function ldap_close() {
695
        if (isset($this->ldapconnection)) {
696
            @ldap_close($this->ldapconnection);
697
            $this->ldapconnection = null;
698
        }
699
        return;
700
    }
701
 
702
    /**
703
     * Return multidimensional array with details of user courses (at
704
     * least dn and idnumber).
705
     *
706
     * @param string $memberuid user idnumber (without magic quotes).
707
     * @param object role is a record from the mdl_role table.
708
     * @return array
709
     */
710
    protected function find_ext_enrolments($memberuid, $role) {
711
        global $CFG;
712
        require_once($CFG->libdir.'/ldaplib.php');
713
 
714
        if (empty($memberuid)) {
715
            // No "idnumber" stored for this user, so no LDAP enrolments
716
            return array();
717
        }
718
 
719
        $ldap_contexts = trim($this->get_config('contexts_role'.$role->id));
720
        if (empty($ldap_contexts)) {
721
            // No role contexts, so no LDAP enrolments
722
            return array();
723
        }
724
 
725
        $extmemberuid = core_text::convert($memberuid, 'utf-8', $this->get_config('ldapencoding'));
726
 
727
        if($this->get_config('memberattribute_isdn')) {
728
            if (!($extmemberuid = $this->ldap_find_userdn($extmemberuid))) {
729
                return array();
730
            }
731
        }
732
 
733
        $ldap_search_pattern = '';
734
        if($this->get_config('nested_groups')) {
735
            $usergroups = $this->ldap_find_user_groups($extmemberuid);
736
            if(count($usergroups) > 0) {
737
                foreach ($usergroups as $group) {
738
                    $group = ldap_filter_addslashes($group);
739
                    $ldap_search_pattern .= '('.$this->get_config('memberattribute_role'.$role->id).'='.$group.')';
740
                }
741
            }
742
        }
743
 
744
        // Default return value
745
        $courses = array();
746
 
747
        // Get all the fields we will want for the potential course creation
748
        // as they are light. don't get membership -- potentially a lot of data.
749
        $ldap_fields_wanted = array('dn', $this->get_config('course_idnumber'));
750
        $fullname  = $this->get_config('course_fullname');
751
        $shortname = $this->get_config('course_shortname');
752
        $summary   = $this->get_config('course_summary');
753
        if (isset($fullname)) {
754
            array_push($ldap_fields_wanted, $fullname);
755
        }
756
        if (isset($shortname)) {
757
            array_push($ldap_fields_wanted, $shortname);
758
        }
759
        if (isset($summary)) {
760
            array_push($ldap_fields_wanted, $summary);
761
        }
762
 
763
        // Define the search pattern
764
        if (empty($ldap_search_pattern)) {
765
            $ldap_search_pattern = '('.$this->get_config('memberattribute_role'.$role->id).'='.ldap_filter_addslashes($extmemberuid).')';
766
        } else {
767
            $ldap_search_pattern = '(|' . $ldap_search_pattern .
768
                                       '('.$this->get_config('memberattribute_role'.$role->id).'='.ldap_filter_addslashes($extmemberuid).')' .
769
                                   ')';
770
        }
771
        $ldap_search_pattern='(&'.$this->get_config('objectclass').$ldap_search_pattern.')';
772
 
773
        // Get all contexts and look for first matching user
774
        $ldap_contexts = explode(';', $ldap_contexts);
775
        $ldap_pagedresults = ldap_paged_results_supported($this->get_config('ldap_version'), $this->ldapconnection);
776
        foreach ($ldap_contexts as $context) {
777
            $context = trim($context);
778
            if (empty($context)) {
779
                continue;
780
            }
781
 
782
            $ldap_cookie = '';
783
            $servercontrols = array();
784
            $flat_records = array();
785
            do {
786
                if ($ldap_pagedresults) {
787
                    $servercontrols = array(array(
788
                        'oid' => LDAP_CONTROL_PAGEDRESULTS, 'value' => array(
789
                            'size' => $this->config->pagesize, 'cookie' => $ldap_cookie)));
790
                }
791
 
792
                if ($this->get_config('course_search_sub')) {
793
                    // Use ldap_search to find first user from subtree
794
                    $ldap_result = @ldap_search($this->ldapconnection, $context,
795
                        $ldap_search_pattern, $ldap_fields_wanted,
796
                        0, -1, -1, LDAP_DEREF_NEVER, $servercontrols);
797
                } else {
798
                    // Search only in this context
799
                    $ldap_result = @ldap_list($this->ldapconnection, $context,
800
                        $ldap_search_pattern, $ldap_fields_wanted,
801
                        0, -1, -1, LDAP_DEREF_NEVER, $servercontrols);
802
                }
803
 
804
                if (!$ldap_result) {
805
                    continue;
806
                }
807
 
808
                if ($ldap_pagedresults) {
809
                    // Get next server cookie to know if we'll need to continue searching.
810
                    $ldap_cookie = '';
811
                    // Get next cookie from controls.
812
                    ldap_parse_result($this->ldapconnection, $ldap_result, $errcode, $matcheddn,
813
                        $errmsg, $referrals, $controls);
814
                    if (isset($controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'])) {
815
                        $ldap_cookie = $controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'];
816
                    }
817
                }
818
 
819
                // Check and push results. ldap_get_entries() already
820
                // lowercases the attribute index, so there's no need to
821
                // use array_change_key_case() later.
822
                $records = ldap_get_entries($this->ldapconnection, $ldap_result);
823
 
824
                // LDAP libraries return an odd array, really. Fix it.
825
                for ($c = 0; $c < $records['count']; $c++) {
826
                    array_push($flat_records, $records[$c]);
827
                }
828
                // Free some mem
829
                unset($records);
830
            } while ($ldap_pagedresults && !empty($ldap_cookie));
831
 
832
            // If LDAP paged results were used, the current connection must be completely
833
            // closed and a new one created, to work without paged results from here on.
834
            if ($ldap_pagedresults) {
835
                $this->ldap_close();
836
                $this->ldap_connect();
837
            }
838
 
839
            if (count($flat_records)) {
840
                $courses = array_merge($courses, $flat_records);
841
            }
842
        }
843
 
844
        return $courses;
845
    }
846
 
847
    /**
848
     * Search specified contexts for the specified userid and return the
849
     * user dn like: cn=username,ou=suborg,o=org. It's actually a wrapper
850
     * around ldap_find_userdn().
851
     *
852
     * @param string $userid the userid to search for (in external LDAP encoding, no magic quotes).
853
     * @return mixed the user dn or false
854
     */
855
    protected function ldap_find_userdn($userid) {
856
        global $CFG;
857
        require_once($CFG->libdir.'/ldaplib.php');
858
 
859
        $ldap_contexts = explode(';', $this->get_config('user_contexts'));
860
 
861
        return ldap_find_userdn($this->ldapconnection, $userid, $ldap_contexts,
862
                                $this->userobjectclass,
863
                                $this->get_config('idnumber_attribute'), $this->get_config('user_search_sub'));
864
    }
865
 
866
    /**
867
     * Find the groups a given distinguished name belongs to, both directly
868
     * and indirectly via nested groups membership.
869
     *
870
     * @param string $memberdn distinguished name to search
871
     * @return array with member groups' distinguished names (can be emtpy)
872
     */
873
    protected function ldap_find_user_groups($memberdn) {
874
        $groups = array();
875
 
876
        $this->ldap_find_user_groups_recursively($memberdn, $groups);
877
        return $groups;
878
    }
879
 
880
    /**
881
     * Recursively process the groups the given member distinguished name
882
     * belongs to, adding them to the already processed groups array.
883
     *
884
     * @param string $memberdn distinguished name to search
885
     * @param array reference &$membergroups array with already found
886
     *                        groups, where we'll put the newly found
887
     *                        groups.
888
     */
889
    protected function ldap_find_user_groups_recursively($memberdn, &$membergroups) {
890
        $result = @ldap_read($this->ldapconnection, $memberdn, '(objectClass=*)', array($this->get_config('group_memberofattribute')));
891
        if (!$result) {
892
            return;
893
        }
894
 
895
        if ($entry = ldap_first_entry($this->ldapconnection, $result)) {
896
            do {
897
                $attributes = ldap_get_attributes($this->ldapconnection, $entry);
898
                for ($j = 0; $j < $attributes['count']; $j++) {
899
                    $groups = ldap_get_values_len($this->ldapconnection, $entry, $attributes[$j]);
900
                    foreach ($groups as $key => $group) {
901
                        if ($key === 'count') {  // Skip the entries count
902
                            continue;
903
                        }
904
                        if(!in_array($group, $membergroups)) {
905
                            // Only push and recurse if we haven't 'seen' this group before
906
                            // to prevent loops (MS Active Directory allows them!!).
907
                            array_push($membergroups, $group);
908
                            $this->ldap_find_user_groups_recursively($group, $membergroups);
909
                        }
910
                    }
911
                }
912
            }
913
            while ($entry = ldap_next_entry($this->ldapconnection, $entry));
914
        }
915
    }
916
 
917
    /**
918
     * Given a group name (either a RDN or a DN), get the list of users
919
     * belonging to that group. If the group has nested groups, expand all
920
     * the intermediate groups and return the full list of users that
921
     * directly or indirectly belong to the group.
922
     *
923
     * @param string $group the group name to search
924
     * @param string $memberattibute the attribute that holds the members of the group
925
     * @return array the list of users belonging to the group. If $group
926
     *         is not actually a group, returns array($group).
927
     */
928
    protected function ldap_explode_group($group, $memberattribute) {
929
        switch ($this->get_config('user_type')) {
930
            case 'ad':
931
                // $group is already the distinguished name to search.
932
                $dn = $group;
933
 
934
                $result = ldap_read($this->ldapconnection, $dn, '(objectClass=*)', array('objectClass'));
935
                $entry = ldap_first_entry($this->ldapconnection, $result);
936
                $objectclass = ldap_get_values($this->ldapconnection, $entry, 'objectClass');
937
 
938
                if (!in_array('group', $objectclass)) {
939
                    // Not a group, so return immediately.
940
                    return array($group);
941
                }
942
 
943
                $result = ldap_read($this->ldapconnection, $dn, '(objectClass=*)', array($memberattribute));
944
                $entry = ldap_first_entry($this->ldapconnection, $result);
945
                $members = @ldap_get_values($this->ldapconnection, $entry, $memberattribute); // Can be empty and throws a warning
946
                if ($members['count'] == 0) {
947
                    // There are no members in this group, return nothing.
948
                    return array();
949
                }
950
                unset($members['count']);
951
 
952
                $users = array();
953
                foreach ($members as $member) {
954
                    $group_members = $this->ldap_explode_group($member, $memberattribute);
955
                    $users = array_merge($users, $group_members);
956
                }
957
 
958
                return ($users);
959
                break;
960
            default:
961
                error_log($this->errorlogtag.get_string('explodegroupusertypenotsupported', 'enrol_ldap',
962
                                                        $this->get_config('user_type_name')));
963
 
964
                return array($group);
965
        }
966
    }
967
 
968
    /**
969
     * Will create the moodle course from the template
970
     * course_ext is an array as obtained from ldap -- flattened somewhat
971
     *
972
     * @param array $course_ext
973
     * @param progress_trace $trace
974
     * @return mixed false on error, id for the newly created course otherwise.
975
     */
976
    function create_course($course_ext, progress_trace $trace) {
977
        global $CFG, $DB;
978
 
979
        require_once("$CFG->dirroot/course/lib.php");
980
 
981
        // Override defaults with template course
982
        $template = false;
983
        if ($this->get_config('template')) {
984
            if ($template = $DB->get_record('course', array('shortname'=>$this->get_config('template')))) {
985
                $template = fullclone(course_get_format($template)->get_course());
986
                unset($template->id); // So we are clear to reinsert the record
987
                unset($template->fullname);
988
                unset($template->shortname);
989
                unset($template->idnumber);
990
            }
991
        }
992
        if (!$template) {
993
            $courseconfig = get_config('moodlecourse');
994
            $template = new stdClass();
995
            $template->summary        = '';
996
            $template->summaryformat  = FORMAT_HTML;
997
            $template->format         = $courseconfig->format;
998
            $template->newsitems      = $courseconfig->newsitems;
999
            $template->showgrades     = $courseconfig->showgrades;
1000
            $template->showreports    = $courseconfig->showreports;
1001
            $template->maxbytes       = $courseconfig->maxbytes;
1002
            $template->groupmode      = $courseconfig->groupmode;
1003
            $template->groupmodeforce = $courseconfig->groupmodeforce;
1004
            $template->visible        = $courseconfig->visible;
1005
            $template->lang           = $courseconfig->lang;
1006
            $template->enablecompletion = $courseconfig->enablecompletion;
1007
        }
1008
        $course = $template;
1009
 
1010
        $course->category = $this->get_config('category');
1011
        if (!$DB->record_exists('course_categories', array('id'=>$this->get_config('category')))) {
1012
            $categories = $DB->get_records('course_categories', array(), 'sortorder', 'id', 0, 1);
1013
            $first = reset($categories);
1014
            $course->category = $first->id;
1015
        }
1016
 
1017
        // Override with required ext data
1018
        $course->idnumber  = $course_ext[$this->get_config('course_idnumber')][0];
1019
        $course->fullname  = $course_ext[$this->get_config('course_fullname')][0];
1020
        $course->shortname = $course_ext[$this->get_config('course_shortname')][0];
1021
        if (empty($course->idnumber) || empty($course->fullname) || empty($course->shortname)) {
1022
            // We are in trouble!
1023
            $trace->output(get_string('cannotcreatecourse', 'enrol_ldap').' '.var_export($course, true));
1024
            return false;
1025
        }
1026
 
1027
        $summary = $this->get_config('course_summary');
1028
        if (!isset($summary) || empty($course_ext[$summary][0])) {
1029
            $course->summary = '';
1030
        } else {
1031
            $course->summary = $course_ext[$this->get_config('course_summary')][0];
1032
        }
1033
 
1034
        // Check if the shortname already exists if it does - skip course creation.
1035
        if ($DB->record_exists('course', array('shortname' => $course->shortname))) {
1036
            $trace->output(get_string('duplicateshortname', 'enrol_ldap', $course));
1037
            return false;
1038
        }
1039
 
1040
        $newcourse = create_course($course);
1041
        return $newcourse->id;
1042
    }
1043
 
1044
    /**
1045
     * Will update a moodle course with new values from LDAP
1046
     * A field will be updated only if it is marked to be updated
1047
     * on sync in plugin settings
1048
     *
1049
     * @param object $course
1050
     * @param array $externalcourse
1051
     * @param progress_trace $trace
1052
     * @return bool
1053
     */
1054
    protected function update_course($course, $externalcourse, progress_trace $trace) {
1055
        global $CFG, $DB;
1056
 
1057
        $coursefields = array ('shortname', 'fullname', 'summary');
1058
        static $shouldupdate;
1059
 
1060
        // Initialize $shouldupdate variable. Set to true if one or more fields are marked for update.
1061
        if (!isset($shouldupdate)) {
1062
            $shouldupdate = false;
1063
            foreach ($coursefields as $field) {
1064
                $shouldupdate = $shouldupdate || $this->get_config('course_'.$field.'_updateonsync');
1065
            }
1066
        }
1067
 
1068
        // If we should not update return immediately.
1069
        if (!$shouldupdate) {
1070
            return false;
1071
        }
1072
 
1073
        require_once("$CFG->dirroot/course/lib.php");
1074
        $courseupdated = false;
1075
        $updatedcourse = new stdClass();
1076
        $updatedcourse->id = $course->id;
1077
 
1078
        // Update course fields if necessary.
1079
        foreach ($coursefields as $field) {
1080
            // If field is marked to be updated on sync && field data was changed update it.
1081
            if ($this->get_config('course_'.$field.'_updateonsync')
1082
                    && isset($externalcourse[$this->get_config('course_'.$field)][0])
1083
                    && $course->{$field} != $externalcourse[$this->get_config('course_'.$field)][0]) {
1084
                $updatedcourse->{$field} = $externalcourse[$this->get_config('course_'.$field)][0];
1085
                $courseupdated = true;
1086
            }
1087
        }
1088
 
1089
        if (!$courseupdated) {
1090
            $trace->output(get_string('courseupdateskipped', 'enrol_ldap', $course));
1091
            return false;
1092
        }
1093
 
1094
        // Do not allow empty fullname or shortname.
1095
        if ((isset($updatedcourse->fullname) && empty($updatedcourse->fullname))
1096
                || (isset($updatedcourse->shortname) && empty($updatedcourse->shortname))) {
1097
            // We are in trouble!
1098
            $trace->output(get_string('cannotupdatecourse', 'enrol_ldap', $course));
1099
            return false;
1100
        }
1101
 
1102
        // Check if the shortname already exists if it does - skip course updating.
1103
        if (isset($updatedcourse->shortname)
1104
                && $DB->record_exists('course', array('shortname' => $updatedcourse->shortname))) {
1105
            $trace->output(get_string('cannotupdatecourse_duplicateshortname', 'enrol_ldap', $course));
1106
            return false;
1107
        }
1108
 
1109
        // Finally - update course in DB.
1110
        update_course($updatedcourse);
1111
        $trace->output(get_string('courseupdated', 'enrol_ldap', $course));
1112
 
1113
        return true;
1114
    }
1115
 
1116
    /**
1117
     * Automatic enrol sync executed during restore.
1118
     * Useful for automatic sync by course->idnumber or course category.
1119
     * @param stdClass $course course record
1120
     */
1121
    public function restore_sync_course($course) {
1122
        // TODO: this can not work because restore always nukes the course->idnumber, do not ask me why (MDL-37312)
1123
        // NOTE: for now restore does not do any real logging yet, let's do the same here...
1124
        $trace = new error_log_progress_trace();
1125
        $this->sync_enrolments($trace, $course->id);
1126
    }
1127
 
1128
    /**
1129
     * Restore instance and map settings.
1130
     *
1131
     * @param restore_enrolments_structure_step $step
1132
     * @param stdClass $data
1133
     * @param stdClass $course
1134
     * @param int $oldid
1135
     */
1136
    public function restore_instance(restore_enrolments_structure_step $step, stdClass $data, $course, $oldid) {
1137
        global $DB;
1138
        // There is only 1 ldap enrol instance per course.
1139
        if ($instances = $DB->get_records('enrol', array('courseid'=>$data->courseid, 'enrol'=>'ldap'), 'id')) {
1140
            $instance = reset($instances);
1141
            $instanceid = $instance->id;
1142
        } else {
1143
            $instanceid = $this->add_instance($course, (array)$data);
1144
        }
1145
        $step->set_mapping('enrol', $oldid, $instanceid);
1146
    }
1147
 
1148
    /**
1149
     * Restore user enrolment.
1150
     *
1151
     * @param restore_enrolments_structure_step $step
1152
     * @param stdClass $data
1153
     * @param stdClass $instance
1154
     * @param int $oldinstancestatus
1155
     * @param int $userid
1156
     */
1157
    public function restore_user_enrolment(restore_enrolments_structure_step $step, $data, $instance, $userid, $oldinstancestatus) {
1158
        global $DB;
1159
 
1160
        if ($this->get_config('unenrolaction') == ENROL_EXT_REMOVED_UNENROL) {
1161
            // Enrolments were already synchronised in restore_instance(), we do not want any suspended leftovers.
1162
 
1163
        } else if ($this->get_config('unenrolaction') == ENROL_EXT_REMOVED_KEEP) {
1164
            if (!$DB->record_exists('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
1165
                $this->enrol_user($instance, $userid, null, 0, 0, $data->status);
1166
            }
1167
 
1168
        } else {
1169
            if (!$DB->record_exists('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
1170
                $this->enrol_user($instance, $userid, null, 0, 0, ENROL_USER_SUSPENDED);
1171
            }
1172
        }
1173
    }
1174
 
1175
    /**
1176
     * Restore role assignment.
1177
     *
1178
     * @param stdClass $instance
1179
     * @param int $roleid
1180
     * @param int $userid
1181
     * @param int $contextid
1182
     */
1183
    public function restore_role_assignment($instance, $roleid, $userid, $contextid) {
1184
        global $DB;
1185
 
1186
        if ($this->get_config('unenrolaction') == ENROL_EXT_REMOVED_UNENROL or $this->get_config('unenrolaction') == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
1187
            // Skip any roles restore, they should be already synced automatically.
1188
            return;
1189
        }
1190
 
1191
        // Just restore every role.
1192
        if ($DB->record_exists('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) {
1193
            role_assign($roleid, $userid, $contextid, 'enrol_'.$instance->enrol, $instance->id);
1194
        }
1195
    }
1196
}