Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Bulk user registration functions
 *
 * @package    tool
 * @subpackage uploaduser
 * @copyright  2004 onwards Martin Dougiamas (http://dougiamas.com)
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

defined('MOODLE_INTERNAL') || die();

define('UU_USER_ADDNEW', 0);
define('UU_USER_ADDINC', 1);
define('UU_USER_ADD_UPDATE', 2);
define('UU_USER_UPDATE', 3);

define('UU_UPDATE_NOCHANGES', 0);
define('UU_UPDATE_FILEOVERRIDE', 1);
define('UU_UPDATE_ALLOVERRIDE', 2);
define('UU_UPDATE_MISSING', 3);

define('UU_BULK_NONE', 0);
define('UU_BULK_NEW', 1);
define('UU_BULK_UPDATED', 2);
define('UU_BULK_ALL', 3);

define('UU_PWRESET_NONE', 0);
define('UU_PWRESET_WEAK', 1);
define('UU_PWRESET_ALL', 2);

/**
 * Tracking of processed users.
 *
 * This class prints user information into a html table.
 *
 * @package    core
 * @subpackage admin
 * @copyright  2007 Petr Skoda  {@link http://skodak.org}
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class uu_progress_tracker {
< private $_row;
> /** @var array */ > protected $_row;
/** * The columns shown on the table. * @var array */
< public $columns = array('status', 'line', 'id', 'username', 'firstname', 'lastname', 'email', < 'password', 'auth', 'enrolments', 'suspended', 'theme', 'deleted');
> public $columns = []; > /** @var array column headers */ > protected $headers = []; > > /** > * uu_progress_tracker constructor. > */ > public function __construct() { > $this->headers = [ > 'status' => get_string('status'), > 'line' => get_string('uucsvline', 'tool_uploaduser'), > 'id' => 'ID', > 'username' => get_string('username'), > 'firstname' => get_string('firstname'), > 'lastname' => get_string('lastname'), > 'email' => get_string('email'), > 'password' => get_string('password'), > 'auth' => get_string('authentication'), > 'enrolments' => get_string('enrolments', 'enrol'), > 'suspended' => get_string('suspended', 'auth'), > 'theme' => get_string('theme'), > 'deleted' => get_string('delete'), > ]; > $this->columns = array_keys($this->headers); > }
/** * Print table header. * @return void */ public function start() { $ci = 0; echo '<table id="uuresults" class="generaltable boxaligncenter flexible-wrap" summary="'.get_string('uploadusersresult', 'tool_uploaduser').'">'; echo '<tr class="heading r0">';
< echo '<th class="header c'.$ci++.'" scope="col">'.get_string('status').'</th>'; < echo '<th class="header c'.$ci++.'" scope="col">'.get_string('uucsvline', 'tool_uploaduser').'</th>'; < echo '<th class="header c'.$ci++.'" scope="col">ID</th>'; < echo '<th class="header c'.$ci++.'" scope="col">'.get_string('username').'</th>'; < echo '<th class="header c'.$ci++.'" scope="col">'.get_string('firstname').'</th>'; < echo '<th class="header c'.$ci++.'" scope="col">'.get_string('lastname').'</th>'; < echo '<th class="header c'.$ci++.'" scope="col">'.get_string('email').'</th>'; < echo '<th class="header c'.$ci++.'" scope="col">'.get_string('password').'</th>'; < echo '<th class="header c'.$ci++.'" scope="col">'.get_string('authentication').'</th>'; < echo '<th class="header c'.$ci++.'" scope="col">'.get_string('enrolments', 'enrol').'</th>'; < echo '<th class="header c'.$ci++.'" scope="col">'.get_string('suspended', 'auth').'</th>'; < echo '<th class="header c'.$ci++.'" scope="col">'.get_string('theme').'</th>'; < echo '<th class="header c'.$ci++.'" scope="col">'.get_string('delete').'</th>';
> foreach ($this->headers as $key => $header) { > echo '<th class="header c'.$ci++.'" scope="col">'.$header.'</th>'; > }
echo '</tr>'; $this->_row = null; } /** * Flush previous line and start a new one. * @return void */ public function flush() { if (empty($this->_row) or empty($this->_row['line']['normal'])) { // Nothing to print - each line has to have at least number $this->_row = array(); foreach ($this->columns as $col) { $this->_row[$col] = array('normal'=>'', 'info'=>'', 'warning'=>'', 'error'=>''); } return; } $ci = 0; $ri = 1; echo '<tr class="r'.$ri.'">'; foreach ($this->_row as $key=>$field) { foreach ($field as $type=>$content) { if ($field[$type] !== '') { $field[$type] = '<span class="uu'.$type.'">'.$field[$type].'</span>'; } else { unset($field[$type]); } } echo '<td class="cell c'.$ci++.'">'; if (!empty($field)) { echo implode('<br />', $field); } else { echo '&nbsp;'; } echo '</td>'; } echo '</tr>'; foreach ($this->columns as $col) { $this->_row[$col] = array('normal'=>'', 'info'=>'', 'warning'=>'', 'error'=>''); } } /** * Add tracking info * @param string $col name of column * @param string $msg message * @param string $level 'normal', 'warning' or 'error' * @param bool $merge true means add as new line, false means override all previous text of the same type * @return void */ public function track($col, $msg, $level = 'normal', $merge = true) { if (empty($this->_row)) { $this->flush(); //init arrays } if (!in_array($col, $this->columns)) { debugging('Incorrect column:'.$col); return; } if ($merge) { if ($this->_row[$col][$level] != '') { $this->_row[$col][$level] .='<br />'; } $this->_row[$col][$level] .= $msg; } else { $this->_row[$col][$level] = $msg; } } /** * Print the table end * @return void */ public function close() { $this->flush(); echo '</table>'; } } /** * Validation callback function - verified the column line of csv file. * Converts standard column names to lowercase. * @param csv_import_reader $cir * @param array $stdfields standard user fields * @param array $profilefields custom profile fields * @param moodle_url $returnurl return url in case of any error * @return array list of fields */ function uu_validate_user_upload_columns(csv_import_reader $cir, $stdfields, $profilefields, moodle_url $returnurl) { $columns = $cir->get_columns(); if (empty($columns)) { $cir->close(); $cir->cleanup();
< print_error('cannotreadtmpfile', 'error', $returnurl);
> throw new \moodle_exception('cannotreadtmpfile', 'error', $returnurl);
} if (count($columns) < 2) { $cir->close(); $cir->cleanup();
< print_error('csvfewcolumns', 'error', $returnurl);
> throw new \moodle_exception('csvfewcolumns', 'error', $returnurl);
} // test columns $processed = array();
> $acceptedfields = [ foreach ($columns as $key=>$unused) { > 'category', $field = $columns[$key]; > 'categoryrole', $field = trim($field); > 'cohort', $lcfield = core_text::strtolower($field); > 'course', if (in_array($field, $stdfields) or in_array($lcfield, $stdfields)) { > 'enrolperiod', // standard fields are only lowercase > 'enrolstatus', $newfield = $lcfield; > 'enroltimestart', > 'group', } else if (in_array($field, $profilefields)) { > 'role', // exact profile field name match - these are case sensitive > 'sysrole', $newfield = $field; > 'type', > ]; } else if (in_array($lcfield, $profilefields)) { > $specialfieldsregex = "/^(" . implode('|', $acceptedfields) . ")\d+$/"; // hack: somebody wrote uppercase in csv file, but the system knows only lowercase profile field >
$newfield = $lcfield;
< } else if (preg_match('/^(sysrole|cohort|course|group|type|role|enrolperiod|enrolstatus|enroltimestart)\d+$/', $lcfield)) {
> } else if (preg_match($specialfieldsregex, $lcfield)) {
// special fields for enrolments $newfield = $lcfield; } else { $cir->close(); $cir->cleanup();
< print_error('invalidfieldname', 'error', $returnurl, $field);
> throw new \moodle_exception('invalidfieldname', 'error', $returnurl, $field);
} if (in_array($newfield, $processed)) { $cir->close(); $cir->cleanup();
< print_error('duplicatefieldname', 'error', $returnurl, $newfield);
> throw new \moodle_exception('duplicatefieldname', 'error', $returnurl, $newfield);
} $processed[$key] = $newfield; } return $processed; } /** * Increments username - increments trailing number or adds it if not present. * Varifies that the new username does not exist yet * @param string $username * @return incremented username which does not exist yet */ function uu_increment_username($username) { global $DB, $CFG; if (!preg_match_all('/(.*?)([0-9]+)$/', $username, $matches)) { $username = $username.'2'; } else { $username = $matches[1][0].($matches[2][0]+1); } if ($DB->record_exists('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id))) { return uu_increment_username($username); } else { return $username; } } /** * Check if default field contains templates and apply them. * @param string template - potential tempalte string * @param object user object- we need username, firstname and lastname * @return string field value */ function uu_process_template($template, $user) { if (is_array($template)) { // hack for for support of text editors with format $t = $template['text']; } else { $t = $template; } if (strpos($t, '%') === false) { return $template; } $username = isset($user->username) ? $user->username : ''; $firstname = isset($user->firstname) ? $user->firstname : ''; $lastname = isset($user->lastname) ? $user->lastname : ''; $callback = partial('uu_process_template_callback', $username, $firstname, $lastname); $result = preg_replace_callback('/(?<!%)%([+-~])?(\d)*([flu])/', $callback, $t); if (is_null($result)) { return $template; //error during regex processing?? } if (is_array($template)) { $template['text'] = $result; return $t; } else { return $result; } } /** * Internal callback function. */ function uu_process_template_callback($username, $firstname, $lastname, $block) { switch ($block[3]) { case 'u': $repl = $username; break; case 'f': $repl = $firstname; break; case 'l': $repl = $lastname; break; default: return $block[0]; } switch ($block[1]) { case '+': $repl = core_text::strtoupper($repl); break; case '-': $repl = core_text::strtolower($repl); break; case '~': $repl = core_text::strtotitle($repl); break; } if (!empty($block[2])) { $repl = core_text::substr($repl, 0 , $block[2]); } return $repl; } /** * Returns list of auth plugins that are enabled and known to work. * * If ppl want to use some other auth type they have to include it * in the CSV file next on each line. * * @return array type=>name */ function uu_supported_auths() { // Get all the enabled plugins. $plugins = get_enabled_auth_plugins(); $choices = array(); foreach ($plugins as $plugin) { $objplugin = get_auth_plugin($plugin); // If the plugin can not be manually set skip it. if (!$objplugin->can_be_manually_set()) { continue; } $choices[$plugin] = get_string('pluginname', "auth_{$plugin}"); } return $choices; } /** * Returns list of roles that are assignable in courses * @return array */ function uu_allowed_roles() { // let's cheat a bit, frontpage is guaranteed to exist and has the same list of roles ;-) $roles = get_assignable_roles(context_course::instance(SITEID), ROLENAME_ORIGINALANDSHORT); return array_reverse($roles, true); } /**
< * Returns mapping of all roles using short role name as index.
> * Returns assignable roles for current user using short role name and role ID as index. > * This function is no longer called without parameters. > * > * @param int|null $categoryid Id of the category to get roles for. > * @param int|null $courseid Id of the course to get roles for.
* @return array */
< function uu_allowed_roles_cache() {
> function uu_allowed_roles_cache(?int $categoryid = null, ?int $courseid = null): array { > if (!is_null($categoryid) && !is_null($courseid)) { > return []; > } else if (is_null($categoryid) && !is_null($courseid)) { > $allowedroles = get_assignable_roles(context_course::instance($courseid), ROLENAME_SHORT); > } else if (is_null($courseid) && !is_null($categoryid)) { > $allowedroles = get_assignable_roles(context_coursecat::instance($categoryid), ROLENAME_SHORT); > } else {
$allowedroles = get_assignable_roles(context_course::instance(SITEID), ROLENAME_SHORT);
> } $rolecache = []; >
foreach ($allowedroles as $rid=>$rname) {
> // A role can be searched for by its ID or by its shortname.
$rolecache[$rid] = new stdClass(); $rolecache[$rid]->id = $rid; $rolecache[$rid]->name = $rname;
< if (!is_numeric($rname)) { // only non-numeric shortnames are supported!!!
> // Since numeric short names are allowed, to avoid replacement of another role, we only accept non-numeric values. > if (!is_numeric($rname)) {
$rolecache[$rname] = new stdClass(); $rolecache[$rname]->id = $rid; $rolecache[$rname]->name = $rname; } } return $rolecache; } /** * Returns mapping of all system roles using short role name as index. * @return array */ function uu_allowed_sysroles_cache() { $allowedroles = get_assignable_roles(context_system::instance(), ROLENAME_SHORT); $rolecache = []; foreach ($allowedroles as $rid => $rname) { $rolecache[$rid] = new stdClass(); $rolecache[$rid]->id = $rid; $rolecache[$rid]->name = $rname; if (!is_numeric($rname)) { // Only non-numeric shortnames are supported! $rolecache[$rname] = new stdClass(); $rolecache[$rname]->id = $rid; $rolecache[$rname]->name = $rname; } } return $rolecache; } /** * Pre process custom profile data, and update it with corrected value * * @param stdClass $data user profile data * @return stdClass pre-processed custom profile data */ function uu_pre_process_custom_profile_data($data) {
< global $CFG, $DB;
> global $CFG; > require_once($CFG->dirroot . '/user/profile/lib.php'); > $fields = profile_get_user_fields_with_data(0); >
// find custom profile fields and check if data needs to converted. foreach ($data as $key => $value) { if (preg_match('/^profile_field_/', $key)) { $shortname = str_replace('profile_field_', '', $key);
< if ($fields = $DB->get_records('user_info_field', array('shortname' => $shortname))) { < foreach ($fields as $field) { < require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php'); < $newfield = 'profile_field_'.$field->datatype; < $formfield = new $newfield($field->id, $data->id); < if (method_exists($formfield, 'convert_external_data')) {
> if ($fields) { > foreach ($fields as $formfield) { > if ($formfield->get_shortname() === $shortname && method_exists($formfield, 'convert_external_data')) {
$data->$key = $formfield->convert_external_data($value); } } } } } return $data; } /** * Checks if data provided for custom fields is correct * Currently checking for custom profile field or type menu * * @param array $data user profile data
> * @param array $profilefieldvalues Used to track previous profile field values to ensure uniqueness is observed
* @return bool true if no error else false */
< function uu_check_custom_profile_data(&$data) { < global $CFG, $DB;
> function uu_check_custom_profile_data(&$data, array &$profilefieldvalues = []) { > global $CFG; > require_once($CFG->dirroot.'/user/profile/lib.php'); >
$noerror = true; $testuserid = null; if (!empty($data['username'])) { if (preg_match('/id=(.*)"/i', $data['username'], $result)) { $testuserid = $result[1]; } }
> $profilefields = profile_get_user_fields_with_data(0);
// Find custom profile fields and check if data needs to converted. foreach ($data as $key => $value) { if (preg_match('/^profile_field_/', $key)) { $shortname = str_replace('profile_field_', '', $key);
< if ($fields = $DB->get_records('user_info_field', array('shortname' => $shortname))) { < foreach ($fields as $field) { < require_once($CFG->dirroot.'/user/profile/field/'.$field->datatype.'/field.class.php'); < $newfield = 'profile_field_'.$field->datatype; < $formfield = new $newfield($field->id, 0);
> foreach ($profilefields as $formfield) { > if ($formfield->get_shortname() === $shortname) {
if (method_exists($formfield, 'convert_external_data') && is_null($formfield->convert_external_data($value))) { $data['status'][] = get_string('invaliduserfield', 'error', $shortname); $noerror = false; }
> // Check for duplicate value. > // Ensure unique field value doesn't already exist in supplied data. if (method_exists($formfield, 'edit_validate_field') ) { > $formfieldunique = $formfield->is_unique() && ($value !== '' || $formfield->is_required()); $testuser = new stdClass(); > if ($formfieldunique && array_key_exists($shortname, $profilefieldvalues) && $testuser->{$key} = $value; > (array_search($value, $profilefieldvalues[$shortname]) !== false)) { $testuser->id = $testuserid; > $err = $formfield->edit_validate_field($testuser); > $data['status'][] = get_string('valuealreadyused') . " ({$key})"; if (!empty($err[$key])) { > $noerror = false; $data['status'][] = $err[$key].' ('.$key.')'; > } $noerror = false; >
}
> } } > } > // Record value of unique field, so it can be compared for duplicates. } > if ($formfieldunique) { } > $profilefieldvalues[$shortname][] = $value;
} return $noerror; }