Skip to content

Commit d535668

Browse files
Merge pull request #6939 from christianbeeznest/GH-6870-5
Course: Fix tool intro restore - refs #6870
2 parents 1a0efdf + 0ab8af8 commit d535668

File tree

2 files changed

+143
-128
lines changed

2 files changed

+143
-128
lines changed

src/CoreBundle/Controller/CourseMaintenanceController.php

Lines changed: 79 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -3309,29 +3309,35 @@ private function normalizeBucketsForRestorer(object $course): void
33093309
return;
33103310
}
33113311

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,
3326-
];
3312+
// Split meta buckets
3313+
$all = $course->resources;
3314+
$meta = [];
3315+
foreach ($all as $k => $v) {
3316+
if (\is_string($k) && str_starts_with($k, '__')) {
3317+
$meta[$k] = $v;
3318+
unset($all[$k]);
3319+
}
3320+
}
33273321

3328-
// Minimal, well-scoped alias map (input -> canonical)
3329-
$alias = [
3330-
// docs
3331-
'documents' => 'document',
3332-
'document' => 'document',
3333-
'document ' => 'document',
3334-
'Document' => 'document',
3322+
// Start from current
3323+
$out = $all;
3324+
3325+
// merge array buckets preserving numeric/string ids
3326+
$merge = static function (array $dst, array $src): array {
3327+
foreach ($src as $id => $obj) {
3328+
if (!\array_key_exists($id, $dst)) {
3329+
$dst[$id] = $obj;
3330+
}
3331+
}
3332+
return $dst;
3333+
};
3334+
3335+
// safe alias map (input -> canonical). Extend only if needed.
3336+
$aliases = [
3337+
// documents
3338+
'documents' => 'document',
3339+
'Document' => 'document',
3340+
'document ' => 'document',
33353341

33363342
// tool intro
33373343
'tool introduction' => 'tool_intro',
@@ -3341,96 +3347,70 @@ private function normalizeBucketsForRestorer(object $course): void
33413347
'Tool introduction' => 'tool_intro',
33423348

33433349
// 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',
3350+
'forums' => 'forum',
3351+
'Forum' => 'forum',
3352+
'Forum_Category' => 'forum_category',
3353+
'forumcategory' => 'forum_category',
3354+
'thread' => 'forum_topic',
3355+
'Thread' => 'forum_topic',
3356+
'forumtopic' => 'forum_topic',
3357+
'post' => 'forum_post',
3358+
'Post' => 'forum_post',
3359+
'forumpost' => 'forum_post',
33553360

33563361
// links
3357-
'link' => 'link',
3358-
'links' => 'link',
3359-
'link_category' => 'link_category',
3360-
'link category' => 'link_category',
3362+
'links' => 'link',
3363+
'link category' => 'link_category',
33613364

33623365
// quiz + questions
3363-
'quiz' => 'quiz',
3364-
'exercise_question' => 'exercise_question',
3365-
'Exercise_Question' => 'exercise_question',
3366-
'exercisequestion' => 'exercise_question',
3366+
'Exercise_Question' => 'exercise_question',
3367+
'exercisequestion' => 'exercise_question',
33673368

33683369
// 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']);
3370+
'surveys' => 'survey',
3371+
'surveyquestion' => 'survey_question',
33903372

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;
3396-
}
3397-
}
3398-
return $dst;
3399-
};
3400-
3401-
$out = [];
3373+
// announcements
3374+
'announcements' => 'announcement',
3375+
'Announcements' => 'announcement',
3376+
];
34023377

