PageRenderTime 54ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/admin/tool/uploadcourse/classes/course.php

https://bitbucket.org/synergylearning/campusconnect
PHP | 920 lines | 578 code | 106 blank | 236 comment | 159 complexity | 3dd60ade4b6a393225c4b1704bc422a0 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, LGPL-2.1, Apache-2.0, BSD-3-Clause, AGPL-3.0
  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. * File containing the course class.
  18. *
  19. * @package tool_uploadcourse
  20. * @copyright 2013 Frédéric Massart
  21. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22. */
  23. defined('MOODLE_INTERNAL') || die();
  24. require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
  25. require_once($CFG->dirroot . '/course/lib.php');
  26. /**
  27. * Course class.
  28. *
  29. * @package tool_uploadcourse
  30. * @copyright 2013 Frédéric Massart
  31. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  32. */
  33. class tool_uploadcourse_course {
  34. /** Outcome of the process: creating the course */
  35. const DO_CREATE = 1;
  36. /** Outcome of the process: updating the course */
  37. const DO_UPDATE = 2;
  38. /** Outcome of the process: deleting the course */
  39. const DO_DELETE = 3;
  40. /** @var array final import data. */
  41. protected $data = array();
  42. /** @var array default values. */
  43. protected $defaults = array();
  44. /** @var array enrolment data. */
  45. protected $enrolmentdata;
  46. /** @var array errors. */
  47. protected $errors = array();
  48. /** @var int the ID of the course that had been processed. */
  49. protected $id;
  50. /** @var array containing options passed from the processor. */
  51. protected $importoptions = array();
  52. /** @var int import mode. Matches tool_uploadcourse_processor::MODE_* */
  53. protected $mode;
  54. /** @var array course import options. */
  55. protected $options = array();
  56. /** @var int constant value of self::DO_*, what to do with that course */
  57. protected $do;
  58. /** @var bool set to true once we have prepared the course */
  59. protected $prepared = false;
  60. /** @var bool set to true once we have started the process of the course */
  61. protected $processstarted = false;
  62. /** @var array course import data. */
  63. protected $rawdata = array();
  64. /** @var array restore directory. */
  65. protected $restoredata;
  66. /** @var string course shortname. */
  67. protected $shortname;
  68. /** @var array errors. */
  69. protected $statuses = array();
  70. /** @var int update mode. Matches tool_uploadcourse_processor::UPDATE_* */
  71. protected $updatemode;
  72. /** @var array fields allowed as course data. */
  73. static protected $validfields = array('fullname', 'shortname', 'idnumber', 'category', 'visible', 'startdate',
  74. 'summary', 'format', 'theme', 'lang', 'newsitems', 'showgrades', 'showreports', 'legacyfiles', 'maxbytes',
  75. 'groupmode', 'groupmodeforce', 'groupmodeforce', 'enablecompletion');
  76. /** @var array fields required on course creation. */
  77. static protected $mandatoryfields = array('fullname', 'category');
  78. /** @var array fields which are considered as options. */
  79. static protected $optionfields = array('delete' => false, 'rename' => null, 'backupfile' => null,
  80. 'templatecourse' => null, 'reset' => false);
  81. /** @var array options determining what can or cannot be done at an import level. */
  82. static protected $importoptionsdefaults = array('canrename' => false, 'candelete' => false, 'canreset' => false,
  83. 'reset' => false, 'restoredir' => null, 'shortnametemplate' => null);
  84. /**
  85. * Constructor
  86. *
  87. * @param int $mode import mode, constant matching tool_uploadcourse_processor::MODE_*
  88. * @param int $updatemode update mode, constant matching tool_uploadcourse_processor::UPDATE_*
  89. * @param array $rawdata raw course data.
  90. * @param array $defaults default course data.
  91. * @param array $importoptions import options.
  92. */
  93. public function __construct($mode, $updatemode, $rawdata, $defaults = array(), $importoptions = array()) {
  94. if ($mode !== tool_uploadcourse_processor::MODE_CREATE_NEW &&
  95. $mode !== tool_uploadcourse_processor::MODE_CREATE_ALL &&
  96. $mode !== tool_uploadcourse_processor::MODE_CREATE_OR_UPDATE &&
  97. $mode !== tool_uploadcourse_processor::MODE_UPDATE_ONLY) {
  98. throw new coding_exception('Incorrect mode.');
  99. } else if ($updatemode !== tool_uploadcourse_processor::UPDATE_NOTHING &&
  100. $updatemode !== tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY &&
  101. $updatemode !== tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_OR_DEFAUTLS &&
  102. $updatemode !== tool_uploadcourse_processor::UPDATE_MISSING_WITH_DATA_OR_DEFAUTLS) {
  103. throw new coding_exception('Incorrect update mode.');
  104. }
  105. $this->mode = $mode;
  106. $this->updatemode = $updatemode;
  107. if (isset($rawdata['shortname'])) {
  108. $this->shortname = $rawdata['shortname'];
  109. }
  110. $this->rawdata = $rawdata;
  111. $this->defaults = $defaults;
  112. // Extract course options.
  113. foreach (self::$optionfields as $option => $default) {
  114. $this->options[$option] = isset($rawdata[$option]) ? $rawdata[$option] : $default;
  115. }
  116. // Import options.
  117. foreach (self::$importoptionsdefaults as $option => $default) {
  118. $this->importoptions[$option] = isset($importoptions[$option]) ? $importoptions[$option] : $default;
  119. }
  120. }
  121. /**
  122. * Does the mode allow for course creation?
  123. *
  124. * @return bool
  125. */
  126. public function can_create() {
  127. return in_array($this->mode, array(tool_uploadcourse_processor::MODE_CREATE_ALL,
  128. tool_uploadcourse_processor::MODE_CREATE_NEW,
  129. tool_uploadcourse_processor::MODE_CREATE_OR_UPDATE)
  130. );
  131. }
  132. /**
  133. * Does the mode allow for course deletion?
  134. *
  135. * @return bool
  136. */
  137. public function can_delete() {
  138. return $this->importoptions['candelete'];
  139. }
  140. /**
  141. * Does the mode only allow for course creation?
  142. *
  143. * @return bool
  144. */
  145. public function can_only_create() {
  146. return in_array($this->mode, array(tool_uploadcourse_processor::MODE_CREATE_ALL,
  147. tool_uploadcourse_processor::MODE_CREATE_NEW));
  148. }
  149. /**
  150. * Does the mode allow for course rename?
  151. *
  152. * @return bool
  153. */
  154. public function can_rename() {
  155. return $this->importoptions['canrename'];
  156. }
  157. /**
  158. * Does the mode allow for course reset?
  159. *
  160. * @return bool
  161. */
  162. public function can_reset() {
  163. return $this->importoptions['canreset'];
  164. }
  165. /**
  166. * Does the mode allow for course update?
  167. *
  168. * @return bool
  169. */
  170. public function can_update() {
  171. return in_array($this->mode,
  172. array(
  173. tool_uploadcourse_processor::MODE_UPDATE_ONLY,
  174. tool_uploadcourse_processor::MODE_CREATE_OR_UPDATE)
  175. ) && $this->updatemode != tool_uploadcourse_processor::UPDATE_NOTHING;
  176. }
  177. /**
  178. * Can we use default values?
  179. *
  180. * @return bool
  181. */
  182. public function can_use_defaults() {
  183. return in_array($this->updatemode, array(tool_uploadcourse_processor::UPDATE_MISSING_WITH_DATA_OR_DEFAUTLS,
  184. tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_OR_DEFAUTLS));
  185. }
  186. /**
  187. * Delete the current course.
  188. *
  189. * @return bool
  190. */
  191. protected function delete() {
  192. global $DB;
  193. $this->id = $DB->get_field_select('course', 'id', 'shortname = :shortname',
  194. array('shortname' => $this->shortname), MUST_EXIST);
  195. return delete_course($this->id, false);
  196. }
  197. /**
  198. * Log an error
  199. *
  200. * @param string $code error code.
  201. * @param lang_string $message error message.
  202. * @return void
  203. */
  204. protected function error($code, lang_string $message) {
  205. if (array_key_exists($code, $this->errors)) {
  206. throw new coding_exception('Error code already defined');
  207. }
  208. $this->errors[$code] = $message;
  209. }
  210. /**
  211. * Return whether the course exists or not.
  212. *
  213. * @param string $shortname the shortname to use to check if the course exists. Falls back on $this->shortname if empty.
  214. * @return bool
  215. */
  216. protected function exists($shortname = null) {
  217. global $DB;
  218. if (is_null($shortname)) {
  219. $shortname = $this->shortname;
  220. }
  221. if (!empty($shortname) || is_numeric($shortname)) {
  222. return $DB->record_exists('course', array('shortname' => $shortname));
  223. }
  224. return false;
  225. }
  226. /**
  227. * Return the data that will be used upon saving.
  228. *
  229. * @return null|array
  230. */
  231. public function get_data() {
  232. return $this->data;
  233. }
  234. /**
  235. * Return the errors found during preparation.
  236. *
  237. * @return array
  238. */
  239. public function get_errors() {
  240. return $this->errors;
  241. }
  242. /**
  243. * Assemble the course data based on defaults.
  244. *
  245. * This returns the final data to be passed to create_course().
  246. *
  247. * @param array $data current data.
  248. * @return array
  249. */
  250. protected function get_final_create_data($data) {
  251. foreach (self::$validfields as $field) {
  252. if (!isset($data[$field]) && isset($this->defaults[$field])) {
  253. $data[$field] = $this->defaults[$field];
  254. }
  255. }
  256. $data['shortname'] = $this->shortname;
  257. return $data;
  258. }
  259. /**
  260. * Assemble the course data based on defaults.
  261. *
  262. * This returns the final data to be passed to update_course().
  263. *
  264. * @param array $data current data.
  265. * @param bool $usedefaults are defaults allowed?
  266. * @param bool $missingonly ignore fields which are already set.
  267. * @return array
  268. */
  269. protected function get_final_update_data($data, $usedefaults = false, $missingonly = false) {
  270. global $DB;
  271. $newdata = array();
  272. $existingdata = $DB->get_record('course', array('shortname' => $this->shortname));
  273. foreach (self::$validfields as $field) {
  274. if ($missingonly) {
  275. if (!is_null($existingdata->$field) and $existingdata->$field !== '') {
  276. continue;
  277. }
  278. }
  279. if (isset($data[$field])) {
  280. $newdata[$field] = $data[$field];
  281. } else if ($usedefaults && isset($this->defaults[$field])) {
  282. $newdata[$field] = $this->defaults[$field];
  283. }
  284. }
  285. $newdata['id'] = $existingdata->id;
  286. return $newdata;
  287. }
  288. /**
  289. * Return the ID of the processed course.
  290. *
  291. * @return int|null
  292. */
  293. public function get_id() {
  294. if (!$this->processstarted) {
  295. throw new coding_exception('The course has not been processed yet!');
  296. }
  297. return $this->id;
  298. }
  299. /**
  300. * Get the directory of the object to restore.
  301. *
  302. * @return string|false|null subdirectory in $CFG->tempdir/backup/..., false when an error occured
  303. * and null when there is simply nothing.
  304. */
  305. protected function get_restore_content_dir() {
  306. $backupfile = null;
  307. $shortname = null;
  308. if (!empty($this->options['backupfile'])) {
  309. $backupfile = $this->options['backupfile'];
  310. } else if (!empty($this->options['templatecourse']) || is_numeric($this->options['templatecourse'])) {
  311. $shortname = $this->options['templatecourse'];
  312. }
  313. $errors = array();
  314. $dir = tool_uploadcourse_helper::get_restore_content_dir($backupfile, $shortname, $errors);
  315. if (!empty($errors)) {
  316. foreach ($errors as $key => $message) {
  317. $this->error($key, $message);
  318. }
  319. return false;
  320. } else if ($dir === false) {
  321. // We want to return null when nothing was wrong, but nothing was found.
  322. $dir = null;
  323. }
  324. if (empty($dir) && !empty($this->importoptions['restoredir'])) {
  325. $dir = $this->importoptions['restoredir'];
  326. }
  327. return $dir;
  328. }
  329. /**
  330. * Return the errors found during preparation.
  331. *
  332. * @return array
  333. */
  334. public function get_statuses() {
  335. return $this->statuses;
  336. }
  337. /**
  338. * Return whether there were errors with this course.
  339. *
  340. * @return boolean
  341. */
  342. public function has_errors() {
  343. return !empty($this->errors);
  344. }
  345. /**
  346. * Validates and prepares the data.
  347. *
  348. * @return bool false is any error occured.
  349. */
  350. public function prepare() {
  351. global $DB, $SITE;
  352. $this->prepared = true;
  353. // Validate the shortname.
  354. if (!empty($this->shortname) || is_numeric($this->shortname)) {
  355. if ($this->shortname !== clean_param($this->shortname, PARAM_TEXT)) {
  356. $this->error('invalidshortname', new lang_string('invalidshortname', 'tool_uploadcourse'));
  357. return false;
  358. }
  359. }
  360. $exists = $this->exists();
  361. // Do we want to delete the course?
  362. if ($this->options['delete']) {
  363. if (!$exists) {
  364. $this->error('cannotdeletecoursenotexist', new lang_string('cannotdeletecoursenotexist', 'tool_uploadcourse'));
  365. return false;
  366. } else if (!$this->can_delete()) {
  367. $this->error('coursedeletionnotallowed', new lang_string('coursedeletionnotallowed', 'tool_uploadcourse'));
  368. return false;
  369. }
  370. $this->do = self::DO_DELETE;
  371. return true;
  372. }
  373. // Can we create/update the course under those conditions?
  374. if ($exists) {
  375. if ($this->mode === tool_uploadcourse_processor::MODE_CREATE_NEW) {
  376. $this->error('courseexistsanduploadnotallowed',
  377. new lang_string('courseexistsanduploadnotallowed', 'tool_uploadcourse'));
  378. return false;
  379. } else if ($this->can_update()) {
  380. // We can never allow for any front page changes!
  381. if ($this->shortname == $SITE->shortname) {
  382. $this->error('cannotupdatefrontpage', new lang_string('cannotupdatefrontpage', 'tool_uploadcourse'));
  383. return false;
  384. }
  385. }
  386. } else {
  387. if (!$this->can_create()) {
  388. $this->error('coursedoesnotexistandcreatenotallowed',
  389. new lang_string('coursedoesnotexistandcreatenotallowed', 'tool_uploadcourse'));
  390. return false;
  391. }
  392. }
  393. // Basic data.
  394. $coursedata = array();
  395. foreach ($this->rawdata as $field => $value) {
  396. if (!in_array($field, self::$validfields)) {
  397. continue;
  398. } else if ($field == 'shortname') {
  399. // Let's leave it apart from now, use $this->shortname only.
  400. continue;
  401. }
  402. $coursedata[$field] = $value;
  403. }
  404. $mode = $this->mode;
  405. $updatemode = $this->updatemode;
  406. $usedefaults = $this->can_use_defaults();
  407. // Resolve the category, and fail if not found.
  408. $errors = array();
  409. $catid = tool_uploadcourse_helper::resolve_category($this->rawdata, $errors);
  410. if (empty($errors)) {
  411. $coursedata['category'] = $catid;
  412. } else {
  413. foreach ($errors as $key => $message) {
  414. $this->error($key, $message);
  415. }
  416. return false;
  417. }
  418. // If the course does not exist, or will be forced created.
  419. if (!$exists || $mode === tool_uploadcourse_processor::MODE_CREATE_ALL) {
  420. // Mandatory fields upon creation.
  421. $errors = array();
  422. foreach (self::$mandatoryfields as $field) {
  423. if ((!isset($coursedata[$field]) || $coursedata[$field] === '') &&
  424. (!isset($this->defaults[$field]) || $this->defaults[$field] === '')) {
  425. $errors[] = $field;
  426. }
  427. }
  428. if (!empty($errors)) {
  429. $this->error('missingmandatoryfields', new lang_string('missingmandatoryfields', 'tool_uploadcourse',
  430. implode(', ', $errors)));
  431. return false;
  432. }
  433. }
  434. // Should the course be renamed?
  435. if (!empty($this->options['rename']) || is_numeric($this->options['rename'])) {
  436. if (!$this->can_update()) {
  437. $this->error('canonlyrenameinupdatemode', new lang_string('canonlyrenameinupdatemode', 'tool_uploadcourse'));
  438. return false;
  439. } else if (!$exists) {
  440. $this->error('cannotrenamecoursenotexist', new lang_string('cannotrenamecoursenotexist', 'tool_uploadcourse'));
  441. return false;
  442. } else if (!$this->can_rename()) {
  443. $this->error('courserenamingnotallowed', new lang_string('courserenamingnotallowed', 'tool_uploadcourse'));
  444. return false;
  445. } else if ($this->options['rename'] !== clean_param($this->options['rename'], PARAM_TEXT)) {
  446. $this->error('invalidshortname', new lang_string('invalidshortname', 'tool_uploadcourse'));
  447. return false;
  448. } else if ($this->exists($this->options['rename'])) {
  449. $this->error('cannotrenameshortnamealreadyinuse',
  450. new lang_string('cannotrenameshortnamealreadyinuse', 'tool_uploadcourse'));
  451. return false;
  452. } else if (isset($coursedata['idnumber']) &&
  453. $DB->count_records_select('course', 'idnumber = :idn AND shortname != :sn',
  454. array('idn' => $coursedata['idnumber'], 'sn' => $this->shortname)) > 0) {
  455. $this->error('cannotrenameidnumberconflict', new lang_string('cannotrenameidnumberconflict', 'tool_uploadcourse'));
  456. return false;
  457. }
  458. $coursedata['shortname'] = $this->options['rename'];
  459. $this->status('courserenamed', new lang_string('courserenamed', 'tool_uploadcourse',
  460. array('from' => $this->shortname, 'to' => $coursedata['shortname'])));
  461. }
  462. // Should we generate a shortname?
  463. if (empty($this->shortname) && !is_numeric($this->shortname)) {
  464. if (empty($this->importoptions['shortnametemplate'])) {
  465. $this->error('missingshortnamenotemplate', new lang_string('missingshortnamenotemplate', 'tool_uploadcourse'));
  466. return false;
  467. } else if (!$this->can_only_create()) {
  468. $this->error('cannotgenerateshortnameupdatemode',
  469. new lang_string('cannotgenerateshortnameupdatemode', 'tool_uploadcourse'));
  470. return false;
  471. } else {
  472. $newshortname = tool_uploadcourse_helper::generate_shortname($coursedata,
  473. $this->importoptions['shortnametemplate']);
  474. if (is_null($newshortname)) {
  475. $this->error('generatedshortnameinvalid', new lang_string('generatedshortnameinvalid', 'tool_uploadcourse'));
  476. return false;
  477. } else if ($this->exists($newshortname)) {
  478. if ($mode === tool_uploadcourse_processor::MODE_CREATE_NEW) {
  479. $this->error('generatedshortnamealreadyinuse',
  480. new lang_string('generatedshortnamealreadyinuse', 'tool_uploadcourse'));
  481. return false;
  482. }
  483. $exists = true;
  484. }
  485. $this->status('courseshortnamegenerated', new lang_string('courseshortnamegenerated', 'tool_uploadcourse',
  486. $newshortname));
  487. $this->shortname = $newshortname;
  488. }
  489. }
  490. // If exists, but we only want to create courses, increment the shortname.
  491. if ($exists && $mode === tool_uploadcourse_processor::MODE_CREATE_ALL) {
  492. $original = $this->shortname;
  493. $this->shortname = tool_uploadcourse_helper::increment_shortname($this->shortname);
  494. $exists = false;
  495. if ($this->shortname != $original) {
  496. $this->status('courseshortnameincremented', new lang_string('courseshortnameincremented', 'tool_uploadcourse',
  497. array('from' => $original, 'to' => $this->shortname)));
  498. if (isset($coursedata['idnumber'])) {
  499. $originalidn = $coursedata['idnumber'];
  500. $coursedata['idnumber'] = tool_uploadcourse_helper::increment_idnumber($coursedata['idnumber']);
  501. if ($originalidn != $coursedata['idnumber']) {
  502. $this->status('courseidnumberincremented', new lang_string('courseidnumberincremented', 'tool_uploadcourse',
  503. array('from' => $originalidn, 'to' => $coursedata['idnumber'])));
  504. }
  505. }
  506. }
  507. }
  508. // If the course does not exist, ensure that the ID number is not taken.
  509. if (!$exists && isset($coursedata['idnumber'])) {
  510. if ($DB->count_records_select('course', 'idnumber = :idn', array('idn' => $coursedata['idnumber'])) > 0) {
  511. $this->error('idnumberalreadyinuse', new lang_string('idnumberalreadyinuse', 'tool_uploadcourse'));
  512. return false;
  513. }
  514. }
  515. // Ultimate check mode vs. existence.
  516. switch ($mode) {
  517. case tool_uploadcourse_processor::MODE_CREATE_NEW:
  518. case tool_uploadcourse_processor::MODE_CREATE_ALL:
  519. if ($exists) {
  520. $this->error('courseexistsanduploadnotallowed',
  521. new lang_string('courseexistsanduploadnotallowed', 'tool_uploadcourse'));
  522. return false;
  523. }
  524. break;
  525. case tool_uploadcourse_processor::MODE_UPDATE_ONLY:
  526. if (!$exists) {
  527. $this->error('coursedoesnotexistandcreatenotallowed',
  528. new lang_string('coursedoesnotexistandcreatenotallowed', 'tool_uploadcourse'));
  529. return false;
  530. }
  531. // No break!
  532. case tool_uploadcourse_processor::MODE_CREATE_OR_UPDATE:
  533. if ($exists) {
  534. if ($updatemode === tool_uploadcourse_processor::UPDATE_NOTHING) {
  535. $this->error('updatemodedoessettonothing',
  536. new lang_string('updatemodedoessettonothing', 'tool_uploadcourse'));
  537. return false;
  538. }
  539. }
  540. break;
  541. default:
  542. // O_o Huh?! This should really never happen here!
  543. $this->error('unknownimportmode', new lang_string('unknownimportmode', 'tool_uploadcourse'));
  544. return false;
  545. }
  546. // Get final data.
  547. if ($exists) {
  548. $missingonly = ($updatemode === tool_uploadcourse_processor::UPDATE_MISSING_WITH_DATA_OR_DEFAUTLS);
  549. $coursedata = $this->get_final_update_data($coursedata, $usedefaults, $missingonly);
  550. // Make sure we are not trying to mess with the front page, though we should never get here!
  551. if ($coursedata['id'] == $SITE->id) {
  552. $this->error('cannotupdatefrontpage', new lang_string('cannotupdatefrontpage', 'tool_uploadcourse'));
  553. return false;
  554. }
  555. $this->do = self::DO_UPDATE;
  556. } else {
  557. $coursedata = $this->get_final_create_data($coursedata);
  558. $this->do = self::DO_CREATE;
  559. }
  560. // Course start date.
  561. if (!empty($coursedata['startdate'])) {
  562. $coursedata['startdate'] = strtotime($coursedata['startdate']);
  563. }
  564. // Add role renaming.
  565. $errors = array();
  566. $rolenames = tool_uploadcourse_helper::get_role_names($this->rawdata, $errors);
  567. if (!empty($errors)) {
  568. foreach ($errors as $key => $message) {
  569. $this->error($key, $message);
  570. }
  571. return false;
  572. }
  573. foreach ($rolenames as $rolekey => $rolename) {
  574. $coursedata[$rolekey] = $rolename;
  575. }
  576. // Some validation.
  577. if (!empty($coursedata['format']) && !in_array($coursedata['format'], tool_uploadcourse_helper::get_course_formats())) {
  578. $this->error('invalidcourseformat', new lang_string('invalidcourseformat', 'tool_uploadcourse'));
  579. return false;
  580. }
  581. // Saving data.
  582. $this->data = $coursedata;
  583. $this->enrolmentdata = tool_uploadcourse_helper::get_enrolment_data($this->rawdata);
  584. // Restore data.
  585. // TODO Speed up things by not really extracting the backup just yet, but checking that
  586. // the backup file or shortname passed are valid. Extraction should happen in proceed().
  587. $this->restoredata = $this->get_restore_content_dir();
  588. if ($this->restoredata === false) {
  589. return false;
  590. }
  591. // We can only reset courses when allowed and we are updating the course.
  592. if ($this->importoptions['reset'] || $this->options['reset']) {
  593. if ($this->do !== self::DO_UPDATE) {
  594. $this->error('canonlyresetcourseinupdatemode',
  595. new lang_string('canonlyresetcourseinupdatemode', 'tool_uploadcourse'));
  596. return false;
  597. } else if (!$this->can_reset()) {
  598. $this->error('courseresetnotallowed', new lang_string('courseresetnotallowed', 'tool_uploadcourse'));
  599. return false;
  600. }
  601. }
  602. return true;
  603. }
  604. /**
  605. * Proceed with the import of the course.
  606. *
  607. * @return void
  608. */
  609. public function proceed() {
  610. global $CFG, $USER;
  611. if (!$this->prepared) {
  612. throw new coding_exception('The course has not been prepared.');
  613. } else if ($this->has_errors()) {
  614. throw new moodle_exception('Cannot proceed, errors were detected.');
  615. } else if ($this->processstarted) {
  616. throw new coding_exception('The process has already been started.');
  617. }
  618. $this->processstarted = true;
  619. if ($this->do === self::DO_DELETE) {
  620. if ($this->delete()) {
  621. $this->status('coursedeleted', new lang_string('coursedeleted', 'tool_uploadcourse'));
  622. } else {
  623. $this->error('errorwhiledeletingcourse', new lang_string('errorwhiledeletingcourse', 'tool_uploadcourse'));
  624. }
  625. return true;
  626. } else if ($this->do === self::DO_CREATE) {
  627. $course = create_course((object) $this->data);
  628. $this->id = $course->id;
  629. $this->status('coursecreated', new lang_string('coursecreated', 'tool_uploadcourse'));
  630. } else if ($this->do === self::DO_UPDATE) {
  631. $course = (object) $this->data;
  632. update_course($course);
  633. $this->id = $course->id;
  634. $this->status('courseupdated', new lang_string('courseupdated', 'tool_uploadcourse'));
  635. } else {
  636. // Strangely the outcome has not been defined, or is unknown!
  637. throw new coding_exception('Unknown outcome!');
  638. }
  639. // Restore a course.
  640. if (!empty($this->restoredata)) {
  641. $rc = new restore_controller($this->restoredata, $course->id, backup::INTERACTIVE_NO,
  642. backup::MODE_IMPORT, $USER->id, backup::TARGET_CURRENT_ADDING);
  643. // Check if the format conversion must happen first.
  644. if ($rc->get_status() == backup::STATUS_REQUIRE_CONV) {
  645. $rc->convert();
  646. }
  647. if ($rc->execute_precheck()) {
  648. $rc->execute_plan();
  649. $this->status('courserestored', new lang_string('courserestored', 'tool_uploadcourse'));
  650. } else {
  651. $this->error('errorwhilerestoringcourse', new lang_string('errorwhilerestoringthecourse', 'tool_uploadcourse'));
  652. }
  653. $rc->destroy();
  654. unset($rc); // File logging is a mess, we can only try to rely on gc to close handles.
  655. }
  656. // Proceed with enrolment data.
  657. $this->process_enrolment_data($course);
  658. // Reset the course.
  659. if ($this->importoptions['reset'] || $this->options['reset']) {
  660. if ($this->do === self::DO_UPDATE && $this->can_reset()) {
  661. $this->reset($course);
  662. $this->status('coursereset', new lang_string('coursereset', 'tool_uploadcourse'));
  663. }
  664. }
  665. // Mark context as dirty.
  666. $context = context_course::instance($course->id);
  667. $context->mark_dirty();
  668. }
  669. /**
  670. * Add the enrolment data for the course.
  671. *
  672. * @param object $course course record.
  673. * @return void
  674. */
  675. protected function process_enrolment_data($course) {
  676. global $DB;
  677. $enrolmentdata = $this->enrolmentdata;
  678. if (empty($enrolmentdata)) {
  679. return;
  680. }
  681. $enrolmentplugins = tool_uploadcourse_helper::get_enrolment_plugins();
  682. $instances = enrol_get_instances($course->id, false);
  683. foreach ($enrolmentdata as $enrolmethod => $method) {
  684. $instance = null;
  685. foreach ($instances as $i) {
  686. if ($i->enrol == $enrolmethod) {
  687. $instance = $i;
  688. break;
  689. }
  690. }
  691. $todelete = isset($method['delete']) && $method['delete'];
  692. $todisable = isset($method['disable']) && $method['disable'];
  693. unset($method['delete']);
  694. unset($method['disable']);
  695. if (!empty($instance) && $todelete) {
  696. // Remove the enrolment method.
  697. foreach ($instances as $instance) {
  698. if ($instance->enrol == $enrolmethod) {
  699. $plugin = $enrolmentplugins[$instance->enrol];
  700. $plugin->delete_instance($instance);
  701. break;
  702. }
  703. }
  704. } else if (!empty($instance) && $todisable) {
  705. // Disable the enrolment.
  706. foreach ($instances as $instance) {
  707. if ($instance->enrol == $enrolmethod) {
  708. $plugin = $enrolmentplugins[$instance->enrol];
  709. $plugin->update_status($instance, ENROL_INSTANCE_DISABLED);
  710. $enrol_updated = true;
  711. break;
  712. }
  713. }
  714. } else {
  715. $plugin = null;
  716. if (empty($instance)) {
  717. $plugin = $enrolmentplugins[$enrolmethod];
  718. $instance = new stdClass();
  719. $instance->id = $plugin->add_default_instance($course);
  720. $instance->roleid = $plugin->get_config('roleid');
  721. $instance->status = ENROL_INSTANCE_ENABLED;
  722. } else {
  723. $plugin = $enrolmentplugins[$instance->enrol];
  724. $plugin->update_status($instance, ENROL_INSTANCE_ENABLED);
  725. }
  726. // Now update values.
  727. foreach ($method as $k => $v) {
  728. $instance->{$k} = $v;
  729. }
  730. // Sort out the start, end and date.
  731. $instance->enrolstartdate = (isset($method['startdate']) ? strtotime($method['startdate']) : 0);
  732. $instance->enrolenddate = (isset($method['enddate']) ? strtotime($method['enddate']) : 0);
  733. // Is the enrolment period set?
  734. if (isset($method['enrolperiod']) && ! empty($method['enrolperiod'])) {
  735. if (preg_match('/^\d+$/', $method['enrolperiod'])) {
  736. $method['enrolperiod'] = (int) $method['enrolperiod'];
  737. } else {
  738. // Try and convert period to seconds.
  739. $method['enrolperiod'] = strtotime('1970-01-01 GMT + ' . $method['enrolperiod']);
  740. }
  741. $instance->enrolperiod = $method['enrolperiod'];
  742. }
  743. if ($instance->enrolstartdate > 0 && isset($method['enrolperiod'])) {
  744. $instance->enrolenddate = $instance->enrolstartdate + $method['enrolperiod'];
  745. }
  746. if ($instance->enrolenddate > 0) {
  747. $instance->enrolperiod = $instance->enrolenddate - $instance->enrolstartdate;
  748. }
  749. if ($instance->enrolenddate < $instance->enrolstartdate) {
  750. $instance->enrolenddate = $instance->enrolstartdate;
  751. }
  752. // Sort out the given role. This does not filter the roles allowed in the course.
  753. if (isset($method['role'])) {
  754. $roleids = tool_uploadcourse_helper::get_role_ids();
  755. if (isset($roleids[$method['role']])) {
  756. $instance->roleid = $roleids[$method['role']];
  757. }
  758. }
  759. $instance->timemodified = time();
  760. $DB->update_record('enrol', $instance);
  761. }
  762. }
  763. }
  764. /**
  765. * Reset the current course.
  766. *
  767. * This does not reset any of the content of the activities.
  768. *
  769. * @param stdClass $course the course object of the course to reset.
  770. * @return array status array of array component, item, error.
  771. */
  772. protected function reset($course) {
  773. global $DB;
  774. $resetdata = new stdClass();
  775. $resetdata->id = $course->id;
  776. $resetdata->reset_start_date = time();
  777. $resetdata->reset_logs = true;
  778. $resetdata->reset_events = true;
  779. $resetdata->reset_notes = true;
  780. $resetdata->delete_blog_associations = true;
  781. $resetdata->reset_completion = true;
  782. $resetdata->reset_roles_overrides = true;
  783. $resetdata->reset_roles_local = true;
  784. $resetdata->reset_groups_members = true;
  785. $resetdata->reset_groups_remove = true;
  786. $resetdata->reset_groupings_members = true;
  787. $resetdata->reset_groupings_remove = true;
  788. $resetdata->reset_gradebook_items = true;
  789. $resetdata->reset_gradebook_grades = true;
  790. $resetdata->reset_comments = true;
  791. if (empty($course->startdate)) {
  792. $course->startdate = $DB->get_field_select('course', 'startdate', 'id = :id', array('id' => $course->id));
  793. }
  794. $resetdata->reset_start_date_old = $course->startdate;
  795. // Add roles.
  796. $roles = tool_uploadcourse_helper::get_role_ids();
  797. $resetdata->unenrol_users = array_values($roles);
  798. $resetdata->unenrol_users[] = 0; // Enrolled without role.
  799. return reset_course_userdata($resetdata);
  800. }
  801. /**
  802. * Log a status
  803. *
  804. * @param string $code status code.
  805. * @param lang_string $message status message.
  806. * @return void
  807. */
  808. protected function status($code, lang_string $message) {
  809. if (array_key_exists($code, $this->statuses)) {
  810. throw new coding_exception('Status code already defined');
  811. }
  812. $this->statuses[$code] = $message;
  813. }
  814. }