hasBackgroundMedia() and * $slide->getBackgroundMediaUrl(). * - UPLOADED content slide image → full-frame fill.media CONTENT element. * Assert via fillMediaUrlAt()/fillMediaUrls()/isFullFrameAt() and the * foreground-action absence via hasForegroundImageMediaAction() == false. * - Text (songtext / name-tag) → text CONTENT element (elementHasTextAt()). * * Content elements are full-frame Graphics\Element fills whose z-order is the * base-slide element array order (index 0 = bottom layer): text first, the * uploaded foreground image appended on top. The background image is NOT an * element; it renders beneath the stack from its BACKGROUND media action. */ trait InspectsSlideFillMedia { /** * Ordered base-slide content elements (z-order; index 0 = bottom layer). * * @return array */ protected function baseSlideElements(Slide $slide): array { foreach ($slide->getCue()->getActions() as $action) { if ($action->getType() !== ActionType::ACTION_TYPE_PRESENTATION_SLIDE) { continue; } $baseSlide = $action->getSlide()?->getPresentation()?->getBaseSlide(); if ($baseSlide === null) { return []; } $elements = []; foreach ($baseSlide->getElements() as $element) { $elements[] = $element; } return $elements; } return []; } /** Bundle-relative fill-media URL of the element at $index, or null when it has no media fill. */ protected function fillMediaUrlAt(Slide $slide, int $index): ?string { $elements = $this->baseSlideElements($slide); $graphics = ($elements[$index] ?? null)?->getElement(); $fill = $graphics?->getFill(); if ($fill === null || ! $fill->hasMedia()) { return null; } return $fill->getMedia()?->getUrl()?->getAbsoluteString(); } /** * Bounds [x, y, width, height] of the element at $index, or null. * * @return array{0: float, 1: float, 2: float, 3: float}|null */ protected function fillBoundsAt(Slide $slide, int $index): ?array { $elements = $this->baseSlideElements($slide); $bounds = ($elements[$index] ?? null)?->getElement()?->getBounds(); if ($bounds === null) { return null; } return [ (float) ($bounds->getOrigin()?->getX() ?? 0.0), (float) ($bounds->getOrigin()?->getY() ?? 0.0), (float) ($bounds->getSize()?->getWidth() ?? 0.0), (float) ($bounds->getSize()?->getHeight() ?? 0.0), ]; } /** True when the element at $index is a full-frame (0,0 → 1920×1080) fill. */ protected function isFullFrameAt(Slide $slide, int $index): bool { return $this->fillBoundsAt($slide, $index) === [0.0, 0.0, 1920.0, 1080.0]; } /** * All fill-media URLs on the slide, in z-order (bottom → top). * * @return array */ protected function fillMediaUrls(Slide $slide): array { $urls = []; foreach ($this->baseSlideElements($slide) as $element) { $fill = $element->getElement()?->getFill(); if ($fill !== null && $fill->hasMedia()) { $urls[] = $fill->getMedia()?->getUrl()?->getAbsoluteString() ?? ''; } } return $urls; } /** Whether any base-slide element carries a fill-media image with the given URL. */ protected function hasFillMediaUrl(Slide $slide, string $url): bool { return in_array($url, $this->fillMediaUrls($slide), true); } /** True while the element at $index carries text (used to assert text sits above the background). */ protected function elementHasTextAt(Slide $slide, int $index): bool { $graphics = ($this->baseSlideElements($slide)[$index] ?? null)?->getElement(); return $graphics !== null && $graphics->hasText(); } /** * Whether the cue carries ANY image MEDIA action (either layer). Useful only * when no service background is configured; when a background IS present it * survives as a BACKGROUND action, so prefer hasForegroundImageMediaAction(). */ protected function hasImageMediaAction(Slide $slide): bool { foreach ($slide->getCue()->getActions() as $action) { if ($action->getType() !== ActionType::ACTION_TYPE_MEDIA) { continue; } $mediaElement = $action->getMedia()?->getElement(); if ($mediaElement !== null && $mediaElement->hasImage()) { return true; } } return false; } /** * Whether the cue still carries a FOREGROUND-layer image MEDIA action. Must be * false after conversion — uploaded images become content elements. A * BACKGROUND-layer action (service background / key-visual) is ignored here. */ protected function hasForegroundImageMediaAction(Slide $slide): bool { foreach ($slide->getCue()->getActions() as $action) { if ($action->getType() !== ActionType::ACTION_TYPE_MEDIA) { continue; } $mediaType = $action->getMedia(); $mediaElement = $mediaType?->getElement(); if ( $mediaElement !== null && $mediaElement->hasImage() && $mediaType->getLayerType() !== LayerType::LAYER_TYPE_BACKGROUND ) { return true; } } return false; } }