flysystem-offload/src/Plugin.php

233 lines
7.4 KiB
PHP

<?php
namespace FlysystemOffload;
use FlysystemOffload\Config\ConfigLoader;
use FlysystemOffload\Filesystem\FilesystemFactory;
use FlysystemOffload\Helpers\PathHelper;
use FlysystemOffload\Media\MediaHooks;
use FlysystemOffload\StreamWrapper\FlysystemStreamWrapper;
use League\Flysystem\FilesystemOperator;
class Plugin
{
private static $instance;
private static string $pluginFile;
private ?FilesystemOperator $filesystem = null;
private bool $streamRegistered = false;
private array $config = [];
private ConfigLoader $configLoader;
private MediaHooks $mediaHooks;
public function __construct()
{
$this->mediaHooks = new MediaHooks();
}
public static function bootstrap(string $pluginFile): void
{
self::$pluginFile = $pluginFile;
register_activation_hook($pluginFile, [self::class, 'activate']);
register_deactivation_hook($pluginFile, [self::class, 'deactivate']);
add_action('plugins_loaded', static function () {
self::instance()->init();
});
}
public static function instance(): self
{
return self::$instance ??= new self();
}
public static function activate(): 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 (in_array('fly', stream_get_wrappers(), true)) {
stream_wrapper_unregister('fly');
}
}
public function init(): void
{
$this->configLoader = new ConfigLoader(self::$pluginFile);
$this->reloadConfig();
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('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) {
\WP_CLI::add_command('flysystem-offload health-check', [Admin\HealthCheck::class, 'run']);
}
}
public function reloadConfig(): void
{
try {
$this->config = $this->configLoader->load();
} catch (\Throwable $e) {
error_log('[Flysystem Offload] Error cargando configuración: ' . $e->getMessage());
$this->config = $this->configLoader->defaults();
}
$this->filesystem = null;
$this->streamRegistered = false;
$this->mediaHooks->unregister();
$this->mediaHooks->setFilesystem(null);
$this->registerStreamWrapper();
}
public function handleSwitchBlog(): void
{
$this->reloadConfig();
}
public function getFilesystem(): FilesystemOperator
{
if (! $this->filesystem) {
$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;
}
private function registerStreamWrapper(): void
{
if ($this->streamRegistered) {
return;
}
try {
$filesystem = $this->getFilesystem();
} catch (\Throwable $e) {
error_log('[Flysystem Offload] No se pudo obtener el filesystem: ' . $e->getMessage());
return;
}
try {
FlysystemStreamWrapper::register(
$filesystem,
'fly',
PathHelper::normalizePrefix($this->config['base_prefix'] ?? '')
);
$this->streamRegistered = true;
} catch (\Throwable $e) {
error_log('[Flysystem Offload] No se pudo registrar el stream wrapper: ' . $e->getMessage());
}
$this->mediaHooks->setFilesystem($filesystem);
$this->mediaHooks->register();
}
public function filterUploadDir(array $dirs): array
{
$remoteBase = $this->getRemoteUrlBase();
$prefix = PathHelper::normalizePrefix($this->config['base_prefix'] ?? '');
$subdir = $dirs['subdir'] ?? '';
$dirs['path'] = "fly://{$prefix}{$subdir}";
$dirs['basedir'] = "fly://{$prefix}";
$dirs['url'] = trailingslashit($remoteBase) . ltrim($subdir, '/');
$dirs['baseurl'] = $remoteBase;
return $dirs;
}
public function filterAttachmentUrl(string $url, int $postId): string
{
$localBase = trailingslashit(wp_get_upload_dir()['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($size['file'], '/');
}
}
unset($size);
}
return $metadata;
}
public function filterOriginalImagePath(string $path): string
{
return PathHelper::ensureFlyProtocol($path);
}
public function handleDeleteFile(string $file): string|false
{
$flyPath = PathHelper::stripProtocol($file);
try {
$this->getFilesystem()->delete($flyPath);
} catch (\Throwable $e) {
error_log('[Flysystem Offload] Error al borrar archivo: ' . $flyPath . ' - ' . $e->getMessage());
}
return false;
}
public function handleDeleteAttachment(int $postId): void
{
$files = PathHelper::collectFilesFromAttachment($postId);
foreach ($files as $relativePath) {
try {
$this->getFilesystem()->delete($relativePath);
} catch (\Throwable $e) {
error_log('[Flysystem Offload] Error al borrar attachment: ' . $relativePath . ' - ' . $e->getMessage());
}
}
}
private function getRemoteUrlBase(): string
{
$adapterKey = $this->config['adapter'] ?? 'local';
$settings = $this->config['adapters'][$adapterKey] ?? [];
return (new FilesystemFactory($this->config))->resolvePublicBaseUrl($adapterKey, $settings);
}
}