3403-
foreach ($before as $rawKey => $bucket) {
3404-
if (!\is_array($bucket)) {
3405-
// Unexpected shape; skip silently (defensive)
3406-
continue;
3378+
// Normalize keys (case/spacing) and apply alias merges
3379+
foreach ($all as $rawKey => $_bucket) {
3380+
if (!\is_array($_bucket)) {
3381+
continue; // defensive
34073382
}
3408-
3409-
// Normalize key shape first
34103383
$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;
3384+
$norm = strtolower(trim(strtr($k, ['\\' => '/', '-' => '_'])));
3385+
$norm2 = str_replace('/', '_', $norm);
34153386

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;
3387+
$canonical = null;
3388+
if (isset($aliases[$norm])) {
3389+
$canonical = $aliases[$norm];
3390+
} elseif (isset($aliases[$norm2])) {
3391+
$canonical = $aliases[$norm2];
34203392
}
34213393

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;
3394+
if ($canonical && $canonical !== $rawKey) {
3395+
// Merge into canonical and drop the alias key
3396+
$out[$canonical] = isset($out[$canonical]) && \is_array($out[$canonical])
3397+
? $merge($out[$canonical], $_bucket)
3398+
: $_bucket;
3399+
unset($out[$rawKey]);
34253400
}
3401+
// else: leave as-is (pass-through)
34263402
}
34273403

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'] ?? []);
3404+
// Safety: if there was any docs bucket under an alias, ensure 'document' is present.
3405+
if (!isset($out['document'])) {
3406+
if (isset($all['documents']) && \is_array($all['documents'])) {
3407+
$out['document'] = $all['documents'];
3408+
} elseif (isset($all['Document']) && \is_array($all['Document'])) {
3409+
$out['document'] = $all['Document'];
3410+
}
34313411
}
34323412

3433-
// Gentle ordering to keep things readable
3413+
// Gentle ordering for readability only (does not affect presence)
34343414
$order = [
34353415
'announcement', 'document', 'link', 'link_category',
34363416
'forum', 'forum_category', 'forum_topic', 'forum_post',
@@ -3444,11 +3424,14 @@ private function normalizeBucketsForRestorer(object $course): void
34443424
uksort($out, static function ($a, $b) use ($w) {
34453425
$wa = $w[$a] ?? 9999;
34463426
$wb = $w[$b] ?? 9999;
3447-
return $wa <=> $wb ?: strcasecmp($a, $b);
3427+
return $wa <=> $wb ?: strcasecmp((string) $a, (string) $b);
34483428
});
34493429

3450-
// Final assign (meta first, then normalized buckets)
3430+
// Final assign: meta first, then normalized buckets
34513431
$course->resources = $meta + $out;
3432+
3433+
// Debug trace to verify we didn't lose keys
3434+
$this->logDebug('[normalizeBucketsForRestorer] final keys', array_keys((array) $course->resources));
34523435
}
34533436

34543437
/**

src/CourseBundle/Component/CourseCopy/CourseRestorer.php

Lines changed: 64 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1659,74 +1659,110 @@ public function restore_links($session_id = 0): void
16591659
/**
16601660
* Restore tool introductions.
16611661
*
1662+
* Accept multiple bucket spellings to be robust against controller normalization:
1663+
* - RESOURCE_TOOL_INTRO (if defined)
1664+
* - 'Tool introduction' (legacy)
1665+
* - 'tool_intro' / 'tool introduction' / 'tool_introduction'
1666+
*
16621667
* @param mixed $sessionId
16631668
*/
16641669
public function restore_tool_intro($sessionId = 0): void
16651670
{
16661671
$resources = $this->course->resources ?? [];
16671672

1673+
// Detect the right bucket key (be generous with aliases)
16681674
$bagKey = null;
1669-
if ($this->course->has_resources(RESOURCE_TOOL_INTRO)) {
1670-
$bagKey = RESOURCE_TOOL_INTRO;
1671-
} elseif (!empty($resources['Tool introduction'])) {
1672-
$bagKey = 'Tool introduction';
1675+
$candidates = [];
1676+
1677+
if (\defined('RESOURCE_TOOL_INTRO')) {
1678+
$candidates[] = RESOURCE_TOOL_INTRO;
16731679
}
1674-
if (null === $bagKey || empty($resources[$bagKey]) || !\is_array($resources[$bagKey])) {
1680+
1681+
// Common spellings seen in exports / normalizers
1682+
$candidates = array_merge($candidates, [
1683+
'Tool introduction',
1684+
'tool introduction',
1685+
'tool_introduction',
1686+
'tool/intro',
1687+
'tool_intro',
1688+
]);
1689+
1690+
foreach ($candidates as $k) {
1691+
if (!empty($resources[$k]) && \is_array($resources[$k])) {
1692+
$bagKey = $k;
1693+
break;
1694+
}
1695+
}
1696+
1697+
if (null === $bagKey) {
1698+
$this->dlog('restore_tool_intro: no matching bucket found', [
1699+
'available_keys' => array_keys((array) $resources),
1700+
]);
16751701
return;
16761702
}
16771703

16781704
$sessionId = (int) $sessionId;
1679-
$this->dlog('restore_tool_intro: begin', ['count' => \count($resources[$bagKey])]);
1705+
$this->dlog('restore_tool_intro: begin', [
1706+
'bucket' => $bagKey,
1707+
'count' => \count($resources[$bagKey]),
1708+
]);
16801709

1681-
$em = Database::getManager();
1682-
$course = api_get_course_entity($this->destination_course_id);
1710+
$em = Database::getManager();
1711+
$course = api_get_course_entity($this->destination_course_id);
16831712
$session = $sessionId ? api_get_session_entity($sessionId) : null;
16841713

1685-
$toolRepo = $em->getRepository(Tool::class);
1686-
$cToolRepo = $em->getRepository(CTool::class);
1687-
$introRepo = $em->getRepository(CToolIntro::class);
1714+
$toolRepo = $em->getRepository(Tool::class);
1715+
$cToolRepo = $em->getRepository(CTool::class);
1716+
$introRepo = $em->getRepository(CToolIntro::class);
16881717

16891718
foreach ($resources[$bagKey] as $rawId => $tIntro) {
1690-
// Resolve tool key
1719+
// Resolve tool key (id may be missing in some dumps)
16911720
$toolKey = trim((string) ($tIntro->id ?? ''));
16921721
if ('' === $toolKey || '0' === $toolKey) {
16931722
$toolKey = (string) $rawId;
16941723
}
16951724
$alias = strtolower($toolKey);
1725+
1726+
// Normalize common aliases to platform keys
16961727
if ('homepage' === $alias || 'course_home' === $alias) {
16971728
$toolKey = 'course_homepage';
16981729
}
16991730

17001731
$this->dlog('restore_tool_intro: resolving tool key', [
1701-
'raw_id' => (string) $rawId,
1702-
'obj_id' => isset($tIntro->id) ? (string) $tIntro->id : null,
1703-
'toolKey' => $toolKey,
1732+
'raw_id' => (string) $rawId,
1733+
'obj_id' => isset($tIntro->id) ? (string) $tIntro->id : null,
1734+
'toolKey' => $toolKey,
17041735
]);
17051736

17061737
// Already mapped?
17071738
$mapped = (int) ($tIntro->destination_id ?? 0);
17081739
if ($mapped > 0) {
1709-
$this->dlog('restore_tool_intro: already mapped, skipping', ['src_id' => $toolKey, 'dst_id' => $mapped]);
1710-
1740+
$this->dlog('restore_tool_intro: already mapped, skipping', [
1741+
'src_id' => $toolKey, 'dst_id' => $mapped,
1742+
]);
17111743
continue;
17121744
}
17131745

1714-
// Rewrite HTML using the central helper
1746+
// Rewrite HTML using centralized helper (keeps document links consistent)
17151747
$introHtml = $this->rewriteHtmlForCourse((string) ($tIntro->intro_text ?? ''), $sessionId, '[tool_intro.intro]');
17161748

1717-
// Find platform Tool entity
1749+
// Find platform Tool entity by title; try a couple of fallbacks
17181750
$toolEntity = $toolRepo->findOneBy(['title' => $toolKey]);
1751+
if (!$toolEntity) {
1752+
// Fallbacks: lower/upper case attempts
1753+
$toolEntity = $toolRepo->findOneBy(['title' => strtolower($toolKey)])
1754+
?: $toolRepo->findOneBy(['title' => ucfirst(strtolower($toolKey))]);
1755+
}
17191756
if (!$toolEntity) {
17201757
$this->dlog('restore_tool_intro: missing Tool entity, skipping', ['tool' => $toolKey]);
1721-
17221758
continue;
17231759
}
17241760

1725-
// Find or create course tool (CTool)
1761+
// Ensure a CTool exists for this course/session+tool
17261762
$cTool = $cToolRepo->findOneBy([
1727-
'course' => $course,
1763+
'course' => $course,
17281764
'session' => $session,
1729-
'title' => $toolKey,
1765+
'title' => $toolKey,
17301766
]);
17311767

17321768
if (!$cTool) {
@@ -1739,9 +1775,7 @@ public function restore_tool_intro($sessionId = 0): void
17391775
->setVisibility(true)
17401776
->setParent($course)
17411777
->setCreator($course->getCreator() ?? null)
1742-
->addCourseLink($course)
1743-
;
1744-
1778+
->addCourseLink($course, $session);
17451779
$em->persist($cTool);
17461780
$em->flush();
17471781

@@ -1751,7 +1785,7 @@ public function restore_tool_intro($sessionId = 0): void
17511785
]);
17521786
}
17531787

1754-
// Intro entity (create/overwrite/skip according to policy)
1788+
// Create/overwrite intro according to file policy
17551789
$intro = $introRepo->findOneBy(['courseTool' => $cTool]);
17561790

17571791
if ($intro) {
@@ -1774,9 +1808,7 @@ public function restore_tool_intro($sessionId = 0): void
17741808
$intro = (new CToolIntro())
17751809
->setCourseTool($cTool)
17761810
->setIntroText($introHtml)
1777-
->setParent($course)
1778-
;
1779-
1811+
->setParent($course);
17801812
$em->persist($intro);
17811813
$em->flush();
17821814

@@ -1786,8 +1818,8 @@ public function restore_tool_intro($sessionId = 0): void
17861818
]);
17871819
}
17881820

1789-
// Map destination id back
1790-
$this->course->resources[$bagKey][$rawId] ??= new stdClass();
1821+
// Map destination id back into the bucket used
1822+
$this->course->resources[$bagKey][$rawId] ??= new \stdClass();
17911823
$this->course->resources[$bagKey][$rawId]->destination_id = (int) $intro->getIid();
17921824
}
17931825

0 commit comments

Comments
 (0)