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