Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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