Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
                // The menu profile field type allows for an empty default value, handle that here.
372
                if (preg_match('/^profile_field_/', $name) && $default === '') {
373
                    $possiblevalues[] = $default;
374
                }
375
            }
376
 
377
            if ($element instanceof \HTML_QuickForm_checkbox) {
378
                $postfix = ":\n  0|1";
379
                $possiblevalues = ['0', '1'];
380
            }
381
 
382
            if ($default !== null & $default !== '') {
383
                $postfix .= "\n  ".get_string('clidefault', 'tool_uploaduser')." ".$default;
384
            }
385
            $options[$name] = [
386
                'hasvalue' => 'VALUE',
387
                'description' => $label.$postfix,
388
                'default' => $default,
389
            ];
390
            if ($possiblevalues !== null) {
391
                $options[$name]['validation'] = function($v) use ($possiblevalues, $name) {
392
                    if (!in_array('' . $v, $possiblevalues)) {
393
                        $this->cli_error(get_string('clierrorargument', 'tool_uploaduser',
394
                            (object)['name' => $name, 'values' => join(', ', $possiblevalues)]));
395
                    }
396
                };
397
            }
398
        }
399
        return $options;
400
    }
401
 
402
    /**
403
     * Get process statistics.
404
     *
405
     * @return array
406
     */
407
    public function get_stats(): array {
408
        return $this->process->get_stats();
409
    }
410
}