php/src/Core/Cdn/Services/FluxCdnService.php
Snider 208cb93c95
All checks were successful
CI / PHP 8.3 (pull_request) Successful in 2m32s
CI / PHP 8.4 (pull_request) Successful in 2m17s
fix(dx): code style fixes, strict_types, and test repair
- Remove non-existent src/Core/Service/ from CLAUDE.md L1 packages list
- Fix LifecycleEventsTest: remove dependency on McpToolHandler interface
  (lives in core-mcp, not needed since McpToolsRegistering stores class
  name strings)
- Run Laravel Pint to fix PSR-12 violations across all source and test files
- Add missing declare(strict_types=1) to 18 PHP files (tests, seeders,
  Layout.php, GenerateServiceOgImages.php)

Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-17 09:03:50 +00:00

206 lines
6.3 KiB
PHP

<?php
/*
* Core PHP Framework
*
* Licensed under the European Union Public Licence (EUPL) v1.2.
* See LICENSE file for details.
*/
declare(strict_types=1);
namespace Core\Cdn\Services;
use Core\Helpers\Cdn;
use Flux\AssetManager;
use Flux\Flux;
/**
* CDN-aware Flux asset service.
*
* In development: Uses standard Laravel routes (/flux/flux.js)
* In production: Uses CDN URLs (cdn.host.uk.com/flux/flux.min.js)
*
* Requires Flux assets to be uploaded to CDN storage zone.
*
* URL building is delegated to CdnUrlBuilder for consistency across services.
*
* ## Methods
*
* | Method | Returns | Description |
* |--------|---------|-------------|
* | `scripts()` | `string` | Get Flux scripts tag with CDN awareness |
* | `editorScripts()` | `string` | Get Flux editor scripts (Pro only) |
* | `editorStyles()` | `string` | Get Flux editor styles (Pro only) |
* | `shouldUseCdn()` | `bool` | Check if CDN should be used |
* | `getCdnAssetPaths()` | `array<string, string>` | Get source-to-CDN path mapping |
*
* @see CdnUrlBuilder For the underlying URL building logic
*/
class FluxCdnService
{
protected CdnUrlBuilder $urlBuilder;
public function __construct(?CdnUrlBuilder $urlBuilder = null)
{
$this->urlBuilder = $urlBuilder ?? new CdnUrlBuilder;
}
/**
* Get the Flux scripts tag with CDN awareness.
*
* @param array<string, mixed> $options Options like ['nonce' => 'abc123']
* @return string HTML script tag
*/
public function scripts(array $options = []): string
{
$nonce = isset($options['nonce']) ? ' nonce="'.$options['nonce'].'"' : '';
// Use CDN when enabled (respects CDN_FORCE_LOCAL for testing)
if (! $this->shouldUseCdn()) {
return app('flux')->scripts($options);
}
// In production, use CDN URL (no vBucket - shared platform asset)
$versionHash = $this->getVersionHash();
$filename = config('app.debug') ? 'flux.js' : 'flux.min.js';
$url = $this->cdnUrl("flux/{$filename}", $versionHash);
return '<script src="'.$url.'" data-navigate-once'.$nonce.'></script>';
}
/**
* Get the Flux editor scripts tag with CDN awareness.
*
* @return string HTML script tag for Flux editor
*
* @throws \Exception When Flux Pro is not available
*/
public function editorScripts(): string
{
if (! Flux::pro()) {
throw new \Exception('Flux Pro is required to use the Flux editor.');
}
// Use CDN when enabled (respects CDN_FORCE_LOCAL for testing)
if (! $this->shouldUseCdn()) {
return AssetManager::editorScripts();
}
// In production, use CDN URL (no vBucket - shared platform asset)
$versionHash = $this->getVersionHash('/editor.js');
$filename = config('app.debug') ? 'editor.js' : 'editor.min.js';
$url = $this->cdnUrl("flux/{$filename}", $versionHash);
return '<script src="'.$url.'" defer></script>';
}
/**
* Get the Flux editor styles tag with CDN awareness.
*
* @return string HTML link tag for Flux editor styles
*
* @throws \Exception When Flux Pro is not available
*/
public function editorStyles(): string
{
if (! Flux::pro()) {
throw new \Exception('Flux Pro is required to use the Flux editor.');
}
// Use CDN when enabled (respects CDN_FORCE_LOCAL for testing)
if (! $this->shouldUseCdn()) {
return AssetManager::editorStyles();
}
// In production, use CDN URL (no vBucket - shared platform asset)
$versionHash = $this->getVersionHash('/editor.css');
$url = $this->cdnUrl('flux/editor.css', $versionHash);
return '<link rel="stylesheet" href="'.$url.'">';
}
/**
* Get version hash from Flux manifest.
*
* @param string $key Manifest key to look up
* @return string 8-character hash for cache busting
*/
protected function getVersionHash(string $key = '/flux.js'): string
{
$manifestPath = Flux::pro()
? base_path('vendor/admin/flux-pro/dist/manifest.json')
: base_path('vendor/admin/flux/dist/manifest.json');
if (! file_exists($manifestPath)) {
return substr(md5(config('app.version', '1.0')), 0, 8);
}
$manifest = json_decode(file_get_contents($manifestPath), true);
return $manifest[$key] ?? substr(md5(config('app.version', '1.0')), 0, 8);
}
/**
* Check if we should use CDN for Flux assets.
*
* Respects CDN_FORCE_LOCAL for testing.
*
* @return bool True if CDN should be used, false for local assets
*/
public function shouldUseCdn(): bool
{
return Cdn::isEnabled();
}
/**
* Build CDN URL for shared platform assets (no vBucket scoping).
*
* Flux assets are shared across all workspaces, so they don't use
* workspace-specific vBucket prefixes.
*
* @param string $path Asset path relative to CDN root
* @param string|null $version Optional version hash for cache busting
* @return string Full CDN URL with optional version query parameter
*/
protected function cdnUrl(string $path, ?string $version = null): string
{
$cdnUrl = config('cdn.urls.cdn');
if (empty($cdnUrl)) {
$baseUrl = asset($path);
return $this->urlBuilder->withVersion($baseUrl, $version);
}
$url = $this->urlBuilder->cdn($path);
return $this->urlBuilder->withVersion($url, $version);
}
/**
* Get the list of Flux files that should be uploaded to CDN.
*
* @return array<string, string> Map of source path => CDN path
*/
public function getCdnAssetPaths(): array
{
$basePath = Flux::pro()
? base_path('vendor/admin/flux-pro/dist')
: base_path('vendor/admin/flux/dist');
$files = [
"{$basePath}/flux.js" => 'flux/flux.js',
"{$basePath}/flux.min.js" => 'flux/flux.min.js',
];
// Add editor files for Pro
if (Flux::pro()) {
$files["{$basePath}/editor.js"] = 'flux/editor.js';
$files["{$basePath}/editor.min.js"] = 'flux/editor.min.js';
$files["{$basePath}/editor.css"] = 'flux/editor.css';
}
return $files;
}
}