Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]

   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   * Bulk user registration functions
  19   *
  20   * @package    tool
  21   * @subpackage uploaduser
  22   * @copyright  2004 onwards Martin Dougiamas (http://dougiamas.com)
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  define('UU_USER_ADDNEW', 0);
  29  define('UU_USER_ADDINC', 1);
  30  define('UU_USER_ADD_UPDATE', 2);
  31  define('UU_USER_UPDATE', 3);
  32  
  33  define('UU_UPDATE_NOCHANGES', 0);
  34  define('UU_UPDATE_FILEOVERRIDE', 1);
  35  define('UU_UPDATE_ALLOVERRIDE', 2);
  36  define('UU_UPDATE_MISSING', 3);
  37  
  38  define('UU_BULK_NONE', 0);
  39  define('UU_BULK_NEW', 1);
  40  define('UU_BULK_UPDATED', 2);
  41  define('UU_BULK_ALL', 3);
  42  
  43  define('UU_PWRESET_NONE', 0);
  44  define('UU_PWRESET_WEAK', 1);
  45  define('UU_PWRESET_ALL', 2);
  46  
  47  /**
  48   * Tracking of processed users.
  49   *
  50   * This class prints user information into a html table.
  51   *
  52   * @package    core
  53   * @subpackage admin
  54   * @copyright  2007 Petr Skoda  {@link http://skodak.org}
  55   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  56   */
  57  class uu_progress_tracker {
  58      /** @var array */
  59      protected $_row;
  60  
  61      /**
  62       * The columns shown on the table.
  63       * @var array
  64       */
  65      public $columns = [];
  66      /** @var array column headers */
  67      protected $headers = [];
  68  
  69      /**
  70       * uu_progress_tracker constructor.
  71       */
  72      public function __construct() {
  73          $this->headers = [
  74              'status' => get_string('status'),
  75              'line' => get_string('uucsvline', 'tool_uploaduser'),
  76              'id' => 'ID',
  77              'username' => get_string('username'),
  78              'firstname' => get_string('firstname'),
  79              'lastname' => get_string('lastname'),
  80              'email' => get_string('email'),
  81              'password' => get_string('password'),
  82              'auth' => get_string('authentication'),
  83              'enrolments' => get_string('enrolments', 'enrol'),
  84              'suspended' => get_string('suspended', 'auth'),
  85              'theme' => get_string('theme'),
  86              'deleted' => get_string('delete'),
  87          ];
  88          $this->columns = array_keys($this->headers);
  89      }
  90  
  91      /**
  92       * Print table header.
  93       * @return void
  94       */
  95      public function start() {
  96          $ci = 0;
  97          echo '<table id="uuresults" class="generaltable boxaligncenter flexible-wrap" summary="'.get_string('uploadusersresult', 'tool_uploaduser').'">';
  98          echo '<tr class="heading r0">';
  99          foreach ($this->headers as $key => $header) {
 100              echo '<th class="header c'.$ci++.'" scope="col">'.$header.'</th>';
 101          }
 102          echo '</tr>';
 103          $this->_row = null;
 104      }
 105  
 106      /**
 107       * Flush previous line and start a new one.
 108       * @return void
 109       */
 110      public function flush() {
 111          if (empty($this->_row) or empty($this->_row['line']['normal'])) {
 112              // Nothing to print - each line has to have at least number
 113              $this->_row = array();
 114              foreach ($this->columns as $col) {
 115                  $this->_row[$col] = array('normal'=>'', 'info'=>'', 'warning'=>'', 'error'=>'');
 116              }
 117              return;
 118          }
 119          $ci = 0;
 120          $ri = 1;
 121          echo '<tr class="r'.$ri.'">';
 122          foreach ($this->_row as $key=>$field) {
 123              foreach ($field as $type=>$content) {
 124                  if ($field[$type] !== '') {
 125                      $field[$type] = '<span class="uu'.$type.'">'.$field[$type].'</span>';
 126                  } else {
 127                      unset($field[$type]);
 128                  }
 129              }
 130              echo '<td class="cell c'.$ci++.'">';
 131              if (!empty($field)) {
 132                  echo implode('<br />', $field);
 133              } else {
 134                  echo '&nbsp;';
 135              }
 136              echo '</td>';
 137          }
 138          echo '</tr>';
 139          foreach ($this->columns as $col) {
 140              $this->_row[$col] = array('normal'=>'', 'info'=>'', 'warning'=>'', 'error'=>'');
 141          }
 142      }
 143  
 144      /**
 145       * Add tracking info
 146       * @param string $col name of column
 147       * @param string $msg message
 148       * @param string $level 'normal', 'warning' or 'error'
 149       * @param bool $merge true means add as new line, false means override all previous text of the same type
 150       * @return void
 151       */
 152      public function track($col, $msg, $level = 'normal', $merge = true) {
 153          if (empty($this->_row)) {
 154              $this->flush(); //init arrays
 155          }
 156          if (!in_array($col, $this->columns)) {
 157              debugging('Incorrect column:'.$col);
 158              return;
 159          }
 160          if ($merge) {
 161              if ($this->_row[$col][$level] != '') {
 162                  $this->_row[$col][$level] .='<br />';
 163              }
 164              $this->_row[$col][$level] .= $msg;
 165          } else {
 166              $this->_row[$col][$level] = $msg;
 167          }
 168      }
 169  
 170      /**
 171       * Print the table end
 172       * @return void
 173       */
 174      public function close() {
 175          $this->flush();
 176          echo '</table>';
 177      }
 178  }
 179  
 180  /**
 181   * Validation callback function - verified the column line of csv file.
 182   * Converts standard column names to lowercase.
 183   * @param csv_import_reader $cir
 184   * @param array $stdfields standard user fields
 185   * @param array $profilefields custom profile fields
 186   * @param moodle_url $returnurl return url in case of any error
 187   * @return array list of fields
 188   */
 189  function uu_validate_user_upload_columns(csv_import_reader $cir, $stdfields, $profilefields, moodle_url $returnurl) {
 190      $columns = $cir->get_columns();
 191  
 192      if (empty($columns)) {
 193          $cir->close();
 194          $cir->cleanup();
 195          print_error('cannotreadtmpfile', 'error', $returnurl);
 196      }
 197      if (count($columns) < 2) {
 198          $cir->close();
 199          $cir->cleanup();
 200          print_error('csvfewcolumns', 'error', $returnurl);
 201      }
 202  
 203      // test columns
 204      $processed = array();
 205      foreach ($columns as $key=>$unused) {
 206          $field = $columns[$key];
 207          $field = trim($field);
 208          $lcfield = core_text::strtolower($field);
 209          if (in_array($field, $stdfields) or in_array($lcfield, $stdfields)) {
 210              // standard fields are only lowercase
 211              $newfield = $lcfield;
 212  
 213          } else if (in_array($field, $profilefields)) {
 214              // exact profile field name match - these are case sensitive
 215              $newfield = $field;
 216  
 217          } else if (in_array($lcfield, $profilefields)) {
 218              // hack: somebody wrote uppercase in csv file, but the system knows only lowercase profile field
 219              $newfield = $lcfield;
 220  
 221          } else if (preg_match('/^(sysrole|cohort|course|group|type|role|enrolperiod|enrolstatus|enroltimestart)\d+$/', $lcfield)) {
 222              // special fields for enrolments
 223              $newfield = $lcfield;
 224  
 225          } else {
 226              $cir->close();
 227              $cir->cleanup();
 228              print_error('invalidfieldname', 'error', $returnurl, $field);
 229          }
 230          if (in_array($newfield, $processed)) {
 231              $cir->close();
 232              $cir->cleanup();
 233              print_error('duplicatefieldname', 'error', $returnurl, $newfield);
 234          }
 235          $processed[$key] = $newfield;
 236      }
 237  
 238      return $processed;
 239  }
 240  
 241  /**
 242   * Increments username - increments trailing number or adds it if not present.
 243   * Varifies that the new username does not exist yet
 244   * @param string $username
 245   * @return incremented username which does not exist yet
 246   */
 247  function uu_increment_username($username) {
 248      global $DB, $CFG;
 249  
 250      if (!preg_match_all('/(.*?)([0-9]+)$/', $username, $matches)) {
 251          $username = $username.'2';
 252      } else {
 253          $username = $matches[1][0].($matches[2][0]+1);
 254      }
 255  
 256      if ($DB->record_exists('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id))) {
 257          return uu_increment_username($username);
 258      } else {
 259          return $username;
 260      }
 261  }
 262  
 263  /**
 264   * Check if default field contains templates and apply them.
 265   * @param string template - potential tempalte string
 266   * @param object user object- we need username, firstname and lastname
 267   * @return string field value
 268   */
 269  function uu_process_template($template, $user) {
 270      if (is_array($template)) {
 271          // hack for for support of text editors with format
 272          $t = $template['text'];
 273      } else {
 274          $t = $template;
 275      }
 276      if (strpos($t, '%') === false) {
 277          return $template;
 278      }
 279  
 280      $username  = isset($user->username)  ? $user->username  : '';
 281      $firstname = isset($user->firstname) ? $user->firstname : '';
 282      $lastname  = isset($user->lastname)  ? $user->lastname  : '';
 283  
 284      $callback = partial('uu_process_template_callback', $username, $firstname, $lastname);
 285  
 286      $result = preg_replace_callback('/(?<!%)%([+-~])?(\d)*([flu])/', $callback, $t);
 287  
 288      if (is_null($result)) {
 289          return $template; //error during regex processing??
 290      }
 291  
 292      if (is_array($template)) {
 293          $template['text'] = $result;
 294          return $t;
 295      } else {
 296          return $result;
 297      }
 298  }
 299  
 300  /**
 301   * Internal callback function.
 302   */
 303  function uu_process_template_callback($username, $firstname, $lastname, $block) {
 304      switch ($block[3]) {
 305          case 'u':
 306              $repl = $username;
 307              break;
 308          case 'f':
 309              $repl = $firstname;
 310              break;
 311          case 'l':
 312              $repl = $lastname;
 313              break;
 314          default:
 315              return $block[0];
 316      }
 317  
 318      switch ($block[1]) {
 319          case '+':
 320              $repl = core_text::strtoupper($repl);
 321              break;
 322          case '-':
 323              $repl = core_text::strtolower($repl);
 324              break;
 325          case '~':
 326              $repl = core_text::strtotitle($repl);
 327              break;
 328      }
 329  
 330      if (!empty($block[2])) {
 331          $repl = core_text::substr($repl, 0 , $block[2]);
 332      }
 333  
 334      return $repl;
 335  }
 336  
 337  /**
 338   * Returns list of auth plugins that are enabled and known to work.
 339   *
 340   * If ppl want to use some other auth type they have to include it
 341   * in the CSV file next on each line.
 342   *
 343   * @return array type=>name
 344   */
 345  function uu_supported_auths() {
 346      // Get all the enabled plugins.
 347      $plugins = get_enabled_auth_plugins();
 348      $choices = array();
 349      foreach ($plugins as $plugin) {
 350          $objplugin = get_auth_plugin($plugin);
 351          // If the plugin can not be manually set skip it.
 352          if (!$objplugin->can_be_manually_set()) {
 353              continue;
 354          }
 355          $choices[$plugin] = get_string('pluginname', "auth_{$plugin}");
 356      }
 357  
 358      return $choices;
 359  }
 360  
 361  /**
 362   * Returns list of roles that are assignable in courses
 363   * @return array
 364   */
 365  function uu_allowed_roles() {
 366      // let's cheat a bit, frontpage is guaranteed to exist and has the same list of roles ;-)
 367      $roles = get_assignable_roles(context_course::instance(SITEID), ROLENAME_ORIGINALANDSHORT);
 368      return array_reverse($roles, true);
 369  }
 370  
 371  /**
 372   * Returns mapping of all roles using short role name as index.
 373   * @return array
 374   */
 375  function uu_allowed_roles_cache() {
 376      $allowedroles = get_assignable_roles(context_course::instance(SITEID), ROLENAME_SHORT);
 377      $rolecache = [];
 378      foreach ($allowedroles as $rid=>$rname) {
 379          $rolecache[$rid] = new stdClass();
 380          $rolecache[$rid]->id   = $rid;
 381          $rolecache[$rid]->name = $rname;
 382          if (!is_numeric($rname)) { // only non-numeric shortnames are supported!!!
 383              $rolecache[$rname] = new stdClass();
 384              $rolecache[$rname]->id   = $rid;
 385              $rolecache[$rname]->name = $rname;
 386          }
 387      }
 388      return $rolecache;
 389  }
 390  
 391  /**
 392   * Returns mapping of all system roles using short role name as index.
 393   * @return array
 394   */
 395  function uu_allowed_sysroles_cache() {
 396      $allowedroles = get_assignable_roles(context_system::instance(), ROLENAME_SHORT);
 397      $rolecache = [];
 398      foreach ($allowedroles as $rid => $rname) {
 399          $rolecache[$rid] = new stdClass();
 400          $rolecache[$rid]->id   = $rid;
 401          $rolecache[$rid]->name = $rname;
 402          if (!is_numeric($rname)) { // Only non-numeric shortnames are supported!
 403              $rolecache[$rname] = new stdClass();
 404              $rolecache[$rname]->id   = $rid;
 405              $rolecache[$rname]->name = $rname;
 406          }
 407      }
 408      return $rolecache;
 409  }
 410  
 411  /**
 412   * Pre process custom profile data, and update it with corrected value
 413   *
 414   * @param stdClass $data user profile data
 415   * @return stdClass pre-processed custom profile data
 416   */
 417  function uu_pre_process_custom_profile_data($data) {
 418      global $CFG;
 419      require_once($CFG->dirroot . '/user/profile/lib.php');
 420      $fields = profile_get_user_fields_with_data(0);
 421  
 422      // find custom profile fields and check if data needs to converted.
 423      foreach ($data as $key => $value) {
 424          if (preg_match('/^profile_field_/', $key)) {
 425              $shortname = str_replace('profile_field_', '', $key);
 426              if ($fields) {
 427                  foreach ($fields as $formfield) {
 428                      if ($formfield->get_shortname() === $shortname && method_exists($formfield, 'convert_external_data')) {
 429                          $data->$key = $formfield->convert_external_data($value);
 430                      }
 431                  }
 432              }
 433          }
 434      }
 435      return $data;
 436  }
 437  
 438  /**
 439   * Checks if data provided for custom fields is correct
 440   * Currently checking for custom profile field or type menu
 441   *
 442   * @param array $data user profile data
 443   * @param array $profilefieldvalues Used to track previous profile field values to ensure uniqueness is observed
 444   * @return bool true if no error else false
 445   */
 446  function uu_check_custom_profile_data(&$data, array &$profilefieldvalues = []) {
 447      global $CFG;
 448      require_once($CFG->dirroot.'/user/profile/lib.php');
 449  
 450      $noerror = true;
 451      $testuserid = null;
 452  
 453      if (!empty($data['username'])) {
 454          if (preg_match('/id=(.*)"/i', $data['username'], $result)) {
 455              $testuserid = $result[1];
 456          }
 457      }
 458      $profilefields = profile_get_user_fields_with_data(0);
 459      // Find custom profile fields and check if data needs to converted.
 460      foreach ($data as $key => $value) {
 461          if (preg_match('/^profile_field_/', $key)) {
 462              $shortname = str_replace('profile_field_', '', $key);
 463              foreach ($profilefields as $formfield) {
 464                  if ($formfield->get_shortname() === $shortname) {
 465                      if (method_exists($formfield, 'convert_external_data') &&
 466                              is_null($formfield->convert_external_data($value))) {
 467                          $data['status'][] = get_string('invaliduserfield', 'error', $shortname);
 468                          $noerror = false;
 469                      }
 470  
 471                      // Ensure unique field value doesn't already exist in supplied data.
 472                      $formfieldunique = $formfield->is_unique() && ($value !== '' || $formfield->is_required());
 473                      if ($formfieldunique && array_key_exists($shortname, $profilefieldvalues) &&
 474                              (array_search($value, $profilefieldvalues[$shortname]) !== false)) {
 475  
 476                          $data['status'][] = get_string('valuealreadyused') . " ({$key})";
 477                          $noerror = false;
 478                      }
 479  
 480                      // Check for duplicate value.
 481                      if (method_exists($formfield, 'edit_validate_field') ) {
 482                          $testuser = new stdClass();
 483                          $testuser->{$key} = $value;
 484                          $testuser->id = $testuserid;
 485                          $err = $formfield->edit_validate_field($testuser);
 486                          if (!empty($err[$key])) {
 487                              $data['status'][] = $err[$key].' ('.$key.')';
 488                              $noerror = false;
 489                          }
 490                      }
 491  
 492                      // Record value of unique field, so it can be compared for duplicates.
 493                      if ($formfieldunique) {
 494                          $profilefieldvalues[$shortname][] = $value;
 495                      }
 496                  }
 497              }
 498          }
 499      }
 500      return $noerror;
 501  }