Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

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