MoodleImport.php 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. /**
  4. * Class MoodleImport
  5. *
  6. * @author José Loguercio <jose.loguercio@beeznest.com>
  7. * @package chamilo.library
  8. */
  9. class MoodleImport
  10. {
  11. /**
  12. * Read and validate the moodleFile
  13. *
  14. * @param resource $uploadedFile *.* mbz file moodle course backup
  15. * @return bool
  16. */
  17. public function readMoodleFile($uploadedFile)
  18. {
  19. $file = $uploadedFile['tmp_name'];
  20. if (is_file($file) && is_readable($file)) {
  21. $package = new PclZip($file);
  22. $packageContent = $package->listContent();
  23. $mainFileKey = 0;
  24. foreach ($packageContent as $index => $value) {
  25. if ($value['filename'] == 'moodle_backup.xml') {
  26. $mainFileKey = $index;
  27. break;
  28. }
  29. }
  30. if (!$mainFileKey) {
  31. Display::addFlash(
  32. Display::return_message(
  33. get_lang('FailedToImportThisIsNotAMoodleFile'),
  34. 'error'
  35. )
  36. );
  37. }
  38. $folder = api_get_unique_id();
  39. $destinationDir = api_get_path(SYS_ARCHIVE_PATH) . $folder;
  40. $coursePath = api_get_course_path();
  41. $sessionId = api_get_session_id();
  42. $groupId = api_get_group_id();
  43. $documentPath = api_get_path(SYS_COURSE_PATH) . $coursePath . '/document';
  44. $courseInfo = api_get_course_info();
  45. mkdir($destinationDir, api_get_permissions_for_new_directories(), true);
  46. create_unexisting_directory(
  47. $courseInfo,
  48. api_get_user_id(),
  49. $sessionId,
  50. $groupId,
  51. null,
  52. $documentPath,
  53. '/moodle',
  54. 'Moodle Docs',
  55. 0
  56. );
  57. $package->extract(
  58. PCLZIP_OPT_PATH,
  59. $destinationDir
  60. );
  61. $xml = @file_get_contents($destinationDir.'/moodle_backup.xml');
  62. $doc = new DOMDocument();
  63. $res = @$doc->loadXML($xml);
  64. if ($res) {
  65. $activities = $doc->getElementsByTagName('activity');
  66. foreach ($activities as $activity) {
  67. if ($activity->childNodes->length) {
  68. $currentItem = [];
  69. foreach ($activity->childNodes as $item) {
  70. $currentItem[$item->nodeName] = $item->nodeValue;
  71. }
  72. $moduleName = isset($currentItem['modulename']) ? $currentItem['modulename'] : false;
  73. switch ($moduleName) {
  74. case 'forum':
  75. require_once '../forum/forumfunction.inc.php';
  76. $catForumValues = [];
  77. // Read the current forum module xml.
  78. $moduleDir = $currentItem['directory'];
  79. $moduleXml = @file_get_contents($destinationDir.'/'.$moduleDir.'/'.$moduleName.'.xml');
  80. $moduleValues = $this->readForumModule($moduleXml);
  81. // Create a Forum category based on Moodle forum type.
  82. $catForumValues['forum_category_title'] = $moduleValues['type'];
  83. $catForumValues['forum_category_comment'] = '';
  84. $catId = store_forumcategory($catForumValues, $courseInfo, false);
  85. $forumValues = [];
  86. $forumValues['forum_title'] = $moduleValues['name'];
  87. $forumValues['forum_image'] = '';
  88. $forumValues['forum_comment'] = $moduleValues['intro'];
  89. $forumValues['forum_category'] = $catId;
  90. $forumValues['moderated'] = 0;
  91. store_forum($forumValues, $courseInfo);
  92. break;
  93. case 'quiz':
  94. // Read the current quiz module xml.
  95. // The quiz case is the very complicate process of all the import.
  96. // Please if you want to review the script, try to see the readingXML functions.
  97. // The readingXML functions in this clases do all the mayor work here.
  98. $moduleDir = $currentItem['directory'];
  99. $moduleXml = @file_get_contents($destinationDir.'/'.$moduleDir.'/'.$moduleName.'.xml');
  100. $questionsXml = @file_get_contents($destinationDir.'/questions.xml');
  101. $moduleValues = $this->readQuizModule($moduleXml);
  102. // At this point we got all the prepared resources from Moodle file
  103. // $moduleValues variable contains all the necesary info to the quiz import
  104. // var_dump($moduleValues); // <-- uncomment this to see the final array
  105. // Lets do this ...
  106. $exercise = new Exercise($courseInfo['real_id']);
  107. $exercise->updateTitle(Exercise::format_title_variable($moduleValues['name']));
  108. $exercise->updateDescription($moduleValues['intro']);
  109. $exercise->updateAttempts($moduleValues['attempts_number']);
  110. $exercise->updateFeedbackType(0);
  111. // Match shuffle question with chamilo
  112. switch ($moduleValues['shufflequestions']) {
  113. case '0':
  114. $exercise->setRandom(0);
  115. break;
  116. case '1':
  117. $exercise->setRandom(-1);
  118. break;
  119. default:
  120. $exercise->setRandom(0);
  121. }
  122. $exercise->updateRandomAnswers($moduleValues['shuffleanswers']);
  123. // @todo divide to minutes
  124. $exercise->updateExpiredTime($moduleValues['timelimit']);
  125. if ($moduleValues['questionsperpage'] == 1) {
  126. $exercise->updateType(2);
  127. } else {
  128. $exercise->updateType(1);
  129. }
  130. // Create the new Quiz
  131. $exercise->save();
  132. // Ok, we got the Quiz and create it, now its time to add the Questions
  133. foreach ($moduleValues['question_instances'] as $index => $question) {
  134. $questionsValues = $this->readMainQuestionsXml($questionsXml, $question['questionid']);
  135. $moduleValues['question_instances'][$index] = $questionsValues;
  136. // Set Question Type from Moodle XML element <qtype>
  137. $qType = $moduleValues['question_instances'][$index]['qtype'];
  138. // Add the matched chamilo question type to the array
  139. $moduleValues['question_instances'][$index]['chamilo_qtype'] = $this->matchMoodleChamiloQuestionTypes($qType);
  140. $questionInstance = Question::getInstance($moduleValues['question_instances'][$index]['chamilo_qtype']);
  141. if ($questionInstance) {
  142. $questionInstance->updateTitle($moduleValues['question_instances'][$index]['name']);
  143. // Replace the path from @@PLUGINFILE@@ to a correct chamilo path
  144. $moduleValues['question_instances'][$index]['questiontext'] = str_replace('@@PLUGINFILE@@', '/courses/' . $coursePath . '/document/moodle', $moduleValues['question_instances'][$index]['questiontext']);
  145. $questionInstance->updateDescription($moduleValues['question_instances'][$index]['questiontext']);
  146. $questionInstance->updateLevel(1);
  147. $questionInstance->updateCategory(0);
  148. //Save normal question if NOT media
  149. if ($questionInstance->type != MEDIA_QUESTION) {
  150. $questionInstance->save($exercise->id);
  151. // modify the exercise
  152. $exercise->addToList($questionInstance->id);
  153. $exercise->update_question_positions();
  154. }
  155. $questionList = $moduleValues['question_instances'][$index]['plugin_qtype_'.$qType.'_question'];
  156. $currentQuestion = $moduleValues['question_instances'][$index];
  157. $this->processAnswers($questionList, $qType, $questionInstance, $currentQuestion);
  158. }
  159. }
  160. break;
  161. case 'resource':
  162. // Read the current resource module xml.
  163. $moduleDir = $currentItem['directory'];
  164. $moduleXml = @file_get_contents($destinationDir.'/'.$moduleDir.'/'.$moduleName.'.xml');
  165. $filesXml = @file_get_contents($destinationDir.'/files.xml');
  166. $moduleValues = $this->readResourceModule($moduleXml);
  167. $mainFileModuleValues = $this->readMainFilesXml($filesXml, $moduleValues['contextid']);
  168. $fileInfo = array_merge($moduleValues, $mainFileModuleValues, $currentItem);
  169. $currentResourceFilePath = $destinationDir.'/files/';
  170. $dirs = new RecursiveDirectoryIterator($currentResourceFilePath);
  171. foreach (new RecursiveIteratorIterator($dirs) as $file) {
  172. if (is_file($file) && strpos($file, $fileInfo['contenthash']) !== false) {
  173. $files = [];
  174. $files['file']['name'] = $fileInfo['filename'];
  175. $files['file']['tmp_name'] = $file->getPathname();
  176. $files['file']['type'] = $fileInfo['mimetype'];
  177. $files['file']['error'] = 0;
  178. $files['file']['size'] = $fileInfo['filesize'];
  179. $files['file']['from_file'] = true;
  180. $files['file']['move_file'] = true;
  181. $_POST['language'] = $courseInfo['language'];
  182. $_POST['moodle_import'] = true;
  183. DocumentManager::upload_document(
  184. $files,
  185. '/moodle',
  186. $fileInfo['title'],
  187. '',
  188. null,
  189. null,
  190. true,
  191. true
  192. );
  193. }
  194. }
  195. break;
  196. case 'url':
  197. // Read the current url module xml.
  198. $moduleDir = $currentItem['directory'];
  199. $moduleXml = @file_get_contents($destinationDir.'/'.$moduleDir.'/'.$moduleName.'.xml');
  200. $moduleValues = $this->readUrlModule($moduleXml);
  201. $_POST['title'] = $moduleValues['name'];
  202. $_POST['url'] = $moduleValues['externalurl'];
  203. $_POST['description'] = $moduleValues['intro'];
  204. $_POST['category_id'] = 0;
  205. $_POST['target'] = '_blank';
  206. Link::addlinkcategory("link");
  207. break;
  208. }
  209. }
  210. }
  211. // This process will upload all question resource files
  212. $filesXml = @file_get_contents($destinationDir.'/files.xml');
  213. $mainFileModuleValues = $this->getAllQuestionFiles($filesXml);
  214. $currentResourceFilePath = $destinationDir.'/files/';
  215. foreach ($mainFileModuleValues as $fileInfo) {
  216. $dirs = new RecursiveDirectoryIterator($currentResourceFilePath);
  217. foreach (new RecursiveIteratorIterator($dirs) as $file) {
  218. if (is_file($file) && strpos($file, $fileInfo['contenthash']) !== false) {
  219. $files = [];
  220. $files['file']['name'] = $fileInfo['filename'];
  221. $files['file']['tmp_name'] = $file->getPathname();
  222. $files['file']['type'] = $fileInfo['mimetype'];
  223. $files['file']['error'] = 0;
  224. $files['file']['size'] = $fileInfo['filesize'];
  225. $files['file']['from_file'] = true;
  226. $files['file']['move_file'] = true;
  227. $_POST['language'] = $courseInfo['language'];
  228. $_POST['moodle_import'] = true;
  229. DocumentManager::upload_document(
  230. $files,
  231. '/moodle',
  232. isset($fileInfo['title']) ? $fileInfo['title'] : pathinfo($fileInfo['filename'], PATHINFO_FILENAME),
  233. '',
  234. null,
  235. null,
  236. true,
  237. true,
  238. 'file',
  239. // This is to validate spaces as hyphens
  240. false
  241. );
  242. }
  243. }
  244. }
  245. } else {
  246. removeDir($destinationDir);
  247. return false;
  248. }
  249. } else {
  250. return false;
  251. }
  252. removeDir($destinationDir);
  253. return $packageContent[$mainFileKey];
  254. }
  255. /**
  256. * Read and validate the forum module XML
  257. *
  258. * @param resource $moduleXml XML file
  259. * @return mixed | array if is a valid xml file, false otherwise
  260. */
  261. public function readForumModule($moduleXml)
  262. {
  263. $moduleDoc = new DOMDocument();
  264. $moduleRes = @$moduleDoc->loadXML($moduleXml);
  265. if ($moduleRes) {
  266. $activities = $moduleDoc->getElementsByTagName('forum');
  267. $currentItem = [];
  268. foreach ($activities as $activity) {
  269. if ($activity->childNodes->length) {
  270. foreach ($activity->childNodes as $item) {
  271. $currentItem[$item->nodeName] = $item->nodeValue;
  272. }
  273. }
  274. }
  275. return $currentItem;
  276. }
  277. return false;
  278. }
  279. /**
  280. * Read and validate the resource module XML
  281. *
  282. * @param resource $moduleXml XML file
  283. * @return mixed | array if is a valid xml file, false otherwise
  284. */
  285. public function readResourceModule($moduleXml)
  286. {
  287. $moduleDoc = new DOMDocument();
  288. $moduleRes = @$moduleDoc->loadXML($moduleXml);
  289. if ($moduleRes) {
  290. $activities = $moduleDoc->getElementsByTagName('resource');
  291. $mainActivity = $moduleDoc->getElementsByTagName('activity');
  292. $contextId = $mainActivity->item(0)->getAttribute('contextid');
  293. $currentItem = [];
  294. foreach ($activities as $activity) {
  295. if ($activity->childNodes->length) {
  296. foreach ($activity->childNodes as $item) {
  297. $currentItem[$item->nodeName] = $item->nodeValue;
  298. }
  299. }
  300. }
  301. $currentItem['contextid'] = $contextId;
  302. return $currentItem;
  303. }
  304. return false;
  305. }
  306. /**
  307. * Read and validate the url module XML
  308. *
  309. * @param resource $moduleXml XML file
  310. * @return mixed | array if is a valid xml file, false otherwise
  311. */
  312. public function readUrlModule($moduleXml)
  313. {
  314. $moduleDoc = new DOMDocument();
  315. $moduleRes = @$moduleDoc->loadXML($moduleXml);
  316. if ($moduleRes) {
  317. $activities = $moduleDoc->getElementsByTagName('url');
  318. $currentItem = [];
  319. foreach ($activities as $activity) {
  320. if ($activity->childNodes->length) {
  321. foreach ($activity->childNodes as $item) {
  322. $currentItem[$item->nodeName] = $item->nodeValue;
  323. }
  324. }
  325. }
  326. return $currentItem;
  327. }
  328. return false;
  329. }
  330. /**
  331. * Read and validate the quiz module XML
  332. *
  333. * @param resource $moduleXml XML file
  334. * @return mixed | array if is a valid xml file, false otherwise
  335. */
  336. public function readQuizModule($moduleXml)
  337. {
  338. $moduleDoc = new DOMDocument();
  339. $moduleRes = @$moduleDoc->loadXML($moduleXml);
  340. if ($moduleRes) {
  341. $activities = $moduleDoc->getElementsByTagName('quiz');
  342. $currentItem = [];
  343. foreach ($activities as $activity) {
  344. if ($activity->childNodes->length) {
  345. foreach ($activity->childNodes as $item) {
  346. $currentItem[$item->nodeName] = $item->nodeValue;
  347. }
  348. }
  349. }
  350. $questions = $moduleDoc->getElementsByTagName('question_instance');
  351. $questionList = [];
  352. $counter = 0;
  353. foreach ($questions as $question) {
  354. if ($question->childNodes->length) {
  355. foreach ($question->childNodes as $item) {
  356. $questionList[$counter][$item->nodeName] = $item->nodeValue;
  357. }
  358. $counter++;
  359. }
  360. }
  361. $currentItem['question_instances'] = $questionList;
  362. return $currentItem;
  363. }
  364. return false;
  365. }
  366. /**
  367. * Search the current file resource in main Files XML
  368. *
  369. * @param resource $filesXml XML file
  370. * @param int $contextId
  371. * @return mixed | array if is a valid xml file, false otherwise
  372. */
  373. public function readMainFilesXml($filesXml, $contextId)
  374. {
  375. $moduleDoc = new DOMDocument();
  376. $moduleRes = @$moduleDoc->loadXML($filesXml);
  377. if ($moduleRes) {
  378. $activities = $moduleDoc->getElementsByTagName('file');
  379. $currentItem = [];
  380. foreach ($activities as $activity) {
  381. if ($activity->childNodes->length) {
  382. $isThisItemThatIWant = false;
  383. foreach ($activity->childNodes as $item) {
  384. if (!$isThisItemThatIWant && $item->nodeName == 'contenthash') {
  385. $currentItem['contenthash'] = $item->nodeValue;
  386. }
  387. if ($item->nodeName == 'contextid' && intval($item->nodeValue) == intval($contextId) && !$isThisItemThatIWant) {
  388. $isThisItemThatIWant = true;
  389. continue;
  390. }
  391. if ($isThisItemThatIWant && $item->nodeName == 'filename') {
  392. $currentItem['filename'] = $item->nodeValue;
  393. }
  394. if ($isThisItemThatIWant && $item->nodeName == 'filesize') {
  395. $currentItem['filesize'] = $item->nodeValue;
  396. }
  397. if ($isThisItemThatIWant && $item->nodeName == 'mimetype' && $item->nodeValue == 'document/unknown') {
  398. break;
  399. }
  400. if ($isThisItemThatIWant && $item->nodeName == 'mimetype' && $item->nodeValue !== 'document/unknown') {
  401. $currentItem['mimetype'] = $item->nodeValue;
  402. break 2;
  403. }
  404. }
  405. }
  406. }
  407. return $currentItem;
  408. }
  409. return false;
  410. }
  411. /**
  412. * Search the current question resource in main Questions XML
  413. *
  414. * @param resource $questionsXml XML file
  415. * @param int $questionId
  416. * @return mixed | array if is a valid xml file, false otherwise
  417. */
  418. public function readMainQuestionsXml($questionsXml, $questionId)
  419. {
  420. $moduleDoc = new DOMDocument();
  421. $moduleRes = @$moduleDoc->loadXML($questionsXml);
  422. if ($moduleRes) {
  423. $questions = $moduleDoc->getElementsByTagName('question');
  424. $currentItem = [];
  425. foreach ($questions as $question) {
  426. if (intval($question->getAttribute('id')) == $questionId) {
  427. if ($question->childNodes->length) {
  428. $currentItem['questionid'] = $questionId;
  429. $questionType = '';
  430. foreach ($question->childNodes as $item) {
  431. $currentItem[$item->nodeName] = $item->nodeValue;
  432. if ($item->nodeName == 'qtype') {
  433. $questionType = $item->nodeValue;
  434. }
  435. if ($item->nodeName == 'plugin_qtype_'.$questionType.'_question') {
  436. $answer = $item->getElementsByTagName($this->getQuestionTypeAnswersTag($questionType));
  437. $currentItem['plugin_qtype_'.$questionType.'_question'] = [];
  438. for ($i = 0; $i <= $answer->length - 1; $i++) {
  439. $currentItem['plugin_qtype_'.$questionType.'_question'][$i]['answerid'] = $answer->item($i)->getAttribute('id');
  440. foreach ($answer->item($i)->childNodes as $properties) {
  441. $currentItem['plugin_qtype_'.$questionType.'_question'][$i][$properties->nodeName] = $properties->nodeValue;
  442. }
  443. }
  444. $typeValues = $item->getElementsByTagName($this->getQuestionTypeOptionsTag($questionType));
  445. for ($i = 0; $i <= $typeValues->length - 1; $i++) {
  446. foreach ($typeValues->item($i)->childNodes as $properties) {
  447. $currentItem[$questionType.'_values'][$properties->nodeName] = $properties->nodeValue;
  448. if ($properties->nodeName == 'sequence') {
  449. $sequence = $properties->nodeValue;
  450. $sequenceIds = explode(',', $sequence);
  451. foreach ($sequenceIds as $qId) {
  452. $questionMatch = $this->readMainQuestionsXml($questionsXml, $qId);
  453. $currentItem['plugin_qtype_'.$questionType.'_question'][] = $questionMatch;
  454. }
  455. }
  456. }
  457. }
  458. }
  459. }
  460. }
  461. }
  462. }
  463. $this->traverseArray($currentItem, ['#text', 'question_hints', 'tags']);
  464. return $currentItem;
  465. }
  466. return false;
  467. }
  468. /**
  469. * return the correct question type options tag
  470. *
  471. * @param string $questionType name
  472. * @return string question type tag
  473. */
  474. public function getQuestionTypeOptionsTag($questionType)
  475. {
  476. switch ($questionType) {
  477. case 'match':
  478. case 'ddmatch':
  479. return 'matchoptions';
  480. break;
  481. default:
  482. return $questionType;
  483. break;
  484. }
  485. }
  486. /**
  487. * return the correct question type answers tag
  488. *
  489. * @param string $questionType name
  490. * @return string question type tag
  491. */
  492. public function getQuestionTypeAnswersTag($questionType)
  493. {
  494. switch ($questionType) {
  495. case 'match':
  496. case 'ddmatch':
  497. return 'match';
  498. break;
  499. default:
  500. return 'answer';
  501. break;
  502. }
  503. }
  504. /**
  505. *
  506. * @param string $moodleQuestionType
  507. * @return integer Chamilo question type
  508. */
  509. public function matchMoodleChamiloQuestionTypes($moodleQuestionType)
  510. {
  511. switch ($moodleQuestionType) {
  512. case 'multichoice':
  513. return UNIQUE_ANSWER;
  514. break;
  515. case 'multianswer':
  516. case 'shortanswer':
  517. case 'match':
  518. return FILL_IN_BLANKS;
  519. break;
  520. case 'essay':
  521. return FREE_ANSWER;
  522. break;
  523. case 'truefalse':
  524. return UNIQUE_ANSWER_NO_OPTION;
  525. break;
  526. }
  527. }
  528. /**
  529. * Process Moodle Answers to Chamilo
  530. *
  531. * @param array $questionList
  532. * @param string $questionType
  533. * @param object $questionInstance Question/Answer instance
  534. * @param array $currentQuestion
  535. * @return integer db response
  536. */
  537. public function processAnswers($questionList, $questionType, $questionInstance, $currentQuestion)
  538. {
  539. switch ($questionType) {
  540. case 'multichoice':
  541. $objAnswer = new Answer($questionInstance->id);
  542. $questionWeighting = 0;
  543. foreach ($questionList as $slot => $answer) {
  544. $this->processUniqueAnswer($objAnswer, $answer, $slot + 1, $questionWeighting);
  545. }
  546. // saves the answers into the data base
  547. $objAnswer->save();
  548. // sets the total weighting of the question
  549. $questionInstance->updateWeighting($questionWeighting);
  550. $questionInstance->save();
  551. return true;
  552. break;
  553. case 'multianswer':
  554. $objAnswer = new Answer($questionInstance->id);
  555. $coursePath = api_get_course_path();
  556. $placeholder = str_replace('@@PLUGINFILE@@', '/courses/' . $coursePath . '/document/moodle', $currentQuestion['questiontext']);
  557. $optionsValues = [];
  558. foreach ($questionList as $slot => $subQuestion) {
  559. $qtype = $subQuestion['qtype'];
  560. $optionsValues[] = $this->processFillBlanks($objAnswer, $qtype, $subQuestion['plugin_qtype_'.$qtype.'_question'], $placeholder, $slot + 1);
  561. }
  562. $answerOptionsWeight = '::';
  563. $answerOptionsSize = '';
  564. $questionWeighting = 0;
  565. foreach ($optionsValues as $index => $value) {
  566. $questionWeighting += $value['weight'];
  567. $answerOptionsWeight .= $value['weight'].',';
  568. $answerOptionsSize .= $value['size'].',';
  569. }
  570. $answerOptionsWeight = substr($answerOptionsWeight, 0, -1);
  571. $answerOptionsSize = substr($answerOptionsSize, 0, -1);
  572. $answerOptions = $answerOptionsWeight.':'.$answerOptionsSize.':0@';
  573. $placeholder = $placeholder.PHP_EOL.$answerOptions;
  574. // This is a minor trick to clean the question description that in a multianswer is the main placeholder
  575. $questionInstance->updateDescription('');
  576. // sets the total weighting of the question
  577. $questionInstance->updateWeighting($questionWeighting);
  578. $questionInstance->save();
  579. // saves the answers into the data base
  580. $objAnswer->createAnswer($placeholder, 0, '', 0, 1);
  581. $objAnswer->save();
  582. return true;
  583. case 'match':
  584. $objAnswer = new Answer($questionInstance->id);
  585. $placeholder = '';
  586. $optionsValues = $this->processFillBlanks($objAnswer, 'match', $questionList, $placeholder, 0);
  587. $answerOptionsWeight = '::';
  588. $answerOptionsSize = '';
  589. $questionWeighting = 0;
  590. foreach ($optionsValues as $index => $value) {
  591. $questionWeighting += $value['weight'];
  592. $answerOptionsWeight .= $value['weight'].',';
  593. $answerOptionsSize .= $value['size'].',';
  594. }
  595. $answerOptionsWeight = substr($answerOptionsWeight, 0, -1);
  596. $answerOptionsSize = substr($answerOptionsSize, 0, -1);
  597. $answerOptions = $answerOptionsWeight.':'.$answerOptionsSize.':0@';
  598. $placeholder = $placeholder.PHP_EOL.$answerOptions;
  599. // sets the total weighting of the question
  600. $questionInstance->updateWeighting($questionWeighting);
  601. $questionInstance->save();
  602. // saves the answers into the data base
  603. $objAnswer->createAnswer($placeholder, 0, '', 0, 1);
  604. $objAnswer->save();
  605. return true;
  606. break;
  607. case 'shortanswer':
  608. case 'ddmatch':
  609. $questionWeighting = $currentQuestion['defaultmark'];
  610. $questionInstance->updateWeighting($questionWeighting);
  611. $questionInstance->updateDescription(get_lang('ThisQuestionIsNotSupportedYet'));
  612. $questionInstance->save();
  613. return false;
  614. break;
  615. case 'essay':
  616. $questionWeighting = $currentQuestion['defaultmark'];
  617. $questionInstance->updateWeighting($questionWeighting);
  618. $questionInstance->save();
  619. return true;
  620. break;
  621. case 'truefalse':
  622. $objAnswer = new Answer($questionInstance->id);
  623. $questionWeighting = 0;
  624. foreach ($questionList as $slot => $answer) {
  625. $this->processTrueFalse($objAnswer, $answer, $slot + 1, $questionWeighting);
  626. }
  627. // saves the answers into the data base
  628. $objAnswer->save();
  629. // sets the total weighting of the question
  630. $questionInstance->updateWeighting($questionWeighting);
  631. $questionInstance->save();
  632. return false;
  633. break;
  634. default:
  635. return false;
  636. break;
  637. }
  638. }
  639. /**
  640. * Process Chamilo Unique Answer
  641. *
  642. * @param object $objAnswer
  643. * @param array $answerValues
  644. * @param integer $position
  645. * @param integer $questionWeighting
  646. * @return integer db response
  647. */
  648. public function processUniqueAnswer($objAnswer, $answerValues, $position, &$questionWeighting)
  649. {
  650. $correct = intval($answerValues['fraction']) ? intval($answerValues['fraction']) : 0;
  651. $answer = $answerValues['answertext'];
  652. $comment = $answerValues['feedback'];
  653. $weighting = $answerValues['fraction'];
  654. $weighting = abs($weighting);
  655. if ($weighting > 0) {
  656. $questionWeighting += $weighting;
  657. }
  658. $goodAnswer = $correct ? true : false;
  659. $objAnswer->createAnswer(
  660. $answer,
  661. $goodAnswer,
  662. $comment,
  663. $weighting,
  664. $position,
  665. null,
  666. null,
  667. ''
  668. );
  669. }
  670. /**
  671. * Process Chamilo True False
  672. *
  673. * @param object $objAnswer
  674. * @param array $answerValues
  675. * @param integer $position
  676. * @param integer $questionWeighting
  677. * @return integer db response
  678. */
  679. public function processTrueFalse($objAnswer, $answerValues, $position, &$questionWeighting)
  680. {
  681. $correct = intval($answerValues['fraction']) ? intval($answerValues['fraction']) : 0;
  682. $answer = $answerValues['answertext'];
  683. $comment = $answerValues['feedback'];
  684. $weighting = $answerValues['fraction'];
  685. $weighting = abs($weighting);
  686. if ($weighting > 0) {
  687. $questionWeighting += $weighting;
  688. }
  689. $goodAnswer = $correct ? true : false;
  690. $objAnswer->createAnswer(
  691. $answer,
  692. $goodAnswer,
  693. $comment,
  694. $weighting,
  695. $position,
  696. null,
  697. null,
  698. ''
  699. );
  700. }
  701. /**
  702. * Process Chamilo FillBlanks
  703. *
  704. * @param object $objAnswer
  705. * @param array $questionType
  706. * @param array $answerValues
  707. * @param string $placeholder
  708. * @param integer $position
  709. * @return integer db response
  710. */
  711. public function processFillBlanks($objAnswer, $questionType, $answerValues, &$placeholder, $position)
  712. {
  713. $coursePath = api_get_course_path();
  714. switch ($questionType) {
  715. case 'multichoice':
  716. $optionsValues = [];
  717. $correctAnswer = '';
  718. $othersAnswer = '';
  719. foreach ($answerValues as $answer) {
  720. $correct = intval($answer['fraction']);
  721. if ($correct) {
  722. $correctAnswer .= $answer['answertext'].'|';
  723. $optionsValues['weight'] = $answer['fraction'];
  724. $optionsValues['size'] = '200';
  725. } else {
  726. $othersAnswer .= $answer['answertext'].'|';
  727. }
  728. }
  729. $currentAnswers = $correctAnswer.$othersAnswer;
  730. $currentAnswers = '['.substr($currentAnswers, 0, -1).']';
  731. $placeholder = str_replace("{#$position}", $currentAnswers, $placeholder);
  732. return $optionsValues;
  733. break;
  734. case 'shortanswer':
  735. $optionsValues = [];
  736. $correctAnswer = '';
  737. foreach ($answerValues as $answer) {
  738. $correct = intval($answer['fraction']);
  739. if ($correct) {
  740. $correctAnswer .= $answer['answertext'];
  741. $optionsValues['weight'] = $answer['fraction'];
  742. $optionsValues['size'] = '200';
  743. }
  744. }
  745. $currentAnswers = '['.$correctAnswer.']';
  746. $placeholder = str_replace("{#$position}", $currentAnswers, $placeholder);
  747. return $optionsValues;
  748. break;
  749. case 'match':
  750. $answers = [];
  751. // Here first we need to extract all the possible answers
  752. foreach ($answerValues as $slot => $answer) {
  753. $answers[$slot] = $answer['answertext'];
  754. }
  755. // Now we set the order of the values matching the correct answer and set it to the first element
  756. $optionsValues = [];
  757. foreach ($answerValues as $slot => $answer) {
  758. $correctAnswer = '';
  759. $othersAnswers = '';
  760. $correctAnswer .= $answer['answertext'].'|';
  761. foreach ($answers as $other) {
  762. if ($other !== $answer['answertext']) {
  763. $othersAnswers .= $other.'|';
  764. }
  765. }
  766. $optionsValues[$slot]['weight'] = 1;
  767. $optionsValues[$slot]['size'] = '200';
  768. $currentAnswers = $correctAnswer.$othersAnswers;
  769. $currentAnswers = '['.substr($currentAnswers, 0, -1).'] ';
  770. $answer['questiontext'] = str_replace('@@PLUGINFILE@@', '/courses/' . $coursePath . '/document/moodle', $answer['questiontext']);
  771. $placeholder .= '<p> ' . strip_tags($answer['questiontext']).' '.$currentAnswers . ' </p>';
  772. }
  773. return $optionsValues;
  774. break;
  775. default:
  776. return false;
  777. break;
  778. }
  779. }
  780. /**
  781. * get All files associated with a question
  782. *
  783. * @param $filesXml
  784. * @return array
  785. */
  786. public function getAllQuestionFiles($filesXml)
  787. {
  788. $moduleDoc = new DOMDocument();
  789. $moduleRes = @$moduleDoc->loadXML($filesXml);
  790. $allFiles = [];
  791. if ($moduleRes) {
  792. $activities = $moduleDoc->getElementsByTagName('file');
  793. foreach ($activities as $activity) {
  794. $currentItem = [];
  795. $thisIsAnInvalidItem = false;
  796. if ($activity->childNodes->length) {
  797. foreach ($activity->childNodes as $item) {
  798. if ($item->nodeName == 'component' && $item->nodeValue == 'mod_resource') {
  799. $thisIsAnInvalidItem = true;
  800. }
  801. if ($item->nodeName == 'contenthash') {
  802. $currentItem['contenthash'] = $item->nodeValue;
  803. }
  804. if ($item->nodeName == 'filename') {
  805. $currentItem['filename'] = $item->nodeValue;
  806. }
  807. if ($item->nodeName == 'filesize') {
  808. $currentItem['filesize'] = $item->nodeValue;
  809. }
  810. if ($item->nodeName == 'mimetype' && $item->nodeValue == 'document/unknown') {
  811. $thisIsAnInvalidItem = true;
  812. }
  813. if ($item->nodeName == 'mimetype' && $item->nodeValue !== 'document/unknown') {
  814. $currentItem['mimetype'] = $item->nodeValue;
  815. }
  816. }
  817. }
  818. if (!$thisIsAnInvalidItem) {
  819. $allFiles[] = $currentItem;
  820. }
  821. }
  822. }
  823. return $allFiles;
  824. }
  825. /**
  826. * Litle utility to delete the unuseful tags
  827. *
  828. * @param $array
  829. * @param $keys
  830. */
  831. public function traverseArray(&$array, $keys)
  832. {
  833. foreach ($array as $key => &$value) {
  834. if (is_array($value)) {
  835. $this->traverseArray($value, $keys);
  836. } else {
  837. if (in_array($key, $keys)) {
  838. unset($array[$key]);
  839. }
  840. }
  841. }
  842. }
  843. }