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