260 lines
8.5 KiB
PHP
260 lines
8.5 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace FlysystemOffload\Media;
|
|
|
|
use FlysystemOffload\Helpers\PathHelper;
|
|
use League\Flysystem\FilesystemOperator;
|
|
|
|
final class MediaHooks {
|
|
private FilesystemOperator $filesystem;
|
|
private array $config;
|
|
private string $protocol;
|
|
private string $streamHost;
|
|
private string $streamRootPrefix;
|
|
private string $s3Prefix;
|
|
private string $baseUrl;
|
|
private string $effectiveBaseUrl;
|
|
private string $remoteUrlPathPrefix;
|
|
private bool $deleteRemote;
|
|
private bool $preferLocal;
|
|
|
|
public function __construct(FilesystemOperator $filesystem, array $config) {
|
|
$this->filesystem = $filesystem;
|
|
$this->config = $config;
|
|
$this->protocol = (string) ($config['stream']['protocol'] ?? 'flysystem');
|
|
|
|
$this->streamRootPrefix = PathHelper::normalize((string) ($config['stream']['root_prefix'] ?? ''));
|
|
$this->streamHost = PathHelper::normalize((string) ($config['stream']['host'] ?? ''));
|
|
$this->s3Prefix = PathHelper::normalize((string) ($config['s3']['prefix'] ?? ''));
|
|
|
|
$this->baseUrl = $this->normaliseBaseUrl((string) ($config['uploads']['base_url'] ?? content_url('uploads')));
|
|
$this->remoteUrlPathPrefix = $this->buildRemoteUrlPathPrefix();
|
|
$this->effectiveBaseUrl = $this->calculateEffectiveBaseUrl($this->baseUrl, $this->remoteUrlPathPrefix);
|
|
|
|
$this->deleteRemote = (bool) ($config['uploads']['delete_remote'] ?? true);
|
|
$this->preferLocal = (bool) ($config['uploads']['prefer_local_for_missing'] ?? false);
|
|
|
|
error_log(sprintf(
|
|
'[MediaHooks] Initialized - protocol: %s, host: %s, root_prefix: %s, base_url: %s, effective_base_url: %s',
|
|
$this->protocol,
|
|
$this->streamHost,
|
|
$this->streamRootPrefix,
|
|
$this->baseUrl,
|
|
$this->effectiveBaseUrl
|
|
));
|
|
}
|
|
|
|
public function register(): void {
|
|
add_filter('upload_dir', [$this, 'filterUploadDir'], 20);
|
|
add_filter('pre_option_upload_path', '__return_false');
|
|
add_filter('pre_option_upload_url_path', '__return_false');
|
|
add_filter('wp_get_attachment_url', [$this, 'rewriteAttachmentUrl'], 9, 2);
|
|
add_filter('image_downsize', [$this, 'filterImageDownsize'], 10, 3);
|
|
add_action('delete_attachment', [$this, 'deleteRemoteFiles']);
|
|
}
|
|
|
|
public function filterUploadDir(array $uploads): array {
|
|
$subdir = $uploads['subdir'] ?? '';
|
|
$normalizedSubdir = $subdir !== '' ? PathHelper::normalize($subdir) : '';
|
|
|
|
$streamSubdir = $normalizedSubdir !== '' ? '/' . $normalizedSubdir : '';
|
|
$streamBase = sprintf('%s://%s', $this->protocol, $this->streamHost ?: 'default');
|
|
|
|
$uploads['path'] = $streamBase . $streamSubdir;
|
|
$uploads['basedir'] = $streamBase;
|
|
$uploads['baseurl'] = rtrim($this->effectiveBaseUrl, '/');
|
|
$uploads['url'] = $this->buildPublicUrl($normalizedSubdir);
|
|
$uploads['subdir'] = $normalizedSubdir !== '' ? '/' . $normalizedSubdir : '';
|
|
$uploads['error'] = false;
|
|
|
|
$uploads['flysystem_protocol'] = $this->protocol;
|
|
$uploads['flysystem_host'] = $this->streamHost;
|
|
$uploads['flysystem_root_prefix'] = $this->streamRootPrefix;
|
|
|
|
error_log(sprintf(
|
|
'[MediaHooks] Upload dir filtered - path: %s, url: %s',
|
|
$uploads['path'],
|
|
$uploads['url']
|
|
));
|
|
|
|
return $uploads;
|
|
}
|
|
|
|
public function rewriteAttachmentUrl(string $url, int $attachmentId): string {
|
|
$file = get_post_meta($attachmentId, '_wp_attached_file', true);
|
|
if (! $file) {
|
|
return $url;
|
|
}
|
|
|
|
$relativePath = PathHelper::normalize($file);
|
|
if ($relativePath === '') {
|
|
return $url;
|
|
}
|
|
|
|
if ($this->remoteUrlPathPrefix !== '') {
|
|
$prefixWithSlash = $this->remoteUrlPathPrefix . '/';
|
|
if (str_starts_with($relativePath, $prefixWithSlash)) {
|
|
$relativePath = substr($relativePath, strlen($prefixWithSlash));
|
|
} elseif ($relativePath === $this->remoteUrlPathPrefix) {
|
|
$relativePath = '';
|
|
}
|
|
}
|
|
|
|
$remoteUrl = $this->buildPublicUrl($relativePath);
|
|
|
|
if (! $this->preferLocal) {
|
|
return $remoteUrl;
|
|
}
|
|
|
|
try {
|
|
if ($this->filesystem->fileExists($this->toRemotePath($relativePath))) {
|
|
return $remoteUrl;
|
|
}
|
|
} catch (\Throwable $exception) {
|
|
error_log(sprintf(
|
|
'[Flysystem Offload] No se pudo verificar la existencia remota de "%s": %s',
|
|
$relativePath,
|
|
$exception->getMessage()
|
|
));
|
|
}
|
|
|
|
return $url;
|
|
}
|
|
|
|
public function filterImageDownsize(bool|array $out, int $attachmentId, array|string $size): bool|array {
|
|
return false;
|
|
}
|
|
|
|
public function deleteRemoteFiles(int $attachmentId): void {
|
|
if (! $this->deleteRemote) {
|
|
return;
|
|
}
|
|
|
|
$files = $this->gatherAttachmentFiles($attachmentId);
|
|
|
|
foreach ($files as $file) {
|
|
$key = $this->toRemotePath($file);
|
|
|
|
try {
|
|
if ($this->filesystem->fileExists($key)) {
|
|
$this->filesystem->delete($key);
|
|
}
|
|
} catch (\Throwable $exception) {
|
|
error_log(sprintf(
|
|
'[Flysystem Offload] No se pudo eliminar el archivo remoto "%s": %s',
|
|
$key,
|
|
$exception->getMessage()
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return list<string>
|
|
*/
|
|
private function gatherAttachmentFiles(int $attachmentId): array {
|
|
$files = [];
|
|
|
|
$attachedFile = get_post_meta($attachmentId, '_wp_attached_file', true);
|
|
if ($attachedFile) {
|
|
$files[] = $attachedFile;
|
|
}
|
|
|
|
$meta = wp_get_attachment_metadata($attachmentId);
|
|
|
|
if (is_array($meta)) {
|
|
if (! empty($meta['file'])) {
|
|
$files[] = $meta['file'];
|
|
}
|
|
|
|
if (! empty($meta['sizes']) && is_array($meta['sizes'])) {
|
|
$baseDir = $this->dirName($meta['file'] ?? '');
|
|
foreach ($meta['sizes'] as $sizeMeta) {
|
|
if (! empty($sizeMeta['file'])) {
|
|
$files[] = ($baseDir !== '' ? $baseDir . '/' : '') . $sizeMeta['file'];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$files = array_filter($files, static fn ($file) => is_string($file) && $file !== '');
|
|
|
|
return array_values(array_unique($files, SORT_STRING));
|
|
}
|
|
|
|
private function dirName(string $path): string {
|
|
$directory = dirname($path);
|
|
return $directory === '.' ? '' : $directory;
|
|
}
|
|
|
|
private function toRemotePath(string $file): string {
|
|
$segments = [];
|
|
|
|
// ✅ Solo agregar si NO están vacíos
|
|
if ($this->streamRootPrefix !== '') {
|
|
$segments[] = $this->streamRootPrefix;
|
|
}
|
|
|
|
if ($this->streamHost !== '') {
|
|
$segments[] = $this->streamHost;
|
|
}
|
|
|
|
$segments[] = $file;
|
|
|
|
$remotePath = PathHelper::join(...$segments);
|
|
|
|
error_log(sprintf(
|
|
'[MediaHooks] toRemotePath - file: %s, remote: %s',
|
|
$file,
|
|
$remotePath
|
|
));
|
|
|
|
return $remotePath;
|
|
}
|
|
|
|
private function normaliseBaseUrl(string $baseUrl): string {
|
|
$baseUrl = trim($baseUrl);
|
|
if ($baseUrl === '') {
|
|
$baseUrl = content_url('uploads');
|
|
}
|
|
|
|
return rtrim($baseUrl, '/');
|
|
}
|
|
|
|
private function buildRemoteUrlPathPrefix(): string {
|
|
$segments = array_filter(
|
|
[$this->s3Prefix, $this->streamRootPrefix, $this->streamHost],
|
|
static fn (string $segment): bool => $segment !== ''
|
|
);
|
|
|
|
return PathHelper::join(...$segments);
|
|
}
|
|
|
|
private function calculateEffectiveBaseUrl(string $baseUrl, string $pathPrefix): string {
|
|
$baseUrl = rtrim($baseUrl, '/');
|
|
|
|
if ($pathPrefix === '') {
|
|
return $baseUrl;
|
|
}
|
|
|
|
$basePath = trim((string) parse_url($baseUrl, PHP_URL_PATH), '/');
|
|
|
|
if ($basePath !== '' && str_ends_with($basePath, $pathPrefix)) {
|
|
return $baseUrl;
|
|
}
|
|
|
|
return $baseUrl;
|
|
}
|
|
|
|
private function buildPublicUrl(string $relativePath): string {
|
|
$base = rtrim($this->effectiveBaseUrl, '/');
|
|
|
|
if ($relativePath === '') {
|
|
return $base;
|
|
}
|
|
|
|
return $base . '/' . PathHelper::normalize($relativePath);
|
|
}
|
|
}
|