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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body