Differences Between: [Versions 310 and 403] [Versions 311 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Class cli_helper 19 * 20 * @package tool_uploaduser 21 * @copyright 2020 Marina Glancy 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 namespace tool_uploaduser; 26 27 defined('MOODLE_INTERNAL') || die(); 28 29 use tool_uploaduser\local\cli_progress_tracker; 30 31 require_once($CFG->dirroot.'/user/profile/lib.php'); 32 require_once($CFG->dirroot.'/user/lib.php'); 33 require_once($CFG->dirroot.'/group/lib.php'); 34 require_once($CFG->dirroot.'/cohort/lib.php'); 35 require_once($CFG->libdir.'/csvlib.class.php'); 36 require_once($CFG->dirroot.'/'.$CFG->admin.'/tool/uploaduser/locallib.php'); 37 require_once($CFG->dirroot.'/'.$CFG->admin.'/tool/uploaduser/user_form.php'); 38 require_once($CFG->libdir . '/clilib.php'); 39 40 /** 41 * Helper method for CLI script to upload users (also has special wrappers for cli* functions for phpunit testing) 42 * 43 * @package tool_uploaduser 44 * @copyright 2020 Marina Glancy 45 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 46 */ 47 class cli_helper { 48 49 /** @var string */ 50 protected $operation; 51 /** @var array */ 52 protected $clioptions; 53 /** @var array */ 54 protected $unrecognized; 55 /** @var string */ 56 protected $progresstrackerclass; 57 58 /** @var process */ 59 protected $process; 60 61 /** 62 * cli_helper constructor. 63 * 64 * @param string|null $progresstrackerclass 65 */ 66 public function __construct(?string $progresstrackerclass = null) { 67 $this->progresstrackerclass = $progresstrackerclass ?? cli_progress_tracker::class; 68 $optionsdefinitions = $this->options_definitions(); 69 $longoptions = []; 70 $shortmapping = []; 71 foreach ($optionsdefinitions as $key => $option) { 72 $longoptions[$key] = $option['default']; 73 if (!empty($option['alias'])) { 74 $shortmapping[$option['alias']] = $key; 75 } 76 } 77 78 list($this->clioptions, $this->unrecognized) = cli_get_params( 79 $longoptions, 80 $shortmapping 81 ); 82 } 83 84 /** 85 * Options used in this CLI script 86 * 87 * @return array 88 */ 89 protected function options_definitions(): array { 90 $options = [ 91 'help' => [ 92 'hasvalue' => false, 93 'description' => get_string('clihelp', 'tool_uploaduser'), 94 'default' => 0, 95 'alias' => 'h', 96 ], 97 'file' => [ 98 'hasvalue' => 'PATH', 99 'description' => get_string('clifile', 'tool_uploaduser'), 100 'default' => null, 101 'validation' => function($file) { 102 if (!$file) { 103 $this->cli_error(get_string('climissingargument', 'tool_uploaduser', 'file')); 104 } 105 if ($file && (!file_exists($file) || !is_readable($file))) { 106 $this->cli_error(get_string('clifilenotreadable', 'tool_uploaduser', $file)); 107 } 108 } 109 ], 110 ]; 111 $form = new \admin_uploaduser_form1(); 112 [$elements, $defaults] = $form->get_form_for_cli(); 113 $options += $this->prepare_form_elements_for_cli($elements, $defaults); 114 // Specify pseudo-column 'type1' to force the form to populate the legacy role mapping selector 115 // but only if user is allowed to assign roles in courses (otherwise form validation will fail). 116 $columns = uu_allowed_roles() ? ['type1'] : []; 117 $form = new \admin_uploaduser_form2(null, ['columns' => $columns, 'data' => []]); 118 [$elements, $defaults] = $form->get_form_for_cli(); 119 $options += $this->prepare_form_elements_for_cli($elements, $defaults); 120 return $options; 121 } 122 123 /** 124 * Print help for export 125 */ 126 public function print_help(): void { 127 $this->cli_writeln(get_string('clititle', 'tool_uploaduser')); 128 $this->cli_writeln(''); 129 $this->print_help_options($this->options_definitions()); 130 $this->cli_writeln(''); 131 $this->cli_writeln('Example:'); 132 $this->cli_writeln('$sudo -u www-data /usr/bin/php admin/tool/uploaduser/cli/uploaduser.php --file=PATH'); 133 } 134 135 /** 136 * Get CLI option 137 * 138 * @param string $key 139 * @return mixed|null 140 */ 141 public function get_cli_option(string $key) { 142 return $this->clioptions[$key] ?? null; 143 } 144 145 /** 146 * Write a text to the given stream 147 * 148 * @param string $text text to be written 149 */ 150 protected function cli_write($text): void { 151 if (PHPUNIT_TEST) { 152 echo $text; 153 } else { 154 cli_write($text); 155 } 156 } 157 158 /** 159 * Write error notification 160 * @param string $text 161 * @return void 162 */ 163 protected function cli_problem($text): void { 164 if (PHPUNIT_TEST) { 165 echo $text; 166 } else { 167 cli_problem($text); 168 } 169 } 170 171 /** 172 * Write a text followed by an end of line symbol to the given stream 173 * 174 * @param string $text text to be written 175 */ 176 protected function cli_writeln($text): void { 177 $this->cli_write($text . PHP_EOL); 178 } 179 180 /** 181 * Write to standard error output and exit with the given code 182 * 183 * @param string $text 184 * @param int $errorcode 185 * @return void (does not return) 186 */ 187 protected function cli_error($text, $errorcode = 1): void { 188 $this->cli_problem($text); 189 $this->die($errorcode); 190 } 191 192 /** 193 * Wrapper for "die()" method so we can unittest it 194 * 195 * @param mixed $errorcode 196 * @throws \moodle_exception 197 */ 198 protected function die($errorcode): void { 199 if (!PHPUNIT_TEST) { 200 die($errorcode); 201 } else { 202 throw new \moodle_exception('CLI script finished with error code '.$errorcode); 203 } 204 } 205 206 /** 207 * Display as CLI table 208 * 209 * @param array $column1 210 * @param array $column2 211 * @param int $indent 212 * @return string 213 */ 214 protected function convert_to_table(array $column1, array $column2, int $indent = 0): string { 215 $maxlengthleft = 0; 216 $left = []; 217 $column1 = array_values($column1); 218 $column2 = array_values($column2); 219 foreach ($column1 as $i => $l) { 220 $left[$i] = str_repeat(' ', $indent) . $l; 221 if (strlen('' . $column2[$i])) { 222 $maxlengthleft = max($maxlengthleft, strlen($l) + $indent); 223 } 224 } 225 $maxlengthright = 80 - $maxlengthleft - 1; 226 $output = ''; 227 foreach ($column2 as $i => $r) { 228 if (!strlen('' . $r)) { 229 $output .= $left[$i] . "\n"; 230 continue; 231 } 232 $right = wordwrap($r, $maxlengthright, "\n"); 233 $output .= str_pad($left[$i], $maxlengthleft) . ' ' . 234 str_replace("\n", PHP_EOL . str_repeat(' ', $maxlengthleft + 1), $right) . PHP_EOL; 235 } 236 return $output; 237 } 238 239 /** 240 * Display available CLI options as a table 241 * 242 * @param array $options 243 */ 244 protected function print_help_options(array $options): void { 245 $left = []; 246 $right = []; 247 foreach ($options as $key => $option) { 248 if ($option['hasvalue'] !== false) { 249 $l = "--$key={$option['hasvalue']}"; 250 } else if (!empty($option['alias'])) { 251 $l = "-{$option['alias']}, --$key"; 252 } else { 253 $l = "--$key"; 254 } 255 $left[] = $l; 256 $right[] = $option['description']; 257 } 258 $this->cli_write('Options:' . PHP_EOL . $this->convert_to_table($left, $right)); 259 } 260 261 /** 262 * Process the upload 263 */ 264 public function process(): void { 265 // First, validate all arguments. 266 $definitions = $this->options_definitions(); 267 foreach ($this->clioptions as $key => $value) { 268 if ($validator = $definitions[$key]['validation'] ?? null) { 269 $validator($value); 270 } 271 } 272 273 // Read the CSV file. 274 $iid = \csv_import_reader::get_new_iid('uploaduser'); 275 $cir = new \csv_import_reader($iid, 'uploaduser'); 276 $cir->load_csv_content(file_get_contents($this->get_cli_option('file')), 277 $this->get_cli_option('encoding'), $this->get_cli_option('delimiter_name')); 278 $csvloaderror = $cir->get_error(); 279 280 if (!is_null($csvloaderror)) { 281 $this->cli_error(get_string('csvloaderror', 'error', $csvloaderror), 1); 282 } 283 284 // Start upload user process. 285 $this->process = new \tool_uploaduser\process($cir, $this->progresstrackerclass); 286 $filecolumns = $this->process->get_file_columns(); 287 288 $form = $this->mock_form(['columns' => $filecolumns, 'data' => ['iid' => $iid, 'previewrows' => 1]], $this->clioptions); 289 290 if (!$form->is_validated()) { 291 $errors = $form->get_validation_errors(); 292 $this->cli_error(get_string('clivalidationerror', 'tool_uploaduser') . PHP_EOL . 293 $this->convert_to_table(array_keys($errors), array_values($errors), 2)); 294 } 295 296 $this->process->set_form_data($form->get_data()); 297 $this->process->process(); 298 } 299 300 /** 301 * Mock form submission 302 * 303 * @param array $customdata 304 * @param array $submitteddata 305 * @return \admin_uploaduser_form2 306 */ 307 protected function mock_form(array $customdata, array $submitteddata): \admin_uploaduser_form2 { 308 global $USER; 309 $submitteddata['description'] = ['text' => $submitteddata['description'], 'format' => FORMAT_HTML]; 310 311 // Now mock the form submission. 312 $submitteddata['_qf__admin_uploaduser_form2'] = 1; 313 $oldignoresesskey = $USER->ignoresesskey ?? null; 314 $USER->ignoresesskey = true; 315 $form = new \admin_uploaduser_form2(null, $customdata, 'post', '', [], true, $submitteddata); 316 $USER->ignoresesskey = $oldignoresesskey; 317 318 $form->set_data($submitteddata); 319 return $form; 320 } 321 322 /** 323 * Prepare form elements for CLI 324 * 325 * @param \HTML_QuickForm_element[] $elements 326 * @param array $defaults 327 * @return array 328 */ 329 protected function prepare_form_elements_for_cli(array $elements, array $defaults): array { 330 $options = []; 331 foreach ($elements as $element) { 332 if ($element instanceof \HTML_QuickForm_submit || $element instanceof \HTML_QuickForm_static) { 333 continue; 334 } 335 $type = $element->getType(); 336 if ($type === 'html' || $type === 'hidden' || $type === 'header') { 337 continue; 338 } 339 340 $name = $element->getName(); 341 if ($name === null || preg_match('/^mform_isexpanded_/', $name) 342 || preg_match('/^_qf__/', $name)) { 343 continue; 344 } 345 346 $label = $element->getLabel(); 347 if (!strlen($label) && method_exists($element, 'getText')) { 348 $label = $element->getText(); 349 } 350 $default = $defaults[$element->getName()] ?? null; 351 352 $postfix = ''; 353 $possiblevalues = null; 354 if ($element instanceof \HTML_QuickForm_select) { 355 $selectoptions = $element->_options; 356 $possiblevalues = []; 357 foreach ($selectoptions as $option) { 358 $possiblevalues[] = '' . $option['attr']['value']; 359 } 360 if (count($selectoptions) < 10) { 361 $postfix .= ':'; 362 foreach ($selectoptions as $option) { 363 $postfix .= "\n ".$option['attr']['value']." - ".$option['text']; 364 } 365 } 366 if (!array_key_exists($name, $defaults)) { 367 $firstoption = reset($selectoptions); 368 $default = $firstoption['attr']['value']; 369 } 370 } 371 372 if ($element instanceof \HTML_QuickForm_checkbox) { 373 $postfix = ":\n 0|1"; 374 $possiblevalues = ['0', '1']; 375 } 376 377 if ($default !== null & $default !== '') { 378 $postfix .= "\n ".get_string('clidefault', 'tool_uploaduser')." ".$default; 379 } 380 $options[$name] = [ 381 'hasvalue' => 'VALUE', 382 'description' => $label.$postfix, 383 'default' => $default, 384 ]; 385 if ($possiblevalues !== null) { 386 $options[$name]['validation'] = function($v) use ($possiblevalues, $name) { 387 if (!in_array('' . $v, $possiblevalues)) { 388 $this->cli_error(get_string('clierrorargument', 'tool_uploaduser', 389 (object)['name' => $name, 'values' => join(', ', $possiblevalues)])); 390 } 391 }; 392 } 393 } 394 return $options; 395 } 396 397 /** 398 * Get process statistics. 399 * 400 * @return array 401 */ 402 public function get_stats(): array { 403 return $this->process->get_stats(); 404 } 405 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body