diff --git a/main/coursecopy/export_moodle.php b/main/coursecopy/export_moodle.php index 47dbbac368c..50b632f2675 100644 --- a/main/coursecopy/export_moodle.php +++ b/main/coursecopy/export_moodle.php @@ -51,6 +51,7 @@ // Rebuild the course object based on selected resources $cb = new CourseBuilder('partial'); $course = $cb->build(0, null, false, array_keys($selectedResources), $selectedResources); + $course = CourseSelectForm::get_posted_course(null, 0, '', $course); // Get admin details $adminId = (int) $_POST['admin_id']; diff --git a/main/inc/lib/moodleexport/CourseExport.php b/main/inc/lib/moodleexport/CourseExport.php index 0870297ecec..4ea86429240 100644 --- a/main/inc/lib/moodleexport/CourseExport.php +++ b/main/inc/lib/moodleexport/CourseExport.php @@ -17,7 +17,7 @@ class CourseExport private $courseInfo; private $activities; - public function __construct($course, $activities) + public function __construct($course, $activities = []) { $this->course = $course; $this->courseInfo = api_get_course_info($course->code); diff --git a/main/inc/lib/moodleexport/FileExport.php b/main/inc/lib/moodleexport/FileExport.php index e6d2d4eed3b..fa9ccab5bbb 100644 --- a/main/inc/lib/moodleexport/FileExport.php +++ b/main/inc/lib/moodleexport/FileExport.php @@ -78,7 +78,7 @@ public function getFilesData(): array 'component' => 'mod_assign', 'filearea' => 'introattachment', 'itemid' => (int) $work->params['id'], - 'filepath' => '/', + 'filepath' => '/Documents/', 'documentpath' => 'document/'.$docData['path'], 'filename' => basename($docData['path']), 'userid' => $adminId, @@ -187,11 +187,15 @@ private function createFileXmlEntry(array $file): string private function processDocument(array $filesData, object $document): array { if ($document->file_type === 'file') { - $filesData['files'][] = $this->getFileData($document); + $fileData = $this->getFileData($document); + $fileData['filepath'] = '/Documents/'; + $fileData['contextid'] = 0; + $fileData['component'] = 'mod_folder'; + $filesData['files'][] = $fileData; } elseif ($document->file_type === 'folder') { $folderFiles = \DocumentManager::getAllDocumentsByParentId($this->course->info, $document->source_id); foreach ($folderFiles as $file) { - $filesData['files'][] = $this->getFolderFileData($file, (int) $document->source_id); + $filesData['files'][] = $this->getFolderFileData($file, (int) $document->source_id, '/Documents/'.dirname($file['path']).'/'); } } @@ -233,14 +237,14 @@ private function getFileData(object $document): array /** * Get file data for files inside a folder. */ - private function getFolderFileData(array $file, int $sourceId): array + private function getFolderFileData(array $file, int $sourceId, string $parentPath = '/Documents/'): array { $adminData = MoodleExport::getAdminUserData(); $adminId = $adminData['id']; $contenthash = hash('sha1', basename($file['path'])); $mimetype = $this->getMimeType($file['path']); $filename = basename($file['path']); - $filepath = $this->ensureTrailingSlash(dirname($file['path'])); + $filepath = $this->ensureTrailingSlash($parentPath); return [ 'id' => $file['id'], @@ -267,9 +271,15 @@ private function getFolderFileData(array $file, int $sourceId): array /** * Ensure the directory path has a trailing slash. */ - private function ensureTrailingSlash($path): string + private function ensureTrailingSlash(string $path): string { - return empty($path) || $path === '.' || $path === '/' ? '/' : rtrim($path, '/').'/'; + if (empty($path) || $path === '.' || $path === '/') { + return '/'; + } + + $path = preg_replace('/\/+/', '/', $path); + + return rtrim($path, '/') . '/'; } /** diff --git a/main/inc/lib/moodleexport/FolderExport.php b/main/inc/lib/moodleexport/FolderExport.php index 58b3ba7f0b1..017f272a65b 100644 --- a/main/inc/lib/moodleexport/FolderExport.php +++ b/main/inc/lib/moodleexport/FolderExport.php @@ -44,26 +44,32 @@ public function export($activityId, $exportDir, $moduleId, $sectionId): void */ public function getData(int $folderId, int $sectionId): ?array { - $folder = $this->course->resources['document'][$folderId]; - - $folderPath = $folder->path.'/'; - foreach ($this->course->resources['document'] as $resource) { - if ($resource->path !== $folder->path && str_starts_with($resource->path, $folderPath)) { - return [ - 'id' => $folderId, - 'moduleid' => $folder->source_id, - 'modulename' => 'folder', - 'contextid' => $folder->source_id, - 'name' => $folder->title, - 'sectionid' => $sectionId, - 'timemodified' => time(), - ]; - } + if ($folderId === 0) { + return [ + 'id' => 0, + 'moduleid' => 0, + 'modulename' => 'folder', + 'contextid' => 0, + 'name' => 'Documents', + 'sectionid' => $sectionId, + 'timemodified' => time(), + ]; } - return null; + $folder = $this->course->resources['document'][$folderId]; + + return [ + 'id' => $folderId, + 'moduleid' => $folder->source_id, + 'modulename' => 'folder', + 'contextid' => $folder->source_id, + 'name' => $folder->title, + 'sectionid' => $sectionId, + 'timemodified' => time(), + ]; } + /** * Create the XML file for the folder. */ @@ -92,19 +98,19 @@ private function createFolderXml(array $folderData, string $folderDir): void */ private function getFilesForFolder(int $folderId): array { - $documentData = \DocumentManager::getAllDocumentsByParentId($this->course->info, $folderId); - $files = []; - foreach ($documentData as $doc) { - if ($doc['filetype'] === 'file') { - $files[] = [ - 'id' => (int) $doc['id'], - 'contenthash' => 'hash'.$doc['id'], - 'filename' => $doc['basename'], - 'filepath' => $doc['path'], - 'filesize' => (int) $doc['size'], - 'mimetype' => $this->getMimeType($doc['basename']), - ]; + if ($folderId === 0) { + foreach ($this->course->resources[RESOURCE_DOCUMENT] as $doc) { + if ($doc->file_type === 'file') { + $files[] = [ + 'id' => (int) $doc->source_id, + 'contenthash' => hash('sha1', basename($doc->path)), + 'filename' => basename($doc->path), + 'filepath' => '/Documents/', + 'filesize' => (int) $doc->size, + 'mimetype' => $this->getMimeType($doc->path), + ]; + } } } diff --git a/main/inc/lib/moodleexport/MoodleExport.php b/main/inc/lib/moodleexport/MoodleExport.php index 71cd0ce9355..eff2bcbef6b 100644 --- a/main/inc/lib/moodleexport/MoodleExport.php +++ b/main/inc/lib/moodleexport/MoodleExport.php @@ -4,6 +4,7 @@ namespace moodleexport; +use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder; use Exception; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; @@ -25,7 +26,18 @@ class MoodleExport */ public function __construct(object $course) { + // Build the complete course object + $cb = new CourseBuilder('complete'); + $complete = $cb->build(); + + // Store the selected course $this->course = $course; + + // Fill missing resources from learnpath + $this->fillResourcesFromLearnpath($complete); + + // Fill missing quiz questions + $this->fillQuestionsFromQuiz($complete); } /** @@ -177,6 +189,67 @@ public static function getAdminUserData(): array return self::$adminUserData; } + /** + * Fills missing resources from the learnpath into the course structure. + * + * This method checks if the course has a learnpath and ensures that all + * referenced resources (documents, quizzes, etc.) exist in the course's + * resources array by pulling them from the complete course object. + */ + private function fillResourcesFromLearnpath(object $complete): void + { + // Check if the course has learnpath + if (!isset($this->course->resources['learnpath'])) { + return; + } + + foreach ($this->course->resources['learnpath'] as $learnpathId => $learnpath) { + if (!isset($learnpath->items)) { + continue; + } + + foreach ($learnpath->items as $item) { + $type = $item['item_type']; // Resource type (document, quiz, etc.) + $resourceId = $item['path']; // Resource ID in resources + + // Check if the resource exists in the complete object and is not yet in the course resources + if (isset($complete->resources[$type][$resourceId]) && !isset($this->course->resources[$type][$resourceId])) { + // Add the resource directly to the original course resources structure + $this->course->resources[$type][$resourceId] = $complete->resources[$type][$resourceId]; + } + } + } + } + + /** + * Fills missing exercise questions related to quizzes in the course. + * + * This method checks if the course has quizzes and ensures that all referenced + * questions exist in the course's resources array by pulling them from the complete + * course object. + */ + private function fillQuestionsFromQuiz(object $complete): void + { + // Check if the course has quizzes + if (!isset($this->course->resources['quiz'])) { + return; + } + + foreach ($this->course->resources['quiz'] as $quizId => $quiz) { + if (!isset($quiz->obj->question_ids)) { + continue; + } + + foreach ($quiz->obj->question_ids as $questionId) { + // Check if the question exists in the complete object and is not yet in the course resources + if (isset($complete->resources['Exercise_Question'][$questionId]) && !isset($this->course->resources['Exercise_Question'][$questionId])) { + // Add the question directly to the original course resources structure + $this->course->resources['Exercise_Question'][$questionId] = $complete->resources['Exercise_Question'][$questionId]; + } + } + } + } + /** * Export root XML files such as badges, completion, gradebook, etc. */ @@ -359,6 +432,14 @@ private function getActivities(): array $activities = []; $glossaryAdded = false; + $documentsFolder = [ + 'id' => 0, + 'sectionid' => 0, + 'modulename' => 'folder', + 'moduleid' => 0, + 'title' => 'Documents', + ]; + $activities[] = $documentsFolder; foreach ($this->course->resources as $resourceType => $resources) { foreach ($resources as $resource) { $exportClass = null; @@ -403,36 +484,27 @@ private function getActivities(): array $moduleName = 'page'; $id = $resource->source_id; $title = $document['title']; - } elseif ('file' === $resource->file_type) { - $isRoot = substr_count($resource->path, '/') === 1; - - if ($isRoot) { - $exportClass = ResourceExport::class; - $moduleName = 'resource'; - $id = $resource->source_id; - $title = $resource->title; - } - } elseif ('folder' === $resource->file_type) { - $isEmpty = true; - $folderPath = $resource->path.'/'; - - foreach ($this->course->resources['document'] as $childResource) { - if (str_starts_with($childResource->path, $folderPath) && $childResource->path !== $resource->path) { - $isEmpty = false; - break; + } + if ('file' === $resource->file_type) { + $resourceExport = new ResourceExport($this->course); + if ($resourceExport->getSectionIdForActivity($resource->source_id, $resourceType) > 0) { + $isRoot = substr_count($resource->path, '/') === 1; + if ($isRoot) { + $exportClass = ResourceExport::class; + $moduleName = 'resource'; + $id = $resource->source_id; + $title = $resource->title; } } - - $isRoot = substr_count($resource->path, '/') === 1; - - if (!$isEmpty && $isRoot) { - $exportClass = FolderExport::class; - $moduleName = 'folder'; - $id = $resource->source_id; - $title = $resource->title; - } } } + // Handle course introduction (page) + elseif ($resourceType === RESOURCE_TOOL_INTRO && $resource->source_id == 'course_homepage') { + $exportClass = PageExport::class; + $moduleName = 'page'; + $id = 0; + $title = get_lang('Introduction'); + } // Handle assignments (work) elseif ($resourceType === RESOURCE_WORK && $resource->source_id > 0) { $exportClass = AssignExport::class; diff --git a/main/inc/lib/moodleexport/PageExport.php b/main/inc/lib/moodleexport/PageExport.php index 18d4bec6eb4..45f4beb2e81 100644 --- a/main/inc/lib/moodleexport/PageExport.php +++ b/main/inc/lib/moodleexport/PageExport.php @@ -44,12 +44,35 @@ public function export($activityId, $exportDir, $moduleId, $sectionId): void */ public function getData(int $pageId, int $sectionId): ?array { - $pageResources = $this->course->resources[RESOURCE_DOCUMENT]; + $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) + ) { + return [ + 'id' => 0, + 'moduleid' => 0, + 'modulename' => 'page', + 'contextid' => $contextid, + 'name' => get_lang('Introduction'), + 'intro' => '', + 'content' => trim($this->course->resources[RESOURCE_TOOL_INTRO]['course_homepage']->intro_text), + 'sectionid' => $sectionId, + 'sectionnumber' => 1, + 'display' => 0, + 'timemodified' => time(), + 'users' => [], + 'files' => [], + ]; + } + } + + $pageResources = $this->course->resources[RESOURCE_DOCUMENT] ?? []; foreach ($pageResources as $page) { if ($page->source_id == $pageId) { - $contextid = $this->course->info['real_id']; - return [ 'id' => $page->source_id, 'moduleid' => $page->source_id, diff --git a/main/inc/lib/moodleexport/SectionExport.php b/main/inc/lib/moodleexport/SectionExport.php index f8cf2bc1323..482ab263e64 100644 --- a/main/inc/lib/moodleexport/SectionExport.php +++ b/main/inc/lib/moodleexport/SectionExport.php @@ -77,6 +77,7 @@ public function getGeneralItems(): array RESOURCE_WORK => 'source_id', RESOURCE_FORUM => 'source_id', RESOURCE_SURVEY => 'source_id', + RESOURCE_TOOL_INTRO => 'source_id', ]; foreach ($resourceTypes as $resourceType => $idKey) { @@ -110,7 +111,19 @@ public function getActivitiesForGeneral(): array 'source_id' => 0, ]; - return $this->getActivitiesForSection($generalLearnpath, true); + $activities = $this->getActivitiesForSection($generalLearnpath, true); + + if (!in_array('folder', array_column($activities, 'modulename'))) { + $activities[] = [ + 'id' => 0, + 'moduleid' => 0, + 'modulename' => 'folder', + 'name' => 'Documents', + 'sectionid' => 0, + ]; + } + + return $activities; } /** @@ -212,6 +225,18 @@ private function isItemInLearnpath(object $item, string $type): bool */ private function addActivityToList(array $item, int $sectionId, array &$activities): void { + static $documentsFolderAdded = false; + if (!$documentsFolderAdded && $sectionId === 0) { + $activities[] = [ + 'id' => 0, + 'moduleid' => 0, + 'type' => 'folder', + 'modulename' => 'folder', + 'name' => 'Documents', + ]; + $documentsFolderAdded = true; + } + $activityData = null; $activityClassMap = [ 'quiz' => QuizExport::class, @@ -221,11 +246,17 @@ private function addActivityToList(array $item, int $sectionId, array &$activiti 'forum' => ForumExport::class, 'page' => PageExport::class, 'resource' => ResourceExport::class, - 'folder' => FolderExport::class, 'feedback' => FeedbackExport::class, ]; - $itemType = $item['item_type'] === 'link' ? 'url' : ($item['item_type'] === 'work' ? 'assign' : ($item['item_type'] === 'survey' ? 'feedback' : $item['item_type'])); + if ($item['id'] == 'course_homepage') { + $item['item_type'] = 'page'; + $item['path'] = 0; + } + + $itemType = $item['item_type'] === 'link' ? 'url' : + ($item['item_type'] === 'work' ? 'assign' : + ($item['item_type'] === 'survey' ? 'feedback' : $item['item_type'])); switch ($itemType) { case 'quiz': @@ -234,6 +265,7 @@ private function addActivityToList(array $item, int $sectionId, array &$activiti case 'url': case 'forum': case 'feedback': + case 'page': $activityId = $itemType === 'glossary' ? 1 : (int) $item['path']; $exportClass = $activityClassMap[$itemType]; $exportInstance = new $exportClass($this->course); @@ -241,17 +273,19 @@ private function addActivityToList(array $item, int $sectionId, array &$activiti break; case 'document': - $documentId = (int) $item['path']; - $document = \DocumentManager::get_document_data_by_id($documentId, $this->course->code); - - // Determine the type of document and get the corresponding export class - $documentType = $this->getDocumentType($document['filetype'], $document['path']); - if ($documentType) { - $activityClass = $activityClassMap[$documentType]; - $exportInstance = new $activityClass($this->course); - $activityData = $exportInstance->getData($item['path'], $sectionId); + if ($sectionId > 0) { + $documentId = (int)$item['path']; + $document = \DocumentManager::get_document_data_by_id($documentId, $this->course->code); + + // Determine the type of document and get the corresponding export class + $documentType = $this->getDocumentType($document['filetype'], $document['path']); + if ($documentType) { + $activityClass = $activityClassMap[$documentType]; + $exportInstance = new $activityClass($this->course); + $activityData = $exportInstance->getData($item['path'], $sectionId); + } + break; } - break; } // Add the activity to the list if the data exists @@ -275,9 +309,9 @@ private function getDocumentType(string $filetype, string $path): ?string return 'page'; } elseif ('file' === $filetype) { return 'resource'; - } elseif ('folder' === $filetype) { + } /*elseif ('folder' === $filetype) { return 'folder'; - } + }*/ return null; }