Merge branch 'MDL-65451' of https://github.com/marinaglancy/moodle into master

This commit is contained in:
Sara Arjona 2020-10-07 09:39:46 +02:00
commit 7941ada0ee
14 changed files with 2533 additions and 1187 deletions

View File

@ -0,0 +1,402 @@
<?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);
$form = new \admin_uploaduser_form2(null, ['columns' => ['type1'], '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'];
}
}
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();
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -0,0 +1,158 @@
<?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;
$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) && $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;
}
}

File diff suppressed because it is too large Load Diff

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.
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);
}

File diff suppressed because it is too large Load Diff

View File

@ -27,19 +27,30 @@ $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 delimiter';
$string['defaultvalues'] = 'Default values';
$string['deleteerrors'] = 'Delete errors';
$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['invaliduserdata'] = 'Invalid data detected for user {$a} and it has been automatically cleaned.';
$string['invalidtheme'] = 'Theme "{$a}" is not installed and will be ignored.';
$string['linex'] = 'Line {$a}';
$string['nochanges'] = 'No changes';
$string['notheme'] = 'No theme is defined for this user.';
$string['pluginname'] = 'User upload';
@ -106,3 +117,4 @@ $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:';

View File

@ -55,14 +55,38 @@ define('UU_PWRESET_ALL', 2);
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class uu_progress_tracker {
private $_row;
/** @var array */
protected $_row;
/**
* The columns shown on the table.
* @var array
*/
public $columns = array('status', 'line', 'id', 'username', 'firstname', 'lastname', 'email',
'password', 'auth', 'enrolments', 'suspended', 'theme', 'deleted');
public $columns = [];
/** @var array column headers */
protected $headers = [];
/**
* uu_progress_tracker constructor.
*/
public function __construct() {
$this->headers = [
'status' => get_string('status'),
'line' => get_string('uucsvline', 'tool_uploaduser'),
'id' => 'ID',
'username' => get_string('username'),
'firstname' => get_string('firstname'),
'lastname' => get_string('lastname'),
'email' => get_string('email'),
'password' => get_string('password'),
'auth' => get_string('authentication'),
'enrolments' => get_string('enrolments', 'enrol'),
'suspended' => get_string('suspended', 'auth'),
'theme' => get_string('theme'),
'deleted' => get_string('delete'),
];
$this->columns = array_keys($this->headers);
}
/**
* Print table header.
@ -72,19 +96,9 @@ class uu_progress_tracker {
$ci = 0;
echo '<table id="uuresults" class="generaltable boxaligncenter flexible-wrap" summary="'.get_string('uploadusersresult', 'tool_uploaduser').'">';
echo '<tr class="heading r0">';
echo '<th class="header c'.$ci++.'" scope="col">'.get_string('status').'</th>';
echo '<th class="header c'.$ci++.'" scope="col">'.get_string('uucsvline', 'tool_uploaduser').'</th>';
echo '<th class="header c'.$ci++.'" scope="col">ID</th>';
echo '<th class="header c'.$ci++.'" scope="col">'.get_string('username').'</th>';
echo '<th class="header c'.$ci++.'" scope="col">'.get_string('firstname').'</th>';
echo '<th class="header c'.$ci++.'" scope="col">'.get_string('lastname').'</th>';
echo '<th class="header c'.$ci++.'" scope="col">'.get_string('email').'</th>';
echo '<th class="header c'.$ci++.'" scope="col">'.get_string('password').'</th>';
echo '<th class="header c'.$ci++.'" scope="col">'.get_string('authentication').'</th>';
echo '<th class="header c'.$ci++.'" scope="col">'.get_string('enrolments', 'enrol').'</th>';
echo '<th class="header c'.$ci++.'" scope="col">'.get_string('suspended', 'auth').'</th>';
echo '<th class="header c'.$ci++.'" scope="col">'.get_string('theme').'</th>';
echo '<th class="header c'.$ci++.'" scope="col">'.get_string('delete').'</th>';
foreach ($this->headers as $key => $header) {
echo '<th class="header c'.$ci++.'" scope="col">'.$header.'</th>';
}
echo '</tr>';
$this->_row = null;
}

View File

@ -0,0 +1,295 @@
<?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/>.
/**
* 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
*/
use \tool_uploaduser\cli_helper;
/**
* 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 tool_uploaduser_cli_testcase 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() {
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() {
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() {
global $DB, $CFG;
$this->resetAfterTest();
set_config('passwordpolicy', 0);
$this->setAdminUser();
$categoryid = $DB->insert_record('user_info_category', ['name' => 'Cat 1', 'sortorder' => 1]);
$this->field1 = $DB->insert_record('user_info_field', [
'shortname' => 'superfield', 'name' => 'Super field', 'categoryid' => $categoryid,
'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->assertEquals((object)['superfield' => 'Loves cats'], $profilefields1);
}
/**
* Testing that help for CLI does not throw errors
*/
public function test_cli_help() {
$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() {
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() {
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() {
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);
}
}

View File

@ -68,6 +68,18 @@ class admin_uploaduser_form1 extends moodleform {
$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];
}
}
@ -434,4 +446,25 @@ class admin_uploaduser_form2 extends moodleform {
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;
}
}

View File

@ -652,3 +652,17 @@
color: $gray-600;
}
}
.path-admin-tool-uploaduser {
.uuwarning {
background-color: $state-warning-bg;
}
.uuerror {
background-color: $state-danger-bg;
}
.uuinfo {
background-color: $state-success-bg;
}
}

View File

@ -12457,6 +12457,15 @@ input[disabled] {
font-size: 0.8203125rem;
color: #6c757d; }
.path-admin-tool-uploaduser .uuwarning {
background-color: #fcefdc; }
.path-admin-tool-uploaduser .uuerror {
background-color: #f6d9d8; }
.path-admin-tool-uploaduser .uuinfo {
background-color: #d7e6d7; }
.blockmovetarget .accesshide {
position: relative;
left: initial; }

View File

@ -12671,6 +12671,15 @@ input[disabled] {
font-size: 0.8203125rem;
color: #6c757d; }
.path-admin-tool-uploaduser .uuwarning {
background-color: #fcefdc; }
.path-admin-tool-uploaduser .uuerror {
background-color: #f6d9d8; }
.path-admin-tool-uploaduser .uuinfo {
background-color: #d7e6d7; }
.blockmovetarget .accesshide {
position: relative;
left: initial; }