PageRenderTime 51ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/local/campusconnect/metadata.php

https://bitbucket.org/synergylearning/campusconnect
PHP | 846 lines | 635 code | 60 blank | 151 comment | 117 complexity | 62df64ad9e944530b0442dd21fd5e835 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 the CampusConnect plugin for 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. * Class to support the mapping of course meta data
  18. *
  19. * @package local_campusconnect
  20. * @copyright 2012 Synergy Learning
  21. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22. */
  23. defined('MOODLE_INTERNAL') || die();
  24. class campusconnect_metadata {
  25. const TYPE_IMPORT_COURSE = 1;
  26. const TYPE_IMPORT_EXTERNAL_COURSE = 2;
  27. const TYPE_EXPORT_COURSE = 3;
  28. const TYPE_EXPORT_EXTERNAL_COURSE = 4;
  29. protected static $coursefields = array(
  30. 'fullname' => 'string', 'shortname' => 'string',
  31. 'idnumber' => 'string', 'summary' => 'string',
  32. 'startdate' => 'date', 'lang' => 'lang',
  33. 'timecreated' => 'date', 'timemodified' => 'date');
  34. protected static $remotefieldcourselink = array(
  35. 'destinationForDisplay' => 'string',
  36. 'lang' => 'lang',
  37. 'hoursPerWeek' => 'string',
  38. 'id' => 'string',
  39. 'number' => 'string',
  40. 'term' => 'string',
  41. 'credits' => 'string',
  42. 'status' => 'string',
  43. 'courseType' => 'string',
  44. 'title' => 'string',
  45. 'firstDate' => 'date',
  46. 'datesAndVenues.day' => 'string',
  47. 'datesAndVenues.start' => 'date',
  48. 'datesAndVenues.end' => 'date',
  49. 'datesAndVenues.cycle' => 'string',
  50. 'datesAndVenues.venue' => 'string',
  51. 'datesAndVenues.firstDate.startDatetime' => 'date',
  52. 'datesAndVenues.firstDate.endDatetime' => 'date',
  53. 'datesAndVenues.lastDate.startDatetime' => 'date',
  54. 'datesAndVenues.lastDate.endDatetime' => 'date',
  55. 'degreeProgrammes_code' => 'degreelist',
  56. 'degreeProgrammes_title' => 'degreelist',
  57. 'degreeProgrammes' => 'degreelist',
  58. 'lecturers_lastName' => 'personlist',
  59. 'lecturers_firstName' => 'personlist',
  60. 'lecturers' => 'personlist'
  61. );
  62. protected static $remotefields = array(
  63. 'lectureID' => 'string',
  64. 'title' => 'string',
  65. 'organisation' => 'string',
  66. 'number' => 'string',
  67. 'term' => 'string',
  68. 'termid' => 'string',
  69. 'lectureType' => 'string',
  70. 'hoursPerWeek' => 'integer',
  71. 'groupScenario' => 'integer',
  72. 'degreeProgrammes_code' => 'degreelist',
  73. 'degreeProgrammes_title' => 'degreelist',
  74. 'degreeProgrammes' => 'degreelist',
  75. 'comment1' => 'string',
  76. 'comment2' => 'string',
  77. 'comment3' => 'string',
  78. 'recommendedReading' => 'string',
  79. 'organisationalUnits' => 'orglist',
  80. 'prerequisites' => 'string',
  81. 'lectureAssessmentType' => 'string',
  82. 'lectureTopics' => 'string',
  83. 'linkToCurriculum' => 'string',
  84. 'targetAudience' => 'string',
  85. 'links' => 'linklist',
  86. 'linkToLecture' => 'link',
  87. 'groups_lecturers' => 'grouplist',
  88. 'groups' => 'grouplist',
  89. 'modules' => 'moduleslist'
  90. );
  91. // Note - leaving out fields 'allocations', 'organisationalUnit', 'groups', as there is no obvious place to map these to in Moodle.
  92. // Default import mappings
  93. protected $importmappings = array(
  94. 'fullname' => '{title}',
  95. 'shortname' => '{lectureID}',
  96. 'idnumber' => '',
  97. 'summary' => null, // This is built on first load using get_string
  98. 'timecreated' => '',
  99. 'timemodified' => ''
  100. );
  101. // Default import mappings
  102. protected $importmappingscourselink = array(
  103. 'fullname' => '{title}',
  104. 'shortname' => '{id}',
  105. 'idnumber' => '',
  106. 'summary' => null, // This is built on first load using get_string
  107. 'startdate' => 'firstDate',
  108. 'lang' => 'lang',
  109. 'timecreated' => '',
  110. 'timemodified' => ''
  111. );
  112. // Default export mappings
  113. protected $exportmappings = array(
  114. 'organisation' => '',
  115. 'id' => '{shortname}',
  116. 'term' => '',
  117. 'number' => '',
  118. 'title' => '{fullname}',
  119. 'courseType' => '',
  120. 'hoursPerWeek' => '',
  121. 'maxParticipants' => '',
  122. 'parallelGroupScenario' => '',
  123. 'lecturers' => '',
  124. 'degreeProgrammes' => '',
  125. 'comment1' => '',
  126. 'comment2' => '',
  127. 'comment3' => '',
  128. 'recommendedReading' => '',
  129. 'prerequisites' => '',
  130. 'courseAssessmentMethod' => '',
  131. 'courseTopics' => '',
  132. 'linkToCurriculum' => '',
  133. 'targetAudience' => '',
  134. 'links' => '',
  135. 'linkToCourse' => '',
  136. 'modules' => ''
  137. );
  138. // Default external export mappings
  139. protected $exportmappingscourselink = array(
  140. 'destinationForDisplay' => '',
  141. 'lang' => 'lang',
  142. 'hoursPerWeek' => '',
  143. 'id' => '{shortname}',
  144. 'number' => '',
  145. 'term' => '',
  146. 'credits' => '',
  147. 'status' => '',
  148. 'courseType' => '',
  149. 'title' => '{fullname}',
  150. 'firstDate' => 'startdate',
  151. 'datesAndVenues.day' => '',
  152. 'datesAndVenues.start' => '',
  153. 'datesAndVenues.end' => '',
  154. 'datesAndVenues.cycle' => '',
  155. 'datesAndVenues.venue' => '',
  156. 'datesAndVenues.firstDate.startDatetime' => '',
  157. 'datesAndVenues.firstDate.endDatetime' => '',
  158. 'datesAndVenues.lastDate.startDatetime' => '',
  159. 'datesAndVenues.lastDate.endDatetime' => '',
  160. 'degreeProgrammes' => '',
  161. 'lecturers' => ''
  162. );
  163. protected $lasterrormsg = null;
  164. protected $lasterrorfield = null;
  165. protected $courselink = true;
  166. protected $ecsid = null;
  167. /**
  168. * Returns a list of all the remote fields (any of which can be
  169. * inserted into text fields as '{fieldname}')
  170. * @param bool $courselink - true for external course mappings, false for internal
  171. * @return array of string - the available fields
  172. */
  173. public static function list_remote_fields($courselink = true) {
  174. if ($courselink) {
  175. return array_keys(self::$remotefieldcourselink);
  176. } else {
  177. return array_keys(self::$remotefields);
  178. }
  179. }
  180. /**
  181. * Returns a list of all local (course) fields
  182. * @return array of string - the available fields
  183. */
  184. public static function list_local_fields() {
  185. return array_keys(self::$coursefields);
  186. }
  187. /**
  188. * Text fields should allow the user to construct the value via a combination
  189. * of free text and remote fields (surrounded by '{' and '}' characters)
  190. * @param string $fieldname - the course field to check
  191. * @return bool true if it is a text field
  192. */
  193. public static function is_text_field($fieldname) {
  194. if (!array_key_exists($fieldname, self::$coursefields)) {
  195. throw new coding_exception("$fieldname is not an available Moodle course field");
  196. }
  197. return (self::$coursefields[$fieldname] == 'string');
  198. }
  199. /**
  200. * Text fields should allow the user to construct the value via a combination
  201. * of free text and course fields (surrounded by '{' and '}' characters)
  202. * @param string $fieldname - the course field to check
  203. * @param bool $courselink - true for external course mappings, false for internal
  204. * @return bool true if it is a text field
  205. */
  206. public static function is_remote_text_field($fieldname, $courselink = true) {
  207. $remotefields = $courselink ? self::$remotefieldcourselink : self::$remotefields;
  208. if (!array_key_exists($fieldname, $remotefields)) {
  209. throw new coding_exception("$fieldname is not an available remote field");
  210. }
  211. return ($remotefields[$fieldname] == 'string');
  212. }
  213. /**
  214. * List suitable remote fields for mapping onto the given course field
  215. * @param string $localfieldname the local field to look for mappings for
  216. * @param bool $courselink - true for external course mappings, false for internal
  217. * @return array of fields that could match this
  218. */
  219. public static function list_remote_to_local_fields($localfieldname, $courselink = true) {
  220. if (!array_key_exists($localfieldname, self::$coursefields)) {
  221. throw new coding_exception("$localfieldname is not an available Moodle course field");
  222. }
  223. $type = self::$coursefields[$localfieldname];
  224. $remotefields = $courselink ? self::$remotefieldcourselink : self::$remotefields;
  225. $ret = array();
  226. foreach ($remotefields as $rname => $rtype) {
  227. if ($rtype == $type) {
  228. $ret[] = $rname;
  229. }
  230. }
  231. return $ret;
  232. }
  233. /**
  234. * List suitable course fields for mapping onto the given remote field
  235. * @param string $remotefieldname the remote field to look for mappings for
  236. * @param bool $courselink - true for external course mappings, false for internal
  237. * @return array of fields that could match this
  238. */
  239. public static function list_local_to_remote_fields($remotefieldname, $courselink = true) {
  240. $remotefields = $courselink ? self::$remotefieldcourselink : self::$remotefields;
  241. if (!array_key_exists($remotefieldname, $remotefields)) {
  242. if ($courselink) {
  243. throw new coding_exception("$remotefieldname is not an available remote external course field");
  244. } else {
  245. throw new coding_exception("$remotefieldname is not an available remote course field");
  246. }
  247. }
  248. $type = $remotefields[$remotefieldname];
  249. $ret = array();
  250. foreach (self::$coursefields as $cname => $ctype) {
  251. if ($ctype == $type) {
  252. $ret[] = $cname;
  253. }
  254. }
  255. return $ret;
  256. }
  257. /**
  258. * Generate a default summary layout (could be used to reset back to the default)
  259. * @param bool $courselink optional (default true)
  260. * @return string the default summary
  261. */
  262. public static function generate_default_summary($courselink = true) {
  263. if ($courselink) {
  264. $mapping = array('destinationForDisplay' => get_string('field_organisation', 'local_campusconnect'),
  265. 'lang' => get_string('field_language', 'local_campusconnect'),
  266. 'term' => get_string('field_term', 'local_campusconnect'),
  267. 'credits' => get_string('field_credits', 'local_campusconnect'),
  268. 'status' => get_string('field_status', 'local_campusconnect'),
  269. 'courseType' => get_string('field_coursetype', 'local_campusconnect'));
  270. } else {
  271. $mapping = array('organisation' => get_string('field_organisation', 'local_campusconnect'),
  272. 'term' => get_string('field_term', 'local_campusconnect'),
  273. 'courseType' => get_string('field_coursetype', 'local_campusconnect'));
  274. }
  275. $summary = '';
  276. foreach ($mapping as $field => $text) {
  277. $summary .= '<b>'.$text.':</b> {'.$field.'}<br/>';
  278. }
  279. return $summary;
  280. }
  281. /**
  282. * Delete all metadata mappings associated with the given ECS
  283. * @param int $ecsid the ECS to clear
  284. */
  285. public static function delete_ecs_metadata_mappings($ecsid) {
  286. global $DB;
  287. $DB->delete_records('local_campusconnect_mappings', array('ecsid' => $ecsid));
  288. }
  289. /**
  290. * @param campusconnect_ecssettings $ecssettings the ECS this is the mapping for
  291. * @param bool $courselink - true if this is the mappings for 'external courses'
  292. */
  293. public function __construct(campusconnect_ecssettings $ecssettings, $courselink = true) {
  294. global $DB;
  295. $this->courselink = $courselink;
  296. $this->ecsid = $ecssettings->get_id();
  297. if ($this->courselink) {
  298. $this->exportmappings = $this->exportmappingscourselink;
  299. $this->importmappings = $this->importmappingscourselink;
  300. }
  301. $remotefields = $this->courselink ? self::$remotefieldcourselink : self::$remotefields;
  302. $mappings = $DB->get_records('local_campusconnect_mappings', array('ecsid' => $this->ecsid));
  303. foreach ($mappings as $mapping) {
  304. if ($courselink) {
  305. if ($mapping->type == self::TYPE_IMPORT_COURSE ||
  306. $mapping->type == self::TYPE_EXPORT_COURSE) {
  307. continue;
  308. }
  309. } else {
  310. if ($mapping->type == self::TYPE_IMPORT_EXTERNAL_COURSE ||
  311. $mapping->type == self::TYPE_EXPORT_EXTERNAL_COURSE) {
  312. continue;
  313. }
  314. }
  315. switch ($mapping->type) {
  316. case self::TYPE_IMPORT_COURSE:
  317. case self::TYPE_IMPORT_EXTERNAL_COURSE:
  318. if (array_key_exists($mapping->field, self::$coursefields)) {
  319. $this->importmappings[$mapping->field] = $mapping->setto;
  320. }
  321. break;
  322. case self::TYPE_EXPORT_COURSE:
  323. case self::TYPE_EXPORT_EXTERNAL_COURSE:
  324. if (array_key_exists($mapping->field, $remotefields)) {
  325. $this->exportmappings[$mapping->field] = $mapping->setto;
  326. }
  327. break;
  328. }
  329. }
  330. if (is_null($this->importmappings['summary'])) {
  331. $this->importmappings['summary'] = self::generate_default_summary($courselink);
  332. }
  333. }
  334. /**
  335. * Is this mapping for external courses?
  336. * @return bool true if the mapping is for external courses
  337. */
  338. public function is_external() {
  339. return $this->courselink;
  340. }
  341. /**
  342. * Get the list of mappings used on import
  343. * @return array localfield => remotefield
  344. */
  345. public function get_import_mappings() {
  346. return $this->importmappings;
  347. }
  348. /**
  349. * Get the list of mappings used on export
  350. * @return array remotefield => localfield
  351. */
  352. public function get_export_mappings() {
  353. return $this->exportmappings;
  354. }
  355. /**
  356. * Get the last error caused during set_import/export_mapping(s)
  357. * @return array (error message, error field name)
  358. */
  359. public function get_last_error() {
  360. return array($this->lasterrormsg, $this->lasterrorfield);
  361. }
  362. /**
  363. * Set a single import mapping
  364. * @param string $localfield - the field that will receive the incoming value
  365. * @param string $remotefield - the name of the field (for non-text fields) or the
  366. * string to set (for text fields) e.g. 'Course name: {title}'
  367. * @return bool false if an error occurred (see get_last_error for details)
  368. */
  369. public function set_import_mapping($localfield, $remotefield) {
  370. $remotefields = $this->courselink ? self::$remotefieldcourselink : self::$remotefields;
  371. if (self::is_text_field($localfield)) {
  372. if (preg_match_all('/\{([^}]+)\}/', $remotefield, $includedfields)) {
  373. foreach ($includedfields[1] as $field) {
  374. if (!array_key_exists($field, $remotefields)) {
  375. $this->lasterrorfield = $localfield;
  376. $this->lasterrormsg = get_string('remotefieldnotfound', 'local_campusconnect', $field);
  377. return false;
  378. }
  379. }
  380. }
  381. } else {
  382. if (!empty($remotefield) && !in_array($remotefield, self::list_remote_to_local_fields($localfield, $this->courselink))) {
  383. throw new coding_exception("$remotefield is not a suitable field to map onto $localfield");
  384. }
  385. }
  386. $required = array('fullname', 'shortname');
  387. if (in_array($localfield, $required) && empty($remotefield)) {
  388. $this->lasterrorfield = $localfield;
  389. $this->lasterrormsg = get_string('cannotbeempty', 'local_campusconnect', $remotefield);
  390. return false;
  391. }
  392. if ($this->courselink) {
  393. $type = self::TYPE_IMPORT_EXTERNAL_COURSE;
  394. } else {
  395. $type = self::TYPE_IMPORT_COURSE;
  396. }
  397. $this->importmappings[$localfield] = $remotefield;
  398. $this->save_mapping($localfield, $remotefield, $type);
  399. return true;
  400. }
  401. /**
  402. * Set a single export mapping
  403. * @param string $remotefield - the field that will receive the exported value
  404. * @param string $localfield - the name of the field to export
  405. * @return bool false if an error occurred (see get_last_error for details)
  406. */
  407. public function set_export_mapping($remotefield, $localfield) {
  408. if (self::is_remote_text_field($remotefield, $this->courselink)) {
  409. if (preg_match_all('/\{([^}]+)\}/', $localfield, $includedfields)) {
  410. foreach ($includedfields[1] as $field) {
  411. if (!array_key_exists($field, self::$coursefields)) {
  412. $this->lasterrorfield = $remotefield;
  413. $this->lasterrormsg = get_string('localfieldnotfound', 'local_campusconnect', $field);
  414. return false;
  415. }
  416. }
  417. }
  418. } else {
  419. if (!empty($localfield) && !in_array($localfield, self::list_local_to_remote_fields($remotefield, $this->courselink))) {
  420. throw new coding_exception("$localfield is not a suitable field to map onto $remotefield");
  421. }
  422. }
  423. $required = array('id', 'title');
  424. if (in_array($remotefield, $required) && empty($localfield)) {
  425. $this->lasterrorfield = $remotefield;
  426. $this->lasterrormsg = get_string('cannotbeempty', 'local_campusconnect', $localfield);
  427. return false;
  428. }
  429. if ($this->courselink) {
  430. $type = self::TYPE_EXPORT_EXTERNAL_COURSE;
  431. } else {
  432. $type = self::TYPE_EXPORT_COURSE;
  433. }
  434. $this->exportmappings[$remotefield] = $localfield;
  435. $this->save_mapping($remotefield, $localfield, $type);
  436. return true;
  437. }
  438. /**
  439. * Set all import mappings - does not delete missing mappings, set to '' to clear
  440. * @param array $mappings - localfield => remotefield/text (see set_import_mapping for details)
  441. * @return bool false if an error occurred (see get_last_error for details)
  442. */
  443. public function set_import_mappings($mappings) {
  444. foreach ($mappings as $local => $remote) {
  445. if (!$this->set_import_mapping($local, $remote)) {
  446. return false;
  447. }
  448. }
  449. return true;
  450. }
  451. /**
  452. * Set all export mappings - does not delete missing mappings, set to '' to clear
  453. * @param array $mappings - remotefield => localfield
  454. * @return bool false if an error occurred (see get_last_error for details)
  455. */
  456. public function set_export_mappings($mappings) {
  457. foreach ($mappings as $remote => $local) {
  458. if (!$this->set_export_mapping($remote, $local)) {
  459. return false;
  460. }
  461. }
  462. return true;
  463. }
  464. /**
  465. * Save the setting in the database
  466. * @param string $field - the name of the field the mapping is for
  467. * @param string $setto - what this field should map to
  468. * @param int $type - the type of the mapping
  469. */
  470. protected function save_mapping($field, $setto, $type) {
  471. global $DB;
  472. $existing = $DB->get_record('local_campusconnect_mappings', array('ecsid' => $this->ecsid,
  473. 'field' => $field,
  474. 'type' => $type));
  475. if ($existing) {
  476. $upd = new stdClass();
  477. $upd->id = $existing->id;
  478. $upd->setto = $setto;
  479. $DB->update_record('local_campusconnect_mappings', $upd);
  480. } else {
  481. $ins = new stdClass();
  482. $ins->field = $field;
  483. $ins->setto = $setto;
  484. $ins->ecsid = $this->ecsid;
  485. $ins->type = $type;
  486. $DB->insert_record('local_campusconnect_mappings', $ins);
  487. }
  488. }
  489. /**
  490. * Convert the structured details into a flat array for easier processing
  491. * @param stdClass $remotedetails
  492. * @param bool $flattenarrays true if any array-based fields should be imploded
  493. * @return array
  494. */
  495. public function flatten_remote_data($remotedetails, $flattenarrays = false) {
  496. $details = array();
  497. foreach ($remotedetails as $name => $value) {
  498. if ($name == 'datesAndVenues') {
  499. if (!empty($value)) {
  500. foreach ($value[0] as $fieldname => $fieldvalue) {
  501. if ($fieldname == 'firstDate') {
  502. $details['datesAndVenues.firstDate.startDatetime'] = $fieldvalue->startDatetime;
  503. $details['datesAndVenues.firstDate.endDatetime'] = $fieldvalue->endDatetime;
  504. } elseif ($fieldname == 'lastDate') {
  505. $details['datesAndVenues.lastDate.startDatetime'] = $fieldvalue->startDatetime;
  506. $details['datesAndVenues.lastDate.endDatetime'] = $fieldvalue->endDatetime;
  507. } else {
  508. $details['datesAndVenues.'.$fieldname] = $fieldvalue;
  509. }
  510. }
  511. }
  512. continue;
  513. }
  514. $details[$name] = $value;
  515. }
  516. // Convert dates, lists, etc. into suitable format for Moodle.
  517. $remotefields = $this->courselink ? self::$remotefieldcourselink : self::$remotefields;
  518. foreach ($remotefields as $fieldname => $fieldtype) {
  519. $fieldnameparts = explode('_', $fieldname);
  520. $basename = $fieldnameparts[0];
  521. $subname = false;
  522. if (isset($fieldnameparts[1])) {
  523. $subname = $fieldnameparts[1];
  524. }
  525. if (isset($details[$basename])) {
  526. switch ($fieldtype) {
  527. case 'date':
  528. $details[$fieldname] = strtotime($details[$basename]);
  529. break;
  530. case 'personlist':
  531. foreach ($details[$basename] as $key => $person) {
  532. if ($subname) {
  533. $details[$fieldname][$key] = $person->{$subname};
  534. } else {
  535. $fakeuser = new stdClass();
  536. $fakeuser->firstname = $person->firstName;
  537. $fakeuser->lastname = $person->lastName;
  538. $details[$fieldname][$key] = self::fullname($fakeuser);
  539. }
  540. }
  541. if ($flattenarrays) {
  542. $details[$fieldname] = implode(', ', $details[$fieldname]);
  543. }
  544. break;
  545. case 'orglist':
  546. foreach ($details[$fieldname] as $key => $organisation) {
  547. $details[$fieldname][$key] = $organisation->title;
  548. }
  549. if ($flattenarrays) {
  550. $details[$fieldname] = implode(', ', $details[$fieldname]);
  551. }
  552. break;
  553. case 'grouplist':
  554. if ($subname == 'lecturers') {
  555. $lecturers = array();
  556. foreach ($details[$basename] as $group) {
  557. if (isset($group->lecturers)) {
  558. foreach ($group->lecturers as $lecturer) {
  559. $fakeuser = (object)array(
  560. 'firstname' => $lecturer->firstName,
  561. 'lastname' => $lecturer->lastName,
  562. );
  563. $lecturers[] = self::fullname($fakeuser);
  564. }
  565. }
  566. }
  567. $details[$fieldname] = array_unique($lecturers);
  568. } else {
  569. foreach ($details[$basename] as $key => $group) {
  570. $title = '';
  571. if (isset($group->title)) {
  572. $title = $group->title;
  573. }
  574. $details[$fieldname][$key] = $title;
  575. }
  576. }
  577. if ($flattenarrays) {
  578. $details[$fieldname] = implode(', ', $details[$fieldname]);
  579. }
  580. break;
  581. case 'degreelist':
  582. foreach ($details[$basename] as $key => $degree) {
  583. if ($subname) {
  584. $details[$fieldname][$key] = $degree->{$subname};
  585. } else {
  586. $details[$fieldname][$key] = $degree->code.' - '.$degree->title;
  587. }
  588. }
  589. if ($flattenarrays) {
  590. $details[$fieldname] = implode(', ', $details[$fieldname]);
  591. }
  592. break;
  593. case 'linklist':
  594. foreach ($details[$basename] as $key => $link) {
  595. $details[$fieldname][$key] = html_writer::link($details[$basename][$key]->href, $details[$basename][$key]->title);
  596. }
  597. if ($flattenarrays) {
  598. $details[$fieldname] = implode(', ', $details[$fieldname]);
  599. }
  600. break;
  601. case 'moduleslist':
  602. foreach ($details[$basename] as $key => $module) {
  603. $details[$fieldname][$key] = $module->title;
  604. }
  605. if ($flattenarrays) {
  606. $details[$fieldname] = implode(', ', $details[$fieldname]);
  607. }
  608. break;
  609. case 'list':
  610. if ($flattenarrays) {
  611. $details[$fieldname] = implode(', ', $details[$basename]);
  612. }
  613. break;
  614. case 'link':
  615. $title = (isset($details[$basename]->title)) ? $details[$basename]->title : $details[$basename]->href;
  616. $details[$fieldname] = html_writer::link($details[$basename]->href, $title);
  617. break;
  618. case 'lang':
  619. $details[$fieldname] = self::check_lang($details[$fieldname]);
  620. break;
  621. case 'url':
  622. case 'string':
  623. default:
  624. // Nothing to do for these.
  625. }
  626. if (is_string($details[$fieldname])) {
  627. $details[$fieldname] = trim($details[$fieldname]);
  628. }
  629. }
  630. }
  631. return $details;
  632. }
  633. protected static function fullname($user) {
  634. return "{$user->firstname} {$user->lastname}";
  635. }
  636. /**
  637. * Check the lang string represents an installed language pack - return an empty string if it does not.
  638. * @param string $lang the language from the course/courselink
  639. * @return string either the original language identifier, or blank
  640. */
  641. protected static function check_lang($lang) {
  642. $sm = get_string_manager();
  643. if ($sm->translation_exists($lang, false)) {
  644. return $lang;
  645. }
  646. return '';
  647. }
  648. /**
  649. * Maps the remote details onto a course object
  650. * @param object $remotedetails the details from the ECS server
  651. * @return object the course details
  652. */
  653. public function map_remote_to_course($remotedetails) {
  654. // Copy all details out of structured object into flat array.
  655. $details = $this->flatten_remote_data($remotedetails);
  656. $remotefields = $this->courselink ? self::$remotefieldcourselink : self::$remotefields;
  657. // Copy details into course object, as specified by $this->importmappings.
  658. $course = new stdClass();
  659. foreach ($this->importmappings as $localfield => $remotefield) {
  660. if (empty($remotefield)) {
  661. continue;
  662. }
  663. if (self::is_text_field($localfield)) {
  664. $course->$localfield = $remotefield;
  665. if (preg_match_all('/\{([^}]+)\}/', $course->$localfield, $includedfields)) {
  666. foreach ($includedfields[1] as $field) {
  667. if (isset($details[$field])) {
  668. $type = $remotefields[$field];
  669. if ($type == 'date') {
  670. $val = userdate($details[$field], get_string('strftimedatetime'));
  671. } else {
  672. $val = $details[$field];
  673. }
  674. } else {
  675. $val = '';
  676. }
  677. if (is_array($val)) {
  678. campusconnect_log::add("Unexpected array in field $remotefield", false, true, true);
  679. $field = implode(',', $field);
  680. }
  681. $course->$localfield = str_replace('{'.$field.'}', $val, $course->$localfield);
  682. }
  683. }
  684. } else {
  685. if (isset($details[$remotefield])) {
  686. $course->$localfield = $details[$remotefield];
  687. }
  688. }
  689. }
  690. // Fix up the status field
  691. if ($this->courselink) {
  692. if (isset($remotedetails->status) && $remotedetails->status == 'offline') {
  693. $course->visible = 0;
  694. } else {
  695. $course->visible = 1;
  696. }
  697. }
  698. return $course;
  699. }
  700. /**
  701. * Maps the course object onto the remote details
  702. * @param object $course the course details
  703. * @return object details to send to the ECS server
  704. */
  705. public function map_course_to_remote($course) {
  706. // Make sure we don't update the original $course object
  707. $course = (array)$course;
  708. $course = (object)$course;
  709. // Convert data types, as required.
  710. foreach (self::$coursefields as $fieldname => $fieldtype) {
  711. if (isset($course->$fieldname)) {
  712. switch ($fieldtype) {
  713. case 'date':
  714. $course->$fieldname = userdate($course->$fieldname, '%Y-%m-%dT%H:%M:%S%z', 99, false);
  715. break;
  716. case 'list':
  717. $course->$fieldname = explode(',', $course->$fieldname);
  718. break;
  719. case 'lang': // TODO - test if this needs any conversion.
  720. case 'url':
  721. case 'string':
  722. default:
  723. // Nothing to do for these.
  724. }
  725. }
  726. }
  727. // Copy all details from the course into a flat array (as specified by $this->exportmappings).
  728. $details = array();
  729. foreach ($this->exportmappings as $remotefield => $localfield) {
  730. if (empty($localfield)) {
  731. continue;
  732. }
  733. if (self::is_remote_text_field($remotefield, $this->courselink)) {
  734. $details[$remotefield] = $localfield;
  735. if (preg_match_all('/\{([^}]+)\}/', $details[$remotefield], $includedfields)) {
  736. foreach ($includedfields[1] as $field) {
  737. if (isset($course->$field)) {
  738. $val = $course->$field;
  739. } else {
  740. $val = '';
  741. }
  742. $details[$remotefield] = str_replace('{'.$field.'}', $val, $details[$remotefield]);
  743. }
  744. }
  745. } else {
  746. if (isset($course->$localfield)) {
  747. $details[$remotefield] = $course->$localfield;
  748. }
  749. }
  750. }
  751. // Copy the details into the final structure.
  752. $remotedetails = new stdClass();
  753. foreach ($details as $field => $value) {
  754. if ($this->courselink) {
  755. $fieldparts = explode('.', $field);
  756. if ($fieldparts[0] == 'datesAndVenues') {
  757. if (!isset($remotedetails->datesAndVenues)) {
  758. $remotedetails->datesAndVenues = array(new stdClass());
  759. }
  760. if (count($fieldparts) > 2) {
  761. $subfieldname1 = $fieldparts[1];
  762. $subfieldname2 = $fieldparts[2];
  763. if (!isset($remotedetails->datesAndVenues[0]->$subfieldname1)) {
  764. $remotedetails->datesAndVenues[0]->$subfieldname1 = new stdClass();
  765. }
  766. $remotedetails->datesAndVenues[0]->$subfieldname1->$subfieldname2 = $value;
  767. } else {
  768. $subfieldname = $fieldparts[1];
  769. $remotedetails->datesAndVenues[0]->$subfieldname = $value;
  770. }
  771. continue;
  772. }
  773. }
  774. $remotedetails->$field = $value;
  775. }
  776. // Fix up the status field
  777. if ($this->courselink) {
  778. if ($course->visible) {
  779. $remotedetails->status = 'online';
  780. } else {
  781. $remotedetails->status = 'offline';
  782. }
  783. }
  784. return $remotedetails;
  785. }
  786. }