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