Skip to content

Commit 1a0efdf

Browse files
Merge pull request #6938 from christianbeeznest/GH-6870-4
Course: Improve course import for documents - refs #6870
2 parents a647501 + 05c5c83 commit 1a0efdf

File tree

2 files changed

+330
-95
lines changed

2 files changed

+330
-95
lines changed

src/CoreBundle/Controller/CourseMaintenanceController.php

Lines changed: 158 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,8 @@ public function importResources(int $node, string $backupId, Request $req): Json
138138
$this->setDebugFromRequest($req);
139139
$mode = strtolower((string) $req->query->get('mode', 'auto')); // 'auto' | 'dat' | 'moodle'
140140

141-
// Reutilizas TU loader actual con el nuevo flag
142141
$course = $this->loadLegacyCourseForAnyBackup($backupId, $mode === 'dat' ? 'chamilo' : $mode);
143142

144-
// Lo demás igual
145143
$this->logDebug('[importResources] course loaded', [
146144
'has_resources' => \is_array($course->resources ?? null),
147145
'keys' => array_keys((array) ($course->resources ?? [])),
@@ -178,6 +176,9 @@ public function importRestore(
178176

179177
try {
180178
$payload = json_decode($req->getContent() ?: '{}', true);
179+
// Keep mode consistent with GET /import/{backupId}/resources
180+
$mode = strtolower((string) ($payload['mode'] ?? 'auto'));
181+
181182
$importOption = (string) ($payload['importOption'] ?? 'full_backup');
182183
$sameFileNameOption = (int) ($payload['sameFileNameOption'] ?? 2);
183184

@@ -192,24 +193,28 @@ public function importRestore(
192193
'sameFileNameOption' => $sameFileNameOption,
193194
'selectedTypes' => $selectedTypes,
194195
'hasResourcesMap' => !empty($selectedResources),
196+
'mode' => $mode,
195197
]);
196198

197-
$course = $this->loadLegacyCourseForAnyBackup($backupId);
199+
// Load with same mode to avoid switching source on POST
200+
$course = $this->loadLegacyCourseForAnyBackup($backupId, $mode === 'dat' ? 'chamilo' : $mode);
198201
if (!\is_object($course) || empty($course->resources) || !\is_array($course->resources)) {
199202
return $this->json(['error' => 'Backup has no resources'], 400);
200203
}
201204

202-
$resourcesAll = (array) $course->resources;
205+
$resourcesAll = $course->resources;
203206
$this->logDebug('[importRestore] BEFORE filter keys', array_keys($resourcesAll));
204207

208+
// Always hydrate LP dependencies (even in full_backup).
209+
$this->hydrateLpDependenciesFromSnapshot($course, $resourcesAll);
210+
$this->logDebug('[importRestore] AFTER hydrate keys', array_keys((array) $course->resources));
211+
205212
// Detect source BEFORE any filtering (meta may be dropped by filters)
206213
$importSource = $this->getImportSource($course);
207214
$isMoodle = ('moodle' === $importSource);
208215
$this->logDebug('[importRestore] detected import source', ['import_source' => $importSource, 'isMoodle' => $isMoodle]);
209216

210217
if ('select_items' === $importOption) {
211-
$this->hydrateLpDependenciesFromSnapshot($course, $resourcesAll);
212-
213218
if (empty($selectedResources) && !empty($selectedTypes)) {
214219
$selectedResources = $this->buildSelectionFromTypes($course, $selectedTypes);
215220
}
@@ -218,7 +223,6 @@ public function importRestore(
218223
foreach ($selectedResources as $ids) {
219224
if (\is_array($ids) && !empty($ids)) {
220225
$hasAny = true;
221-
222226
break;
223227
}
224228
}
@@ -237,7 +241,11 @@ public function importRestore(
237241
// NON-MOODLE
238242
if (!$isMoodle) {
239243
$this->logDebug('[importRestore] non-Moodle backup -> using CourseRestorer');
244+
// Trace around normalization to detect bucket drops
245+
$this->logDebug('[importRestore] BEFORE normalize', array_keys((array) $course->resources));
246+
240247
$this->normalizeBucketsForRestorer($course);
248+
$this->logDebug('[importRestore] AFTER normalize', array_keys((array) $course->resources));
241249

242250
$restorer = new CourseRestorer($course);
243251
$restorer->set_file_option($this->mapSameNameOption($sameFileNameOption));
@@ -276,15 +284,16 @@ public function importRestore(
276284
$mark = static function (array &$dst, bool $cond, string $key): void { if ($cond) { $dst[$key] = true; } };
277285

278286
if ('full_backup' === $importOption) {
287+
// Be tolerant with plural 'documents'
279288
$mark($wantedGroups, $present('link') || $present('link_category'), 'links');
280289
$mark($wantedGroups, $present('forum') || $present('forum_category'), 'forums');
281-
$mark($wantedGroups, $present('document'), 'documents');
290+
$mark($wantedGroups, $present('document') || $present('documents'), 'documents');
282291
$mark($wantedGroups, $present('quiz') || $present('exercise'), 'quizzes');
283292
$mark($wantedGroups, $present('scorm'), 'scorm');
284293
} else {
285294
$mark($wantedGroups, $present('link'), 'links');
286295
$mark($wantedGroups, $present('forum') || $present('forum_category'), 'forums');
287-
$mark($wantedGroups, $present('document'), 'documents');
296+
$mark($wantedGroups, $present('document') || $present('documents'), 'documents');
288297
$mark($wantedGroups, $present('quiz') || $present('exercise'), 'quizzes');
289298
$mark($wantedGroups, $present('scorm'), 'scorm');
290299
}
@@ -3288,40 +3297,158 @@ private function loadLegacyCourseForAnyBackup(string $backupId, string $force =
32883297
throw new \RuntimeException('Unsupported package: neither course_info.dat nor moodle_backup.xml found.');
32893298
}
32903299

3300+
/**
3301+
* Normalize resource buckets to the exact keys supported by CourseRestorer.
3302+
* Only the canonical keys below are produced; common aliases are mapped.
3303+
* - Never drop data: merge buckets; keep __meta as-is.
3304+
* - Make sure "document" survives if it existed before.
3305+
*/
32913306
private function normalizeBucketsForRestorer(object $course): void
32923307
{
32933308
if (!isset($course->resources) || !\is_array($course->resources)) {
32943309
return;
32953310
}
32963311

3297-
$map = [
3298-
'link' => RESOURCE_LINK,
3299-
'link_category' => RESOURCE_LINKCATEGORY,
3300-
'forum' => RESOURCE_FORUM,
3301-
'forum_category' => RESOURCE_FORUMCATEGORY,
3302-
'forum_topic' => RESOURCE_FORUMTOPIC,
3303-
'forum_post' => RESOURCE_FORUMPOST,
3304-
'thread' => RESOURCE_FORUMTOPIC,
3305-
'post' => RESOURCE_FORUMPOST,
3306-
'document' => RESOURCE_DOCUMENT,
3307-
'quiz' => RESOURCE_QUIZ,
3308-
'exercise_question' => RESOURCE_QUIZQUESTION,
3309-
'survey' => RESOURCE_SURVEY,
3310-
'survey_question' => RESOURCE_SURVEYQUESTION,
3311-
'tool_intro' => RESOURCE_TOOL_INTRO,
3312+
// Canonical keys -> constants used by the restorer (reference only)
3313+
$allowed = [
3314+
'link' => \defined('RESOURCE_LINK') ? RESOURCE_LINK : null,
3315+
'link_category' => \defined('RESOURCE_LINKCATEGORY') ? RESOURCE_LINKCATEGORY : null,
3316+
'forum' => \defined('RESOURCE_FORUM') ? RESOURCE_FORUM : null,
3317+
'forum_category' => \defined('RESOURCE_FORUMCATEGORY') ? RESOURCE_FORUMCATEGORY : null,
3318+
'forum_topic' => \defined('RESOURCE_FORUMTOPIC') ? RESOURCE_FORUMTOPIC : null,
3319+
'forum_post' => \defined('RESOURCE_FORUMPOST') ? RESOURCE_FORUMPOST : null,
3320+
'document' => \defined('RESOURCE_DOCUMENT') ? RESOURCE_DOCUMENT : null,
3321+
'quiz' => \defined('RESOURCE_QUIZ') ? RESOURCE_QUIZ : null,
3322+
'exercise_question' => \defined('RESOURCE_QUIZQUESTION') ? RESOURCE_QUIZQUESTION : null,
3323+
'survey' => \defined('RESOURCE_SURVEY') ? RESOURCE_SURVEY : null,
3324+
'survey_question' => \defined('RESOURCE_SURVEYQUESTION') ? RESOURCE_SURVEYQUESTION : null,
3325+
'tool_intro' => \defined('RESOURCE_TOOL_INTRO') ? RESOURCE_TOOL_INTRO : null,
33123326
];
33133327

3314-
$res = $course->resources;
3315-
foreach ($map as $from => $to) {
3316-
if (isset($res[$from]) && \is_array($res[$from])) {
3317-
if (!isset($res[$to])) {
3318-
$res[$to] = $res[$from];
3328+
// Minimal, well-scoped alias map (input -> canonical)
3329+
$alias = [
3330+
// docs
3331+
'documents' => 'document',
3332+
'document' => 'document',
3333+
'document ' => 'document',
3334+
'Document' => 'document',
3335+
3336+
// tool intro
3337+
'tool introduction' => 'tool_intro',
3338+
'tool_introduction' => 'tool_intro',
3339+
'tool/introduction' => 'tool_intro',
3340+
'tool intro' => 'tool_intro',
3341+
'Tool introduction' => 'tool_intro',
3342+
3343+
// forums
3344+
'forum' => 'forum',
3345+
'forums' => 'forum',
3346+
'forum_category' => 'forum_category',
3347+
'Forum_Category' => 'forum_category',
3348+
'forumcategory' => 'forum_category',
3349+
'forum_topic' => 'forum_topic',
3350+
'forumtopic' => 'forum_topic',
3351+
'thread' => 'forum_topic',
3352+
'forum_post' => 'forum_post',
3353+
'forumpost' => 'forum_post',
3354+
'post' => 'forum_post',
3355+
3356+
// links
3357+
'link' => 'link',
3358+
'links' => 'link',
3359+
'link_category' => 'link_category',
3360+
'link category' => 'link_category',
3361+
3362+
// quiz + questions
3363+
'quiz' => 'quiz',
3364+
'exercise_question' => 'exercise_question',
3365+
'Exercise_Question' => 'exercise_question',
3366+
'exercisequestion' => 'exercise_question',
3367+
3368+
// surveys
3369+
'survey' => 'survey',
3370+
'surveys' => 'survey',
3371+
'survey_question' => 'survey_question',
3372+
'surveyquestion' => 'survey_question',
3373+
];
3374+
3375+
$before = $course->resources;
3376+
3377+
// Keep meta buckets verbatim
3378+
$meta = [];
3379+
foreach ($before as $k => $v) {
3380+
if (\is_string($k) && str_starts_with($k, '__')) {
3381+
$meta[$k] = $v;
3382+
unset($before[$k]);
3383+
}
3384+
}
3385+
3386+
$hadDocument =
3387+
isset($before['document']) ||
3388+
isset($before['Document']) ||
3389+
isset($before['documents']);
3390+
3391+
// Merge helper (preserve numeric/string ids)
3392+
$merge = static function (array $dst, array $src): array {
3393+
foreach ($src as $id => $obj) {
3394+
if (!array_key_exists($id, $dst)) {
3395+
$dst[$id] = $obj;
33193396
}
3320-
unset($res[$from]);
3397+
}
3398+
return $dst;
3399+
};
3400+
3401+
$out = [];
3402+
3403+
foreach ($before as $rawKey => $bucket) {
3404+
if (!\is_array($bucket)) {
3405+
// Unexpected shape; skip silently (defensive)
3406+
continue;
3407+
}
3408+
3409+
// Normalize key shape first
3410+
$k = (string) $rawKey;
3411+
$norm = strtolower(trim($k));
3412+
$norm = strtr($norm, ['\\' => '/', '-' => '_']); // cheap normalization
3413+
// Map via alias table if present
3414+
$canon = $alias[$norm] ?? $alias[str_replace('/', '_', $norm)] ?? null;
3415+
3416+
// If still unknown, try a sane guess: underscores + lowercase
3417+
if (null === $canon) {
3418+
$guess = str_replace(['/', ' '], '_', $norm);
3419+
$canon = \array_key_exists($guess, $allowed) ? $guess : null;
3420+
}
3421+
3422+
// Only produce buckets with canonical keys we support; unknown keys are ignored here
3423+
if (null !== $canon && \array_key_exists($canon, $allowed)) {
3424+
$out[$canon] = isset($out[$canon]) ? $merge($out[$canon], $bucket) : $bucket;
33213425
}
33223426
}
33233427

3324-
$course->resources = $res;
3428+
// Hard safety net: if a "document" bucket existed before, ensure it remains.
3429+
if ($hadDocument && !isset($out['document'])) {
3430+
$out['document'] = (array) ($before['document'] ?? $before['Document'] ?? $before['documents'] ?? []);
3431+
}
3432+
3433+
// Gentle ordering to keep things readable
3434+
$order = [
3435+
'announcement', 'document', 'link', 'link_category',
3436+
'forum', 'forum_category', 'forum_topic', 'forum_post',
3437+
'quiz', 'exercise_question',
3438+
'survey', 'survey_question',
3439+
'learnpath', 'tool_intro',
3440+
'work',
3441+
];
3442+
$w = [];
3443+
foreach ($order as $i => $key) { $w[$key] = $i; }
3444+
uksort($out, static function ($a, $b) use ($w) {
3445+
$wa = $w[$a] ?? 9999;
3446+
$wb = $w[$b] ?? 9999;
3447+
return $wa <=> $wb ?: strcasecmp($a, $b);
3448+
});
3449+
3450+
// Final assign (meta first, then normalized buckets)
3451+
$course->resources = $meta + $out;
33253452
}
33263453

33273454
/**

0 commit comments

Comments
 (0)