diff --git a/config/schema/flysystem.schema.yml b/config/schema/flysystem.schema.yml new file mode 100644 index 0000000000000000000000000000000000000000..52c28407b827a0710a4523ac9d5748fd984d2271 --- /dev/null +++ b/config/schema/flysystem.schema.yml @@ -0,0 +1,32 @@ +flysystem.flysystem_adapter_config.*: + type: config_entity + label: flysystem_adapter_config + mapping: + id: + type: string + label: ID + label: + type: label + label: Label + uuid: + type: string + description: + type: string + label: 'Description' + status: + type: boolean + label: 'Status' + sub_adapter: + type: string + label: 'Adapter Plugin ID' + sub_adapter_config: + type: plugin.plugin_configuration.flysystem_adapter_config.[%parent.backend] + langcode: + type: string + label: 'Language code' + dependencies: + type: config_dependencies + label: 'Dependencies' + +plugin.plugin_configuration.flysystem_adapter_config.*: + type: mapping diff --git a/flysystem.info.yml b/flysystem.info.yml index 7696651a77efe5d26fde6cdd2f593a73e353a053..5db4e1f56c05ac342794d7b704b141f8c9bf794c 100644 --- a/flysystem.info.yml +++ b/flysystem.info.yml @@ -1,5 +1,5 @@ name: Flysystem +type: module description: 'Provides access to various filesystem backends using Flysystem.' package: Flysystem -type: module -core_version_requirement: ^9.5 || ^10.0 +core_version_requirement: ^10.3 diff --git a/flysystem.links.action.yml b/flysystem.links.action.yml new file mode 100644 index 0000000000000000000000000000000000000000..0b396c0c6761ab2fbf0caf68c9f3a2f42546dd0e --- /dev/null +++ b/flysystem.links.action.yml @@ -0,0 +1,5 @@ +entity.flysystem_adapter_config.add_form: + route_name: 'entity.flysystem_adapter_config.add_form' + title: 'Configure a Flysystem Adapter' + appears_on: + - entity.flysystem_adapter_config.collection diff --git a/flysystem.links.menu.yml b/flysystem.links.menu.yml index 38140dbe3231fffe94104800a79ba2debce2baf6..8491e60a1d4525055a8604c7cf6698f321733299 100644 --- a/flysystem.links.menu.yml +++ b/flysystem.links.menu.yml @@ -1,5 +1,5 @@ -flysystem.config: - title: 'Flysystem' - description: 'Configure settings related to Flysystem.' - parent: system.file_system_settings - route_name: flysystem.config +entity.flysystem_adapter_config.overview: + title: Configured Flysystem Adapters + parent: system.admin_config_media + description: 'List of configured Flysystem Adapters.' + route_name: entity.flysystem_adapter_config.collection diff --git a/flysystem.permissions.yml b/flysystem.permissions.yml index fd3ff5a48f18b96f09271dce27678bf290146c1a..a734df252009d97820c128c9817507ff134b3cc3 100644 --- a/flysystem.permissions.yml +++ b/flysystem.permissions.yml @@ -1,4 +1,4 @@ -administer flysystem: - title: 'Administer Flysystem' - description: 'Sync Flysystem filesystems.' - restrict access: 'TRUE' +administer flysystem_adapter_config: + title: 'Manage configuration of Flysystem Adapters' + descriiption: 'Manage configuration of League/Flysystem Filesystem Adapters' + restricted: TRUE \ No newline at end of file diff --git a/flysystem.routing.yml b/flysystem.routing.yml index 0eb3d82306f4d9ea5b751c9dd5bb3801e41bde94..0bb0d4350045855099b7c47215cfe5f889859cfc 100644 --- a/flysystem.routing.yml +++ b/flysystem.routing.yml @@ -1,35 +1,31 @@ -flysystem.files: - path: '/_flysystem/{scheme}' +entity.flysystem_adapter_config.collection: + path: '/admin/config/media/flysystem-adapter-config' defaults: - _controller: 'Drupal\system\FileDownloadController::download' - _disable_route_normalizer: 'TRUE' + _entity_list: 'flysystem_adapter_config' + _title: 'Configured Flysystem Adapters' requirements: - # Permissions are handled through Drupal file create / update permissions - _access: 'TRUE' - scheme: '^[a-zA-Z0-9+.-]+$' - options: - _maintenance_access: 'TRUE' + _permission: 'administer flysystem_adapter_config' -flysystem.serve: - path: '/_flysystem/{scheme}/{filepath}' +entity.flysystem_adapter_config.add_form: + path: '/admin/config/media/flysystem_adapter_config/add' defaults: - _controller: 'Drupal\system\FileDownloadController::download' - _disable_route_normalizer: 'TRUE' + _entity_form: 'flysystem_adapter_config.add' + _title: 'Configure a Flysystem Adapter' requirements: - # Permissions are handled through Drupal access content permissions - _access: 'TRUE' - scheme: '^[a-zA-Z0-9+.-]+$' - filepath: .+ - options: - _maintenance_access: 'TRUE' + _permission: 'administer flysystem_adapter_config' -flysystem.config: - path: '/admin/config/media/file-system/flysystem' +entity.flysystem_adapter_config.edit_form: + path: '/admin/config/media/flysystem-adapter-config/{flysystem_adapter_config}' defaults: - _form: 'Drupal\flysystem\Form\ConfigForm' - _title: Flysystem + _entity_form: 'flysystem_adapter_config.edit' + _title: 'Modify configuration for a Flysystem Adapter' requirements: - _permission: 'administer flysystem' + _permission: 'administer flysystem_adapter_config' -route_callbacks: - - 'Drupal\flysystem\Routing\FlysystemRoutes::routes' +entity.flysystem_adapter_config.delete_form: + path: '/admin/config/media/flysystem-adapter-config/{flysystem_adapter_config}/delete' + defaults: + _entity_form: 'flysystem_adapter_config.delete' + _title: 'Delete a configured Flysystem Adapter' + requirements: + _permission: 'administer flysystem_adapter_config' diff --git a/flysystem.services.yml b/flysystem.services.yml index b86019827f00c089ccc2dedb58c5276441caee18..8cb0bc5c75685499320dd8cbe4d897eb92a46595 100644 --- a/flysystem.services.yml +++ b/flysystem.services.yml @@ -6,6 +6,7 @@ services: decorates: file_system decoration_priority: 5 arguments: ['@flysystem.filesystem.inner', '@stream_wrapper_manager', '@settings'] + cache.flysystem: class: Drupal\Core\Cache\CacheBackendInterface tags: @@ -17,17 +18,6 @@ services: parent: logger.channel_base arguments: ['flysystem'] - # @todo Remove plugin manager, implement Adapters instead. - # Should there be an adapter, or adapter manager service? - #plugin.manager.flysystem: - # class: Drupal\flysystem\Plugin\FlysystemPluginManager - # arguments: ['@container.namespaces', '@cache.discovery', '@module_handler'] - - # @todo Rename to a Stream wrapper instead of a factory - #flysystem_factory: - # class: Drupal\flysystem\FlysystemStreamWrapperManager - # arguments: ['@plugin.manager.flysystem', '@stream_wrapper_manager', '@cache.flysystem', '@event_dispatcher', '@settings'] - path_processor.flysystem: class: Drupal\flysystem\PathProcessor\FlysystemPathProcessor tags: @@ -39,6 +29,6 @@ services: tags: - { name: event_subscriber } - flysystem.asset.css.dumper: - class: Drupal\flysystem\Asset\AssetDumper - arguments: ['@file_system', '@config.factory'] + plugin.manager.flysystem_adapter: + class: Drupal\flysystem\FlysystemAdapterPluginManager + parent: default_plugin_manager \ No newline at end of file diff --git a/modules/config/schema/flysystem_local.adapter.schema.yml b/modules/config/schema/flysystem_local.adapter.schema.yml new file mode 100644 index 0000000000000000000000000000000000000000..6d9aeccc4602b5e9136646c6d74500953c297f65 --- /dev/null +++ b/modules/config/schema/flysystem_local.adapter.schema.yml @@ -0,0 +1,31 @@ +plugin.plugin_configuration.flysystem_adapter_config.flysystem_local: + type: mapping + label: 'Flysystem Local Filesystem settings' + mapping: + schema: + type: 'string' + label: 'File management schema' + root_directory: + type: 'string' + label: 'File Storage directory, relative to Drupal root' + permissions: + file: + public: + type: integer + label: 'Public file permissions' + private: + type: integer + label: 'Private file permissions' + dir: + public: + type: integer + label: 'Public directory permissions' + private: + type: integer + label: 'Private directory permissions' + write_flags: + type: integer + label: 'Write flags' + link_handling: + type: integer + label: 'Links handling (disallow or skip)' diff --git a/modules/flysystem_local/flysystem_local.info.yml b/modules/flysystem_local/flysystem_local.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..11e0c753b7f5cc373388acba74fb4e1f0260ef75 --- /dev/null +++ b/modules/flysystem_local/flysystem_local.info.yml @@ -0,0 +1,5 @@ +name: 'Flysystem Local Filesytem Adapter' +type: module +description: 'Flysystem Local Filesystem Adapter.' +package: 'Flysystem' +core_version_requirement: ^10 diff --git a/modules/flysystem_local/src/Plugin/FlysystemAdapter/LocalAdapter.php b/modules/flysystem_local/src/Plugin/FlysystemAdapter/LocalAdapter.php new file mode 100644 index 0000000000000000000000000000000000000000..7c37904d1fcd3591bcf7eda29aa234eae49dcec9 --- /dev/null +++ b/modules/flysystem_local/src/Plugin/FlysystemAdapter/LocalAdapter.php @@ -0,0 +1,129 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\flysystem_local\Plugin\FlysystemAdapter; + +use Drupal\Core\Form\FormStateInterface; +use Drupal\flysystem\FlysystemSubAdapterInterface; +use Drupal\flysystem\Plugin\FlysystemAdapterPluginBase; +use League\Flysystem\Local\LocalFilesystemAdapter; + +/** + * Plugin implementation of the flysystem_adapter. + * + * @FlysystemAdapter( + * id = "local_adapter", + * label = @Translation("Local Adapter"), + * description = @Translation("Flysystem Local Adapter.") + * ) + */ +final class LocalAdapter extends FlysystemAdapterPluginBase implements FlysystemSubAdapterInterface { + + /** + * The flysystem_subadapter ID. + */ + protected string $id; + + /** + * The flysystem_subadapter label. + */ + protected string $label; + + /** + * The flysystem_subadapter description. + */ + protected string $description; + + /** + * {@inheritdoc} + */ + public function getDescription() { + return !empty($this->description) ? $this->description : ''; + } + + /** + * {@inheritdoc} + */ + public function buildSubConfigurationForm(array $form, FormStateInterface $form_state, $flysystem_adapter_config) { + $form['schema'] = [ + '#type' => 'textfield', + '#title' => $this->t('Schema'), + '#maxlength' => 255, + '#description' => $this->t('The schema used to identify files managed by this adapter.'), + // @todo determine how to retrieve saved values once form is saved. + '#default_value' => $flysystem_adapter_config->getSubAdapterConfigItem('schema'), + '#required' => TRUE, + ]; + $form['root_directory'] = [ + '#type' => 'textfield', + '#title' => $this->t('File Storage Location'), + '#maxlength' => 255, + '#description' => $this->t('File Storage directory, relative to Drupal root.'), + // @todo determine how to retrieve saved values once form is saved. + '#default_value' => $flysystem_adapter_config->getSubAdapterConfigItem('root_directory'), + '#required' => TRUE, + ]; + $form['permissions']['file']['public'] = [ + '#type' => 'number', + '#title' => $this->t('Public file permissions'), + '#description' => $this->t('Default permissions for public files.'), + // @todo determine how to retrieve saved values once form is saved. + '#default_value' => $flysystem_adapter_config->getSubAdapterConfigItem(['permissions', 'file', 'public']) ?? '0755', + ]; + $form['permissions']['file']['private'] = [ + '#type' => 'number', + '#title' => $this->t('Private file permissions'), + '#description' => $this->t('Default permissions for private files.'), + // @todo determine how to retrieve saved values once form is saved. + '#default_value' => $flysystem_adapter_config->getSubAdapterConfigItem(['permissions', 'file', 'private']) ?? '0755', + ]; + $form['permissions']['dir']['public'] = [ + '#type' => 'number', + '#title' => $this->t('Public directory permissions'), + '#description' => $this->t('Default permissions for public directories.'), + // @todo determine how to retrieve saved values once form is saved. + '#default_value' => $flysystem_adapter_config->getSubAdapterConfigItem(['permissions', 'dir', 'public']) ?? '0755', + ]; + $form['permissions']['dir']['private'] = [ + '#type' => 'number', + '#title' => $this->t('Private directory permissions'), + '#description' => $this->t('Default permissions for private directories.'), + // @todo determine how to retrieve saved values once form is saved. + '#default_value' => $flysystem_adapter_config->getSubAdapterConfigItem(['permissions', 'dir', 'private']) ?? '0755', + ]; + $form['write_flags'] = [ + '#type' => 'hidden', + '#title' => $this->t('Write flags'), + // @todo convert to proper translated entity. + '#description' => $this->t('Lock types for file resources, reference <a href="https://www.php.net/manual/en/function.flock.php#:~:text=operation-,operation,-is%20one%20of">PHP file locking operations</a> for more information.'), + // @todo determine how to retrieve saved values once form is saved. + '#default_value' => (strlen($flysystem_adapter_config->getSubAdapterConfigItem('write_flag')) == 0) ? LOCK_EX : NULL, + ]; + $form['link_handling'] = [ + '#type' => 'hidden', + '#title' => $this->t('Link handler flags'), + // @todo convert to proper translated entity. + '#description' => $this->t('How to handle links, reference <a href="https://flysystem.thephpleague.com/docs/adapter/local/">Local File Adatper, advanced usage</a> for more information.'), + // @todo determine how to retrieve saved values once form is saved. + '#default_value' => (strlen($flysystem_adapter_config->getSubAdapterConfigItem('link_handling')) == 0) ? LocalFilesystemAdapter::DISALLOW_LINKS : NULL, + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { + + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + + } + +} diff --git a/src/Adapter/CacheItem.php b/src/Adapter/CacheItem.php deleted file mode 100644 index 8c1e22e4907913964513ea8ddb7e078799f348e6..0000000000000000000000000000000000000000 --- a/src/Adapter/CacheItem.php +++ /dev/null @@ -1,45 +0,0 @@ -<?php - -namespace Drupal\flysystem\Adapter; - -/** - * A filesystem item stored in the Drupal cache. - */ -class CacheItem { - - /** - * The array of metadata for the item. - * - * @var array - */ - protected $metadata = []; - - /** - * Returns the metadata for the item. - * - * @return array - * The array of metadata for the item. - */ - public function getMetadata() { - return $this->metadata; - } - - /** - * Updates the metadata for the item. - * - * @param array $metadata - * The array of metadata for the item. - */ - public function updateMetadata(array $metadata) { - static $keys = [ - 'fileSize' => TRUE, - 'mimeType' => TRUE, - 'visibility' => TRUE, - 'lastModified' => TRUE, - 'type' => TRUE, - ]; - - $this->metadata = array_intersect_key($metadata, $keys) + $this->metadata; - } - -} diff --git a/src/Adapter/CacheItemBackend.php b/src/Adapter/CacheItemBackend.php deleted file mode 100644 index 265bed7da22f97cd6685f233e4037e46fc1b26ba..0000000000000000000000000000000000000000 --- a/src/Adapter/CacheItemBackend.php +++ /dev/null @@ -1,135 +0,0 @@ -<?php - -namespace Drupal\flysystem\Adapter; - -use Drupal\Component\Utility\Crypt; -use Drupal\Core\Cache\CacheBackendInterface; - -/** - * Storage backend for cache items. - * - * This class is separated out from CacheItems so we can easily test loading, - * saving, and deleting separately from the logic to reach back to a child - * Flysystem adapter. - */ -class CacheItemBackend { - - /** - * The Drupal cache backend to store data in. - * - * @var \Drupal\Core\Cache\CacheBackendInterface - */ - protected $cacheBackend; - - /** - * The scheme this cache is managing. - * - * @var string - */ - protected $scheme; - - /** - * Constructs a new CacheItemBackend. - * - * @param string $scheme - * The scheme being managed by the cache. - * @param \Drupal\Core\Cache\CacheBackendInterface $cacheBackend - * The Drupal cache backend to store items in. - */ - public function __construct($scheme, CacheBackendInterface $cacheBackend) { - $this->scheme = $scheme; - $this->cacheBackend = $cacheBackend; - } - - /** - * Returns whether the cache item exists. - * - * @param string $path - * The path of the cache item. - * - * @return bool - * True if the item exists, false if not. - */ - public function has($path) { - $cacheKey = $this->getCacheKey($path); - - if (isset($this->cacheItems[$cacheKey])) { - return TRUE; - } - - return (bool) $this->cacheBackend->get($cacheKey); - } - - /** - * Loads a cache item for a given path. - * - * @param string $path - * The path of the item to load. - * - * @return \Drupal\flysystem\Adapter\CacheItem - * The cache item, or a new cache item if one isn't in the cache. - */ - public function load($path) { - $key = $this->getCacheKey($path); - - if ($cached = $this->cacheBackend->get($key)) { - /** @var \Drupal\flysystem\Adapter\CacheItem $item */ - $item = $cached->data; - } - else { - $item = new CacheItem(); - } - - return $item; - } - - /** - * Sets a cache item in the backend. - * - * @param string $path - * The file path. - * @param \Drupal\flysystem\Adapter\CacheItem $item - * The item to set. - */ - public function set($path, CacheItem $item) { - $this->cacheBackend->set($this->getCacheKey($path), $item); - } - - /** - * Deletes an item by the path. - * - * @param string $path - * The path of the item to delete. - */ - public function delete($path) { - $this->deleteMultiple([$path]); - } - - /** - * Deletes multiple paths. - * - * @param array $paths - * The array of paths to delete. - */ - public function deleteMultiple(array $paths) { - $keys = []; - foreach ($paths as $path) { - $keys[] = $this->getCacheKey($path); - } - $this->cacheBackend->deleteMultiple($keys); - } - - /** - * Gets the cache key for a cache item. - * - * @param string $path - * The path of the cache item. - * - * @return string - * A hashed key suitable for use in a cache. - */ - protected function getCacheKey($path) { - return Crypt::hashBase64($this->scheme . '://' . $path); - } - -} diff --git a/src/Adapter/DrupalCacheAdapter.php b/src/Adapter/DrupalCacheAdapter.php deleted file mode 100644 index c42803078fba852d71a905989ea8ce2c567870fb..0000000000000000000000000000000000000000 --- a/src/Adapter/DrupalCacheAdapter.php +++ /dev/null @@ -1,288 +0,0 @@ -<?php - -namespace Drupal\flysystem\Adapter; - -use League\Flysystem\Config; -use League\Flysystem\FileAttributes; -use League\Flysystem\FilesystemAdapter; - -/** - * A Flysystem adapter implementing caching with Drupal's Cache API. - */ -class DrupalCacheAdapter implements FilesystemAdapter { - - /** - * The Flysystem adapter to cache data for. - * - * @var \League\Flysystem\FilesystemAdapter - */ - protected FilesystemAdapter $adapter; - - /** - * The cache backend to store data in. - * - * @var \Drupal\flysystem\Adapter\CacheItemBackend - */ - protected CacheItemBackend $cacheItemBackend; - - /** - * The scheme of the stream wrapper used for this adapter. - * - * @var string - */ - protected string $scheme; - - /** - * Constructs a new caching Flysystem adapter. - * - * @param string $scheme - * The scheme of the stream wrapper used for this adapter. - * @param \League\Flysystem\FilesystemAdapter $adapter - * The flysystem adapter to cache data for. - * @param \Drupal\flysystem\Adapter\CacheItemBackend $cacheItemBackend - * The cache backend to store data in. - */ - public function __construct(string $scheme, FilesystemAdapter $adapter, CacheItemBackend $cacheItemBackend) { - $this->scheme = $scheme; - $this->adapter = $adapter; - $this->cacheItemBackend = $cacheItemBackend; - } - - /** - * {@inheritdoc} - */ - public function write($path, $contents, Config $config): void { - $this->adapter->write($path, $contents, $config); - } - - /** - * {@inheritdoc} - */ - public function writeStream($path, $contents, Config $config): void { - $this->adapter->writeStream($path, $contents, $config); - } - - /** - * {@inheritdoc} - */ - public function copy($source, $destination, $config): void { - $this->adapter->copy($source, $destination, $config); - - $item = $this->cacheItemBackend->load($source); - $newitem = clone $item; - $this->cacheItemBackend->set($destination, $newitem); - } - - /** - * {@inheritdoc} - */ - public function delete($path): void { - $this->adapter->delete($path); - - $this->cacheItemBackend->delete($path); - } - - /** - * {@inheritdoc} - */ - public function setVisibility($path, $visibility): void { - $this->adapter->setVisibility($path, $visibility); - - $this->updateMetadata($path, ['visibility' => $visibility]); - } - - /** - * {@inheritdoc} - */ - public function read($path): string { - return $this->adapter->read($path); - } - - /** - * {@inheritdoc} - */ - public function readStream($path) { - return $this->adapter->readStream($path); - } - - /** - * {@inheritdoc} - */ - public function listContents($path = '', $deep = FALSE): iterable { - // Don't cache directory listings to avoid having to keep track of - // incomplete cache entries. - // @todo This could be a good place for a microcache? - return $this->adapter->listContents($path, $deep); - } - - /** - * Something something. - * - * @param string $path - * The file path. - * - * @return array - * The metadata. - */ - public function getMetadata(string $path): array { - $item = $this->cacheItemBackend->load($path); - - if ($metadata = $item->getMetadata()) { - return $metadata; - } - - $filesize = $this->adapter->fileSize($path); - $lastmodified = $this->adapter->lastModified($path); - $mimetype = $this->adapter->mimeType($path); - $visibility = $this->adapter->visibility($path); - - // $metadata = $this->adapter->getMetadata($path); - $item->updateMetadata([ - 'fileSize' => $filesize, - 'lastModified' => $lastmodified, - 'mimeType' => $mimetype, - 'visibility' => $visibility, - ]); - $this->cacheItemBackend->set($path, $item); - - return $metadata; - } - - /** - * Fetches a specific key from metadata. - * - * @param string $path - * The path to load metadata for. - * @param string $key - * The key in metadata, such as 'mimetype', to load metadata for. - * - * @return string|bool - * The array of metadata. - */ - protected function fetchMetadataKey(string $path, string $key): string|bool { - $item = $this->cacheItemBackend->load($path); - - if (($metadata = $item->getMetadata()) && isset($metadata[$key])) { - return $metadata[$key]; - } - - $metadata = $this->adapter->$key($path); - $updatedMetadata = $this->updateMetadata($path, [$key => $metadata->$key()]); - return $updatedMetadata[$key] ?? FALSE; - } - - /** - * Updates the metadata for a given path. - * - * @param string $path - * The path of file file or directory. - * @param array|false $metadata - * The metadata to update. - * - * @return array|false - * Returns the value passed in as metadata. - */ - protected function updateMetadata(string $path, array|false $metadata): array|false { - if (!empty($metadata)) { - $item = $this->cacheItemBackend->load($path); - $item->updateMetadata($metadata); - $this->cacheItemBackend->set($path, $item); - } - - return $metadata; - } - - /** - * {@inheritdoc} - */ - public function fileExists(string $path): bool { - if ($this->cacheItemBackend->has($path)) { - return TRUE; - } - - // Always check the upstream adapter for new files. - // @todo This could be a good place for a microcache? - return $this->adapter->fileExists($path); - } - - /** - * {@inheritdoc} - */ - public function directoryExists(string $path): bool { - if ($this->cacheItemBackend->has($path)) { - return TRUE; - } - - // Always check the upstream adapter for new directoies. - // @todo This could be a good place for a microcache? - return $this->adapter->directoryExists($path); - } - - /** - * {@inheritdoc} - */ - public function deleteDirectory(string $path): void { - // Before the delete we need to know what files are in the directory. - $contents = $this->adapter->listContents($path, TRUE); - - $this->adapter->deleteDirectory($path); - - $paths = array_column($contents, 'path'); - $this->cacheItemBackend->deleteMultiple($paths); - } - - /** - * {@inheritdoc} - */ - public function createDirectory(string $path, Config $config): void { - $this->adapter->createDirectory($path, $config); - - // Warm the metadata cache. - $item = new CacheItem(); - $this->cacheItemBackend->set($path, $item); - } - - /** - * {@inheritdoc} - */ - public function visibility(string $path): FileAttributes { - $visibility = $this->fetchMetadataKey($path, 'visibility'); - return new FileAttributes($path, NULL, $visibility); - } - - /** - * {@inheritdoc} - */ - public function mimeType(string $path): FileAttributes { - $mimeType = $this->fetchMetadataKey($path, 'mimeType'); - return new FileAttributes($path, NULL, NULL, NULL, $mimeType); - } - - /** - * {@inheritdoc} - */ - public function lastModified(string $path): FileAttributes { - return $this->adapter->lastModified($path); - // Return new FileAttributes($path, NULL, NULL, $lastModified);. - } - - /** - * {@inheritdoc} - */ - public function fileSize(string $path): FileAttributes { - return $this->adapter->fileSize($path); - } - - /** - * {@inheritdoc} - */ - public function move(string $source, string $destination, Config $config): void { - $this->adapter->move($source, $destination, $config); - - $item = $this->cacheItemBackend->load($source); - $newitem = clone $item; - $this->cacheItemBackend->set($destination, $newitem); - $this->cacheItemBackend->delete($source); - } - -} diff --git a/src/Adapter/FlysystemAdapterInterface.php b/src/Adapter/FlysystemAdapterInterface.php deleted file mode 100644 index 16d19e9dca492fec4d05cb2b44326dead7fa0e60..0000000000000000000000000000000000000000 --- a/src/Adapter/FlysystemAdapterInterface.php +++ /dev/null @@ -1,48 +0,0 @@ -<?php - -namespace Drupal\flysystem\Adapter; - -/** - * Interface definition for Flysystem adapters. - */ -interface FlysystemAdapterInterface { - - /** - * Returns the Flysystem adapter. - * - * Plugins should not keep references to the adapter. If a plugin needs to - * perform filesystem operations, it should either use a scheme:// or have the - * \Drupal\flysystem\FlysystemStreamWrapperManager injected. - * - * @return \League\Flysystem\FilesystemAdapter - * The Flysytem adapter. - */ - public function getAdapter(); - - /** - * Returns a web accessible URL for the resource. - * - * This function should return a URL that can be embedded in a web page - * and accessed from a browser. For example, the external URL of - * "youtube://xIpLd0WQKCY" might be - * "http://www.youtube.com/watch?v=xIpLd0WQKCY". - * - * @param string $uri - * The URI to provide a URL for. - * - * @return string - * Returns a string containing a web accessible URL for the resource. - */ - public function getExternalUrl($uri); - - /** - * Checks the sanity of the filesystem. - * - * If this is a local filesystem, .htaccess file should be in place. - * - * @return array - * A list of error messages. - */ - public function ensure($force = FALSE); - -} diff --git a/src/Adapter/FlysystemAdapterManager.php b/src/Adapter/FlysystemAdapterManager.php deleted file mode 100644 index 184e7eb7d71553a459f74c416c1d391339ba9f9e..0000000000000000000000000000000000000000 --- a/src/Adapter/FlysystemAdapterManager.php +++ /dev/null @@ -1,59 +0,0 @@ -<?php - -namespace Drupal\flysystem\Adapter; - -use Drupal\Component\Plugin\FallbackPluginManagerInterface; -use Drupal\Core\Cache\CacheBackendInterface; -use Drupal\Core\Extension\ModuleHandlerInterface; -use Drupal\Core\Plugin\DefaultPluginManager; - -/** - * Manages Flysystem plugins. - */ -class FlysystemAdapterManager extends DefaultPluginManager implements FallbackPluginManagerInterface { - - /** - * Constructs a Flysystem object. - * - * @param \Traversable $namespaces - * An object that implements \Traversable which contains the root paths - * keyed by the corresponding namespace to look for plugin implementations. - * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend - * Cache backend instance to use. - * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler - * The module handler to invoke the alter hook with. - * - * @todo Determine if a derivative of this is needed, if not remove. - */ - public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) { - parent::__construct('Flysystem', $namespaces, $module_handler, 'Drupal\flysystem\Plugin\FlysystemPluginInterface', 'Drupal\flysystem\Annotation\Adapter'); - $this->setCacheBackend($cache_backend, 'flysystem_plugins'); - } - - /** - * {@inheritdoc} - */ - public function getFallbackPluginId($plugin_id, array $configuration = []) { - return 'missing'; - } - - /** - * {@inheritdoc} - */ - protected function alterDefinitions(&$definitions) { - // Remove definitions that are missing necessary extensions. - foreach ($definitions as $id => $definition) { - foreach ($definition['extensions'] as $extension) { - if (extension_loaded($extension)) { - continue; - } - - unset($definitions[$id]); - break; - } - } - - parent::alterDefinitions($definitions); - } - -} diff --git a/src/Adapter/FlysystemUrlTrait.php b/src/Adapter/FlysystemUrlTrait.php deleted file mode 100644 index a164251d4167f8bbd0ef6d9b2579fceceb8ef396..0000000000000000000000000000000000000000 --- a/src/Adapter/FlysystemUrlTrait.php +++ /dev/null @@ -1,82 +0,0 @@ -<?php - -namespace Drupal\flysystem\Adapter; - -use Drupal\Core\Url; -use League\Flysystem\WhitespacePathNormalizer; - -/** - * Helper trait for generating URLs from adapter plugins. - * - * @todo Review and determine if needed, if not remove. - * @see flysystem.serve route. - */ -trait FlysystemUrlTrait { - - /** - * @var \League\Flysystem\WhitespacePathNormalizer - */ - protected WhitespacePathNormalizer $pathNormalizer; - - /** - * - */ - protected function getPathNormalizer() { - if (!isset($this->pathNormalizer)) { - $this->pathNormalizer = new WhitespacePathNormalizer(); - } - return $this->pathNormalizer; - } - - /** - * Returns a web accessible URL for the resource. - * - * This function should return a URL that can be embedded in a web page - * and accessed from a browser. For example, the external URL of - * "youtube://xIpLd0WQKCY" might be - * "http://www.youtube.com/watch?v=xIpLd0WQKCY". - * - * @param string $uri - * The URI to provide a URL for. - * - * @return string - * Returns a string containing a web accessible URL for the resource. - */ - public function getExternalUrl($uri) { - $path = str_replace('\\', '/', $this->getTarget($uri)); - - $arguments = [ - 'scheme' => $this->getScheme($uri), - 'filepath' => $path, - ]; - // @todo Review, see flysystem.routing.yml, route flysystem.serve - return Url::fromRoute('flysystem.serve', $arguments, ['absolute' => TRUE])->toString(); - } - - /** - * Returns the target file path of a URI. - * - * @param string $uri - * The URI. - * - * @return string - * The file path of the URI. - */ - protected function getTarget($uri) { - return $this->getPathNormalizer()->normalizePath(substr($uri, strpos($uri, '://') + 3)); - } - - /** - * Returns the scheme from the internal URI. - * - * @param string $uri - * The URI. - * - * @return string - * The scheme. - */ - protected function getScheme($uri) { - return substr($uri, 0, strpos($uri, '://')); - } - -} diff --git a/src/Adapter/ImageStyleGenerationTrait.php b/src/Adapter/ImageStyleGenerationTrait.php deleted file mode 100644 index 5d6eb429bb5a615ad7b360e61f35f9af851c8df0..0000000000000000000000000000000000000000 --- a/src/Adapter/ImageStyleGenerationTrait.php +++ /dev/null @@ -1,70 +0,0 @@ -<?php - -namespace Drupal\flysystem\Adapter; - -use Drupal\Component\Utility\Crypt; -use Drupal\image\Entity\ImageStyle; - -/** - * Helper trait for generating URLs from adapter plugins. - * - * @todo Review and determine if this is needed, if not delete. - */ -trait ImageStyleGenerationTrait { - - /** - * Generates an image style for a file target. - * - * @param string $target - * The file target. - * - * @return bool - * True on success, false on failure. - */ - protected function generateImageStyle($target) { - if (strpos($target, 'styles/') !== 0 || substr_count($target, '/') < 3) { - return FALSE; - } - - [, $style, $scheme, $file] = explode('/', $target, 4); - - if (!$image_style = ImageStyle::load($style)) { - return FALSE; - } - - $image_uri = $scheme . '://' . $file; - - $derivative_uri = $image_style->buildUri($image_uri); - - if (!file_exists($image_uri)) { - $path_info = pathinfo($image_uri); - $converted_image_uri = $path_info['dirname'] . '/' . $path_info['filename']; - - if (!file_exists($converted_image_uri)) { - return FALSE; - } - else { - // The converted file does exist, use it as the source. - $image_uri = $converted_image_uri; - } - } - - $lock_name = 'image_style_deliver:' . $image_style->id() . ':' . Crypt::hashBase64($image_uri); - - if (!file_exists($derivative_uri)) { - $lock_acquired = \Drupal::lock()->acquire($lock_name); - if (!$lock_acquired) { - return FALSE; - } - } - - $success = file_exists($derivative_uri) || $image_style->createDerivative($image_uri, $derivative_uri); - - if (!empty($lock_acquired)) { - \Drupal::lock()->release($lock_name); - } - - return $success; - } - -} diff --git a/src/Adapter/Local.php b/src/Adapter/Local.php deleted file mode 100644 index d8ec959b0ace7d219569137d9e45444e613bf95d..0000000000000000000000000000000000000000 --- a/src/Adapter/Local.php +++ /dev/null @@ -1,189 +0,0 @@ -<?php - -namespace Drupal\flysystem\Adapter; - -use Drupal\Component\FileSecurity\FileSecurity; -use Drupal\Component\Utility\UrlHelper; -use Drupal\Core\File\FileSystem; -use Drupal\Core\Logger\RfcLogLevel; -use Drupal\Core\Plugin\ContainerFactoryPluginInterface; -use League\Flysystem\Local\LocalFilesystemAdapter; -use Symfony\Component\DependencyInjection\ContainerInterface; - -/** - * Adapter for the "Local" Flysystem adapter. - * - * @Adapter(id = "local") - */ -class Local implements FlysystemAdapterInterface, ContainerFactoryPluginInterface { - - use FlysystemUrlTrait { - getExternalUrl as getDownloadlUrl; - } - - /** - * The permissions to create directories with. - * - * @var int - */ - protected $directoryPerm; - - /** - * Whether the root is in the public path. - * - * @var bool - */ - protected $isPublic; - - /** - * The root of the local adapter. - * - * @var string - */ - protected $root; - - /** - * Whether the root exists and is readable. - * - * @var bool - */ - protected $rootExists; - - /** - * Constructs a Local object. - * - * @param string $root - * The root of the adapter's filesystem. - * @param bool $is_public - * (optional) Whether this is a public file system. Defaults to false. - * @param int $directory_permission - * (optional) The permissions to create directories with. - */ - public function __construct($root, $is_public = FALSE, $directory_permission = FileSystem::CHMOD_DIRECTORY) { - $this->isPublic = $is_public; - $this->root = $root; - $this->directoryPerm = $directory_permission; - $this->rootExists = $this->ensureDirectory(); - } - - /** - * {@inheritdoc} - * - * @todo Remove if it is decided that we're not using ContainerFactoryPluginInterface; - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration['root'], - !empty($configuration['public']), - $container->get('settings')->get('file_chmod_directory', FileSystem::CHMOD_DIRECTORY) - ); - } - - /** - * {@inheritdoc} - */ - public function getAdapter() { - return $this->rootExists ? new LocalFilesystemAdapter($this->root) : new MissingAdapter(); - } - - /** - * {@inheritdoc} - */ - public function getExternalUrl($uri) { - if ($this->isPublic === FALSE) { - return $this->getDownloadlUrl($uri); - } - - $path = str_replace('\\', '/', $this->root . '/' . $this->getTarget($uri)); - - return $GLOBALS['base_url'] . '/' . UrlHelper::encodePath($path); - } - - /** - * {@inheritdoc} - */ - public function ensure($force = FALSE) { - if (!$this->rootExists) { - return [ - [ - 'severity' => RfcLogLevel::ERROR, - 'message' => 'The %root directory either does not exist or is not readable and attempts to create it have failed.', - 'context' => ['%root' => $this->root], - ], - ]; - } - - if (!$this->writeHtaccess($force)) { - return [ - [ - 'severity' => RfcLogLevel::ERROR, - 'message' => 'See <a href="@url">@url</a> for information about the recommended .htaccess file which should be added to the %directory directory to help protect against arbitrary code execution.', - 'context' => [ - '%directory' => $this->root, - '@url' => 'https://www.drupal.org/SA-CORE-2013-003', - ], - ], - ]; - } - - return [ - [ - 'severity' => RfcLogLevel::INFO, - 'message' => 'The directory %root exists and is readable.', - 'context' => ['%root' => $this->root], - ], - ]; - } - - /** - * Checks that the directory exists and is readable. - * - * This will attempt to create the directory if it doesn't exist. - * - * @return bool - * True on success, false on failure. - */ - protected function ensureDirectory() { - // Go for the success case first. - if (is_dir($this->root) && is_readable($this->root)) { - return TRUE; - } - - if (!file_exists($this->root)) { - mkdir($this->root, $this->directoryPerm, TRUE); - } - - if (is_dir($this->root) && chmod($this->root, $this->directoryPerm)) { - clearstatcache(TRUE, $this->root); - $this->writeHtaccess(TRUE); - return TRUE; - } - - return FALSE; - } - - /** - * Writes an .htaccess file. - * - * @param bool $force - * Whether to overwrite an existing file. - * - * @return bool - * True on success, false on failure. - */ - protected function writeHtaccess($force) { - $htaccess_path = $this->root . '/.htaccess'; - - if (file_exists($htaccess_path) && !$force) { - // Short circuit if the .htaccess file already exists. - return TRUE; - } - - // Make file writable so that we can overwrite it. - if (file_exists($htaccess_path)) { - chmod($htaccess_path, 0666); - } - return @file_put_contents($htaccess_path, FileSecurity::htaccessLines(!$this->isPublic)) && chmod($htaccess_path, 0444); - } - -} diff --git a/src/Adapter/Missing.php b/src/Adapter/Missing.php deleted file mode 100644 index bb07cb3706a7e7087e65c7cf643e63482eabc536..0000000000000000000000000000000000000000 --- a/src/Adapter/Missing.php +++ /dev/null @@ -1,43 +0,0 @@ -<?php - -namespace Drupal\flysystem\Flysystem\Adapter; - -use Drupal\Core\Logger\RfcLogLevel; -use Drupal\flysystem\Adapter\FlysystemAdapterInterface; -use Drupal\flysystem\Adapter\MissingAdapter; - -/** - * Drupal plugin for the "NullAdapter" Flysystem adapter. - * - * @Adapter(id = "missing") - */ -class Missing implements FlysystemAdapterInterface { - - /** - * {@inheritdoc} - */ - public function getAdapter() { - return new MissingAdapter(); - } - - /** - * {@inheritdoc} - */ - public function getExternalUrl($uri) { - return ''; - } - - /** - * {@inheritdoc} - */ - public function ensure($force = FALSE) { - return [ - [ - 'severity' => RfcLogLevel::ERROR, - 'message' => 'The Flysystem driver is missing.', - 'context' => [], - ], - ]; - } - -} diff --git a/src/Adapter/MissingAdapter.php b/src/Adapter/MissingAdapter.php deleted file mode 100644 index 0e06a1e11ca491dd2da6d861ce196157dda51f2d..0000000000000000000000000000000000000000 --- a/src/Adapter/MissingAdapter.php +++ /dev/null @@ -1,141 +0,0 @@ -<?php - -namespace Drupal\flysystem\Adapter; - -use League\Flysystem\Config; -use League\Flysystem\FileAttributes; -use League\Flysystem\FilesystemAdapter; -use League\Flysystem\InvalidVisibilityProvided; -use League\Flysystem\UnableToCopyFile; -use League\Flysystem\UnableToCreateDirectory; -use League\Flysystem\UnableToDeleteDirectory; -use League\Flysystem\UnableToDeleteFile; -use League\Flysystem\UnableToMoveFile; -use League\Flysystem\UnableToReadFile; -use League\Flysystem\UnableToWriteFile; - -/** - * The Flysystem MissingAdapter definition for integration with Drupal. - */ -class MissingAdapter implements FilesystemAdapter { - - /** - * {@inheritdoc} - */ - public function fileExists(string $path): bool { - return FALSE; - } - - /** - * {@inheritdoc} - */ - public function directoryExists(string $path): bool { - return FALSE; - } - - /** - * {@inheritdoc} - */ - public function write(string $path, string $contents, Config $config): void { - throw new UnableToWriteFile(); - } - - /** - * {@inheritdoc} - */ - public function writeStream(string $path, $contents, Config $config): void { - throw new UnableToWriteFile(); - } - - /** - * {@inheritdoc} - */ - public function read(string $path): string { - return ''; - } - - /** - * {@inheritdoc} - */ - public function readStream(string $path) { - throw new UnableToReadFile(); - } - - /** - * {@inheritdoc} - */ - public function delete(string $path): void { - throw new UnableToDeleteFile(); - } - - /** - * {@inheritdoc} - */ - public function deleteDirectory(string $dirname): void { - throw new UnableToDeleteDirectory(); - } - - /** - * {@inheritdoc} - */ - public function createDirectory(string $dirname, Config $config): void { - throw new UnableToCreateDirectory(); - } - - /** - * {@inheritdoc} - */ - public function setVisibility(string $path, string $visibility): void { - throw new InvalidVisibilityProvided(); - } - - /** - * {@inheritdoc} - */ - public function visibility(string $path): FileAttributes { - return new FileAttributes(''); - } - - /** - * {@inheritdoc} - */ - public function mimeType(string $path): FileAttributes { - return new FileAttributes(''); - } - - /** - * {@inheritdoc} - */ - public function lastModified(string $path): FileAttributes { - return new FileAttributes(''); - } - - /** - * {@inheritdoc} - */ - public function fileSize(string $path): FileAttributes { - return new FileAttributes(''); - } - - /** - * {@inheritdoc} - */ - public function listContents(string $directory = '', $deep = FALSE): iterable { - return []; - } - - /** - * {@inheritdoc} - */ - public function move(string $source, string $destination, Config $config): void { - throw new UnableToMoveFile(); - } - - /** - * {@inheritdoc} - */ - public function copy(string $source, string $destination, Config $config): void { - throw new UnableToCopyFile(); - } - -} diff --git a/src/Annotation/Adapter.php b/src/Annotation/Adapter.php new file mode 100644 index 0000000000000000000000000000000000000000..15f609df2b9ddca57ffd64d38b0852ce281f46a4 --- /dev/null +++ b/src/Annotation/Adapter.php @@ -0,0 +1,34 @@ +<?php + +namespace Drupal\flysystem\Annotation; + +use Drupal\Component\Annotation\Plugin; + +/** + * Defines a Flysystem adapter plguin. + * + * Plugin Namespace: Flysystem. + * + * For a working example, see \Drupal\flysystem\Flysystem\Local. + * + * @see plugin_api + * + * @Annotation + */ +class Adapter extends Plugin { + + /** + * The plugin ID. + * + * @var string + */ + public $id; + + /** + * A list of extension dependencies. + * + * @var string[] + */ + public $extensions = []; + +} diff --git a/src/Annotation/FlysystemAdapter.php b/src/Annotation/FlysystemAdapter.php new file mode 100644 index 0000000000000000000000000000000000000000..dce4d7aaa0ca600e7112170658256ac0edabdc2b --- /dev/null +++ b/src/Annotation/FlysystemAdapter.php @@ -0,0 +1,35 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\flysystem\Annotation; + +use Drupal\Component\Annotation\Plugin; + +/** + * Defines flysystem_adapter annotation object. + * + * @Annotation + */ +final class FlysystemAdapter extends Plugin { + + /** + * The plugin ID. + */ + public string $id; + + /** + * The human-readable name of the plugin. + * + * @ingroup plugin_translatable + */ + public string $title; + + /** + * The description of the plugin. + * + * @ingroup plugin_translatable + */ + public string $description; + +} diff --git a/src/Entity/FlysystemAdapterConfig.php b/src/Entity/FlysystemAdapterConfig.php new file mode 100644 index 0000000000000000000000000000000000000000..0e6c111c7f6363ac34011c215716146f54c14ff0 --- /dev/null +++ b/src/Entity/FlysystemAdapterConfig.php @@ -0,0 +1,278 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\flysystem\Entity; + +use Drupal\Component\Utility\NestedArray; +use Drupal\Core\Config\Config; +use Drupal\Core\Config\Entity\ConfigEntityBase; +use Drupal\Core\Config\Entity\ConfigEntityTypeInterface; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\flysystem\FlysystemAdapterConfigInterface; +use Drupal\flysystem\FlysystemException; + +/** + * Defines the flysystem_adapter_config entity type. + * + * @ConfigEntityType( + * id = "flysystem_adapter_config", + * label = @Translation("Configured Flysystem Adapter"), + * label_collection = @Translation("Configured Flysystem Adapters"), + * label_singular = @Translation("configured Flysystem Adapter"), + * label_plural = @Translation("configured Flysystem Adapters"), + * label_count = @PluralTranslation( + * singular = "@count configured Flysystem Adapter", + * plural = "@count configured Flysystem Adapters", + * ), + * handlers = { + * "list_builder" = "Drupal\flysystem\FlysystemAdapterConfigListBuilder", + * "form" = { + * "add" = "Drupal\flysystem\Form\FlysystemAdapterConfigForm", + * "edit" = "Drupal\flysystem\Form\FlysystemAdapterConfigForm", + * "delete" = "Drupal\Core\Entity\EntityDeleteForm", + * }, + * }, + * config_prefix = "flysystem_adapter_config", + * admin_permission = "administer flysystem_adapter_config", + * links = { + * "collection" = "/admin/config/media/flysystem-adapter-config", + * "add-form" = "/admin/config/media/flysystem-adapter-config/add", + * "edit-form" = "/admin/config/media/flysystem-adapter-config/{flysystem_adapter_config}", + * "delete-form" = "/admin/config/media/flysystem-adapter-config/{flysystem_adapter_config}/delete", + * }, + * entity_keys = { + * "id" = "id", + * "label" = "label", + * "uuid" = "uuid", + * }, + * config_export = { + * "id", + * "label", + * "description", + * "sub_adapter", + * "sub_adapter_config", + * }, + * ) + */ +final class FlysystemAdapterConfig extends ConfigEntityBase implements FlysystemAdapterConfigInterface { + + /** + * The flysystem_adapter_config ID. + */ + protected string $id; + + /** + * The flysystem_adapter_config label. + */ + protected string $label; + + /** + * The flysystem_adapter_config description. + */ + protected string $description; + + /** + * The ID of the subAdapter plugin. + * + * @var string + */ + protected $sub_adapter; + + /** + * The adapter plugin configuration. + * + * @var array + */ + protected $sub_adapter_config = []; + + /** + * The adapter plugin instance. + * + * @var \Drupal\flysystem\FlysystemAdapterInterface|null + */ + protected $subAdapterPlugin; + + /** + * {@inheritdoc} + */ + public function getDescription() { + return !empty($this->description) ? $this->description : ''; + } + + /** + * {@inheritdoc} + */ + public function hasValidSubAdapter() { + $subadapter_plugin_definition = \Drupal::service('plugin.manager.flysystem_adapter')->getDefinition($this->getSubAdapterId(), FALSE); + return !empty($subadapter_plugin_definition); + } + + /** + * {@inheritdoc} + */ + public function getSubAdapterId() { + return $this->sub_adapter; + } + + /** + * {@inheritdoc} + */ + public function getSubAdapterValue($key) { + if (is_array($key)) { + return NestedArray::getValue($key, $this->sub_adapter_config); + } + return $this->sub_adapter_config[$key] ?? ''; + } + + /** + * {@inheritdoc} + */ + public function getSubAdapter() { + if (!$this->subAdapterPlugin) { + $subadapter_plugin_manager = \Drupal::service('plugin.manager.flysystem_adapter'); + $config = $this->sub_adapter_config; + $config['#flysystem_adapter_config'] = $this; + if (!($this->subAdapterPlugin = $subadapter_plugin_manager->createInstance($this->getSubAdapterId(), $config))) { + $sub_adapter_id = $this->getSubAdapterId(); + throw new FlysystemException("The adapter with ID '$sub_adapter_id' could not be retrieved."); + } + } + return $this->subAdapterPlugin; + } + + /** + * {@inheritdoc} + */ + public function getSubAdapterConfig() { + return $this->sub_adapter_config; + } + + /** + * {@inheritdoc} + */ + public function getSubAdapterConfigItem($key) { + if (is_array($key)) { + return NestedArray::getValue($this->sub_adapter_config, $key); + } + // var_dump($key); die; + // var_dump($this->sub_adapter_config[$key]); die; + // var_dump($key); die;. + return $this->sub_adapter_config[$key] ?? ''; + } + + /** + * {@inheritdoc} + */ + public function setSubAdapterConfig(array $sub_adapter_config) { + $this->sub_adapter_config = $sub_adapter_config; + // In case the subadapter plugin is already loaded, make sure the + // configuration stays in sync. + if ($this->subAdapterPlugin + && $this->getSubAdapter()->getConfiguration() !== $sub_adapter_config) { + $this->getSubAdapter()->setConfiguration($sub_adapter_config); + } + return $this; + } + + /** + * {@inheritdoc} + */ + public function preSave(EntityStorageInterface $storage) { + parent::preSave($storage); + + // The rest of the code only applies to updates. + if (!isset($this->original)) { + return; + } + // Retrieve active config overrides for this adapter config entity. + $overrides = $this->getConfigOverrides($this); + + // If there are overrides for the sub_adapter or its configuration, attempt + // to apply them for the preUpdate() call. + if (isset($overrides['sub_adapter']) || isset($overrides['sub_adapter_config'])) { + $sub_adapter_config = $this->getSubAdapterConfig(); + if (isset($overrides['sub_adapter_config'])) { + $sub_adapter_config = $overrides['sub_adapter_config']; + } + $sub_adapter_id = $this->getSubAdapterId(); + if (isset($overrides['sub_adapter'])) { + $sub_adapter_id = $overrides['sub_adapter']; + } + $subadapter_plugin_manager = \Drupal::service('plugin.manager.flysystem_adapter'); + $sub_adapter_config['#flysystem_adapter_config'] = $this; + if (!($subadapter_plugin_manager->createInstance($sub_adapter_id, $sub_adapter_config))) { + throw new FlysystemException("The adapter with ID '$sub_adapter_id' could not be retrieved."); + } + } + } + + /** + * Implements the magic __clone() method. + * + * Prevents the adapter plugin instance from being cloned. + */ + public function __clone() { + $this->subAdapterPlugin = NULL; + } + + /** + * Retrieves all overridden property values for the given config entity. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The config entity to check for overrides. + * + * @return array + * An associative array mapping property names to their overridden values. + */ + private function getConfigOverrides(EntityInterface $entity) { + $entity_type = $entity->getEntityType(); + if (!($entity_type instanceof ConfigEntityTypeInterface)) { + return []; + } + + $config_key = $entity_type->getConfigPrefix() . '.' . $entity->id(); + $config = \Drupal::config($config_key); + if (!$config->hasOverrides()) { + return []; + } + + return $this->collectOverrides($config, $config->get()); + } + + /** + * Collects overrides from a config object. + * + * @param \Drupal\Core\Config\Config $config + * The config object. + * @param array $values + * The array of values for the given $prefix. + * @param array $overrides + * (optional) The overrides collected so far. Internal use only. + * @param string $prefix + * (optional) The config key prefix for the current call level. Internal + * use only. + * + * @return array + * An associative array mapping property names to their overridden values. + */ + private function collectOverrides(Config $config, array $values, array $overrides = [], $prefix = '') { + foreach ($values as $key => $value) { + $key = "$prefix$key"; + if (!$config->hasOverrides($key)) { + continue; + } + if (is_array($value)) { + NestedArray::setValue($overrides, explode('.', $key), []); + $overrides = $this->collectOverrides($config, $value, $overrides, "$key."); + } + else { + NestedArray::setValue($overrides, explode('.', $key), $value); + } + } + + return $overrides; + } + +} diff --git a/src/FlysystemAdapterConfigInterface.php b/src/FlysystemAdapterConfigInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..782d24f1967087691c369b9cb30e8dd57c0f9c42 --- /dev/null +++ b/src/FlysystemAdapterConfigInterface.php @@ -0,0 +1,68 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\flysystem; + +use Drupal\Core\Config\Entity\ConfigEntityInterface; + +/** + * Provides an interface defining a flysystem_adapter_config entity type. + */ +interface FlysystemAdapterConfigInterface extends ConfigEntityInterface { + + /** + * Determines whether Flysystem adapter is valid. + * + * @return bool + * TRUE if valid, FALSE otherwise. + */ + public function hasValidSubAdapter(); + + /** + * Retrieves the Flysystem adapter. + * + * @return \Drupal\flysystem\FlysystemAdapterInterface + * This adapter config entity's Flysystem adapter. + * + * @throws \Drupal\flysystem\FlysystemException + * Thrown if the adapter plugin could not be retrieved. + */ + public function getSubAdapter(); + + /** + * Retrieves plugin ID of the Flysystem (sub)adapter of this config entity. + * + * @return string + * The plugin ID of the subadapter. + */ + public function getSubAdapterId(); + + /** + * Return the configuration of this config entity's Flysystem (sub)adapter. + * + * @return array + * An associative array with the subadapter configuration. + */ + public function getSubAdapterConfig(); + + /** + * Get adapter config description. + * + * @return string + * Adapter config description. + */ + public function getDescription(); + + /** + * Returns value from subadapter config array. + * + * @param mixed $key + * Array key or keys for which value is to be retrieved. + * + * @return mixed + * Value to return from array key/keys. + */ + public function getSubAdapterValue($key); + +} diff --git a/src/FlysystemAdapterConfigListBuilder.php b/src/FlysystemAdapterConfigListBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..d5903c8035432c67ff99970ca4b2e9e06608a9c7 --- /dev/null +++ b/src/FlysystemAdapterConfigListBuilder.php @@ -0,0 +1,36 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\flysystem; + +use Drupal\Core\Config\Entity\ConfigEntityListBuilder; +use Drupal\Core\Entity\EntityInterface; + +/** + * Provides a listing of flysystem_adapter_configs. + */ +final class FlysystemAdapterConfigListBuilder extends ConfigEntityListBuilder { + + /** + * {@inheritdoc} + */ + public function buildHeader(): array { + $header['label'] = $this->t('Label'); + $header['id'] = $this->t('Machine name'); + $header['status'] = $this->t('Status'); + return $header + parent::buildHeader(); + } + + /** + * {@inheritdoc} + */ + public function buildRow(EntityInterface $entity): array { + /** @var \Drupal\flysystem\FlysystemAdapterConfigInterface $entity */ + $row['label'] = $entity->label(); + $row['id'] = $entity->id(); + $row['status'] = $entity->status() ? $this->t('Enabled') : $this->t('Disabled'); + return $row + parent::buildRow($entity); + } + +} diff --git a/src/FlysystemAdapterInterface.php b/src/FlysystemAdapterInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..2e0a02a1016e47539a82504782342e3ce27a46ab --- /dev/null +++ b/src/FlysystemAdapterInterface.php @@ -0,0 +1,55 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\flysystem; + +use Drupal\Component\Plugin\ConfigurableInterface; +use Drupal\Component\Plugin\DependentPluginInterface; +use Drupal\Component\Plugin\DerivativeInspectionInterface; +use Drupal\Component\Plugin\PluginInspectionInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; + +/** + * Interface for flysystem_adapter plugins. + */ +interface FlysystemAdapterInterface extends PluginInspectionInterface, DerivativeInspectionInterface, ConfigurableInterface, DependentPluginInterface, ContainerFactoryPluginInterface { + + /** + * Returns the translated plugin label. + */ + public function label(): string; + + /** + * Retrieves plugin ID of the Flysystem (sub)adapter of this config entity. + * + * @return string + * The plugin ID of the subadapter. + */ + public function getSubAdapterId(); + + /** + * Return the configuration of this config entity's Flysystem (sub)adapter. + * + * @return array + * An associative array with the subadapter configuration. + */ + public function getSubAdapterConfig(); + + /** + * Builds the Sub Configuration Form components for Form array. + * + * @param array $form + * Form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * Drupal Form State object. + * @param \Drupal\flysystem\FlysystemAdapterConfigInterface $flysystem_adapter_config + * Flysystem Adapter Configuration entity. + * + * @return array + * Form array with subform items added. + */ + public function buildSubConfigurationForm(array $form, FormStateInterface $form_state, FlysystemAdapterConfigInterface $flysystem_adapter_config); + +} diff --git a/src/FlysystemAdapterPluginManager.php b/src/FlysystemAdapterPluginManager.php new file mode 100644 index 0000000000000000000000000000000000000000..7b032a3ec18e49f3e69e24904b80dc3e1777c339 --- /dev/null +++ b/src/FlysystemAdapterPluginManager.php @@ -0,0 +1,26 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\flysystem; + +use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Plugin\DefaultPluginManager; +use Drupal\flysystem\Annotation\FlysystemAdapter; + +/** + * FlysystemAdapter plugin manager. + */ +final class FlysystemAdapterPluginManager extends DefaultPluginManager { + + /** + * Constructs the object. + */ + public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) { + parent::__construct('Plugin/FlysystemAdapter', $namespaces, $module_handler, FlysystemSubAdapterInterface::class, FlysystemAdapter::class); + $this->alterInfo('flysystem_adapter_info'); + $this->setCacheBackend($cache_backend, 'flysystem_adapter_plugins'); + } + +} diff --git a/src/FlysystemBridge.php b/src/FlysystemBridge.php index a1d87d5819d3ff8e5bf123775f9937e6af5642b2..5c955003ec0855eb0c1e74a8490f5677ff001b2f 100644 --- a/src/FlysystemBridge.php +++ b/src/FlysystemBridge.php @@ -5,6 +5,7 @@ namespace Drupal\flysystem; use Drupal\Core\StreamWrapper\StreamWrapperInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use League\Flysystem\Filesystem; +use League\Flysystem\FilesystemOperator; /** * FlysystemStreamWrapper implementation for Drupal compatibility. @@ -133,7 +134,7 @@ class FlysystemBridge extends Filesystem { * @param string $scheme * The scheme. * - * @return \Drupal\flysystem\Flysystem\Interface\FilesystemInterface + * @return \League\Flysystem\FilesystemOperator * The filesystem for the scheme. */ protected function getFilesystemForScheme($scheme) { diff --git a/src/FlysystemDrupalFileSystem.php b/src/FlysystemDrupalFileSystem.php index df1ded0b507286b80abc732c69bb17c8616100b0..1aaf6da550d76a1dc0bf194782982147883d7476 100644 --- a/src/FlysystemDrupalFileSystem.php +++ b/src/FlysystemDrupalFileSystem.php @@ -14,9 +14,11 @@ use Drupal\Core\StreamWrapper\StreamWrapperInterface; use Drupal\Core\StreamWrapper\StreamWrapperManager; use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface; use League\Flysystem\FilesystemAdapter; +use League\Flysystem\InvalidInvisibilityProvided; +use League\Flysystem\Config; /** - * + * Decorator for Drupal]\Core\File\FileSystem. */ class FlysystemDrupalFileSystem extends FileSystem { @@ -94,8 +96,9 @@ class FlysystemDrupalFileSystem extends FileSystem { } elseif ($wrapper->fileExists($uri)) { $drupalMode = $this->settings->get('file_chmod_file', static::CHMOD_FILE); + $wrapper->setVisibility($uri, $drupalMode); + return TRUE; } - return $wrapper->setVisibility($uri, $drupalMode); } catch (\Exception $e) { // @todo write exception handler for possible exceptions thrown @@ -119,7 +122,10 @@ class FlysystemDrupalFileSystem extends FileSystem { // @todo figure out how to utilize directory permissions, see notes on // Flysystem VisibilityConverter. // @see \League\Flysystem\Filesystem::createDirectory() - $wrapper->createDirectory($uri); + // @see \League\FlysystemConfig + $options[Config::OPTION_DIRECTORY_VISIBILITY] = $mode; + $config = new Config($options); + $wrapper->createDirectory($uri, $config); return TRUE; } catch (\Exception $e) { @@ -167,7 +173,9 @@ class FlysystemDrupalFileSystem extends FileSystem { // @todo figure out how to utilize directory permissions, see notes on // Flysystem VisibilityConverter. // @see \League\Flysystem\Filesystem::copy(). - $wrapper->copy($source, $destination); + $options[Config::OPTION_VISIBILITY] = NULL; + $config = new Config($options); + $wrapper->copy($source, $destination, $config); return $destination; } catch (\Exception $e) { @@ -184,7 +192,7 @@ class FlysystemDrupalFileSystem extends FileSystem { * @todo finish writing, see inline todo comments. */ public function delete($path) { - /** @var \Drupal\Core\StreamWrapper\StreamWrapperManager $wrappe */ + /** @var \Drupal\Core\StreamWrapper\StreamWrapperManager $wrapper */ $wrapper = $this->streamWrapperManager->getViaUri($path); if ($wrapper instanceof FilesystemAdapter) { if ($wrapper->fileExists($path) || $wrapper->directoryExists($path)) { @@ -223,9 +231,9 @@ class FlysystemDrupalFileSystem extends FileSystem { } if ($wrapper->directoryExists($path)) { - $dir = $wrapper->listContents($path); + $dir = $wrapper->listContents($path, TRUE); foreach ($dir as $entry) { - $entry_path = $path . '/' . $entry; + $entry_path = $path . '/' . $entry->path(); $this->deleteRecursive($entry_path, $callback); } return $this->rmdir($path); @@ -249,7 +257,9 @@ class FlysystemDrupalFileSystem extends FileSystem { // Flysystem VisibilityConverter. // @see \League\Flysystem\Filesystem::move(). // What do we do with the $replace flag? - $wrapper->move($source, $destination); + $options[Config::OPTION_VISIBILITY] = NULL; + $config = new Config($options); + $wrapper->move($source, $destination, $config); return $destination; } catch (\Exception $e) { @@ -285,7 +295,9 @@ class FlysystemDrupalFileSystem extends FileSystem { // Flysystem VisibilityConverter. // @see \League\Flysystem\Filesystem::move(). // Do we need to use the $replace flag here in the FlysystemAdapter? - $wrapper->move($temp_name, $destination); + $options[Config::OPTION_VISIBILITY] = NULL; + $config = new Config($options); + $wrapper->move($temp_name, $destination, $config); return $destination; } catch (\Exception $e) { @@ -349,7 +361,7 @@ class FlysystemDrupalFileSystem extends FileSystem { $wrapper = $this->streamWrapperManager->getViaScheme(StreamWrapperManager::getScheme($destination)); if ($wrapper instanceof FilesystemAdapter) { try { - if ($wrapper->fileExist($destination)) { + if ($wrapper->fileExists($destination)) { switch ($replace) { case FileSystemInterface::EXISTS_REPLACE: // Do nothing here, we want to overwrite the existing file. diff --git a/src/FlysystemException.php b/src/FlysystemException.php new file mode 100644 index 0000000000000000000000000000000000000000..f9ad08f62bc2600b31cadde31c476e6d6e273cb9 --- /dev/null +++ b/src/FlysystemException.php @@ -0,0 +1,8 @@ +<?php + +namespace Drupal\flysystem; + +/** + * Represents an exception that occurred in some part of Flysystem. + */ +class FlysystemException extends \Exception {} diff --git a/src/FlysystemStreamWrapper.php b/src/FlysystemStreamWrapper.php index 509971d021970990d8ae58b5bec731533ff88507..8aae7edd3e9713f9a6dac967fd21e75b2599e833 100644 --- a/src/FlysystemStreamWrapper.php +++ b/src/FlysystemStreamWrapper.php @@ -6,15 +6,15 @@ use Drupal\Core\Site\Settings; use Drupal\Core\StreamWrapper\LocalStream; use Drupal\flysystem\Adapter\FlysystemUrlTrait; use League\Flysystem\FilesystemAdapter; +use League\Flysystem\UnableToCheckExistence; +use League\Flysystem\InvalidInvisibilityProvided; /** * FlysystemStreamWrapper implementation for Drupal compatibility. */ -abstract class FlysystemStreamWrapper extends LocalStream { +abstract class FlysystemStreamWrapper extends LocalStream implements FilesystemAdapter { - use FlysystemUrlTrait { - getExternalUrl as getDownloadlUrl; - } + use FlysystemUrlTrait; /** * The site settings. @@ -43,4 +43,26 @@ abstract class FlysystemStreamWrapper extends LocalStream { $this->adapter = $adapter; } + /** + * Determine if file exists. + * + * @param string $path + * Path to file. + * + * @return bool + * TRUE if file exists, FALSE if not. + * + * @throws UnableToCheckExistence + */ + public function fileExists(string $path): bool { + + try { + return $this->adapter->fileExists($path); + } + catch (\Exception $e) { + // @todo Write exception handler. + throw new UnableToCheckExistence(); + } + + } } diff --git a/src/FlysystemStreamWrapperManager.php b/src/FlysystemStreamWrapperManager.php index 2a5001d1cd53147bca6151648a51373751a45b35..dca73223b02fc27494a8c7dd5c91749edfec49c7 100644 --- a/src/FlysystemStreamWrapperManager.php +++ b/src/FlysystemStreamWrapperManager.php @@ -9,6 +9,7 @@ use Drupal\Core\StreamWrapper\StreamWrapperManager; use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface; use Drupal\flysystem\Adapter\CacheItemBackend; use Drupal\flysystem\Adapter\DrupalCacheAdapter; +use Drupal\flysystem\Adapter\FlysystemAdapterInterface; use Drupal\flysystem\Event\EnsureEvent; use Drupal\flysystem\Event\FlysystemEvents; use League\Flysystem\Filesystem; @@ -69,7 +70,7 @@ class FlysystemStreamWrapperManager extends StreamWrapperManager { /** * Created plugins. * - * @var \Drupal\flysystem\Plugin\FlysystemPluginInterface[] + * @var \Drupal\flysystem\Adapter\FlysystemAdapterInterface[] */ protected $plugins = []; @@ -134,7 +135,7 @@ class FlysystemStreamWrapperManager extends StreamWrapperManager { * @param string $scheme * The scheme. * - * @return \Drupal\flysystem\Plugin\FlysystemPluginInterface + * @return \Drupal\flysystem\Adapter\FlysystemAdapterInterface * The plugin. */ public function getPlugin($scheme) { @@ -171,7 +172,7 @@ class FlysystemStreamWrapperManager extends StreamWrapperManager { } /** - * Calls FlysystemPluginInterface::ensure() on each plugin. + * Calls FlysystemAdapterInterface::ensure() on each plugin. * * @param bool $force * (optional) Wheter to force the insurance. Defaults to false. diff --git a/src/FlysystemSubAdapterInterface.php b/src/FlysystemSubAdapterInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..a9410b8c99b1b13f62d98b208e3d4f5dca97465c --- /dev/null +++ b/src/FlysystemSubAdapterInterface.php @@ -0,0 +1,32 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\flysystem; + +use Drupal\Component\Plugin\ConfigurableInterface; +use Drupal\Component\Plugin\DependentPluginInterface; +use Drupal\Component\Plugin\DerivativeInspectionInterface; +use Drupal\Component\Plugin\PluginInspectionInterface; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Plugin\PluginFormInterface; + +/** + * Interface for flysystem_adapter plugins. + */ +interface FlysystemSubAdapterInterface extends PluginFormInterface, PluginInspectionInterface, DerivativeInspectionInterface, ConfigurableInterface, DependentPluginInterface, ContainerFactoryPluginInterface { + + /** + * Returns the translated plugin label. + */ + public function label(): string; + + /** + * Returns the subAdaper description. + * + * @return string + * SubAdapter description. + */ + public function getDescription(); + +} diff --git a/src/Form/ConfigForm.php b/src/Form/ConfigForm.php index e2c98ed1568ecefa4384636723824b86cd8058a1..06095a7f8fceb661a00ebee7cd4db5133d0d221d 100644 --- a/src/Form/ConfigForm.php +++ b/src/Form/ConfigForm.php @@ -230,16 +230,20 @@ class ConfigForm extends FormBase { * @param string $scheme * The scheme. * - * @return mixed + * @return array|FALSE * A list of files, or FALSE. */ protected function getFileList($scheme) { $filesystem = $this->factory->getFilesystem($scheme); + $directoryContents = $filesystem->listContents('', TRUE); - $files = array_filter($filesystem->listContents('', TRUE), function ($meta) { + $files = array_filter($directoryContents->toArray(), function ($meta) { return $meta['type'] === 'file'; }); - + // @todo solve phpstan issue: + // Parameter #1 $callback of function array_map expects + // (callable(League\Flysystem\StorageAttributes): mixed)|null, + // Closure(array): mixed given. return array_map(function (array $meta) { return $meta['path']; }, $files); diff --git a/src/Form/FlysystemAdapterConfigForm.php b/src/Form/FlysystemAdapterConfigForm.php new file mode 100644 index 0000000000000000000000000000000000000000..9da09f65f73320db26d6316a6f20b729aca5818c --- /dev/null +++ b/src/Form/FlysystemAdapterConfigForm.php @@ -0,0 +1,309 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\flysystem\Form; + +use Drupal\Component\Plugin\Exception\PluginException; +use Drupal\Component\Render\MarkupInterface; +use Drupal\Component\Utility\Html; +use Drupal\Core\Entity\EntityForm; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Messenger\MessengerInterface; +use Drupal\Core\Plugin\PluginFormInterface; +use Drupal\Core\Render\Markup; +use Drupal\flysystem\Entity\FlysystemAdapterConfig; +use Drupal\flysystem\FlysystemAdapterConfigInterface; +use Drupal\flysystem\FlysystemAdapterPluginManager; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Flysystem_adapter_config form. + */ +final class FlysystemAdapterConfigForm extends EntityForm { + /** + * The adapter plugin manager. + * + * @var \Drupal\flysystem\FlysystemAdapterPluginManager + */ + protected $subAdapterPluginManager; + + /** + * The messenger. + * + * @var \Drupal\Core\Messenger\MessengerInterface + */ + protected $messenger; + + /** + * Constructs a FlysystemAdapterConfigForm object. + * + * @param \Drupal\flysystem\FlysystemAdapterPluginManager $subadapter_plugin_manager + * The adapter plugin manager. + * @param \Drupal\Core\Messenger\MessengerInterface $messenger + * The messenger. + */ + public function __construct(FlysystemAdapterPluginManager $subadapter_plugin_manager, MessengerInterface $messenger) { + $this->subAdapterPluginManager = $subadapter_plugin_manager; + $this->messenger = $messenger; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + $subadapter_plugin_manager = $container->get('plugin.manager.flysystem_adapter'); + $messenger = $container->get('messenger'); + + return new static($subadapter_plugin_manager, $messenger); + } + + /** + * {@inheritdoc} + */ + public function form(array $form, FormStateInterface $form_state): array { + // If the form is being rebuilt, rebuild the entity with the current form + // values. + if ($form_state->isRebuilding()) { + $this->entity = $this->buildEntity($form, $form_state); + } + + $form = parent::form($form, $form_state); + + /** @var \Drupal\flysystem\FlysystemAdapterConfigInterface $flysystem_adapter_config */ + $flysystem_adapter_config = $this->getEntity(); + // Set the page title according to whether we are creating or editing the + // adapter. + if ($flysystem_adapter_config->isNew()) { + $form['#title'] = $this->t('Configure a new Flysystem Adapter'); + } + else { + $form['#title'] = $this->t('Edit Flysystem Adapter %label', ['%label' => $flysystem_adapter_config->label()]); + } + + $this->buildEntityConfigForm($form, $form_state, $flysystem_adapter_config); + + // Skip adding the adapter config form if we cleared the adapter form due to + // an error. + if ($form) { + $this->buildSubAdapterConfigForm($form, $form_state, $flysystem_adapter_config); + } + + return $form; + } + + /** + * Builds the form for the basic adapter configuration properties. + * + * @param array $form + * The current form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current form state. + * @param \Drupal\flysystem\FlysystemAdapterConfigInterface $flysystem_adapter_config + * The server that is being created or edited. + */ + protected function buildEntityConfigForm(array &$form, FormStateInterface $form_state, FlysystemAdapterConfigInterface $flysystem_adapter_config) { + // var_dump($flysystem_adapter_config); die;. + $form['label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Label'), + '#maxlength' => 255, + '#default_value' => $flysystem_adapter_config->label(), + '#required' => TRUE, + ]; + + $form['id'] = [ + '#type' => 'machine_name', + '#default_value' => $flysystem_adapter_config->id(), + '#machine_name' => [ + 'exists' => [FlysystemAdapterConfig::class, 'load'], + ], + '#disabled' => !$flysystem_adapter_config->isNew(), + ]; + + $form['status'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Enabled'), + '#default_value' => $flysystem_adapter_config->status(), + ]; + + $form['description'] = [ + '#type' => 'textarea', + '#title' => $this->t('Description'), + '#default_value' => $flysystem_adapter_config->getDescription(), + ]; + + $sub_adapters = $this->subAdapterPluginManager->getDefinitions(); + $options = []; + $descriptions = []; + foreach ($sub_adapters as $subadapter_id => $definition) { + $config = $subadapter_id === $flysystem_adapter_config->getSubAdapterId() ? $flysystem_adapter_config->getSubAdapterConfig() : []; + $config['#flysystem_adapter_config'] = $flysystem_adapter_config; + try { + /** @var \Drupal\flysystem\FlysystemSubAdapterInterface $sub_adapter */ + $sub_adapter = $this->subAdapterPluginManager + ->createInstance($subadapter_id, $config); + } + catch (PluginException) { + // @todo add graceful error handling. + continue; + } + $options[$subadapter_id] = $this->escapeHtml($sub_adapter->label()); + $descriptions[$subadapter_id]['#description'] = $this->escapeHtml($sub_adapter->getDescription()); + + } + asort($options, SORT_NATURAL | SORT_FLAG_CASE); + if ($options) { + if (count($options) == 1) { + $flysystem_adapter_config->set('sub_adapter', key($options)); + } + $form['sub_adapter'] = [ + '#type' => 'radios', + '#title' => $this->t('Flysystem Adapters'), + '#description' => $this->t('Choose a Flysystem Adapter type to configure.'), + '#options' => $options, + '#default_value' => $flysystem_adapter_config->getSubAdapterId(), + '#required' => TRUE, + '#disabled' => !$flysystem_adapter_config->isNew(), + '#ajax' => [ + 'callback' => [get_class($this), 'buildAjaxBackendConfigForm'], + 'wrapper' => 'flysystem-adapter-config-form', + 'method' => 'replace', + 'effect' => 'fade', + ], + ]; + $form['sub_adapter'] += $descriptions; + } + /* + else { + // @todo update with link to documentation on D.O. + $url = 'https://www.drupal.org/docs/8/modules/search-api/getting-started/server-backends-and-features'; + $args[':url'] = Url::fromUri($url)->toString(); + $error = $this->t('There are no Flysystem Adapters available to configure. \ + Install a <a href=":url">module that provides a Flysystem Adapter</a> \ + to proceed.', $args); + $this->messenger->addError($error); + $form = []; + } + */ + } + + /** + * {@inheritdoc} + */ + protected function actions(array $form, FormStateInterface $form_state) { + if ($form === []) { + return []; + } + + return parent::actions($form, $form_state); + } + + /** + * Builds the adapter-specific configuration form. + * + * @param array $form + * The current form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current form state. + * @param \Drupal\flysystem\FlysystemAdapterConfigInterface $flysystem_adapter_config + * The adapter that is being created or edited. + */ + public function buildSubAdapterConfigForm(array &$form, FormStateInterface $form_state, FlysystemAdapterConfigInterface $flysystem_adapter_config) { + // var_dump($flysystem_adapter_config); die(__METHOD__ . ":" . __LINE__); + // var_dump($form_state->getValues()); die(__METHOD__ . ":" . __LINE__);. + $form['sub_adapter_config'] = []; + if ($flysystem_adapter_config->hasValidSubAdapter()) { + // var_dump($flysystem_adapter_config); die(__METHOD__ . ":" . __LINE__);. + $sub_adapter_plugin = $flysystem_adapter_config->getSubAdapter(); + + $form_state->set('sub_adapter', $sub_adapter_plugin->getPluginId()); + $form_state->set(['sub_adapter_config', 'schema'], $flysystem_adapter_config->getSubAdapterValue('schema')); + + if ($sub_adapter_plugin instanceof PluginFormInterface) { + if ($form_state->isRebuilding()) { + $this->messenger->addWarning($this->t('Configure the selected Flysystem Adapter.')); + } + + // Attach the subadapter plugin configuration form. + $form['sub_adapter_config'] = $sub_adapter_plugin->buildSubConfigurationForm($form['sub_adapter_config'], $form_state, $flysystem_adapter_config); + // var_dump($form['sub_adapter_config']['schema']); die; + // Modify the backend plugin configuration container element. + $form['sub_adapter_config']['#type'] = 'details'; + $form['sub_adapter_config']['#title'] = $this->t('Configure %plugin configuration', ['%plugin' => $sub_adapter_plugin->label()]); + $form['sub_adapter_config']['#open'] = TRUE; + } + } + // Only notify the user of a missing backend plugin if we're editing an + // existing server. + elseif (!$flysystem_adapter_config->isNew()) { + $this->messenger->addError($this->t('The Flysystem adapter plugin is missing or invalid.')); + return; + } + $form['sub_adapter_config'] += [ + '#type' => 'container', + ]; + $form['sub_adapter_config']['#attributes']['id'] = 'flysystem-adapter-config-form'; + $form['sub_adapter_config']['#tree'] = TRUE; + } + + /** + * Handles switching the selected adapter plugin. + * + * @param array $form + * The current form array. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current form state. + * + * @return array + * The part of the form to return as AJAX. + */ + public static function buildAjaxAdapterConfigForm(array $form, FormStateInterface $form_state) { + // The work is already done in form(), where we rebuild the entity according + // to the current form values and then create the backend configuration form + // based on that. So we just need to return the relevant part of the form + // here. + return $form['sub_adapter_config']; + } + + /** + * {@inheritdoc} + */ + public function save(array $form, FormStateInterface $form_state): int { + $result = parent::save($form, $form_state); + $this->messenger->addStatus($this->t('The Flysystem adapter was successfully saved.')); + $message_args = ['%label' => $this->entity->label()]; + $message = ''; + switch ($result) { + case \SAVED_NEW: + $message = $this->t('Created new example %label.', $message_args); + break; + + case \SAVED_UPDATED: + $message = $this->t('Updated example %label.', $message_args); + break; + } + $this->messenger()->addStatus($message); + $form_state->setRedirectUrl($this->entity->toUrl('collection')); + return $result; + } + + /** + * Escapes HTML special characters in plain text, if necessary. + * + * @param string|\Drupal\Component\Render\MarkupInterface $text + * The text to escape. + * + * @return \Drupal\Component\Render\MarkupInterface + * If a markup object was passed as $text, it is returned as-is. Otherwise, + * the text is escaped and returned + */ + private function escapeHtml($text) { + if ($text instanceof MarkupInterface) { + return $text; + } + + return Markup::create(Html::escape((string) $text)); + } + +} diff --git a/src/Plugin/FlysystemAdapterPluginBase.php b/src/Plugin/FlysystemAdapterPluginBase.php new file mode 100644 index 0000000000000000000000000000000000000000..38060fd3d065bf13a8917b8eb9368188d97524f0 --- /dev/null +++ b/src/Plugin/FlysystemAdapterPluginBase.php @@ -0,0 +1,106 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\flysystem\Plugin; + +use Drupal\Component\Plugin\PluginBase; +use Drupal\Component\Utility\NestedArray; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Plugin\PluginDependencyTrait; +use Drupal\Core\StringTranslation\StringTranslationTrait; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Base class for flysystem_adapter plugins. + */ +abstract class FlysystemAdapterPluginBase extends PluginBase { + + use PluginDependencyTrait { + getPluginDependencies as traitGetPluginDependencies; + calculatePluginDependencies as traitCalculatePluginDependencies; + } + use StringTranslationTrait; + + /** + * The adapter plugin configuration. + * + * @var array + */ + protected $sub_adapter_config = []; + + /** + * {@inheritdoc} + */ + public function __construct(array $configuration, $plugin_id, array $plugin_definition) { + $configuration += $this->defaultConfiguration(); + parent::__construct($configuration, $plugin_id, $plugin_definition); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + $plugin = new static($configuration, $plugin_id, $plugin_definition); + + /** @var \Drupal\Core\StringTranslation\TranslationInterface $translation */ + $translation = $container->get('string_translation'); + $plugin->setStringTranslation($translation); + + return $plugin; + } + + /** + * {@inheritdoc} + */ + public function getSubAdapterValue($key) { + if (is_array($key)) { + return NestedArray::getValue($key, $this->sub_adapter_config); + } + return $this->sub_adapter_config[$key] ?? ''; + } + + /** + * {@inheritdoc} + */ + public function calculateDependencies() { + return []; + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return []; + } + + /** + * {@inheritdoc} + */ + public function getConfiguration() { + return $this->configuration; + } + + /** + * {@inheritdoc} + */ + public function setConfiguration(array $configuration) { + $this->configuration = $configuration + $this->defaultConfiguration(); + } + + /** + * {@inheritdoc} + */ + public function label(): string { + // Cast the label to a string since it is a TranslatableMarkup object. + return (string) $this->pluginDefinition['label']; + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + return $form; + } + +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index 079563db8d09fb362212ee3cccf48c42e9d90255..0000000000000000000000000000000000000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,27 +0,0 @@ -<?php - -/** - * @file - * Searches for the core bootstrap file. - */ - -$dir = __DIR__; - -// Match against previous dir for Windows. -$previous_dir = ''; - -while ($dir = dirname($dir)) { - // We've reached the root. - if ($dir === $previous_dir) { - break; - } - - $previous_dir = $dir; - - if (is_file($dir . '/core/tests/bootstrap.php')) { - require_once $dir . '/core/tests/bootstrap.php'; - return; - } -} - -throw new RuntimeException('Unable to load core bootstrap.php.'); diff --git a/tests/src/Functional/ModuleInstallUninstallWebTest.php b/tests/src/Functional/ModuleInstallUninstallWebTest.php deleted file mode 100644 index 8cb2d41cd2421355dd483d8c19005c6a16917f8f..0000000000000000000000000000000000000000 --- a/tests/src/Functional/ModuleInstallUninstallWebTest.php +++ /dev/null @@ -1,38 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Functional; - -use Drupal\Tests\BrowserTestBase; - -/** - * Tests module installation and uninstallation. - * - * @group flysystem - */ -class ModuleInstallUninstallWebTest extends BrowserTestBase { - - /** - * {@inheritdoc} - */ - protected $defaultTheme = 'stark'; - - /** - * {@inheritdoc} - */ - protected static $modules = ['flysystem']; - - /** - * Tests installation and uninstallation. - */ - public function testInstallationAndUninstallation() { - $module_handler = \Drupal::moduleHandler(); - $this->assertTrue($module_handler->moduleExists(reset(static::$modules))); - - /** @var \Drupal\Core\Extension\ModuleInstallerInterface $module_installer */ - $module_installer = \Drupal::service('module_installer'); - - $module_installer->uninstall(static::$modules); - $this->assertFalse($module_handler->moduleExists(reset(static::$modules))); - } - -} diff --git a/tests/src/Kernel/CollectionOptimizerTest.php b/tests/src/Kernel/CollectionOptimizerTest.php deleted file mode 100644 index 9fb14f7ed0bb56e0e3979c0364bf52412033f090..0000000000000000000000000000000000000000 --- a/tests/src/Kernel/CollectionOptimizerTest.php +++ /dev/null @@ -1,184 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Kernel; - -use Drupal\Core\Asset\AssetCollectionGrouperInterface; -use Drupal\Core\Asset\CssOptimizer; -use Drupal\Core\Asset\JsOptimizer; -use Drupal\Core\State\StateInterface; -use Drupal\flysystem\Asset\AssetDumper; -use Drupal\flysystem\Asset\CssCollectionOptimizer; -use Drupal\flysystem\Asset\JsCollectionOptimizer; -use Drupal\KernelTests\KernelTestBase; -use org\bovigo\vfs\vfsStream; - -/** - * @covers \Drupal\flysystem\Asset\JsCollectionOptimizer - * @covers \Drupal\flysystem\Asset\CssCollectionOptimizer - * - * @group flysystem - */ -class CollectionOptimizerTest extends KernelTestBase { - - /** - * {@inheritdoc} - */ - protected static $modules = ['file']; - - /** - * The file URL generator. - * - * @var \Drupal\Core\File\FileUrlGeneratorInterface - */ - protected $fileUrlGenerator; - - /** - * {@inheritdoc} - */ - public function setUp(): void { - parent::setUp(); - $this->fileUrlGenerator = $this->container->get('file_url_generator'); - $this->cleanUp(); - } - - /** - * {@inheritdoc} - */ - public function tearDown(): void { - $this->cleanUp(); - parent::tearDown(); - } - - /** - * @covers \Drupal\flysystem\Asset\JsCollectionOptimizer - */ - public function testJsCollectionOptimizer() { - vfsStream::setup('js'); - - $this->setSetting('flysystem', [ - 'vfs' => [ - 'serve_js' => TRUE, - 'driver' => 'local', - ], - ]); - - foreach ($this->jsFilesUnderTest() as $js_file => $expired) { - file_put_contents($js_file, 'dummy'); - if ($expired === TRUE) { - // 2592000 is the default value of stale_file_threshold - touch($js_file, \Drupal::time()->getRequestTime() - 2592001); - continue; - } - touch($js_file, \Drupal::time()->getRequestTime() - 2591999); - } - - $grouper = $this->prophesize(AssetCollectionGrouperInterface::class); - $dumper = $this->prophesize(AssetDumper::class); - $state = $this->prophesize(StateInterface::class); - - $optimizer = new JsCollectionOptimizer($grouper->reveal(), new JsOptimizer(), $dumper->reveal(), $state->reveal(), $this->container->get('file_system')); - $optimizer->deleteAll(); - - foreach ($this->jsFilesUnderTest() as $js_file => $expired) { - if ($expired === TRUE) { - $this->assertFileDoesNotExist($js_file); - continue; - } - $this->assertFileExists($js_file); - } - - } - - /** - * @covers \Drupal\flysystem\Asset\CssCollectionOptimizer - */ - public function testCssCollectionOptimizer() { - vfsStream::setup('css'); - - $this->setSetting('flysystem', [ - 'vfs' => [ - 'serve_css' => TRUE, - 'driver' => 'local', - ], - ]); - - foreach ($this->cssFilesUnderTest() as $css_file => $expired) { - file_put_contents($css_file, 'dummy'); - if ($expired === TRUE) { - // 2592000 is the default value of stale_file_threshold - touch($css_file, \Drupal::time()->getRequestTime() - 2592001); - continue; - } - touch($css_file, \Drupal::time()->getRequestTime() - 2591999); - } - - $grouper = $this->prophesize(AssetCollectionGrouperInterface::class); - $dumper = $this->prophesize(AssetDumper::class); - $state = $this->prophesize(StateInterface::class); - - $optimizer = new CssCollectionOptimizer($grouper->reveal(), new CssOptimizer($this->fileUrlGenerator), $dumper->reveal(), $state->reveal(), $this->container->get('file_system')); - $optimizer->deleteAll(); - - foreach ($this->cssFilesUnderTest() as $css_file => $expired) { - if ($expired === TRUE) { - $this->assertFileDoesNotExist($css_file); - continue; - } - $this->assertFileExists($css_file); - } - - } - - /** - * CSS files involve in testing CssCollectionOptimizer. - * - * @return array - * Keyed by the file URI, and its value is the flag of expiration. TRUE to - * valid, FALSE to expired. - */ - private function cssFilesUnderTest() { - return [ - 'vfs://css/foo_expired.css' => TRUE, - 'vfs://css/bar_expired.css' => TRUE, - 'vfs://css/baz_expired.css' => TRUE, - 'vfs://css/foo.css' => FALSE, - 'vfs://css/bar.css' => FALSE, - 'vfs://css/baz.css' => FALSE, - ]; - } - - /** - * JS files involve in testing JsCollectionOptimizer. - * - * @return array - * Keyed by the file URI, and its value is the flag of expiration. TRUE to - * expired, FALSE to non-expired. - */ - private function jsFilesUnderTest() { - return [ - 'vfs://js/foo_expired.js' => TRUE, - 'vfs://js/bar_expired.js' => TRUE, - 'vfs://js/baz_expired.js' => TRUE, - 'vfs://js/foo.js' => FALSE, - 'vfs://js/bar.js' => FALSE, - 'vfs://js/zoo.js' => FALSE, - ]; - } - - /** - * A helper method for removing files before and after running tests. - */ - private function cleanUp() { - foreach ($this->jsFilesUnderTest() as $js_file => $flag) { - if (file_exists($js_file)) { - unlink($js_file); - } - } - foreach ($this->cssFilesUnderTest() as $css_file => $flag) { - if (file_exists($css_file)) { - unlink($css_file); - } - } - } - -} diff --git a/tests/src/Unit/Asset/SchemeExtensionTraitTest.php b/tests/src/Unit/Asset/SchemeExtensionTraitTest.php deleted file mode 100644 index a9a01a9c781c4b39031c3552789fea3789d08b83..0000000000000000000000000000000000000000 --- a/tests/src/Unit/Asset/SchemeExtensionTraitTest.php +++ /dev/null @@ -1,33 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Unit\Asset; - -use Drupal\Core\Site\Settings; -use Drupal\flysystem\Asset\SchemeExtensionTrait; -use Drupal\Tests\UnitTestCase; - -/** - * @coversDefaultClass \Drupal\flysystem\Asset\SchemeExtensionTrait - * @group flysystem - */ -class SchemeExtensionTraitTest extends UnitTestCase { - - /** - * @covers ::getSchemeForExtension - */ - public function test() { - - new Settings([ - 'flysystem' => [ - 'local' => ['serve_js' => TRUE, 'driver' => 'asdf'], - 'ftp' => ['serve_css' => TRUE], - ], - ]); - - $trait = $this->getMockForTrait(SchemeExtensionTrait::class); - $this->assertSame('local', $trait->getSchemeForExtension('js')); - $this->assertSame('public', $trait->getSchemeForExtension('css')); - $this->assertSame('public', $trait->getSchemeForExtension('jpg')); - } - -} diff --git a/tests/src/Unit/Event/EnsureEventTest.php b/tests/src/Unit/Event/EnsureEventTest.php deleted file mode 100644 index 8960845826e74c4d8b593dd47786cfcc4b52d3b5..0000000000000000000000000000000000000000 --- a/tests/src/Unit/Event/EnsureEventTest.php +++ /dev/null @@ -1,29 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Unit\Event; - -use Drupal\flysystem\Event\EnsureEvent; -use Drupal\Tests\UnitTestCase; - -/** - * Tests EnsureEvent. - * - * @coversDefaultClass \Drupal\flysystem\Event\EnsureEvent - * @covers \Drupal\flysystem\Event\EnsureEvent - * @group flysystem - */ -class EnsureEventTest extends UnitTestCase { - - /** - * Tests the basic setters/getters of EnsureEvent. - */ - public function test() { - $event = new EnsureEvent('scheme', 10, 'message', ['1234']); - - $this->assertSame('scheme', $event->getScheme()); - $this->assertSame(10, $event->getSeverity()); - $this->assertSame('message', $event->getMessage()); - $this->assertSame(['1234'], $event->getContext()); - } - -} diff --git a/tests/src/Unit/EventSubscriber/EnsureSubscriberTest.php b/tests/src/Unit/EventSubscriber/EnsureSubscriberTest.php deleted file mode 100644 index 2b2effd16308697fdb885ee089fd29e3dda2ea1a..0000000000000000000000000000000000000000 --- a/tests/src/Unit/EventSubscriber/EnsureSubscriberTest.php +++ /dev/null @@ -1,44 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Unit\EventSubscriber; - -use Drupal\flysystem\Event\EnsureEvent; -use Drupal\flysystem\Event\FlysystemEvents; -use Drupal\flysystem\EventSubscriber\EnsureSubscriber; -use Drupal\Tests\UnitTestCase; -use Psr\Log\LoggerInterface; -use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; - -/** - * @coversDefaultClass \Drupal\flysystem\EventSubscriber\EnsureSubscriber - * @covers \Drupal\flysystem\EventSubscriber\EnsureSubscriber - * - * @group flysystem - */ -class EnsureSubscriberTest extends UnitTestCase { - - /** - * Tests that the event subscriber logs ensure() calls. - */ - public function testLoggingHappens() { - $logger = $this->prophesize(LoggerInterface::class); - $dispatcher = $this->createMock(EventDispatcherInterface::class); - $logger->log('severity', 'message', ['context'])->shouldBeCalled(); - - $event = new EnsureEvent('scheme', 'severity', 'message', ['context']); - - $subscriber = new EnsureSubscriber($logger->reveal()); - - $subscriber->onEnsure($event, FlysystemEvents::ENSURE, $dispatcher); - } - - /** - * Tests that the ENSURE event is registered. - */ - public function testSubscribedEvents() { - $result = EnsureSubscriber::getSubscribedEvents(); - - $this->assertTrue(isset($result[FlysystemEvents::ENSURE])); - } - -} diff --git a/tests/src/Unit/Flysystem/Adapter/CacheItemBackendTest.php b/tests/src/Unit/Flysystem/Adapter/CacheItemBackendTest.php deleted file mode 100644 index 6c235672598bdf7220121a36bf813dc5dc138f99..0000000000000000000000000000000000000000 --- a/tests/src/Unit/Flysystem/Adapter/CacheItemBackendTest.php +++ /dev/null @@ -1,100 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Unit\Flysystem\Adapter; - -use Drupal\Core\Cache\MemoryBackend; -use Drupal\flysystem\Flysystem\Adapter\CacheItem; -use Drupal\flysystem\Flysystem\Adapter\CacheItemBackend; -use Drupal\Tests\UnitTestCase; - -/** - * Tests \Drupal\flysystem\Flysystem\Adapter\CacheItemBackend. - * - * @group flysystem - * - * @coversDefaultClass \Drupal\flysystem\Flysystem\Adapter\CacheItemBackend - * @covers \Drupal\flysystem\Flysystem\Adapter\CacheItemBackend - */ -class CacheItemBackendTest extends UnitTestCase { - - /** - * The cache backend used in the CacheItemBackend. - * - * @var \Drupal\Core\Cache\CacheBackendInterface - */ - protected $cacheBackend; - - /** - * The cache item backend to test. - * - * @var \Drupal\flysystem\Flysystem\Adapter\CacheItemBackend - */ - protected $cacheItemBackend; - - /** - * {@inheritdoc} - */ - public function setUp(): void { - parent::setUp(); - $this->cacheBackend = new MemoryBackend('foo'); - $this->cacheItemBackend = new CacheItemBackend('test-scheme', $this->cacheBackend); - } - - /** - * Tests whether a cache item exists. - */ - public function testHas() { - $this->assertFalse($this->cacheItemBackend->has('test.txt')); - } - - /** - * Tests loading a cache item from the cache. - */ - public function testSetIsLoaded() { - $cache_item = new CacheItem(); - $cache_item->updateMetadata(['mimetype' => 'test_mimetype']); - $this->cacheItemBackend->set('test_path', $cache_item); - - $metadata = $this->cacheItemBackend->load('test_path')->getMetadata(); - $this->assertSame('test_mimetype', $metadata['mimetype']); - } - - /** - * Tests when loading a cache item creates a new item. - */ - public function testLoadMiss() { - $item = $this->cacheItemBackend->load('test_path'); - $this->assertInstanceOf(CacheItem::class, $item); - } - - /** - * Tests deleting by a path. - */ - public function testDelete() { - $cache_item = new CacheItem(); - $cache_item->updateMetadata(['mimetype' => 'test_mimetype']); - - $this->cacheItemBackend->set('test_path', $cache_item); - $this->cacheItemBackend->delete('test_path'); - - $metadata = $this->cacheItemBackend->load('test_path')->getMetadata(); - $this->assertTrue(empty($metadata['mimetype'])); - } - - /** - * Tests deleting multiple items at once. - */ - public function testDeleteMultiple() { - $cache_item_one = new CacheItem(); - $cache_item_two = new CacheItem(); - - $this->cacheItemBackend->set('one', $cache_item_one); - $this->cacheItemBackend->set('two', $cache_item_two); - - $this->cacheItemBackend->deleteMultiple(['one', 'two']); - - $this->assertNotSame($cache_item_one, $this->cacheItemBackend->load('one')); - $this->assertNotSame($cache_item_two, $this->cacheItemBackend->load('two')); - } - -} diff --git a/tests/src/Unit/Flysystem/Adapter/CacheItemTest.php b/tests/src/Unit/Flysystem/Adapter/CacheItemTest.php deleted file mode 100644 index 70a91bf3ef7a26402c67048f0b7eaa7b42c1ca39..0000000000000000000000000000000000000000 --- a/tests/src/Unit/Flysystem/Adapter/CacheItemTest.php +++ /dev/null @@ -1,42 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Unit\Flysystem\Adapter; - -use Drupal\flysystem\Flysystem\Adapter\CacheItem; -use Drupal\Tests\UnitTestCase; - -/** - * Tests \Drupal\flysystem\Flysystem\Adapter\CacheItem. - * - * @group flysystem - * - * @coversDefaultClass \Drupal\flysystem\Flysystem\Adapter\CacheItem - * @covers \Drupal\flysystem\Flysystem\Adapter\CacheItem - */ -class CacheItemTest extends UnitTestCase { - - /** - * Tests metadata updating and getting. - */ - public function test() { - $cache_item = new CacheItem(); - - $metadata = [ - 'size' => 1234, - 'mimetype' => 'test_mimetype', - 'visibility' => 'public', - 'timestamp' => 123456, - 'type' => 'file', - 'contents' => 'test contents', - 'path' => 'file_path', - ]; - - $cache_item->updateMetadata($metadata); - - unset($metadata['contents'], $metadata['path']); - - $this->assertSame($metadata, $cache_item->getMetadata()); - - } - -} diff --git a/tests/src/Unit/Flysystem/Adapter/DrupalCacheAdapterTest.php b/tests/src/Unit/Flysystem/Adapter/DrupalCacheAdapterTest.php deleted file mode 100644 index 0b6d04eb45f2a7c944aaea15a72164046ca06072..0000000000000000000000000000000000000000 --- a/tests/src/Unit/Flysystem/Adapter/DrupalCacheAdapterTest.php +++ /dev/null @@ -1,299 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Unit\Flysystem\Adapter; - -use Drupal\Core\Cache\MemoryBackend; -use Drupal\flysystem\Flysystem\Adapter\CacheItemBackend; -use Drupal\flysystem\Flysystem\Adapter\DrupalCacheAdapter; -use Drupal\Tests\UnitTestCase; -use League\Flysystem\AdapterInterface; -use League\Flysystem\Config; - -/** - * Test the Drupal Cache Adapter. - * - * @group flysystem - * - * @coversDefaultClass \Drupal\flysystem\Flysystem\Adapter\DrupalCacheAdapter - * @covers \Drupal\flysystem\Flysystem\Adapter\DrupalCacheAdapter - */ -class DrupalCacheAdapterTest extends UnitTestCase { - - /** - * URI scheme to use for testing. - * - * @var string - */ - const SCHEME = 'test-scheme'; - - /** - * The main test file. - * - * @var string - */ - const FILE = 'test.txt'; - - /** - * The wrapped Flysytem adaper. - * - * @var \League\Flysystem\AdapterInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $adapter; - - /** - * The cache adapter under test. - * - * @var \Drupal\flysystem\Flysystem\Adapter\DrupalCacheAdapter - */ - protected $cacheAdapter; - - /** - * The flysystem backend for testing. - * - * @var \Drupal\flysystem\Flysystem\Adapter\CacheItemBackend - */ - protected $cacheItemBackend; - - /** - * {@inheritdoc} - */ - public function setUp(): void { - parent::setUp(); - $this->cacheItemBackend = new CacheItemBackend(static::SCHEME, new MemoryBackend('foo')); - $this->adapter = $this->prophesize(AdapterInterface::class); - $this->cacheAdapter = new DrupalCacheAdapter(static::SCHEME, $this->adapter->reveal(), $this->cacheItemBackend); - } - - /** - * Tests creating a public file. - */ - public function testWriteSuccess() { - $config = new Config(); - $this->adapter - ->write(static::FILE, 'contents', $config) - ->willReturn(['visibility' => 'public']); - - $metadata = $this->cacheAdapter->write(static::FILE, 'contents', $config); - $this->assertSame('public', $metadata['visibility']); - $this->assertSame('public', $this->cacheAdapter->getVisibility(static::FILE)['visibility']); - $this->assertTrue($this->cacheItemBackend->has(static::FILE)); - } - - /** - * Tests creating a public file stream. - */ - public function testWriteStreamSuccess() { - $config = new Config(); - $stream = fopen('data:text/plain,contents', 'rb'); - - $this->adapter - ->writeStream(static::FILE, $stream, $config) - ->willReturn(['timestamp' => 12345]); - - $metadata = $this->cacheAdapter->writeStream(static::FILE, $stream, $config); - $this->assertSame(12345, $metadata['timestamp']); - $this->assertSame(12345, $this->cacheAdapter->getTimestamp(static::FILE)['timestamp']); - $this->assertTrue($this->cacheItemBackend->has(static::FILE)); - } - - /** - * Tests public file updates. - */ - public function testUpdateSuccess() { - $config = new Config(); - $this->adapter - ->update(static::FILE, 'contents', $config) - ->willReturn(['visibility' => 'public']); - - $metadata = $this->cacheAdapter->update(static::FILE, 'contents', $config); - $this->assertSame('public', $metadata['visibility']); - $this->assertSame('public', $this->cacheAdapter->getVisibility(static::FILE)['visibility']); - } - - /** - * Tests public file stream updates. - */ - public function testUpdateStreamSuccess() { - $config = new Config(); - $stream = fopen('data:text/plain,contents', 'rb'); - - $this->adapter - ->updateStream(static::FILE, $stream, $config) - ->willReturn(['mimetype' => 'test_mimetype']); - - $metadata = $this->cacheAdapter->updateStream(static::FILE, $stream, $config); - $this->assertSame('test_mimetype', $metadata['mimetype']); - $this->assertSame('test_mimetype', $this->cacheAdapter->getMimetype(static::FILE)['mimetype']); - $this->assertTrue($this->cacheItemBackend->has(static::FILE)); - } - - /** - * Tests renaming a file. - */ - public function testRenameSuccess() { - $config = new Config(); - $this->adapter - ->write(static::FILE, 'contents', $config) - ->willReturn(['size' => 1234]); - - $this->cacheAdapter->write(static::FILE, 'contents', $config); - - $this->adapter - ->rename(static::FILE, 'new.txt') - ->willReturn(TRUE); - - $this->assertTrue($this->cacheAdapter->rename(static::FILE, 'new.txt')); - $this->assertSame(1234, $this->cacheAdapter->getSize('new.txt')['size']); - - $this->assertFalse($this->cacheItemBackend->has(static::FILE)); - $this->assertTrue($this->cacheItemBackend->has('new.txt')); - - } - - /** - * Tests copying a file. - */ - public function testCopySuccess() { - $config = new Config(); - $this->adapter - ->write(static::FILE, 'contents', $config) - ->willReturn(['size' => 1234]); - - $this->cacheAdapter->write(static::FILE, 'contents', $config); - - $this->adapter->copy(static::FILE, 'new.txt')->willReturn(TRUE); - - $this->assertTrue($this->cacheAdapter->copy(static::FILE, 'new.txt')); - - $this->assertSame(1234, $this->cacheAdapter->getSize(static::FILE)['size']); - $this->assertSame(1234, $this->cacheAdapter->getSize('new.txt')['size']); - $this->assertTrue($this->cacheItemBackend->has(static::FILE)); - $this->assertTrue($this->cacheItemBackend->has('new.txt')); - } - - /** - * Tests deleting a file. - */ - public function testDeleteSuccess() { - $config = new Config(); - $this->adapter - ->write(static::FILE, 'contents', $config) - ->willReturn(['size' => 1234]); - - $this->cacheAdapter->write(static::FILE, 'contents', $config); - - $this->adapter->delete(static::FILE)->willReturn(TRUE); - - $this->assertTrue($this->cacheAdapter->delete(static::FILE)); - $this->assertFalse($this->cacheItemBackend->has(static::FILE)); - } - - /** - * Tests deleting a file directory. - */ - public function testDeleteDirSuccess() { - $config = new Config(); - // Create a directory with one sub file. - $this->adapter->createDir('testdir', $config)->willReturn(['type' => 'dir']); - $this->adapter->write('testdir/test.txt', 'contents', $config) - ->willReturn(['size' => 1234]); - $this->adapter->deleteDir('testdir')->willReturn(TRUE); - $this->adapter->listContents('testdir', TRUE)->willReturn([ - ['path' => 'testdir'], - ['path' => 'testdir/test.txt'], - ]); - - $this->cacheAdapter->createDir('testdir', $config); - $this->cacheAdapter->write('testdir/test.txt', 'contents', $config); - - $this->assertTrue($this->cacheAdapter->deleteDir('testdir')); - - $this->assertFalse($this->cacheItemBackend->has('testdir/test.txt')); - $this->assertFalse($this->cacheItemBackend->has('testdir')); - } - - /** - * Tests visibility checking on private files. - */ - public function testSetVisibilitySuccess() { - $this->adapter - ->setVisibility(static::FILE, 'private') - ->willReturn(['visibility' => 'private']); - - $metadata = $this->cacheAdapter->setVisibility(static::FILE, 'private'); - $this->assertSame('private', $metadata['visibility']); - $this->assertSame('private', $this->cacheAdapter->getVisibility(static::FILE)['visibility']); - $this->assertTrue($this->cacheItemBackend->has(static::FILE)); - } - - /** - * Tests file loading success. - */ - public function testHasSuccess() { - $cache_item = $this->cacheItemBackend->load(static::FILE); - $this->cacheItemBackend->set(static::FILE, $cache_item); - $this->assertTrue($this->cacheAdapter->has(static::FILE)); - } - - /** - * Tests file loading failure. - */ - public function testHasFail() { - $this->adapter->has(static::FILE)->willReturn(TRUE); - $this->assertTrue($this->cacheAdapter->has(static::FILE)); - } - - /** - * Tests reading a file. - */ - public function testRead() { - $this->adapter->read(static::FILE)->willReturn(TRUE); - $this->assertTrue($this->cacheAdapter->read(static::FILE)); - } - - /** - * Tests reading a stream. - */ - public function testReadStream() { - $this->adapter->readStream(static::FILE)->willReturn(TRUE); - $this->assertTrue($this->cacheAdapter->readStream(static::FILE)); - } - - /** - * Tests listing contents of a directory. - */ - public function testListContentsSuccess() { - $this->adapter->listContents('testdir', TRUE)->willReturn(TRUE); - $this->assertTrue($this->cacheAdapter->listContents('testdir', TRUE)); - } - - /** - * Tests retrieving file metadata. - */ - public function testGetMetadataSuccess() { - $cache_item = $this->cacheItemBackend->load(static::FILE); - $cache_item->updateMetadata(['type' => 'dir']); - $this->cacheItemBackend->set(static::FILE, $cache_item); - - $this->assertSame('dir', $this->cacheAdapter->getMetadata(static::FILE)['type']); - } - - /** - * Tests failing to retrieve file metadata. - */ - public function testGetMetadataFail() { - $this->adapter->getMetadata(static::FILE)->willReturn(['type' => 'dir']); - - $this->assertSame('dir', $this->cacheAdapter->getMetadata(static::FILE)['type']); - } - - /** - * Tests failing to retrieve file size. - */ - public function testGetSizeFail() { - $this->adapter->getSize(static::FILE)->willReturn(['size' => 123]); - - $this->assertSame(123, $this->cacheAdapter->getSize(static::FILE)['size']); - } - -} diff --git a/tests/src/Unit/Flysystem/Adapter/MissingAdapterTest.php b/tests/src/Unit/Flysystem/Adapter/MissingAdapterTest.php deleted file mode 100644 index 7bae52f524c2facf20a20331cce938fce7f54308..0000000000000000000000000000000000000000 --- a/tests/src/Unit/Flysystem/Adapter/MissingAdapterTest.php +++ /dev/null @@ -1,42 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Unit\Flysystem\Adapter; - -use Drupal\flysystem\Flysystem\Adapter\MissingAdapter; -use Drupal\Tests\UnitTestCase; -use League\Flysystem\Config; - -/** - * @coversDefaultClass \Drupal\flysystem\Flysystem\Adapter\MissingAdapter - * @group flysystem - */ -class MissingAdapterTest extends UnitTestCase { - - /** - * @covers \Drupal\flysystem\Flysystem\Adapter\MissingAdapter - */ - public function test() { - $adapter = new MissingAdapter(); - - $this->assertFalse($adapter->copy('', '')); - $this->assertFalse($adapter->createDir('', new Config())); - $this->assertFalse($adapter->delete('')); - $this->assertFalse($adapter->deleteDir('')); - $this->assertIsArray($adapter->listContents('')); - $this->assertFalse($adapter->getMetadata('')); - $this->assertFalse($adapter->getMimetype('')); - $this->assertFalse($adapter->getSize('')); - $this->assertFalse($adapter->getTimestamp('')); - $this->assertFalse($adapter->getVisibility('')); - $this->assertFalse($adapter->has('')); - $this->assertFalse($adapter->setVisibility('', 'public')); - $this->assertFalse($adapter->update('', '', new Config())); - $this->assertFalse($adapter->updateStream('', NULL, new Config())); - $this->assertFalse($adapter->read('')); - $this->assertFalse($adapter->readStream('')); - $this->assertFalse($adapter->rename('', '')); - $this->assertFalse($adapter->write('', '', new Config())); - $this->assertFalse($adapter->writeStream('', NULL, new Config())); - } - -} diff --git a/tests/src/Unit/Flysystem/LocalTest.php b/tests/src/Unit/Flysystem/LocalTest.php deleted file mode 100644 index 233f8b391f7d51c43e0c1c543c856166d284597c..0000000000000000000000000000000000000000 --- a/tests/src/Unit/Flysystem/LocalTest.php +++ /dev/null @@ -1,147 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Unit\Flysystem; - -use Drupal\Core\DependencyInjection\ContainerBuilder; -use Drupal\Core\Logger\RfcLogLevel; -use Drupal\Core\Routing\UrlGeneratorInterface; -use Drupal\Core\Site\Settings; -use Drupal\flysystem\Flysystem\Adapter\MissingAdapter; -use Drupal\flysystem\Flysystem\Local; -use Drupal\Tests\UnitTestCase; -use League\Flysystem\Adapter\Local as LocalAdapter; -use Prophecy\Argument; - -/** - * @coversDefaultClass \Drupal\flysystem\Flysystem\Local - * @group flysystem - */ -class LocalTest extends UnitTestCase { - - /** - * {@inheritdoc} - */ - public function setUp(): void { - parent::setUp(); - $GLOBALS['base_url'] = 'http://example.com'; - - $container = new ContainerBuilder(); - $url_generator = $this->prophesize(UrlGeneratorInterface::class); - $url_generator->generateFromRoute(Argument::cetera())->willReturn('download'); - $container->set('url_generator', $url_generator->reveal()); - \Drupal::setContainer($container); - (new LocalAdapter('foo/bar'))->deleteDir(''); - @rmdir('foo/bar'); - @rmdir('foo'); - mkdir('foo'); - mkdir('foo/bar'); - - touch('test.txt'); - } - - /** - * {@inheritdoc} - */ - public function tearDown(): void { - (new LocalAdapter('foo'))->deleteDir(''); - @rmdir('foo'); - - unlink('test.txt'); - - @unlink('does_not_exist/.htaccess'); - @rmdir('does_not_exist'); - } - - /** - * @covers ::__construct - * @covers ::create - */ - public function testCreateReturnsPlugin() { - $container = new ContainerBuilder(); - $settings = new Settings([]); - $container->set('settings', $settings); - - $configuration = ['root' => 'foo/bar']; - - $this->assertInstanceOf(Local::class, Local::create($container, $configuration, '', [])); - } - - /** - * @covers ::getAdapter - * @covers ::ensureDirectory - */ - public function testReturnsLocalAdapter() { - $this->assertInstanceOf(LocalAdapter::class, (new Local('foo/bar', FALSE))->getAdapter()); - } - - /** - * @covers ::getAdapter - * @covers ::ensureDirectory - */ - public function testMissingAdapterReturnedWhenPathIsFile() { - $this->assertInstanceOf(MissingAdapter::class, (new Local('test.txt'))->getAdapter()); - } - - /** - * @covers ::getExternalUrl - */ - public function testReturnsValidLocalUrl() { - $plugin = new Local('foo/bar', FALSE); - $this->assertSame('download', $plugin->getExternalUrl('uri://test.html')); - } - - /** - * @covers ::getExternalUrl - */ - public function testReturnsValidExternalUrl() { - $plugin = new Local('foo/bar', TRUE); - $this->assertSame('http://example.com/foo/bar/test%20thing.html', $plugin->getExternalUrl('uri://test thing.html')); - } - - /** - * @covers ::ensure - * @covers ::ensureDirectory - */ - public function testDirectoryIsAutoCreatedAndHtaccessIsWritten() { - new Local('does_not_exist'); - $this->assertTrue(is_dir('does_not_exist')); - $this->assertTrue(is_file('does_not_exist/.htaccess')); - - } - - /** - * @covers ::ensure - * @covers ::writeHtaccess - */ - public function testHtaccessNotOverwritten() { - file_put_contents('foo/bar/.htaccess', 'htcontent'); - - $result = (new Local('foo/bar'))->ensure(); - - $this->assertCount(1, $result); - $this->assertSame(RfcLogLevel::INFO, $result[0]['severity']); - $this->assertSame('htcontent', file_get_contents('foo/bar/.htaccess')); - } - - /** - * @covers ::ensure - * @covers ::writeHtaccess - */ - public function testHtaccessNotOverwrittenAndFails() { - mkdir('foo/bar/.htaccess', 0777, TRUE); - - $result = (new Local('foo/bar'))->ensure(TRUE); - $this->assertCount(1, $result); - $this->assertSame('https://www.drupal.org/SA-CORE-2013-003', $result[0]['context']['@url']); - } - - /** - * @covers ::ensure - * @covers ::writeHtaccess - */ - public function testEnsureReturnsErrorWhenCantCreateDir() { - $result = (new Local('test.txt'))->ensure(); - $this->assertSame('test.txt', $result[0]['context']['%root']); - } - -} diff --git a/tests/src/Unit/Flysystem/MissingTest.php b/tests/src/Unit/Flysystem/MissingTest.php deleted file mode 100644 index d69a8bbd4be12bca2dd143601418e09d92a3cded..0000000000000000000000000000000000000000 --- a/tests/src/Unit/Flysystem/MissingTest.php +++ /dev/null @@ -1,26 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Unit\Flysystem; - -use Drupal\flysystem\Flysystem\Adapter\MissingAdapter; -use Drupal\flysystem\Flysystem\Missing; -use Drupal\Tests\UnitTestCase; - -/** - * @coversDefaultClass \Drupal\flysystem\Flysystem\Missing - * @group flysystem - */ -class MissingTest extends UnitTestCase { - - /** - * @covers \Drupal\flysystem\Flysystem\Missing - */ - public function test() { - $plugin = new Missing([]); - $this->assertInstanceOf(MissingAdapter::class, $plugin->getAdapter()); - $this->assertTrue(is_array($plugin->ensure())); - $this->assertCount(1, $plugin->ensure()); - $this->assertSame('', $plugin->getExternalUrl('asdf')); - } - -} diff --git a/tests/src/Unit/FlysystemServiceProviderTest.php b/tests/src/Unit/FlysystemServiceProviderTest.php deleted file mode 100644 index 7312582b9c57baff2f3b4bf706e5e3d44dc884d2..0000000000000000000000000000000000000000 --- a/tests/src/Unit/FlysystemServiceProviderTest.php +++ /dev/null @@ -1,144 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Unit; - -use Drupal\Core\Asset\AssetDumper; -use Drupal\Core\Asset\CssCollectionOptimizer; -use Drupal\Core\Asset\CssOptimizer; -use Drupal\Core\Asset\JsCollectionOptimizer; -use Drupal\Core\DependencyInjection\ContainerBuilder; -use Drupal\Core\Site\Settings; -use Drupal\flysystem\Asset\AssetDumper as FlysystemAssetDumper; -use Drupal\flysystem\Asset\CssCollectionOptimizer as FlysystemCssCollectionOptimizer; -use Drupal\flysystem\Asset\CssOptimizer as FlysystemCssOptimizer; -use Drupal\flysystem\Asset\JsCollectionOptimizer as FlysystemJsCollectionOptimizer; -use Drupal\flysystem\FlysystemStreamWrapper; -use Drupal\flysystem\FlysystemServiceProvider; -use Drupal\flysystem\PathProcessor\LocalPathProcessor; -use Drupal\Tests\UnitTestCase; - -/** - * @coversDefaultClass \Drupal\flysystem\FlysystemServiceProvider - * @group flysystem - */ -class FlysystemServiceProviderTest extends UnitTestCase { - - /** - * The container. - * - * @var \Symfony\Component\DependencyInjection\ContainerInterface - */ - protected $container; - - /** - * {@inheritdoc} - */ - public function setUp(): void { - parent::setUp(); - $this->container = new ContainerBuilder(); - } - - /** - * @covers ::register - */ - public function testNothingFailsIfContainerIsEmpty() { - new Settings([]); - (new FlysystemServiceProvider())->register($this->container); - $this->assertFalse($this->container->has('flysystem_stream_wrapper')); - } - - /** - * @covers ::register - */ - public function testMissingDriverIsSkipped() { - new Settings(['flysystem' => ['testscheme' => []]]); - - (new FlysystemServiceProvider())->register($this->container); - - $this->assertFalse($this->container->has('flysystem_stream_wrapper.testscheme')); - } - - /** - * @covers ::register - */ - public function testValidSchemeConfiguration() { - new Settings(['flysystem' => ['testscheme' => ['driver' => 'whatever']]]); - - (new FlysystemServiceProvider())->register($this->container); - - $this->assertTrue($this->container->has('flysystem_stream_wrapper.testscheme')); - $this->assertSame(FlysystemStreamWrapper::class, $this->container->getDefinition('flysystem_stream_wrapper.testscheme')->getClass()); - $this->assertSame([['scheme' => 'testscheme']], $this->container->getDefinition('flysystem_stream_wrapper.testscheme')->getTag('stream_wrapper')); - } - - /** - * @covers ::register - */ - public function testLocalRouteProviderGetsAdded() { - new Settings([ - 'flysystem' => [ - 'testscheme' => [ - 'driver' => 'local', - 'config' => ['public' => TRUE], - ], - ], - ]); - - (new FlysystemServiceProvider())->register($this->container); - $this->assertSame(LocalPathProcessor::class, $this->container->getDefinition('flysystem.testscheme.path_processor')->getClass()); - } - - /** - * @covers \Drupal\flysystem\FlysystemServiceProvider - */ - public function test() { - // Test swapping the asset dumper. - $this->container->register('asset.js.dumper', AssetDumper::class); - (new FlysystemServiceProvider())->register($this->container); - $this->assertSame(AssetDumper::class, $this->container->getDefinition('asset.js.dumper')->getClass()); - - $this->container->register('asset.js.collection_optimizer', JsCollectionOptimizer::class); - (new FlysystemServiceProvider())->register($this->container); - $this->assertSame(AssetDumper::class, $this->container->getDefinition('asset.js.dumper')->getClass()); - $this->assertSame(JsCollectionOptimizer::class, $this->container->getDefinition('asset.js.collection_optimizer')->getClass()); - - // A successful swap. - new Settings([ - 'flysystem' => [ - 'testscheme' => [ - 'driver' => 'whatever', - 'serve_js' => TRUE, - ], - ], - ]); - (new FlysystemServiceProvider())->register($this->container); - $this->assertSame(FlysystemAssetDumper::class, $this->container->getDefinition('asset.js.dumper')->getClass()); - $this->assertSame(FlysystemJsCollectionOptimizer::class, $this->container->getDefinition('asset.js.collection_optimizer')->getClass()); - } - - /** - * @covers \Drupal\flysystem\FlysystemServiceProvider - */ - public function testSwappingCssServices() { - // Test swapping the asset dumper. - $this->container->register('asset.css.dumper', AssetDumper::class); - $this->container->register('asset.css.collection_optimizer', CssCollectionOptimizer::class); - $this->container->register('asset.css.optimizer', CssOptimizer::class); - - new Settings([ - 'flysystem' => [ - 'testscheme' => [ - 'driver' => 'whatever', - 'serve_css' => TRUE, - ], - ], - ]); - - (new FlysystemServiceProvider())->register($this->container); - - $this->assertSame(FlysystemAssetDumper::class, $this->container->getDefinition('asset.css.dumper')->getClass()); - $this->assertSame(FlysystemCssCollectionOptimizer::class, $this->container->getDefinition('asset.css.collection_optimizer')->getClass()); - $this->assertSame(FlysystemCssOptimizer::class, $this->container->getDefinition('asset.css.optimizer')->getClass()); - } - -} diff --git a/tests/src/Unit/FlysystemStreamWrapperManagerTest.php b/tests/src/Unit/FlysystemStreamWrapperManagerTest.php deleted file mode 100644 index 9bf18dae9f199bfbec18d93baeff18b28c0048a6..0000000000000000000000000000000000000000 --- a/tests/src/Unit/FlysystemStreamWrapperManagerTest.php +++ /dev/null @@ -1,215 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Unit; - -use Drupal\Component\Plugin\PluginManagerInterface; -use Drupal\Core\Cache\NullBackend; -use Drupal\Core\Site\Settings; -use Drupal\Core\StreamWrapper\StreamWrapperManager; -use Drupal\flysystem\Flysystem\Adapter\DrupalCacheAdapter; -use Drupal\flysystem\Flysystem\Adapter\MissingAdapter; -use Drupal\flysystem\Flysystem\Missing; -use Drupal\flysystem\FlysystemStreamWrapperManager; -use Drupal\flysystem\Plugin\FlysystemPluginInterface; -use Drupal\Tests\UnitTestCase; -use League\Flysystem\Adapter\NullAdapter; -use League\Flysystem\FilesystemInterface; -use League\Flysystem\Replicate\ReplicateAdapter; -use Prophecy\Argument; -use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; - -/** - * @coversDefaultClass \Drupal\flysystem\FlysystemStreamWrapperManager - * @group flysystem - */ -class FlysystemStreamWrapperManagerTest extends UnitTestCase { - - /** - * Backend Cache. - * - * @var \Drupal\Core\Cache\CacheBackendInterface - */ - protected $cache; - - /** - * Event Dispatcher. - * - * @var \Symfony\Contracts\EventDispatcher\EventDispatcherInterface - */ - protected $eventDispatcher; - - /** - * Mocked File System. - * - * @var \Prophecy\Prophecy\ObjectProphecy - */ - protected $filesystem; - - /** - * Mocked Plugin. - * - * @var \Prophecy\Prophecy\ObjectProphecy - */ - protected $plugin; - - /** - * Mocked Plugin Manager. - * - * @var \Prophecy\Prophecy\ObjectProphecy - */ - protected $pluginManager; - - /** - * Mocked Settings manager. - */ - protected $settings; - - /** - * {@inheritdoc} - */ - public function setUp(): void { - parent::setUp(); - - $this->cache = new NullBackend('bin'); - $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); - - $this->pluginManager = $this->prophesize(PluginManagerInterface::class); - $this->plugin = $this->prophesize(FlysystemPluginInterface::class); - $this->plugin->getAdapter()->willReturn(new NullAdapter()); - - $this->pluginManager->createInstance('testdriver', [])->willReturn($this->plugin->reveal()); - $this->pluginManager->createInstance('', [])->willReturn(new Missing()); - - $this->filesystem = $this->prophesize(StreamWrapperManager::class); - $this->filesystem->isValidScheme(Argument::type('string'))->willReturn(TRUE); - - $this->settings = $this->prophesize(Settings::class); - } - - /** - * @covers ::getFilesystem - * @covers ::__construct - * @covers ::getAdapter - * @covers ::getSettings - * @covers ::getPlugin - */ - public function testGetFilesystemReturnsValidFilesystem() { - new Settings([ - 'flysystem' => [ - 'testscheme' => ['driver' => 'testdriver'], - ], - ]); - - $factory = $this->getFactory(); - - $this->assertInstanceOf(FilesystemInterface::class, $factory->getFilesystem('testscheme')); - $this->assertInstanceOf(NullAdapter::class, $factory->getFilesystem('testscheme')->getAdapter()); - } - - /** - * @covers ::getFilesystem - */ - public function testGetFilesystemReturnsMissingFilesystem() { - new Settings([]); - $factory = $this->getFactory(); - $this->assertInstanceOf(MissingAdapter::class, $factory->getFilesystem('testscheme')->getAdapter()); - } - - /** - * @covers ::getFilesystem - * @covers ::getAdapter - */ - public function testGetFilesystemReturnsCachedAdapter() { - new Settings([ - 'flysystem' => [ - 'testscheme' => ['driver' => 'testdriver' , 'cache' => TRUE], - ], - ]); - - $factory = $this->getFactory(); - $this->assertInstanceOf(DrupalCacheAdapter::class, $factory->getFilesystem('testscheme')->getAdapter()); - } - - /** - * @covers ::getFilesystem - * @covers ::getAdapter - */ - public function testGetFilesystemReturnsReplicateAdapter() { - // Test replicate. - $this->pluginManager->createInstance('wrapped', [])->willReturn($this->plugin->reveal()); - - new Settings([ - 'flysystem' => [ - 'testscheme' => ['driver' => 'testdriver' , 'replicate' => 'wrapped'], - 'wrapped' => ['driver' => 'testdriver'], - ], - ]); - - $factory = $this->getFactory(); - $this->assertInstanceOf(ReplicateAdapter::class, $factory->getFilesystem('testscheme')->getAdapter()); - } - - /** - * @covers ::getSchemes - * @covers ::__construct - */ - public function testGetSchemesFiltersInvalidSchemes() { - new Settings([ - 'flysystem' => [ - 'testscheme' => ['driver' => 'testdriver'], - 'invalidscheme' => ['driver' => 'testdriver'], - ], - ]); - - $this->filesystem->isValidScheme('invalidscheme')->willReturn(FALSE); - - $this->assertSame(['testscheme'], $this->getFactory()->getSchemes()); - } - - /** - * @covers ::getSchemes - */ - public function testGetSchemesHandlesNoSchemes() { - new Settings([]); - $this->assertSame([], $this->getFactory()->getSchemes()); - } - - /** - * @covers ::ensure - */ - public function testEnsureReturnsErrors() { - new Settings([ - 'flysystem' => [ - 'testscheme' => ['driver' => 'testdriver'], - ], - ]); - - $this->plugin->ensure(FALSE)->willReturn([[ - 'severity' => 'bad', - 'message' => 'Something bad', - 'context' => [], - ], - ]); - - $errors = $this->getFactory()->ensure(); - - $this->assertSame('Something bad', $errors['testscheme'][0]['message']); - } - - /** - * Gets and returns the Flysystem Factory. - * - * @return \Drupal\flysystem\FlysystemStreamWrapperManager - * Flysystem Factory. - */ - protected function getFactory() { - return new FlysystemStreamWrapperManager( - $this->pluginManager->reveal(), - $this->filesystem->reveal(), - $this->cache, - $this->eventDispatcher, - $this->settings - ); - } - -} diff --git a/tests/src/Unit/FlysystemStreamWrapperTest.php b/tests/src/Unit/FlysystemStreamWrapperTest.php deleted file mode 100644 index de3f0196a27168af5ccaf6003faea287003a5291..0000000000000000000000000000000000000000 --- a/tests/src/Unit/FlysystemStreamWrapperTest.php +++ /dev/null @@ -1,123 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Unit; - -use Drupal\Core\StreamWrapper\StreamWrapperInterface; -use Drupal\flysystem\Flysystem\Adapter\MissingAdapter; -use Drupal\flysystem\Flysystem\Missing; -use Drupal\flysystem\FlysystemStreamWrapper; -use Drupal\flysystem\FlysystemStreamWrapperManager; -use Drupal\Tests\UnitTestCase; -use League\Flysystem\Filesystem; -use Symfony\Component\DependencyInjection\ContainerBuilder; - -/** - * @coversDefaultClass \Drupal\flysystem\FlysystemStreamWrapper - * @group flysystem - */ -class FlysystemStreamWrapperTest extends UnitTestCase { - - /** - * Flysystem Bridge. - * - * @var \Drupal\flysystem\FlysystemStreamWrapper - */ - protected $bridge; - - /** - * File system. - * - * @var \League\Flysystem\FilesystemInterface - */ - protected $filesystem; - - /** - * {@inheritdoc} - */ - public function setUp(): void { - parent::setUp(); - $this->bridge = new FlysystemStreamWrapper(); - $this->bridge->setStringTranslation($this->getStringTranslationStub()); - $this->bridge->setUri('testscheme://file.txt'); - - $factory = $this->prophesize(FlysystemStreamWrapperManager::class); - $factory->getPlugin('testscheme')->willReturn(new Missing()); - - $this->filesystem = new Filesystem(new MissingAdapter()); - - $factory->getFilesystem('testscheme')->willReturn($this->filesystem); - - $factory->getSettings('testscheme')->willReturn([ - 'name' => '', - 'description' => '', - ]); - - $container = new ContainerBuilder(); - $container->set('flysystem_factory', $factory->reveal()); - \Drupal::setContainer($container); - } - - /** - * @covers ::getType - */ - public function testGetTypeReturnsWriteVisible() { - $this->assertSame(StreamWrapperInterface::WRITE_VISIBLE, FlysystemStreamWrapper::getType()); - } - - /** - * @covers ::getName - */ - public function testGetNameFormattingCorrect() { - $this->assertSame('Flysystem: testscheme', (string) $this->bridge->getName()); - } - - /** - * @covers ::getDescription - */ - public function testGetDescriptionFormattingCorrect() { - $this->assertSame('Flysystem: testscheme', (string) $this->bridge->getDescription()); - } - - /** - * @covers ::getUri - * @covers ::setUri - */ - public function testGetUriMatchesSetUri() { - $this->bridge->setUri('beep://boop'); - $this->assertSame('beep://boop', $this->bridge->getUri()); - } - - /** - * @covers ::getExternalUrl - * @covers ::getFactory - */ - public function testGetExternalUrlDelegatesToPlugin() { - $this->assertSame('', $this->bridge->getExternalUrl('testscheme://testfile.txt')); - } - - /** - * @covers ::realpath - */ - public function testRealpathIsFalse() { - $this->assertFalse($this->bridge->realpath()); - } - - /** - * @covers ::dirname - */ - public function testDirname() { - $this->assertSame('testscheme://', $this->bridge->dirname()); - $this->assertSame('testscheme://dir://dir', $this->bridge->dirname('testscheme:///dir://dir/file.txt')); - } - - /** - * @covers ::getFilesystem - * @covers ::getFilesystemForScheme - */ - public function testGetFilesystemOverridden() { - $method = new \ReflectionMethod($this->bridge, 'getFilesystem'); - $method->setAccessible(TRUE); - $this->assertSame($this->filesystem, $method->invoke($this->bridge)); - } - -} diff --git a/tests/src/Unit/Form/ConfigFormTest.php b/tests/src/Unit/Form/ConfigFormTest.php deleted file mode 100644 index db43cadc0afd877ccbe4085847c591de7a584bcf..0000000000000000000000000000000000000000 --- a/tests/src/Unit/Form/ConfigFormTest.php +++ /dev/null @@ -1,300 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Unit\Form { - - use Drupal\Core\Form\FormState; - use Drupal\Core\Logger\LoggerChannelFactoryInterface; - use Drupal\Core\Messenger\MessengerInterface; - use Drupal\flysystem\FlysystemStreamWrapperManager; - use Drupal\flysystem\Form\ConfigForm; - use Drupal\Tests\UnitTestCase; - use League\Flysystem\Filesystem; - use League\Flysystem\FilesystemInterface; - use League\Flysystem\Memory\MemoryAdapter; - use Prophecy\Argument; - use Psr\Log\LoggerInterface; - use Symfony\Component\DependencyInjection\ContainerBuilder; - use function Drupal\flysystem\Form\batch_set; - - /** - * @coversDefaultClass \Drupal\flysystem\Form\ConfigForm - * @group flysystem - */ - class ConfigFormTest extends UnitTestCase { - - /** - * The Flysystem factory prophecy. - * - * @var \Prophecy\Prophecy\ObjectProphecy - */ - protected $factory; - - /** - * The form object. - * - * @var \Drupal\flysystem\Form\ConfigForm - */ - protected $form; - - /** - * {@inheritdoc} - */ - public function setUp(): void { - parent::setUp(); - - $this->factory = $this->prophesize(FlysystemStreamWrapperManager::class); - $this->factory->getFilesystem('from_empty')->willReturn(new Filesystem(new MemoryAdapter())); - $this->factory->getFilesystem('to_empty')->willReturn(new Filesystem(new MemoryAdapter())); - $this->factory->getSchemes()->willReturn(['from_empty', 'to_empty']); - - $this->form = new ConfigForm($this->factory->reveal()); - $this->form->setStringTranslation($this->getStringTranslationStub()); - $messenger = $this->prophesize(MessengerInterface::class); - - $container = new ContainerBuilder(); - $container->set('string_translation', $this->getStringTranslationStub()); - $container->set('flysystem_factory', $this->factory->reveal()); - $container->set('messenger', $messenger->reveal()); - - $logger = $this->prophesize(LoggerChannelFactoryInterface::class); - $logger->get('flysystem')->willReturn($this->prophesize(LoggerInterface::class)->reveal()); - $container->set('logger.factory', $logger->reveal()); - - \Drupal::setContainer($container); - } - - /** - * @covers ::create - * @covers ::__construct - */ - public function testCreate() { - $container = new ContainerBuilder(); - $container->set('flysystem_factory', $this->factory->reveal()); - - $this->assertInstanceOf(ConfigForm::class, ConfigForm::create($container)); - } - - /** - * @covers ::getFormId - */ - public function testGetFormId() { - $this->assertSame('flysystem_config_form', $this->form->getFormId()); - } - - /** - * @covers ::buildForm - */ - public function testBuildForm() { - $form = $this->form->buildForm([], new FormState()); - $this->assertCount(4, $form); - - $this->assertTrue($form['sync_from']['#required']); - $this->assertTrue($form['sync_to']['#required']); - } - - /** - * @covers ::validateForm - */ - public function testValidateForm() { - $form_state = new FormState(); - $form = $this->form->buildForm([], $form_state); - $form['sync_from']['#parents'] = ['sync_from']; - $form['sync_to']['#parents'] = ['sync_to']; - - $form_state->setValue('sync_from', 'from'); - $form_state->setValue('sync_to', 'to'); - - $this->form->validateForm($form, $form_state); - $this->assertCount(0, $form_state->getErrors()); - $form_state->setValue('sync_to', 'from'); - - $this->form->validateForm($form, $form_state); - $this->assertCount(2, $form_state->getErrors()); - } - - /** - * @covers ::submitForm - * @covers ::getFileList - */ - public function testSubmitForm() { - $form_state = new FormState(); - $form = []; - $form_state->setValue('sync_from', 'from_empty'); - $form_state->setValue('sync_to', 'to_empty'); - - $this->form->submitForm($form, $form_state); - $batch = batch_set(); - - $this->assertSame(ConfigForm::class . '::finishBatch', $batch['finished']); - $this->assertCount(0, $batch['operations']); - - // Test with existing source files. - $from = new Filesystem(new MemoryAdapter()); - $from->write('dir/test.txt', 'abcdefg'); - $from->write('test.txt', 'abcdefg'); - $this->factory->getFilesystem('from_files')->willReturn($from); - - $form_state->setValue('sync_from', 'from_files'); - - $this->form->submitForm($form, $form_state); - - $batch_files = array_map(function (array $operation) { - return $operation[1][2]; - }, batch_set()['operations']); - - $this->assertSame(['dir/test.txt', 'test.txt'], $batch_files); - - // Test with existing destination files, and force true. - $form_state->setValue('force', TRUE); - $form_state->setValue('sync_to', 'from_files'); - - $this->form->submitForm($form, $form_state); - - $batch_files = array_map(function (array $operation) { - return $operation[1][2]; - }, batch_set()['operations']); - - $this->assertSame(['dir/test.txt', 'test.txt'], $batch_files); - } - - /** - * @covers ::copyFile - */ - public function testCopyFile() { - $context = []; - - $from = new Filesystem(new MemoryAdapter()); - $from->write('dir/test.txt', 'abcdefg'); - $this->factory->getFilesystem('from_files')->willReturn($from); - - ConfigForm::copyFile('from_files', 'to_empty', 'dir/test.txt', $context); - - $this->assertSame('abcdefg', $this->factory->reveal()->getFilesystem('to_empty')->read('dir/test.txt')); - $this->assertTrue(empty($context['results'])); - $this->assertSame(1, $context['finished']); - } - - /** - * @covers ::copyFile - */ - public function testCopyFileFailedRead() { - // Tests failed read. - $context = []; - $failed_read = $this->prophesize(FilesystemInterface::class); - $failed_read->readStream('does_not_exist')->willReturn(FALSE); - $this->factory->getFilesystem('failed_read')->willReturn($failed_read->reveal()); - - ConfigForm::copyFile('failed_read', 'to_empty', 'does_not_exist', $context); - - $to_files = $this->factory->reveal()->getFilesystem('to_empty')->listContents('', TRUE); - $this->assertCount(0, $to_files); - $this->assertCount(1, $context['results']['errors']); - } - - /** - * @covers ::copyFile - */ - public function testCopyFileFailedWrite() { - $context = []; - - $from = new Filesystem(new MemoryAdapter()); - $from->write('test.txt', 'abcdefg'); - $this->factory->getFilesystem('from_files')->willReturn($from); - - $failed_write = $this->prophesize(FilesystemInterface::class); - $failed_write->putStream(Argument::cetera())->willReturn(FALSE); - $this->factory->getFilesystem('to_fail')->willReturn($failed_write); - - ConfigForm::copyFile('from_files', 'to_fail', 'test.txt', $context); - - $this->assertCount(1, $context['results']['errors']); - $this->assertTrue(strpos($context['results']['errors'][0][0], 'could not be saved') !== FALSE); - } - - /** - * @covers ::copyFile - */ - public function testCopyFileException() { - $context = []; - ConfigForm::copyFile('from_empty', 'to_empty', 'does_not_exist.txt', $context); - $this->assertCount(2, $context['results']['errors']); - $this->assertTrue(strpos($context['results']['errors'][0][0], 'An eror occured while copying') !== FALSE); - $this->assertTrue(strpos($context['results']['errors'][1], 'File not found at path') !== FALSE); - } - - /** - * @covers ::finishBatch - */ - public function testFinishBatch() { - ConfigForm::finishBatch(TRUE, [], []); - ConfigForm::finishBatch(FALSE, [], ['from', 'to', 'file.txt']); - ConfigForm::finishBatch(TRUE, [ - 'errors' => [ - 'first error', - [ - 'second error', [''], - ], - ], - ], []); - // @todo refactor. - $this->assertTrue(TRUE); - } - - /** - * Converts a file list fron Flysystem into a list of files. - * - * @param array $list - * The file list from Flysystem::listContents(). - * - * @return string[] - * A list of file paths. - */ - protected function getFileList(array $list) { - $list = array_filter($list, function (array $file) { - return $file['type'] === 'file'; - }); - - return array_map(function (array $file) { - return $file['path']; - }, $list); - } - - } -} - -namespace Drupal\flysystem\Form { - - /** - * Override for drupal_set_message(). - */ - function drupal_set_message() { - } - - /** - * Mock batch_set() for testing. - */ - function batch_set($batch = NULL) { - static $last_batch; - - if (isset($batch)) { - $last_batch = $batch; - } - return $last_batch; - } - - /** - * Override for drupal_set_time_limit(). - */ - function drupal_set_time_limit($limit) { - if ($limit !== 0) { - throw new \Exception(); - } - } - - /** - * Override for Watchdog exception(). - */ - function watchdog_exception() { - } - -} diff --git a/tests/src/Unit/InstallFunctionsTest.php b/tests/src/Unit/InstallFunctionsTest.php deleted file mode 100644 index a7c26f31d16ab3f599cbcf7b98f10214f3a1fdab..0000000000000000000000000000000000000000 --- a/tests/src/Unit/InstallFunctionsTest.php +++ /dev/null @@ -1,107 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Unit; - -use Drupal\Core\Logger\RfcLogLevel; -use Drupal\Core\Site\Settings; -use Drupal\flysystem\FlysystemStreamWrapperManager; -use Drupal\Tests\UnitTestCase; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Twistor\FlysystemStreamWrapper; - -/** - * Tests flysystem.install functions. - * - * @group flysystem - */ -class InstallFunctionsTest extends UnitTestCase { - - /** - * The Flysystem factory prophecy object. - * - * @var \Prophecy\Prophecy\ObjectProphecy - */ - protected $factory; - - /** - * {@inheritdoc} - */ - public function setUp(): void { - parent::setUp(); - - if (!defined('REQUIREMENT_ERROR')) { - define('REQUIREMENT_ERROR', 2); - } - - require_once dirname(dirname(dirname(__DIR__))) . '/flysystem.install'; - - $this->factory = $this->prophesize(FlysystemStreamWrapperManager::class); - - $container = new ContainerBuilder(); - $container->set('flysystem_factory', $this->factory->reveal()); - $container->set('string_translation', $this->getStringTranslationStub()); - - \Drupal::setContainer($container); - } - - /** - * Tests flysystem_requirements() handles update. - */ - public function testFlysystemRequirementsHandlesUpdate() { - $dependencies_exist = (int) class_exists(FlysystemStreamWrapper::class); - - $return = flysystem_requirements('update'); - $this->assertCount((1 - $dependencies_exist), $return); - } - - /** - * Tests flysystem_requirements() handles install. - */ - public function testFlysystemRequirementsHandlesInstall() { - $dependencies_exist = (int) class_exists(FlysystemStreamWrapper::class); - - $return = flysystem_requirements('install'); - $this->assertCount((1 - $dependencies_exist), $return); - } - - /** - * Tests flysystem_requirements() handles runtime. - */ - public function testFlysystemRequirementsHandlesRuntime() { - $dependencies_exist = (int) class_exists(FlysystemStreamWrapper::class); - - $this->factory->ensure()->willReturn([ - 'testscheme' => [ - [ - 'message' => 'Test message', - 'context' => [], - 'severity' => RfcLogLevel::ERROR, - ], - ], - ]); - - $return = flysystem_requirements('runtime'); - - $this->assertCount((2 - $dependencies_exist), $return); - $this->assertSame('Test message', (string) $return['flysystem:testscheme']['description']); - } - - /** - * Tests flysystem_requirements() detects invalid schemes. - */ - public function testFlysystemRequirementsHandlesInvalidSchemes() { - new Settings(['flysystem' => ['test_scheme' => []]]); - $requirements = flysystem_requirements('update'); - - $this->assertTrue(isset($requirements['flysystem_invalid_scheme'])); - } - - /** - * Tests flysystem_install() calls ensure(). - */ - public function testFlysystemInstallCallsEnsure() { - $this->factory->ensure()->shouldBeCalled(); - flysystem_install(); - } - -} diff --git a/tests/src/Unit/Logger/ConvertTest.php b/tests/src/Unit/Logger/ConvertTest.php deleted file mode 100644 index c8858796073a9fbb29432627187987070962de46..0000000000000000000000000000000000000000 --- a/tests/src/Unit/Logger/ConvertTest.php +++ /dev/null @@ -1,46 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Unit\Logger; - -use Drupal\Core\Logger\RfcLogLevel; -use Drupal\flysystem\Logger\Convert; -use Drupal\Tests\UnitTestCase; - -/** - * @coversDefaultClass \Drupal\flysystem\Logger\Convert - * @group flysystem - */ -class ConvertTest extends UnitTestCase { - - /** - * {@inheritdoc} - */ - public function setUp(): void { - parent::setUp(); - $consts = [ - 'REQUIREMENT_INFO' => -1, - 'REQUIREMENT_OK' => 0, - 'REQUIREMENT_WARNING' => 1, - 'REQUIREMENT_ERROR' => 2, - ]; - - foreach ($consts as $const => $value) { - if (!defined($const)) { - define($const, $value); - } - } - } - - /** - * @covers ::rfcToHookRequirements - */ - public function test() { - $this->assertSame(REQUIREMENT_ERROR, Convert::rfcToHookRequirements(RfcLogLevel::EMERGENCY)); - $this->assertSame(REQUIREMENT_ERROR, Convert::rfcToHookRequirements(RfcLogLevel::ERROR)); - $this->assertSame(REQUIREMENT_WARNING, Convert::rfcToHookRequirements(RfcLogLevel::WARNING)); - $this->assertSame(REQUIREMENT_INFO, Convert::rfcToHookRequirements(RfcLogLevel::NOTICE)); - $this->assertSame(REQUIREMENT_INFO, Convert::rfcToHookRequirements(RfcLogLevel::INFO)); - $this->assertSame(REQUIREMENT_OK, Convert::rfcToHookRequirements(RfcLogLevel::DEBUG)); - } - -} diff --git a/tests/src/Unit/ModuleFunctionsTest.php b/tests/src/Unit/ModuleFunctionsTest.php deleted file mode 100644 index 6d461bc24415438cb0ea861660cfb02ed6922d73..0000000000000000000000000000000000000000 --- a/tests/src/Unit/ModuleFunctionsTest.php +++ /dev/null @@ -1,92 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Unit; - -use Drupal\Core\File\MimeType\ExtensionMimeTypeGuesser; -use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface; -use Drupal\flysystem\FlysystemStreamWrapperManager; -use Drupal\Tests\UnitTestCase; -use org\bovigo\vfs\vfsStream; -use Prophecy\Argument; -use Symfony\Component\DependencyInjection\ContainerBuilder; - -/** - * Tests module functions. - * - * @group flysystem - */ -class ModuleFunctionsTest extends UnitTestCase { - - /** - * The Flysystem factory prophecy object. - * - * @var \Prophecy\Prophecy\ObjectProphecy - */ - protected $factory; - - /** - * {@inheritdoc} - */ - public function setUp(): void { - parent::setUp(); - vfsStream::setup('module_file'); - - require_once dirname(dirname(dirname(__DIR__))) . '/flysystem.module'; - - $this->factory = $this->prophesize(FlysystemStreamWrapperManager::class); - $this->factory->getSchemes()->willReturn(['vfs']); - - $file_system_helper = $this->prophesize(StreamWrapperManagerInterface::class); - $file_system_helper->isValidScheme(Argument::type('string'))->will(function ($uri) { - [$scheme] = explode('://', $uri[0]); - return $scheme; - }); - - $guesser = $this->prophesize(ExtensionMimeTypeGuesser::class); - $guesser->guessMimeType(Argument::type('string'))->willReturn('txt/flysystem'); - - $container = new ContainerBuilder(); - $container->set('file_system', $file_system_helper->reveal()); - $container->set('flysystem_factory', $this->factory->reveal()); - $container->set('file.mime_type.guesser.extension', $guesser->reveal()); - - \Drupal::setContainer($container); - } - - /** - * Tests flysystem_cron() calls ensure. - */ - public function testFlysystemCronCallsEnsure() { - $this->factory->ensure()->shouldBeCalled(); - flysystem_cron(); - } - - /** - * Tests flysystem_rebuild() calls ensure. - */ - public function testFlysystemRebuildCallsEnsure() { - $this->factory->ensure()->shouldBeCalled(); - flysystem_rebuild(); - } - - /** - * Tests flysystem_file_download() handles valid schemes. - */ - public function testFlysystemFileDownloadFindsValidScheme() { - $success = file_put_contents('vfs://module_file/file.txt', '1234'); - $this->assertEquals($success, 4); - $return = flysystem_file_download('vfs://module_file/file.txt'); - - $this->assertEquals(2, count($return)); - $this->assertSame('txt/flysystem', $return['Content-Type']); - $this->assertEquals(4, $return['Content-Length']); - } - - /** - * Tests flysystem_file_download() ignores invalid schemes. - */ - public function testFlysystemFileDownloadIgnoresInvalidScheme() { - $this->assertNull(flysystem_file_download('invalid://module_file/file.txt')); - } - -} diff --git a/tests/src/Unit/PathProcessor/FlysystemPathProcessorTest.php b/tests/src/Unit/PathProcessor/FlysystemPathProcessorTest.php deleted file mode 100644 index 6786151b9f2303eb1858dee7414cad869b66a87e..0000000000000000000000000000000000000000 --- a/tests/src/Unit/PathProcessor/FlysystemPathProcessorTest.php +++ /dev/null @@ -1,56 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Unit\PathProcessor; - -use Drupal\flysystem\PathProcessor\FlysystemPathProcessor; -use Drupal\Tests\UnitTestCase; -use Symfony\Component\HttpFoundation\Request; - -/** - * @coversDefaultClass \Drupal\flysystem\PathProcessor\FlysystemPathProcessor - * @group flysystem - */ -class FlysystemPathProcessorTest extends UnitTestCase { - - /** - * @covers ::processInbound - */ - public function testCorrectPathsAreProccessed() { - $processor = new FlysystemPathProcessor(); - $this->assertSame('beep', $processor->processInbound('beep', new Request())); - $this->assertSame('/_flysystem/scheme', $processor->processInbound('/_flysystem/scheme', new Request())); - } - - /** - * @covers ::processInbound - */ - public function testImageStylesAreProccessed() { - $request = new Request(); - $processor = new FlysystemPathProcessor(); - $this->assertSame('/_flysystem/styles/scheme/small', $processor->processInbound('/_flysystem/scheme/styles/scheme/small/image.jpg', $request)); - $this->assertSame($request->query->get('file'), 'image.jpg'); - $this->assertSame('/_flysystem/styles/scheme/small', $processor->processInbound('/_flysystem/scheme/styles/scheme/small/dir/image.jpg', $request)); - $this->assertSame($request->query->get('file'), 'dir/image.jpg'); - } - - /** - * @covers ::processInbound - */ - public function testDownloadPathsAreProccessed() { - $request = new Request(); - $processor = new FlysystemPathProcessor(); - $this->assertSame('/_flysystem/scheme', $processor->processInbound('/_flysystem/scheme/file.txt', $request)); - $this->assertSame('file.txt', $request->query->get('file')); - } - - /** - * @covers ::processInbound - */ - public function testDownloadPathsInSubDirsAreProccessed() { - $request = new Request(); - $processor = new FlysystemPathProcessor(); - $this->assertSame('/_flysystem/scheme', $processor->processInbound('/_flysystem/scheme/a/b/c/file.txt', $request)); - $this->assertSame('a/b/c/file.txt', $request->query->get('file')); - } - -} diff --git a/tests/src/Unit/PathProcessor/LocalPathProcessorTest.php b/tests/src/Unit/PathProcessor/LocalPathProcessorTest.php deleted file mode 100644 index 9cd9e1e48c63f59c25626bd24434a3235014b9ff..0000000000000000000000000000000000000000 --- a/tests/src/Unit/PathProcessor/LocalPathProcessorTest.php +++ /dev/null @@ -1,74 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Unit\PathProcessor; - -use Drupal\Core\Site\Settings; -use Drupal\flysystem\PathProcessor\LocalPathProcessor; -use Drupal\Tests\UnitTestCase; -use Symfony\Component\HttpFoundation\Request; - -/** - * @coversDefaultClass \Drupal\flysystem\PathProcessor\LocalPathProcessor - * @group flysystem - */ -class LocalPathProcessorTest extends UnitTestCase { - - /** - * Inbound Path processor. - * - * @var \Drupal\Core\PathProcessor\InboundPathProcessorInterface - */ - protected $processor; - - /** - * {@inheritdoc} - */ - public function setUp(): void { - parent::setUp(); - new Settings( - [ - 'flysystem' => [ - 'testscheme' => [ - 'driver' => 'local', - 'config' => ['root' => 'sites/default/files/flysystem'], - ], - ], - ]); - $this->processor = new LocalPathProcessor('testscheme'); - } - - /** - * @covers ::processInbound - * @covers ::__construct - */ - public function testProcessInboundIgnoresInvalidPaths() { - $this->assertSame('beep', $this->processor->processInbound('beep', new Request())); - } - - /** - * @covers ::processInbound - */ - public function testProcessInboundHandlesImageStyles() { - $request = new Request(); - - $this->assertSame('/sites/default/files/flysystem/styles/testscheme/small', $this->processor->processInbound('/sites/default/files/flysystem/styles/testscheme/small/image.jpg', $request)); - $this->assertSame($request->query->get('file'), 'image.jpg'); - - $this->assertSame('/sites/default/files/flysystem/styles/testscheme/small', $this->processor->processInbound('/sites/default/files/flysystem/styles/testscheme/small/dir/image.jpg', $request)); - $this->assertSame($request->query->get('file'), 'dir/image.jpg'); - } - - /** - * @covers ::processInbound - */ - public function testProcessInboundHandlesSystemDownload() { - $request = new Request(); - - $this->assertSame('/sites/default/files/flysystem', $this->processor->processInbound('/sites/default/files/flysystem/file.txt', $request)); - $this->assertSame('file.txt', $request->query->get('file')); - - $this->assertSame('/sites/default/files/flysystem', $this->processor->processInbound('/sites/default/files/flysystem/a/b/c/file.txt', $request)); - $this->assertSame('a/b/c/file.txt', $request->query->get('file')); - } - -} diff --git a/tests/src/Unit/Plugin/FlysystemPluginManagerTest.php b/tests/src/Unit/Plugin/FlysystemPluginManagerTest.php deleted file mode 100644 index ef9f6834b7f55fbcae870ccfcf9f381ae03f29c6..0000000000000000000000000000000000000000 --- a/tests/src/Unit/Plugin/FlysystemPluginManagerTest.php +++ /dev/null @@ -1,45 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Unit\Plugin; - -use Drupal\Core\Cache\MemoryBackend; -use Drupal\Core\Extension\ModuleHandlerInterface; -use Drupal\flysystem\Plugin\FlysystemPluginManager; -use Drupal\Tests\UnitTestCase; - -/** - * @coversDefaultClass \Drupal\flysystem\Plugin\FlysystemPluginManager - * @group flysystem - */ -class FlysystemPluginManagerTest extends UnitTestCase { - - /** - * @covers \Drupal\flysystem\Plugin\FlysystemPluginManager - */ - public function test() { - $namespaces = new \ArrayObject(); - $cache_backend = new MemoryBackend('bin'); - $module_handle = $this->createMock(ModuleHandlerInterface::class); - - $manager = new FlysystemPluginManager($namespaces, $cache_backend, $module_handle); - $this->assertSame('missing', $manager->getFallbackPluginId('beep')); - $this->assertIsArray($manager->getDefinitions()); - - // Test alterDefinitions(). - $method = new \ReflectionMethod($manager, 'alterDefinitions'); - $method->setAccessible(TRUE); - - $definitions = [ - 'test1' => ['extensions' => []], - 'test2' => ['extensions' => ['pdo']], - 'test3' => ['extensions' => ['missing_extension']], - ]; - - $method->invokeArgs($manager, [&$definitions]); - $this->assertCount(2, $definitions); - $this->assertArrayHasKey('test1', $definitions); - $this->assertArrayHasKey('test2', $definitions); - $this->assertArrayNotHasKey('test3', $definitions); - } - -} diff --git a/tests/src/Unit/Plugin/FlysystemUrlTraitTest.php b/tests/src/Unit/Plugin/FlysystemUrlTraitTest.php deleted file mode 100644 index 1155d85d3b1c8f955ea148496b746418a7a5fd06..0000000000000000000000000000000000000000 --- a/tests/src/Unit/Plugin/FlysystemUrlTraitTest.php +++ /dev/null @@ -1,40 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Unit\Plugin; - -use Drupal\Core\DependencyInjection\ContainerBuilder; -use Drupal\Core\Routing\UrlGenerator; -use Drupal\flysystem\Plugin\FlysystemUrlTrait; -use Drupal\Tests\UnitTestCase; - -/** - * @coversDefaultClass \Drupal\flysystem\Plugin\FlysystemUrlTrait - * @group flysystem - */ -class FlysystemUrlTraitTest extends UnitTestCase { - - /** - * @covers ::getExternalUrl - * @covers ::getScheme - * @covers ::getTarget - */ - public function testGetExternalUrl() { - $trait = $this->getMockForTrait(FlysystemUrlTrait::class); - - $url_generator = $this->prophesize(UrlGenerator::class); - $url_generator->generateFromRoute( - 'flysystem.serve', - ['scheme' => 'testscheme', 'filepath' => 'dir/file.txt'], - ['absolute' => TRUE], - FALSE) - ->willReturn('download'); - - $container = new ContainerBuilder(); - $container->set('url_generator', $url_generator->reveal()); - - \Drupal::setContainer($container); - - $this->assertSame('download', $trait->getExternalUrl('testscheme://dir\file.txt')); - } - -} diff --git a/tests/src/Unit/Plugin/ImageStyleGenerationTraitTest.php b/tests/src/Unit/Plugin/ImageStyleGenerationTraitTest.php deleted file mode 100644 index dbe15db408e778a82392d0cb82185af496085b31..0000000000000000000000000000000000000000 --- a/tests/src/Unit/Plugin/ImageStyleGenerationTraitTest.php +++ /dev/null @@ -1,87 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Unit\Plugin; - -use Drupal\Core\DependencyInjection\ContainerBuilder; -use Drupal\Core\Entity\EntityStorageInterface; -use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\Core\Entity\EntityTypeRepositoryInterface; -use Drupal\Core\Lock\LockBackendInterface; -use Drupal\Core\Lock\NullLockBackend; -use Drupal\flysystem\Plugin\ImageStyleGenerationTrait; -use Drupal\image\Entity\ImageStyle; -use Drupal\Tests\UnitTestCase; -use org\bovigo\vfs\vfsStream; -use Prophecy\Argument; - -/** - * @coversDefaultClass \Drupal\flysystem\Plugin\ImageStyleGenerationTrait - * @group flysystem - */ -class ImageStyleGenerationTraitTest extends UnitTestCase { - - /** - * @covers ::generateImageStyle - */ - public function testGenerateImageStyle() { - vfsStream::setup('flysystem'); - touch('vfs://flysystem/foo.jpg'); - mkdir('vfs://flysystem/styles/pass', 0777, TRUE); - - $container = new ContainerBuilder(); - - $image_style = $this->prophesize(ImageStyle::class); - $image_style->buildUri('vfs://flysystem/foo.jpg')->willReturn('vfs://flysystem/styles/pass/foo.jpg'); - $image_style->buildUri('vfs://flysystem/foo.jpg.png')->willReturn('vfs://flysystem/styles/pass/foo.jpg.png'); - $image_style->id()->willReturn('pass'); - $image_style->createDerivative('vfs://flysystem/foo.jpg', 'vfs://flysystem/styles/pass/foo.jpg')->willReturn(TRUE); - $image_style->createDerivative('vfs://flysystem/foo.jpg', 'vfs://flysystem/styles/pass/foo.jpg.png')->willReturn(TRUE); - - $storage = $this->prophesize(EntityStorageInterface::class); - $storage->load('pass')->willReturn($image_style->reveal()); - $storage->load('fail')->willReturn(FALSE); - - $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class); - $entity_tyep_repository = $this->prophesize(EntityTypeRepositoryInterface::class); - $entity_tyep_repository->getEntityTypeFromClass(ImageStyle::class)->willReturn('image_style'); - $entity_type_manager->getStorage('image_style')->willReturn($storage->reveal()); - - $container->set('entity_type.repository', $entity_tyep_repository->reveal()); - $container->set('entity_type.manager', $entity_type_manager->reveal()); - $container->set('lock', new NullLockBackend()); - - \Drupal::setContainer($container); - - $trait = $this->getMockForTrait(ImageStyleGenerationTrait::class); - - $method = (new \ReflectionMethod($trait, 'generateImageStyle'))->getClosure($trait); - - // Test invlid paths. - $this->assertFalse($method('foo/bar')); - $this->assertFalse($method('styles/image_style/vfs')); - - // Test invalid image style. - $this->assertFalse($method('styles/fail/vfs/flysystem/foo.jpg')); - - // Test existing derivative. - touch('vfs://flysystem/styles/pass/foo.jpg'); - $this->assertTrue($method('styles/pass/vfs/flysystem/foo.jpg')); - unlink('vfs://flysystem/styles/pass/foo.jpg'); - - // Basic passing. - $this->assertTrue($method('styles/pass/vfs/flysystem/foo.jpg')); - $this->assertTrue($method('styles/pass/vfs/flysystem/foo.jpg.png')); - - // Test failed lock. - $fail_lock = $this->prophesize(LockBackendInterface::class); - $fail_lock->acquire(Argument::type('string'))->willReturn(FALSE); - $container->set('lock', $fail_lock->reveal()); - $this->assertFalse($method('styles/pass/vfs/flysystem/foo.jpg')); - $container->set('lock', new NullLockBackend()); - - // Test missing source. - unlink('vfs://flysystem/foo.jpg'); - $this->assertFalse($method('styles/pass/vfs/flysystem/foo.jpg.png')); - } - -} diff --git a/tests/src/Unit/Routing/FlysystemRoutesTest.php b/tests/src/Unit/Routing/FlysystemRoutesTest.php deleted file mode 100644 index ad9c6098a5eda230c1b1cf4b044ef0ccbe3b277d..0000000000000000000000000000000000000000 --- a/tests/src/Unit/Routing/FlysystemRoutesTest.php +++ /dev/null @@ -1,165 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Unit\Routing; - -use Drupal\Core\DependencyInjection\ContainerBuilder; -use Drupal\Core\Extension\ModuleHandlerInterface; -use Drupal\Core\Site\Settings; -use Drupal\Core\StreamWrapper\LocalStream; -use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface; -use Drupal\flysystem\FlysystemStreamWrapperManager; -use Drupal\flysystem\Routing\FlysystemRoutes; -use Drupal\Tests\UnitTestCase; - -/** - * @coversDefaultClass \Drupal\flysystem\Routing\FlysystemRoutes - * @group flysystem - */ -class FlysystemRoutesTest extends UnitTestCase { - - /** - * Flysystem Factory. - * - * @var \Drupal\flysystem\FlysystemStreamWrapperManager - */ - protected $factory; - - /** - * Drupal ModuleHandler. - * - * @var \Drupal\Core\Extension\ModuleHandlerInterface - */ - protected $moduleHandler; - - /** - * Flysystem routing for files. - * - * @var \Drupal\flysystem\Routing\FlysystemRoutes - */ - protected $router; - - /** - * {@inheritdoc} - */ - public function setUp(): void { - parent::setUp(); - $container = new ContainerBuilder(); - - $stream_wrapper = $this->prophesize(LocalStream::class); - $stream_wrapper->getDirectoryPath()->willReturn('sites/default/files'); - - $stream_wrapper_manager = $this->prophesize(StreamWrapperManagerInterface::class); - $stream_wrapper_manager->getViaScheme('public')->willReturn($stream_wrapper->reveal()); - - $this->moduleHandler = $this->prophesize(ModuleHandlerInterface::class); - - $factory = $this->prophesize(FlysystemStreamWrapperManager::class); - $factory->getSchemes()->willReturn(['test']); - - $container->set('flysystem_factory', $factory->reveal()); - $container->set('stream_wrapper_manager', $stream_wrapper_manager->reveal()); - $container->set('module_handler', $this->moduleHandler->reveal()); - - $this->router = FlysystemRoutes::create($container); - } - - /** - * @covers ::__construct - * @covers ::create - * @covers ::routes - */ - public function testInvalidSettingsAreSkipped() { - new Settings([ - 'flysystem' => [ - 'invalid' => ['driver' => 'local'], - 'test' => ['driver' => 'local'], - ], - ]); - - $this->assertSame([], $this->router->routes()); - } - - /** - * @covers ::routes - */ - public function testInvalidDriversAreSkipped() { - new Settings(['flysystem' => ['test' => ['driver' => 'ftp']]]); - - $this->assertSame([], $this->router->routes()); - } - - /** - * @covers ::routes - */ - public function testDriversNotPublicAreSkipped() { - new Settings(['flysystem' => ['test' => ['driver' => 'local']]]); - - $this->assertSame([], $this->router->routes()); - } - - /** - * @covers ::routes - */ - public function testLocalPathSameAsPublicIsSkipped() { - new Settings([ - 'flysystem' => [ - 'test' => [ - 'driver' => 'local', - 'public' => TRUE, - 'config' => [ - 'public' => TRUE, - 'root' => 'sites/default/files', - ], - ], - ], - ]); - - $this->assertSame([], $this->router->routes()); - } - - /** - * @covers ::routes - */ - public function testValidRoutesReturned() { - new Settings([ - 'flysystem' => [ - 'test' => [ - 'driver' => 'local', - 'public' => TRUE, - 'config' => [ - 'public' => TRUE, - 'root' => 'sites/default/files/flysystem', - ], - ], - ], - ]); - - $routes = $this->router->routes(); - $this->assertCount(1, $routes); - $this->assertTrue(isset($routes['flysystem.test.serve'])); - } - - /** - * @covers ::routes - */ - public function testValidRoutesReturnedWithImageModule() { - new Settings([ - 'flysystem' => [ - 'test' => [ - 'driver' => 'local', - 'public' => TRUE, - 'config' => [ - 'public' => TRUE, - 'root' => 'sites/default/files/flysystem', - ], - ], - ], - ]); - - $this->moduleHandler->moduleExists('image')->willReturn(TRUE); - $routes = $this->router->routes(); - $this->assertCount(3, $routes); - $this->assertTrue(isset($routes['flysystem.image_style'])); - } - -} diff --git a/tests/src/Unit/SerializationStopperTraitTest.php b/tests/src/Unit/SerializationStopperTraitTest.php deleted file mode 100644 index 71a242a96fa4f7fcfc8c20d4afada93532fe902f..0000000000000000000000000000000000000000 --- a/tests/src/Unit/SerializationStopperTraitTest.php +++ /dev/null @@ -1,24 +0,0 @@ -<?php - -namespace Drupal\Tests\flysystem\Unit; - -use Drupal\flysystem\SerializationStopperTrait; -use Drupal\Tests\UnitTestCase; - -/** - * @coversDefaultClass \Drupal\flysystem\SerializationStopperTrait - * @group flysystem - */ -class SerializationStopperTraitTest extends UnitTestCase { - - /** - * @covers ::__sleep - */ - public function test() { - $this->expectException(\LogicException::class); - $this->expectExceptionMessage('can not be serialized.'); - $trait = $this->getMockForTrait(SerializationStopperTrait::class); - serialize($trait); - } - -}