Skip to content

Commit c6f5c5e

Browse files
Course: Fix course tools API: handle missing plugin tool handlers & normalize tool names - refs #7415
1 parent 3dddf12 commit c6f5c5e

2 files changed

Lines changed: 109 additions & 11 deletions

File tree

public/plugin/H5pImport/H5pImportPlugin.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -287,11 +287,11 @@ private function addCourseTools(): void
287287
}
288288
}
289289

290-
private function deleteCourseToolLinks()
290+
private function deleteCourseToolLinks(): void
291291
{
292-
Database::getManager()
293-
->createQuery('DELETE FROM ChamiloCourseBundle:CTool t WHERE t.category = :category AND t.link LIKE :link')
294-
->execute(['category' => 'authoring', 'link' => '../plugin/H5pImport/start.php%']);
292+
$em = Database::getManager();
293+
$em->createQuery('DELETE FROM ChamiloCourseBundle:CTool t WHERE t.title = :title')
294+
->execute(['title' => 'H5P import']);
295295
}
296296

297297
/**

src/CoreBundle/State/CToolStateProvider.php

Lines changed: 105 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Event;
2424
use Symfony\Bundle\SecurityBundle\Security;
2525
use Symfony\Component\HttpFoundation\RequestStack;
26+
use Throwable;
2627

2728
/**
2829
* @template-implements ProviderInterface<CTool>
@@ -72,25 +73,39 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
7273
$session = $this->getSession();
7374
$course = $this->getCourse();
7475

75-
[$restrictToPositioning, $allowedToolName] = $this->shouldRestrictToPositioningOnly($user, $course->getId(), $session?->getId());
76+
[$restrictToPositioning, $allowedToolName] = $this->shouldRestrictToPositioningOnly(
77+
$user,
78+
$course->getId(),
79+
$session?->getId()
80+
);
7681

7782
$results = [];
7883

7984
/** @var CTool $cTool */
8085
foreach ($result as $cTool) {
81-
if ($restrictToPositioning && $cTool->getTool()->getTitle() !== $allowedToolName) {
86+
$resolved = $this->resolveToolModelFromCTool($cTool);
87+
if (null === $resolved) {
8288
continue;
8389
}
8490

85-
$toolModel = $this->toolChain->getToolFromName(
86-
$cTool->getTool()->getTitle()
87-
);
91+
$toolModel = $resolved['model'];
92+
$resolvedName = $resolved['name'];
93+
94+
// If a positioning restriction is active, keep only the allowed tool.
95+
if ($restrictToPositioning && $allowedToolName && $resolvedName !== $allowedToolName) {
96+
continue;
97+
}
8898

8999
if (!$isAllowToEdit && 'admin' === $toolModel->getCategory()) {
90100
continue;
91101
}
92102

93-
$resourceLinks = $cTool->getResourceNode()->getResourceLinks();
103+
$resourceNode = $cTool->getResourceNode();
104+
if (!$resourceNode) {
105+
continue;
106+
}
107+
108+
$resourceLinks = $resourceNode->getResourceLinks();
94109

95110
if ($session && $allowVisibilityInSession) {
96111
$sessionLink = $resourceLinks->findFirst(
@@ -109,8 +124,12 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
109124
}
110125

111126
if (!$isAllowToEdit || 'studentview' === $studentView) {
112-
$notPublishedLink = ResourceLink::VISIBILITY_PUBLISHED !== $resourceLinks->first()->getVisibility();
127+
$firstLink = $resourceLinks->first();
128+
if (!$firstLink) {
129+
continue;
130+
}
113131

132+
$notPublishedLink = ResourceLink::VISIBILITY_PUBLISHED !== $firstLink->getVisibility();
114133
if ($notPublishedLink) {
115134
continue;
116135
}
@@ -122,6 +141,85 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
122141
return $results;
123142
}
124143

144+
/**
145+
* Resolve a ToolChain model for a given CTool safely.
146+
* Tries multiple candidate names derived from the stored tool title.
147+
*
148+
* @return array{model: object, name: string}|null
149+
*/
150+
private function resolveToolModelFromCTool(CTool $cTool): ?array
151+
{
152+
$toolEntity = $cTool->getTool();
153+
$rawTitle = $toolEntity ? (string) $toolEntity->getTitle() : '';
154+
155+
foreach ($this->buildToolNameCandidates($rawTitle) as $candidate) {
156+
try {
157+
$model = $this->toolChain->getToolFromName($candidate);
158+
159+
// Return the candidate we used so other logic can rely on a stable key.
160+
return [
161+
'model' => $model,
162+
'name' => $candidate,
163+
];
164+
} catch (Throwable) {
165+
// Try next candidate
166+
}
167+
}
168+
169+
return null;
170+
}
171+
172+
/**
173+
* Build candidate tool names from a DB title.
174+
* This keeps backward compatibility while supporting human titles like "H5P import".
175+
*
176+
* @return string[]
177+
*/
178+
private function buildToolNameCandidates(string $rawTitle): array
179+
{
180+
$rawTitle = trim($rawTitle);
181+
if ('' === $rawTitle) {
182+
return [];
183+
}
184+
185+
$candidates = [];
186+
187+
// Prefer lowercase first (ToolChain commonly uses lowercase keys)
188+
$lower = strtolower($rawTitle);
189+
$candidates[] = $lower;
190+
191+
// Original as fallback
192+
if ($rawTitle !== $lower) {
193+
$candidates[] = $rawTitle;
194+
}
195+
196+
// Replace spaces/dashes with underscores (e.g., "H5P import" -> "h5p_import")
197+
$spaceSnake = strtolower(preg_replace('/[\s\-]+/', '_', $rawTitle) ?? $rawTitle);
198+
$spaceSnake = trim($spaceSnake, '_');
199+
$candidates[] = $spaceSnake;
200+
201+
// Replace any non-alnum with underscores
202+
$alnumSnake = strtolower(preg_replace('/[^a-z0-9]+/i', '_', $rawTitle) ?? $rawTitle);
203+
$alnumSnake = trim($alnumSnake, '_');
204+
$candidates[] = $alnumSnake;
205+
206+
// CamelCase to snake_case (e.g., "CustomCertificate" -> "custom_certificate")
207+
$camelSnake = preg_replace('/(?<!^)[A-Z]/', '_$0', $rawTitle) ?? $rawTitle;
208+
$camelSnake = strtolower($camelSnake);
209+
$camelSnake = strtolower(preg_replace('/[^a-z0-9]+/i', '_', $camelSnake) ?? $camelSnake);
210+
$camelSnake = trim($camelSnake, '_');
211+
$candidates[] = $camelSnake;
212+
213+
// Some tool keys might be stored without underscores
214+
$candidates[] = str_replace('_', '', $camelSnake);
215+
$candidates[] = str_replace('_', '', $alnumSnake);
216+
217+
// Unique + non-empty
218+
$candidates = array_values(array_unique(array_filter($candidates, static fn ($v) => is_string($v) && '' !== trim($v))));
219+
220+
return $candidates;
221+
}
222+
125223
private function shouldRestrictToPositioningOnly(?User $user, int $courseId, ?int $sessionId): array
126224
{
127225
if (!$user || !$user->isStudent()) {

0 commit comments

Comments
 (0)