Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 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 array */ 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', 'skype', 147 'msn', 'aim', 'yahoo', 'icq', 'phone1', 'phone2', 'address', 148 'url', 'description', 'descriptionformat', 'password', 149 'auth', // Watch out when changing auth type or using external auth plugins! 150 'oldusername', // Use when renaming users - this is the original username. 151 'suspended', // 1 means suspend user account, 0 means activate user account, nothing means keep as is. 152 'theme', // Define a theme for user when 'allowuserthemes' is enabled. 153 'deleted', // 1 means delete user 154 'mnethostid', // Can not be used for adding, updating or deleting of users - only for enrolments, 155 // groups, cohorts and suspending. 156 'interests', 157 ); 158 // Include all name fields. 159 $this->standardfields = array_merge($this->standardfields, get_all_user_name_fields()); 160 } 161 162 /** 163 * Profile fields 164 */ 165 protected function find_profile_fields(): void { 166 global $DB; 167 $this->allprofilefields = $DB->get_records('user_info_field'); 168 $this->profilefields = []; 169 if ($proffields = $this->allprofilefields) { 170 foreach ($proffields as $key => $proffield) { 171 $profilefieldname = 'profile_field_'.$proffield->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 $proffields = $this->allprofilefields; 534 foreach ($this->profilefields as $field) { 535 if (isset($user->$field)) { 536 continue; 537 } 538 if (isset($this->formdata->$field)) { 539 // Process templates. 540 $user->$field = uu_process_template($this->formdata->$field, $user); 541 542 // Form contains key and later code expects value. 543 // Convert key to value for required profile fields. 544 require_once($CFG->dirroot.'/user/profile/field/'.$proffields[$field]->datatype.'/field.class.php'); 545 $profilefieldclass = 'profile_field_'.$proffields[$field]->datatype; 546 $profilefield = new $profilefieldclass($proffields[$field]->id); 547 if (method_exists($profilefield, 'convert_external_data')) { 548 $user->$field = $profilefield->edit_save_data_preprocess($user->$field, null); 549 } 550 551 $formdefaults[$field] = true; 552 } 553 } 554 } 555 556 // Delete user. 557 if (!empty($user->deleted)) { 558 if (!$this->get_allow_deletes() or $remoteuser or 559 !has_capability('moodle/user:delete', context_system::instance())) { 560 $this->usersskipped++; 561 $this->upt->track('status', get_string('usernotdeletedoff', 'error'), 'warning'); 562 return; 563 } 564 if ($existinguser) { 565 if (is_siteadmin($existinguser->id)) { 566 $this->upt->track('status', get_string('usernotdeletedadmin', 'error'), 'error'); 567 $this->deleteerrors++; 568 return; 569 } 570 if (delete_user($existinguser)) { 571 $this->upt->track('status', get_string('userdeleted', 'tool_uploaduser')); 572 $this->deletes++; 573 } else { 574 $this->upt->track('status', get_string('usernotdeletederror', 'error'), 'error'); 575 $this->deleteerrors++; 576 } 577 } else { 578 $this->upt->track('status', get_string('usernotdeletedmissing', 'error'), 'error'); 579 $this->deleteerrors++; 580 } 581 return; 582 } 583 // We do not need the deleted flag anymore. 584 unset($user->deleted); 585 586 // Renaming requested? 587 if (!empty($user->oldusername) ) { 588 if (!$this->get_allow_renames()) { 589 $this->usersskipped++; 590 $this->upt->track('status', get_string('usernotrenamedoff', 'error'), 'warning'); 591 return; 592 } 593 594 if ($existinguser) { 595 $this->upt->track('status', get_string('usernotrenamedexists', 'error'), 'error'); 596 $this->renameerrors++; 597 return; 598 } 599 600 if ($user->username === 'guest') { 601 $this->upt->track('status', get_string('guestnoeditprofileother', 'error'), 'error'); 602 $this->renameerrors++; 603 return; 604 } 605 606 if ($this->get_normalise_user_names()) { 607 $oldusername = \core_user::clean_field($user->oldusername, 'username'); 608 } else { 609 $oldusername = $user->oldusername; 610 } 611 612 // No guessing when looking for old username, it must be exact match. 613 if ($olduser = $DB->get_record('user', 614 ['username' => $oldusername, 'mnethostid' => $CFG->mnet_localhost_id])) { 615 $this->upt->track('id', $olduser->id, 'normal', false); 616 if (is_siteadmin($olduser->id)) { 617 $this->upt->track('status', get_string('usernotrenamedadmin', 'error'), 'error'); 618 $this->renameerrors++; 619 return; 620 } 621 $DB->set_field('user', 'username', $user->username, ['id' => $olduser->id]); 622 $this->upt->track('username', '', 'normal', false); // Clear previous. 623 $this->upt->track('username', s($oldusername).'-->'.s($user->username), 'info'); 624 $this->upt->track('status', get_string('userrenamed', 'tool_uploaduser')); 625 $this->renames++; 626 } else { 627 $this->upt->track('status', get_string('usernotrenamedmissing', 'error'), 'error'); 628 $this->renameerrors++; 629 return; 630 } 631 $existinguser = $olduser; 632 $existinguser->username = $user->username; 633 } 634 635 // Can we process with update or insert? 636 $skip = false; 637 switch ($this->get_operation_type()) { 638 case UU_USER_ADDNEW: 639 if ($existinguser) { 640 $this->usersskipped++; 641 $this->upt->track('status', get_string('usernotaddedregistered', 'error'), 'warning'); 642 $skip = true; 643 } 644 break; 645 646 case UU_USER_ADDINC: 647 if ($existinguser) { 648 // This should not happen! 649 $this->upt->track('status', get_string('usernotaddederror', 'error'), 'error'); 650 $this->userserrors++; 651 $skip = true; 652 } 653 break; 654 655 case UU_USER_ADD_UPDATE: 656 break; 657 658 case UU_USER_UPDATE: 659 if (!$existinguser) { 660 $this->usersskipped++; 661 $this->upt->track('status', get_string('usernotupdatednotexists', 'error'), 'warning'); 662 $skip = true; 663 } 664 break; 665 666 default: 667 // Unknown type. 668 $skip = true; 669 } 670 671 if ($skip) { 672 return; 673 } 674 675 if ($existinguser) { 676 $user->id = $existinguser->id; 677 678 $this->upt->track('username', \html_writer::link( 679 new \moodle_url('/user/profile.php', ['id' => $existinguser->id]), s($existinguser->username)), 'normal', false); 680 $this->upt->track('suspended', $this->get_string_yes_no($existinguser->suspended) , 'normal', false); 681 $this->upt->track('auth', $existinguser->auth, 'normal', false); 682 683 if (is_siteadmin($user->id)) { 684 $this->upt->track('status', get_string('usernotupdatedadmin', 'error'), 'error'); 685 $this->userserrors++; 686 return; 687 } 688 689 $existinguser->timemodified = time(); 690 // Do NOT mess with timecreated or firstaccess here! 691 692 // Load existing profile data. 693 profile_load_data($existinguser); 694 695 $doupdate = false; 696 $dologout = false; 697 698 if ($this->get_update_type() != UU_UPDATE_NOCHANGES and !$remoteuser) { 699 if (!empty($user->auth) and $user->auth !== $existinguser->auth) { 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