This commit is contained in:
Brasdrive 2025-11-07 02:24:16 -04:00
parent d4087f5299
commit 65c7a7365d
10 changed files with 534 additions and 272 deletions

View File

@ -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",

View File

@ -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<int, string> $args
* @param array<string, mixed> $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<string, mixed> $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;
}
}

View File

@ -0,0 +1,240 @@
<?php
declare(strict_types=1);
namespace FlysystemOffload\Filesystem\Adapters;
use DateTimeInterface;
use Generator;
use League\Flysystem\CalculateChecksumFromStream;
use League\Flysystem\ChecksumProvider;
use League\Flysystem\Config;
use League\Flysystem\FileAttributes;
use League\Flysystem\FilesystemAdapter;
use League\Flysystem\PathPrefixer;
use League\Flysystem\UnableToCheckDirectoryExistence;
use League\Flysystem\UnableToCheckFileExistence;
use League\Flysystem\UnableToCopyFile;
use League\Flysystem\UnableToCreateDirectory;
use League\Flysystem\UnableToDeleteDirectory;
use League\Flysystem\UnableToDeleteFile;
use League\Flysystem\UnableToGeneratePublicUrl;
use League\Flysystem\UnableToGenerateTemporaryUrl;
use League\Flysystem\UnableToMoveFile;
use League\Flysystem\UnableToReadFile;
use League\Flysystem\UnableToRetrieveMetadata;
use League\Flysystem\UnableToSetVisibility;
use League\Flysystem\UnableToWriteFile;
use League\Flysystem\UrlGeneration\PublicUrlGenerator;
use League\Flysystem\UrlGeneration\TemporaryUrlGenerator;
use Throwable;
class PrefixedAdapter implements FilesystemAdapter, PublicUrlGenerator, ChecksumProvider, TemporaryUrlGenerator
{
use CalculateChecksumFromStream;
/** @var PathPrefixer */
private $prefix;
/** @var FilesystemAdapter */
private $adapter;
public function __construct(FilesystemAdapter $adapter, string $prefix)
{
if ($prefix === '') {
throw new \InvalidArgumentException('The prefix must not be empty.');
}
$this->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
);
}
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -1,261 +1,142 @@
<?php
declare(strict_types=1);
namespace FlysystemOffload;
use FlysystemOffload\Admin\HealthCheck;
use FlysystemOffload\Config\ConfigLoader;
use FlysystemOffload\Filesystem\FilesystemFactory;
use FlysystemOffload\Helpers\PathHelper;
use FlysystemOffload\Media\MediaHooks;
use FlysystemOffload\Settings\SettingsPage;
use FlysystemOffload\StreamWrapper\FlysystemStreamWrapper;
use League\Flysystem\FilesystemOperator;
use League\Flysystem\PortableVisibility;
use Throwable;
use League\Flysystem\Visibility;
use RuntimeException;
use WP_Error;
final class Plugin
{
private static ?self $instance = null;
private static string $pluginFile;
private ?FilesystemOperator $filesystem = null;
private bool $streamRegistered = false;
private array $config = [];
private ConfigLoader $configLoader;
private FilesystemFactory $filesystemFactory;
private MediaHooks $mediaHooks;
private SettingsPage $settingsPage;
private array $config = [];
private bool $isInitialized = false;
public function __construct()
{
$this->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[] = '<a href="' . esc_url($settingsUrl) . '">' . esc_html__('Ajustes', 'flysystem-offload') . '</a>';
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;
}
}

View File

@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace FlysystemOffload\Settings;
use FlysystemOffload\Plugin;
class SettingsPage
{
private const OPTION_KEY = 'flysystem_offload_settings';
/**
* @return array<string, mixed>
*/
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();
?>
<div class="wrap">
<h1><?php esc_html_e('Flysystem Offload', 'flysystem-offload'); ?></h1>
<p><?php esc_html_e('Configura el almacenamiento remoto para WordPress.', 'flysystem-offload'); ?></p>
<form action="options.php" method="post">
<?php
settings_fields('flysystem_offload');
do_settings_sections('flysystem-offload');
submit_button();
?>
</form>
</div>
<?php
}
public function registerSettings(): void
{
register_setting('flysystem_offload', self::OPTION_KEY);
add_settings_section(
'flysystem_offload_general',
__('Configuración General', 'flysystem-offload'),
function () {
echo '<p>' . esc_html__(
'Introduce las credenciales del proveedor que deseas utilizar.',
'flysystem-offload'
) . '</p>';
},
'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'] ?? '';
?>
<select name="<?php echo esc_attr(self::OPTION_KEY . '[driver]'); ?>">
<option value=""><?php esc_html_e('Selecciona un driver', 'flysystem-offload'); ?></option>
<option value="s3" <?php selected($driver, 's3'); ?>><?php esc_html_e('Amazon S3 / Compatible', 'flysystem-offload'); ?></option>
<option value="gcs" <?php selected($driver, 'gcs'); ?>><?php esc_html_e('Google Cloud Storage', 'flysystem-offload'); ?></option>
<option value="azure" <?php selected($driver, 'azure'); ?>><?php esc_html_e('Azure Blob Storage', 'flysystem-offload'); ?></option>
<option value="sftp" <?php selected($driver, 'sftp'); ?>><?php esc_html_e('SFTP', 'flysystem-offload'); ?></option>
<option value="webdav" <?php selected($driver, 'webdav'); ?>><?php esc_html_e('WebDAV', 'flysystem-offload'); ?></option>
</select>
<?php
}
}

View File

@ -6,11 +6,11 @@ namespace FlysystemOffload\StreamWrapper;
use League\Flysystem\FilesystemException;
use League\Flysystem\FilesystemOperator;
use League\Flysystem\PortableVisibility;
use League\Flysystem\StorageAttributes;
use League\Flysystem\UnableToDeleteFile;
use League\Flysystem\UnableToReadFile;
use League\Flysystem\UnableToWriteFile;
use League\Flysystem\Visibility;
use RuntimeException;
use Throwable;
@ -51,7 +51,7 @@ final class FlysystemStreamWrapper
self::$filesystems[$protocol] = $filesystem;
self::$prefixes[$protocol] = trim($prefix, '/');
self::$writeOptions[$protocol] = $writeOptions + [
'visibility' => 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];
}
}