See Release Notes
Long Term Support Release
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 * Processor. 19 * 20 * @package tool_lpmigrate 21 * @copyright 2016 Frédéric Massart - FMCorz.net 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 namespace tool_lpmigrate; 26 defined('MOODLE_INTERNAL') || die(); 27 28 use coding_exception; 29 use moodle_exception; 30 use core_competency\api; 31 use core_competency\competency; 32 use core_competency\course_competency; 33 use core_competency\course_module_competency; 34 35 /** 36 * Processor class. 37 * 38 * @package tool_lpmigrate 39 * @copyright 2016 Frédéric Massart - FMCorz.net 40 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 41 */ 42 class framework_processor { 43 44 /** @var array Indexed as courseid => competencyids */ 45 protected $coursescompetencies = array(); 46 /** @var array Indexed as courseid => competencyid => ruleoutcome*/ 47 protected $coursescompetenciesoutcomes = array(); 48 /** @var array Indexed as courseid => cmid => competencyids */ 49 protected $modulecompetencies = array(); 50 /** @var array Indexed as courseid => cmid => competencyid => ruleoutcome*/ 51 protected $modulecompetenciesoutcomes = array(); 52 /** @var array The IDs of the objects of origin. */ 53 protected $fromids = array(); 54 /** @var array The mapping originId => destinationId. */ 55 protected $mappings = array(); 56 57 /** @var array Courses found. */ 58 protected $coursesfound = array(); 59 /** @var array Course modules found. */ 60 protected $cmsfound = array(); 61 /** @var integer Number of migrations expected in courses. */ 62 protected $coursecompetencyexpectedmigrations = 0; 63 /** @var integer Count of migrations in the course level. */ 64 protected $coursecompetencymigrations = 0; 65 /** @var integer Count of removals in the course level. */ 66 protected $coursecompetencyremovals = 0; 67 /** @var integer Number of migrations expected in CMs. */ 68 protected $modulecompetencyexpectedmigrations = 0; 69 /** @var integer Count of migrations in CMs. */ 70 protected $modulecompetencymigrations = 0; 71 /** @var integer Count of removals in CMs. */ 72 protected $modulecompetencyremovals = 0; 73 /** @var array IDs of objects missing a mapping in origin, originId => true. */ 74 protected $missingmappings = array(); 75 /** @var array List of errors. */ 76 protected $errors = array(); 77 /** @var array List of warnings. */ 78 protected $warnings = array(); 79 80 /** @var array List of course IDs that can be migrated. */ 81 protected $allowedcourses = array(); 82 /** @var int Minimum start date of courses that can be migrated. */ 83 protected $coursestartdatefrom = 0; 84 /** @var array List of course IDs that cannot be migrated. */ 85 protected $disallowedcourses = array(); 86 /** @var bool Whether to remove the original competency when its destination was already there. */ 87 protected $removeoriginalwhenalreadypresent = false; 88 /** @var bool Whether to remove the competency from course, or cm, when a mapping is not found. */ 89 protected $removewhenmappingismissing = false; 90 91 /** @var boolean Has this processor run? */ 92 protected $proceeded = false; 93 /** @var framework_mapper The mapper. */ 94 protected $mapper; 95 /** @var \core\progress\base The progress. */ 96 protected $progress; 97 98 /** 99 * Constructor. 100 * 101 * @param framework_mapper $mapper The mapper. 102 * @param \core\progress\base $progress The progress object. 103 */ 104 public function __construct(framework_mapper $mapper, \core\progress\base $progress = null) { 105 $this->mapper = $mapper; 106 107 if ($progress == null) { 108 $progress = new \core\progress\none(); 109 } 110 $this->progress = $progress; 111 } 112 113 /** 114 * Process the mapping. 115 * @return void 116 */ 117 protected function process_mapping() { 118 $this->mappings = $this->mapper->get_mappings(); 119 $this->fromids = $this->mapper->get_all_from(); 120 } 121 122 /** 123 * Identifies what courses and their competencies to work with. 124 * @return void 125 */ 126 protected function find_coursescompetencies() { 127 global $DB; 128 $this->progress->start_progress(get_string('findingcoursecompetencies', 'tool_lpmigrate'), 3); 129 $this->progress->increment_progress(); 130 131 $joins = array(); 132 $conditions = array(); 133 $params = array(); 134 135 // Limit to mapped objects. 136 list($insql, $inparams) = $DB->get_in_or_equal($this->fromids, SQL_PARAMS_NAMED); 137 $conditions[] = "c.id $insql"; 138 $params += $inparams; 139 140 // Restriction on course IDs. 141 if (!empty($this->allowedcourses)) { 142 list($insql, $inparams) = $DB->get_in_or_equal($this->allowedcourses, SQL_PARAMS_NAMED); 143 $conditions[] = "cc.courseid $insql"; 144 $params += $inparams; 145 } 146 if (!empty($this->disallowedcourses)) { 147 list($insql, $inparams) = $DB->get_in_or_equal($this->disallowedcourses, SQL_PARAMS_NAMED, 'param', false); 148 $conditions[] = "cc.courseid $insql"; 149 $params += $inparams; 150 } 151 152 // Restriction on start date. 153 if (!empty($this->coursestartdatefrom)) { 154 $joins[] = "JOIN {course} co 155 ON co.id = cc.courseid"; 156 $conditions[] = "co.startdate >= :startdate"; 157 $params += array('startdate' => $this->coursestartdatefrom); 158 } 159 160 // Find the courses. 161 $ccs = array(); 162 $ccsoutcomes = array(); 163 $joins = implode(' ', $joins); 164 $conditions = implode(' AND ', $conditions); 165 $sql = "SELECT cc.id, cc.courseid, cc.competencyid, cc.ruleoutcome 166 FROM {" . course_competency::TABLE . "} cc 167 JOIN {" . competency::TABLE . "} c 168 ON c.id = cc.competencyid 169 $joins 170 WHERE $conditions 171 ORDER BY cc.sortorder, cc.id"; 172 173 $records = $DB->get_recordset_sql($sql, $params); 174 $this->progress->increment_progress(); 175 176 foreach ($records as $record) { 177 if (!isset($ccs[$record->courseid])) { 178 $ccs[$record->courseid] = array(); 179 $ccsoutcomes[$record->courseid] = array(); 180 } 181 $ccs[$record->courseid][] = $record->competencyid; 182 $ccsoutcomes[$record->courseid][$record->competencyid] = $record->ruleoutcome; 183 } 184 $records->close(); 185 186 $this->coursescompetencies = $ccs; 187 $this->coursescompetenciesoutcomes = $ccsoutcomes; 188 $this->coursesfound = $ccs; 189 190 $this->progress->increment_progress(); 191 $this->progress->end_progress(); 192 } 193 194 /** 195 * Identifies what course modules and their competencies to work with. 196 * @return void 197 */ 198 protected function find_modulecompetencies() { 199 global $DB; 200 if (empty($this->coursescompetencies)) { 201 return; 202 } 203 204 $this->progress->start_progress(get_string('findingmodulecompetencies', 'tool_lpmigrate'), 3); 205 $this->progress->increment_progress(); 206 207 // Limit to mapped objects. 208 list($inidsql, $inidparams) = $DB->get_in_or_equal($this->fromids, SQL_PARAMS_NAMED); 209 210 // Limit to known courses. 211 list($incoursesql, $incourseparams) = $DB->get_in_or_equal(array_keys($this->coursescompetencies), SQL_PARAMS_NAMED); 212 $sql = "SELECT mc.id, cm.course AS courseid, mc.cmid, mc.competencyid, mc.ruleoutcome 213 FROM {" . course_module_competency::TABLE . "} mc 214 JOIN {course_modules} cm 215 ON cm.id = mc.cmid 216 AND cm.course $incoursesql 217 JOIN {" . competency::TABLE . "} c 218 ON c.id = mc.competencyid 219 WHERE c.id $inidsql 220 ORDER BY mc.sortorder, mc.id"; 221 $params = $inidparams + $incourseparams; 222 223 $records = $DB->get_recordset_sql($sql, $params); 224 $this->progress->increment_progress(); 225 $cmsfound = array(); 226 227 $cmcs = array(); 228 $cmcsoutcomes = array(); 229 foreach ($records as $record) { 230 if (!isset($cmcs[$record->courseid])) { 231 $cmcs[$record->courseid] = array(); 232 $cmcsoutcomes[$record->courseid] = array(); 233 } 234 if (!isset($cmcs[$record->courseid][$record->cmid])) { 235 $cmcs[$record->courseid][$record->cmid] = array(); 236 $cmcsoutcomes[$record->courseid][$record->cmid] = array(); 237 } 238 $cmcs[$record->courseid][$record->cmid][] = $record->competencyid; 239 $cmcsoutcomes[$record->courseid][$record->cmid][$record->competencyid] = $record->ruleoutcome; 240 $cmsfound[$record->cmid] = true; 241 } 242 $records->close(); 243 244 $this->modulecompetencies = $cmcs; 245 $this->modulecompetenciesoutcomes = $cmcsoutcomes; 246 $this->cmsfound = $cmsfound; 247 248 $this->progress->increment_progress(); 249 $this->progress->end_progress(); 250 } 251 252 /** 253 * Return a list of CMs found. 254 * @return int 255 */ 256 public function get_cms_found() { 257 return $this->cmsfound; 258 } 259 260 /** 261 * Return the number of CMs found. 262 * @return int 263 */ 264 public function get_cms_found_count() { 265 return count($this->cmsfound); 266 } 267 268 /** 269 * Return a list of courses found. 270 * @return int 271 */ 272 public function get_courses_found() { 273 return $this->coursesfound; 274 } 275 276 /** 277 * Return the number of courses found. 278 * @return int 279 */ 280 public function get_courses_found_count() { 281 return count($this->coursesfound); 282 } 283 284 /** 285 * Get the number of course migrations. 286 * @return int 287 */ 288 public function get_course_competency_migrations() { 289 return $this->coursecompetencymigrations; 290 } 291 292 /** 293 * Get the number of removals. 294 * @return int 295 */ 296 public function get_course_competency_removals() { 297 return $this->coursecompetencyremovals; 298 } 299 300 /** 301 * Get the number of expected course migrations. 302 * @return int 303 */ 304 public function get_expected_course_competency_migrations() { 305 return $this->coursecompetencyexpectedmigrations; 306 } 307 308 /** 309 * Get the number of expected course module migrations. 310 * @return int 311 */ 312 public function get_expected_module_competency_migrations() { 313 return $this->modulecompetencyexpectedmigrations; 314 } 315 316 /** 317 * Get the number of course module migrations. 318 * @return int 319 */ 320 public function get_module_competency_migrations() { 321 return $this->modulecompetencymigrations; 322 } 323 324 /** 325 * Get the number of removals. 326 * @return int 327 */ 328 public function get_module_competency_removals() { 329 return $this->modulecompetencyremovals; 330 } 331 332 /** 333 * Return a list of errors. 334 * @return array 335 */ 336 public function get_errors() { 337 return $this->errors; 338 } 339 340 /** 341 * Get the missing mappings. 342 * @return array Where keys are origin IDs. 343 */ 344 public function get_missing_mappings() { 345 if (!$this->has_run()) { 346 throw new coding_exception('The processor has not run yet.'); 347 } 348 return $this->missingmappings; 349 } 350 351 /** 352 * Return a list of warnings. 353 * @return array 354 */ 355 public function get_warnings() { 356 return $this->warnings; 357 } 358 359 /** 360 * Whether the processor has run. 361 * @return boolean 362 */ 363 public function has_run() { 364 return $this->proceeded; 365 } 366 367 /** 368 * Log an error. 369 * @param int $courseid The course ID. 370 * @param int $competencyid The competency ID. 371 * @param int $cmid The CM ID. 372 * @param string $message The error message. 373 * @return void 374 */ 375 protected function log_error($courseid, $competencyid, $cmid, $message) { 376 $this->errors[] = array( 377 'courseid' => $courseid, 378 'competencyid' => $competencyid, 379 'cmid' => $cmid, 380 'message' => $message 381 ); 382 } 383 384 /** 385 * Log a warning. 386 * @param int $courseid The course ID. 387 * @param int $competencyid The competency ID. 388 * @param int $cmid The CM ID. 389 * @param string $message The warning message. 390 * @return void 391 */ 392 protected function log_warning($courseid, $competencyid, $cmid, $message) { 393 $this->warnings[] = array( 394 'courseid' => $courseid, 395 'competencyid' => $competencyid, 396 'cmid' => $cmid, 397 'message' => $message 398 ); 399 } 400 401 /** 402 * Execute the whole task. 403 * @return void 404 */ 405 public function proceed() { 406 if ($this->has_run()) { 407 throw new coding_exception('The processor has already run.'); 408 } else if (!$this->mapper->has_mappings()) { 409 throw new coding_exception('Mapping was not set.'); 410 } 411 412 $this->proceeded = true; 413 $this->process_mapping(); 414 $this->find_coursescompetencies(); 415 $this->find_modulecompetencies(); 416 $this->process_courses(); 417 } 418 419 /** 420 * Process each course individually. 421 * @return void 422 */ 423 protected function process_courses() { 424 global $DB; 425 $this->progress->start_progress(get_string('migratingcourses', 'tool_lpmigrate'), count($this->coursescompetencies)); 426 427 // Process each course. 428 foreach ($this->coursescompetencies as $courseid => $competencyids) { 429 $this->progress->increment_progress(); 430 431 $competenciestoremovefromcourse = array(); 432 $skipcompetencies = array(); 433 434 // First, add all the new competencies to the course. 435 foreach ($competencyids as $key => $competencyid) { 436 $this->coursecompetencyexpectedmigrations++; 437 $mapto = isset($this->mappings[$competencyid]) ? $this->mappings[$competencyid] : false; 438 439 // Skip the competencies that are not mapped. 440 if ($mapto === false) { 441 $this->missingmappings[$competencyid] = true; 442 443 if ($this->removewhenmappingismissing) { 444 $competenciestoremovefromcourse[$competencyid] = true; 445 } 446 447 continue; 448 } 449 450 $transaction = $DB->start_delegated_transaction(); 451 try { 452 // Add the new competency to the course. 453 if (api::add_competency_to_course($courseid, $mapto)) { 454 455 // Find the added course competency. 456 $cc = course_competency::get_record(array('courseid' => $courseid, 'competencyid' => $mapto)); 457 458 // Set the rule. 459 api::set_course_competency_ruleoutcome($cc, $this->coursescompetenciesoutcomes[$courseid][$competencyid]); 460 461 // Adapt the sortorder. 462 api::reorder_course_competency($courseid, $mapto, $competencyid); 463 464 $competenciestoremovefromcourse[$competencyid] = true; 465 $this->coursecompetencymigrations++; 466 467 } else { 468 // The competency was already in the course... 469 if ($this->removeoriginalwhenalreadypresent) { 470 $competenciestoremovefromcourse[$competencyid] = true; 471 } else { 472 $this->log_warning($courseid, $competencyid, null, 473 get_string('warningdestinationcoursecompetencyalreadyexists', 'tool_lpmigrate')); 474 } 475 } 476 477 } catch (moodle_exception $e) { 478 // There was a major problem with this competency, we will ignore it entirely for the course. 479 $skipcompetencies[$competencyid] = true; 480 481 $this->log_error($courseid, $competencyid, null, 482 get_string('errorwhilemigratingcoursecompetencywithexception', 'tool_lpmigrate', $e->getMessage())); 483 484 try { 485 $transaction->rollback($e); 486 } catch (moodle_exception $e) { 487 // Catch the re-thrown exception. 488 } 489 490 continue; 491 } 492 $transaction->allow_commit(); 493 } 494 495 // Then, convert the module competencies. 496 if (!empty($this->modulecompetencies[$courseid])) { 497 foreach ($this->modulecompetencies[$courseid] as $cmid => $competencyids) { 498 foreach ($competencyids as $competencyid) { 499 $this->modulecompetencyexpectedmigrations++; 500 501 // This mapped competency was not added to the course. 502 if (!empty($skipcompetencies[$competencyid])) { 503 continue; 504 } 505 506 $remove = true; 507 $mapto = isset($this->mappings[$competencyid]) ? $this->mappings[$competencyid] : false; 508 509 // We don't have mapping. 510 if ($mapto === false) { 511 if (!$this->removewhenmappingismissing) { 512 $remove = false; 513 } 514 515 } else { 516 // We have a mapping. 517 $transaction = $DB->start_delegated_transaction(); 518 try { 519 // The competency was added successfully. 520 if (api::add_competency_to_course_module($cmid, $mapto)) { 521 522 // Find the added module competency. 523 $mc = course_module_competency::get_record(array('cmid' => $cmid, 'competencyid' => $mapto)); 524 525 // Set the competency rule. 526 api::set_course_module_competency_ruleoutcome($mc, 527 $this->modulecompetenciesoutcomes[$courseid][$cmid][$competencyid]); 528 529 // Adapt the sortorder. 530 api::reorder_course_module_competency($cmid, $mapto, $competencyid); 531 532 $this->modulecompetencymigrations++; 533 534 } else { 535 // The competency was already in the module. 536 if (!$this->removeoriginalwhenalreadypresent) { 537 $remove = false; 538 $competencieswithissues[$competencyid] = true; 539 $this->log_warning($courseid, $competencyid, $cmid, 540 get_string('warningdestinationmodulecompetencyalreadyexists', 'tool_lpmigrate')); 541 } 542 } 543 544 } catch (moodle_exception $e) { 545 // There was a major problem with this competency in this module. 546 $competencieswithissues[$competencyid] = true; 547 $message = get_string('errorwhilemigratingmodulecompetencywithexception', 'tool_lpmigrate', 548 $e->getMessage()); 549 $this->log_error($courseid, $competencyid, $cmid, $message); 550 551 try { 552 $transaction->rollback($e); 553 } catch (moodle_exception $e) { 554 // Catch the re-thrown exception. 555 } 556 557 continue; 558 } 559 $transaction->allow_commit(); 560 } 561 562 try { 563 // Go away competency! 564 if ($remove && api::remove_competency_from_course_module($cmid, $competencyid)) { 565 $this->modulecompetencyremovals++; 566 } 567 } catch (moodle_exception $e) { 568 $competencieswithissues[$competencyid] = true; 569 $this->log_warning($courseid, $competencyid, $cmid, 570 get_string('warningcouldnotremovemodulecompetency', 'tool_lpmigrate')); 571 } 572 } 573 } 574 } 575 576 // Finally, we remove the course competencies, but only for the 100% successful ones. 577 foreach ($competenciestoremovefromcourse as $competencyid => $unused) { 578 579 // Skip competencies with issues. 580 if (isset($competencieswithissues[$competencyid])) { 581 continue; 582 } 583 584 try { 585 // Process the course competency. 586 api::remove_competency_from_course($courseid, $competencyid); 587 $this->coursecompetencyremovals++; 588 } catch (moodle_exception $e) { 589 $this->log_warning($courseid, $competencyid, null, 590 get_string('warningcouldnotremovecoursecompetency', 'tool_lpmigrate')); 591 } 592 } 593 } 594 595 $this->progress->end_progress(); 596 } 597 598 /** 599 * Set the IDs of the courses that are allowed. 600 * @param array $courseids 601 */ 602 public function set_allowedcourses(array $courseids) { 603 $this->allowedcourses = $courseids; 604 } 605 606 /** 607 * Set the minimum start date for courses to be migrated. 608 * @param int $value Timestamp, or 0. 609 */ 610 public function set_course_start_date_from($value) { 611 $this->coursestartdatefrom = intval($value); 612 } 613 614 /** 615 * Set the IDs of the courses that are not allowed. 616 * @param array $courseids 617 */ 618 public function set_disallowedcourses(array $courseids) { 619 $this->disallowedcourses = $courseids; 620 } 621 622 /** 623 * Set whether we should remove original competencies when the destination competency was already there. 624 * @param bool $value 625 */ 626 public function set_remove_original_when_destination_already_present($value) { 627 $this->removeoriginalwhenalreadypresent = $value; 628 } 629 630 /** 631 * Set whether we should remove unmapped competencies. 632 * @param bool $value 633 */ 634 public function set_remove_when_mapping_is_missing($value) { 635 $this->removewhenmappingismissing = $value; 636 } 637 638 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body