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
 * Class process
19
 *
20
 * @package     tool_uploaduser
21
 * @copyright   2020 Moodle
22
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
namespace tool_uploaduser;
26
 
27
defined('MOODLE_INTERNAL') || die();
28
 
29
use context_system;
30
use context_coursecat;
31
use core_course_category;
32
 
33
use tool_uploaduser\local\field_value_validators;
34
 
35
require_once($CFG->dirroot.'/user/profile/lib.php');
36
require_once($CFG->dirroot.'/user/lib.php');
37
require_once($CFG->dirroot.'/group/lib.php');
38
require_once($CFG->dirroot.'/cohort/lib.php');
39
require_once($CFG->libdir.'/csvlib.class.php');
40
require_once($CFG->dirroot.'/'.$CFG->admin.'/tool/uploaduser/locallib.php');
41
 
42
/**
43
 * Process CSV file with users data, this will create/update users, enrol them into courses, etc
44
 *
45
 * @package     tool_uploaduser
46
 * @copyright   2020 Moodle
47
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
48
 */
49
class process {
50
 
51
    /** @var \csv_import_reader  */
52
    protected $cir;
53
    /** @var \stdClass  */
54
    protected $formdata;
55
    /** @var \uu_progress_tracker  */
56
    protected $upt;
57
    /** @var array  */
58
    protected $filecolumns = null;
59
    /** @var int  */
60
    protected $today;
61
    /** @var \enrol_plugin|null */
62
    protected $manualenrol = null;
63
    /** @var array */
64
    protected $standardfields = [];
65
    /** @var array */
66
    protected $profilefields = [];
67
    /** @var \profile_field_base[] */
68
    protected $allprofilefields = [];
69
    /** @var string|\uu_progress_tracker|null  */
70
    protected $progresstrackerclass = null;
71
 
72
    /** @var int */
73
    protected $usersnew      = 0;
74
    /** @var int */
75
    protected $usersupdated  = 0;
76
    /** @var int /not printed yet anywhere */
77
    protected $usersuptodate = 0;
78
    /** @var int */
79
    protected $userserrors   = 0;
80
    /** @var int */
81
    protected $deletes       = 0;
82
    /** @var int */
83
    protected $deleteerrors  = 0;
84
    /** @var int */
85
    protected $renames       = 0;
86
    /** @var int */
87
    protected $renameerrors  = 0;
88
    /** @var int */
89
    protected $usersskipped  = 0;
90
    /** @var int */
91
    protected $weakpasswords = 0;
92
 
93
    /** @var array course cache - do not fetch all courses here, we  will not probably use them all anyway */
94
    protected $ccache         = [];
95
    /** @var array */
96
    protected $cohorts        = [];
97
    /** @var array  Course roles lookup cache. */
98
    protected $rolecache      = [];
99
    /** @var array System roles lookup cache. */
100
    protected $sysrolecache   = [];
101
    /** @var array cache of used manual enrol plugins in each course */
102
    protected $manualcache    = [];
103
    /** @var array officially supported plugins that are enabled */
104
    protected $supportedauths = [];
105
 
106
    /**
107
     * process constructor.
108
     *
109
     * @param \csv_import_reader $cir
110
     * @param string|null $progresstrackerclass
111
     * @throws \coding_exception
112
     */
113
    public function __construct(\csv_import_reader $cir, string $progresstrackerclass = null) {
114
        $this->cir = $cir;
115
        if ($progresstrackerclass) {
116
            if (!class_exists($progresstrackerclass) || !is_subclass_of($progresstrackerclass, \uu_progress_tracker::class)) {
117
                throw new \coding_exception('Progress tracker class must extend \uu_progress_tracker');
118
            }
119
            $this->progresstrackerclass = $progresstrackerclass;
120
        } else {
121
            $this->progresstrackerclass = \uu_progress_tracker::class;
122
        }
123
 
124
        // Keep timestamp consistent.
125
        $today = time();
126
        $today = make_timestamp(date('Y', $today), date('m', $today), date('d', $today), 0, 0, 0);
127
        $this->today = $today;
128
 
129
        $this->sysrolecache   = uu_allowed_sysroles_cache(); // System roles lookup cache.
130
        $this->supportedauths = uu_supported_auths(); // Officially supported plugins that are enabled.
131
 
132
        if (enrol_is_enabled('manual')) {
133
            // We use only manual enrol plugin here, if it is disabled no enrol is done.
134
            $this->manualenrol = enrol_get_plugin('manual');
135
        }
136
 
137
        $this->find_profile_fields();
138
        $this->find_standard_fields();
139
    }
140
 
141
    /**
142
     * Standard user fields.
143
     */
144
    protected function find_standard_fields(): void {
145
        $this->standardfields = array('id', 'username', 'email', 'emailstop',
146
            'city', 'country', 'lang', 'timezone', 'mailformat',
147
            'maildisplay', 'maildigest', 'htmleditor', 'autosubscribe',
148
            'institution', 'department', 'idnumber', 'phone1', 'phone2', 'address',
149
            'description', 'descriptionformat', 'password',
150
            'auth',        // Watch out when changing auth type or using external auth plugins!
151
            'oldusername', // Use when renaming users - this is the original username.
152
            'suspended',   // 1 means suspend user account, 0 means activate user account, nothing means keep as is.
153
            'theme',       // Define a theme for user when 'allowuserthemes' is enabled.
154
            'deleted',     // 1 means delete user
155
            'mnethostid',  // Can not be used for adding, updating or deleting of users - only for enrolments,
156
                           // groups, cohorts and suspending.
157
            'interests',
158
        );
159
        // Include all name fields.
160
        $this->standardfields = array_merge($this->standardfields, \core_user\fields::get_name_fields());
161
    }
162
 
163
    /**
164
     * Profile fields
165
     */
166
    protected function find_profile_fields(): void {
167
        global $CFG;
168
        require_once($CFG->dirroot . '/user/profile/lib.php');
169
        $this->allprofilefields = profile_get_user_fields_with_data(0);
170
        $this->profilefields = [];
171
        if ($proffields = $this->allprofilefields) {
172
            foreach ($proffields as $key => $proffield) {
173
                $profilefieldname = 'profile_field_'.$proffield->get_shortname();
174
                $this->profilefields[] = $profilefieldname;
175
                // Re-index $proffields with key as shortname. This will be
176
                // used while checking if profile data is key and needs to be converted (eg. menu profile field).
177
                $proffields[$profilefieldname] = $proffield;
178
                unset($proffields[$key]);
179
            }
180
            $this->allprofilefields = $proffields;
181
        }
182
    }
183
 
184
    /**
185
     * Returns the list of columns in the file
186
     *
187
     * @return array
188
     */
189
    public function get_file_columns(): array {
190
        if ($this->filecolumns === null) {
191
            $returnurl = new \moodle_url('/admin/tool/uploaduser/index.php');
192
            $this->filecolumns = uu_validate_user_upload_columns($this->cir,
193
                $this->standardfields, $this->profilefields, $returnurl);
194
        }
195
        return $this->filecolumns;
196
    }
197
 
198
    /**
199
     * Set data from the form (or from CLI options)
200
     *
201
     * @param \stdClass $formdata
202
     */
203
    public function set_form_data(\stdClass $formdata): void {
204
        global $SESSION;
205
        $this->formdata = $formdata;
206
 
207
        // Clear bulk selection.
208
        if ($this->get_bulk()) {
209
            $SESSION->bulk_users = array();
210
        }
211
    }
212
 
213
    /**
214
     * Operation type
215
     * @return int
216
     */
217
    protected function get_operation_type(): int {
218
        return (int)$this->formdata->uutype;
219
    }
220
 
221
    /**
222
     * Setting to allow deletes
223
     * @return bool
224
     */
225
    protected function get_allow_deletes(): bool {
226
        $optype = $this->get_operation_type();
227
        return (!empty($this->formdata->uuallowdeletes) and $optype != UU_USER_ADDNEW and $optype != UU_USER_ADDINC);
228
    }
229
 
230
    /**
231
     * Setting to allow matching user accounts on email
232
     * @return bool
233
     */
234
    protected function get_match_on_email(): bool {
235
        $optype = $this->get_operation_type();
236
        return (!empty($this->formdata->uumatchemail) && $optype != UU_USER_ADDNEW && $optype != UU_USER_ADDINC);
237
    }
238
 
239
    /**
240
     * Setting to allow deletes
241
     * @return bool
242
     */
243
    protected function get_allow_renames(): bool {
244
        $optype = $this->get_operation_type();
245
        return (!empty($this->formdata->uuallowrenames) and $optype != UU_USER_ADDNEW and $optype != UU_USER_ADDINC);
246
    }
247
 
248
    /**
249
     * Setting to select for bulk actions (not available in CLI)
250
     * @return bool
251
     */
252
    public function get_bulk(): bool {
253
        return $this->formdata->uubulk ?? false;
254
    }
255
 
256
    /**
257
     * Setting for update type
258
     * @return int
259
     */
260
    protected function get_update_type(): int {
261
        return isset($this->formdata->uuupdatetype) ? $this->formdata->uuupdatetype : 0;
262
    }
263
 
264
    /**
265
     * Setting to allow update passwords
266
     * @return bool
267
     */
268
    protected function get_update_passwords(): bool {
269
        return !empty($this->formdata->uupasswordold)
270
            and $this->get_operation_type() != UU_USER_ADDNEW
271
            and $this->get_operation_type() != UU_USER_ADDINC
272
            and ($this->get_update_type() == UU_UPDATE_FILEOVERRIDE or $this->get_update_type() == UU_UPDATE_ALLOVERRIDE);
273
    }
274
 
275
    /**
276
     * Setting to allow email duplicates
277
     * @return bool
278
     */
279
    protected function get_allow_email_duplicates(): bool {
280
        global $CFG;
281
        return !(empty($CFG->allowaccountssameemail) ? 1 : $this->formdata->uunoemailduplicates);
282
    }
283
 
284
    /**
285
     * Setting for reset password
286
     * @return int UU_PWRESET_NONE, UU_PWRESET_WEAK, UU_PWRESET_ALL
287
     */
288
    protected function get_reset_passwords(): int {
289
        return isset($this->formdata->uuforcepasswordchange) ? $this->formdata->uuforcepasswordchange : UU_PWRESET_NONE;
290
    }
291
 
292
    /**
293
     * Setting to allow create passwords
294
     * @return bool
295
     */
296
    protected function get_create_paswords(): bool {
297
        return (!empty($this->formdata->uupasswordnew) and $this->get_operation_type() != UU_USER_UPDATE);
298
    }
299
 
300
    /**
301
     * Setting to allow suspends
302
     * @return bool
303
     */
304
    protected function get_allow_suspends(): bool {
305
        return !empty($this->formdata->uuallowsuspends);
306
    }
307
 
308
    /**
309
     * Setting to normalise user names
310
     * @return bool
311
     */
312
    protected function get_normalise_user_names(): bool {
313
        return !empty($this->formdata->uustandardusernames);
314
    }
315
 
316
    /**
317
     * Helper method to return Yes/No string
318
     *
319
     * @param bool $value
320
     * @return string
321
     */
322
    protected function get_string_yes_no($value): string {
323
        return $value ? get_string('yes') : get_string('no');
324
    }
325
 
326
    /**
327
     * Process the CSV file
328
     */
329
    public function process() {
330
        // Init csv import helper.
331
        $this->cir->init();
332
 
333
        $classname = $this->progresstrackerclass;
334
        $this->upt = new $classname();
335
        $this->upt->start(); // Start table.
336
 
337
        $linenum = 1; // Column header is first line.
338
        while ($line = $this->cir->next()) {
339
            $this->upt->flush();
340
            $linenum++;
341
 
342
            $this->upt->track('line', $linenum);
343
            $this->process_line($line);
344
        }
345
 
346
        $this->upt->close(); // Close table.
347
        $this->cir->close();
348
        $this->cir->cleanup(true);
349
    }
350
 
351
    /**
352
     * Prepare one line from CSV file as a user record
353
     *
354
     * @param array $line
355
     * @return \stdClass|null
356
     */
357
    protected function prepare_user_record(array $line): ?\stdClass {
358
        global $CFG, $USER;
359
 
360
        $user = new \stdClass();
361
 
362
        // Add fields to user object.
363
        foreach ($line as $keynum => $value) {
364
            if (!isset($this->get_file_columns()[$keynum])) {
365
                // This should not happen.
366
                continue;
367
            }
368
            $key = $this->get_file_columns()[$keynum];
369
            if (strpos($key, 'profile_field_') === 0) {
370
                // NOTE: bloody mega hack alert!!
371
                if (isset($USER->$key) and is_array($USER->$key)) {
372
                    // This must be some hacky field that is abusing arrays to store content and format.
373
                    $user->$key = array();
374
                    $user->{$key['text']}   = $value;
375
                    $user->{$key['format']} = FORMAT_MOODLE;
376
                } else {
377
                    $user->$key = trim($value);
378
                }
379
            } else {
380
                $user->$key = trim($value);
381
            }
382
 
383
            if (in_array($key, $this->upt->columns)) {
384
                // Default value in progress tracking table, can be changed later.
385
                $this->upt->track($key, s($value), 'normal');
386
            }
387
        }
388
        if (!isset($user->username)) {
389
            // Prevent warnings below.
390
            $user->username = '';
391
        }
392
 
393
        if ($this->get_operation_type() == UU_USER_ADDNEW or $this->get_operation_type() == UU_USER_ADDINC) {
394
            // User creation is a special case - the username may be constructed from templates using firstname and lastname
395
            // better never try this in mixed update types.
396
            $error = false;
397
            if (!isset($user->firstname) or $user->firstname === '') {
398
                $this->upt->track('status', get_string('missingfield', 'error', 'firstname'), 'error');
399
                $this->upt->track('firstname', get_string('error'), 'error');
400
                $error = true;
401
            }
402
            if (!isset($user->lastname) or $user->lastname === '') {
403
                $this->upt->track('status', get_string('missingfield', 'error', 'lastname'), 'error');
404
                $this->upt->track('lastname', get_string('error'), 'error');
405
                $error = true;
406
            }
407
            if ($error) {
408
                $this->userserrors++;
409
                return null;
410
            }
411
            // We require username too - we might use template for it though.
412
            if (empty($user->username) and !empty($this->formdata->username)) {
413
                $user->username = uu_process_template($this->formdata->username, $user);
414
                $this->upt->track('username', s($user->username));
415
            }
416
        }
417
 
418
        // Normalize username.
419
        $user->originalusername = $user->username;
420
        if ($this->get_normalise_user_names()) {
421
            $user->username = \core_user::clean_field($user->username, 'username');
422
        }
423
 
424
        // Make sure we really have username.
425
        if (empty($user->username) && !$this->get_match_on_email()) {
426
            $this->upt->track('status', get_string('missingfield', 'error', 'username'), 'error');
427
            $this->upt->track('username', get_string('error'), 'error');
428
            $this->userserrors++;
429
            return null;
430
        } else if ($user->username === 'guest') {
431
            $this->upt->track('status', get_string('guestnoeditprofileother', 'error'), 'error');
432
            $this->userserrors++;
433
            return null;
434
        }
435
 
436
        if ($user->username !== \core_user::clean_field($user->username, 'username')) {
437
            $this->upt->track('status', get_string('invalidusername', 'error', 'username'), 'error');
438
            $this->upt->track('username', get_string('error'), 'error');
439
            $this->userserrors++;
440
        }
441
 
442
        if (empty($user->mnethostid)) {
443
            $user->mnethostid = $CFG->mnet_localhost_id;
444
        }
445
 
446
        return $user;
447
    }
448
 
449
    /**
450
     * Process one line from CSV file
451
     *
452
     * @param array $line
453
     * @throws \coding_exception
454
     * @throws \dml_exception
455
     * @throws \moodle_exception
456
     */
457
    public function process_line(array $line) {
458
        global $DB, $CFG, $SESSION;
459
 
460
        if (!$user = $this->prepare_user_record($line)) {
461
            return;
462
        }
463
 
464
        if ($this->get_match_on_email()) {
465
            // Case-insensitive query for the given email address.
466
            $userselect = $DB->sql_equal('email', ':email', false);
467
            $userparams = ['email' => $user->email];
468
        } else {
469
            $userselect = 'username = :username';
470
            $userparams = ['username' => $user->username];
471
        }
472
 
473
        // Match the user, also accounting for multiple records by email.
474
        $existinguser = $DB->get_records_select('user', "{$userselect} AND mnethostid = :mnethostid",
475
            $userparams + ['mnethostid' => $user->mnethostid]);
476
        $existingusercount = count($existinguser);
477
 
478
        if ($existingusercount > 0) {
479
            if ($existingusercount !== 1) {
480
                $this->upt->track('status', get_string('duplicateemail', 'tool_uploaduser', $user->email), 'warning');
481
                $this->userserrors++;
482
                return;
483
 
484
            }
485
 
486
            $existinguser = is_array($existinguser) ? array_values($existinguser)[0] : $existinguser;
487
            $this->upt->track('id', $existinguser->id, 'normal', false);
488
        }
489
 
490
        if ($user->mnethostid == $CFG->mnet_localhost_id) {
491
            $remoteuser = false;
492
 
493
            // Find out if username incrementing required.
494
            if ($existinguser and $this->get_operation_type() == UU_USER_ADDINC) {
495
                $user->username = uu_increment_username($user->username);
496
                $existinguser = false;
497
            }
498
 
499
        } else {
500
            if (!$existinguser or $this->get_operation_type() == UU_USER_ADDINC) {
501
                $this->upt->track('status', get_string('errormnetadd', 'tool_uploaduser'), 'error');
502
                $this->userserrors++;
503
                return;
504
            }
505
 
506
            $remoteuser = true;
507
 
508
            // Make sure there are no changes of existing fields except the suspended status.
509
            foreach ((array)$existinguser as $k => $v) {
510
                if ($k === 'suspended') {
511
                    continue;
512
                }
513
                if (property_exists($user, $k)) {
514
                    $user->$k = $v;
515
                }
516
                if (in_array($k, $this->upt->columns)) {
517
                    if ($k === 'password' or $k === 'oldusername' or $k === 'deleted') {
518
                        $this->upt->track($k, '', 'normal', false);
519
                    } else {
520
                        $this->upt->track($k, s($v), 'normal', false);
521
                    }
522
                }
523
            }
524
            unset($user->oldusername);
525
            unset($user->password);
526
            $user->auth = $existinguser->auth;
527
        }
528
 
529
        // Notify about nay username changes.
530
        if ($user->originalusername !== $user->username) {
531
            $this->upt->track('username', '', 'normal', false); // Clear previous.
532
            $this->upt->track('username', s($user->originalusername).'-->'.s($user->username), 'info');
533
        } else {
534
            $this->upt->track('username', s($user->username), 'normal', false);
535
        }
536
        unset($user->originalusername);
537
 
538
        // Verify if the theme is valid and allowed to be set.
539
        if (isset($user->theme)) {
540
            list($status, $message) = field_value_validators::validate_theme($user->theme);
541
            if ($status !== 'normal' && !empty($message)) {
542
                $this->upt->track('status', $message, $status);
543
                // Unset the theme when validation fails.
544
                unset($user->theme);
545
            }
546
        }
547
 
548
        // Add default values for remaining fields.
549
        $formdefaults = array();
550
        if (!$existinguser ||
551
                ($this->get_update_type() != UU_UPDATE_FILEOVERRIDE && $this->get_update_type() != UU_UPDATE_NOCHANGES)) {
552
            foreach ($this->standardfields as $field) {
553
                if (isset($user->$field)) {
554
                    continue;
555
                }
556
                // All validation moved to form2.
557
                if (isset($this->formdata->$field)) {
558
                    // Process templates.
559
                    $user->$field = uu_process_template($this->formdata->$field, $user);
560
                    $formdefaults[$field] = true;
561
                    if (in_array($field, $this->upt->columns)) {
562
                        $this->upt->track($field, s($user->$field), 'normal');
563
                    }
564
                }
565
            }
566
            foreach ($this->allprofilefields as $field => $profilefield) {
567
                if (isset($user->$field)) {
568
                    continue;
569
                }
570
                if (isset($this->formdata->$field)) {
571
                    // Process templates.
572
                    $user->$field = uu_process_template($this->formdata->$field, $user);
573
 
574
                    // Form contains key and later code expects value.
575
                    // Convert key to value for required profile fields.
576
                    if (method_exists($profilefield, 'convert_external_data')) {
577
                        $user->$field = $profilefield->edit_save_data_preprocess($user->$field, null);
578
                    }
579
 
580
                    $formdefaults[$field] = true;
581
                }
582
            }
583
        }
584
 
585
        // Delete user.
586
        if (!empty($user->deleted)) {
587
            if (!$this->get_allow_deletes() or $remoteuser or
588
                    !has_capability('moodle/user:delete', context_system::instance())) {
589
                $this->usersskipped++;
590
                $this->upt->track('status', get_string('usernotdeletedoff', 'error'), 'warning');
591
                return;
592
            }
593
            if ($existinguser) {
594
                if (is_siteadmin($existinguser->id)) {
595
                    $this->upt->track('status', get_string('usernotdeletedadmin', 'error'), 'error');
596
                    $this->deleteerrors++;
597
                    return;
598
                }
599
                if (delete_user($existinguser)) {
600
                    $this->upt->track('status', get_string('userdeleted', 'tool_uploaduser'));
601
                    $this->deletes++;
602
                } else {
603
                    $this->upt->track('status', get_string('usernotdeletederror', 'error'), 'error');
604
                    $this->deleteerrors++;
605
                }
606
            } else {
607
                $this->upt->track('status', get_string('usernotdeletedmissing', 'error'), 'error');
608
                $this->deleteerrors++;
609
            }
610
            return;
611
        }
612
        // We do not need the deleted flag anymore.
613
        unset($user->deleted);
614
 
615
        $matchonemailallowrename = $this->get_match_on_email() && $this->get_allow_renames();
616
        if ($matchonemailallowrename && $user->username && ($user->username !== $existinguser->username)) {
617
            $user->oldusername = $existinguser->username;
618
            $existinguser = false;
619
        }
620
 
621
        // Renaming requested?
622
        if (!empty($user->oldusername) ) {
623
            if (!$this->get_allow_renames()) {
624
                $this->usersskipped++;
625
                $this->upt->track('status', get_string('usernotrenamedoff', 'error'), 'warning');
626
                return;
627
            }
628
 
629
            if ($existinguser) {
630
                $this->upt->track('status', get_string('usernotrenamedexists', 'error'), 'error');
631
                $this->renameerrors++;
632
                return;
633
            }
634
 
635
            if ($user->username === 'guest') {
636
                $this->upt->track('status', get_string('guestnoeditprofileother', 'error'), 'error');
637
                $this->renameerrors++;
638
                return;
639
            }
640
 
641
            if ($this->get_normalise_user_names()) {
642
                $oldusername = \core_user::clean_field($user->oldusername, 'username');
643
            } else {
644
                $oldusername = $user->oldusername;
645
            }
646
 
647
            // No guessing when looking for old username, it must be exact match.
648
            if ($olduser = $DB->get_record('user',
649
                    ['username' => $oldusername, 'mnethostid' => $CFG->mnet_localhost_id])) {
650
                $this->upt->track('id', $olduser->id, 'normal', false);
651
                if (is_siteadmin($olduser->id)) {
652
                    $this->upt->track('status', get_string('usernotrenamedadmin', 'error'), 'error');
653
                    $this->renameerrors++;
654
                    return;
655
                }
656
                $DB->set_field('user', 'username', $user->username, ['id' => $olduser->id]);
657
                $this->upt->track('username', '', 'normal', false); // Clear previous.
658
                $this->upt->track('username', s($oldusername).'-->'.s($user->username), 'info');
659
                $this->upt->track('status', get_string('userrenamed', 'tool_uploaduser'));
660
                $this->renames++;
661
            } else {
662
                $this->upt->track('status', get_string('usernotrenamedmissing', 'error'), 'error');
663
                $this->renameerrors++;
664
                return;
665
            }
666
            $existinguser = $olduser;
667
            $existinguser->username = $user->username;
668
        }
669
 
670
        // Can we process with update or insert?
671
        $skip = false;
672
        switch ($this->get_operation_type()) {
673
            case UU_USER_ADDNEW:
674
                if ($existinguser) {
675
                    $this->usersskipped++;
676
                    $this->upt->track('status', get_string('usernotaddedregistered', 'error'), 'warning');
677
                    $skip = true;
678
                }
679
                break;
680
 
681
            case UU_USER_ADDINC:
682
                if ($existinguser) {
683
                    // This should not happen!
684
                    $this->upt->track('status', get_string('usernotaddederror', 'error'), 'error');
685
                    $this->userserrors++;
686
                    $skip = true;
687
                }
688
                break;
689
 
690
            case UU_USER_ADD_UPDATE:
691
                if ($this->get_match_on_email()) {
692
                    if ($usersbyname = $DB->get_records('user', ['username' => $user->username])) {
693
                        foreach ($usersbyname as $userbyname) {
694
                            if (strtolower($userbyname->email) != strtolower($user->email)) {
695
                                $this->usersskipped++;
696
                                $this->upt->track('status', get_string('usernotaddedusernameexists', 'error'), 'warning');
697
                                $skip = true;
698
                            }
699
                        }
700
                    }
701
                }
702
                break;
703
 
704
            case UU_USER_UPDATE:
705
                if (!$existinguser) {
706
                    $this->usersskipped++;
707
                    $this->upt->track('status', get_string('usernotupdatednotexists', 'error'), 'warning');
708
                    $skip = true;
709
                }
710
                break;
711
 
712
            default:
713
                // Unknown type.
714
                $skip = true;
715
        }
716
 
717
        if ($skip) {
718
            return;
719
        }
720
 
721
        if ($existinguser) {
722
            $user->id = $existinguser->id;
723
 
724
            $this->upt->track('username', \html_writer::link(
725
                new \moodle_url('/user/profile.php', ['id' => $existinguser->id]), s($existinguser->username)), 'normal', false);
726
            $this->upt->track('suspended', $this->get_string_yes_no($existinguser->suspended) , 'normal', false);
727
            $this->upt->track('auth', $existinguser->auth, 'normal', false);
728
 
729
            if (is_siteadmin($user->id)) {
730
                $this->upt->track('status', get_string('usernotupdatedadmin', 'error'), 'error');
731
                $this->userserrors++;
732
                return;
733
            }
734
 
735
            $existinguser->timemodified = time();
736
            // Do NOT mess with timecreated or firstaccess here!
737
 
738
            // Load existing profile data.
739
            profile_load_data($existinguser);
740
 
741
            $doupdate = false;
742
            $dologout = false;
743
 
744
            if ($this->get_update_type() != UU_UPDATE_NOCHANGES and !$remoteuser) {
745
 
746
                // Handle 'auth' column separately, the field can never be missing from a user.
747
                if (!empty($user->auth) && ($user->auth !== $existinguser->auth) &&
748
                        ($this->get_update_type() != UU_UPDATE_MISSING)) {
749
 
750
                    $this->upt->track('auth', s($existinguser->auth).'-->'.s($user->auth), 'info', false);
751
                    $existinguser->auth = $user->auth;
752
                    if (!isset($this->supportedauths[$user->auth])) {
753
                        $this->upt->track('auth', get_string('userauthunsupported', 'error'), 'warning');
754
                    }
755
                    $doupdate = true;
756
                    if ($existinguser->auth === 'nologin') {
757
                        $dologout = true;
758
                    }
759
                }
760
                $allcolumns = array_merge($this->standardfields, $this->profilefields);
761
                foreach ($allcolumns as $column) {
762
                    if ($column === 'username' or $column === 'password' or $column === 'auth' or $column === 'suspended') {
763
                        // These can not be changed here.
764
                        continue;
765
                    }
766
                    if (!property_exists($user, $column) or !property_exists($existinguser, $column)) {
767
                        continue;
768
                    }
769
                    if ($this->get_update_type() == UU_UPDATE_MISSING) {
770
                        if (!is_null($existinguser->$column) and $existinguser->$column !== '') {
771
                            continue;
772
                        }
773
                    } else if ($this->get_update_type() == UU_UPDATE_ALLOVERRIDE) {
774
                        // We override everything.
775
                        null;
776
                    } else if ($this->get_update_type() == UU_UPDATE_FILEOVERRIDE) {
777
                        if (!empty($formdefaults[$column])) {
778
                            // Do not override with form defaults.
779
                            continue;
780
                        }
781
                    }
782
                    if ($existinguser->$column !== $user->$column) {
783
                        if ($column === 'email') {
784
                            $select = $DB->sql_like('email', ':email', false, true, false, '|');
785
                            $params = array('email' => $DB->sql_like_escape($user->email, '|'));
786
                            if ($DB->record_exists_select('user', $select , $params)) {
787
 
788
                                $changeincase = \core_text::strtolower($existinguser->$column) === \core_text::strtolower(
789
                                        $user->$column);
790
 
791
                                if ($changeincase) {
792
                                    // If only case is different then switch to lower case and carry on.
793
                                    $user->$column = \core_text::strtolower($user->$column);
794
                                    continue;
795
                                } else if (!$this->get_allow_email_duplicates()) {
796
                                    $this->upt->track('email', get_string('useremailduplicate', 'error'), 'error');
797
                                    $this->upt->track('status', get_string('usernotupdatederror', 'error'), 'error');
798
                                    $this->userserrors++;
799
                                    return;
800
                                } else {
801
                                    $this->upt->track('email', get_string('useremailduplicate', 'error'), 'warning');
802
                                }
803
                            }
804
                            if (!validate_email($user->email)) {
805
                                $this->upt->track('email', get_string('invalidemail'), 'warning');
806
                            }
807
                        }
808
 
809
                        if ($column === 'lang') {
810
                            if (empty($user->lang)) {
811
                                // Do not change to not-set value.
812
                                continue;
813
                            } else if (\core_user::clean_field($user->lang, 'lang') === '') {
814
                                $this->upt->track('status', get_string('cannotfindlang', 'error', $user->lang), 'warning');
815
                                continue;
816
                            }
817
                        }
818
 
819
                        if (in_array($column, $this->upt->columns)) {
820
                            $this->upt->track($column, s($existinguser->$column).'-->'.s($user->$column), 'info', false);
821
                        }
822
                        $existinguser->$column = $user->$column;
823
                        $doupdate = true;
824
                    }
825
                }
826
            }
827
 
828
            try {
829
                $auth = get_auth_plugin($existinguser->auth);
830
            } catch (\Exception $e) {
831
                $this->upt->track('auth', get_string('userautherror', 'error', s($existinguser->auth)), 'error');
832
                $this->upt->track('status', get_string('usernotupdatederror', 'error'), 'error');
833
                $this->userserrors++;
834
                return;
835
            }
836
            $isinternalauth = $auth->is_internal();
837
 
838
            // Deal with suspending and activating of accounts.
839
            if ($this->get_allow_suspends() and isset($user->suspended) and $user->suspended !== '') {
840
                $user->suspended = $user->suspended ? 1 : 0;
841
                if ($existinguser->suspended != $user->suspended) {
842
                    $this->upt->track('suspended', '', 'normal', false);
843
                    $this->upt->track('suspended',
844
                        $this->get_string_yes_no($existinguser->suspended).'-->'.$this->get_string_yes_no($user->suspended),
845
                        'info', false);
846
                    $existinguser->suspended = $user->suspended;
847
                    $doupdate = true;
848
                    if ($existinguser->suspended) {
849
                        $dologout = true;
850
                    }
851
                }
852
            }
853
 
854
            // Changing of passwords is a special case
855
            // do not force password changes for external auth plugins!
856
            $oldpw = $existinguser->password;
857
 
858
            if ($remoteuser) {
859
                // Do not mess with passwords of remote users.
860
                null;
861
            } else if (!$isinternalauth) {
862
                $existinguser->password = AUTH_PASSWORD_NOT_CACHED;
863
                $this->upt->track('password', '-', 'normal', false);
864
                // Clean up prefs.
865
                unset_user_preference('create_password', $existinguser);
866
                unset_user_preference('auth_forcepasswordchange', $existinguser);
867
 
868
            } else if (!empty($user->password)) {
869
                if ($this->get_update_passwords()) {
870
                    // Check for passwords that we want to force users to reset next
871
                    // time they log in.
872
                    $errmsg = null;
873
                    $weak = !check_password_policy($user->password, $errmsg, $user);
874
                    if ($this->get_reset_passwords() == UU_PWRESET_ALL or
875
                            ($this->get_reset_passwords() == UU_PWRESET_WEAK and $weak)) {
876
                        if ($weak) {
877
                            $this->weakpasswords++;
878
                            $this->upt->track('password', get_string('invalidpasswordpolicy', 'error'), 'warning');
879
                        }
880
                        set_user_preference('auth_forcepasswordchange', 1, $existinguser);
881
                    } else {
882
                        unset_user_preference('auth_forcepasswordchange', $existinguser);
883
                    }
884
                    unset_user_preference('create_password', $existinguser); // No need to create password any more.
885
 
886
                    // Use a low cost factor when generating bcrypt hash otherwise
887
                    // hashing would be slow when uploading lots of users. Hashes
888
                    // will be automatically updated to a higher cost factor the first
889
                    // time the user logs in.
890
                    $existinguser->password = hash_internal_user_password($user->password, true);
891
                    $this->upt->track('password', $user->password, 'normal', false);
892
                } else {
893
                    // Do not print password when not changed.
894
                    $this->upt->track('password', '', 'normal', false);
895
                }
896
            }
897
 
898
            if ($doupdate or $existinguser->password !== $oldpw) {
899
                // We want only users that were really updated.
900
                user_update_user($existinguser, false, false);
901
 
902
                $this->upt->track('status', get_string('useraccountupdated', 'tool_uploaduser'));
903
                $this->usersupdated++;
904
 
905
                if (!$remoteuser) {
906
                    // Pre-process custom profile menu fields data from csv file.
907
                    $existinguser = uu_pre_process_custom_profile_data($existinguser);
908
                    // Save custom profile fields data from csv file.
909
                    profile_save_data($existinguser);
910
                }
911
 
912
                if ($this->get_bulk() == UU_BULK_UPDATED or $this->get_bulk() == UU_BULK_ALL) {
913
                    if (!in_array($user->id, $SESSION->bulk_users)) {
914
                        $SESSION->bulk_users[] = $user->id;
915
                    }
916
                }
917
 
918
                // Trigger event.
919
                \core\event\user_updated::create_from_userid($existinguser->id)->trigger();
920
 
921
            } else {
922
                // No user information changed.
923
                $this->upt->track('status', get_string('useraccountuptodate', 'tool_uploaduser'));
924
                $this->usersuptodate++;
925
 
926
                if ($this->get_bulk() == UU_BULK_ALL) {
927
                    if (!in_array($user->id, $SESSION->bulk_users)) {
928
                        $SESSION->bulk_users[] = $user->id;
929
                    }
930
                }
931
            }
932
 
933
            if ($dologout) {
934
                \core\session\manager::kill_user_sessions($existinguser->id);
935
            }
936
 
937
        } else {
938
            // Save the new user to the database.
939
            $user->confirmed    = 1;
940
            $user->timemodified = time();
941
            $user->timecreated  = time();
942
            $user->mnethostid   = $CFG->mnet_localhost_id; // We support ONLY local accounts here, sorry.
943
 
944
            if (!isset($user->suspended) or $user->suspended === '') {
945
                $user->suspended = 0;
946
            } else {
947
                $user->suspended = $user->suspended ? 1 : 0;
948
            }
949
            $this->upt->track('suspended', $this->get_string_yes_no($user->suspended), 'normal', false);
950
 
951
            if (empty($user->auth)) {
952
                $user->auth = 'manual';
953
            }
954
            $this->upt->track('auth', $user->auth, 'normal', false);
955
 
956
            // Do not insert record if new auth plugin does not exist!
957
            try {
958
                $auth = get_auth_plugin($user->auth);
959
            } catch (\Exception $e) {
960
                $this->upt->track('auth', get_string('userautherror', 'error', s($user->auth)), 'error');
961
                $this->upt->track('status', get_string('usernotaddederror', 'error'), 'error');
962
                $this->userserrors++;
963
                return;
964
            }
965
            if (!isset($this->supportedauths[$user->auth])) {
966
                $this->upt->track('auth', get_string('userauthunsupported', 'error'), 'warning');
967
            }
968
 
969
            $isinternalauth = $auth->is_internal();
970
 
971
            if (empty($user->email)) {
972
                $this->upt->track('email', get_string('invalidemail'), 'error');
973
                $this->upt->track('status', get_string('usernotaddederror', 'error'), 'error');
974
                $this->userserrors++;
975
                return;
976
 
977
            } else if ($DB->record_exists('user', ['email' => $user->email])) {
978
                if (!$this->get_allow_email_duplicates()) {
979
                    $this->upt->track('email', get_string('useremailduplicate', 'error'), 'error');
980
                    $this->upt->track('status', get_string('usernotaddederror', 'error'), 'error');
981
                    $this->userserrors++;
982
                    return;
983
                } else {
984
                    $this->upt->track('email', get_string('useremailduplicate', 'error'), 'warning');
985
                }
986
            }
987
            if (!validate_email($user->email)) {
988
                $this->upt->track('email', get_string('invalidemail'), 'warning');
989
            }
990
 
991
            if (empty($user->lang)) {
992
                $user->lang = '';
993
            } else if (\core_user::clean_field($user->lang, 'lang') === '') {
994
                $this->upt->track('status', get_string('cannotfindlang', 'error', $user->lang), 'warning');
995
                $user->lang = '';
996
            }
997
 
998
            $forcechangepassword = false;
999
 
1000
            if ($isinternalauth) {
1001
                if (empty($user->password)) {
1002
                    if ($this->get_create_paswords()) {
1003
                        $user->password = 'to be generated';
1004
                        $this->upt->track('password', '', 'normal', false);
1005
                        $this->upt->track('password', get_string('uupasswordcron', 'tool_uploaduser'), 'warning', false);
1006
                    } else {
1007
                        $this->upt->track('password', '', 'normal', false);
1008
                        $this->upt->track('password', get_string('missingfield', 'error', 'password'), 'error');
1009
                        $this->upt->track('status', get_string('usernotaddederror', 'error'), 'error');
1010
                        $this->userserrors++;
1011
                        return;
1012
                    }
1013
                } else {
1014
                    $errmsg = null;
1015
                    $weak = !check_password_policy($user->password, $errmsg, $user);
1016
                    if ($this->get_reset_passwords() == UU_PWRESET_ALL or
1017
                            ($this->get_reset_passwords() == UU_PWRESET_WEAK and $weak)) {
1018
                        if ($weak) {
1019
                            $this->weakpasswords++;
1020
                            $this->upt->track('password', get_string('invalidpasswordpolicy', 'error'), 'warning');
1021
                        }
1022
                        $forcechangepassword = true;
1023
                    }
1024
                    // Use a low cost factor when generating bcrypt hash otherwise
1025
                    // hashing would be slow when uploading lots of users. Hashes
1026
                    // will be automatically updated to a higher cost factor the first
1027
                    // time the user logs in.
1028
                    $user->password = hash_internal_user_password($user->password, true);
1029
                }
1030
            } else {
1031
                $user->password = AUTH_PASSWORD_NOT_CACHED;
1032
                $this->upt->track('password', '-', 'normal', false);
1033
            }
1034
 
1035
            $user->id = user_create_user($user, false, false);
1036
            $this->upt->track('username', \html_writer::link(
1037
                new \moodle_url('/user/profile.php', ['id' => $user->id]), s($user->username)), 'normal', false);
1038
 
1039
            // Pre-process custom profile menu fields data from csv file.
1040
            $user = uu_pre_process_custom_profile_data($user);
1041
            // Save custom profile fields data.
1042
            profile_save_data($user);
1043
 
1044
            if ($forcechangepassword) {
1045
                set_user_preference('auth_forcepasswordchange', 1, $user);
1046
            }
1047
            if ($user->password === 'to be generated') {
1048
                set_user_preference('create_password', 1, $user);
1049
            }
1050
 
1051
            // Trigger event.
1052
            \core\event\user_created::create_from_userid($user->id)->trigger();
1053
 
1054
            $this->upt->track('status', get_string('newuser'));
1055
            $this->upt->track('id', $user->id, 'normal', false);
1056
            $this->usersnew++;
1057
 
1058
            // Make sure user context exists.
1059
            \context_user::instance($user->id);
1060
 
1061
            if ($this->get_bulk() == UU_BULK_NEW or $this->get_bulk() == UU_BULK_ALL) {
1062
                if (!in_array($user->id, $SESSION->bulk_users)) {
1063
                    $SESSION->bulk_users[] = $user->id;
1064
                }
1065
            }
1066
        }
1067
 
1068
        // Update user interests.
1069
        if (isset($user->interests) && strval($user->interests) !== '') {
1070
            useredit_update_interests($user, preg_split('/\s*,\s*/', $user->interests, -1, PREG_SPLIT_NO_EMPTY));
1071
        }
1072
 
1073
        // Add to cohort first, it might trigger enrolments indirectly - do NOT create cohorts here!
1074
        foreach ($this->get_file_columns() as $column) {
1075
            if (!preg_match('/^cohort\d+$/', $column)) {
1076
                continue;
1077
            }
1078
 
1079
            if (!empty($user->$column)) {
1080
                $addcohort = $user->$column;
1081
                if (!isset($this->cohorts[$addcohort])) {
1082
                    if (is_number($addcohort)) {
1083
                        // Only non-numeric idnumbers!
1084
                        $cohort = $DB->get_record('cohort', ['id' => $addcohort]);
1085
                    } else {
1086
                        $cohort = $DB->get_record('cohort', ['idnumber' => $addcohort]);
1087
                        if (empty($cohort) && has_capability('moodle/cohort:manage', \context_system::instance())) {
1088
                            // Cohort was not found. Create a new one.
1089
                            $cohortid = cohort_add_cohort((object)array(
1090
                                'idnumber' => $addcohort,
1091
                                'name' => $addcohort,
1092
                                'contextid' => \context_system::instance()->id
1093
                            ));
1094
                            $cohort = $DB->get_record('cohort', ['id' => $cohortid]);
1095
                        }
1096
                    }
1097
 
1098
                    if (empty($cohort)) {
1099
                        $this->cohorts[$addcohort] = get_string('unknowncohort', 'core_cohort', s($addcohort));
1100
                    } else if (!empty($cohort->component)) {
1101
                        // Cohorts synchronised with external sources must not be modified!
1102
                        $this->cohorts[$addcohort] = get_string('external', 'core_cohort');
1103
                    } else {
1104
                        $this->cohorts[$addcohort] = $cohort;
1105
                    }
1106
                }
1107
 
1108
                if (is_object($this->cohorts[$addcohort])) {
1109
                    $cohort = $this->cohorts[$addcohort];
1110
                    if (!$DB->record_exists('cohort_members', ['cohortid' => $cohort->id, 'userid' => $user->id])) {
1111
                        cohort_add_member($cohort->id, $user->id);
1112
                        // We might add special column later, for now let's abuse enrolments.
1113
                        $this->upt->track('enrolments', get_string('useradded', 'core_cohort', s($cohort->name)), 'info');
1114
                    }
1115
                } else {
1116
                    // Error message.
1117
                    $this->upt->track('enrolments', $this->cohorts[$addcohort], 'error');
1118
                }
1119
            }
1120
        }
1121
 
1122
        // Find course enrolments, groups, roles/types and enrol periods
1123
        // this is again a special case, we always do this for any updated or created users.
1124
        foreach ($this->get_file_columns() as $column) {
1125
            if (preg_match('/^sysrole\d+$/', $column)) {
1126
 
1127
                if (!empty($user->$column)) {
1128
                    $sysrolename = $user->$column;
1129
                    if ($sysrolename[0] == '-') {
1130
                        $removing = true;
1131
                        $sysrolename = substr($sysrolename, 1);
1132
                    } else {
1133
                        $removing = false;
1134
                    }
1135
 
1136
                    if (array_key_exists($sysrolename, $this->sysrolecache)) {
1137
                        $sysroleid = $this->sysrolecache[$sysrolename]->id;
1138
                    } else {
1139
                        $this->upt->track('enrolments', get_string('unknownrole', 'error', s($sysrolename)), 'error');
1140
                        continue;
1141
                    }
1142
 
1143
                    if ($removing) {
1144
                        if (user_has_role_assignment($user->id, $sysroleid, SYSCONTEXTID)) {
1145
                            role_unassign($sysroleid, $user->id, SYSCONTEXTID);
1146
                            $this->upt->track('enrolments', get_string('unassignedsysrole',
1147
                                'tool_uploaduser', $this->sysrolecache[$sysroleid]->name), 'info');
1148
                        }
1149
                    } else {
1150
                        if (!user_has_role_assignment($user->id, $sysroleid, SYSCONTEXTID)) {
1151
                            role_assign($sysroleid, $user->id, SYSCONTEXTID);
1152
                            $this->upt->track('enrolments', get_string('assignedsysrole',
1153
                                'tool_uploaduser', $this->sysrolecache[$sysroleid]->name), 'info');
1154
                        }
1155
                    }
1156
                }
1157
 
1158
                continue;
1159
            }
1160
 
1161
            if (preg_match('/^categoryrole(?<roleid>\d+)$/', $column, $rolematches)) {
1162
                $categoryrolecache = [];
1163
                $categorycache  = []; // Category cache - do not fetch all categories here, we will not probably use them all.
1164
 
1165
                $categoryfield = "category{$rolematches['roleid']}";
1166
                $categoryrolefield = "categoryrole{$rolematches['roleid']}";
1167
 
1168
                if (empty($user->{$categoryfield})) {
1169
                    continue;
1170
                }
1171
 
1172
                $categoryidnumber = $user->{$categoryfield};
1173
 
1174
                if (!array_key_exists($categoryidnumber, $categorycache)) {
1175
                    $category = $DB->get_record('course_categories', ['idnumber' => $categoryidnumber], 'id, idnumber');
1176
                    if (empty($category)) {
1177
                        $this->upt->track('enrolments', get_string('unknowncategory', 'error', s($categoryidnumber)), 'error');
1178
                        continue;
1179
                    }
1180
                    $categoryrolecache[$categoryidnumber] = uu_allowed_roles_cache($category->id);
1181
                    $categoryobj = core_course_category::get($category->id);
1182
                    $context = context_coursecat::instance($categoryobj->id);
1183
                    $categorycache[$categoryidnumber] = $context;
1184
                }
1185
                // Check the user's category role.
1186
                if (!empty($user->{$categoryrolefield})) {
1187
                    $rolename = $user->{$categoryrolefield};
1188
                    if (array_key_exists($rolename, $categoryrolecache[$categoryidnumber])) {
1189
                        $roleid = $categoryrolecache[$categoryidnumber][$rolename]->id;
1190
                        // Assign a role to user with category context.
1191
                        role_assign($roleid, $user->id, $categorycache[$categoryidnumber]->id);
1192
                    } else {
1193
                        $this->upt->track('enrolments', get_string('unknownrole', 'error', s($rolename)), 'error');
1194
                        continue;
1195
                    }
1196
                } else {
1197
                    $this->upt->track('enrolments', get_string('missingcategoryrole', 'error', s($categoryidnumber)), 'error');
1198
                    continue;
1199
                }
1200
            }
1201
 
1202
            if (!preg_match('/^course\d+$/', $column)) {
1203
                continue;
1204
            }
1205
            $i = substr($column, 6);
1206
 
1207
            if (empty($user->{'course'.$i})) {
1208
                continue;
1209
            }
1210
            $shortname = $user->{'course'.$i};
1211
            if (!array_key_exists($shortname, $this->ccache)) {
1212
                if (!$course = $DB->get_record('course', ['shortname' => $shortname], 'id, shortname')) {
1213
                    $this->upt->track('enrolments', get_string('unknowncourse', 'error', s($shortname)), 'error');
1214
                    continue;
1215
                }
1216
                $this->ccache[$shortname] = $course;
1217
                $this->ccache[$shortname]->groups = null;
1218
            }
1219
            $courseid      = $this->ccache[$shortname]->id;
1220
            $coursecontext = \context_course::instance($courseid);
1221
            if (!isset($this->manualcache[$courseid])) {
1222
                $this->manualcache[$courseid] = false;
1223
                if ($this->manualenrol) {
1224
                    if ($instances = enrol_get_instances($courseid, false)) {
1225
                        foreach ($instances as $instance) {
1226
                            if ($instance->enrol === 'manual') {
1227
                                $this->manualcache[$courseid] = $instance;
1228
                                break;
1229
                            }
1230
                        }
1231
                    }
1232
                }
1233
            }
1234
 
1235
            if (!array_key_exists($courseid, $this->rolecache)) {
1236
                $this->rolecache[$courseid] = uu_allowed_roles_cache(null, (int)$courseid);
1237
            }
1238
 
1239
            if ($courseid == SITEID) {
1240
                // Technically frontpage does not have enrolments, but only role assignments,
1241
                // let's not invent new lang strings here for this rarely used feature.
1242
 
1243
                if (!empty($user->{'role'.$i})) {
1244
                    $rolename = $user->{'role'.$i};
1245
                    if (array_key_exists($rolename, $this->rolecache[$courseid]) ) {
1246
                        $roleid = $this->rolecache[$courseid][$rolename]->id;
1247
                    } else {
1248
                        $this->upt->track('enrolments', get_string('unknownrole', 'error', s($rolename)), 'error');
1249
                        continue;
1250
                    }
1251
 
1252
                    role_assign($roleid, $user->id, \context_course::instance($courseid));
1253
 
1254
                    $a = new \stdClass();
1255
                    $a->course = $shortname;
1256
                    $a->role = $this->rolecache[$courseid][$roleid]->name;
1257
                    $this->upt->track('enrolments', get_string('enrolledincourserole', 'enrol_manual', $a), 'info');
1258
                }
1259
 
1260
            } else if ($this->manualenrol and $this->manualcache[$courseid]) {
1261
 
1262
                // Find role.
1263
                $roleid = false;
1264
                if (!empty($user->{'role'.$i})) {
1265
                    $rolename = $user->{'role'.$i};
1266
                    if (array_key_exists($rolename, $this->rolecache[$courseid])) {
1267
                        $roleid = $this->rolecache[$courseid][$rolename]->id;
1268
                    } else {
1269
                        $this->upt->track('enrolments', get_string('unknownrole', 'error', s($rolename)), 'error');
1270
                        continue;
1271
                    }
1272
 
1273
                } else if (!empty($user->{'type'.$i})) {
1274
                    // If no role, then find "old" enrolment type.
1275
                    $addtype = $user->{'type'.$i};
1276
                    if ($addtype < 1 or $addtype > 3) {
1277
                        $this->upt->track('enrolments', get_string('error').': typeN = 1|2|3', 'error');
1278
                        continue;
1279
                    } else if (empty($this->formdata->{'uulegacy'.$addtype})) {
1280
                        continue;
1281
                    } else {
1282
                        $roleid = $this->formdata->{'uulegacy'.$addtype};
1283
                    }
1284
                } else {
1285
                    // No role specified, use the default from manual enrol plugin.
1286
                    $defaultenrolroleid = (int)$this->manualcache[$courseid]->roleid;
1287
                    // Validate the current user can assign this role.
1288
                    if (array_key_exists($defaultenrolroleid, $this->rolecache[$courseid]) ) {
1289
                        $roleid = $defaultenrolroleid;
1290
                    } else {
1291
                        $role = $DB->get_record('role', ['id' => $defaultenrolroleid]);
1292
                        $this->upt->track('enrolments', get_string('unknownrole', 'error', s($role->shortname)), 'error');
1293
                        continue;
1294
                    }
1295
                }
1296
 
1297
                if ($roleid) {
1298
                    // Find duration and/or enrol status.
1299
                    $timeend = 0;
1300
                    $timestart = $this->today;
1301
                    $status = null;
1302
 
1303
                    if (isset($user->{'enrolstatus'.$i})) {
1304
                        $enrolstatus = $user->{'enrolstatus'.$i};
1305
                        if ($enrolstatus == '') {
1306
                            $status = null;
1307
                        } else if ($enrolstatus === (string)ENROL_USER_ACTIVE) {
1308
                            $status = ENROL_USER_ACTIVE;
1309
                        } else if ($enrolstatus === (string)ENROL_USER_SUSPENDED) {
1310
                            $status = ENROL_USER_SUSPENDED;
1311
                        } else {
1312
                            debugging('Unknown enrolment status.');
1313
                        }
1314
                    }
1315
 
1316
                    if (!empty($user->{'enroltimestart'.$i})) {
1317
                        $parsedtimestart = strtotime($user->{'enroltimestart'.$i});
1318
                        if ($parsedtimestart !== false) {
1319
                            $timestart = $parsedtimestart;
1320
                        }
1321
                    }
1322
 
1323
                    if (!empty($user->{'enrolperiod'.$i})) {
1324
                        $duration = (int)$user->{'enrolperiod'.$i} * 60 * 60 * 24; // Convert days to seconds.
1325
                        if ($duration > 0) { // Sanity check.
1326
                            $timeend = $timestart + $duration;
1327
                        }
1328
                    } else if ($this->manualcache[$courseid]->enrolperiod > 0) {
1329
                        $timeend = $timestart + $this->manualcache[$courseid]->enrolperiod;
1330
                    }
1331
 
1332
                    $this->manualenrol->enrol_user($this->manualcache[$courseid], $user->id, $roleid,
1333
                        $timestart, $timeend, $status);
1334
 
1335
                    $a = new \stdClass();
1336
                    $a->course = $shortname;
1337
                    $a->role = $this->rolecache[$courseid][$roleid]->name;
1338
                    $this->upt->track('enrolments', get_string('enrolledincourserole', 'enrol_manual', $a), 'info');
1339
                }
1340
            }
1341
 
1342
            // Find group to add to.
1343
            if (!empty($user->{'group'.$i})) {
1344
                // Make sure user is enrolled into course before adding into groups.
1345
                if (!is_enrolled($coursecontext, $user->id)) {
1346
                    $this->upt->track('enrolments', get_string('addedtogroupnotenrolled', '', $user->{'group'.$i}), 'error');
1347
                    continue;
1348
                }
1349
                // Build group cache.
1350
                if (is_null($this->ccache[$shortname]->groups)) {
1351
                    $this->ccache[$shortname]->groups = array();
1352
                    if ($groups = groups_get_all_groups($courseid)) {
1353
                        foreach ($groups as $gid => $group) {
1354
                            $this->ccache[$shortname]->groups[$gid] = new \stdClass();
1355
                            $this->ccache[$shortname]->groups[$gid]->id   = $gid;
1356
                            $this->ccache[$shortname]->groups[$gid]->name = $group->name;
1357
                            if (!is_numeric($group->name)) { // Only non-numeric names are supported!!!
1358
                                $this->ccache[$shortname]->groups[$group->name] = new \stdClass();
1359
                                $this->ccache[$shortname]->groups[$group->name]->id   = $gid;
1360
                                $this->ccache[$shortname]->groups[$group->name]->name = $group->name;
1361
                            }
1362
                        }
1363
                    }
1364
                }
1365
                // Group exists?
1366
                $addgroup = $user->{'group'.$i};
1367
                if (!array_key_exists($addgroup, $this->ccache[$shortname]->groups)) {
1368
                    // If group doesn't exist,  create it.
1369
                    $newgroupdata = new \stdClass();
1370
                    $newgroupdata->name = $addgroup;
1371
                    $newgroupdata->courseid = $this->ccache[$shortname]->id;
1372
                    $newgroupdata->description = '';
1373
                    $gid = groups_create_group($newgroupdata);
1374
                    if ($gid) {
1375
                        $this->ccache[$shortname]->groups[$addgroup] = new \stdClass();
1376
                        $this->ccache[$shortname]->groups[$addgroup]->id   = $gid;
1377
                        $this->ccache[$shortname]->groups[$addgroup]->name = $newgroupdata->name;
1378
                    } else {
1379
                        $this->upt->track('enrolments', get_string('unknowngroup', 'error', s($addgroup)), 'error');
1380
                        continue;
1381
                    }
1382
                }
1383
                $gid   = $this->ccache[$shortname]->groups[$addgroup]->id;
1384
                $gname = $this->ccache[$shortname]->groups[$addgroup]->name;
1385
 
1386
                try {
1387
                    if (groups_add_member($gid, $user->id)) {
1388
                        $this->upt->track('enrolments', get_string('addedtogroup', '', s($gname)), 'info');
1389
                    } else {
1390
                        $this->upt->track('enrolments', get_string('addedtogroupnot', '', s($gname)), 'error');
1391
                    }
1392
                } catch (\moodle_exception $e) {
1393
                    $this->upt->track('enrolments', get_string('addedtogroupnot', '', s($gname)), 'error');
1394
                    continue;
1395
                }
1396
            }
1397
        }
1398
 
1399
        // Warn user about invalid data values.
1400
        if (($invalid = \core_user::validate($user)) !== true) {
1401
            $listseparator = get_string('listsep', 'langconfig') . ' ';
1402
            $this->upt->track('status', get_string('invaliduserdatavalues', 'tool_uploaduser', [
1403
                'username' => s($user->username),
1404
                'values' => implode($listseparator, array_keys($invalid)),
1405
            ]), 'warning');
1406
        }
1407
    }
1408
 
1409
    /**
1410
     * Summary about the whole process (how many users created, skipped, updated, etc)
1411
     *
1412
     * @return array
1413
     */
1414
    public function get_stats() {
1415
        $lines = [];
1416
 
1417
        if ($this->get_operation_type() != UU_USER_UPDATE) {
1418
            $lines[] = get_string('userscreated', 'tool_uploaduser').': '.$this->usersnew;
1419
        }
1420
        if ($this->get_operation_type() == UU_USER_UPDATE or $this->get_operation_type() == UU_USER_ADD_UPDATE) {
1421
            $lines[] = get_string('usersupdated', 'tool_uploaduser').': '.$this->usersupdated;
1422
        }
1423
        if ($this->get_allow_deletes()) {
1424
            $lines[] = get_string('usersdeleted', 'tool_uploaduser').': '.$this->deletes;
1425
            $lines[] = get_string('deleteerrors', 'tool_uploaduser').': '.$this->deleteerrors;
1426
        }
1427
        if ($this->get_allow_renames()) {
1428
            $lines[] = get_string('usersrenamed', 'tool_uploaduser').': '.$this->renames;
1429
            $lines[] = get_string('renameerrors', 'tool_uploaduser').': '.$this->renameerrors;
1430
        }
1431
        if ($usersskipped = $this->usersskipped) {
1432
            $lines[] = get_string('usersskipped', 'tool_uploaduser').': '.$usersskipped;
1433
        }
1434
        $lines[] = get_string('usersweakpassword', 'tool_uploaduser').': '.$this->weakpasswords;
1435
        $lines[] = get_string('errors', 'tool_uploaduser').': '.$this->userserrors;
1436
 
1437
        return $lines;
1438
    }
1439
}