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