Differences Between: [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 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 $form = new \admin_uploaduser_form2(null, ['columns' => ['type1'], 'data' => []]); 115 [$elements, $defaults] = $form->get_form_for_cli(); 116 $options += $this->prepare_form_elements_for_cli($elements, $defaults); 117 return $options; 118 } 119 120 /** 121 * Print help for export 122 */ 123 public function print_help(): void { 124 $this->cli_writeln(get_string('clititle', 'tool_uploaduser')); 125 $this->cli_writeln(''); 126 $this->print_help_options($this->options_definitions()); 127 $this->cli_writeln(''); 128 $this->cli_writeln('Example:'); 129 $this->cli_writeln('$sudo -u www-data /usr/bin/php admin/tool/uploaduser/cli/uploaduser.php --file=PATH'); 130 } 131 132 /** 133 * Get CLI option 134 * 135 * @param string $key 136 * @return mixed|null 137 */ 138 public function get_cli_option(string $key) { 139 return $this->clioptions[$key] ?? null; 140 } 141 142 /** 143 * Write a text to the given stream 144 * 145 * @param string $text text to be written 146 */ 147 protected function cli_write($text): void { 148 if (PHPUNIT_TEST) { 149 echo $text; 150 } else { 151 cli_write($text); 152 } 153 } 154 155 /** 156 * Write error notification 157 * @param string $text 158 * @return void 159 */ 160 protected function cli_problem($text): void { 161 if (PHPUNIT_TEST) { 162 echo $text; 163 } else { 164 cli_problem($text); 165 } 166 } 167 168 /** 169 * Write a text followed by an end of line symbol to the given stream 170 * 171 * @param string $text text to be written 172 */ 173 protected function cli_writeln($text): void { 174 $this->cli_write($text . PHP_EOL); 175 } 176 177 /** 178 * Write to standard error output and exit with the given code 179 * 180 * @param string $text 181 * @param int $errorcode 182 * @return void (does not return) 183 */ 184 protected function cli_error($text, $errorcode = 1): void { 185 $this->cli_problem($text); 186 $this->die($errorcode); 187 } 188 189 /** 190 * Wrapper for "die()" method so we can unittest it 191 * 192 * @param mixed $errorcode 193 * @throws \moodle_exception 194 */ 195 protected function die($errorcode): void { 196 if (!PHPUNIT_TEST) { 197 die($errorcode); 198 } else { 199 throw new \moodle_exception('CLI script finished with error code '.$errorcode); 200 } 201 } 202 203 /** 204 * Display as CLI table 205 * 206 * @param array $column1 207 * @param array $column2 208 * @param int $indent 209 * @return string 210 */ 211 protected function convert_to_table(array $column1, array $column2, int $indent = 0): string { 212 $maxlengthleft = 0; 213 $left = []; 214 $column1 = array_values($column1); 215 $column2 = array_values($column2); 216 foreach ($column1 as $i => $l) { 217 $left[$i] = str_repeat(' ', $indent) . $l; 218 if (strlen('' . $column2[$i])) { 219 $maxlengthleft = max($maxlengthleft, strlen($l) + $indent); 220 } 221 } 222 $maxlengthright = 80 - $maxlengthleft - 1; 223 $output = ''; 224 foreach ($column2 as $i => $r) { 225 if (!strlen('' . $r)) { 226 $output .= $left[$i] . "\n"; 227 continue; 228 } 229 $right = wordwrap($r, $maxlengthright, "\n"); 230 $output .= str_pad($left[$i], $maxlengthleft) . ' ' . 231 str_replace("\n", PHP_EOL . str_repeat(' ', $maxlengthleft + 1), $right) . PHP_EOL; 232 } 233 return $output; 234 } 235 236 /** 237 * Display available CLI options as a table 238 * 239 * @param array $options 240 */ 241 protected function print_help_options(array $options): void { 242 $left = []; 243 $right = []; 244 foreach ($options as $key => $option) { 245 if ($option['hasvalue'] !== false) { 246 $l = "--$key={$option['hasvalue']}"; 247 } else if (!empty($option['alias'])) { 248 $l = "-{$option['alias']}, --$key"; 249 } else { 250 $l = "--$key"; 251 } 252 $left[] = $l; 253 $right[] = $option['description']; 254 } 255 $this->cli_write('Options:' . PHP_EOL . $this->convert_to_table($left, $right)); 256 } 257 258 /** 259 * Process the upload 260 */ 261 public function process(): void { 262 // First, validate all arguments. 263 $definitions = $this->options_definitions(); 264 foreach ($this->clioptions as $key => $value) { 265 if ($validator = $definitions[$key]['validation'] ?? null) { 266 $validator($value); 267 } 268 } 269 270 // Read the CSV file. 271 $iid = \csv_import_reader::get_new_iid('uploaduser'); 272 $cir = new \csv_import_reader($iid, 'uploaduser'); 273 $cir->load_csv_content(file_get_contents($this->get_cli_option('file')), 274 $this->get_cli_option('encoding'), $this->get_cli_option('delimiter_name')); 275 $csvloaderror = $cir->get_error(); 276 277 if (!is_null($csvloaderror)) { 278 $this->cli_error(get_string('csvloaderror', 'error', $csvloaderror), 1); 279 } 280 281 // Start upload user process. 282 $this->process = new \tool_uploaduser\process($cir, $this->progresstrackerclass); 283 $filecolumns = $this->process->get_file_columns(); 284 285 $form = $this->mock_form(['columns' => $filecolumns, 'data' => ['iid' => $iid, 'previewrows' => 1]], $this->clioptions); 286 287 if (!$form->is_validated()) { 288 $errors = $form->get_validation_errors(); 289 $this->cli_error(get_string('clivalidationerror', 'tool_uploaduser') . PHP_EOL . 290 $this->convert_to_table(array_keys($errors), array_values($errors), 2)); 291 } 292 293 $this->process->set_form_data($form->get_data()); 294 $this->process->process(); 295 } 296 297 /** 298 * Mock form submission 299 * 300 * @param array $customdata 301 * @param array $submitteddata 302 * @return \admin_uploaduser_form2 303 */ 304 protected function mock_form(array $customdata, array $submitteddata): \admin_uploaduser_form2 { 305 global $USER; 306 $submitteddata['description'] = ['text' => $submitteddata['description'], 'format' => FORMAT_HTML]; 307 308 // Now mock the form submission. 309 $submitteddata['_qf__admin_uploaduser_form2'] = 1; 310 $oldignoresesskey = $USER->ignoresesskey ?? null; 311 $USER->ignoresesskey = true; 312 $form = new \admin_uploaduser_form2(null, $customdata, 'post', '', [], true, $submitteddata); 313 $USER->ignoresesskey = $oldignoresesskey; 314 315 $form->set_data($submitteddata); 316 return $form; 317 } 318 319 /** 320 * Prepare form elements for CLI 321 * 322 * @param \HTML_QuickForm_element[] $elements 323 * @param array $defaults 324 * @return array 325 */ 326 protected function prepare_form_elements_for_cli(array $elements, array $defaults): array { 327 $options = []; 328 foreach ($elements as $element) { 329 if ($element instanceof \HTML_QuickForm_submit || $element instanceof \HTML_QuickForm_static) { 330 continue; 331 } 332 $type = $element->getType(); 333 if ($type === 'html' || $type === 'hidden' || $type === 'header') { 334 continue; 335 } 336 337 $name = $element->getName(); 338 if ($name === null || preg_match('/^mform_isexpanded_/', $name) 339 || preg_match('/^_qf__/', $name)) { 340 continue; 341 } 342 343 $label = $element->getLabel(); 344 if (!strlen($label) && method_exists($element, 'getText')) { 345 $label = $element->getText(); 346 } 347 $default = $defaults[$element->getName()] ?? null; 348 349 $postfix = ''; 350 $possiblevalues = null; 351 if ($element instanceof \HTML_QuickForm_select) { 352 $selectoptions = $element->_options; 353 $possiblevalues = []; 354 foreach ($selectoptions as $option) { 355 $possiblevalues[] = '' . $option['attr']['value']; 356 } 357 if (count($selectoptions) < 10) { 358 $postfix .= ':'; 359 foreach ($selectoptions as $option) { 360 $postfix .= "\n ".$option['attr']['value']." - ".$option['text']; 361 } 362 } 363 if (!array_key_exists($name, $defaults)) { 364 $firstoption = reset($selectoptions); 365 $default = $firstoption['attr']['value']; 366 } 367 } 368 369 if ($element instanceof \HTML_QuickForm_checkbox) { 370 $postfix = ":\n 0|1"; 371 $possiblevalues = ['0', '1']; 372 } 373 374 if ($default !== null & $default !== '') { 375 $postfix .= "\n ".get_string('clidefault', 'tool_uploaduser')." ".$default; 376 } 377 $options[$name] = [ 378 'hasvalue' => 'VALUE', 379 'description' => $label.$postfix, 380 'default' => $default, 381 ]; 382 if ($possiblevalues !== null) { 383 $options[$name]['validation'] = function($v) use ($possiblevalues, $name) { 384 if (!in_array('' . $v, $possiblevalues)) { 385 $this->cli_error(get_string('clierrorargument', 'tool_uploaduser', 386 (object)['name' => $name, 'values' => join(', ', $possiblevalues)])); 387 } 388 }; 389 } 390 } 391 return $options; 392 } 393 394 /** 395 * Get process statistics. 396 * 397 * @return array 398 */ 399 public function get_stats(): array { 400 return $this->process->get_stats(); 401 } 402 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body