@@ -676,6 +676,7 @@ public function moodleExportOptions(int $node, Request $req, UserRepository $use
676676 ['value ' => 'glossary ' , 'label ' => 'Glossary ' ],
677677 ['value ' => 'learnpaths ' , 'label ' => 'Paths learning ' ],
678678 ['value ' => 'tool_intro ' , 'label ' => 'Course Introduction ' ],
679+ ['value ' => 'course_description ' , 'label ' => 'Course descriptions ' ],
679680 ];
680681
681682 $ defaults ['tools ' ] = array_column ($ tools , 'value ' );
@@ -709,6 +710,7 @@ public function moodleExportResources(int $node, Request $req): JsonResponse
709710 'learnpaths ' , 'learnpath_category ' ,
710711 'works ' , 'glossary ' ,
711712 'tool_intro ' ,
713+ 'course_descriptions ' ,
712714 ];
713715
714716 // Use client tools if provided; otherwise our Moodle-safe defaults
@@ -774,14 +776,14 @@ public function moodleExportExecute(int $node, Request $req, UserRepository $use
774776 {
775777 $ this ->setDebugFromRequest ($ req );
776778
777- $ p = json_decode ($ req ->getContent () ?: '{} ' , true );
778- $ moodleVersion = (string ) ($ p ['moodleVersion ' ] ?? '4 ' );
779- $ scope = (string ) ($ p ['scope ' ] ?? 'full ' );
780- $ adminId = (int ) ($ p ['adminId ' ] ?? 0 );
781- $ adminLogin = trim ((string ) ($ p ['adminLogin ' ] ?? '' ));
782- $ adminEmail = trim ((string ) ($ p ['adminEmail ' ] ?? '' ));
783- $ selected = ( array ) ( $ p ['resources ' ] ?? []) ;
784- $ toolsInput = ( array ) ( $ p ['tools ' ] ?? []) ;
779+ $ p = json_decode ($ req ->getContent () ?: '{} ' , true ) ?: [] ;
780+ $ moodleVersion = (string ) ($ p ['moodleVersion ' ] ?? '4 ' ); // "3" | "4"
781+ $ scope = (string ) ($ p ['scope ' ] ?? 'full ' ); // "full" | "selected"
782+ $ adminId = (int ) ($ p ['adminId ' ] ?? 0 );
783+ $ adminLogin = trim ((string ) ($ p ['adminLogin ' ] ?? '' ));
784+ $ adminEmail = trim ((string ) ($ p ['adminEmail ' ] ?? '' ));
785+ $ selected = is_array ( $ p [ ' resources ' ] ?? null ) ? ( array ) $ p ['resources ' ] : [];
786+ $ toolsInput = is_array ( $ p [ ' tools ' ] ?? null ) ? ( array ) $ p ['tools ' ] : [];
785787
786788 if (!\in_array ($ moodleVersion , ['3 ' , '4 ' ], true )) {
787789 return $ this ->json (['error ' => 'Unsupported Moodle version ' ], 400 );
@@ -790,7 +792,15 @@ public function moodleExportExecute(int $node, Request $req, UserRepository $use
790792 return $ this ->json (['error ' => 'No resources selected ' ], 400 );
791793 }
792794
793- // Normalize tools from client (adds implied deps)
795+ $ defaultTools = [
796+ 'documents ' , 'links ' , 'forums ' ,
797+ 'quizzes ' , 'quiz_questions ' ,
798+ 'surveys ' , 'survey_questions ' ,
799+ 'learnpaths ' , 'learnpath_category ' ,
800+ 'works ' , 'glossary ' ,
801+ 'course_descriptions ' ,
802+ ];
803+
794804 $ tools = $ this ->normalizeSelectedTools ($ toolsInput );
795805
796806 // If scope=selected, merge inferred tools from selection
@@ -799,64 +809,86 @@ public function moodleExportExecute(int $node, Request $req, UserRepository $use
799809 $ tools = $ this ->normalizeSelectedTools (array_merge ($ tools , $ inferred ));
800810 }
801811
812+ // Remove unsupported tools
802813 $ tools = array_values (array_unique (array_diff ($ tools , ['gradebook ' ])));
803- if (!in_array ('tool_intro ' , $ tools , true )) {
804- $ tools [] = 'tool_intro ' ;
814+ $ clientSentNoTools = empty ($ toolsInput );
815+ $ useDefault = ($ scope === 'full ' && $ clientSentNoTools );
816+ $ toolsToBuild = $ useDefault ? $ defaultTools : $ tools ;
817+
818+ // Ensure "tool_intro" is present (append only if missing)
819+ if (!in_array ('tool_intro ' , $ toolsToBuild , true )) {
820+ $ toolsToBuild [] = 'tool_intro ' ;
805821 }
806822
807- if ($ adminId <= 0 || '' === $ adminLogin || '' === $ adminEmail ) {
808- $ adm = $ users ->getDefaultAdminForExport ();
809- $ adminId = $ adminId > 0 ? $ adminId : (int ) ($ adm ['id ' ] ?? 1 );
810- $ adminLogin = '' !== $ adminLogin ? $ adminLogin : (string ) ($ adm ['username ' ] ?? 'admin ' );
811- $ adminEmail = '' !== $ adminEmail ? $ adminEmail : (string ) ($ adm ['email ' ] ?? 'admin@example.com ' );
823+ // Final dedupe/normalize
824+ $ toolsToBuild = array_values (array_unique ($ toolsToBuild ));
825+
826+ $ this ->logDebug ('[moodleExportExecute] course tools to build (final) ' , $ toolsToBuild );
827+
828+ if ($ adminId <= 0 || $ adminLogin === '' || $ adminEmail === '' ) {
829+ $ adm = $ users ->getDefaultAdminForExport ();
830+ $ adminId = $ adminId > 0 ? $ adminId : (int ) ($ adm ['id ' ] ?? 1 );
831+ $ adminLogin = $ adminLogin !== '' ? $ adminLogin : (string ) ($ adm ['username ' ] ?? 'admin ' );
832+ $ adminEmail = $ adminEmail !== '' ? $ adminEmail : (string ) ($ adm ['email ' ] ?? 'admin@example.com ' );
812833 }
813834
814- $ this ->logDebug ('[moodleExportExecute] tools for CourseBuilder ' , $ tools );
835+ $ courseId = api_get_course_id ();
836+ if (empty ($ courseId )) {
837+ return $ this ->json (['error ' => 'No active course context ' ], 400 );
838+ }
815839
816- // Build legacy Course from CURRENT course
817840 $ cb = new CourseBuilder ();
818- $ cb ->set_tools_to_build (!empty ($ tools ) ? $ tools : [
819- // Fallback should mirror the Moodle-safe list used in the picker
820- 'documents ' , 'links ' , 'forums ' ,
821- 'quizzes ' , 'quiz_questions ' ,
822- 'surveys ' , 'survey_questions ' ,
823- 'learnpaths ' , 'learnpath_category ' ,
824- 'works ' , 'glossary ' ,
825- 'tool_intro ' ,
826- ]);
827- $ course = $ cb ->build (0 , api_get_course_id ());
841+ $ cb ->set_tools_to_build ($ toolsToBuild );
842+ $ course = $ cb ->build (0 , $ courseId );
828843
829- // IMPORTANT: when scope === 'selected', use the same robust selection filter as copy-course
830844 if ('selected ' === $ scope ) {
831- // This method trims buckets to only selected items and pulls needed deps (LP/quiz/survey)
832845 $ course = $ this ->filterLegacyCourseBySelection ($ course , $ selected );
833- // Safety guard: fail if nothing remains after filtering
834846 if (empty ($ course ->resources ) || !\is_array ($ course ->resources )) {
835847 return $ this ->json (['error ' => 'Selection produced no resources to export ' ], 400 );
836848 }
837849 }
838850
839851 try {
840- // Pass selection flag to exporter so it does NOT re-hydrate from a complete snapshot.
841- $ selectionMode = (' selected ' === $ scope );
852+ // === Export to Moodle MBZ ===
853+ $ selectionMode = ($ scope === ' selected ' );
842854 $ exporter = new MoodleExport ($ course , $ selectionMode );
843855 $ exporter ->setAdminUserData ($ adminId , $ adminLogin , $ adminEmail );
844856
845- $ courseId = api_get_course_id ();
846- $ exportDir = 'moodle_export_ ' .date ('Ymd_His ' );
847- $ versionNum = ('3 ' === $ moodleVersion ) ? 3 : 4 ;
857+ $ exportDir = 'moodle_export_ ' . date ('Ymd_His ' );
858+ $ versionNum = ($ moodleVersion === '3 ' ) ? 3 : 4 ;
859+
860+ $ this ->logDebug ('[moodleExportExecute] starting exporter ' , [
861+ 'courseId ' => $ courseId ,
862+ 'exportDir ' => $ exportDir ,
863+ 'versionNum ' => $ versionNum ,
864+ 'selection ' => $ selectionMode ,
865+ 'scope ' => $ scope ,
866+ ]);
848867
849868 $ mbzPath = $ exporter ->export ($ courseId , $ exportDir , $ versionNum );
850869
870+ if (!\is_string ($ mbzPath ) || $ mbzPath === '' || !is_file ($ mbzPath )) {
871+ return $ this ->json (['error ' => 'Moodle export failed: artifact not found ' ], 500 );
872+ }
873+
874+ // Build download response
851875 $ resp = new BinaryFileResponse ($ mbzPath );
852876 $ resp ->setContentDisposition (
853877 ResponseHeaderBag::DISPOSITION_ATTACHMENT ,
854878 basename ($ mbzPath )
855879 );
880+ $ resp ->headers ->set ('X-Moodle-Version ' , (string ) $ versionNum );
881+ $ resp ->headers ->set ('X-Export-Scope ' , $ scope );
882+ $ resp ->headers ->set ('X-Selection-Mode ' , $ selectionMode ? '1 ' : '0 ' );
856883
857884 return $ resp ;
858- } catch (Throwable $ e ) {
859- return $ this ->json (['error ' => 'Moodle export failed: ' .$ e ->getMessage ()], 500 );
885+ } catch (\Throwable $ e ) {
886+ $ this ->logDebug ('[moodleExportExecute] exception ' , [
887+ 'message ' => $ e ->getMessage (),
888+ 'code ' => (int ) $ e ->getCode (),
889+ ]);
890+
891+ return $ this ->json (['error ' => 'Moodle export failed: ' . $ e ->getMessage ()], 500 );
860892 }
861893 }
862894
@@ -3145,7 +3177,7 @@ private function filterCourseResources(object $course, array $selected): void
31453177 'survey_questions ' => RESOURCE_SURVEYQUESTION ,
31463178 'announcements ' => RESOURCE_ANNOUNCEMENT ,
31473179 'events ' => RESOURCE_EVENT ,
3148- 'course_descriptions ' => RESOURCE_COURSEDESCRIPTION ,
3180+ 'course_description ' => RESOURCE_COURSEDESCRIPTION ,
31493181 'glossary ' => RESOURCE_GLOSSARY ,
31503182 'wiki ' => RESOURCE_WIKI ,
31513183 'thematic ' => RESOURCE_THEMATIC ,
@@ -3608,6 +3640,7 @@ private function inferToolsFromSelection(array $selected): array
36083640 if ($ has ('work ' )) { $ want [] = 'works ' ; }
36093641 if ($ has ('glossary ' )) { $ want [] = 'glossary ' ; }
36103642 if ($ has ('tool_intro ' )) { $ want [] = 'tool_intro ' ; }
3643+ if ($ has ('course_descriptions ' ) || $ has ('course_description ' )) { $ tools [] = 'course_descriptions ' ; }
36113644
36123645 // Dedup
36133646 return array_values (array_unique (array_filter ($ want )));
0 commit comments