first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
@@ -0,0 +1,410 @@
<?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/>.
/**
* Class cli_helper
*
* @package tool_uploaduser
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_uploaduser;
defined('MOODLE_INTERNAL') || die();
use tool_uploaduser\local\cli_progress_tracker;
require_once($CFG->dirroot.'/user/profile/lib.php');
require_once($CFG->dirroot.'/user/lib.php');
require_once($CFG->dirroot.'/group/lib.php');
require_once($CFG->dirroot.'/cohort/lib.php');
require_once($CFG->libdir.'/csvlib.class.php');
require_once($CFG->dirroot.'/'.$CFG->admin.'/tool/uploaduser/locallib.php');
require_once($CFG->dirroot.'/'.$CFG->admin.'/tool/uploaduser/user_form.php');
require_once($CFG->libdir . '/clilib.php');
/**
* Helper method for CLI script to upload users (also has special wrappers for cli* functions for phpunit testing)
*
* @package tool_uploaduser
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cli_helper {
/** @var string */
protected $operation;
/** @var array */
protected $clioptions;
/** @var array */
protected $unrecognized;
/** @var string */
protected $progresstrackerclass;
/** @var process */
protected $process;
/**
* cli_helper constructor.
*
* @param string|null $progresstrackerclass
*/
public function __construct(?string $progresstrackerclass = null) {
$this->progresstrackerclass = $progresstrackerclass ?? cli_progress_tracker::class;
$optionsdefinitions = $this->options_definitions();
$longoptions = [];
$shortmapping = [];
foreach ($optionsdefinitions as $key => $option) {
$longoptions[$key] = $option['default'];
if (!empty($option['alias'])) {
$shortmapping[$option['alias']] = $key;
}
}
list($this->clioptions, $this->unrecognized) = cli_get_params(
$longoptions,
$shortmapping
);
}
/**
* Options used in this CLI script
*
* @return array
*/
protected function options_definitions(): array {
$options = [
'help' => [
'hasvalue' => false,
'description' => get_string('clihelp', 'tool_uploaduser'),
'default' => 0,
'alias' => 'h',
],
'file' => [
'hasvalue' => 'PATH',
'description' => get_string('clifile', 'tool_uploaduser'),
'default' => null,
'validation' => function($file) {
if (!$file) {
$this->cli_error(get_string('climissingargument', 'tool_uploaduser', 'file'));
}
if ($file && (!file_exists($file) || !is_readable($file))) {
$this->cli_error(get_string('clifilenotreadable', 'tool_uploaduser', $file));
}
}
],
];
$form = new \admin_uploaduser_form1();
[$elements, $defaults] = $form->get_form_for_cli();
$options += $this->prepare_form_elements_for_cli($elements, $defaults);
// Specify pseudo-column 'type1' to force the form to populate the legacy role mapping selector
// but only if user is allowed to assign roles in courses (otherwise form validation will fail).
$columns = uu_allowed_roles() ? ['type1'] : [];
$form = new \admin_uploaduser_form2(null, ['columns' => $columns, 'data' => []]);
[$elements, $defaults] = $form->get_form_for_cli();
$options += $this->prepare_form_elements_for_cli($elements, $defaults);
return $options;
}
/**
* Print help for export
*/
public function print_help(): void {
$this->cli_writeln(get_string('clititle', 'tool_uploaduser'));
$this->cli_writeln('');
$this->print_help_options($this->options_definitions());
$this->cli_writeln('');
$this->cli_writeln('Example:');
$this->cli_writeln('$sudo -u www-data /usr/bin/php admin/tool/uploaduser/cli/uploaduser.php --file=PATH');
}
/**
* Get CLI option
*
* @param string $key
* @return mixed|null
*/
public function get_cli_option(string $key) {
return $this->clioptions[$key] ?? null;
}
/**
* Write a text to the given stream
*
* @param string $text text to be written
*/
protected function cli_write($text): void {
if (PHPUNIT_TEST) {
echo $text;
} else {
cli_write($text);
}
}
/**
* Write error notification
* @param string $text
* @return void
*/
protected function cli_problem($text): void {
if (PHPUNIT_TEST) {
echo $text;
} else {
cli_problem($text);
}
}
/**
* Write a text followed by an end of line symbol to the given stream
*
* @param string $text text to be written
*/
protected function cli_writeln($text): void {
$this->cli_write($text . PHP_EOL);
}
/**
* Write to standard error output and exit with the given code
*
* @param string $text
* @param int $errorcode
* @return void (does not return)
*/
protected function cli_error($text, $errorcode = 1): void {
$this->cli_problem($text);
$this->die($errorcode);
}
/**
* Wrapper for "die()" method so we can unittest it
*
* @param mixed $errorcode
* @throws \moodle_exception
*/
protected function die($errorcode): void {
if (!PHPUNIT_TEST) {
die($errorcode);
} else {
throw new \moodle_exception('CLI script finished with error code '.$errorcode);
}
}
/**
* Display as CLI table
*
* @param array $column1
* @param array $column2
* @param int $indent
* @return string
*/
protected function convert_to_table(array $column1, array $column2, int $indent = 0): string {
$maxlengthleft = 0;
$left = [];
$column1 = array_values($column1);
$column2 = array_values($column2);
foreach ($column1 as $i => $l) {
$left[$i] = str_repeat(' ', $indent) . $l;
if (strlen('' . $column2[$i])) {
$maxlengthleft = max($maxlengthleft, strlen($l) + $indent);
}
}
$maxlengthright = 80 - $maxlengthleft - 1;
$output = '';
foreach ($column2 as $i => $r) {
if (!strlen('' . $r)) {
$output .= $left[$i] . "\n";
continue;
}
$right = wordwrap($r, $maxlengthright, "\n");
$output .= str_pad($left[$i], $maxlengthleft) . ' ' .
str_replace("\n", PHP_EOL . str_repeat(' ', $maxlengthleft + 1), $right) . PHP_EOL;
}
return $output;
}
/**
* Display available CLI options as a table
*
* @param array $options
*/
protected function print_help_options(array $options): void {
$left = [];
$right = [];
foreach ($options as $key => $option) {
if ($option['hasvalue'] !== false) {
$l = "--$key={$option['hasvalue']}";
} else if (!empty($option['alias'])) {
$l = "-{$option['alias']}, --$key";
} else {
$l = "--$key";
}
$left[] = $l;
$right[] = $option['description'];
}
$this->cli_write('Options:' . PHP_EOL . $this->convert_to_table($left, $right));
}
/**
* Process the upload
*/
public function process(): void {
// First, validate all arguments.
$definitions = $this->options_definitions();
foreach ($this->clioptions as $key => $value) {
if ($validator = $definitions[$key]['validation'] ?? null) {
$validator($value);
}
}
// Read the CSV file.
$iid = \csv_import_reader::get_new_iid('uploaduser');
$cir = new \csv_import_reader($iid, 'uploaduser');
$cir->load_csv_content(file_get_contents($this->get_cli_option('file')),
$this->get_cli_option('encoding'), $this->get_cli_option('delimiter_name'));
$csvloaderror = $cir->get_error();
if (!is_null($csvloaderror)) {
$this->cli_error(get_string('csvloaderror', 'error', $csvloaderror), 1);
}
// Start upload user process.
$this->process = new \tool_uploaduser\process($cir, $this->progresstrackerclass);
$filecolumns = $this->process->get_file_columns();
$form = $this->mock_form(['columns' => $filecolumns, 'data' => ['iid' => $iid, 'previewrows' => 1]], $this->clioptions);
if (!$form->is_validated()) {
$errors = $form->get_validation_errors();
$this->cli_error(get_string('clivalidationerror', 'tool_uploaduser') . PHP_EOL .
$this->convert_to_table(array_keys($errors), array_values($errors), 2));
}
$this->process->set_form_data($form->get_data());
$this->process->process();
}
/**
* Mock form submission
*
* @param array $customdata
* @param array $submitteddata
* @return \admin_uploaduser_form2
*/
protected function mock_form(array $customdata, array $submitteddata): \admin_uploaduser_form2 {
global $USER;
$submitteddata['description'] = ['text' => $submitteddata['description'], 'format' => FORMAT_HTML];
// Now mock the form submission.
$submitteddata['_qf__admin_uploaduser_form2'] = 1;
$oldignoresesskey = $USER->ignoresesskey ?? null;
$USER->ignoresesskey = true;
$form = new \admin_uploaduser_form2(null, $customdata, 'post', '', [], true, $submitteddata);
$USER->ignoresesskey = $oldignoresesskey;
$form->set_data($submitteddata);
return $form;
}
/**
* Prepare form elements for CLI
*
* @param \HTML_QuickForm_element[] $elements
* @param array $defaults
* @return array
*/
protected function prepare_form_elements_for_cli(array $elements, array $defaults): array {
$options = [];
foreach ($elements as $element) {
if ($element instanceof \HTML_QuickForm_submit || $element instanceof \HTML_QuickForm_static) {
continue;
}
$type = $element->getType();
if ($type === 'html' || $type === 'hidden' || $type === 'header') {
continue;
}
$name = $element->getName();
if ($name === null || preg_match('/^mform_isexpanded_/', $name)
|| preg_match('/^_qf__/', $name)) {
continue;
}
$label = $element->getLabel();
if (!strlen($label) && method_exists($element, 'getText')) {
$label = $element->getText();
}
$default = $defaults[$element->getName()] ?? null;
$postfix = '';
$possiblevalues = null;
if ($element instanceof \HTML_QuickForm_select) {
$selectoptions = $element->_options;
$possiblevalues = [];
foreach ($selectoptions as $option) {
$possiblevalues[] = '' . $option['attr']['value'];
}
if (count($selectoptions) < 10) {
$postfix .= ':';
foreach ($selectoptions as $option) {
$postfix .= "\n ".$option['attr']['value']." - ".$option['text'];
}
}
if (!array_key_exists($name, $defaults)) {
$firstoption = reset($selectoptions);
$default = $firstoption['attr']['value'];
}
// The menu profile field type allows for an empty default value, handle that here.
if (preg_match('/^profile_field_/', $name) && $default === '') {
$possiblevalues[] = $default;
}
}
if ($element instanceof \HTML_QuickForm_checkbox) {
$postfix = ":\n 0|1";
$possiblevalues = ['0', '1'];
}
if ($default !== null & $default !== '') {
$postfix .= "\n ".get_string('clidefault', 'tool_uploaduser')." ".$default;
}
$options[$name] = [
'hasvalue' => 'VALUE',
'description' => $label.$postfix,
'default' => $default,
];
if ($possiblevalues !== null) {
$options[$name]['validation'] = function($v) use ($possiblevalues, $name) {
if (!in_array('' . $v, $possiblevalues)) {
$this->cli_error(get_string('clierrorargument', 'tool_uploaduser',
(object)['name' => $name, 'values' => join(', ', $possiblevalues)]));
}
};
}
}
return $options;
}
/**
* Get process statistics.
*
* @return array
*/
public function get_stats(): array {
return $this->process->get_stats();
}
}
@@ -0,0 +1,43 @@
<?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/>.
/**
* Class cli_progress_tracker
*
* @package tool_uploaduser
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_uploaduser\local;
/**
* Tracks the progress of the user upload and outputs it in CLI script (writes to STDOUT)
*
* @package tool_uploaduser
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cli_progress_tracker extends text_progress_tracker {
/**
* Output one line (followed by newline)
* @param string $line
*/
protected function output_line(string $line): void {
cli_writeln($line);
}
}
@@ -0,0 +1,79 @@
<?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/>.
/**
* File containing the field_value_validators class.
*
* @package tool_uploaduser
* @copyright 2019 Mathew May
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_uploaduser\local;
defined('MOODLE_INTERNAL') || die();
/**
* Field validator class.
*
* @package tool_uploaduser
* @copyright 2019 Mathew May
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class field_value_validators {
/**
* List of valid and compatible themes.
*
* @return array
*/
protected static $themescache;
/**
* Validates the value provided for the theme field.
*
* @param string $value The value for the theme field.
* @return array Contains the validation status and message.
*/
public static function validate_theme($value) {
global $CFG;
$status = 'normal';
$message = '';
// Validate if user themes are allowed.
if (!$CFG->allowuserthemes) {
$status = 'warning';
$message = get_string('userthemesnotallowed', 'tool_uploaduser');
} else {
// Cache list of themes if not yet set.
if (!isset(self::$themescache)) {
self::$themescache = get_list_of_themes();
}
// Check if we have a valid theme.
if (empty($value)) {
$status = 'warning';
$message = get_string('notheme', 'tool_uploaduser');
} else if (!isset(self::$themescache[$value])) {
$status = 'warning';
$message = get_string('invalidtheme', 'tool_uploaduser', s($value));
}
}
return [$status, $message];
}
}
@@ -0,0 +1,124 @@
<?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/>.
/**
* Class text_progress_tracker
*
* @package tool_uploaduser
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_uploaduser\local;
/**
* Tracks the progress of the user upload and echos it in a text format
*
* @package tool_uploaduser
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class text_progress_tracker extends \uu_progress_tracker {
/**
* Print table header.
* @return void
*/
public function start() {
$this->_row = null;
}
/**
* Output one line (followed by newline)
* @param string $line
*/
protected function output_line(string $line): void {
echo $line . PHP_EOL;
}
/**
* 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] = ['normal' => '', 'info' => '', 'warning' => '', 'error' => ''];
}
return;
}
$this->output_line(get_string('linex', 'tool_uploaduser', $this->_row['line']['normal']));
$prefix = [
'normal' => '',
'info' => '',
'warning' => get_string('warningprefix', 'tool_uploaduser') . ' ',
'error' => get_string('errorprefix', 'tool_uploaduser') . ' ',
];
foreach ($this->_row['status'] as $type => $content) {
if (strlen($content)) {
$this->output_line(' '.$prefix[$type].$content);
}
}
foreach ($this->_row as $key => $field) {
foreach ($field as $type => $content) {
if ($key !== 'status' && $type !== 'normal' && strlen($content)) {
$this->output_line(' ' . $prefix[$type] . $this->headers[$key] . ': ' .
str_replace("\n", "\n".str_repeat(" ", strlen($prefix[$type] . $this->headers[$key]) + 4), $content));
}
}
}
foreach ($this->columns as $col) {
$this->_row[$col] = ['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();
}
if (!in_array($col, $this->columns)) {
return;
}
if ($merge) {
if ($this->_row[$col][$level] != '') {
$this->_row[$col][$level] .= "\n";
}
$this->_row[$col][$level] .= $msg;
} else {
$this->_row[$col][$level] = $msg;
}
}
/**
* Print the table end
* @return void
*/
public function close() {
$this->flush();
$this->output_line(str_repeat('-', 79));
}
}
+161
View File
@@ -0,0 +1,161 @@
<?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/>.
/**
* Class preview
*
* @package tool_uploaduser
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_uploaduser;
defined('MOODLE_INTERNAL') || die();
use tool_uploaduser\local\field_value_validators;
require_once($CFG->libdir.'/csvlib.class.php');
require_once($CFG->dirroot.'/'.$CFG->admin.'/tool/uploaduser/locallib.php');
/**
* Display the preview of a CSV file
*
* @package tool_uploaduser
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class preview extends \html_table {
/** @var \csv_import_reader */
protected $cir;
/** @var array */
protected $filecolumns;
/** @var int */
protected $previewrows;
/** @var bool */
protected $noerror = true; // Keep status of any error.
/**
* preview constructor.
*
* @param \csv_import_reader $cir
* @param array $filecolumns
* @param int $previewrows
* @throws \coding_exception
*/
public function __construct(\csv_import_reader $cir, array $filecolumns, int $previewrows) {
parent::__construct();
$this->cir = $cir;
$this->filecolumns = $filecolumns;
$this->previewrows = $previewrows;
$this->id = "uupreview";
$this->attributes['class'] = 'generaltable';
$this->tablealign = 'center';
$this->summary = get_string('uploaduserspreview', 'tool_uploaduser');
$this->head = array();
$this->data = $this->read_data();
$this->head[] = get_string('uucsvline', 'tool_uploaduser');
foreach ($filecolumns as $column) {
$this->head[] = $column;
}
$this->head[] = get_string('status');
}
/**
* Read data
*
* @return array
* @throws \coding_exception
* @throws \dml_exception
* @throws \moodle_exception
*/
protected function read_data() {
global $DB, $CFG;
// Track whether values for profile fields defined as unique have already been used.
$profilefieldvalues = [];
$data = array();
$this->cir->init();
$linenum = 1; // Column header is first line.
while ($linenum <= $this->previewrows and $fields = $this->cir->next()) {
$linenum++;
$rowcols = array();
$rowcols['line'] = $linenum;
foreach ($fields as $key => $field) {
$rowcols[$this->filecolumns[$key]] = s(trim($field));
}
$rowcols['status'] = array();
if (isset($rowcols['username'])) {
$stdusername = \core_user::clean_field($rowcols['username'], 'username');
if ($rowcols['username'] !== $stdusername) {
$rowcols['status'][] = get_string('invalidusernameupload');
}
if ($userid = $DB->get_field('user', 'id',
['username' => $stdusername, 'mnethostid' => $CFG->mnet_localhost_id])) {
$rowcols['username'] = \html_writer::link(
new \moodle_url('/user/profile.php', ['id' => $userid]), $rowcols['username']);
}
} else {
$rowcols['status'][] = get_string('missingusername');
}
if (isset($rowcols['email'])) {
if (!validate_email($rowcols['email'])) {
$rowcols['status'][] = get_string('invalidemail');
}
$select = $DB->sql_like('email', ':email', false, true, false, '|');
$params = array('email' => $DB->sql_like_escape($rowcols['email'], '|'));
if ($DB->record_exists_select('user', $select , $params)) {
$rowcols['status'][] = get_string('useremailduplicate', 'error');
}
}
if (isset($rowcols['theme'])) {
list($status, $message) = field_value_validators::validate_theme($rowcols['theme']);
if ($status !== 'normal' && !empty($message)) {
$rowcols['status'][] = $message;
}
}
// Check if rowcols have custom profile field with correct data and update error state.
$this->noerror = uu_check_custom_profile_data($rowcols, $profilefieldvalues) && $this->noerror;
$rowcols['status'] = implode('<br />', $rowcols['status']);
$data[] = $rowcols;
}
if ($fields = $this->cir->next()) {
$data[] = array_fill(0, count($fields) + 2, '...');
}
$this->cir->close();
return $data;
}
/**
* Getter for noerror
*
* @return bool
*/
public function get_no_error() {
return $this->noerror;
}
}
@@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for tool_uploaduser.
*
* @package tool_uploaduser
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_uploaduser\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for tool_uploaduser implementing null_provider.
*
* @copyright 2018 Zig Tan <zig@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
File diff suppressed because it is too large Load Diff
+53
View File
@@ -0,0 +1,53 @@
<?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/>.
/**
* CLI script to upload users
*
* @package tool_uploaduser
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define('CLI_SCRIPT', true);
require_once(__DIR__ . '/../../../../config.php');
require_once($CFG->libdir . '/clilib.php');
if (moodle_needs_upgrading()) {
cli_error("Moodle upgrade pending, export execution suspended.");
}
// Increase time and memory limit.
core_php_time_limit::raise();
raise_memory_limit(MEMORY_EXTRA);
// Emulate normal session - we use admin account by default, set language to the site language.
\core\cron::setup_user();
$USER->lang = $CFG->lang;
$clihelper = new \tool_uploaduser\cli_helper();
if ($clihelper->get_cli_option('help')) {
$clihelper->print_help();
die();
}
$clihelper->process();
foreach ($clihelper->get_stats() as $line) {
cli_writeln($line);
}
+39
View File
@@ -0,0 +1,39 @@
<?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/>.
/**
* Defines the capabilities used by the user upload admin tool
*
* @package tool_uploaduser
* @copyright 2013 Dan Poltawski <dan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$capabilities = array(
// Allows the user to upload user pictures.
'tool/uploaduser:uploaduserpictures' => array(
'riskbitmask' => RISK_SPAM,
'captype' => 'write',
'contextlevel' => CONTEXT_SYSTEM,
'archetypes' => array(
'manager' => CAP_ALLOW
),
'clonepermissionsfrom' => 'moodle/site:uploadusers',
),
);
+4
View File
@@ -0,0 +1,4 @@
username,firstname,lastname,email
student1,Student,One,s1@example.com
student2,Student,Two,s2@example.com
student3,Student,Three,s3@example.com
1 username firstname lastname email
2 student1 Student One s1@example.com
3 student2 Student Two s2@example.com
4 student3 Student Three s3@example.com
+126
View File
@@ -0,0 +1,126 @@
<?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 script from a comma separated file
*
* @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
*/
require('../../../config.php');
require_once($CFG->libdir.'/adminlib.php');
require_once($CFG->libdir.'/csvlib.class.php');
require_once($CFG->dirroot.'/'.$CFG->admin.'/tool/uploaduser/locallib.php');
require_once($CFG->dirroot.'/'.$CFG->admin.'/tool/uploaduser/user_form.php');
$iid = optional_param('iid', '', PARAM_INT);
$previewrows = optional_param('previewrows', 10, PARAM_INT);
core_php_time_limit::raise(60 * 60); // 1 hour should be enough.
raise_memory_limit(MEMORY_HUGE);
admin_externalpage_setup('tooluploaduser');
$returnurl = new moodle_url('/admin/tool/uploaduser/index.php');
$bulknurl = new moodle_url('/admin/user/user_bulk.php');
if (empty($iid)) {
$mform1 = new admin_uploaduser_form1();
if ($formdata = $mform1->get_data()) {
$iid = csv_import_reader::get_new_iid('uploaduser');
$cir = new csv_import_reader($iid, 'uploaduser');
$content = $mform1->get_file_content('userfile');
$readcount = $cir->load_csv_content($content, $formdata->encoding, $formdata->delimiter_name);
$csvloaderror = $cir->get_error();
unset($content);
if (!is_null($csvloaderror)) {
throw new \moodle_exception('csvloaderror', '', $returnurl, $csvloaderror);
}
// Continue to form2.
} else {
echo $OUTPUT->header();
echo $OUTPUT->heading_with_help(get_string('uploadusers', 'tool_uploaduser'), 'uploadusers', 'tool_uploaduser');
$mform1->display();
echo $OUTPUT->footer();
die;
}
} else {
$cir = new csv_import_reader($iid, 'uploaduser');
}
// Test if columns ok.
$process = new \tool_uploaduser\process($cir);
$filecolumns = $process->get_file_columns();
$mform2 = new admin_uploaduser_form2(null,
['columns' => $filecolumns, 'data' => ['iid' => $iid, 'previewrows' => $previewrows]]);
// If a file has been uploaded, then process it.
if ($formdata = $mform2->is_cancelled()) {
$cir->cleanup(true);
redirect($returnurl);
} else if ($formdata = $mform2->get_data()) {
// Print the header.
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('uploadusersresult', 'tool_uploaduser'));
$process->set_form_data($formdata);
$process->process();
echo $OUTPUT->box_start('boxwidthnarrow boxaligncenter generalbox', 'uploadresults');
echo html_writer::tag('p', join('<br />', $process->get_stats()));
echo $OUTPUT->box_end();
if ($process->get_bulk()) {
echo $OUTPUT->continue_button($bulknurl);
} else {
echo $OUTPUT->continue_button($returnurl);
}
echo $OUTPUT->footer();
die;
}
// Print the header.
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('uploaduserspreview', 'tool_uploaduser'));
// NOTE: this is JUST csv processing preview, we must not prevent import from here if there is something in the file!!
// this was intended for validation of csv formatting and encoding, not filtering the data!!!!
// we definitely must not process the whole file!
// Preview table data.
$table = new \tool_uploaduser\preview($cir, $filecolumns, $previewrows);
echo html_writer::tag('div', html_writer::table($table), ['class' => 'flexible-wrap']);
// Print the form if valid values are available.
if ($table->get_no_error()) {
$mform2->display();
}
echo $OUTPUT->footer();
die;
@@ -0,0 +1 @@
invaliduserdata,tool_uploaduser
@@ -0,0 +1,125 @@
<?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/>.
/**
* Strings for component 'tool_uploaduser', language 'en', branch 'MOODLE_22_STABLE'
*
* @package tool
* @subpackage uploaduser
* @copyright 2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['allowdeletes'] = 'Allow deletes';
$string['allowrenames'] = 'Allow renames';
$string['allowsuspends'] = 'Allow suspending and activating of accounts';
$string['assignedsysrole'] = 'Assigned system role {$a}';
$string['clidefault'] = 'Default:';
$string['clierrorargument'] = 'Value for argument --{$a->name} is not valid. Allowed values: {$a->values}';
$string['clifile'] = 'Path to CSV file with the user data. Required.';
$string['clifilenotreadable'] = 'File {$a} does not exist or is not readable';
$string['clihelp'] = 'Print out this help.';
$string['climissingargument'] = 'Argument --{$a} is required';
$string['clititle'] = 'Command line Upload user tool.';
$string['clivalidationerror'] = 'Validation error:';
$string['csvdelimiter'] = 'CSV separator';
$string['defaultvalues'] = 'Default values';
$string['deleteerrors'] = 'Delete errors';
$string['duplicateemail'] = 'Multiple users with email {$a} detected';
$string['encoding'] = 'Encoding';
$string['errormnetadd'] = 'Can not add remote users';
$string['errorprefix'] = 'Error:';
$string['errors'] = 'Errors';
$string['examplecsv'] = 'Example text file';
$string['examplecsv_help'] = 'To use the example text file, download it then open it with a text or spreadsheet editor. Leave the first line unchanged, then edit the following lines (records) and add your user data, adding more lines as necessary. Save the file as CSV then upload it.
The example text file may also be used for testing, as you are able to preview user data and can choose to cancel the action before user accounts are created.';
$string['infoprefix'] = 'Info:';
$string['invalidupdatetype'] = 'This option cannot be selected with the chosen upload type.';
$string['invaliduserdatavalues'] = 'Incorrect data ({$a->values}) found for user {$a->username}. This data has been corrected or deleted.';
$string['invalidtheme'] = 'Theme "{$a}" is not installed and will be ignored.';
$string['linex'] = 'Line {$a}';
$string['matchemail'] = 'Match on email address';
$string['nochanges'] = 'No changes';
$string['notheme'] = 'No theme is defined for this user.';
$string['pluginname'] = 'User upload';
$string['renameerrors'] = 'Rename errors';
$string['requiredtemplate'] = 'Required. You may use template syntax here (%l = lastname, %f = firstname, %u = username). See help for details and examples.';
$string['rowpreviewnum'] = 'Preview rows';
$string['unassignedsysrole'] = 'Unassigned system role {$a}';
$string['userthemesnotallowed'] = 'User themes are not enabled, so any included in the upload users file will be ignored.';
$string['uploadpicture_baduserfield'] = 'The user attribute specified is not valid. Please, try again.';
$string['uploadpicture_cannotmovezip'] = 'Cannot move zip file to temporary directory.';
$string['uploadpicture_cannotprocessdir'] = 'Cannot process unzipped files.';
$string['uploadpicture_cannotsave'] = 'Cannot save picture for user {$a}. Check original picture file.';
$string['uploadpicture_cannotunzip'] = 'Cannot unzip pictures file.';
$string['uploadpicture_invalidfilename'] = 'Picture file {$a} has invalid characters in its name. Skipping.';
$string['uploadpicture_overwrite'] = 'Overwrite existing user pictures?';
$string['uploadpicture_userfield'] = 'User attribute to use to match pictures:';
$string['uploadpicture_usernotfound'] = 'User with a \'{$a->userfield}\' value of \'{$a->uservalue}\' does not exist. Skipping.';
$string['uploadpicture_userskipped'] = 'Skipping user {$a} (already has a picture).';
$string['uploadpicture_userupdated'] = 'Picture updated for user {$a}.';
$string['uploadpictures'] = 'Upload user pictures';
$string['uploadpictures_help'] = 'User pictures can be uploaded as a zip file of image files. The image files should be named chosen-user-attribute.extension, for example user1234.jpg for a user with username user1234.';
$string['uploadusers'] = 'Upload users';
$string['uploadusers_help'] = 'Users may be uploaded (and optionally enrolled in courses) via text file. The format of the file should be as follows:
* Each line of the file contains one record
* Each record is a series of data separated by the selected separator
* The first record contains a list of fieldnames defining the format of the rest of the file
* Required fieldnames are username, password, firstname, lastname, email';
$string['uploadusers_link'] = 'admin/tool/uploaduser/index';
$string['uploaduserspreview'] = 'Upload users preview';
$string['uploadusersresult'] = 'Upload users results';
$string['uploaduser:uploaduserpictures'] = 'Upload user pictures';
$string['useraccountupdated'] = 'User updated';
$string['useraccountuptodate'] = 'User up-to-date';
$string['userdeleted'] = 'User deleted';
$string['userrenamed'] = 'User renamed';
$string['userscreated'] = 'Users created';
$string['usersdeleted'] = 'Users deleted';
$string['usersrenamed'] = 'Users renamed';
$string['usersskipped'] = 'Users skipped';
$string['usersupdated'] = 'Users updated';
$string['usersweakpassword'] = 'Users having a weak password';
$string['uubulk'] = 'Select for bulk user actions';
$string['uubulkall'] = 'All users';
$string['uubulknew'] = 'New users';
$string['uubulkupdated'] = 'Updated users';
$string['uucsvline'] = 'CSV line';
$string['uulegacy1role'] = '(Original Student) typeN=1';
$string['uulegacy2role'] = '(Original Teacher) typeN=2';
$string['uulegacy3role'] = '(Original Non-editing teacher) typeN=3';
$string['uunoemailduplicates'] = 'Prevent email address duplicates';
$string['uuoptype'] = 'Upload type';
$string['uuoptype_addinc'] = 'Add all, append number to usernames if needed';
$string['uuoptype_addnew'] = 'Add new only, skip existing users';
$string['uuoptype_addupdate'] = 'Add new and update existing users';
$string['uuoptype_update'] = 'Update existing users only';
$string['uupasswordcron'] = 'Generated in cron';
$string['uupasswordnew'] = 'New user password';
$string['uupasswordold'] = 'Existing user password';
$string['uustandardusernames'] = 'Standardise usernames';
$string['uuupdateall'] = 'Override with file and defaults';
$string['uuupdatefromfile'] = 'Override with file';
$string['uuupdatemissing'] = 'Fill in missing from file and defaults';
$string['uuupdatetype'] = 'Existing user details';
$string['uuusernametemplate'] = 'Username template';
$string['privacy:metadata'] = 'The User upload plugin does not store any personal data.';
$string['warningprefix'] = 'Warning:';
// Deprecated since Moodle 4.4.
$string['invaliduserdata'] = 'Invalid data detected for user {$a} and it has been automatically cleaned.';
+531
View File
@@ -0,0 +1,531 @@
<?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 {
/** @var array */
protected $_row;
/**
* The columns shown on the table.
* @var array
*/
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">';
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();
throw new \moodle_exception('cannotreadtmpfile', 'error', $returnurl);
}
if (count($columns) < 2) {
$cir->close();
$cir->cleanup();
throw new \moodle_exception('csvfewcolumns', 'error', $returnurl);
}
// test columns
$processed = array();
$acceptedfields = [
'category',
'categoryrole',
'cohort',
'course',
'enrolperiod',
'enrolstatus',
'enroltimestart',
'group',
'role',
'sysrole',
'type',
];
$specialfieldsregex = "/^(" . implode('|', $acceptedfields) . ")\d+$/";
foreach ($columns as $key=>$unused) {
$field = $columns[$key];
$field = trim($field);
$lcfield = core_text::strtolower($field);
if (in_array($field, $stdfields) or in_array($lcfield, $stdfields)) {
// standard fields are only lowercase
$newfield = $lcfield;
} else if (in_array($field, $profilefields)) {
// exact profile field name match - these are case sensitive
$newfield = $field;
} else if (in_array($lcfield, $profilefields)) {
// hack: somebody wrote uppercase in csv file, but the system knows only lowercase profile field
$newfield = $lcfield;
} else if (preg_match($specialfieldsregex, $lcfield)) {
// special fields for enrolments
$newfield = $lcfield;
} else {
$cir->close();
$cir->cleanup();
throw new \moodle_exception('invalidfieldname', 'error', $returnurl, $field);
}
if (in_array($newfield, $processed)) {
$cir->close();
$cir->cleanup();
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 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(?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 = [];
// A role can be searched for by its ID or by its shortname.
foreach ($allowedroles as $rid=>$rname) {
$rolecache[$rid] = new stdClass();
$rolecache[$rid]->id = $rid;
$rolecache[$rid]->name = $rname;
// 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;
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) {
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, 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);
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;
}
// Ensure unique field value doesn't already exist in supplied data.
$formfieldunique = $formfield->is_unique() && ($value !== '' || $formfield->is_required());
if ($formfieldunique && array_key_exists($shortname, $profilefieldvalues) &&
(array_search($value, $profilefieldvalues[$shortname]) !== false)) {
$data['status'][] = get_string('valuealreadyused') . " ({$key})";
$noerror = false;
}
// Check for duplicate value.
if (method_exists($formfield, 'edit_validate_field') ) {
$testuser = new stdClass();
$testuser->{$key} = $value;
$testuser->id = $testuserid;
$err = $formfield->edit_validate_field($testuser);
if (!empty($err[$key])) {
$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;
}
+252
View File
@@ -0,0 +1,252 @@
<?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 upload of user pictures
*
* Based on .../admin/uploaduser.php and .../lib/gdlib.php
*
* @package tool
* @subpackage uploaduser
* @copyright (C) 2007 Inaki Arenaza
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require('../../../config.php');
require_once($CFG->libdir.'/adminlib.php');
require_once($CFG->libdir.'/gdlib.php');
require_once('picture_form.php');
define ('PIX_FILE_UPDATED', 0);
define ('PIX_FILE_ERROR', 1);
define ('PIX_FILE_SKIPPED', 2);
admin_externalpage_setup('tooluploaduserpictures');
require_capability('tool/uploaduser:uploaduserpictures', context_system::instance());
$site = get_site();
if (!$adminuser = get_admin()) {
throw new \moodle_exception('noadmins', 'error');
}
$strfile = get_string('file');
$struser = get_string('user');
$strusersupdated = get_string('usersupdated', 'tool_uploaduser');
$struploadpictures = get_string('uploadpictures','tool_uploaduser');
$userfields = array (
0 => 'username',
1 => 'idnumber',
2 => 'id' );
$userfield = optional_param('userfield', 0, PARAM_INT);
$overwritepicture = optional_param('overwritepicture', 0, PARAM_BOOL);
/// Print the header
echo $OUTPUT->header();
echo $OUTPUT->heading_with_help($struploadpictures, 'uploadpictures', 'tool_uploaduser');
$mform = new admin_uploadpicture_form(null, $userfields);
if ($formdata = $mform->get_data()) {
if (!array_key_exists($userfield, $userfields)) {
echo $OUTPUT->notification(get_string('uploadpicture_baduserfield', 'tool_uploaduser'));
} else {
// Large files are likely to take their time and memory. Let PHP know
// that we'll take longer, and that the process should be recycled soon
// to free up memory.
core_php_time_limit::raise();
raise_memory_limit(MEMORY_EXTRA);
// Create a unique temporary directory, to process the zip file
// contents.
$zipdir = my_mktempdir($CFG->tempdir.'/', 'usrpic');
$dstfile = $zipdir.'/images.zip';
if (!$mform->save_file('userpicturesfile', $dstfile, true)) {
echo $OUTPUT->notification(get_string('uploadpicture_cannotmovezip', 'tool_uploaduser'));
@remove_dir($zipdir);
} else {
$fp = get_file_packer('application/zip');
$unzipresult = $fp->extract_to_pathname($dstfile, $zipdir);
if (!$unzipresult) {
echo $OUTPUT->notification(get_string('uploadpicture_cannotunzip', 'tool_uploaduser'));
@remove_dir($zipdir);
} else {
// We don't need the zip file any longer, so delete it to make
// it easier to process the rest of the files inside the directory.
@unlink($dstfile);
$results = array ('errors' => 0,'updated' => 0);
process_directory($zipdir, $userfields[$userfield], $overwritepicture, $results);
// Finally remove the temporary directory with all the user images and print some stats.
remove_dir($zipdir);
echo $OUTPUT->notification(get_string('usersupdated', 'tool_uploaduser') . ": " . $results['updated'], 'notifysuccess');
echo $OUTPUT->notification(get_string('errors', 'tool_uploaduser') . ": " . $results['errors'], ($results['errors'] ? 'notifyproblem' : 'notifysuccess'));
echo '<hr />';
}
}
}
}
$mform->display();
echo $OUTPUT->footer();
exit;
// ----------- Internal functions ----------------
/**
* Create a unique temporary directory with a given prefix name,
* inside a given directory, with given permissions. Return the
* full path to the newly created temp directory.
*
* @param string $dir where to create the temp directory.
* @param string $prefix prefix for the temp directory name (default '')
*
* @return string The full path to the temp directory.
*/
function my_mktempdir($dir, $prefix='') {
global $CFG;
if (substr($dir, -1) != '/') {
$dir .= '/';
}
do {
$path = $dir.$prefix.mt_rand(0, 9999999);
} while (file_exists($path));
check_dir_exists($path);
return $path;
}
/**
* Recursively process a directory, picking regular files and feeding
* them to process_file().
*
* @param string $dir the full path of the directory to process
* @param string $userfield the prefix_user table field to use to
* match picture files to users.
* @param bool $overwrite overwrite existing picture or not.
* @param array $results (by reference) accumulated statistics of
* users updated and errors.
*
* @return nothing
*/
function process_directory ($dir, $userfield, $overwrite, &$results) {
global $OUTPUT;
if(!($handle = opendir($dir))) {
echo $OUTPUT->notification(get_string('uploadpicture_cannotprocessdir', 'tool_uploaduser'));
return;
}
while (false !== ($item = readdir($handle))) {
if ($item != '.' && $item != '..') {
if (is_dir($dir.'/'.$item)) {
process_directory($dir.'/'.$item, $userfield, $overwrite, $results);
} else if (is_file($dir.'/'.$item)) {
$result = process_file($dir.'/'.$item, $userfield, $overwrite);
switch ($result) {
case PIX_FILE_ERROR:
$results['errors']++;
break;
case PIX_FILE_UPDATED:
$results['updated']++;
break;
}
}
// Ignore anything else that is not a directory or a file (e.g.,
// symbolic links, sockets, pipes, etc.)
}
}
closedir($handle);
}
/**
* Given the full path of a file, try to find the user the file
* corresponds to and assign him/her this file as his/her picture.
* Make extensive checks to make sure we don't open any security holes
* and report back any success/error.
*
* @param string $file the full path of the file to process
* @param string $userfield the prefix_user table field to use to
* match picture files to users.
* @param bool $overwrite overwrite existing picture or not.
*
* @return integer either PIX_FILE_UPDATED, PIX_FILE_ERROR or
* PIX_FILE_SKIPPED
*/
function process_file ($file, $userfield, $overwrite) {
global $DB, $OUTPUT;
// Add additional checks on the filenames, as they are user
// controlled and we don't want to open any security holes.
$path_parts = pathinfo(cleardoubleslashes($file));
$basename = $path_parts['basename'];
$extension = $path_parts['extension'];
// The picture file name (without extension) must match the
// userfield attribute.
$uservalue = substr($basename, 0,
strlen($basename) -
strlen($extension) - 1);
// userfield names are safe, so don't quote them.
if (!($user = $DB->get_record('user', array ($userfield => $uservalue, 'deleted' => 0)))) {
$a = new stdClass();
$a->userfield = clean_param($userfield, PARAM_CLEANHTML);
$a->uservalue = clean_param($uservalue, PARAM_CLEANHTML);
echo $OUTPUT->notification(get_string('uploadpicture_usernotfound', 'tool_uploaduser', $a));
return PIX_FILE_ERROR;
}
$haspicture = $DB->get_field('user', 'picture', array('id'=>$user->id));
if ($haspicture && !$overwrite) {
echo $OUTPUT->notification(get_string('uploadpicture_userskipped', 'tool_uploaduser', $user->username));
return PIX_FILE_SKIPPED;
}
if ($newrev = my_save_profile_image($user->id, $file)) {
$DB->set_field('user', 'picture', $newrev, array('id'=>$user->id));
echo $OUTPUT->notification(get_string('uploadpicture_userupdated', 'tool_uploaduser', $user->username), 'notifysuccess');
return PIX_FILE_UPDATED;
} else {
echo $OUTPUT->notification(get_string('uploadpicture_cannotsave', 'tool_uploaduser', $user->username));
return PIX_FILE_ERROR;
}
}
/**
* Try to save the given file (specified by its full path) as the
* picture for the user with the given id.
*
* @param integer $id the internal id of the user to assign the
* picture file to.
* @param string $originalfile the full path of the picture file.
*
* @return mixed new unique revision number or false if not saved
*/
function my_save_profile_image($id, $originalfile) {
$context = context_user::instance($id);
return process_new_icon($context, 'user', 'icon', 0, $originalfile);
}
+55
View File
@@ -0,0 +1,55 @@
<?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 picture upload form
*
* @package tool
* @subpackage uploaduser
* @copyright (C) 2007 Inaki Arenaza
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once $CFG->libdir.'/formslib.php';
class admin_uploadpicture_form extends moodleform {
function definition(){
global $CFG, $USER;
$mform =& $this->_form;
$mform->addElement('header', 'settingsheader', get_string('upload'));
$options = array();
$options['accepted_types'] = array('.zip');
$mform->addElement('filepicker', 'userpicturesfile', get_string('file'), 'size="40"', $options);
$mform->addRule('userpicturesfile', null, 'required');
$choices =& $this->_customdata;
$mform->addElement('select', 'userfield', get_string('uploadpicture_userfield', 'tool_uploaduser'), $choices);
$mform->setType('userfield', PARAM_INT);
$choices = array( 0 => get_string('no'), 1 => get_string('yes') );
$mform->addElement('select', 'overwritepicture', get_string('uploadpicture_overwrite', 'tool_uploaduser'), $choices);
$mform->setType('overwritepicture', PARAM_INT);
$this->add_action_buttons(false, get_string('uploadpictures', 'tool_uploaduser'));
}
}
+29
View File
@@ -0,0 +1,29 @@
<?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/>.
/**
* Link to CSV user upload
*
* @package tool
* @subpackage uploaduser
* @copyright 2010 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$ADMIN->add('accounts', new admin_externalpage('tooluploaduser', get_string('uploadusers', 'tool_uploaduser'), "$CFG->wwwroot/$CFG->admin/tool/uploaduser/index.php", 'moodle/site:uploadusers'));
$ADMIN->add('accounts', new admin_externalpage('tooluploaduserpictures', get_string('uploadpictures','tool_uploaduser'), "$CFG->wwwroot/$CFG->admin/tool/uploaduser/picture.php", 'tool/uploaduser:uploaduserpictures'));
@@ -0,0 +1,320 @@
@tool @tool_uploaduser @_file_upload
Feature: Upload users
In order to add users to the system
As an admin
I need to upload files containing the users data
@javascript
Scenario: Upload users enrolling them on courses and groups
Given the following "courses" exist:
| fullname | shortname | category |
| Maths | math102 | 0 |
And the following "groups" exist:
| name | course | idnumber |
| Section 1 | math102 | S1 |
| Section 3 | math102 | S3 |
And I log in as "admin"
And I navigate to "Users > Accounts > Upload users" in site administration
When I upload "lib/tests/fixtures/upload_users.csv" file to "File" filemanager
And I press "Upload users"
Then I should see "Upload users preview"
And I should see "Tom"
And I should see "Jones"
And I should see "verysecret"
And I should see "jonest@example.com"
And I should see "Reznor"
And I should see "course1"
And I should see "math102"
And I should see "group1"
And I should see "Section 1"
And I press "Upload users"
And I press "Continue"
And I navigate to "Users > Accounts > Browse list of users" in site administration
And I should see "Tom Jones"
And I should see "Trent Reznor"
And I should see "reznor@example.com"
And I am on the "Maths" "groups" page
And I set the field "groups" to "Section 1 (1)"
And the "members" select box should contain "Tom Jones (jonest@example.com)"
@javascript
Scenario: Upload users enrolling them on courses and groups applying defaults
Given the following "courses" exist:
| fullname | shortname | category |
| Maths | math102 | 0 |
And the following "groups" exist:
| name | course | idnumber |
| Section 1 | math102 | S1 |
| Section 3 | math102 | S3 |
And I log in as "admin"
And I navigate to "Users > Accounts > Upload users" in site administration
When I upload "lib/tests/fixtures/upload_users.csv" file to "File" filemanager
And I press "Upload users"
And I should see "Upload users preview"
And I set the following fields to these values:
| City/town | Brighton |
| Department | Purchasing |
And I press "Upload users"
And I press "Continue"
And I navigate to "Users > Accounts > Browse list of users" in site administration
And I should see "Tom Jones"
And I follow "Tom Jones"
And I follow "Edit profile"
And the field "City/town" matches value "Brighton"
And the field "Department" matches value "Purchasing"
@javascript
Scenario: Upload users with custom profile fields
# Create user profile field.
Given the following "custom profile fields" exist:
| datatype | shortname | name |
| text | superfield | Super field |
And I log in as "admin"
# Upload users.
When I navigate to "Users > Accounts > Upload users" in site administration
And I upload "lib/tests/fixtures/upload_users_profile.csv" file to "File" filemanager
And I press "Upload users"
And I should see "Upload users preview"
And I press "Upload users"
# Check that users were created and the superfield is filled.
And I navigate to "Users > Accounts > Browse list of users" in site administration
And I follow "Tom Jones"
And I should see "Super field"
And I should see "The big guy"
And I log out
@javascript
Scenario: Upload users setting their email stop value
Given I log in as "admin"
And I navigate to "Users > Accounts > Upload users" in site administration
When I upload "lib/tests/fixtures/upload_users_emailstop.csv" file to "File" filemanager
And I press "Upload users"
Then I should see "Upload users preview"
And the following should exist in the "uupreview" table:
| CSV line | username | emailstop |
| 2 | jbloggs | 1 |
| 3 | fbloggs | 0 |
And I press "Upload users"
And I should see "Users created: 2"
And I log out
@javascript
Scenario: Upload users setting their user theme
Given the following "courses" exist:
| fullname | shortname | category |
| Maths | math102 | 0 |
# We need to do a bit of setup here.
And I change window size to "large"
And I log in as "admin"
And I navigate to "Security > Site security settings" in site administration
And I click on "Password policy" "checkbox"
And I click on "Save changes" "button"
And I navigate to "Appearance > Advanced theme settings" in site administration
And I click on "Allow user themes" "checkbox"
And I click on "Save changes" "button"
# Upload the users.
And I navigate to "Users > Accounts > Upload users" in site administration
When I upload "lib/tests/fixtures/upload_users_themes.csv" file to "File" filemanager
And I press "Upload users"
Then I should see "Upload users preview"
And I should see "boost"
And I should see "classic"
And I should see "No theme is defined for this user."
And I should see "Theme \"somefaketheme\" is not installed and will be ignored."
And I press "Upload users"
And I should see "Users created: 4"
And I press "Continue"
# Boost check.
And I am on the "jonest@example.com" "user > editing" page
And I should see "Boost"
# Classic check.
And I am on the "reznor@example.com" "user > editing" page
And I should see "Classic"
@javascript
Scenario: Upload users setting their user theme when allowuserthemes is false
Given the following "courses" exist:
| fullname | shortname | category |
| Maths | math102 | 0 |
# We need to do a bit of setup here.
And I change window size to "large"
And I log in as "admin"
And I navigate to "Security > Site security settings" in site administration
And I click on "Password policy" "checkbox"
And I click on "Save changes" "button"
# Upload the users.
And I navigate to "Users > Accounts > Upload users" in site administration
When I upload "lib/tests/fixtures/upload_users_themes.csv" file to "File" filemanager
And I press "Upload users"
Then I should see "Upload users preview"
And I should see "boost"
And I should see "classic"
And I press "Upload users"
And I should see "User themes are not enabled, so any included in the upload users file will be ignored."
And I should see "Users created: 4"
And I press "Continue"
And I log out
@javascript
Scenario: Upload users setting their enrol date and period
Given the following "courses" exist:
| fullname | shortname | category |
| Maths | math102 | 0 |
# Upload the users.
And I change window size to "large"
And I log in as "admin"
And I navigate to "Users > Accounts > Upload users" in site administration
When I upload "lib/tests/fixtures/upload_users_enrol_date_period.csv" file to "File" filemanager
And I press "Upload users"
Then I should see "Upload users preview"
And I press "Upload users"
# Check user enrolment start date and period
And I am on "Maths" course homepage
Then I navigate to course participants
And I click on "Manual enrolments" "link" in the "Student One" "table_row"
Then I should see "1 January 2019" in the "Enrolment starts" "table_row"
And I should not see "Enrolment ends"
And I click on "Close" "button" in the "Enrolment details" "dialogue"
And I click on "Manual enrolments" "link" in the "Student Two" "table_row"
Then I should see "2 January 2020" in the "Enrolment starts" "table_row"
And I should see "12 January 2020" in the "Enrolment ends" "table_row"
And I click on "Close" "button" in the "Enrolment details" "dialogue"
And I log out
@javascript
Scenario: Upload users enrolling them on courses and assign category roles
Given the following "courses" exist:
| fullname | shortname |
| management1 | management1 |
| film1 | film1 |
And the following "categories" exist:
| name | idnumber |
| MGMT | MGMT |
| Film | Film |
And I log in as "admin"
And I navigate to "Users > Accounts > Upload users" in site administration
When I upload "lib/tests/fixtures/upload_users_category.csv" file to "File" filemanager
And I press "Upload users"
Then I should see "Upload users preview"
And I should see "Tom"
And I should see "Jones"
And I should see "Trent"
And I should see "Reznor"
And I should see "Aurora"
And I should see "Jiang"
And I should see "Federico"
And I should see "Fellini"
And I should see "Ivan"
And I should see "Ivanov"
And I should see "John"
And I should see "Smith"
And I should see "Warm"
And I should see "Cool"
And I should see "James"
And I should see "Bond"
And I should see "MGMT"
And I should see "Film"
And I should see "manager"
And I should see "student"
And I should see "coursecreator"
And I should see "management1"
And I should see "film1"
And I press "Upload users"
And I should see "Unknown category with category ID number \"Movie\""
And I should see "Unknown course named \"movie1\""
And I should see "Unknown role \"notcoursecreator\""
And I should see "Could not assign role to user: missing role for category"
And I press "Continue"
And I navigate to "Users > Accounts > Browse list of users" in site administration
And I should see "Tom Jones"
And I should see "Trent Reznor"
And I should see "reznor@example.com"
And I am on the "management1" "enrolled users" page
And I should see "Tom Jones"
And I should see "Trent Reznor"
And I should see "Aurora Jiang"
And I should see "Student"
And I am on the "film1" "enrolled users" page
And I should see "Federico Fellini"
And I should see "Student"
And I am on site homepage
And I navigate to "Courses > Manage courses and categories" in site administration
And I click on "permissions" action for "MGMT" in management category listing
And I set the field "Participants tertiary navigation" to "Assign roles"
And I should see "Manager"
And I should see "Tom Jones"
And I should see "Trent Reznor"
And I should see "Course creator"
And I should see "Aurora Jiang"
And I am on site homepage
And I navigate to "Courses > Manage courses and categories" in site administration
And I click on "permissions" action for "Film" in management category listing
And I set the field "Participants tertiary navigation" to "Assign roles"
And I should see "Course creator"
And I should see "Federico Fellini"
@javascript
Scenario: Update existing users matching them on email
Given the following "users" exist:
| username | firstname | lastname | email |
| bilbob | Blasbo | Blabbins | bilbo@example.com |
| frodob | Frodeo | Baspins | frodo@example.com |
And I log in as "admin"
And I navigate to "Users > Accounts >Upload users" in site administration
When I upload "lib/tests/fixtures/upload_users_email_matching.csv" file to "File" filemanager
And I press "Upload users"
Then I should see "Upload users preview"
And I set the following fields to these values:
| Upload type | Update existing users only |
| Existing user details | Override with file |
| Match on email address | Yes |
And I press "Upload users"
And I press "Continue"
And I navigate to "Users > Accounts > Browse list of users" in site administration
And I should see "Bilbo Baggins"
And I should see "Frodo Baggins"
@javascript
Scenario: Update existing users matching them on email where one email address is associated with multiple users
Given the following "users" exist:
| username | firstname | lastname | email |
| bilbob | Blasbo | Blabbins | bilbo@example.com |
| frodob | Frodeo | Baspins | frodo@example.com |
| fredob | Fredoo | Baspins | frodo@example.com |
And I log in as "admin"
And I navigate to "Users > Accounts > Upload users" in site administration
When I upload "lib/tests/fixtures/upload_users_email_matching.csv" file to "File" filemanager
And I press "Upload users"
Then I should see "Upload users preview"
And I set the following fields to these values:
| Upload type | Update existing users only |
| Existing user details | Override with file |
| Match on email address | Yes |
And I press "Upload users"
And I should see "Multiple users with email frodo@example.com detected"
And I press "Continue"
And I navigate to "Users > Accounts > Browse list of users" in site administration
And I should see "Bilbo Baggins"
And I should not see "Frodo Baggins"
@javascript
Scenario: Create a new user when matching them on email where where the username already exists
Given the following "users" exist:
| username | firstname | lastname | email |
| bilbob | Samwise | Gamgee | samwise@example.com |
| frodob | Frodeo | Baspins | frodo@example.com |
And I log in as "admin"
And I navigate to "Users > Accounts > Upload users" in site administration
When I upload "lib/tests/fixtures/upload_users_email_matching.csv" file to "File" filemanager
And I press "Upload users"
Then I should see "Upload users preview"
And I set the following fields to these values:
| Upload type | Add new and update existing users |
| Existing user details | Override with file |
| Match on email address | Yes |
And I press "Upload users"
And I should see "User not added - username already exists under a different email"
And I press "Continue"
And I navigate to "Users > Accounts > Browse list of users" in site administration
And I should see "Samwise Gamgee"
And I should see "Frodo Baggins"
+287
View File
@@ -0,0 +1,287 @@
<?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/>.
namespace tool_uploaduser;
/**
* Tests for CLI tool_uploaduser.
*
* @package tool_uploaduser
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cli_test extends \advanced_testcase {
/**
* Generate cli_helper and mock $_SERVER['argv']
*
* @param array $mockargv
* @return \tool_uploaduser\cli_helper
*/
protected function construct_helper(array $mockargv = []) {
if (array_key_exists('argv', $_SERVER)) {
$oldservervars = $_SERVER['argv'];
}
$_SERVER['argv'] = array_merge([''], $mockargv);
$clihelper = new cli_helper(\tool_uploaduser\local\text_progress_tracker::class);
if (isset($oldservervars)) {
$_SERVER['argv'] = $oldservervars;
} else {
unset($_SERVER['argv']);
}
return $clihelper;
}
/**
* Tests simple upload with course enrolment and group allocation
*/
public function test_upload_with_course_enrolment(): void {
global $CFG;
$this->resetAfterTest();
set_config('passwordpolicy', 0);
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course(['fullname' => 'Maths', 'shortname' => 'math102']);
$g1 = $this->getDataGenerator()->create_group(['courseid' => $course->id, 'name' => 'Section 1', 'idnumber' => 'S1']);
$g2 = $this->getDataGenerator()->create_group(['courseid' => $course->id, 'name' => 'Section 3', 'idnumber' => 'S3']);
$filepath = $CFG->dirroot.'/lib/tests/fixtures/upload_users.csv';
$clihelper = $this->construct_helper(["--file=$filepath"]);
ob_start();
$clihelper->process();
$output = ob_get_contents();
ob_end_clean();
// CLI output suggests that 2 users were created.
$stats = $clihelper->get_stats();
$this->assertEquals(2, preg_match_all('/New user/', $output));
$this->assertEquals('Users created: 2', $stats[0]);
// Tom Jones and Trent Reznor are enrolled into the course, first one to group $g1 and second to group $g2.
$enrols = array_values(enrol_get_course_users($course->id));
$this->assertEqualsCanonicalizing(['reznor', 'jonest'], [$enrols[0]->username, $enrols[1]->username]);
$g1members = groups_get_groups_members($g1->id);
$this->assertEquals(1, count($g1members));
$this->assertEquals('Jones', $g1members[key($g1members)]->lastname);
$g2members = groups_get_groups_members($g2->id);
$this->assertEquals(1, count($g2members));
$this->assertEquals('Reznor', $g2members[key($g2members)]->lastname);
}
/**
* Test applying defaults during the user upload
*/
public function test_upload_with_applying_defaults(): void {
global $CFG;
$this->resetAfterTest();
set_config('passwordpolicy', 0);
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course(['fullname' => 'Maths', 'shortname' => 'math102']);
$g1 = $this->getDataGenerator()->create_group(['courseid' => $course->id, 'name' => 'Section 1', 'idnumber' => 'S1']);
$g2 = $this->getDataGenerator()->create_group(['courseid' => $course->id, 'name' => 'Section 3', 'idnumber' => 'S3']);
$filepath = $CFG->dirroot.'/lib/tests/fixtures/upload_users.csv';
$clihelper = $this->construct_helper(["--file=$filepath", '--city=Brighton', '--department=Purchasing']);
ob_start();
$clihelper->process();
$output = ob_get_contents();
ob_end_clean();
// CLI output suggests that 2 users were created.
$stats = $clihelper->get_stats();
$this->assertEquals(2, preg_match_all('/New user/', $output));
$this->assertEquals('Users created: 2', $stats[0]);
// Users have default values applied.
$user1 = \core_user::get_user_by_username('jonest');
$this->assertEquals('Brighton', $user1->city);
$this->assertEquals('Purchasing', $user1->department);
}
/**
* User upload with user profile fields
*/
public function test_upload_with_profile_fields(): void {
global $CFG;
$this->resetAfterTest();
set_config('passwordpolicy', 0);
$this->setAdminUser();
$this->getDataGenerator()->create_custom_profile_field([
'shortname' => 'superfield', 'name' => 'Super field',
'datatype' => 'text', 'signup' => 1, 'visible' => 1, 'required' => 1, 'sortorder' => 1]);
$filepath = $CFG->dirroot.'/lib/tests/fixtures/upload_users_profile.csv';
$clihelper = $this->construct_helper(["--file=$filepath"]);
ob_start();
$clihelper->process();
$output = ob_get_contents();
ob_end_clean();
// CLI output suggests that 2 users were created.
$stats = $clihelper->get_stats();
$this->assertEquals(2, preg_match_all('/New user/', $output));
$this->assertEquals('Users created: 2', $stats[0]);
// Created users have data in the profile fields.
$user1 = \core_user::get_user_by_username('reznort');
$profilefields1 = profile_user_record($user1->id);
$this->assertObjectHasProperty('superfield', $profilefields1);
$this->assertEquals('Loves cats', $profilefields1->superfield);
}
/**
* Testing that help for CLI does not throw errors
*/
public function test_cli_help(): void {
$this->resetAfterTest();
$this->setAdminUser();
$clihelper = $this->construct_helper(["--help"]);
ob_start();
$clihelper->print_help();
$output = ob_get_contents();
ob_end_clean();
// Basically a test that everything can be parsed and displayed without errors. Check that some options are present.
$this->assertEquals(1, preg_match('/--delimiter_name=VALUE/', $output));
$this->assertEquals(1, preg_match('/--uutype=VALUE/', $output));
$this->assertEquals(1, preg_match('/--auth=VALUE/', $output));
}
/**
* Testing skipped user when one exists
*/
public function test_create_when_user_exists(): void {
global $CFG;
$this->resetAfterTest();
set_config('passwordpolicy', 0);
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course(['fullname' => 'Maths', 'shortname' => 'math102']);
$g1 = $this->getDataGenerator()->create_group(['courseid' => $course->id, 'name' => 'Section 1', 'idnumber' => 'S1']);
$g2 = $this->getDataGenerator()->create_group(['courseid' => $course->id, 'name' => 'Section 3', 'idnumber' => 'S3']);
// Create a user with username jonest.
$user1 = $this->getDataGenerator()->create_user(['username' => 'jonest', 'email' => 'jonest@someplace.edu']);
$filepath = $CFG->dirroot.'/lib/tests/fixtures/upload_users.csv';
$clihelper = $this->construct_helper(["--file=$filepath"]);
ob_start();
$clihelper->process();
$output = ob_get_contents();
ob_end_clean();
// CLI output suggests that 1 user was created and 1 skipped.
$stats = $clihelper->get_stats();
$this->assertEquals(1, preg_match_all('/New user/', $output));
$this->assertEquals('Users created: 1', $stats[0]);
$this->assertEquals('Users skipped: 1', $stats[1]);
// Trent Reznor is enrolled into the course, Tom Jones is not!
$enrols = array_values(enrol_get_course_users($course->id));
$this->assertEqualsCanonicalizing(['reznor'], [$enrols[0]->username]);
}
/**
* Testing update mode - do not update user records but allow enrolments
*/
public function test_enrolments_when_user_exists(): void {
global $CFG;
require_once($CFG->dirroot.'/'.$CFG->admin.'/tool/uploaduser/locallib.php');
$this->resetAfterTest();
set_config('passwordpolicy', 0);
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course(['fullname' => 'Maths', 'shortname' => 'math102']);
$g1 = $this->getDataGenerator()->create_group(['courseid' => $course->id, 'name' => 'Section 1', 'idnumber' => 'S1']);
$g2 = $this->getDataGenerator()->create_group(['courseid' => $course->id, 'name' => 'Section 3', 'idnumber' => 'S3']);
// Create a user with username jonest.
$this->getDataGenerator()->create_user(['username' => 'jonest', 'email' => 'jonest@someplace.edu',
'firstname' => 'OLDNAME']);
$filepath = $CFG->dirroot.'/lib/tests/fixtures/upload_users.csv';
$clihelper = $this->construct_helper(["--file=$filepath", '--uutype='.UU_USER_UPDATE]);
ob_start();
$clihelper->process();
$output = ob_get_contents();
ob_end_clean();
// CLI output suggests that 1 user was created and 1 skipped.
$stats = $clihelper->get_stats();
$this->assertEquals(0, preg_match_all('/New user/', $output));
$this->assertEquals('Users updated: 0', $stats[0]);
$this->assertEquals('Users skipped: 1', $stats[1]);
// Tom Jones is enrolled into the course.
$enrols = array_values(enrol_get_course_users($course->id));
$this->assertEqualsCanonicalizing(['jonest'], [$enrols[0]->username]);
// User reznor is not created.
$this->assertFalse(\core_user::get_user_by_username('reznor'));
// User jonest is not updated.
$this->assertEquals('OLDNAME', \core_user::get_user_by_username('jonest')->firstname);
}
/**
* Testing update mode - update user records and perform enrolments.
*/
public function test_udpate_user(): void {
global $CFG;
require_once($CFG->dirroot.'/'.$CFG->admin.'/tool/uploaduser/locallib.php');
$this->resetAfterTest();
set_config('passwordpolicy', 0);
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course(['fullname' => 'Maths', 'shortname' => 'math102']);
$g1 = $this->getDataGenerator()->create_group(['courseid' => $course->id, 'name' => 'Section 1', 'idnumber' => 'S1']);
$g2 = $this->getDataGenerator()->create_group(['courseid' => $course->id, 'name' => 'Section 3', 'idnumber' => 'S3']);
// Create a user with username jonest.
$this->getDataGenerator()->create_user(['username' => 'jonest',
'email' => 'jonest@someplace.edu', 'firstname' => 'OLDNAME']);
$filepath = $CFG->dirroot.'/lib/tests/fixtures/upload_users.csv';
$clihelper = $this->construct_helper(["--file=$filepath", '--uutype='.UU_USER_UPDATE,
'--uuupdatetype='.UU_UPDATE_FILEOVERRIDE]);
ob_start();
$clihelper->process();
$output = ob_get_contents();
ob_end_clean();
// CLI output suggests that 1 user was created and 1 skipped.
$stats = $clihelper->get_stats();
$this->assertEquals(0, preg_match_all('/New user/', $output));
$this->assertEquals('Users updated: 1', $stats[0]);
$this->assertEquals('Users skipped: 1', $stats[1]);
// Tom Jones is enrolled into the course.
$enrols = array_values(enrol_get_course_users($course->id));
$this->assertEqualsCanonicalizing(['jonest'], [$enrols[0]->username]);
// User reznor is not created.
$this->assertFalse(\core_user::get_user_by_username('reznor'));
// User jonest is updated, new first name is Tom.
$this->assertEquals('Tom', \core_user::get_user_by_username('jonest')->firstname);
}
}
@@ -0,0 +1,72 @@
<?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/>.
namespace tool_uploaduser;
use tool_uploaduser\local\field_value_validators;
/**
* Tests for field value validators of tool_uploaduser.
*
* @package tool_uploaduser
* @copyright 2019 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class field_value_validators_test extends \advanced_testcase {
/**
* Data provider for \field_value_validators_testcase::test_validate_theme().
*/
public function themes_provider() {
return [
'User themes disabled' => [
false, 'boost', 'warning', get_string('userthemesnotallowed', 'tool_uploaduser')
],
'User themes enabled, empty theme' => [
true, '', 'warning', get_string('notheme', 'tool_uploaduser')
],
'User themes enabled, invalid theme' => [
true, 'badtheme', 'warning', get_string('invalidtheme', 'tool_uploaduser', 'badtheme')
],
'User themes enabled, valid theme' => [
true, 'boost', 'normal', ''
],
];
}
/**
* Unit test for \tool_uploaduser\local\field_value_validators::validate_theme()
*
* @dataProvider themes_provider
* @param boolean $userthemesallowed Whether to allow user themes.
* @param string $themename The theme name to be tested.
* @param string $expectedstatus The expected status.
* @param string $expectedmessage The expected validation message.
*/
public function test_validate_theme($userthemesallowed, $themename, $expectedstatus, $expectedmessage): void {
$this->resetAfterTest();
// Set value for $CFG->allowuserthemes.
set_config('allowuserthemes', $userthemesallowed);
// Validate the theme.
list($status, $message) = field_value_validators::validate_theme($themename);
// Check the status and validation message.
$this->assertEquals($expectedstatus, $status);
$this->assertEquals($expectedmessage, $message);
}
}
@@ -0,0 +1,241 @@
<?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/>.
namespace tool_uploaduser;
use advanced_testcase;
use context_system;
use context_course;
use context_coursecat;
use stdClass;
use tool_uploaduser\cli_helper;
use tool_uploaduser\local\text_progress_tracker;
/**
* Class upload_users_test
*
* @package tool_uploaduser
* @copyright 2020 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class upload_users_test extends advanced_testcase {
/**
* Load required test libraries
*/
public static function setUpBeforeClass(): void {
global $CFG;
require_once("{$CFG->dirroot}/{$CFG->admin}/tool/uploaduser/locallib.php");
}
/**
* Test upload users, enrol and role assignation
* @covers \tool_uploadusers::process
*/
public function test_user_can_upload_with_course_enrolment(): void {
$this->resetAfterTest();
set_config('passwordpolicy', 0);
$this->setAdminUser();
// Create category and course.
$coursecat = $this->getDataGenerator()->create_category();
$coursecatcontext = context_coursecat::instance($coursecat->id);
$course = $this->getDataGenerator()->create_course(['shortname' => 'course01', 'category' => $coursecat->id]);
$coursecontext = context_course::instance($course->id);
// Create user.
$user = $this->getDataGenerator()->create_user();
// Create role with capability to upload CSV files, and assign this role to user.
$uploadroleid = create_role('upload role', 'uploadrole', '');
set_role_contextlevels($uploadroleid, [CONTEXT_SYSTEM]);
$systemcontext = context_system::instance();
assign_capability('moodle/site:uploadusers', CAP_ALLOW, $uploadroleid, $systemcontext->id);
$this->getDataGenerator()->role_assign($uploadroleid, $user->id, $systemcontext->id);
// Create role with some of allowed capabilities to enrol users, and assign this role to user.
$enrolroleid = create_role('enrol role', 'enrolrole', '');
set_role_contextlevels($enrolroleid, [CONTEXT_COURSECAT]);
assign_capability('enrol/manual:enrol', CAP_ALLOW, $enrolroleid, $coursecatcontext->id);
assign_capability('moodle/course:enrolreview', CAP_ALLOW, $enrolroleid, $coursecatcontext->id);
assign_capability('moodle/role:assign', CAP_ALLOW, $enrolroleid, $coursecatcontext->id);
$this->getDataGenerator()->role_assign($enrolroleid, $user->id, $coursecatcontext->id);
// User makes assignments.
$studentarch = get_archetype_roles('student');
$studentrole = array_shift($studentarch);
core_role_set_assign_allowed($enrolroleid, $studentrole->id);
// Flush accesslib.
accesslib_clear_all_caches_for_unit_testing();
// Process CSV file as user.
$csv = <<<EOF
username,firstname,lastname,email,course1,role1
student1,Student,One,s1@example.com,{$course->shortname},{$studentrole->shortname}
student2,Student,Two,s2@example.com,{$course->shortname},teacher
EOF;
$this->setUser($user);
$output = $this->process_csv_upload($csv, ['--uutype=' . UU_USER_ADDNEW]);
$this->assertStringContainsString('Enrolled in "course01" as "student"', $output);
$this->assertStringContainsString('Unknown role "teacher"', $output);
// Check user creation, enrolment and role assignation.
$this->assertEquals(1, count_enrolled_users($coursecontext));
$usersasstudent = get_role_users($studentrole->id, $coursecontext);
$this->assertCount(1, $usersasstudent);
$this->assertEquals('student1', reset($usersasstudent)->username);
}
/**
* Test upload users, enrol and assign default role from manual enrol plugin.
* @covers \tool_uploadusers::process
*/
public function test_user_can_upload_with_course_enrolment_default_role(): void {
$this->resetAfterTest();
set_config('passwordpolicy', 0);
$this->setAdminUser();
// Create category and courses.
$coursecat = $this->getDataGenerator()->create_category();
$coursecatcontext = context_coursecat::instance($coursecat->id);
$course1 = $this->getDataGenerator()->create_course(['shortname' => 'course01', 'category' => $coursecat->id]);
$course1context = context_course::instance($course1->id);
// Change the default role to 'teacher'.
set_config('roleid', 4, 'enrol_manual');
$course2 = $this->getDataGenerator()->create_course(['shortname' => 'course02', 'category' => $coursecat->id]);
$course2context = context_course::instance($course2->id);
// Create user.
$user = $this->getDataGenerator()->create_user();
// Create role with capability to upload CSV files, and assign this role to user.
$uploadroleid = create_role('upload role', 'uploadrole', '');
set_role_contextlevels($uploadroleid, [CONTEXT_SYSTEM]);
$systemcontext = context_system::instance();
assign_capability('moodle/site:uploadusers', CAP_ALLOW, $uploadroleid, $systemcontext->id);
$this->getDataGenerator()->role_assign($uploadroleid, $user->id, $systemcontext->id);
// Create role with some of allowed capabilities to enrol users, and assign this role to user.
$enrolroleid = create_role('enrol role', 'enrolrole', '');
set_role_contextlevels($enrolroleid, [CONTEXT_COURSECAT]);
assign_capability('enrol/manual:enrol', CAP_ALLOW, $enrolroleid, $coursecatcontext->id);
assign_capability('moodle/course:enrolreview', CAP_ALLOW, $enrolroleid, $coursecatcontext->id);
assign_capability('moodle/role:assign', CAP_ALLOW, $enrolroleid, $coursecatcontext->id);
$this->getDataGenerator()->role_assign($enrolroleid, $user->id, $coursecatcontext->id);
// User makes assignments.
$studentarch = get_archetype_roles('student');
$studentrole = array_shift($studentarch);
core_role_set_assign_allowed($enrolroleid, $studentrole->id);
// Flush accesslib.
accesslib_clear_all_caches_for_unit_testing();
// Process CSV file (no roles specified) as user.
$csv = <<<EOF
username,firstname,lastname,email,course1,role1
student1,Student,One,s1@example.com,{$course1->shortname},
student2,Student,Two,s2@example.com,{$course2->shortname},
EOF;
$this->setUser($user);
$output = $this->process_csv_upload($csv, ['--uutype=' . UU_USER_ADDNEW]);
$this->assertStringContainsString('Enrolled in "course01" as "student"', $output);
// This $user cannot assign teacher role.
$this->assertStringContainsString('Unknown role "teacher"', $output);
// Check user creation, enrolment and role assignation.
$this->assertEquals(1, count_enrolled_users($course1context));
// This $user cannot enrol anyone as teacher.
$this->assertEquals(0, count_enrolled_users($course2context));
// Test user is enrolled as default-manual-enrol-plugin role.
$manualenrolinstance = new stdClass;
$enrolinstances = enrol_get_instances($course1->id, true);
foreach ($enrolinstances as $courseenrolinstance) {
if ($courseenrolinstance->enrol === 'manual') {
$manualenrolinstance = $courseenrolinstance;
break;
}
}
$defaulroleidexpected = $manualenrolinstance->roleid ?? 0;
// The default role of course01 is student, id 5.
$this->assertEquals(5, $defaulroleidexpected);
$usersasdefaultrole = get_role_users($defaulroleidexpected, $course1context);
$this->assertCount(1, $usersasdefaultrole);
$this->assertEquals('student1', reset($usersasdefaultrole)->username);
}
/**
* Test that invalid data contained in uploaded CSV triggers appropriate warnings
*/
public function test_user_upload_user_validate(): void {
$this->resetAfterTest();
$this->setAdminUser();
$csv = <<<EOF
username,firstname,lastname,email,country
student1,Student,One,s1@example.com,Wales
EOF;
$output = $this->process_csv_upload($csv, ['--uutype=' . UU_USER_ADDNEW]);
// We should get the debugging from the user class itself, as well as warning in the output regarding the same.
$this->assertDebuggingCalled('The property \'country\' has invalid data and has been cleaned.');
$this->assertStringContainsString('Incorrect data (country) found for user student1. ' .
'This data has been corrected or deleted.', $output);
}
/**
* Generate cli_helper and mock $_SERVER['argv']
*
* @param string $filecontent
* @param array $mockargv
* @return string
*/
protected function process_csv_upload(string $filecontent, array $mockargv = []): string {
$filepath = make_request_directory() . '/upload.csv';
file_put_contents($filepath, $filecontent);
$mockargv[] = "--file={$filepath}";
if (array_key_exists('argv', $_SERVER)) {
$oldservervars = $_SERVER['argv'];
}
$_SERVER['argv'] = array_merge([''], $mockargv);
$clihelper = new cli_helper(text_progress_tracker::class);
if (isset($oldservervars)) {
$_SERVER['argv'] = $oldservervars;
} else {
unset($_SERVER['argv']);
}
ob_start();
$clihelper->process();
$output = ob_get_contents();
ob_end_clean();
return $output;
}
}
+458
View File
@@ -0,0 +1,458 @@
<?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 upload forms
*
* @package tool
* @subpackage uploaduser
* @copyright 2007 Dan Poltawski
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once $CFG->libdir.'/formslib.php';
require_once($CFG->dirroot . '/user/editlib.php');
/**
* Upload a file CVS file with user information.
*
* @copyright 2007 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_uploaduser_form1 extends moodleform {
function definition() {
$mform = $this->_form;
$mform->addElement('header', 'settingsheader', get_string('upload'));
$url = new moodle_url('example.csv');
$link = html_writer::link($url, 'example.csv');
$mform->addElement('static', 'examplecsv', get_string('examplecsv', 'tool_uploaduser'), $link);
$mform->addHelpButton('examplecsv', 'examplecsv', 'tool_uploaduser');
$mform->addElement('filepicker', 'userfile', get_string('file'));
$mform->addRule('userfile', null, 'required');
$choices = csv_import_reader::get_delimiter_list();
$mform->addElement('select', 'delimiter_name', get_string('csvdelimiter', 'tool_uploaduser'), $choices);
if (array_key_exists('cfg', $choices)) {
$mform->setDefault('delimiter_name', 'cfg');
} else if (get_string('listsep', 'langconfig') == ';') {
$mform->setDefault('delimiter_name', 'semicolon');
} else {
$mform->setDefault('delimiter_name', 'comma');
}
$choices = core_text::get_encodings();
$mform->addElement('select', 'encoding', get_string('encoding', 'tool_uploaduser'), $choices);
$mform->setDefault('encoding', 'UTF-8');
$choices = array('10'=>10, '20'=>20, '100'=>100, '1000'=>1000, '100000'=>100000);
$mform->addElement('select', 'previewrows', get_string('rowpreviewnum', 'tool_uploaduser'), $choices);
$mform->setType('previewrows', PARAM_INT);
$this->add_action_buttons(false, get_string('uploadusers', 'tool_uploaduser'));
}
/**
* Returns list of elements and their default values, to be used in CLI
*
* @return array
*/
public function get_form_for_cli() {
$elements = array_filter($this->_form->_elements, function($element) {
return !in_array($element->getName(), ['buttonar', 'userfile', 'previewrows']);
});
return [$elements, $this->_form->_defaultValues];
}
}
/**
* Specify user upload details
*
* @copyright 2007 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class admin_uploaduser_form2 extends moodleform {
function definition() {
global $CFG, $USER;
$mform = $this->_form;
$columns = $this->_customdata['columns'];
$data = $this->_customdata['data'];
// upload settings and file
$mform->addElement('header', 'settingsheader', get_string('settings'));
$choices = array(UU_USER_ADDNEW => get_string('uuoptype_addnew', 'tool_uploaduser'),
UU_USER_ADDINC => get_string('uuoptype_addinc', 'tool_uploaduser'),
UU_USER_ADD_UPDATE => get_string('uuoptype_addupdate', 'tool_uploaduser'),
UU_USER_UPDATE => get_string('uuoptype_update', 'tool_uploaduser'));
$mform->addElement('select', 'uutype', get_string('uuoptype', 'tool_uploaduser'), $choices);
$choices = array(0 => get_string('infilefield', 'auth'), 1 => get_string('createpasswordifneeded', 'auth'));
$mform->addElement('select', 'uupasswordnew', get_string('uupasswordnew', 'tool_uploaduser'), $choices);
$mform->setDefault('uupasswordnew', 1);
$mform->hideIf('uupasswordnew', 'uutype', 'eq', UU_USER_UPDATE);
$choices = array(UU_UPDATE_NOCHANGES => get_string('nochanges', 'tool_uploaduser'),
UU_UPDATE_FILEOVERRIDE => get_string('uuupdatefromfile', 'tool_uploaduser'),
UU_UPDATE_ALLOVERRIDE => get_string('uuupdateall', 'tool_uploaduser'),
UU_UPDATE_MISSING => get_string('uuupdatemissing', 'tool_uploaduser'));
$mform->addElement('select', 'uuupdatetype', get_string('uuupdatetype', 'tool_uploaduser'), $choices);
$mform->setDefault('uuupdatetype', UU_UPDATE_NOCHANGES);
$mform->hideIf('uuupdatetype', 'uutype', 'eq', UU_USER_ADDNEW);
$mform->hideIf('uuupdatetype', 'uutype', 'eq', UU_USER_ADDINC);
$choices = array(0 => get_string('nochanges', 'tool_uploaduser'), 1 => get_string('update'));
$mform->addElement('select', 'uupasswordold', get_string('uupasswordold', 'tool_uploaduser'), $choices);
$mform->setDefault('uupasswordold', 0);
$mform->hideIf('uupasswordold', 'uutype', 'eq', UU_USER_ADDNEW);
$mform->hideIf('uupasswordold', 'uutype', 'eq', UU_USER_ADDINC);
$mform->hideIf('uupasswordold', 'uuupdatetype', 'eq', 0);
$mform->hideIf('uupasswordold', 'uuupdatetype', 'eq', 3);
$choices = array(UU_PWRESET_WEAK => get_string('usersweakpassword', 'tool_uploaduser'),
UU_PWRESET_NONE => get_string('none'),
UU_PWRESET_ALL => get_string('all'));
if (empty($CFG->passwordpolicy)) {
unset($choices[UU_PWRESET_WEAK]);
}
$mform->addElement('select', 'uuforcepasswordchange', get_string('forcepasswordchange', 'core'), $choices);
$mform->addElement('selectyesno', 'uumatchemail', get_string('matchemail', 'tool_uploaduser'));
$mform->setDefault('uumatchemail', 0);
$mform->hideIf('uumatchemail', 'uutype', 'eq', UU_USER_ADDNEW);
$mform->hideIf('uumatchemail', 'uutype', 'eq', UU_USER_ADDINC);
$mform->addElement('selectyesno', 'uuallowrenames', get_string('allowrenames', 'tool_uploaduser'));
$mform->setDefault('uuallowrenames', 0);
$mform->hideIf('uuallowrenames', 'uutype', 'eq', UU_USER_ADDNEW);
$mform->hideIf('uuallowrenames', 'uutype', 'eq', UU_USER_ADDINC);
$mform->addElement('selectyesno', 'uuallowdeletes', get_string('allowdeletes', 'tool_uploaduser'));
$mform->setDefault('uuallowdeletes', 0);
// Ensure user is able to perform user deletion.
if (!has_capability('moodle/user:delete', context_system::instance())) {
$mform->hardFreeze('uuallowdeletes');
$mform->setConstant('uuallowdeletes', 0);
}
$mform->hideIf('uuallowdeletes', 'uutype', 'eq', UU_USER_ADDNEW);
$mform->hideIf('uuallowdeletes', 'uutype', 'eq', UU_USER_ADDINC);
$mform->addElement('selectyesno', 'uuallowsuspends', get_string('allowsuspends', 'tool_uploaduser'));
$mform->setDefault('uuallowsuspends', 1);
$mform->hideIf('uuallowsuspends', 'uutype', 'eq', UU_USER_ADDNEW);
$mform->hideIf('uuallowsuspends', 'uutype', 'eq', UU_USER_ADDINC);
if (!empty($CFG->allowaccountssameemail)) {
$mform->addElement('selectyesno', 'uunoemailduplicates', get_string('uunoemailduplicates', 'tool_uploaduser'));
$mform->setDefault('uunoemailduplicates', 1);
} else {
$mform->addElement('hidden', 'uunoemailduplicates', 1);
}
$mform->setType('uunoemailduplicates', PARAM_BOOL);
$mform->addElement('selectyesno', 'uustandardusernames', get_string('uustandardusernames', 'tool_uploaduser'));
$mform->setDefault('uustandardusernames', 1);
$choices = array(UU_BULK_NONE => get_string('no'),
UU_BULK_NEW => get_string('uubulknew', 'tool_uploaduser'),
UU_BULK_UPDATED => get_string('uubulkupdated', 'tool_uploaduser'),
UU_BULK_ALL => get_string('uubulkall', 'tool_uploaduser'));
$mform->addElement('select', 'uubulk', get_string('uubulk', 'tool_uploaduser'), $choices);
$mform->setDefault('uubulk', 0);
// roles selection
$showroles = false;
foreach ($columns as $column) {
if (preg_match('/^type\d+$/', $column)) {
$showroles = true;
break;
}
}
if ($showroles) {
$mform->addElement('header', 'rolesheader', get_string('roles'));
$choices = uu_allowed_roles(true);
$mform->addElement('select', 'uulegacy1', get_string('uulegacy1role', 'tool_uploaduser'), $choices);
if ($studentroles = get_archetype_roles('student')) {
foreach ($studentroles as $role) {
if (isset($choices[$role->id])) {
$mform->setDefault('uulegacy1', $role->id);
break;
}
}
unset($studentroles);
}
$mform->addElement('select', 'uulegacy2', get_string('uulegacy2role', 'tool_uploaduser'), $choices);
if ($editteacherroles = get_archetype_roles('editingteacher')) {
foreach ($editteacherroles as $role) {
if (isset($choices[$role->id])) {
$mform->setDefault('uulegacy2', $role->id);
break;
}
}
unset($editteacherroles);
}
$mform->addElement('select', 'uulegacy3', get_string('uulegacy3role', 'tool_uploaduser'), $choices);
if ($teacherroles = get_archetype_roles('teacher')) {
foreach ($teacherroles as $role) {
if (isset($choices[$role->id])) {
$mform->setDefault('uulegacy3', $role->id);
break;
}
}
unset($teacherroles);
}
}
// default values
$mform->addElement('header', 'defaultheader', get_string('defaultvalues', 'tool_uploaduser'));
$mform->addElement('text', 'username', get_string('uuusernametemplate', 'tool_uploaduser'), 'size="20"');
$mform->setType('username', PARAM_RAW); // No cleaning here. The process verifies it later.
$mform->hideIf('username', 'uutype', 'eq', UU_USER_ADD_UPDATE);
$mform->hideIf('username', 'uutype', 'eq', UU_USER_UPDATE);
$mform->setForceLtr('username');
$mform->addElement('text', 'email', get_string('email'), 'maxlength="100" size="30"');
$mform->setType('email', PARAM_RAW); // No cleaning here. The process verifies it later.
$mform->hideIf('email', 'uutype', 'eq', UU_USER_ADD_UPDATE);
$mform->hideIf('email', 'uutype', 'eq', UU_USER_UPDATE);
$mform->setForceLtr('email');
// only enabled and known to work plugins
$choices = uu_supported_auths();
$mform->addElement('select', 'auth', get_string('chooseauthmethod','auth'), $choices);
$mform->setDefault('auth', 'manual'); // manual is a sensible backwards compatible default
$mform->addHelpButton('auth', 'chooseauthmethod', 'auth');
$mform->setAdvanced('auth');
$choices = array(0 => get_string('emaildisplayno'), 1 => get_string('emaildisplayyes'), 2 => get_string('emaildisplaycourse'));
$mform->addElement('select', 'maildisplay', get_string('emaildisplay'), $choices);
$mform->setDefault('maildisplay', core_user::get_property_default('maildisplay'));
$mform->addHelpButton('maildisplay', 'emaildisplay');
$choices = array(0 => get_string('emailenable'), 1 => get_string('emaildisable'));
$mform->addElement('select', 'emailstop', get_string('emailstop'), $choices);
$mform->setDefault('emailstop', core_user::get_property_default('emailstop'));
$mform->setAdvanced('emailstop');
$choices = array(0 => get_string('textformat'), 1 => get_string('htmlformat'));
$mform->addElement('select', 'mailformat', get_string('emailformat'), $choices);
$mform->setDefault('mailformat', core_user::get_property_default('mailformat'));
$mform->setAdvanced('mailformat');
$choices = array(0 => get_string('emaildigestoff'), 1 => get_string('emaildigestcomplete'), 2 => get_string('emaildigestsubjects'));
$mform->addElement('select', 'maildigest', get_string('emaildigest'), $choices);
$mform->setDefault('maildigest', core_user::get_property_default('maildigest'));
$mform->setAdvanced('maildigest');
$choices = array(1 => get_string('autosubscribeyes'), 0 => get_string('autosubscribeno'));
$mform->addElement('select', 'autosubscribe', get_string('autosubscribe'), $choices);
$mform->setDefault('autosubscribe', core_user::get_property_default('autosubscribe'));
$mform->addElement('text', 'city', get_string('city'), 'maxlength="120" size="25"');
$mform->setType('city', core_user::get_property_type('city'));
$mform->setDefault('city', core_user::get_property_default('city'));
$mform->addElement('select', 'country', get_string('selectacountry'), core_user::get_property_choices('country'));
$mform->setDefault('country', core_user::get_property_default('country') ?: '');
$mform->setAdvanced('country');
$mform->addElement('select', 'timezone', get_string('timezone'), core_date::get_list_of_timezones(null, true));
$mform->setDefault('timezone', core_user::get_property_default('timezone'));
$mform->setAdvanced('timezone');
$mform->addElement('select', 'lang', get_string('preferredlanguage'), core_user::get_property_choices('lang'));
$mform->setDefault('lang', core_user::get_property_default('lang'));
$mform->setAdvanced('lang');
$editoroptions = array('maxfiles'=>0, 'maxbytes'=>0, 'trusttext'=>false, 'forcehttps'=>false);
$mform->addElement('editor', 'description', get_string('userdescription'), null, $editoroptions);
$mform->setType('description', PARAM_CLEANHTML);
$mform->addHelpButton('description', 'userdescription');
$mform->setAdvanced('description');
$mform->addElement('text', 'idnumber', get_string('idnumber'), 'maxlength="255" size="25"');
$mform->setType('idnumber', core_user::get_property_type('idnumber'));
$mform->setForceLtr('idnumber');
$mform->addElement('text', 'institution', get_string('institution'), 'maxlength="255" size="25"');
$mform->setType('institution', core_user::get_property_type('institution'));
$mform->addElement('text', 'department', get_string('department'), 'maxlength="255" size="25"');
$mform->setType('department', core_user::get_property_type('department'));
$mform->addElement('text', 'phone1', get_string('phone1'), 'maxlength="20" size="25"');
$mform->setType('phone1', core_user::get_property_type('phone1'));
$mform->setAdvanced('phone1');
$mform->setForceLtr('phone1');
$mform->addElement('text', 'phone2', get_string('phone2'), 'maxlength="20" size="25"');
$mform->setType('phone2', core_user::get_property_type('phone2'));
$mform->setAdvanced('phone2');
$mform->setForceLtr('phone2');
$mform->addElement('text', 'address', get_string('address'), 'maxlength="255" size="25"');
$mform->setType('address', core_user::get_property_type('address'));
$mform->setAdvanced('address');
// Next the profile defaults
profile_definition($mform);
// hidden fields
$mform->addElement('hidden', 'iid');
$mform->setType('iid', PARAM_INT);
$mform->addElement('hidden', 'previewrows');
$mform->setType('previewrows', PARAM_INT);
$this->add_action_buttons(true, get_string('uploadusers', 'tool_uploaduser'));
$this->set_data($data);
}
/**
* Form tweaks that depend on current data.
*/
function definition_after_data() {
$mform = $this->_form;
$columns = $this->_customdata['columns'];
foreach ($columns as $column) {
if ($mform->elementExists($column)) {
$mform->removeElement($column);
}
}
if (!in_array('password', $columns)) {
// password resetting makes sense only if password specified in csv file
if ($mform->elementExists('uuforcepasswordchange')) {
$mform->removeElement('uuforcepasswordchange');
}
}
}
/**
* Server side validation.
*/
function validation($data, $files) {
$errors = parent::validation($data, $files);
$columns = $this->_customdata['columns'];
$optype = $data['uutype'];
$updatetype = $data['uuupdatetype'];
// detect if password column needed in file
if (!in_array('password', $columns)) {
switch ($optype) {
case UU_USER_UPDATE:
if (!empty($data['uupasswordold'])) {
$errors['uupasswordold'] = get_string('missingfield', 'error', 'password');
}
break;
case UU_USER_ADD_UPDATE:
if (empty($data['uupasswordnew'])) {
$errors['uupasswordnew'] = get_string('missingfield', 'error', 'password');
}
if (!empty($data['uupasswordold'])) {
$errors['uupasswordold'] = get_string('missingfield', 'error', 'password');
}
break;
case UU_USER_ADDNEW:
if (empty($data['uupasswordnew'])) {
$errors['uupasswordnew'] = get_string('missingfield', 'error', 'password');
}
break;
case UU_USER_ADDINC:
if (empty($data['uupasswordnew'])) {
$errors['uupasswordnew'] = get_string('missingfield', 'error', 'password');
}
break;
}
}
// If the 'Existing user details' value is set we need to ensure that the
// 'Upload type' is not set to something invalid.
if (!empty($updatetype) && ($optype == UU_USER_ADDNEW || $optype == UU_USER_ADDINC)) {
$errors['uuupdatetype'] = get_string('invalidupdatetype', 'tool_uploaduser');
}
// look for other required data
if ($optype != UU_USER_UPDATE) {
$requiredusernames = useredit_get_required_name_fields();
$missing = array();
foreach ($requiredusernames as $requiredusername) {
if (!in_array($requiredusername, $columns)) {
$missing[] = get_string('missingfield', 'error', $requiredusername);;
}
}
if ($missing) {
$errors['uutype'] = implode('<br />', $missing);
}
if (!in_array('email', $columns) and empty($data['email'])) {
$errors['email'] = get_string('requiredtemplate', 'tool_uploaduser');
}
}
return $errors;
}
/**
* Used to reformat the data from the editor component
*
* @return stdClass
*/
function get_data() {
$data = parent::get_data();
if ($data !== null and isset($data->description)) {
$data->descriptionformat = $data->description['format'];
$data->description = $data->description['text'];
}
return $data;
}
/**
* Returns list of elements and their default values, to be used in CLI
*
* @return array
*/
public function get_form_for_cli() {
$elements = array_filter($this->_form->_elements, function($element) {
return !in_array($element->getName(), ['buttonar', 'uubulk']);
});
return [$elements, $this->_form->_defaultValues];
}
/**
* Returns validation errors (used in CLI)
*
* @return array
*/
public function get_validation_errors(): array {
return $this->_form->_errors;
}
}
+31
View File
@@ -0,0 +1,31 @@
<?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/>.
/**
* Plugin version info
*
* @package tool
* @subpackage uploaduser
* @copyright 2011 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2024042200; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'tool_uploaduser'; // Full name of the plugin (used for diagnostics)