MDL-70143 tool_uploaduser: fix wrong cache reference
[moodle.git] / admin / tool / uploaduser / classes / process.php
CommitLineData
2a4d1c64
MG
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 array */
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', 'skype',
146 'msn', 'aim', 'yahoo', 'icq', 'phone1', 'phone2', 'address',
147 'url', 'description', 'descriptionformat', 'password',
148 'auth', // Watch out when changing auth type or using external auth plugins!
149 'oldusername', // Use when renaming users - this is the original username.
150 'suspended', // 1 means suspend user account, 0 means activate user account, nothing means keep as is.
151 'theme', // Define a theme for user when 'allowuserthemes' is enabled.
152 'deleted', // 1 means delete user
153 'mnethostid', // Can not be used for adding, updating or deleting of users - only for enrolments,
154 // groups, cohorts and suspending.
155 'interests',
156 );
157 // Include all name fields.
158 $this->standardfields = array_merge($this->standardfields, get_all_user_name_fields());
159 }
160
161 /**
162 * Profile fields
163 */
164 protected function find_profile_fields(): void {
165 global $DB;
166 $this->allprofilefields = $DB->get_records('user_info_field');
167 $this->profilefields = [];
168 if ($proffields = $this->allprofilefields) {
169 foreach ($proffields as $key => $proffield) {
170 $profilefieldname = 'profile_field_'.$proffield->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 $proffields = $this->allprofilefields;
533 foreach ($this->profilefields as $field) {
534 if (isset($user->$field)) {
535 continue;
536 }
537 if (isset($this->formdata->$field)) {
538 // Process templates.
539 $user->$field = uu_process_template($this->formdata->$field, $user);
540
541 // Form contains key and later code expects value.
542 // Convert key to value for required profile fields.
543 require_once($CFG->dirroot.'/user/profile/field/'.$proffields[$field]->datatype.'/field.class.php');
544 $profilefieldclass = 'profile_field_'.$proffields[$field]->datatype;
545 $profilefield = new $profilefieldclass($proffields[$field]->id);
546 if (method_exists($profilefield, 'convert_external_data')) {
547 $user->$field = $profilefield->edit_save_data_preprocess($user->$field, null);
548 }
549
550 $formdefaults[$field] = true;
551 }
552 }
553 }
554
555 // Delete user.
556 if (!empty($user->deleted)) {
557 if (!$this->get_allow_deletes() or $remoteuser) {
558 $this->usersskipped++;
559 $this->upt->track('status', get_string('usernotdeletedoff', 'error'), 'warning');
560 return;
561 }
562 if ($existinguser) {
563 if (is_siteadmin($existinguser->id)) {
564 $this->upt->track('status', get_string('usernotdeletedadmin', 'error'), 'error');
565 $this->deleteerrors++;
566 return;
567 }
568 if (delete_user($existinguser)) {
569 $this->upt->track('status', get_string('userdeleted', 'tool_uploaduser'));
570 $this->deletes++;
571 } else {
572 $this->upt->track('status', get_string('usernotdeletederror', 'error'), 'error');
573 $this->deleteerrors++;
574 }
575 } else {
576 $this->upt->track('status', get_string('usernotdeletedmissing', 'error'), 'error');
577 $this->deleteerrors++;
578 }
579 return;
580 }
581 // We do not need the deleted flag anymore.
582 unset($user->deleted);
583
584 // Renaming requested?
585 if (!empty($user->oldusername) ) {
586 if (!$this->get_allow_renames()) {
587 $this->usersskipped++;
588 $this->upt->track('status', get_string('usernotrenamedoff', 'error'), 'warning');
589 return;
590 }
591
592 if ($existinguser) {
593 $this->upt->track('status', get_string('usernotrenamedexists', 'error'), 'error');
594 $this->renameerrors++;
595 return;
596 }
597
598 if ($user->username === 'guest') {
599 $this->upt->track('status', get_string('guestnoeditprofileother', 'error'), 'error');
600 $this->renameerrors++;
601 return;
602 }
603
604 if ($this->get_normalise_user_names()) {
605 $oldusername = \core_user::clean_field($user->oldusername, 'username');
606 } else {
607 $oldusername = $user->oldusername;
608 }
609
610 // No guessing when looking for old username, it must be exact match.
611 if ($olduser = $DB->get_record('user',
612 ['username' => $oldusername, 'mnethostid' => $CFG->mnet_localhost_id])) {
613 $this->upt->track('id', $olduser->id, 'normal', false);
614 if (is_siteadmin($olduser->id)) {
615 $this->upt->track('status', get_string('usernotrenamedadmin', 'error'), 'error');
616 $this->renameerrors++;
617 return;
618 }
619 $DB->set_field('user', 'username', $user->username, ['id' => $olduser->id]);
620 $this->upt->track('username', '', 'normal', false); // Clear previous.
621 $this->upt->track('username', s($oldusername).'-->'.s($user->username), 'info');
622 $this->upt->track('status', get_string('userrenamed', 'tool_uploaduser'));
623 $this->renames++;
624 } else {
625 $this->upt->track('status', get_string('usernotrenamedmissing', 'error'), 'error');
626 $this->renameerrors++;
627 return;
628 }
629 $existinguser = $olduser;
630 $existinguser->username = $user->username;
631 }
632
633 // Can we process with update or insert?
634 $skip = false;
635 switch ($this->get_operation_type()) {
636 case UU_USER_ADDNEW:
637 if ($existinguser) {
638 $this->usersskipped++;
639 $this->upt->track('status', get_string('usernotaddedregistered', 'error'), 'warning');
640 $skip = true;
641 }
642 break;
643
644 case UU_USER_ADDINC:
645 if ($existinguser) {
646 // This should not happen!
647 $this->upt->track('status', get_string('usernotaddederror', 'error'), 'error');
648 $this->userserrors++;
649 $skip = true;
650 }
651 break;
652
653 case UU_USER_ADD_UPDATE:
654 break;
655
656 case UU_USER_UPDATE:
657 if (!$existinguser) {
658 $this->usersskipped++;
659 $this->upt->track('status', get_string('usernotupdatednotexists', 'error'), 'warning');
660 $skip = true;
661 }
662 break;
663
664 default:
665 // Unknown type.
666 $skip = true;
667 }
668
669 if ($skip) {
670 return;
671 }
672
673 if ($existinguser) {
674 $user->id = $existinguser->id;
675
676 $this->upt->track('username', \html_writer::link(
677 new \moodle_url('/user/profile.php', ['id' => $existinguser->id]), s($existinguser->username)), 'normal', false);
678 $this->upt->track('suspended', $this->get_string_yes_no($existinguser->suspended) , 'normal', false);
679 $this->upt->track('auth', $existinguser->auth, 'normal', false);
680
681 if (is_siteadmin($user->id)) {
682 $this->upt->track('status', get_string('usernotupdatedadmin', 'error'), 'error');
683 $this->userserrors++;
684 return;
685 }
686
687 $existinguser->timemodified = time();
688 // Do NOT mess with timecreated or firstaccess here!
689
690 // Load existing profile data.
691 profile_load_data($existinguser);
692
693 $doupdate = false;
694 $dologout = false;
695
696 if ($this->get_update_type() != UU_UPDATE_NOCHANGES and !$remoteuser) {
697 if (!empty($user->auth) and $user->auth !== $existinguser->auth) {
698 $this->upt->track('auth', s($existinguser->auth).'-->'.s($user->auth), 'info', false);
699 $existinguser->auth = $user->auth;
700 if (!isset($this->supportedauths[$user->auth])) {
701 $this->upt->track('auth', get_string('userauthunsupported', 'error'), 'warning');
702 }
703 $doupdate = true;
704 if ($existinguser->auth === 'nologin') {
705 $dologout = true;
706 }
707 }
708 $allcolumns = array_merge($this->standardfields, $this->profilefields);
709 foreach ($allcolumns as $column) {
710 if ($column === 'username' or $column === 'password' or $column === 'auth' or $column === 'suspended') {
711 // These can not be changed here.
712 continue;
713 }
714 if (!property_exists($user, $column) or !property_exists($existinguser, $column)) {
715 continue;
716 }
717 if ($this->get_update_type() == UU_UPDATE_MISSING) {
718 if (!is_null($existinguser->$column) and $existinguser->$column !== '') {
719 continue;
720 }
721 } else if ($this->get_update_type() == UU_UPDATE_ALLOVERRIDE) {
722 // We override everything.
723 null;
724 } else if ($this->get_update_type() == UU_UPDATE_FILEOVERRIDE) {
725 if (!empty($formdefaults[$column])) {
726 // Do not override with form defaults.
727 continue;
728 }
729 }
730 if ($existinguser->$column !== $user->$column) {
731 if ($column === 'email') {
732 $select = $DB->sql_like('email', ':email', false, true, false, '|');
733 $params = array('email' => $DB->sql_like_escape($user->email, '|'));
734 if ($DB->record_exists_select('user', $select , $params)) {
735
736 $changeincase = \core_text::strtolower($existinguser->$column) === \core_text::strtolower(
737 $user->$column);
738
739 if ($changeincase) {
740 // If only case is different then switch to lower case and carry on.
741 $user->$column = \core_text::strtolower($user->$column);
742 continue;
743 } else if (!$this->get_allow_email_duplicates()) {
744 $this->upt->track('email', get_string('useremailduplicate', 'error'), 'error');
745 $this->upt->track('status', get_string('usernotupdatederror', 'error'), 'error');
746 $this->userserrors++;
747 return;
748 } else {
749 $this->upt->track('email', get_string('useremailduplicate', 'error'), 'warning');
750 }
751 }
752 if (!validate_email($user->email)) {
753 $this->upt->track('email', get_string('invalidemail'), 'warning');
754 }
755 }
756
757 if ($column === 'lang') {
758 if (empty($user->lang)) {
759 // Do not change to not-set value.
760 continue;
761 } else if (\core_user::clean_field($user->lang, 'lang') === '') {
762 $this->upt->track('status', get_string('cannotfindlang', 'error', $user->lang), 'warning');
763 continue;
764 }
765 }
766
767 if (in_array($column, $this->upt->columns)) {
768 $this->upt->track($column, s($existinguser->$column).'-->'.s($user->$column), 'info', false);
769 }
770 $existinguser->$column = $user->$column;
771 $doupdate = true;
772 }
773 }
774 }
775
776 try {
777 $auth = get_auth_plugin($existinguser->auth);
778 } catch (\Exception $e) {
779 $this->upt->track('auth', get_string('userautherror', 'error', s($existinguser->auth)), 'error');
780 $this->upt->track('status', get_string('usernotupdatederror', 'error'), 'error');
781 $this->userserrors++;
782 return;
783 }
784 $isinternalauth = $auth->is_internal();
785
786 // Deal with suspending and activating of accounts.
787 if ($this->get_allow_suspends() and isset($user->suspended) and $user->suspended !== '') {
788 $user->suspended = $user->suspended ? 1 : 0;
789 if ($existinguser->suspended != $user->suspended) {
790 $this->upt->track('suspended', '', 'normal', false);
791 $this->upt->track('suspended',
792 $this->get_string_yes_no($existinguser->suspended).'-->'.$this->get_string_yes_no($user->suspended),
793 'info', false);
794 $existinguser->suspended = $user->suspended;
795 $doupdate = true;
796 if ($existinguser->suspended) {
797 $dologout = true;
798 }
799 }
800 }
801
802 // Changing of passwords is a special case
803 // do not force password changes for external auth plugins!
804 $oldpw = $existinguser->password;
805
806 if ($remoteuser) {
807 // Do not mess with passwords of remote users.
808 null;
809 } else if (!$isinternalauth) {
810 $existinguser->password = AUTH_PASSWORD_NOT_CACHED;
811 $this->upt->track('password', '-', 'normal', false);
812 // Clean up prefs.
813 unset_user_preference('create_password', $existinguser);
814 unset_user_preference('auth_forcepasswordchange', $existinguser);
815
816 } else if (!empty($user->password)) {
817 if ($this->get_update_passwords()) {
818 // Check for passwords that we want to force users to reset next
819 // time they log in.
820 $errmsg = null;
821 $weak = !check_password_policy($user->password, $errmsg, $user);
822 if ($this->get_reset_passwords() == UU_PWRESET_ALL or
823 ($this->get_reset_passwords() == UU_PWRESET_WEAK and $weak)) {
824 if ($weak) {
825 $this->weakpasswords++;
826 $this->upt->track('password', get_string('invalidpasswordpolicy', 'error'), 'warning');
827 }
828 set_user_preference('auth_forcepasswordchange', 1, $existinguser);
829 } else {
830 unset_user_preference('auth_forcepasswordchange', $existinguser);
831 }
832 unset_user_preference('create_password', $existinguser); // No need to create password any more.
833
834 // Use a low cost factor when generating bcrypt hash otherwise
835 // hashing would be slow when uploading lots of users. Hashes
836 // will be automatically updated to a higher cost factor the first
837 // time the user logs in.
838 $existinguser->password = hash_internal_user_password($user->password, true);
839 $this->upt->track('password', $user->password, 'normal', false);
840 } else {
841 // Do not print password when not changed.
842 $this->upt->track('password', '', 'normal', false);
843 }
844 }
845
846 if ($doupdate or $existinguser->password !== $oldpw) {
847 // We want only users that were really updated.
848 user_update_user($existinguser, false, false);
849
850 $this->upt->track('status', get_string('useraccountupdated', 'tool_uploaduser'));
851 $this->usersupdated++;
852
853 if (!$remoteuser) {
854 // Pre-process custom profile menu fields data from csv file.
855 $existinguser = uu_pre_process_custom_profile_data($existinguser);
856 // Save custom profile fields data from csv file.
857 profile_save_data($existinguser);
858 }
859
860 if ($this->get_bulk() == UU_BULK_UPDATED or $this->get_bulk() == UU_BULK_ALL) {
861 if (!in_array($user->id, $SESSION->bulk_users)) {
862 $SESSION->bulk_users[] = $user->id;
863 }
864 }
865
866 // Trigger event.
867 \core\event\user_updated::create_from_userid($existinguser->id)->trigger();
868
869 } else {
870 // No user information changed.
871 $this->upt->track('status', get_string('useraccountuptodate', 'tool_uploaduser'));
872 $this->usersuptodate++;
873
874 if ($this->get_bulk() == UU_BULK_ALL) {
875 if (!in_array($user->id, $SESSION->bulk_users)) {
876 $SESSION->bulk_users[] = $user->id;
877 }
878 }
879 }
880
881 if ($dologout) {
882 \core\session\manager::kill_user_sessions($existinguser->id);
883 }
884
885 } else {
886 // Save the new user to the database.
887 $user->confirmed = 1;
888 $user->timemodified = time();
889 $user->timecreated = time();
890 $user->mnethostid = $CFG->mnet_localhost_id; // We support ONLY local accounts here, sorry.
891
892 if (!isset($user->suspended) or $user->suspended === '') {
893 $user->suspended = 0;
894 } else {
895 $user->suspended = $user->suspended ? 1 : 0;
896 }
897 $this->upt->track('suspended', $this->get_string_yes_no($user->suspended), 'normal', false);
898
899 if (empty($user->auth)) {
900 $user->auth = 'manual';
901 }
902 $this->upt->track('auth', $user->auth, 'normal', false);
903
904 // Do not insert record if new auth plugin does not exist!
905 try {
906 $auth = get_auth_plugin($user->auth);
907 } catch (\Exception $e) {
908 $this->upt->track('auth', get_string('userautherror', 'error', s($user->auth)), 'error');
909 $this->upt->track('status', get_string('usernotaddederror', 'error'), 'error');
910 $this->userserrors++;
911 return;
912 }
913 if (!isset($this->supportedauths[$user->auth])) {
914 $this->upt->track('auth', get_string('userauthunsupported', 'error'), 'warning');
915 }
916
917 $isinternalauth = $auth->is_internal();
918
919 if (empty($user->email)) {
920 $this->upt->track('email', get_string('invalidemail'), 'error');
921 $this->upt->track('status', get_string('usernotaddederror', 'error'), 'error');
922 $this->userserrors++;
923 return;
924
925 } else if ($DB->record_exists('user', ['email' => $user->email])) {
926 if (!$this->get_allow_email_duplicates()) {
927 $this->upt->track('email', get_string('useremailduplicate', 'error'), 'error');
928 $this->upt->track('status', get_string('usernotaddederror', 'error'), 'error');
929 $this->userserrors++;
930 return;
931 } else {
932 $this->upt->track('email', get_string('useremailduplicate', 'error'), 'warning');
933 }
934 }
935 if (!validate_email($user->email)) {
936 $this->upt->track('email', get_string('invalidemail'), 'warning');
937 }
938
939 if (empty($user->lang)) {
940 $user->lang = '';
941 } else if (\core_user::clean_field($user->lang, 'lang') === '') {
942 $this->upt->track('status', get_string('cannotfindlang', 'error', $user->lang), 'warning');
943 $user->lang = '';
944 }
945
946 $forcechangepassword = false;
947
948 if ($isinternalauth) {
949 if (empty($user->password)) {
950 if ($this->get_create_paswords()) {
951 $user->password = 'to be generated';
952 $this->upt->track('password', '', 'normal', false);
953 $this->upt->track('password', get_string('uupasswordcron', 'tool_uploaduser'), 'warning', false);
954 } else {
955 $this->upt->track('password', '', 'normal', false);
956 $this->upt->track('password', get_string('missingfield', 'error', 'password'), 'error');
957 $this->upt->track('status', get_string('usernotaddederror', 'error'), 'error');
958 $this->userserrors++;
959 return;
960 }
961 } else {
962 $errmsg = null;
963 $weak = !check_password_policy($user->password, $errmsg, $user);
964 if ($this->get_reset_passwords() == UU_PWRESET_ALL or
965 ($this->get_reset_passwords() == UU_PWRESET_WEAK and $weak)) {
966 if ($weak) {
967 $this->weakpasswords++;
968 $this->upt->track('password', get_string('invalidpasswordpolicy', 'error'), 'warning');
969 }
970 $forcechangepassword = true;
971 }
972 // Use a low cost factor when generating bcrypt hash otherwise
973 // hashing would be slow when uploading lots of users. Hashes
974 // will be automatically updated to a higher cost factor the first
975 // time the user logs in.
976 $user->password = hash_internal_user_password($user->password, true);
977 }
978 } else {
979 $user->password = AUTH_PASSWORD_NOT_CACHED;
980 $this->upt->track('password', '-', 'normal', false);
981 }
982
983 $user->id = user_create_user($user, false, false);
984 $this->upt->track('username', \html_writer::link(
985 new \moodle_url('/user/profile.php', ['id' => $user->id]), s($user->username)), 'normal', false);
986
987 // Pre-process custom profile menu fields data from csv file.
988 $user = uu_pre_process_custom_profile_data($user);
989 // Save custom profile fields data.
990 profile_save_data($user);
991
992 if ($forcechangepassword) {
993 set_user_preference('auth_forcepasswordchange', 1, $user);
994 }
995 if ($user->password === 'to be generated') {
996 set_user_preference('create_password', 1, $user);
997 }
998
999 // Trigger event.
1000 \core\event\user_created::create_from_userid($user->id)->trigger();
1001
1002 $this->upt->track('status', get_string('newuser'));
1003 $this->upt->track('id', $user->id, 'normal', false);
1004 $this->usersnew++;
1005
1006 // Make sure user context exists.
1007 \context_user::instance($user->id);
1008
1009 if ($this->get_bulk() == UU_BULK_NEW or $this->get_bulk() == UU_BULK_ALL) {
1010 if (!in_array($user->id, $SESSION->bulk_users)) {
1011 $SESSION->bulk_users[] = $user->id;
1012 }
1013 }
1014 }
1015
1016 // Update user interests.
1017 if (isset($user->interests) && strval($user->interests) !== '') {
1018 useredit_update_interests($user, preg_split('/\s*,\s*/', $user->interests, -1, PREG_SPLIT_NO_EMPTY));
1019 }
1020
1021 // Add to cohort first, it might trigger enrolments indirectly - do NOT create cohorts here!
1022 foreach ($this->get_file_columns() as $column) {
1023 if (!preg_match('/^cohort\d+$/', $column)) {
1024 continue;
1025 }
1026
1027 if (!empty($user->$column)) {
1028 $addcohort = $user->$column;
1029 if (!isset($this->cohorts[$addcohort])) {
1030 if (is_number($addcohort)) {
1031 // Only non-numeric idnumbers!
1032 $cohort = $DB->get_record('cohort', ['id' => $addcohort]);
1033 } else {
1034 $cohort = $DB->get_record('cohort', ['idnumber' => $addcohort]);
1035 if (empty($cohort) && has_capability('moodle/cohort:manage', \context_system::instance())) {
1036 // Cohort was not found. Create a new one.
1037 $cohortid = cohort_add_cohort((object)array(
1038 'idnumber' => $addcohort,
1039 'name' => $addcohort,
1040 'contextid' => \context_system::instance()->id
1041 ));
1042 $cohort = $DB->get_record('cohort', ['id' => $cohortid]);
1043 }
1044 }
1045
1046 if (empty($cohort)) {
1047 $this->cohorts[$addcohort] = get_string('unknowncohort', 'core_cohort', s($addcohort));
1048 } else if (!empty($cohort->component)) {
1049 // Cohorts synchronised with external sources must not be modified!
1050 $this->cohorts[$addcohort] = get_string('external', 'core_cohort');
1051 } else {
1052 $this->cohorts[$addcohort] = $cohort;
1053 }
1054 }
1055
1056 if (is_object($this->cohorts[$addcohort])) {
1057 $cohort = $this->cohorts[$addcohort];
1058 if (!$DB->record_exists('cohort_members', ['cohortid' => $cohort->id, 'userid' => $user->id])) {
1059 cohort_add_member($cohort->id, $user->id);
1060 // We might add special column later, for now let's abuse enrolments.
1061 $this->upt->track('enrolments', get_string('useradded', 'core_cohort', s($cohort->name)), 'info');
1062 }
1063 } else {
1064 // Error message.
1065 $this->upt->track('enrolments', $this->cohorts[$addcohort], 'error');
1066 }
1067 }
1068 }
1069
1070 // Find course enrolments, groups, roles/types and enrol periods
1071 // this is again a special case, we always do this for any updated or created users.
1072 foreach ($this->get_file_columns() as $column) {
1073 if (preg_match('/^sysrole\d+$/', $column)) {
1074
1075 if (!empty($user->$column)) {
1076 $sysrolename = $user->$column;
1077 if ($sysrolename[0] == '-') {
1078 $removing = true;
1079 $sysrolename = substr($sysrolename, 1);
1080 } else {
1081 $removing = false;
1082 }
1083
1084 if (array_key_exists($sysrolename, $this->sysrolecache)) {
1085 $sysroleid = $this->sysrolecache[$sysrolename]->id;
1086 } else {
1087 $this->upt->track('enrolments', get_string('unknownrole', 'error', s($sysrolename)), 'error');
1088 continue;
1089 }
1090
1091 if ($removing) {
1092 if (user_has_role_assignment($user->id, $sysroleid, SYSCONTEXTID)) {
1093 role_unassign($sysroleid, $user->id, SYSCONTEXTID);
1094 $this->upt->track('enrolments', get_string('unassignedsysrole',
1095 'tool_uploaduser', $this->sysrolecache[$sysroleid]->name), 'info');
1096 }
1097 } else {
1098 if (!user_has_role_assignment($user->id, $sysroleid, SYSCONTEXTID)) {
1099 role_assign($sysroleid, $user->id, SYSCONTEXTID);
1100 $this->upt->track('enrolments', get_string('assignedsysrole',
1101 'tool_uploaduser', $this->sysrolecache[$sysroleid]->name), 'info');
1102 }
1103 }
1104 }
1105
1106 continue;
1107 }
1108 if (!preg_match('/^course\d+$/', $column)) {
1109 continue;
1110 }
1111 $i = substr($column, 6);
1112
1113 if (empty($user->{'course'.$i})) {
1114 continue;
1115 }
1116 $shortname = $user->{'course'.$i};
1117 if (!array_key_exists($shortname, $this->ccache)) {
1118 if (!$course = $DB->get_record('course', ['shortname' => $shortname], 'id, shortname')) {
1119 $this->upt->track('enrolments', get_string('unknowncourse', 'error', s($shortname)), 'error');
1120 continue;
1121 }
a350376b
MG
1122 $this->ccache[$shortname] = $course;
1123 $this->ccache[$shortname]->groups = null;
2a4d1c64 1124 }
a350376b 1125 $courseid = $this->ccache[$shortname]->id;
2a4d1c64
MG
1126 $coursecontext = \context_course::instance($courseid);
1127 if (!isset($this->manualcache[$courseid])) {
1128 $this->manualcache[$courseid] = false;
1129 if ($this->manualenrol) {
1130 if ($instances = enrol_get_instances($courseid, false)) {
1131 foreach ($instances as $instance) {
1132 if ($instance->enrol === 'manual') {
1133 $this->manualcache[$courseid] = $instance;
1134 break;
1135 }
1136 }
1137 }
1138 }
1139 }
1140
1141 if ($courseid == SITEID) {
1142 // Technically frontpage does not have enrolments, but only role assignments,
1143 // let's not invent new lang strings here for this rarely used feature.
1144
1145 if (!empty($user->{'role'.$i})) {
1146 $rolename = $user->{'role'.$i};
1147 if (array_key_exists($rolename, $this->rolecache)) {
1148 $roleid = $this->rolecache[$rolename]->id;
1149 } else {
1150 $this->upt->track('enrolments', get_string('unknownrole', 'error', s($rolename)), 'error');
1151 continue;
1152 }
1153
1154 role_assign($roleid, $user->id, \context_course::instance($courseid));
1155
1156 $a = new \stdClass();
1157 $a->course = $shortname;
1158 $a->role = $this->rolecache[$roleid]->name;
1159 $this->upt->track('enrolments', get_string('enrolledincourserole', 'enrol_manual', $a), 'info');
1160 }
1161
1162 } else if ($this->manualenrol and $this->manualcache[$courseid]) {
1163
1164 // Find role.
1165 $roleid = false;
1166 if (!empty($user->{'role'.$i})) {
1167 $rolename = $user->{'role'.$i};
1168 if (array_key_exists($rolename, $this->rolecache)) {
1169 $roleid = $this->rolecache[$rolename]->id;
1170 } else {
1171 $this->upt->track('enrolments', get_string('unknownrole', 'error', s($rolename)), 'error');
1172 continue;
1173 }
1174
1175 } else if (!empty($user->{'type'.$i})) {
1176 // If no role, then find "old" enrolment type.
1177 $addtype = $user->{'type'.$i};
1178 if ($addtype < 1 or $addtype > 3) {
1179 $this->upt->track('enrolments', get_string('error').': typeN = 1|2|3', 'error');
1180 continue;
1181 } else if (empty($this->formdata->{'uulegacy'.$addtype})) {
1182 continue;
1183 } else {
1184 $roleid = $this->formdata->{'uulegacy'.$addtype};
1185 }
1186 } else {
1187 // No role specified, use the default from manual enrol plugin.
1188 $roleid = $this->manualcache[$courseid]->roleid;
1189 }
1190
1191 if ($roleid) {
1192 // Find duration and/or enrol status.
1193 $timeend = 0;
1194 $timestart = $this->today;
1195 $status = null;
1196
1197 if (isset($user->{'enrolstatus'.$i})) {
1198 $enrolstatus = $user->{'enrolstatus'.$i};
1199 if ($enrolstatus == '') {
1200 $status = null;
1201 } else if ($enrolstatus === (string)ENROL_USER_ACTIVE) {
1202 $status = ENROL_USER_ACTIVE;
1203 } else if ($enrolstatus === (string)ENROL_USER_SUSPENDED) {
1204 $status = ENROL_USER_SUSPENDED;
1205 } else {
1206 debugging('Unknown enrolment status.');
1207 }
1208 }
1209
1210 if (!empty($user->{'enroltimestart'.$i})) {
1211 $parsedtimestart = strtotime($user->{'enroltimestart'.$i});
1212 if ($parsedtimestart !== false) {
1213 $timestart = $parsedtimestart;
1214 }
1215 }
1216
1217 if (!empty($user->{'enrolperiod'.$i})) {
1218 $duration = (int)$user->{'enrolperiod'.$i} * 60 * 60 * 24; // Convert days to seconds.
1219 if ($duration > 0) { // Sanity check.
1220 $timeend = $timestart + $duration;
1221 }
1222 } else if ($this->manualcache[$courseid]->enrolperiod > 0) {
1223 $timeend = $timestart + $this->manualcache[$courseid]->enrolperiod;
1224 }
1225
1226 $this->manualenrol->enrol_user($this->manualcache[$courseid], $user->id, $roleid,
1227 $timestart, $timeend, $status);
1228
1229 $a = new \stdClass();
1230 $a->course = $shortname;
1231 $a->role = $this->rolecache[$roleid]->name;
1232 $this->upt->track('enrolments', get_string('enrolledincourserole', 'enrol_manual', $a), 'info');
1233 }
1234 }
1235
1236 // Find group to add to.
1237 if (!empty($user->{'group'.$i})) {
1238 // Make sure user is enrolled into course before adding into groups.
1239 if (!is_enrolled($coursecontext, $user->id)) {
1240 $this->upt->track('enrolments', get_string('addedtogroupnotenrolled', '', $user->{'group'.$i}), 'error');
1241 continue;
1242 }
1243 // Build group cache.
a350376b
MG
1244 if (is_null($this->ccache[$shortname]->groups)) {
1245 $this->ccache[$shortname]->groups = array();
2a4d1c64
MG
1246 if ($groups = groups_get_all_groups($courseid)) {
1247 foreach ($groups as $gid => $group) {
a350376b
MG
1248 $this->ccache[$shortname]->groups[$gid] = new \stdClass();
1249 $this->ccache[$shortname]->groups[$gid]->id = $gid;
1250 $this->ccache[$shortname]->groups[$gid]->name = $group->name;
2a4d1c64 1251 if (!is_numeric($group->name)) { // Only non-numeric names are supported!!!
a350376b
MG
1252 $this->ccache[$shortname]->groups[$group->name] = new \stdClass();
1253 $this->ccache[$shortname]->groups[$group->name]->id = $gid;
1254 $this->ccache[$shortname]->groups[$group->name]->name = $group->name;
2a4d1c64
MG
1255 }
1256 }
1257 }
1258 }
1259 // Group exists?
1260 $addgroup = $user->{'group'.$i};
a350376b 1261 if (!array_key_exists($addgroup, $this->ccache[$shortname]->groups)) {
2a4d1c64
MG
1262 // If group doesn't exist, create it.
1263 $newgroupdata = new \stdClass();
1264 $newgroupdata->name = $addgroup;
a350376b 1265 $newgroupdata->courseid = $this->ccache[$shortname]->id;
2a4d1c64
MG
1266 $newgroupdata->description = '';
1267 $gid = groups_create_group($newgroupdata);
1268 if ($gid) {
a350376b
MG
1269 $this->ccache[$shortname]->groups[$addgroup] = new \stdClass();
1270 $this->ccache[$shortname]->groups[$addgroup]->id = $gid;
1271 $this->ccache[$shortname]->groups[$addgroup]->name = $newgroupdata->name;
2a4d1c64
MG
1272 } else {
1273 $this->upt->track('enrolments', get_string('unknowngroup', 'error', s($addgroup)), 'error');
1274 continue;
1275 }
1276 }
a350376b
MG
1277 $gid = $this->ccache[$shortname]->groups[$addgroup]->id;
1278 $gname = $this->ccache[$shortname]->groups[$addgroup]->name;
2a4d1c64
MG
1279
1280 try {
1281 if (groups_add_member($gid, $user->id)) {
1282 $this->upt->track('enrolments', get_string('addedtogroup', '', s($gname)), 'info');
1283 } else {
1284 $this->upt->track('enrolments', get_string('addedtogroupnot', '', s($gname)), 'error');
1285 }
1286 } catch (\moodle_exception $e) {
1287 $this->upt->track('enrolments', get_string('addedtogroupnot', '', s($gname)), 'error');
1288 continue;
1289 }
1290 }
1291 }
1292 if (($invalid = \core_user::validate($user)) !== true) {
1293 $this->upt->track('status', get_string('invaliduserdata', 'tool_uploaduser', s($user->username)), 'warning');
1294 }
1295 }
1296
1297 /**
1298 * Summary about the whole process (how many users created, skipped, updated, etc)
1299 *
1300 * @return array
1301 */
1302 public function get_stats() {
1303 $lines = [];
1304
1305 if ($this->get_operation_type() != UU_USER_UPDATE) {
1306 $lines[] = get_string('userscreated', 'tool_uploaduser').': '.$this->usersnew;
1307 }
1308 if ($this->get_operation_type() == UU_USER_UPDATE or $this->get_operation_type() == UU_USER_ADD_UPDATE) {
1309 $lines[] = get_string('usersupdated', 'tool_uploaduser').': '.$this->usersupdated;
1310 }
1311 if ($this->get_allow_deletes()) {
1312 $lines[] = get_string('usersdeleted', 'tool_uploaduser').': '.$this->deletes;
1313 $lines[] = get_string('deleteerrors', 'tool_uploaduser').': '.$this->deleteerrors;
1314 }
1315 if ($this->get_allow_renames()) {
1316 $lines[] = get_string('usersrenamed', 'tool_uploaduser').': '.$this->renames;
1317 $lines[] = get_string('renameerrors', 'tool_uploaduser').': '.$this->renameerrors;
1318 }
1319 if ($usersskipped = $this->usersskipped) {
1320 $lines[] = get_string('usersskipped', 'tool_uploaduser').': '.$usersskipped;
1321 }
1322 $lines[] = get_string('usersweakpassword', 'tool_uploaduser').': '.$this->weakpasswords;
1323 $lines[] = get_string('errors', 'tool_uploaduser').': '.$this->userserrors;
1324
1325 return $lines;
1326 }
1327}