diff --git a/Render/Html/GetNelmioAsset.php b/Render/Html/GetNelmioAsset.php index 8767682..38e85a6 100644 --- a/Render/Html/GetNelmioAsset.php +++ b/Render/Html/GetNelmioAsset.php @@ -12,30 +12,82 @@ namespace Nelmio\ApiDocBundle\Render\Html; use Symfony\Bridge\Twig\Extension\AssetExtension; +use Twig\TwigFunction; class GetNelmioAsset { private $assetExtension; - private $defaultAssetsMode; + private $resourcesDir; + private $cdnUrl; + private $assetsMode = AssetsMode::BUNDLE; - public function __construct(AssetExtension $assetExtension, $defaultAssetsMode) + public function __construct(AssetExtension $assetExtension) { $this->assetExtension = $assetExtension; - $this->defaultAssetsMode = $defaultAssetsMode; + $this->cdnUrl = 'https://cdn.jsdelivr.net/gh/nelmio/NelmioApiDocBundle/Resources/public'; + $this->resourcesDir = __DIR__.'/../../Resources/public'; } - public function __invoke($asset, $forcedMode = null) + public function toTwigFunction($assetsMode): TwigFunction { - $mode = $forcedMode ?: $this->defaultAssetsMode; - if (AssetsMode::CDN === $mode) { - return sprintf( - 'https://cdn.jsdelivr.net/gh/nelmio/NelmioApiDocBundle@4.1/Resources/public/%s', - $asset - ); - } elseif (AssetsMode::OFFLINE === $mode) { - return file_get_contents(__DIR__.sprintf('/../../Resources/public/%s', $asset)); + $this->assetsMode = $assetsMode; + + return new TwigFunction('nelmioAsset', $this, ['is_safe' => ['html']]); + } + + public function __invoke($asset) + { + [$extension, $mode] = $this->getExtension($asset); + [$resource, $isInline] = $this->getResource($asset, $mode); + if ('js' == $extension) { + return $this->renderJavascript($resource, $isInline); + } elseif ('css' == $extension) { + return $this->renderCss($resource, $isInline); } else { - return $this->assetExtension->getAssetUrl(sprintf('bundles/nelmioapidoc/%s', $asset)); + return $resource; + } + } + + private function getExtension($asset) + { + $extension = mb_substr($asset, -3, 3, 'utf-8'); + if ('.js' === $extension) { + return ['js', $this->assetsMode]; + } elseif ('png' === $extension) { + return ['png', AssetsMode::OFFLINE == $this->assetsMode ? AssetsMode::CDN : $this->assetsMode]; + } else { + return ['css', $this->assetsMode]; + } + } + + private function getResource($asset, $mode) + { + if (filter_var($asset, FILTER_VALIDATE_URL)) { + return [$asset, false]; + } elseif (AssetsMode::OFFLINE === $mode) { + return [file_get_contents($this->resourcesDir.'/'.$asset), true]; + } elseif (AssetsMode::CDN === $mode) { + return [$this->cdnUrl.'/'.$asset, false]; + } else { + return [$this->assetExtension->getAssetUrl(sprintf('bundles/nelmioapidoc/%s', $asset)), false]; + } + } + + private function renderJavascript(string $script, bool $isInline) + { + if ($isInline) { + return sprintf('<script>%s</script>', $script); + } else { + return sprintf('<script src="%s"></script>', $script); + } + } + + private function renderCss(string $stylesheet, bool $isInline) + { + if ($isInline) { + return sprintf('<style>%s</style>', $stylesheet); + } else { + return sprintf('<link rel="stylesheet" href="%s">', $stylesheet); } } } diff --git a/Render/Html/HtmlOpenApiRenderer.php b/Render/Html/HtmlOpenApiRenderer.php index 5bb8645..bedf14e 100644 --- a/Render/Html/HtmlOpenApiRenderer.php +++ b/Render/Html/HtmlOpenApiRenderer.php @@ -16,28 +16,23 @@ use Nelmio\ApiDocBundle\Render\OpenApiRenderer; use Nelmio\ApiDocBundle\Render\RenderOpenApi; use OpenApi\Annotations\OpenApi; use OpenApi\Annotations\Server; -use Symfony\Bridge\Twig\Extension\AssetExtension; use Twig\Environment; -use Twig\TwigFunction; class HtmlOpenApiRenderer implements OpenApiRenderer { - /** - * @var Environment|\Twig_Environment - */ + /** @var Environment|\Twig_Environment */ private $twig; - /** - * @var AssetExtension - */ - private $assetExtension; - public function __construct($twig, AssetExtension $assetExtension) + /** @var GetNelmioAsset */ + private $getNelmioAsset; + + public function __construct($twig, GetNelmioAsset $getNelmioAsset) { if (!$twig instanceof \Twig_Environment && !$twig instanceof Environment) { throw new InvalidArgumentException(sprintf('Providing an instance of "%s" as twig is not supported.', get_class($twig))); } $this->twig = $twig; - $this->assetExtension = $assetExtension; + $this->getNelmioAsset = $getNelmioAsset; } public function getFormat(): string @@ -53,12 +48,7 @@ class HtmlOpenApiRenderer implements OpenApiRenderer 'swagger_ui_config' => [], ]; - $this->twig->addFunction( - new TwigFunction( - 'nelmioAsset', - new GetNelmioAsset($this->assetExtension, $options['assets_mode']) - ) - ); + $this->twig->addFunction($this->getNelmioAsset->toTwigFunction($options['assets_mode'])); return $this->twig->render( '@NelmioApiDoc/SwaggerUi/index.html.twig', diff --git a/Resources/config/services.xml b/Resources/config/services.xml index e0127ae..d125a10 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -33,6 +33,9 @@ </service> <service id="nelmio_api_doc.render_docs.html" class="Nelmio\ApiDocBundle\Render\Html\HtmlOpenApiRenderer" public="false"> <argument type="service" id="twig" /> + <argument type="service" id="nelmio_api_doc.render_docs.html.asset" /> + </service> + <service id="nelmio_api_doc.render_docs.html.asset" class="Nelmio\ApiDocBundle\Render\Html\GetNelmioAsset" public="false"> <argument type="service" id="twig.extension.assets" /> </service> <service id="nelmio_api_doc.render_docs.json" class="Nelmio\ApiDocBundle\Render\Json\JsonOpenApiRenderer" public="false"> diff --git a/Resources/views/SwaggerUi/index.html.twig b/Resources/views/SwaggerUi/index.html.twig index 0db7f8b..a1b3129 100644 --- a/Resources/views/SwaggerUi/index.html.twig +++ b/Resources/views/SwaggerUi/index.html.twig @@ -14,13 +14,8 @@ file that was distributed with this source code. #} <title>{% block title %}{{ swagger_data.spec.info.title }}{% endblock title %}</title> {% block stylesheets %} - {% if assets_mode == 'offline' %} - <style>{{ nelmioAsset('swagger-ui/swagger-ui.css')|raw }}</style> - <style>{{ nelmioAsset('style.css')|raw }}</style> - {% else %} - <link rel="stylesheet" href="{{ nelmioAsset('swagger-ui/swagger-ui.css') }}"> - <link rel="stylesheet" href="{{ nelmioAsset('style.css.css') }}"> - {% endif %} + {{ nelmioAsset('swagger-ui/swagger-ui.css') }} + {{ nelmioAsset('style.css') }} {% endblock stylesheets %} {% block swagger_data %} @@ -59,11 +54,7 @@ file that was distributed with this source code. #} <header> {% block header %} <a id="logo" href="https://github.com/nelmio/NelmioApiDocBundle"> - {% if assets_mode in ['offline', 'cdn'] %} - <img src="{{ nelmioAsset('logo.png', 'cdn') }}" alt="NelmioApiDocBundle"> - {% else %} <img src="{{ nelmioAsset('logo.png') }}" alt="NelmioApiDocBundle"> - {% endif %} </a> {% endblock header %} </header> @@ -73,20 +64,11 @@ file that was distributed with this source code. #} {% endblock %} {% block javascripts %} - {% if assets_mode == 'offline' %} - <script>{{ nelmioAsset('swagger-ui/swagger-ui-bundle.js')|raw }}</script> - <script>{{ nelmioAsset('swagger-ui/swagger-ui-standalone-preset.js')|raw }}</script> - {% else %} - <script src="{{ nelmioAsset('swagger-ui/swagger-ui-bundle.js') }}"></script> - <script src="{{ nelmioAsset('swagger-ui/swagger-ui-standalone-preset.js') }}"></script> - {% endif %} + {{ nelmioAsset('swagger-ui/swagger-ui-bundle.js') }} + {{ nelmioAsset('swagger-ui/swagger-ui-standalone-preset.js') }} {% endblock javascripts %} - {% if assets_mode == 'offline' %} - <script>{{ nelmioAsset('init-swagger-ui.js')|raw }}</script> - {% else %} - <script src="{{ nelmioAsset('init-swagger-ui.js') }}"></script> - {% endif %} + {{ nelmioAsset('init-swagger-ui.js') }} {% block swagger_initialization %} <script type="text/javascript"> diff --git a/Tests/Render/Html/GetNelmioAssetTest.php b/Tests/Render/Html/GetNelmioAssetTest.php new file mode 100644 index 0000000..2077a11 --- /dev/null +++ b/Tests/Render/Html/GetNelmioAssetTest.php @@ -0,0 +1,112 @@ +<?php + +/* + * This file is part of the NelmioApiDocBundle package. + * + * (c) Nelmio + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Nelmio\ApiDocBundle\Tests\Render\Html; + +use Nelmio\ApiDocBundle\Render\Html\AssetsMode; +use Nelmio\ApiDocBundle\Render\Html\GetNelmioAsset; +use Nelmio\ApiDocBundle\Tests\Functional\WebTestCase; + +class GetNelmioAssetTest extends WebTestCase +{ + /** @dataProvider provideAsset */ + public function test($mode, $asset, $expectedContent) + { + static::bootKernel(); + /** @var GetNelmioAsset $getNelmioAsset */ + $getNelmioAsset = self::getContainer()->get('nelmio_api_doc.render_docs.html.asset'); + $twigFunction = $getNelmioAsset->toTwigFunction($mode); + self::assertSame($expectedContent, $twigFunction->getCallable()->__invoke($asset, $mode)); + } + + public function provideAsset() + { + $cdnDir = 'https://cdn.jsdelivr.net/gh/nelmio/NelmioApiDocBundle/Resources/public'; + $resourceDir = __DIR__.'/../../../Resources/public'; + + return $this->provideCss($cdnDir, $resourceDir) + + $this->provideJs($cdnDir, $resourceDir) + + $this->provideImage($cdnDir); + } + + private function provideCss($cdnDir, $resourceDir) + { + return [ + 'bundled css' => [ + AssetsMode::BUNDLE, + 'style.css', + '<link rel="stylesheet" href="/bundles/nelmioapidoc/style.css">', + ], + 'cdn css' => [ + AssetsMode::CDN, + 'style.css', + '<link rel="stylesheet" href="'.$cdnDir.'/style.css">', + ], + 'offline css' => [ + AssetsMode::OFFLINE, + 'style.css', + '<style>'.file_get_contents($resourceDir.'/style.css').'</style>', + ], + 'external css' => [ + AssetsMode::BUNDLE, + 'https://cdn.com/my.css', + '<link rel="stylesheet" href="https://cdn.com/my.css">', + ], + ]; + } + + private function provideJs($cdnDir, $resourceDir) + { + return [ + 'bundled js' => [ + AssetsMode::BUNDLE, + 'init-swagger-ui.js', + '<script src="/bundles/nelmioapidoc/init-swagger-ui.js"></script>', + ], + 'cdn js' => [ + AssetsMode::CDN, + 'init-swagger-ui.js', + '<script src="'.$cdnDir.'/init-swagger-ui.js"></script>', + ], + 'offline js' => [ + AssetsMode::OFFLINE, + 'init-swagger-ui.js', + '<script>'.file_get_contents($resourceDir.'/init-swagger-ui.js').'</script>', + ], + 'external js' => [ + AssetsMode::BUNDLE, + 'https://cdn.com/my.js', + '<script src="https://cdn.com/my.js"></script>', + ], + ]; + } + + private function provideImage($cdnDir) + { + return [ + 'bundled image' => [ + AssetsMode::BUNDLE, + 'logo.png', + '/bundles/nelmioapidoc/logo.png', + ], + 'cdn image' => [ + AssetsMode::CDN, + 'logo.png', + $cdnDir.'/logo.png', + ], + 'offline image fallbacks to cdn' => [ + AssetsMode::OFFLINE, + 'logo.png', + $cdnDir.'/logo.png', + ], + ]; + } +}