From 221e6d80ab21a77839af690dbe3a81728eb48133 Mon Sep 17 00:00:00 2001 From: Christian Beeznest Date: Mon, 23 Jun 2025 19:59:32 -0500 Subject: [PATCH] Course: Improve export of quiz questions, assignments in LP, and images in introduction page with mzb format - refs BT#21977 --- main/coursecopy/export_moodle.php | 2 +- main/inc/lib/moodleexport/ActivityExport.php | 1 + main/inc/lib/moodleexport/AssignExport.php | 16 +++-- main/inc/lib/moodleexport/FileExport.php | 10 ++- main/inc/lib/moodleexport/MoodleExport.php | 15 +++- main/inc/lib/moodleexport/PageExport.php | 73 +++++++++++++++++--- main/inc/lib/moodleexport/QuizExport.php | 9 ++- main/inc/lib/moodleexport/SectionExport.php | 2 +- 8 files changed, 109 insertions(+), 19 deletions(-) diff --git a/main/coursecopy/export_moodle.php b/main/coursecopy/export_moodle.php index 50b632f2675..7ed6ec88f6b 100644 --- a/main/coursecopy/export_moodle.php +++ b/main/coursecopy/export_moodle.php @@ -138,7 +138,7 @@ $exportDir = 'moodle_export_'.$courseId; try { - $moodleVersion = isset($values['moodle_version']) ? $values['moodle_version'] : '3'; + $moodleVersion = $values['moodle_version'] ?? '3'; $mbzFile = $exporter->export($courseId, $exportDir, $moodleVersion); echo Display::return_message(get_lang('MoodleExportCreated'), 'confirm'); echo '
'; diff --git a/main/inc/lib/moodleexport/ActivityExport.php b/main/inc/lib/moodleexport/ActivityExport.php index 2c054454fb4..e7cd8951326 100644 --- a/main/inc/lib/moodleexport/ActivityExport.php +++ b/main/inc/lib/moodleexport/ActivityExport.php @@ -33,6 +33,7 @@ public function getSectionIdForActivity(int $activityId, string $itemType): int { foreach ($this->course->resources[RESOURCE_LEARNPATH] as $learnpath) { foreach ($learnpath->items as $item) { + $item['item_type'] = $item['item_type'] === 'student_publication' ? 'work' : $item['item_type']; if ($item['item_type'] == $itemType && $item['path'] == $activityId) { return $learnpath->source_id; } diff --git a/main/inc/lib/moodleexport/AssignExport.php b/main/inc/lib/moodleexport/AssignExport.php index 72f6263ea34..77a3644244e 100644 --- a/main/inc/lib/moodleexport/AssignExport.php +++ b/main/inc/lib/moodleexport/AssignExport.php @@ -49,6 +49,12 @@ public function getData(int $assignId, int $sectionId): ?array { $work = $this->course->resources[RESOURCE_WORK][$assignId]; + if (empty($work->params['id']) || empty($work->params['title'])) { + return null; + } + + $sentDate = !empty($work->params['sent_date']) ? strtotime($work->params['sent_date']) : time(); + $workFiles = getAllDocumentToWork($assignId, $this->course->info['real_id']); $files = []; if (!empty($workFiles)) { @@ -73,11 +79,11 @@ public function getData(int $assignId, int $sectionId): ?array 'contextid' => $this->course->info['real_id'], 'sectionid' => $sectionId, 'sectionnumber' => 0, - 'name' => htmlspecialchars($work->params['title']), - 'intro' => $work->params['description'], - 'duedate' => strtotime($work->params['sent_date']), - 'gradingduedate' => strtotime($work->params['sent_date']) + 86400 * 7, - 'allowsubmissionsfromdate' => strtotime($work->params['sent_date']), + 'name' => htmlspecialchars($work->params['title'], ENT_QUOTES), + 'intro' => htmlspecialchars($work->params['description'], ENT_QUOTES), + 'duedate' => $sentDate, + 'gradingduedate' => $sentDate + 7 * 86400, + 'allowsubmissionsfromdate' => $sentDate, 'timemodified' => time(), 'grade_item_id' => 0, 'files' => $files, diff --git a/main/inc/lib/moodleexport/FileExport.php b/main/inc/lib/moodleexport/FileExport.php index 77ff305d72a..4f982ec3628 100644 --- a/main/inc/lib/moodleexport/FileExport.php +++ b/main/inc/lib/moodleexport/FileExport.php @@ -186,6 +186,14 @@ private function createFileXmlEntry(array $file): string */ private function processDocument(array $filesData, object $document): array { + if ( + $document->file_type === 'file' && + isset($this->course->used_page_doc_ids) && + in_array($document->source_id, $this->course->used_page_doc_ids) + ) { + return $filesData; + } + if ( $document->file_type === 'file' && pathinfo($document->path, PATHINFO_EXTENSION) === 'html' && @@ -296,7 +304,7 @@ private function ensureTrailingSlash(string $path): string /** * Get MIME type based on the file extension. */ - private function getMimeType($filePath): string + public function getMimeType($filePath): string { $extension = pathinfo($filePath, PATHINFO_EXTENSION); $mimeTypes = $this->getMimeTypes(); diff --git a/main/inc/lib/moodleexport/MoodleExport.php b/main/inc/lib/moodleexport/MoodleExport.php index af26de5b19d..bc8d7cf6e80 100644 --- a/main/inc/lib/moodleexport/MoodleExport.php +++ b/main/inc/lib/moodleexport/MoodleExport.php @@ -69,8 +69,15 @@ public function export(string $courseId, string $exportDir, int $version) $courseExport->exportCourse($tempDir); // Export files-related data and actual files + $pageExport = new PageExport($this->course); + $pageFiles = []; + $pageData = $pageExport->getData(0, 1); + if (!empty($pageData['files'])) { + $pageFiles = $pageData['files']; + } $fileExport = new FileExport($this->course); $filesData = $fileExport->getFilesData(); + $filesData['files'] = array_merge($filesData['files'], $pageFiles); $fileExport->exportFiles($filesData, $tempDir); // Export sections of the course @@ -98,8 +105,12 @@ public function exportQuestionsXml(array $questionsData, string $exportDir): voi $xmlContent .= ''.PHP_EOL; foreach ($questionsData as $quiz) { - $categoryId = $quiz['questions'][0]['questioncategoryid'] ?? '0'; - + $categoryId = $quiz['questions'][0]['questioncategoryid'] ?? '1'; + $hash = md5($categoryId . $quiz['name']); + if (isset($categoryHashes[$hash])) { + continue; + } + $categoryHashes[$hash] = true; $xmlContent .= ' '.PHP_EOL; $xmlContent .= ' Default for '.htmlspecialchars($quiz['name'] ?? 'Unknown').''.PHP_EOL; $xmlContent .= ' '.($quiz['contextid'] ?? '0').''.PHP_EOL; diff --git a/main/inc/lib/moodleexport/PageExport.php b/main/inc/lib/moodleexport/PageExport.php index b4bdeeae130..7e69deef9d1 100644 --- a/main/inc/lib/moodleexport/PageExport.php +++ b/main/inc/lib/moodleexport/PageExport.php @@ -46,11 +46,50 @@ public function getData(int $pageId, int $sectionId): ?array { $contextid = $this->course->info['real_id']; if ($pageId === 0) { - if ( - isset($this->course->resources[RESOURCE_TOOL_INTRO]['course_homepage']) && - is_object($this->course->resources[RESOURCE_TOOL_INTRO]['course_homepage']) && - !empty($this->course->resources[RESOURCE_TOOL_INTRO]['course_homepage']->intro_text) - ) { + $introText = trim($this->course->resources[RESOURCE_TOOL_INTRO]['course_homepage']->intro_text ?? ''); + + if (!empty($introText)) { + $files = []; + $resources = \DocumentManager::get_resources_from_source_html($introText); + $courseInfo = api_get_course_info($this->course->code); + $adminId = MoodleExport::getAdminUserData()['id']; + + foreach ($resources as [$src]) { + if (preg_match('#/document(/[^"\']+)#', $src, $matches)) { + $path = $matches[1]; + $docId = \DocumentManager::get_document_id($courseInfo, $path); + if ($docId) { + $this->course->used_page_doc_ids[] = $docId; + $document = \DocumentManager::get_document_data_by_id($docId, $this->course->code); + if ($document) { + $contenthash = hash('sha1', basename($document['path'])); + $mimetype = (new FileExport($this->course))->getMimeType($document['path']); + + $files[] = [ + 'id' => $document['id'], + 'contenthash' => $contenthash, + 'contextid' => $contextid, + 'component' => 'mod_page', + 'filearea' => 'content', + 'itemid' => 1, + 'filepath' => '/Documents/', + 'documentpath' => 'document' . $document['path'], + 'filename' => basename($document['path']), + 'userid' => $adminId, + 'filesize' => $document['size'], + 'mimetype' => $mimetype, + 'status' => 0, + 'timecreated' => time() - 3600, + 'timemodified' => time(), + 'source' => $document['title'], + 'author' => 'Unknown', + 'license' => 'allrightsreserved', + ]; + } + } + } + } + return [ 'id' => 0, 'moduleid' => 0, @@ -58,13 +97,13 @@ public function getData(int $pageId, int $sectionId): ?array 'contextid' => $contextid, 'name' => get_lang('Introduction'), 'intro' => '', - 'content' => trim($this->course->resources[RESOURCE_TOOL_INTRO]['course_homepage']->intro_text), + 'content' => $this->normalizeContent($introText), 'sectionid' => $sectionId, 'sectionnumber' => 1, 'display' => 0, 'timemodified' => time(), 'users' => [], - 'files' => [], + 'files' => $files, ]; } } @@ -79,7 +118,7 @@ public function getData(int $pageId, int $sectionId): ?array 'contextid' => $contextid, 'name' => $page->title, 'intro' => $page->comment ?? '', - 'content' => $this->getPageContent($page), + 'content' => $this->normalizeContent($this->getPageContent($page)), 'sectionid' => $sectionId, 'sectionnumber' => 1, 'display' => 0, @@ -117,6 +156,24 @@ private function createPageXml(array $pageData, string $pageDir): void $this->createXmlFile('page', $xmlContent, $pageDir); } + private function normalizeContent(string $html): string + { + return preg_replace_callback( + '#]+src=["\'](?[^"\']+)["\']#i', + function ($match) { + $src = $match['url']; + + if (preg_match('#/courses/[^/]+/document/(.+)$#', $src, $parts)) { + $filename = basename($parts[1]); + return str_replace($src, '@@PLUGINFILE@@/Documents/' . $filename, $match[0]); + } + + return $match[0]; + }, + $html + ); + } + /** * Retrieves the content of the page. */ diff --git a/main/inc/lib/moodleexport/QuizExport.php b/main/inc/lib/moodleexport/QuizExport.php index efa39d41467..7cd6794b6ba 100644 --- a/main/inc/lib/moodleexport/QuizExport.php +++ b/main/inc/lib/moodleexport/QuizExport.php @@ -172,11 +172,13 @@ private function getQuestionsForQuiz(int $quizId): array foreach ($quizResources as $questionId => $questionData) { if (in_array($questionId, $this->course->resources[RESOURCE_QUIZ][$quizId]->obj->question_ids)) { + $categoryId = $questionData->question_category ?? 0; + $categoryId = $categoryId > 0 ? $categoryId : $this->getDefaultCategoryId(); $questions[] = [ 'id' => $questionData->source_id, 'questiontext' => $questionData->question, 'qtype' => $this->mapQuestionType($questionData->quiz_type), - 'questioncategoryid' => $questionData->question_category ?? 0, + 'questioncategoryid' => $categoryId, 'answers' => $this->getAnswersForQuestion($questionData->source_id), 'maxmark' => $questionData->ponderation ?? 1, ]; @@ -246,6 +248,11 @@ private function getFeedbacksForQuiz(int $quizId): array return $feedbacks; } + private function getDefaultCategoryId(): int + { + return 1; + } + /** * Creates the quiz.xml file. */ diff --git a/main/inc/lib/moodleexport/SectionExport.php b/main/inc/lib/moodleexport/SectionExport.php index e87a23262ef..2a83bc4790a 100644 --- a/main/inc/lib/moodleexport/SectionExport.php +++ b/main/inc/lib/moodleexport/SectionExport.php @@ -255,7 +255,7 @@ private function addActivityToList(array $item, int $sectionId, array &$activiti } $itemType = $item['item_type'] === 'link' ? 'url' : - ($item['item_type'] === 'work' ? 'assign' : + ($item['item_type'] === 'work' || $item['item_type'] === 'student_publication' ? 'assign' : ($item['item_type'] === 'survey' ? 'feedback' : $item['item_type'])); switch ($itemType) {