cdn.install | 11 +++++++++ cdn.routing.yml | 8 ++++--- cdn.services.yml | 2 +- cdn_ui/js/summaries.js | 5 ++-- cdn_ui/src/Form/CdnSettingsForm.php | 24 ++++++++++--------- config/install/cdn.settings.yml | 4 +++- config/schema/cdn.schema.yml | 4 ++-- src/CdnFarfutureController.php | 31 ++++++++++++++++++------- src/CdnSettings.php | 22 ++++++++++++++---- src/File/FileUrlGenerator.php | 16 ++++++------- src/PathProcessor/CdnFarfuturePathProcessor.php | 27 ++++++++++++++++----- tests/src/Functional/CdnIntegrationTest.php | 9 ++++--- tests/src/Unit/CdnSettingsTest.php | 4 +++- tests/src/Unit/File/FileUrlGeneratorTest.php | 18 +++++++------- 14 files changed, 124 insertions(+), 61 deletions(-) diff --git a/cdn.install b/cdn.install index 040f054..5924f94 100644 --- a/cdn.install +++ b/cdn.install @@ -29,3 +29,14 @@ function cdn_update_8001() { /** * @} End of "addtogroup updates-8.x-1.x-beta". */ + +/** + * Update the default settings to include the stream_wrappers schema. + */ +function cdn_update_8002() { + $cdn_settings = \Drupal::configFactory()->getEditable('cdn.settings'); + if (is_null($cdn_settings->get('stream_wrappers'))) { + $cdn_settings->set('stream_wrappers', []); + $cdn_settings->save(); + } +} diff --git a/cdn.routing.yml b/cdn.routing.yml index a1c77ff..95d1b33 100644 --- a/cdn.routing.yml +++ b/cdn.routing.yml @@ -1,8 +1,10 @@ -cdn.farfuture.download: - path: '/cdn/farfuture/{security_token}/{mtime}/{scheme}' +# The /cdn/farfuture route has been deprecated and is rewritten +# in CdnFarfuturePathProcessor. +cdn.farfuture_scheme.download: + path: '/cdn/ff/{security_token}/{mtime}/{scheme}' defaults: _controller: '\Drupal\cdn\CdnFarfutureController::download' requirements: _access: 'TRUE' mtime: \d+ - scheme: .+ + scheme: '(:\w+:)|([a-zA-Z0-9+.-]+)' diff --git a/cdn.services.yml b/cdn.services.yml index f9b2ad3..2b5cc43 100644 --- a/cdn.services.yml +++ b/cdn.services.yml @@ -1,7 +1,7 @@ services: cdn.settings: class: Drupal\cdn\CdnSettings - arguments: ['@config.factory'] + arguments: ['@config.factory', '@stream_wrapper_manager'] cdn.file_url_generator: class: Drupal\cdn\File\FileUrlGenerator diff --git a/cdn_ui/js/summaries.js b/cdn_ui/js/summaries.js index 89e0d58..0be8aa3 100644 --- a/cdn_ui/js/summaries.js +++ b/cdn_ui/js/summaries.js @@ -16,13 +16,12 @@ }); $('[data-drupal-selector="edit-wrappers"]').drupalSetSummary(function () { - var additional = $('[data-drupal-selector="edit-wrappers-additional-wrappers"] input:checked'); + var additional = $('[data-drupal-selector="edit-wrappers-stream-wrappers"] input:checked'); var wrappers = []; additional.each(function(index) { wrappers.push(this.getAttribute('value')); }); - var text = wrappers.join(', '); - return text.length ? text : Drupal.t('No additional wrappers'); + return wrappers.join(', '); }); $('[data-drupal-selector="edit-mapping"]').drupalSetSummary(function () { diff --git a/cdn_ui/src/Form/CdnSettingsForm.php b/cdn_ui/src/Form/CdnSettingsForm.php index 47b6209..13b974a 100644 --- a/cdn_ui/src/Form/CdnSettingsForm.php +++ b/cdn_ui/src/Form/CdnSettingsForm.php @@ -171,25 +171,27 @@ class CdnSettingsForm extends ConfigFormBase { '#default_value' => $config->get('farfuture.status'), ]; - $wrappers = array_keys($this->streamWrapperManager - ->getWrappers(StreamWrapperInterface::WRITE_VISIBLE)); + $visibleWrappers = array_keys($this->streamWrapperManager + ->getWrappers(StreamWrapperInterface::VISIBLE)); $localWrappers = array_keys($this->streamWrapperManager - ->getWrappers(StreamWrapperInterface::LOCAL)); - $wrappers = array_diff($wrappers, $localWrappers); - $existingWrappers = $config->get('additional_wrappers'); + ->getWrappers(StreamWrapperInterface::LOCAL_NORMAL)); + $wrappers = array_unique(array_merge($localWrappers, $visibleWrappers)); + $existingWrappers = $config->get('stream_wrappers'); $form['wrappers'] = [ '#type' => 'details', - '#title' => $this->t('Additional stream wrappers'), + '#title' => $this->t('Stream wrappers'), '#group' => 'cdn_settings', - '#access' => $wrappers || $existingWrappers, '#tree' => TRUE, ]; - $form['wrappers']['additional_wrappers'] = [ + $form['wrappers']['stream_wrappers'] = [ '#type' => 'checkboxes', '#options' => array_combine($wrappers, $wrappers), - '#default_value' => !is_null($existingWrappers) ? $existingWrappers : [], - '#description' => $this->t('Additional stream wrappers to rewrite for CDN.') + '#default_value' => array_merge($localWrappers, $existingWrappers), + '#description' => $this->t('Stream wrappers to rewrite for CDN. "Local" stream wrappers are always enabled.') ]; + foreach ($localWrappers as $localWrapper) { + $form['wrappers']['stream_wrappers'][$localWrapper]['#disabled'] = TRUE; + } return parent::buildForm($form, $form_state); } @@ -217,7 +219,7 @@ class CdnSettingsForm extends ConfigFormBase { $config->set('status', (bool) $form_state->getValue('status')); // Vertical tab: 'Additional stream wrappers' - $config->set('additional_wrappers', array_values(array_filter($form_state->getValue(['wrappers', 'additional_wrappers'])))); + $config->set('stream_wrappers', array_values(array_filter($form_state->getValue(['wrappers', 'stream_wrappers'])))); // Vertical tab: 'Mapping'. if ($form_state->getValue(['mapping', 'type']) === 'simple') { diff --git a/config/install/cdn.settings.yml b/config/install/cdn.settings.yml index 5934069..4a68d64 100644 --- a/config/install/cdn.settings.yml +++ b/config/install/cdn.settings.yml @@ -69,4 +69,6 @@ mapping: farfuture: status: true -additional_wrappers: [] +# All LOCAL_NORMAL wrappers are enabled by rule, +# additional wrappers can be added. +stream_wrappers: [] diff --git a/config/schema/cdn.schema.yml b/config/schema/cdn.schema.yml index 8a419ed..e543e5b 100644 --- a/config/schema/cdn.schema.yml +++ b/config/schema/cdn.schema.yml @@ -20,8 +20,8 @@ cdn.settings: status: label: 'Forever cacheable files — status' type: boolean - additional_wrappers: - label: 'Additional stream wrappers for CDN' + stream_wrappers: + label: 'Stream wrappers for CDN' type: sequence sequence: type: string diff --git a/src/CdnFarfutureController.php b/src/CdnFarfutureController.php index ec11763..24e62a1 100644 --- a/src/CdnFarfutureController.php +++ b/src/CdnFarfutureController.php @@ -2,8 +2,10 @@ namespace Drupal\cdn; +use Drupal\cdn\File\FileUrlGenerator; use Drupal\Component\Utility\Crypt; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\Core\File\FileSystemInterface; use Drupal\Core\PrivateKey; use Drupal\Core\Site\Settings; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -29,12 +31,20 @@ class CdnFarfutureController implements ContainerInjectionInterface { protected $privateKey; /** + * The file system service. + * + * @var \Drupal\Core\File\FileSystemInterface + */ + protected $fileSystem; + + /** * @param \Drupal\Core\PrivateKey $private_key * The private key service. */ - public function __construct($root, PrivateKey $private_key) { + public function __construct($root, PrivateKey $private_key, FileSystemInterface $fileSystem) { $this->root = $root; $this->privateKey = $private_key; + $this->fileSystem = $fileSystem; } /** @@ -43,7 +53,8 @@ class CdnFarfutureController implements ContainerInjectionInterface { public static function create(ContainerInterface $container) { return new static( $container->get('app.root'), - $container->get('private_key') + $container->get('private_key'), + $container->get('file_system') ); } @@ -61,6 +72,8 @@ class CdnFarfutureController implements ContainerInjectionInterface { * The file's mtime. * @param string $scheme * The file's scheme. + * @param string $root_relative_file_url + * The relative path from FileUrlGenerator::generate * * @returns \Symfony\Component\HttpFoundation\BinaryFileResponse * The response that will efficiently send the requested file. @@ -73,16 +86,16 @@ class CdnFarfutureController implements ContainerInjectionInterface { * Thrown when an invalid security token is provided. */ public function download(Request $request, $security_token, $mtime, $scheme) { - // Ensure \Drupal\cdn\PathProcessor\CdnFarfuturePathProcessor did its job. - if (!$request->query->has('root_relative_file_url')) { + // Validate the scheme early. + if ($scheme != FileUrlGenerator::RELATIVE && !$this->fileSystem->validScheme($scheme)) { throw new BadRequestHttpException(); } + $path = $request->query->get('root_relative_file_url'); // Validate security token. - $root_relative_file_url = $request->query->get('root_relative_file_url'); - $uri = $scheme == '_shipped' - ? '/' . $root_relative_file_url - : $scheme . '://' . $root_relative_file_url; + $uri = $scheme == FileUrlGenerator::RELATIVE + ? $path + : $scheme . ':/' . $path; // Path comes with a leading slash from the URL. $calculated_token = Crypt::hmacBase64($mtime . $uri, $this->privateKey->get() . Settings::getHashSalt()); if ($security_token !== $calculated_token) { throw new AccessDeniedHttpException('Invalid security token.'); @@ -119,7 +132,7 @@ class CdnFarfutureController implements ContainerInjectionInterface { 'Last-Modified' => 'Wed, 20 Jan 1988 04:20:42 GMT', ]; - if ($scheme == '_shipped') { + if ($scheme == FileUrlGenerator::RELATIVE) { $uri = $this->root . $uri; } $response = new BinaryFileResponse($uri, 200, $farfuture_headers, TRUE, NULL, FALSE, FALSE); diff --git a/src/CdnSettings.php b/src/CdnSettings.php index 8fbfd18..784effc 100644 --- a/src/CdnSettings.php +++ b/src/CdnSettings.php @@ -5,6 +5,8 @@ namespace Drupal\cdn; use Drupal\Component\Utility\Unicode; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Config\ConfigValueException; +use Drupal\Core\StreamWrapper\StreamWrapperInterface; +use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface; /** * Wraps the CDN settings configuration, contains all parsing. @@ -28,14 +30,22 @@ class CdnSettings { protected $lookupTable; /** + * The stream wrapper manager. + * + * @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface + */ + protected $streamWrapperManager; + + /** * Constructs a new CdnSettings object. * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The config factory. */ - public function __construct(ConfigFactoryInterface $config_factory) { + public function __construct(ConfigFactoryInterface $config_factory, StreamWrapperManagerInterface $streamWrapperManager) { $this->rawSettings = $config_factory->get('cdn.settings'); $this->lookupTable = NULL; + $this->streamWrapperManager = $streamWrapperManager; } /** @@ -79,13 +89,15 @@ class CdnSettings { } /** - * Returns all additional stream wrappers to apply CDN to. + * Returns configured stream wrappers to apply CDN to. * * @return array */ - public function additionalWrappers() { - $additional = $this->rawSettings->get('additional_wrappers'); - return $additional ? $additional : []; + public function streamWrappers() { + $configured = $this->rawSettings->get('stream_wrappers'); + // LOCAL_NORMAL stream wrappers are always served. + return array_merge(array_keys($this->streamWrapperManager + ->getWrappers(StreamWrapperInterface::LOCAL_NORMAL)), $configured); } /** diff --git a/src/File/FileUrlGenerator.php b/src/File/FileUrlGenerator.php index ed114db..58929ca 100644 --- a/src/File/FileUrlGenerator.php +++ b/src/File/FileUrlGenerator.php @@ -8,7 +8,6 @@ use Drupal\Component\Utility\Unicode; use Drupal\Core\File\FileSystemInterface; use Drupal\Core\PrivateKey; use Drupal\Core\Site\Settings; -use Drupal\Core\StreamWrapper\StreamWrapperInterface; use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface; use Symfony\Component\HttpFoundation\RequestStack; @@ -19,6 +18,8 @@ use Symfony\Component\HttpFoundation\RequestStack; */ class FileUrlGenerator { + const RELATIVE = ':relative:'; + /** * The app root. * @@ -122,13 +123,14 @@ class FileUrlGenerator { // When farfuture is enabled, rewrite the file URL to let Drupal serve the // file with optimal headers. Only possible if the file exists. if (!$scheme = $this->fileSystem->uriScheme($uri)) { - $scheme = '_shipped'; - $fileUri = $filePath = '/' . $uri; + $scheme = self::RELATIVE; + $fileUri = $filePath = $relative = '/' . $uri; $realFile = $this->root . $fileUri; } else { $fileUri = $realFile = $uri; $filePath = substr($fileUri, strlen($scheme . ':/')); // Leading slash. + $relative = str_replace($this->requestStack->getCurrentRequest()->getSchemeAndHttpHost() . $this->getBasePath(), '', $this->streamWrapperManager->getViaUri($uri)->getExternalUrl()); } if ($this->settings->farfutureIsEnabled() && file_exists($realFile)) { // We do the filemtime() call separately, because a failed filemtime() @@ -140,10 +142,10 @@ class FileUrlGenerator { // they want by manipulating the URL (they could otherwise request // settings.php for example). See https://www.drupal.org/node/1441502. $calculated_token = Crypt::hmacBase64($mtime . $fileUri, $this->privateKey->get() . Settings::getHashSalt()); - return '//' . $cdn_domain . $this->getBasePath() . '/cdn/farfuture/' . $calculated_token . '/' . $mtime . '/' . $scheme . $filePath; + return '//' . $cdn_domain . $this->getBasePath() . '/cdn/ff/' . $calculated_token . '/' . $mtime . '/' . $scheme . $filePath; } - return '//' . $cdn_domain . $this->getBasePath() . '/' . $uri; + return '//' . $cdn_domain . $this->getBasePath() . $relative; } /** @@ -161,9 +163,7 @@ class FileUrlGenerator { $scheme = $this->fileSystem->uriScheme($uri); // Allow additional stream wrappers to be served via CDN. - $streamWrapperTypes = array_merge(array_keys($this->streamWrapperManager - ->getWrappers(StreamWrapperInterface::LOCAL)), - $this->settings->additionalWrappers()); + $streamWrapperTypes = $this->settings->streamWrappers(); // If the URI is absolute — HTTP(S) or otherwise — return early, except if // it's an absolute URI using an approved stream wrapper type. if ($scheme && !in_array($scheme, $streamWrapperTypes)) { diff --git a/src/PathProcessor/CdnFarfuturePathProcessor.php b/src/PathProcessor/CdnFarfuturePathProcessor.php index ec5209e..6f3508d 100644 --- a/src/PathProcessor/CdnFarfuturePathProcessor.php +++ b/src/PathProcessor/CdnFarfuturePathProcessor.php @@ -2,6 +2,7 @@ namespace Drupal\cdn\PathProcessor; +use Drupal\cdn\File\FileUrlGenerator; use Drupal\Core\PathProcessor\InboundPathProcessorInterface; use Symfony\Component\HttpFoundation\Request; @@ -11,6 +12,9 @@ use Symfony\Component\HttpFoundation\Request; * As the route system does not allow arbitrary amount of parameters convert * the file path to a query parameter on the request. * + * Also normalizes legacy far-future URLs generated prior to + * https://www.drupal.org/node/2870435 + * * @see \Drupal\image\PathProcessor\PathProcessorImageStyles */ class CdnFarfuturePathProcessor implements InboundPathProcessorInterface { @@ -19,19 +23,30 @@ class CdnFarfuturePathProcessor implements InboundPathProcessorInterface { * {@inheritdoc} */ public function processInbound($path, Request $request) { - if (strpos($path, '/cdn/farfuture/') !== 0) { + if (!preg_match('/^\/cdn\/(ff|farfuture)\/.*/', $path, $matches)) { return $path; } - // Parse the security token, mtime and root-relative file URL. - $tail = substr($path, strlen('/cdn/farfuture/')); - list($security_token, $mtime, $scheme, $root_relative_file_url) = explode('/', $tail, 4); + // Backwards compatibility for non-scheme aware farfuture paths. + if ($matches[1] == 'farfuture') { + // Normalize legacy path. + // Parse the security token, mtime and root-relative file URL. + $tail = substr($path, strlen('/cdn/farfuture/')); + list($security_token, $mtime, $root_relative_file_url) = explode('/', $tail, 3); + $returnPath = "/cdn/ff/$security_token/$mtime/" . FileUrlGenerator::RELATIVE; + } + else { + // Parse the security token, mtime, scheme and root-relative file URL. + $tail = substr($path, strlen('/cdn/ff/')); + list($security_token, $mtime, $scheme, $root_relative_file_url) = explode('/', $tail, 4); + $returnPath = "/cdn/ff/$security_token/$mtime/$scheme"; + } // Set the root-relative file URL as query parameter. - $request->query->set('root_relative_file_url', $root_relative_file_url); + $request->query->set('root_relative_file_url', '/' . $root_relative_file_url); // Return the same path, but without the trailing file. - return "/cdn/farfuture/$security_token/$mtime/$scheme"; + return $returnPath; } } diff --git a/tests/src/Functional/CdnIntegrationTest.php b/tests/src/Functional/CdnIntegrationTest.php index bc2fc62..89fe79c 100644 --- a/tests/src/Functional/CdnIntegrationTest.php +++ b/tests/src/Functional/CdnIntegrationTest.php @@ -115,7 +115,7 @@ class CdnIntegrationTest extends BrowserTestBase { $this->drupalGet(''); $this->assertSame('MISS', $session->getResponseHeader('X-Drupal-Cache'), 'Changing CDN settings causes Page Cache miss: setting changes have immediate effect.'); $href = $this->cssSelect('link[rel=stylesheet]')[0]->getAttribute('href'); - $regexp = '#//cdn.example.com' . base_path() . 'cdn/farfuture/[a-zA-Z0-9_-]{43}/[0-9]{10}/' . $this->siteDirectory . '/files/css/css_[a-zA-Z0-9_-]{43}\.css\?[a-z0-9]{6}#'; + $regexp = '#//cdn.example.com' . base_path() . 'cdn/ff/[a-zA-Z0-9_-]{43}/[0-9]{10}/public/css/css_[a-zA-Z0-9_-]{43}\.css\?[a-z0-9]{6}#'; $this->assertSame(1, preg_match($regexp, $href)); $this->assertCssFileUsesRootRelativeUrl($this->baseUrl . str_replace('//cdn.example.com', '', $href)); } @@ -159,6 +159,9 @@ class CdnIntegrationTest extends BrowserTestBase { $drupal_js_mtime = filemtime(DRUPAL_ROOT . '/core/misc/drupal.js'); $drupal_js_security_token = Crypt::hmacBase64($drupal_js_mtime . '/core/misc/drupal.js', \Drupal::service('private_key')->get() . Settings::getHashSalt()); + $this->drupalGet('/cdn/ff/' . $drupal_js_security_token . '/' . $drupal_js_mtime . '/:relative:/core/misc/drupal.js'); + $this->assertSession()->statusCodeEquals(200); + // Test backwards-compatible path for relative assets. $this->drupalGet('/cdn/farfuture/' . $drupal_js_security_token . '/' . $drupal_js_mtime . '/core/misc/drupal.js'); $this->assertSession()->statusCodeEquals(200); // Assert presence of headers that \Drupal\cdn\CdnFarfutureController sets. @@ -166,8 +169,8 @@ class CdnIntegrationTest extends BrowserTestBase { // Assert presence of headers that Symfony's BinaryFileResponse sets. $this->assertSame('bytes', $this->getSession()->getResponseHeader('Accept-Ranges')); - // Any chance to the security token should cause a 403. - $this->drupalGet('/cdn/farfuture/' . substr($drupal_js_security_token, 1) . '/' . $drupal_js_mtime . '/core/misc/drupal.js'); + // Any change to the security token should cause a 403. + $this->drupalGet('/cdn/ff/' . substr($drupal_js_security_token, 1) . '/' . $drupal_js_mtime . '/:relative:/core/misc/drupal.js'); $this->assertSession()->statusCodeEquals(403); } diff --git a/tests/src/Unit/CdnSettingsTest.php b/tests/src/Unit/CdnSettingsTest.php index 1ea53e5..4abbd5c 100644 --- a/tests/src/Unit/CdnSettingsTest.php +++ b/tests/src/Unit/CdnSettingsTest.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\cdn\Unit; use Drupal\cdn\CdnSettings; +use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface; use Drupal\Tests\UnitTestCase; /** @@ -449,7 +450,8 @@ class CdnSettingsTest extends UnitTestCase { * The CdnSettings object to test. */ protected function createCdnSettings(array $raw_config) { - return new CdnSettings($this->getConfigFactoryStub(['cdn.settings' => $raw_config])); + $stream_wrapper_manager = $this->prophesize(StreamWrapperManagerInterface::class); + return new CdnSettings($this->getConfigFactoryStub(['cdn.settings' => $raw_config]), $stream_wrapper_manager->reveal()); } } diff --git a/tests/src/Unit/File/FileUrlGeneratorTest.php b/tests/src/Unit/File/FileUrlGeneratorTest.php index 073ec58..9ada8d1 100644 --- a/tests/src/Unit/File/FileUrlGeneratorTest.php +++ b/tests/src/Unit/File/FileUrlGeneratorTest.php @@ -67,10 +67,11 @@ class FileUrlGeneratorTest extends UnitTestCase { ], ], ], - 'farfuture' => [ - 'status' => FALSE, - ], ], + 'farfuture' => [ + 'status' => FALSE, + ], + 'stream_wrappers' => [], ]); $this->assertSame($expected_result, $gen->generate($uri)); } @@ -133,6 +134,7 @@ class FileUrlGeneratorTest extends UnitTestCase { 'farfuture' => [ 'status' => TRUE, ], + 'stream_wrappers' => [], ]; // In root. @@ -140,14 +142,14 @@ class FileUrlGeneratorTest extends UnitTestCase { $this->assertSame('//cdn.example.com/core/misc/does-not-exist.js', $gen->generate('core/misc/does-not-exist.js')); $drupal_js_mtime = filemtime($this->root . '/core/misc/drupal.js'); $drupal_js_security_token = Crypt::hmacBase64($drupal_js_mtime . '/core/misc/drupal.js', static::$privateKey . Settings::getHashSalt()); - $this->assertSame('//cdn.example.com/cdn/farfuture/' . $drupal_js_security_token . '/' . $drupal_js_mtime . '/core/misc/drupal.js', $gen->generate('core/misc/drupal.js')); + $this->assertSame('//cdn.example.com/cdn/ff/' . $drupal_js_security_token . '/' . $drupal_js_mtime . '/:relative:/core/misc/drupal.js', $gen->generate('core/misc/drupal.js')); // In subdir. $gen = $this->createFileUrlGenerator('/subdir', $config); $this->assertSame('//cdn.example.com/subdir/core/misc/does-not-exist.js', $gen->generate('core/misc/does-not-exist.js')); $drupal_js_mtime = filemtime($this->root . '/core/misc/drupal.js'); $drupal_js_security_token = Crypt::hmacBase64($drupal_js_mtime . '/core/misc/drupal.js', static::$privateKey . Settings::getHashSalt()); - $this->assertSame('//cdn.example.com/subdir/cdn/farfuture/' . $drupal_js_security_token . '/' . $drupal_js_mtime . '/core/misc/drupal.js', $gen->generate('core/misc/drupal.js')); + $this->assertSame('//cdn.example.com/subdir/cdn/ff/' . $drupal_js_security_token . '/' . $drupal_js_mtime . '/:relative:/core/misc/drupal.js', $gen->generate('core/misc/drupal.js')); } /** @@ -182,8 +184,8 @@ class FileUrlGeneratorTest extends UnitTestCase { return 'http://example.com' . $base_path . '/sites/default/files/' . substr($current_uri, 9); }); $stream_wrapper_manager = $this->prophesize(StreamWrapperManagerInterface::class); - $stream_wrapper_manager->getWrappers(StreamWrapperInterface::LOCAL) - ->willReturn(['public' => TRUE, 'private' => TRUE]); + $stream_wrapper_manager->getWrappers(StreamWrapperInterface::LOCAL_NORMAL) + ->willReturn(['public' => TRUE]); $stream_wrapper_manager->getViaUri(Argument::that(function ($uri) { return substr($uri, 0, 9) === 'public://'; })) @@ -206,7 +208,7 @@ class FileUrlGeneratorTest extends UnitTestCase { $stream_wrapper_manager->reveal(), $request_stack->reveal(), $private_key->reveal(), - new CdnSettings($this->getConfigFactoryStub(['cdn.settings' => $raw_config])) + new CdnSettings($this->getConfigFactoryStub(['cdn.settings' => $raw_config]), $stream_wrapper_manager->reveal()) ); }