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
 * This file contains the class to import a competency framework.
19
 *
20
 * @package   tool_lpimportcsv
21
 * @copyright 2015 Damyon Wiese
22
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
namespace tool_lpimportcsv;
26
 
27
defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
28
 
29
use core_competency\api;
30
use grade_scale;
31
use stdClass;
32
use context_system;
33
use csv_import_reader;
34
 
35
/**
36
 * This file contains the class to import a competency framework.
37
 *
38
 * @package   tool_lpimportcsv
39
 * @copyright 2015 Damyon Wiese
40
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41
 */
42
class framework_importer {
43
 
44
    /** @var string $error The errors message from reading the xml */
45
    protected $error = '';
46
 
47
    /** @var array $flat The flat competencies tree */
48
    protected $flat = array();
49
    /** @var array $framework The framework info */
50
    protected $framework = array();
51
    protected $mappings = array();
52
    protected $importid = 0;
53
    protected $importer = null;
54
    protected $foundheaders = array();
55
    protected $scalecache = array();
56
    /** @var bool $useprogressbar Control whether importing should use progress bars or not. */
57
    protected $useprogressbar = false;
58
    /** @var \core\progress\display_if_slow|null $progress The progress bar instance. */
59
    protected $progress = null;
60
 
61
    /**
62
     * Store an error message for display later
63
     * @param string $msg
64
     */
65
    public function fail($msg) {
66
        $this->error = $msg;
67
        return false;
68
    }
69
 
70
    /**
71
     * Get the CSV import id
72
     * @return string The import id.
73
     */
74
    public function get_importid() {
75
        return $this->importid;
76
    }
77
 
78
    /**
79
     * Get the list of headers required for import.
80
     * @return array The headers (lang strings)
81
     */
82
    public static function list_required_headers() {
83
        return array(
84
            get_string('parentidnumber', 'tool_lpimportcsv'),
85
            get_string('idnumber', 'tool_lpimportcsv'),
86
            get_string('shortname', 'tool_lpimportcsv'),
87
            get_string('description', 'tool_lpimportcsv'),
88
            get_string('descriptionformat', 'tool_lpimportcsv'),
89
            get_string('scalevalues', 'tool_lpimportcsv'),
90
            get_string('scaleconfiguration', 'tool_lpimportcsv'),
91
            get_string('ruletype', 'tool_lpimportcsv'),
92
            get_string('ruleoutcome', 'tool_lpimportcsv'),
93
            get_string('ruleconfig', 'tool_lpimportcsv'),
94
            get_string('relatedidnumbers', 'tool_lpimportcsv'),
95
            get_string('exportid', 'tool_lpimportcsv'),
96
            get_string('isframework', 'tool_lpimportcsv'),
97
            get_string('taxonomy', 'tool_lpimportcsv'),
98
        );
99
    }
100
 
101
    /**
102
     * Get the list of headers found in the import.
103
     * @return array The found headers (names from import)
104
     */
105
    public function list_found_headers() {
106
        return $this->foundheaders;
107
    }
108
 
109
    /**
110
     * Read the data from the mapping form.
111
     * @param array The mapping data.
112
     */
113
    protected function read_mapping_data($data) {
114
        if ($data) {
115
            return array(
116
                'parentidnumber' => $data->header0,
117
                'idnumber' => $data->header1,
118
                'shortname' => $data->header2,
119
                'description' => $data->header3,
120
                'descriptionformat' => $data->header4,
121
                'scalevalues' => $data->header5,
122
                'scaleconfiguration' => $data->header6,
123
                'ruletype' => $data->header7,
124
                'ruleoutcome' => $data->header8,
125
                'ruleconfig' => $data->header9,
126
                'relatedidnumbers' => $data->header10,
127
                'exportid' => $data->header11,
128
                'isframework' => $data->header12,
129
                'taxonomies' => $data->header13
130
            );
131
        } else {
132
            return array(
133
                'parentidnumber' => 0,
134
                'idnumber' => 1,
135
                'shortname' => 2,
136
                'description' => 3,
137
                'descriptionformat' => 4,
138
                'scalevalues' => 5,
139
                'scaleconfiguration' => 6,
140
                'ruletype' => 7,
141
                'ruleoutcome' => 8,
142
                'ruleconfig' => 9,
143
                'relatedidnumbers' => 10,
144
                'exportid' => 11,
145
                'isframework' => 12,
146
                'taxonomies' => 13
147
            );
148
        }
149
    }
150
 
151
    /**
152
     * Get the a column from the imported data.
153
     * @param array The imported raw row
154
     * @param index The column index we want
155
     * @return string The column data.
156
     */
157
    protected function get_column_data($row, $index) {
158
        if ($index < 0) {
159
            return '';
160
        }
161
        return isset($row[$index]) ? $row[$index] : '';
162
    }
163
 
164
    /**
165
     * Constructor - parses the raw text for sanity.
166
     * @param string $text The raw csv text.
167
     * @param string $encoding The encoding of the csv file.
168
     * @param string delimiter The specified delimiter for the file.
169
     * @param string importid The id of the csv import.
170
     * @param array mappingdata The mapping data from the import form.
171
     * @param bool $useprogressbar Whether progress bar should be displayed, to avoid html output on CLI.
172
     */
173
    public function __construct($text = null, $encoding = null, $delimiter = null, $importid = 0, $mappingdata = null,
174
            $useprogressbar = false) {
175
 
176
        global $CFG;
177
 
178
        // The format of our records is:
179
        // Parent ID number, ID number, Shortname, Description, Description format, Scale values, Scale configuration,
180
        // Rule type, Rule outcome, Rule config, Is framework, Taxonomy.
181
 
182
        // The idnumber is concatenated with the category names.
183
        require_once($CFG->libdir . '/csvlib.class.php');
184
 
185
        $type = 'competency_framework';
186
 
187
        if (!$importid) {
188
            if ($text === null) {
189
                return;
190
            }
191
            $this->importid = csv_import_reader::get_new_iid($type);
192
 
193
            $this->importer = new csv_import_reader($this->importid, $type);
194
 
195
            if (!$this->importer->load_csv_content($text, $encoding, $delimiter)) {
196
                $this->fail(get_string('invalidimportfile', 'tool_lpimportcsv'));
197
                $this->importer->cleanup();
198
                return;
199
            }
200
 
201
        } else {
202
            $this->importid = $importid;
203
 
204
            $this->importer = new csv_import_reader($this->importid, $type);
205
        }
206
 
207
        if (!$this->importer->init()) {
208
            $this->fail(get_string('invalidimportfile', 'tool_lpimportcsv'));
209
            $this->importer->cleanup();
210
            return;
211
        }
212
 
213
        $this->foundheaders = $this->importer->get_columns();
214
        $this->useprogressbar = $useprogressbar;
215
        $domainid = 1;
216
 
217
        $flat = array();
218
        $framework = null;
219
 
220
        while ($row = $this->importer->next()) {
221
            $mapping = $this->read_mapping_data($mappingdata);
222
 
223
            $parentidnumber = $this->get_column_data($row, $mapping['parentidnumber']);
224
            $idnumber = $this->get_column_data($row, $mapping['idnumber']);
225
            $shortname = $this->get_column_data($row, $mapping['shortname']);
226
            $description = $this->get_column_data($row, $mapping['description']);
227
            $descriptionformat = $this->get_column_data($row, $mapping['descriptionformat']);
228
            $scalevalues = $this->get_column_data($row, $mapping['scalevalues']);
229
            $scaleconfiguration = $this->get_column_data($row, $mapping['scaleconfiguration']);
230
            $ruletype = $this->get_column_data($row, $mapping['ruletype']);
231
            $ruleoutcome = $this->get_column_data($row, $mapping['ruleoutcome']);
232
            $ruleconfig = $this->get_column_data($row, $mapping['ruleconfig']);
233
            $relatedidnumbers = $this->get_column_data($row, $mapping['relatedidnumbers']);
234
            $exportid = $this->get_column_data($row, $mapping['exportid']);
235
            $isframework = $this->get_column_data($row, $mapping['isframework']);
236
            $taxonomies = $this->get_column_data($row, $mapping['taxonomies']);
237
 
238
            if ($isframework) {
239
                $framework = new stdClass();
240
                $framework->idnumber = shorten_text(clean_param($idnumber, PARAM_TEXT), 100);
241
                $framework->shortname = shorten_text(clean_param($shortname, PARAM_TEXT), 100);
242
                $framework->description = clean_param($description, PARAM_RAW);
243
                $framework->descriptionformat = clean_param($descriptionformat, PARAM_INT);
244
                $framework->scalevalues = $scalevalues;
245
                $framework->scaleconfiguration = $scaleconfiguration;
246
                $framework->taxonomies = $taxonomies;
247
                $framework->children = array();
248
            } else {
249
                $competency = new stdClass();
250
                $competency->parentidnumber = clean_param($parentidnumber, PARAM_TEXT);
251
                $competency->idnumber = shorten_text(clean_param($idnumber, PARAM_TEXT), 100);
252
                $competency->shortname = shorten_text(clean_param($shortname, PARAM_TEXT), 100);
253
                $competency->description = clean_param($description, PARAM_RAW);
254
                $competency->descriptionformat = clean_param($descriptionformat, PARAM_INT);
255
                $competency->ruletype = $ruletype;
256
                $competency->ruleoutcome = clean_param($ruleoutcome, PARAM_INT);
257
                $competency->ruleconfig = $ruleconfig;
258
                $competency->relatedidnumbers = $relatedidnumbers;
259
                $competency->exportid = $exportid;
260
                $competency->scalevalues = $scalevalues;
261
                $competency->scaleconfiguration = $scaleconfiguration;
262
                $competency->children = array();
263
                $flat[$idnumber] = $competency;
264
            }
265
        }
266
        $this->flat = $flat;
267
        $this->framework = $framework;
268
 
269
        $this->importer->close();
270
        if ($this->framework == null) {
271
            $this->fail(get_string('invalidimportfile', 'tool_lpimportcsv'));
272
            return;
273
        } else {
274
            // We are calling from browser, display progress bar.
275
            if ($this->useprogressbar === true) {
276
                $this->progress = new \core\progress\display_if_slow(get_string('processingfile', 'tool_lpimportcsv'));
277
            } else {
278
                // Avoid html output on CLI scripts.
279
                $this->progress = new \core\progress\none();
280
            }
281
            $this->progress->start_progress('', count($this->flat));
282
            // Build a tree from this flat list.
283
            raise_memory_limit(MEMORY_EXTRA);
284
            $this->add_children($this->framework, '');
285
            $this->progress->end_progress();
286
        }
287
    }
288
 
289
    /**
290
     * Add a competency to the parent with the specified idnumber.
291
     *
292
     * @param competency $node (pass by reference)
293
     * @param string $parentidnumber Add this competency to the parent with this idnumber.
294
     */
295
    public function add_children(& $node, $parentidnumber) {
296
        foreach ($this->flat as $competency) {
297
            if ($competency->parentidnumber == $parentidnumber) {
298
                $this->progress->increment_progress();
299
                $node->children[] = $competency;
300
                $this->add_children($competency, $competency->idnumber);
301
            }
302
        }
303
    }
304
 
305
    /**
306
     * Get parse errors.
307
     * @return array of errors from parsing the xml.
308
     */
309
    public function get_error() {
310
        return $this->error;
311
    }
312
 
313
    /**
314
     * Recursive function to add a competency with all it's children.
315
     *
316
     * @param stdClass $record Raw data for the new competency
317
     * @param competency $parent
318
     * @param competency_framework $framework
319
     */
320
    public function create_competency($record, $parent, $framework) {
321
        $competency = new stdClass();
322
        $competency->competencyframeworkid = $framework->get('id');
323
        $competency->shortname = $record->shortname;
324
        if (!empty($record->description)) {
325
            $competency->description = $record->description;
326
            $competency->descriptionformat = $record->descriptionformat;
327
        }
328
        if ($record->scalevalues) {
329
            $competency->scaleid = $this->get_scale_id($record->scalevalues, $competency->shortname);
330
            $competency->scaleconfiguration = $this->get_scale_configuration($competency->scaleid, $record->scaleconfiguration);
331
        }
332
        if ($parent) {
333
            $competency->parentid = $parent->get('id');
334
        } else {
335
            $competency->parentid = 0;
336
        }
337
        $competency->idnumber = $record->idnumber;
338
 
339
        if (!empty($competency->idnumber) && !empty($competency->shortname)) {
340
            $comp = api::create_competency($competency);
341
            if ($record->exportid) {
342
                $this->mappings[$record->exportid] = $comp;
343
            }
344
            $record->createdcomp = $comp;
345
            foreach ($record->children as $child) {
346
                $this->create_competency($child, $comp, $framework);
347
            }
348
 
349
            return $comp;
350
        }
351
        return false;
352
    }
353
 
354
    /**
355
     * Recreate the scale config to point to a new scaleid.
356
     * @param int $scaleid
357
     * @param string $config json encoded scale data.
358
     */
359
    public function get_scale_configuration($scaleid, $config) {
360
        $asarray = json_decode($config);
361
        $asarray[0]->scaleid = $scaleid;
362
        return json_encode($asarray);
363
    }
364
 
365
    /**
366
     * Search for a global scale that matches this set of scalevalues.
367
     * If one is not found it will be created.
368
     * @param array $scalevalues
369
     * @param string $competencyname (Used to create a new scale if required)
370
     * @return int The id of the scale
371
     */
372
    public function get_scale_id($scalevalues, $competencyname) {
373
        global $CFG, $USER;
374
 
375
        require_once($CFG->libdir . '/gradelib.php');
376
 
377
        if (empty($this->scalecache)) {
378
            $allscales = grade_scale::fetch_all_global();
379
            foreach ($allscales as $scale) {
380
                $scale->load_items();
381
                $this->scalecache[$scale->compact_items()] = $scale;
382
            }
383
        }
384
        $matchingscale = false;
385
        if (isset($this->scalecache[$scalevalues])) {
386
            $matchingscale = $this->scalecache[$scalevalues];
387
        }
388
        if (!$matchingscale) {
389
            // Create it.
390
            $newscale = new grade_scale();
391
            $newscale->name = get_string('competencyscale', 'tool_lpimportcsv', $competencyname);
392
            $newscale->courseid = 0;
393
            $newscale->userid = $USER->id;
394
            $newscale->scale = $scalevalues;
395
            $newscale->description = get_string('competencyscaledescription', 'tool_lpimportcsv');
396
            $newscale->insert();
397
            $this->scalecache[$scalevalues] = $newscale;
398
            return $newscale->id;
399
        }
400
        return $matchingscale->id;
401
    }
402
 
403
    /**
404
     * Walk through the idnumbers in the relatedidnumbers col and set the relations.
405
     * @param stdClass $record
406
     */
407
    protected function set_related($record) {
408
        $comp = $record->createdcomp;
409
        if ($record->relatedidnumbers) {
410
            $allidnumbers = explode(',', $record->relatedidnumbers);
411
            foreach ($allidnumbers as $rawidnumber) {
412
                $idnumber = str_replace('%2C', ',', $rawidnumber);
413
 
414
                if (isset($this->flat[$idnumber])) {
415
                    $relatedcomp = $this->flat[$idnumber]->createdcomp;
416
                    api::add_related_competency($comp->get('id'), $relatedcomp->get('id'));
417
                }
418
            }
419
        }
420
        foreach ($record->children as $child) {
421
            $this->set_related($child);
422
        }
423
    }
424
 
425
    /**
426
     * Create any completion rule attached to this competency.
427
     * @param stdClass $record
428
     */
429
    protected function set_rules($record) {
430
        $comp = $record->createdcomp;
431
        if ($record->ruletype) {
432
            $class = $record->ruletype;
433
            if (class_exists($class)) {
434
                $oldruleconfig = $record->ruleconfig;
435
                if ($oldruleconfig == "null") {
436
                    $oldruleconfig = null;
437
                }
438
                $newruleconfig = $class::migrate_config($oldruleconfig, $this->mappings);
439
                $comp->set('ruleconfig', $newruleconfig);
440
                $comp->set('ruletype', $class);
441
                $comp->set('ruleoutcome', $record->ruleoutcome);
442
                $comp->update();
443
            }
444
        }
445
        foreach ($record->children as $child) {
446
            $this->set_rules($child);
447
        }
448
    }
449
 
450
    /**
451
     * Do the job.
452
     * @return competency_framework
453
     */
454
    public function import() {
455
        $record = clone $this->framework;
456
        unset($record->children);
457
 
458
        $record->scaleid = $this->get_scale_id($record->scalevalues, $record->shortname);
459
        $record->scaleconfiguration = $this->get_scale_configuration($record->scaleid, $record->scaleconfiguration);
460
        unset($record->scalevalues);
461
        $record->contextid = context_system::instance()->id;
462
 
463
        $framework = api::create_framework($record);
464
        if ($this->useprogressbar === true) {
465
            $this->progress = new \core\progress\display_if_slow(get_string('importingfile', 'tool_lpimportcsv'));
466
        } else {
467
            $this->progress = new \core\progress\none();
468
        }
469
 
470
        $this->progress->start_progress('', (count($this->framework->children) * 2));
471
        raise_memory_limit(MEMORY_EXTRA);
472
        // Now all the children.
473
        foreach ($this->framework->children as $comp) {
474
            $this->progress->increment_progress();
475
            $this->create_competency($comp, null, $framework);
476
        }
477
 
478
        // Now create the rules.
479
        foreach ($this->framework->children as $record) {
480
            $this->progress->increment_progress();
481
            $this->set_rules($record);
482
            $this->set_related($record);
483
        }
484
        $this->progress->end_progress();
485
 
486
        $this->importer->cleanup();
487
        return $framework;
488
    }
489
}