diff --git a/composer.json b/composer.json index d45704c..7029339 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ "league/flysystem-azure-blob-storage": "^3.24", "league/flysystem-google-cloud-storage": "^3.24", "league/flysystem-webdav": "^3.24", + "league/flysystem-path-prefixing": "^3.24", "aws/aws-sdk-php": "^3.330", "google/cloud-storage": "^1.33", "microsoft/azure-storage-blob": "^1.5", diff --git a/src/Admin/HealthCheck.php b/src/Admin/HealthCheck.php index a8eeb8f..a0503e6 100644 --- a/src/Admin/HealthCheck.php +++ b/src/Admin/HealthCheck.php @@ -4,61 +4,70 @@ declare(strict_types=1); namespace FlysystemOffload\Admin; -use FlysystemOffload\Helpers\PathHelper; -use FlysystemOffload\Plugin; -use League\Flysystem\PortableVisibility; -use Throwable; +use FlysystemOffload\Filesystem\FilesystemFactory; +use League\Flysystem\FilesystemException; +use League\Flysystem\Visibility; +use WP_CLI; +use WP_CLI_Command; +use WP_Error; -final class HealthCheck +class HealthCheck extends WP_CLI_Command { - /** - * @param array $args - * @param array $assocArgs - */ - public static function run(array $args, array $assocArgs): void - { - unset($args, $assocArgs); + private FilesystemFactory $factory; + + public function __construct(FilesystemFactory $factory) + { + $this->factory = $factory; + } + + /** + * Ejecuta un chequeo básico de conectividad e integración. + * + * ## EXAMPLES + * + * wp flysystem-offload health-check + */ + public function __invoke(): void + { + $settings = get_option('flysystem_offload_settings', []); + + if (! is_array($settings)) { + WP_CLI::error('No se encontraron ajustes del plugin.'); - if (! class_exists('\WP_CLI')) { return; } - try { - $plugin = Plugin::instance(); - $filesystem = $plugin->getFilesystem(); + $result = $this->run($settings); - if (! in_array('fly', stream_get_wrappers(), true)) { - \WP_CLI::error('El stream wrapper "fly://" no está registrado.'); - } + if ($result instanceof WP_Error) { + WP_CLI::error($result->get_error_message()); - $report = []; - - $report[] = 'Adapter: ' . get_class($filesystem); - - $uploadDir = wp_get_upload_dir(); - $report[] = 'Base URL remoto: ' . ($uploadDir['baseurl'] ?? '(desconocido)'); - $report[] = 'Directorio remoto: ' . ($uploadDir['basedir'] ?? '(desconocido)'); - - $testKey = trim(PathHelper::stripProtocol(($uploadDir['path'] ?? '') . '/flysystem-offload-health-check-' . uniqid('', true)), '/'); - - $filesystem->write($testKey, 'ok', ['visibility' => PortableVisibility::PUBLIC]); - $content = $filesystem->read($testKey); - - if ($content !== 'ok') { - \WP_CLI::warning('El contenido leído no coincide con lo escrito.'); - } else { - $report[] = 'Lectura/escritura remota verificada.'; - } - - $filesystem->delete($testKey); - - foreach ($report as $line) { - \WP_CLI::line($line); - } - - \WP_CLI::success('Health-check completado correctamente.'); - } catch (Throwable $exception) { - \WP_CLI::error('Health-check falló: ' . $exception->getMessage()); + return; } + + WP_CLI::success('Chequeo completado correctamente.'); + } + + /** + * @param array $settings + */ + public function run(array $settings) + { + try { + $filesystem = $this->factory->build($settings); + } catch (FilesystemException|\Throwable $exception) { + return new WP_Error('flysystem_offload_health_check_failed', $exception->getMessage()); + } + + $testKey = sprintf('health-check/%s.txt', wp_generate_uuid4()); + + try { + $filesystem->write($testKey, 'ok', ['visibility' => Visibility::PUBLIC]); + $filesystem->delete($testKey); + } catch (\Throwable $exception) { + return new WP_Error('flysystem_offload_health_check_failed', $exception->getMessage()); + } + + return true; } } diff --git a/src/Filesystem/Adapters/PrefixedAdapter.php b/src/Filesystem/Adapters/PrefixedAdapter.php new file mode 100644 index 0000000..7c3eff6 --- /dev/null +++ b/src/Filesystem/Adapters/PrefixedAdapter.php @@ -0,0 +1,240 @@ +adapter = $adapter; + $this->prefix = new PathPrefixer($prefix); + } + + public function read(string $location): string + { + try { + return $this->adapter->read($this->prefix->prefixPath($location)); + } catch (Throwable $previous) { + throw UnableToReadFile::fromLocation($location, $previous->getMessage(), $previous); + } + } + + public function readStream(string $location) + { + try { + return $this->adapter->readStream($this->prefix->prefixPath($location)); + } catch (Throwable $previous) { + throw UnableToReadFile::fromLocation($location, $previous->getMessage(), $previous); + } + } + + public function listContents(string $location, bool $deep): Generator + { + foreach ($this->adapter->listContents($this->prefix->prefixPath($location), $deep) as $attributes) { + yield $attributes->withPath($this->prefix->stripPrefix($attributes->path())); + } + } + + public function fileExists(string $location): bool + { + try { + return $this->adapter->fileExists($this->prefix->prefixPath($location)); + } catch (Throwable $previous) { + throw UnableToCheckFileExistence::forLocation($location, $previous); + } + } + + public function directoryExists(string $location): bool + { + try { + return $this->adapter->directoryExists($this->prefix->prefixPath($location)); + } catch (Throwable $previous) { + throw UnableToCheckDirectoryExistence::forLocation($location, $previous); + } + } + + public function lastModified(string $path): FileAttributes + { + try { + return $this->adapter->lastModified($this->prefix->prefixPath($path)); + } catch (Throwable $previous) { + throw UnableToRetrieveMetadata::lastModified($path, $previous->getMessage(), $previous); + } + } + + public function fileSize(string $path): FileAttributes + { + try { + return $this->adapter->fileSize($this->prefix->prefixPath($path)); + } catch (Throwable $previous) { + throw UnableToRetrieveMetadata::fileSize($path, $previous->getMessage(), $previous); + } + } + + public function mimeType(string $path): FileAttributes + { + try { + return $this->adapter->mimeType($this->prefix->prefixPath($path)); + } catch (Throwable $previous) { + throw UnableToRetrieveMetadata::mimeType($path, $previous->getMessage(), $previous); + } + } + + public function visibility(string $path): FileAttributes + { + try { + return $this->adapter->visibility($this->prefix->prefixPath($path)); + } catch (Throwable $previous) { + throw UnableToRetrieveMetadata::visibility($path, $previous->getMessage(), $previous); + } + } + + public function write(string $location, string $contents, Config $config): void + { + try { + $this->adapter->write($this->prefix->prefixPath($location), $contents, $config); + } catch (Throwable $previous) { + throw UnableToWriteFile::atLocation($location, $previous->getMessage(), $previous); + } + } + + public function writeStream(string $location, $contents, Config $config): void + { + try { + $this->adapter->writeStream($this->prefix->prefixPath($location), $contents, $config); + } catch (Throwable $previous) { + throw UnableToWriteFile::atLocation($location, $previous->getMessage(), $previous); + } + } + + public function setVisibility(string $path, string $visibility): void + { + try { + $this->adapter->setVisibility($this->prefix->prefixPath($path), $visibility); + } catch (Throwable $previous) { + throw UnableToSetVisibility::atLocation($path, $previous->getMessage(), $previous); + } + } + + public function delete(string $location): void + { + try { + $this->adapter->delete($this->prefix->prefixPath($location)); + } catch (Throwable $previous) { + throw UnableToDeleteFile::atLocation($location, $previous->getMessage(), $previous); + } + } + + public function deleteDirectory(string $location): void + { + try { + $this->adapter->deleteDirectory($this->prefix->prefixPath($location)); + } catch (Throwable $previous) { + throw UnableToDeleteDirectory::atLocation($location, $previous->getMessage(), $previous); + } + } + + public function createDirectory(string $location, Config $config): void + { + try { + $this->adapter->createDirectory($this->prefix->prefixPath($location), $config); + } catch (Throwable $previous) { + throw UnableToCreateDirectory::atLocation($location, $previous->getMessage(), $previous); + } + } + + public function move(string $source, string $destination, Config $config): void + { + try { + $this->adapter->move( + $this->prefix->prefixPath($source), + $this->prefix->prefixPath($destination), + $config + ); + } catch (Throwable $previous) { + throw UnableToMoveFile::fromLocationTo($source, $destination, $previous); + } + } + + public function copy(string $source, string $destination, Config $config): void + { + try { + $this->adapter->copy( + $this->prefix->prefixPath($source), + $this->prefix->prefixPath($destination), + $config + ); + } catch (Throwable $previous) { + throw UnableToCopyFile::fromLocationTo($source, $destination, $previous); + } + } + + public function publicUrl(string $path, Config $config): string + { + if (! $this->adapter instanceof PublicUrlGenerator) { + throw UnableToGeneratePublicUrl::noGeneratorConfigured($path); + } + + return $this->adapter->publicUrl($this->prefix->prefixPath($path), $config); + } + + public function checksum(string $path, Config $config): string + { + if ($this->adapter instanceof ChecksumProvider) { + return $this->adapter->checksum($this->prefix->prefixPath($path), $config); + } + + return $this->calculateChecksumFromStream($path, $config); + } + + public function temporaryUrl(string $path, DateTimeInterface $expiresAt, Config $config): string + { + if (! $this->adapter instanceof TemporaryUrlGenerator) { + throw UnableToGenerateTemporaryUrl::noGeneratorConfigured($path); + } + + return $this->adapter->temporaryUrl( + $this->prefix->prefixPath($path), + $expiresAt, + $config + ); + } +} diff --git a/src/Filesystem/Adapters/S3Adapter.php b/src/Filesystem/Adapters/S3Adapter.php index 060cf01..2b23bf7 100644 --- a/src/Filesystem/Adapters/S3Adapter.php +++ b/src/Filesystem/Adapters/S3Adapter.php @@ -50,7 +50,7 @@ class S3Adapter implements AdapterInterface ]; if (! empty($settings['endpoint'])) { - $clientConfig['endpoint'] = rtrim($settings['endpoint'], '/'); + $clientConfig['endpoint'] = rtrim($settings['endpoint'], '/'); $clientConfig['use_path_style_endpoint'] = (bool) ($settings['use_path_style_endpoint'] ?? true); } @@ -68,11 +68,14 @@ class S3Adapter implements AdapterInterface mimeTypeDetector: $this->mimeTypeDetector ); - if (! empty($settings['prefix'])) { - $adapter = new PathPrefixedAdapter( - $adapter, - trim($settings['prefix'], '/') - ); + $prefix = trim((string) ($settings['prefix'] ?? ''), '/'); + + if ($prefix !== '') { + if (class_exists(PathPrefixedAdapter::class)) { + $adapter = new PathPrefixedAdapter($adapter, $prefix); + } else { + $adapter = new PrefixedAdapter($adapter, $prefix); + } } return $adapter; @@ -92,7 +95,7 @@ class S3Adapter implements AdapterInterface $endpoint = $settings['endpoint'] ?? null; $region = $settings['region'] ?? 'us-east-1'; $usePathStyle = (bool) ($settings['use_path_style_endpoint'] ?? false); - $prefix = trim($settings['prefix'] ?? '', '/'); + $prefix = trim((string) ($settings['prefix'] ?? ''), '/'); $normalizedUrl = null; diff --git a/src/Helpers/PathHelper.php b/src/Helpers/PathHelper.php index f566573..1eb452b 100644 --- a/src/Helpers/PathHelper.php +++ b/src/Helpers/PathHelper.php @@ -39,4 +39,17 @@ final class PathHelper return self::trimTrailingSlash($path); } + + public static function normalizePrefix(?string $prefix): string + { + if ($prefix === null || $prefix === '') { + return ''; + } + + $prefix = self::stripProtocol($prefix); + $prefix = self::normalizeDirectory($prefix); + $prefix = self::trimSlashes($prefix); + + return $prefix === '' ? '' : self::ensureTrailingSlash($prefix); + } } diff --git a/src/Media/ImageEditorGD.php b/src/Media/ImageEditorGD.php index 706c205..776595e 100644 --- a/src/Media/ImageEditorGD.php +++ b/src/Media/ImageEditorGD.php @@ -11,11 +11,18 @@ if (! defined('ABSPATH')) { exit; } -if (! class_exists(\WP_Image_Editor_GD::class) && file_exists(ABSPATH . WPINC . '/class-wp-image-editor-gd.php')) { - require_once ABSPATH . WPINC . '/class-wp-image-editor-gd.php'; +if (! class_exists(\WP_Image_Editor::class, false)) { + require_once ABSPATH . WPINC . '/class-wp-image-editor.php'; } -if (! class_exists(\WP_Image_Editor_GD::class)) { +if (! class_exists(\WP_Image_Editor_GD::class, false)) { + $gdEditorFile = ABSPATH . WPINC . '/class-wp-image-editor-gd.php'; + if (file_exists($gdEditorFile)) { + require_once $gdEditorFile; + } +} + +if (! class_exists(\WP_Image_Editor_GD::class, false)) { return; } diff --git a/src/Media/ImageEditorImagick.php b/src/Media/ImageEditorImagick.php index 7db893f..0e0fab6 100644 --- a/src/Media/ImageEditorImagick.php +++ b/src/Media/ImageEditorImagick.php @@ -11,11 +11,19 @@ if (! defined('ABSPATH')) { exit; } -if (! class_exists(\WP_Image_Editor_Imagick::class) && file_exists(ABSPATH . WPINC . '/class-wp-image-editor-imagick.php')) { - require_once ABSPATH . WPINC . '/class-wp-image-editor-imagick.php'; +if (! class_exists(\WP_Image_Editor::class, false)) { + require_once ABSPATH . WPINC . '/class-wp-image-editor.php'; } -if (! class_exists(\WP_Image_Editor_Imagick::class)) { +if (! class_exists(\WP_Image_Editor_Imagick::class, false)) { + $imagickEditorFile = ABSPATH . WPINC . '/class-wp-image-editor-imagick.php'; + + if (file_exists($imagickEditorFile)) { + require_once $imagickEditorFile; + } +} + +if (! class_exists(\WP_Image_Editor_Imagick::class, false)) { return; } diff --git a/src/Plugin.php b/src/Plugin.php index 644c26d..1ce998e 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -1,261 +1,142 @@ mediaHooks = new MediaHooks(); + public function __construct( + FilesystemFactory $filesystemFactory, + MediaHooks $mediaHooks, + SettingsPage $settingsPage + ) { + $this->filesystemFactory = $filesystemFactory; + $this->mediaHooks = $mediaHooks; + $this->settingsPage = $settingsPage; } - public static function bootstrap(string $pluginFile): void + public function register(): void { - self::$pluginFile = $pluginFile; - - register_activation_hook($pluginFile, [self::class, 'activate']); - register_deactivation_hook($pluginFile, [self::class, 'deactivate']); - - add_action('plugins_loaded', static function (): void { - self::instance()->init(); - }); + add_action('plugins_loaded', [$this, 'bootstrap'], 5); + add_action('init', [$this, 'loadTextDomain']); + add_filter('plugin_action_links_' . plugin_basename(FlysystemOffload::PLUGIN_FILE), [$this, 'pluginLinks']); } - public static function instance(): self + public function loadTextDomain(): void { - return self::$instance ??= new self(); + load_plugin_textdomain( + 'flysystem-offload', + false, + dirname(plugin_basename(FlysystemOffload::PLUGIN_FILE)) . '/languages' + ); } - public static function activate(): void + public function bootstrap(): void { - wp_mkdir_p(WP_CONTENT_DIR . '/flysystem-uploads'); - - if (! defined('FLYSYSTEM_OFFLOAD_CONFIG') && ! file_exists(WP_CONTENT_DIR . '/flysystem-offload.php')) { - error_log('[Flysystem Offload] No se encontró un archivo de configuración. Copia config/flysystem-offload.example.php a wp-content/flysystem-offload.php y ajústalo.'); - } - } - - public static function deactivate(): void - { - if ($instance = self::$instance) { - $instance->mediaHooks->unregister(); - $instance->mediaHooks->setFilesystem(null); + if ($this->isInitialized) { + return; } - if (in_array('fly', stream_get_wrappers(), true)) { - stream_wrapper_unregister('fly'); + $this->config = $this->settingsPage->getSettings(); + + if (! is_array($this->config)) { + $this->config = []; } - } - public function init(): void - { - $this->configLoader = new ConfigLoader(self::$pluginFile); - $this->reloadConfig(); + $this->registerStreamWrapper(); + $this->mediaHooks->register($this); + $this->settingsPage->register($this); - add_filter('upload_dir', [$this, 'filterUploadDir'], 20); - add_filter('wp_get_attachment_url', [$this, 'filterAttachmentUrl'], 20, 2); - add_filter('wp_get_attachment_metadata', [$this, 'filterAttachmentMetadata'], 20); - add_filter('wp_get_original_image_path', [$this, 'filterOriginalImagePath'], 20); - add_filter('get_attached_file', [$this, 'filterAttachedFile'], 20, 2); - - add_filter('wp_delete_file', [$this, 'handleDeleteFile'], 20); - add_action('delete_attachment', [$this, 'handleDeleteAttachment'], 20); - add_action('switch_blog', [$this, 'handleSwitchBlog']); - add_action('flysystem_offload_reload_config', [$this, 'reloadConfig']); - - if (defined('WP_CLI') && WP_CLI && class_exists(\FlysystemOffload\Admin\HealthCheck::class)) { - \WP_CLI::add_command('flysystem-offload health-check', [\FlysystemOffload\Admin\HealthCheck::class, 'run']); + if (defined('WP_CLI') && WP_CLI) { + if (class_exists(HealthCheck::class)) { + \WP_CLI::add_command('flysystem-offload health-check', new HealthCheck($this->filesystemFactory)); + } } + + $this->isInitialized = true; } public function reloadConfig(): void { - try { - $this->config = $this->configLoader->load(); - } catch (Throwable $exception) { - error_log('[Flysystem Offload] Error cargando configuración: ' . $exception->getMessage()); - $this->config = $this->configLoader->defaults(); - } - - $this->filesystem = null; - $this->streamRegistered = false; - $this->mediaHooks->unregister(); - $this->mediaHooks->setFilesystem(null); + $this->config = $this->settingsPage->getSettings(); + if (! is_array($this->config)) { + $this->config = []; + } $this->registerStreamWrapper(); + $this->mediaHooks->register($this); } - public function handleSwitchBlog(): void + public function registerStreamWrapper(): void { - $this->reloadConfig(); - } - - /** - * @throws Throwable - */ - public function getFilesystem(): FilesystemOperator - { - if ($this->filesystem === null) { - $factory = new FilesystemFactory($this->config); - $result = $factory->make(); - - if (is_wp_error($result)) { - throw new \RuntimeException($result->get_error_message()); - } - - $this->filesystem = $result; - } - - return $this->filesystem; - } - - public function filterUploadDir(array $dirs): array - { - $remoteBase = $this->getRemoteUrlBase(); - $prefix = PathHelper::normalizePrefix($this->config['base_prefix'] ?? ''); - - $subdir = $dirs['subdir'] ?? ''; - $dirs['path'] = "fly://{$prefix}" . ltrim($subdir, '/'); - $dirs['basedir'] = "fly://{$prefix}"; - $dirs['url'] = trailingslashit($remoteBase) . ltrim($subdir, '/'); - $dirs['baseurl'] = $remoteBase; - - return $dirs; - } - - public function filterAttachmentUrl(string $url, int $postId): string - { - unset($postId); - - $uploadDir = wp_get_upload_dir(); - $localBase = trailingslashit($uploadDir['baseurl'] ?? ''); - $remoteBase = trailingslashit($this->getRemoteUrlBase()); - - return str_replace($localBase, $remoteBase, $url); - } - - public function filterAttachmentMetadata(array $metadata): array - { - if (! empty($metadata['file'])) { - $metadata['file'] = PathHelper::stripProtocol($metadata['file']); - } - - if (! empty($metadata['sizes'])) { - foreach ($metadata['sizes'] as &$size) { - if (! empty($size['file'])) { - $size['file'] = ltrim(PathHelper::stripProtocol($size['file']), '/'); - } - } - - unset($size); - } - - return $metadata; - } - - public function filterOriginalImagePath(string $path): string - { - return PathHelper::ensureFlyProtocol($path); - } - - public function filterAttachedFile(string $file, int $attachmentId): string - { - unset($attachmentId); - - if (PathHelper::isFlyProtocol($file)) { - return $file; - } - - return PathHelper::ensureFlyProtocol($file); - } - - public function handleDeleteFile(string $file): string|false - { - $flyPath = PathHelper::stripProtocol($file); - try { - $this->getFilesystem()->delete($flyPath); - } catch (Throwable $exception) { - error_log('[Flysystem Offload] Error al borrar archivo: ' . $flyPath . ' - ' . $exception->getMessage()); - } - - return false; - } - - public function handleDeleteAttachment(int $postId): void - { - $files = PathHelper::collectFilesFromAttachment($postId); - - foreach ($files as $relativePath) { - try { - $this->getFilesystem()->delete($relativePath); - } catch (Throwable $exception) { - error_log('[Flysystem Offload] Error al borrar attachment: ' . $relativePath . ' - ' . $exception->getMessage()); - } - } - } - - private function registerStreamWrapper(): void - { - if ($this->streamRegistered) { - return; - } - - try { - $filesystem = $this->getFilesystem(); - } catch (Throwable $exception) { - error_log('[Flysystem Offload] No se pudo obtener el filesystem: ' . $exception->getMessage()); - - return; - } - - try { - $visibility = $this->config['visibility'] ?? PortableVisibility::PUBLIC; + $filesystem = $this->filesystemFactory->build($this->config); + $protocol = $this->config['protocol'] ?? 'fly'; + $prefix = $this->config['root_prefix'] ?? ''; FlysystemStreamWrapper::register( $filesystem, - 'fly', - PathHelper::normalizePrefix($this->config['base_prefix'] ?? ''), - ['visibility' => $visibility] + $protocol, + $prefix, + [ + 'visibility' => $this->config['visibility'] ?? Visibility::PUBLIC, + ] ); - - $this->streamRegistered = true; - } catch (Throwable $exception) { + } catch (\Throwable $exception) { error_log('[Flysystem Offload] No se pudo registrar el stream wrapper: ' . $exception->getMessage()); } - - $this->mediaHooks->setFilesystem($filesystem); - $this->mediaHooks->register(); } - private function getRemoteUrlBase(): string + public function pluginLinks(array $links): array { - $adapterKey = $this->config['adapter'] ?? 'local'; - $settings = $this->config['adapters'][$adapterKey] ?? []; + $settingsUrl = admin_url('options-general.php?page=flysystem-offload'); - return (new FilesystemFactory($this->config)) - ->resolvePublicBaseUrl($adapterKey, $settings); + $links[] = '' . esc_html__('Ajustes', 'flysystem-offload') . ''; + + return $links; + } + + public function getConfig(): array + { + return $this->config; + } + + public function getFilesystemFactory(): FilesystemFactory + { + return $this->filesystemFactory; + } + + public function getRemoteUrlBase(): string + { + $driver = $this->config['driver'] ?? null; + + if (! $driver) { + return ''; + } + + $result = $this->filesystemFactory->resolvePublicBaseUrl($driver, $this->config); + + if ($result instanceof WP_Error) { + throw new RuntimeException($result->get_error_message()); + } + + return $result; } } diff --git a/src/Settings/SettingsPage.php b/src/Settings/SettingsPage.php new file mode 100644 index 0000000..d26a5c4 --- /dev/null +++ b/src/Settings/SettingsPage.php @@ -0,0 +1,100 @@ + + */ + public function getSettings(): array + { + $settings = get_option(self::OPTION_KEY, []); + + return is_array($settings) ? $settings : []; + } + + public function register(Plugin $plugin): void + { + add_action('admin_menu', function () { + add_options_page( + __('Flysystem Offload', 'flysystem-offload'), + __('Flysystem Offload', 'flysystem-offload'), + 'manage_options', + 'flysystem-offload', + [$this, 'renderPage'] + ); + }); + + add_action('admin_init', [$this, 'registerSettings']); + } + + public function renderPage(): void + { + if (! current_user_can('manage_options')) { + wp_die(__('No tienes permisos para acceder a esta página.', 'flysystem-offload')); + } + + $settings = $this->getSettings(); + ?> +
+

+

+ +
+ +
+
+ ' . esc_html__( + 'Introduce las credenciales del proveedor que deseas utilizar.', + 'flysystem-offload' + ) . '

'; + }, + 'flysystem-offload' + ); + + add_settings_field( + 'flysystem_offload_driver', + __('Driver', 'flysystem-offload'), + [$this, 'renderDriverField'], + 'flysystem-offload', + 'flysystem_offload_general' + ); + } + + public function renderDriverField(): void + { + $settings = $this->getSettings(); + $driver = $settings['driver'] ?? ''; + ?> + + PortableVisibility::PUBLIC, + 'visibility' => Visibility::PUBLIC, ]; stream_wrapper_register($protocol, static::class, STREAM_IS_URL); @@ -500,6 +500,6 @@ final class FlysystemStreamWrapper */ private function writeOptionsForProtocol(string $protocol): array { - return self::$writeOptions[$protocol] ?? ['visibility' => PortableVisibility::PUBLIC]; + return self::$writeOptions[$protocol] ?? ['visibility' => Visibility::PUBLIC]; } }