mainContext instanceof ResourceInterface; } public function generate(?BreadcrumbContext $context, ?ServerRequestInterface $request): array { if ($context === null || !$context->mainContext instanceof ResourceInterface) { return []; } $resource = $context->mainContext; $breadcrumb = []; $currentModule = $this->moduleResolver->resolveModule($request); // Add module node if ($currentModule !== null) { $languageService = $this->getLanguageService(); $breadcrumb[] = new BreadcrumbNode( identifier: $currentModule->getIdentifier(), label: $languageService->sL($currentModule->getTitle()), icon: $currentModule->getIconIdentifier(), iconOverlay: null, url: (string)$this->uriBuilder->buildUriFromRoute($currentModule->getIdentifier(), ['id' => '']), forceShowIcon: true, ); } // Build resource hierarchy $resourceHierarchy = $this->buildResourceHierarchy($resource); // Add resource nodes foreach ($resourceHierarchy as $item) { try { $icon = $this->iconFactory->getIconForResource($item, IconSize::SMALL); $label = $item->getName(); $combinedIdentifier = $this->getCombinedIdentifier($item); // Use storage name for root folder if ($item->getIdentifier() === $item->getStorage()->getRootLevelFolder()->getIdentifier()) { $label = $item->getStorage()->getName(); } $breadcrumb[] = new BreadcrumbNode( identifier: $combinedIdentifier, label: $label, icon: $icon->getIdentifier(), iconOverlay: $icon->getOverlayIcon()?->getIdentifier(), url: (string)$this->uriBuilder->buildUriFromRoute($this->getTargetModule(), ['id' => $combinedIdentifier]), ); } catch (\Exception $e) { $this->logger->warning( 'Failed to create breadcrumb node for resource', ['identifier' => $item->getIdentifier(), 'exception' => $e->getMessage()] ); } } return $breadcrumb; } public function getPriority(): int { return 10; } /** * Returns the target module identifier for navigation. */ private function getTargetModule(): string { return 'media_management'; } /** * Builds the resource hierarchy from root to current resource. * * @return ResourceInterface[] */ private function buildResourceHierarchy(ResourceInterface $resource): array { $hierarchy = []; $folder = null; // Start with the resource itself if ($resource instanceof FileInterface) { $hierarchy[] = $resource; try { $folder = $resource->getParentFolder(); } catch (\Exception $e) { $this->logger->warning( 'Failed to get parent folder for file', ['identifier' => $resource->getIdentifier(), 'exception' => $e->getMessage()] ); return $hierarchy; } } elseif ($resource instanceof FolderInterface) { $folder = $resource; } // Traverse up the folder hierarchy if ($folder instanceof Folder) { $currentFolder = $folder; $hierarchy[] = $folder; // Walk up to the root folder $maxDepth = 100; // Safety limit to prevent infinite loops $depth = 0; while ($depth < $maxDepth) { $depth++; try { $parent = $currentFolder->getParentFolder(); } catch (InsufficientFolderAccessPermissionsException $e) { // User doesn't have access to parent folder, stop here $this->logger->info( 'Stopped breadcrumb traversal due to insufficient folder access', ['folder' => $currentFolder->getCombinedIdentifier()] ); break; } // Check if we've reached the root (parent points to itself) if ($parent->getCombinedIdentifier() === $currentFolder->getCombinedIdentifier()) { break; } // Add parent to hierarchy and continue upwards $hierarchy[] = $parent; $currentFolder = $parent; } } // Reverse to get root-to-current order return array_reverse($hierarchy); } /** * Gets the combined identifier for a resource. * Constructs it from storage UID and resource identifier. */ private function getCombinedIdentifier(ResourceInterface $resource): string { return $resource->getStorage()->getUid() . ':' . $resource->getIdentifier(); } private function getLanguageService(): LanguageService { return $GLOBALS['LANG']; } }