Commit bf498378 authored by catch's avatar catch

Issue #2028109 by tim.plunkett, almaudoh, twistor, larowlan, slashrsm, Berdir,...

Issue #2028109 by tim.plunkett, almaudoh, twistor, larowlan, slashrsm, Berdir, Arla, neclimdul, Cottser: Convert hook_stream_wrappers() to tagged services.
parent 6ba1da1c
......@@ -861,6 +861,23 @@ services:
plugin.manager.element_info:
class: Drupal\Core\Render\ElementInfoManager
parent: default_plugin_manager
stream_wrapper_manager:
class: Drupal\Core\StreamWrapper\StreamWrapperManager
arguments: ['@module_handler']
calls:
- [setContainer, ['@service_container']]
stream_wrapper.public:
class: Drupal\Core\StreamWrapper\PublicStream
tags:
- { name: stream_wrapper, scheme: public }
stream_wrapper.private:
class: Drupal\Core\StreamWrapper\PrivateStream
tags:
- { name: stream_wrapper, scheme: private }
stream_wrapper.temporary:
class: Drupal\Core\StreamWrapper\TemporaryStream
tags:
- { name: stream_wrapper, scheme: temporary }
kernel_destruct_subscriber:
class: Drupal\Core\EventSubscriber\KernelDestructionSubscriber
tags:
......
This diff is collapsed.
......@@ -325,6 +325,12 @@ function install_begin_request(&$install_state) {
->register('path.matcher', 'Drupal\Core\Path\PathMatcher')
->addArgument(new Reference('config.factory'));
// Register the stream wrapper manager.
$container
->register('stream_wrapper_manager', 'Drupal\Core\StreamWrapper\StreamWrapperManager')
->addArgument(new Reference('module_handler'))
->addMethodCall('setContainer', array(new Reference('service_container')));
\Drupal::setContainer($container);
// Determine whether base system services are ready to operate.
......
......@@ -652,16 +652,6 @@ function drupal_install_system($install_state) {
$kernel->getContainer()->get('module_handler')->install(array('system'), FALSE);
\Drupal::service('router.builder')->rebuild();
// DrupalKernel::prepareLegacyRequest() above calls into
// DrupalKernel::bootCode(), which primes file_get_stream_wrappers()'s static
// list of custom stream wrappers that are based on the currently enabled
// list of modules (none).
// @todo Custom stream wrappers of a new module have to be registered as soon
// as the module is installed/enabled. Fix either ModuleHandler::install()
// and/or DrupalKernel::updateModules().
// @see https://drupal.org/node/2028109
drupal_static_reset('file_get_stream_wrappers');
// Ensure default language is saved.
if (isset($install_state['parameters']['langcode'])) {
\Drupal::config('system.site')
......
......@@ -11,6 +11,7 @@
use Drupal\Core\Cache\ListCacheBinsPass;
use Drupal\Core\DependencyInjection\Compiler\BackendCompilerPass;
use Drupal\Core\DependencyInjection\Compiler\StackedKernelPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterStreamWrappersPass;
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\Compiler\ModifyServiceDefinitionsPass;
......@@ -55,6 +56,7 @@ public function register(ContainerBuilder $container) {
// Collect tagged handler services as method calls on consumer services.
$container->addCompilerPass(new TaggedHandlersPass());
$container->addCompilerPass(new RegisterStreamWrappersPass());
// Add a compiler pass for registering event subscribers.
$container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING);
......
<?php
/**
* @file
* Contains Drupal\Core\DependencyInjection\Compiler\RegisterStreamWrappersPass.
*/
namespace Drupal\Core\DependencyInjection\Compiler;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* Adds services tagged 'stream_wrapper' to the stream_wrapper_manager service.
*/
class RegisterStreamWrappersPass implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
if (!$container->hasDefinition('stream_wrapper_manager')) {
return;
}
$stream_wrapper_manager = $container->getDefinition('stream_wrapper_manager');
foreach ($container->findTaggedServiceIds('stream_wrapper') as $id => $attributes) {
$class = $container->getDefinition($id)->getClass();
$scheme = $attributes[0]['scheme'];
$stream_wrapper_manager->addMethodCall('addStreamWrapper', array($id, $class, $scheme));
}
}
}
......@@ -389,6 +389,7 @@ public function boot() {
$seed = unpack("L", Crypt::randomBytes(4));
mt_srand($seed[1]);
$this->container->get('stream_wrapper_manager')->register();
$this->booted = TRUE;
return $this;
......@@ -401,6 +402,7 @@ public function shutdown() {
if (FALSE === $this->booted) {
return;
}
$this->container->get('stream_wrapper_manager')->unregister();
$this->booted = FALSE;
$this->container = NULL;
$this->moduleList = NULL;
......@@ -430,9 +432,6 @@ public function preHandle(Request $request) {
// Put the request on the stack.
$this->container->get('request_stack')->push($request);
// Make sure all stream wrappers are registered.
file_get_stream_wrappers();
// Set the allowed protocols once we have the config available.
$allowed_protocols = $this->container->get('config.factory')->get('system.filter')->get('protocols');
if (!isset($allowed_protocols)) {
......
......@@ -865,8 +865,7 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
// via Drush, as the 'translations' stream wrapper is provided by
// Interface Translation module and is later used to import
// translations.
drupal_static_reset('file_get_stream_wrappers');
file_get_stream_wrappers();
\Drupal::service('stream_wrapper_manager')->register();
// Update the theme registry to include it.
drupal_theme_rebuild();
......
......@@ -42,6 +42,13 @@ abstract class LocalStream implements StreamWrapperInterface {
*/
protected $uri;
/**
* {@inheritdoc}
*/
public static function getType() {
return StreamWrapperInterface::NORMAL;
}
/**
* Gets the path that the wrapper is responsible for.
*
......
......@@ -19,6 +19,27 @@ class PrivateStream extends LocalStream {
use UrlGeneratorTrait;
/**
* {@inheritdoc}
*/
public static function getType() {
return StreamWrapperInterface::LOCAL_NORMAL;
}
/**
* {@inheritdoc}
*/
public function getName() {
return t('Private files');
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return t('Private local files served by Drupal.');
}
/**
* Implements Drupal\Core\StreamWrapper\LocalStream::getDirectoryPath()
*/
......
......@@ -18,6 +18,27 @@
*/
class PublicStream extends LocalStream {
/**
* {@inheritdoc}
*/
public static function getType() {
return StreamWrapperInterface::LOCAL_NORMAL;
}
/**
* {@inheritdoc}
*/
public function getName() {
return t('Public files');
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return t('Public local files served by the webserver.');
}
/**
* {@inheritdoc}
*/
......
......@@ -29,6 +29,100 @@
*/
interface StreamWrapperInterface extends PhpStreamWrapperInterface {
/**
* Stream wrapper bit flags that are the basis for composite types.
*
* Note that 0x0002 is skipped, because it was the value of a constant that
* has since been removed.
*/
/**
* A filter that matches all wrappers.
*/
const ALL = 0x0000;
/**
* Refers to a local file system location.
*/
const LOCAL = 0x0001;
/**
* Wrapper is readable (almost always true).
*/
const READ = 0x0004;
/**
* Wrapper is writeable.
*/
const WRITE = 0x0008;
/**
* Exposed in the UI and potentially web accessible.
*/
const VISIBLE = 0x0010;
/**
* Composite stream wrapper bit flags that are usually used as the types.
*/
/**
* Not visible in the UI or accessible via web, but readable and writable.
* E.g. the temporary directory for uploads.
*/
const HIDDEN = 0x000C;
/**
* Hidden, readable and writeable using local files.
*/
const LOCAL_HIDDEN = 0x000D;
/**
* Visible, readable and writeable.
*/
const WRITE_VISIBLE = 0x001C;
/**
* Visible and read-only.
*/
const READ_VISIBLE = 0x0014;
/**
* This is the default 'type' falg. This does not include
* StreamWrapperInterface::LOCAL, because PHP grants a greater trust level to
* local files (for example, they can be used in an "include" statement,
* regardless of the "allow_url_include" setting), so stream wrappers need to
* explicitly opt-in to this.
*/
const NORMAL = 0x001C;
/**
* Visible, readable and writeable using local files.
*/
const LOCAL_NORMAL = 0x001D;
/**
* Returns the type of stream wrapper.
*
* @return int
*/
public static function getType();
/**
* Returns the name of the stream wrapper for use in the UI.
*
* @return string
* The stream wrapper name.
*/
public function getName();
/**
* Returns the description of the stream wrapper for use in the UI.
*
* @return string
* The stream wrapper description.
*/
public function getDescription();
/**
* Sets the absolute stream resource URI.
*
......
This diff is collapsed.
......@@ -15,6 +15,27 @@
*/
class TemporaryStream extends LocalStream {
/**
* {@inheritdoc}
*/
public static function getType() {
return StreamWrapperInterface::LOCAL_HIDDEN;
}
/**
* {@inheritdoc}
*/
public function getName() {
return t('Temporary files');
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return t('Temporary local files for upload and previews.');
}
/**
* Implements Drupal\Core\StreamWrapper\LocalStream::getDirectoryPath()
*/
......
......@@ -75,9 +75,7 @@ public function boot() {
simpletest_classloader_register();
// Register System module stream wrappers and create the build/artifacts
// directory if necessary.
file_get_stream_wrappers();
// Create the build/artifacts directory if necessary.
if (!is_dir('public://simpletest')) {
mkdir('public://simpletest', 0777, TRUE);
}
......
......@@ -5,6 +5,7 @@
* Administration functions for editor.module.
*/
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\editor\Entity\Editor;
......@@ -51,10 +52,7 @@ function editor_image_upload_settings_form(Editor $editor) {
// Any visible, writable wrapper can potentially be used for uploads,
// including a remote file system that integrates with a CDN.
$stream_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE);
foreach ($stream_wrappers as $scheme => $info) {
$options[$scheme] = $info['description'];
}
$options = \Drupal::service('stream_wrapper_manager')->getDescriptions(StreamWrapperInterface::WRITE_VISIBLE);
if (!empty($options)) {
$form['scheme'] = array(
'#type' => 'radios',
......@@ -68,8 +66,8 @@ function editor_image_upload_settings_form(Editor $editor) {
// Set data- attributes with human-readable names for all possible stream
// wrappers, so that drupal.ckeditor.drupalimage.admin's summary rendering
// can use that.
foreach ($stream_wrappers as $scheme => $info) {
$form['scheme'][$scheme]['#attributes']['data-label'] = t('Storage: @name', array('@name' => $info['name']));
foreach (\Drupal::service('stream_wrapper_manager')->getNames(StreamWrapperInterface::WRITE_VISIBLE) as $scheme => $name) {
$form['scheme'][$scheme]['#attributes']['data-label'] = t('Storage: @name', array('@name' => $name));
}
$form['directory'] = array(
......
......@@ -13,6 +13,7 @@
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Drupal\Core\TypedData\DataDefinition;
/**
......@@ -133,10 +134,7 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state
),
);
$scheme_options = array();
foreach (file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE) as $scheme => $stream_wrapper) {
$scheme_options[$scheme] = $stream_wrapper['name'];
}
$scheme_options = \Drupal::service('stream_wrapper_manager')->getNames(StreamWrapperInterface::WRITE_VISIBLE);
$element['uri_scheme'] = array(
'#type' => 'radios',
'#title' => t('Upload destination'),
......
......@@ -13,29 +13,6 @@
const FILE_URL_TEST_CDN_1 = 'http://cdn1.example.com';
const FILE_URL_TEST_CDN_2 = 'http://cdn2.example.com';
/**
* Implements hook_stream_wrappers().
*/
function file_test_stream_wrappers() {
return array(
'dummy' => array(
'name' => t('Dummy files'),
'class' => 'Drupal\file_test\DummyStreamWrapper',
'description' => t('Dummy wrapper for simpletest.'),
),
'dummy-remote' => array(
'name' => t('Dummy files (remote)'),
'class' => 'Drupal\file_test\DummyRemoteStreamWrapper',
'description' => t('Dummy wrapper for simpletest (remote).'),
),
'dummy-readonly' => array(
'name' => t('Dummy files (readonly)'),
'class' => 'Drupal\file_test\DummyReadOnlyStreamWrapper',
'description' => t('Dummy wrapper for simpletest (readonly).'),
),
);
}
/**
* Reset/initialize the history of calls to the file_* hooks.
*
......
services:
stream_wrapper.dummy_readonly:
class: Drupal\file_test\StreamWrapper\DummyReadOnlyStreamWrapper
tags:
- { name: stream_wrapper, scheme: dummy-readonly }
stream_wrapper.dummy_remote:
class: Drupal\file_test\StreamWrapper\DummyRemoteStreamWrapper
tags:
- { name: stream_wrapper, scheme: dummy-remote }
stream_wrapper.dummy:
class: Drupal\file_test\StreamWrapper\DummyStreamWrapper
tags:
- { name: stream_wrapper, scheme: dummy }
......@@ -2,10 +2,10 @@
/**
* @file
* Definition of Drupal\file_test\DummyReadOnlyStreamWrapper.
* Contains \Drupal\file_test\StreamWrapper\DummyReadOnlyStreamWrapper.
*/
namespace Drupal\file_test;
namespace Drupal\file_test\StreamWrapper;
use Drupal\Core\StreamWrapper\LocalReadOnlyStream;
......@@ -15,6 +15,21 @@
* Dummy stream wrapper implementation (dummy-readonly://).
*/
class DummyReadOnlyStreamWrapper extends LocalReadOnlyStream {
/**
* {@inheritdoc}
*/
public function getName() {
return t('Dummy files (readonly)');
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return t('Dummy wrapper for simpletest (readonly).');
}
function getDirectoryPath() {
return conf_path() . '/files';
}
......
......@@ -2,10 +2,10 @@
/**
* @file
* Definition of Drupal\file_test\DummyRemoteStreamWrapper.
* Contains \Drupal\file_test\StreamWrapper\DummyRemoteStreamWrapper.
*/
namespace Drupal\file_test;
namespace Drupal\file_test\StreamWrapper;
use Drupal\Core\StreamWrapper\PublicStream;
......@@ -17,6 +17,21 @@
* Basically just the public scheme but not returning a local file for realpath.
*/
class DummyRemoteStreamWrapper extends PublicStream {
/**
* {@inheritdoc}
*/
public function getName() {
return t('Dummy files (remote)');
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return t('Dummy wrapper for simpletest (remote).');
}
function realpath() {
return FALSE;
}
......
......@@ -2,10 +2,10 @@
/**
* @file
* Definition of Drupal\file_test\DummyStreamWrapper.
* Contains \Drupal\file_test\StreamWrapper\DummyStreamWrapper.
*/
namespace Drupal\file_test;
namespace Drupal\file_test\StreamWrapper;
use Drupal\Core\StreamWrapper\LocalStream;
......@@ -15,6 +15,21 @@
* Dummy stream wrapper implementation (dummy://).
*/
class DummyStreamWrapper extends LocalStream {
/**
* {@inheritdoc}
*/
public function getName() {
return t('Dummy files');
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return t('Dummy wrapper for simpletest.');
}
function getDirectoryPath() {
return conf_path() . '/files';
}
......
......@@ -19,6 +19,7 @@
use Drupal\image\ImageStyleInterface;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
/**
......@@ -247,7 +248,7 @@ public function flush($path = NULL) {
}
// Delete the style directory in each registered wrapper.
$wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE);
$wrappers = \Drupal::service('stream_wrapper_manager')->getWrappers(StreamWrapperInterface::WRITE_VISIBLE);
foreach ($wrappers as $wrapper => $wrapper_data) {
if (file_exists($directory = $wrapper . '://styles/' . $this->id())) {
file_unmanaged_delete_recursive($directory);
......
......@@ -11,6 +11,7 @@
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\file\Entity\File;
use Drupal\file\Plugin\Field\FieldType\FileItem;
......@@ -168,10 +169,7 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state
// the field.
$settings = $this->getFieldDefinition()->getFieldStorageDefinition()->getSettings();
$scheme_options = array();
foreach (file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE) as $scheme => $stream_wrapper) {
$scheme_options[$scheme] = $stream_wrapper['name'];
}
$scheme_options = \Drupal::service('stream_wrapper_manager')->getNames(StreamWrapperInterface::WRITE_VISIBLE);
$element['uri_scheme'] = array(
'#type' => 'radios',
'#title' => t('Upload destination'),
......
......@@ -195,21 +195,6 @@ function locale_theme() {
);
}
/**
* Implements hook_stream_wrappers().
*/
function locale_stream_wrappers() {
$wrappers = array(
'translations' => array(
'name' => new TranslationWrapper('Translation files'),
'class' => 'Drupal\locale\TranslationsStream',
'description' => new TranslationWrapper('Translation files'),
'type' => STREAM_WRAPPERS_LOCAL_HIDDEN,
),
);
return $wrappers;
}
/**
* Implements hook_ENTITY_TYPE_insert() for 'configurable_language'.
*/
......
......@@ -16,3 +16,7 @@ services:
tags:
- { name: string_translator }
- { name: needs_destruction }
stream_wrapper.translations:
class: Drupal\locale\StreamWrapper\TranslationsStream
tags:
- { name: stream_wrapper, scheme: translations }
<?php
/**
* @file
* Contains \Drupal\locale\StreamWrapper\TranslationStream.
*/
namespace Drupal\locale\StreamWrapper;
use Drupal\Core\Annotation\StreamWrapper;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\StreamWrapper\LocalStream;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
/**
* Defines a Drupal translations (translations://) stream wrapper class.
*
* Provides support for storing translation files.
*/
class TranslationsStream extends LocalStream {
/**
* {@inheritdoc}
*/
public static function getType() {
return StreamWrapperInterface::LOCAL_HIDDEN;
}
/**
* {@inheritdoc}
*/
public function getName() {
return t('Translation files');
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return t('Translation files');
}
/**
* Implements Drupal\Core\StreamWrapper\LocalStream::getDirectoryPath()
*/
function getDirectoryPath() {
return \Drupal::config('locale.settings')->get('translation.path');
}
/**
* Implements Drupal\Core\StreamWrapper\StreamWrapperInterface::getExternalUrl().
* @throws \LogicException PO files URL should not be public.
*/
function getExternalUrl() {
throw new \LogicException('PO files URL should not be public.');
}
}
......@@ -16,6 +16,7 @@
use Drupal\Core\Language\Language;
use Drupal\Core\Site\Settings;
use Symfony\Component\DependencyInjection\Parameter;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpFoundation\Request;
......@@ -78,13 +79,11 @@ abstract class KernelTestBase extends TestBase {
protected $keyValueFactory;
/**
* A list of stream wrappers that have been registered for this test.
*
* @see \Drupal\simpletest\KernelTestBase::registerStreamWrapper()
* Array of registered stream wrappers.