. /** * This file contains the class to import a competency framework. * * @package tool_lpimportcsv * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace tool_lpimportcsv; defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.'); use core_competency\api; use grade_scale; use stdClass; use context_system; use csv_import_reader; /** * This file contains the class to import a competency framework. * * @package tool_lpimportcsv * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class framework_importer { /** @var string $error The errors message from reading the xml */ protected $error = ''; /** @var array $flat The flat competencies tree */ protected $flat = array(); /** @var array $framework The framework info */ protected $framework = array(); protected $mappings = array(); protected $importid = 0; protected $importer = null; protected $foundheaders = array(); protected $scalecache = array(); /** @var bool $useprogressbar Control whether importing should use progress bars or not. */ protected $useprogressbar = false; /** @var \core\progress\display_if_slow|null $progress The progress bar instance. */ protected $progress = null; /** * Store an error message for display later * @param string $msg */ public function fail($msg) { $this->error = $msg; return false; } /** * Get the CSV import id * @return string The import id. */ public function get_importid() { return $this->importid; } /** * Get the list of headers required for import. * @return array The headers (lang strings) */ public static function list_required_headers() { return array( get_string('parentidnumber', 'tool_lpimportcsv'), get_string('idnumber', 'tool_lpimportcsv'), get_string('shortname', 'tool_lpimportcsv'), get_string('description', 'tool_lpimportcsv'), get_string('descriptionformat', 'tool_lpimportcsv'), get_string('scalevalues', 'tool_lpimportcsv'), get_string('scaleconfiguration', 'tool_lpimportcsv'), get_string('ruletype', 'tool_lpimportcsv'), get_string('ruleoutcome', 'tool_lpimportcsv'), get_string('ruleconfig', 'tool_lpimportcsv'), get_string('relatedidnumbers', 'tool_lpimportcsv'), get_string('exportid', 'tool_lpimportcsv'), get_string('isframework', 'tool_lpimportcsv'), get_string('taxonomy', 'tool_lpimportcsv'), ); } /** * Get the list of headers found in the import. * @return array The found headers (names from import) */ public function list_found_headers() { return $this->foundheaders; } /** * Read the data from the mapping form. * @param array The mapping data. */ protected function read_mapping_data($data) { if ($data) { return array( 'parentidnumber' => $data->header0, 'idnumber' => $data->header1, 'shortname' => $data->header2, 'description' => $data->header3, 'descriptionformat' => $data->header4, 'scalevalues' => $data->header5, 'scaleconfiguration' => $data->header6, 'ruletype' => $data->header7, 'ruleoutcome' => $data->header8, 'ruleconfig' => $data->header9, 'relatedidnumbers' => $data->header10, 'exportid' => $data->header11, 'isframework' => $data->header12, 'taxonomies' => $data->header13 ); } else { return array( 'parentidnumber' => 0, 'idnumber' => 1, 'shortname' => 2, 'description' => 3, 'descriptionformat' => 4, 'scalevalues' => 5, 'scaleconfiguration' => 6, 'ruletype' => 7, 'ruleoutcome' => 8, 'ruleconfig' => 9, 'relatedidnumbers' => 10, 'exportid' => 11, 'isframework' => 12, 'taxonomies' => 13 ); } } /** * Get the a column from the imported data. * @param array The imported raw row * @param index The column index we want * @return string The column data. */ protected function get_column_data($row, $index) { if ($index < 0) { return ''; } return isset($row[$index]) ? $row[$index] : ''; } /** * Constructor - parses the raw text for sanity. * @param string $text The raw csv text. * @param string $encoding The encoding of the csv file. * @param string delimiter The specified delimiter for the file. * @param string importid The id of the csv import. * @param array mappingdata The mapping data from the import form. * @param bool $useprogressbar Whether progress bar should be displayed, to avoid html output on CLI. */ public function __construct($text = null, $encoding = null, $delimiter = null, $importid = 0, $mappingdata = null, $useprogressbar = false) { global $CFG; // The format of our records is: // Parent ID number, ID number, Shortname, Description, Description format, Scale values, Scale configuration, // Rule type, Rule outcome, Rule config, Is framework, Taxonomy. // The idnumber is concatenated with the category names. require_once($CFG->libdir . '/csvlib.class.php'); $type = 'competency_framework'; if (!$importid) { if ($text === null) { return; } $this->importid = csv_import_reader::get_new_iid($type); $this->importer = new csv_import_reader($this->importid, $type); if (!$this->importer->load_csv_content($text, $encoding, $delimiter)) { $this->fail(get_string('invalidimportfile', 'tool_lpimportcsv')); $this->importer->cleanup(); return; } } else { $this->importid = $importid; $this->importer = new csv_import_reader($this->importid, $type); } if (!$this->importer->init()) { $this->fail(get_string('invalidimportfile', 'tool_lpimportcsv')); $this->importer->cleanup(); return; } $this->foundheaders = $this->importer->get_columns(); $this->useprogressbar = $useprogressbar; $domainid = 1; $flat = array(); $framework = null; while ($row = $this->importer->next()) { $mapping = $this->read_mapping_data($mappingdata); $parentidnumber = $this->get_column_data($row, $mapping['parentidnumber']); $idnumber = $this->get_column_data($row, $mapping['idnumber']); $shortname = $this->get_column_data($row, $mapping['shortname']); $description = $this->get_column_data($row, $mapping['description']); $descriptionformat = $this->get_column_data($row, $mapping['descriptionformat']); $scalevalues = $this->get_column_data($row, $mapping['scalevalues']); $scaleconfiguration = $this->get_column_data($row, $mapping['scaleconfiguration']); $ruletype = $this->get_column_data($row, $mapping['ruletype']); $ruleoutcome = $this->get_column_data($row, $mapping['ruleoutcome']); $ruleconfig = $this->get_column_data($row, $mapping['ruleconfig']); $relatedidnumbers = $this->get_column_data($row, $mapping['relatedidnumbers']); $exportid = $this->get_column_data($row, $mapping['exportid']); $isframework = $this->get_column_data($row, $mapping['isframework']); $taxonomies = $this->get_column_data($row, $mapping['taxonomies']); if ($isframework) { $framework = new stdClass(); $framework->idnumber = shorten_text(clean_param($idnumber, PARAM_TEXT), 100); $framework->shortname = shorten_text(clean_param($shortname, PARAM_TEXT), 100); $framework->description = clean_param($description, PARAM_RAW); $framework->descriptionformat = clean_param($descriptionformat, PARAM_INT); $framework->scalevalues = $scalevalues; $framework->scaleconfiguration = $scaleconfiguration; $framework->taxonomies = $taxonomies; $framework->children = array(); } else { $competency = new stdClass(); $competency->parentidnumber = clean_param($parentidnumber, PARAM_TEXT); $competency->idnumber = shorten_text(clean_param($idnumber, PARAM_TEXT), 100); $competency->shortname = shorten_text(clean_param($shortname, PARAM_TEXT), 100); $competency->description = clean_param($description, PARAM_RAW); $competency->descriptionformat = clean_param($descriptionformat, PARAM_INT); $competency->ruletype = $ruletype; $competency->ruleoutcome = clean_param($ruleoutcome, PARAM_INT); $competency->ruleconfig = $ruleconfig; $competency->relatedidnumbers = $relatedidnumbers; $competency->exportid = $exportid; $competency->scalevalues = $scalevalues; $competency->scaleconfiguration = $scaleconfiguration; $competency->children = array(); $flat[$idnumber] = $competency; } } $this->flat = $flat; $this->framework = $framework; $this->importer->close(); if ($this->framework == null) { $this->fail(get_string('invalidimportfile', 'tool_lpimportcsv')); return; } else { // We are calling from browser, display progress bar. if ($this->useprogressbar === true) { $this->progress = new \core\progress\display_if_slow(get_string('processingfile', 'tool_lpimportcsv')); } else { // Avoid html output on CLI scripts. $this->progress = new \core\progress\none(); } $this->progress->start_progress('', count($this->flat)); // Build a tree from this flat list. raise_memory_limit(MEMORY_EXTRA); $this->add_children($this->framework, ''); $this->progress->end_progress(); } } /** * Add a competency to the parent with the specified idnumber. * * @param competency $node (pass by reference) * @param string $parentidnumber Add this competency to the parent with this idnumber. */ public function add_children(& $node, $parentidnumber) { foreach ($this->flat as $competency) { if ($competency->parentidnumber == $parentidnumber) { $this->progress->increment_progress(); $node->children[] = $competency; $this->add_children($competency, $competency->idnumber); } } } /** * Get parse errors. * @return array of errors from parsing the xml. */ public function get_error() { return $this->error; } /** * Recursive function to add a competency with all it's children. * * @param stdClass $record Raw data for the new competency * @param competency $parent * @param competency_framework $framework */ public function create_competency($record, $parent, $framework) { $competency = new stdClass(); $competency->competencyframeworkid = $framework->get('id'); $competency->shortname = $record->shortname; if (!empty($record->description)) { $competency->description = $record->description; $competency->descriptionformat = $record->descriptionformat; } if ($record->scalevalues) { $competency->scaleid = $this->get_scale_id($record->scalevalues, $competency->shortname); $competency->scaleconfiguration = $this->get_scale_configuration($competency->scaleid, $record->scaleconfiguration); } if ($parent) { $competency->parentid = $parent->get('id'); } else { $competency->parentid = 0; } $competency->idnumber = $record->idnumber; if (!empty($competency->idnumber) && !empty($competency->shortname)) { $comp = api::create_competency($competency); if ($record->exportid) { $this->mappings[$record->exportid] = $comp; } $record->createdcomp = $comp; foreach ($record->children as $child) { $this->create_competency($child, $comp, $framework); } return $comp; } return false; } /** * Recreate the scale config to point to a new scaleid. * @param int $scaleid * @param string $config json encoded scale data. */ public function get_scale_configuration($scaleid, $config) { $asarray = json_decode($config); $asarray[0]->scaleid = $scaleid; return json_encode($asarray); } /** * Search for a global scale that matches this set of scalevalues. * If one is not found it will be created. * @param array $scalevalues * @param string $competencyname (Used to create a new scale if required) * @return int The id of the scale */ public function get_scale_id($scalevalues, $competencyname) { global $CFG, $USER; require_once($CFG->libdir . '/gradelib.php'); if (empty($this->scalecache)) { $allscales = grade_scale::fetch_all_global(); foreach ($allscales as $scale) { $scale->load_items(); $this->scalecache[$scale->compact_items()] = $scale; } } $matchingscale = false; if (isset($this->scalecache[$scalevalues])) { $matchingscale = $this->scalecache[$scalevalues]; } if (!$matchingscale) { // Create it. $newscale = new grade_scale(); $newscale->name = get_string('competencyscale', 'tool_lpimportcsv', $competencyname); $newscale->courseid = 0; $newscale->userid = $USER->id; $newscale->scale = $scalevalues; $newscale->description = get_string('competencyscaledescription', 'tool_lpimportcsv'); $newscale->insert(); $this->scalecache[$scalevalues] = $newscale; return $newscale->id; } return $matchingscale->id; } /** * Walk through the idnumbers in the relatedidnumbers col and set the relations. * @param stdClass $record */ protected function set_related($record) { $comp = $record->createdcomp; if ($record->relatedidnumbers) { $allidnumbers = explode(',', $record->relatedidnumbers); foreach ($allidnumbers as $rawidnumber) { $idnumber = str_replace('%2C', ',', $rawidnumber); if (isset($this->flat[$idnumber])) { $relatedcomp = $this->flat[$idnumber]->createdcomp; api::add_related_competency($comp->get('id'), $relatedcomp->get('id')); } } } foreach ($record->children as $child) { $this->set_related($child); } } /** * Create any completion rule attached to this competency. * @param stdClass $record */ protected function set_rules($record) { $comp = $record->createdcomp; if ($record->ruletype) { $class = $record->ruletype; if (class_exists($class)) { $oldruleconfig = $record->ruleconfig; if ($oldruleconfig == "null") { $oldruleconfig = null; } $newruleconfig = $class::migrate_config($oldruleconfig, $this->mappings); $comp->set('ruleconfig', $newruleconfig); $comp->set('ruletype', $class); $comp->set('ruleoutcome', $record->ruleoutcome); $comp->update(); } } foreach ($record->children as $child) { $this->set_rules($child); } } /** * Do the job. * @return competency_framework */ public function import() { $record = clone $this->framework; unset($record->children); $record->scaleid = $this->get_scale_id($record->scalevalues, $record->shortname); $record->scaleconfiguration = $this->get_scale_configuration($record->scaleid, $record->scaleconfiguration); unset($record->scalevalues); $record->contextid = context_system::instance()->id; $framework = api::create_framework($record); if ($this->useprogressbar === true) { $this->progress = new \core\progress\display_if_slow(get_string('importingfile', 'tool_lpimportcsv')); } else { $this->progress = new \core\progress\none(); } $this->progress->start_progress('', (count($this->framework->children) * 2)); raise_memory_limit(MEMORY_EXTRA); // Now all the children. foreach ($this->framework->children as $comp) { $this->progress->increment_progress(); $this->create_competency($comp, null, $framework); } // Now create the rules. foreach ($this->framework->children as $record) { $this->progress->increment_progress(); $this->set_rules($record); $this->set_related($record); } $this->progress->end_progress(); $this->importer->cleanup(); return $framework; } }