3.0.0
This commit is contained in:
parent
04784ee337
commit
f8c7736735
|
|
@ -7,7 +7,7 @@ return [
|
|||
'visibility' => getenv('FLYSYSTEM_OFFLOAD_DEFAULT_VISIBILITY') ?: 'public',
|
||||
|
||||
'stream' => [
|
||||
'protocol' => getenv('FLYSYSTEM_OFFLOAD_STREAM_PROTOCOL') ?: 'flysystem',
|
||||
'protocol' => getenv('FLYSYSTEM_OFFLOAD_STREAM_PROTOCOL') ?: 'webdav',
|
||||
'root_prefix' => getenv('FLYSYSTEM_OFFLOAD_STREAM_ROOT_PREFIX') ?: '',
|
||||
'host' => getenv('FLYSYSTEM_OFFLOAD_STREAM_HOST') ?: '',
|
||||
],
|
||||
|
|
|
|||
|
|
@ -456,16 +456,28 @@ class ConfigLoader
|
|||
$config['prefix'] = trim($config['prefix'], '/');
|
||||
}
|
||||
|
||||
// ✅ Asegurar valores por defecto para 'stream' y 'uploads'
|
||||
if (!isset($config['stream'])) {
|
||||
$config['stream'] = [
|
||||
'protocol' => 'flysystem',
|
||||
'root_prefix' => '',
|
||||
'host' => 'uploads',
|
||||
];
|
||||
// Asegurar 'stream' existe (no sobrescribir si viene del archivo o BD)
|
||||
if (!isset($config['stream']) || !is_array($config['stream'])) {
|
||||
$config['stream'] = [];
|
||||
}
|
||||
|
||||
if (!isset($config['uploads'])) {
|
||||
// Si no hay protocolo explícito en 'stream', usar el provider como protocolo por defecto
|
||||
$config['stream']['protocol'] = $config['stream']['protocol'] ??
|
||||
($config['provider'] ?? 'flysystem');
|
||||
|
||||
// Si existe una sección específica del provider que define un protocolo (ej. webdav.stream.protocol), respetarla
|
||||
$provider = $config['provider'] ?? null;
|
||||
if ($provider && isset($config[$provider]) && is_array($config[$provider])) {
|
||||
if (isset($config[$provider]['stream']['protocol'])) {
|
||||
$config['stream']['protocol'] = $config[$provider]['stream']['protocol'];
|
||||
}
|
||||
}
|
||||
|
||||
// Asegurar valores por defecto para 'stream' y 'uploads' si no vinieron en la configuración
|
||||
$config['stream']['root_prefix'] = $config['stream']['root_prefix'] ?? '';
|
||||
$config['stream']['host'] = $config['stream']['host'] ?? '';
|
||||
|
||||
if (!isset($config['uploads']) || !is_array($config['uploads'])) {
|
||||
$config['uploads'] = [
|
||||
'base_url' => content_url('uploads'),
|
||||
'delete_remote' => true,
|
||||
|
|
@ -473,6 +485,16 @@ class ConfigLoader
|
|||
];
|
||||
}
|
||||
|
||||
// Si el provider es webdav y no tenemos uploads.base_url, intentar construirla desde webdav/base_uri + prefix
|
||||
if (($provider === 'webdav') && (!isset($config['uploads']['base_url']) || empty($config['uploads']['base_url']))) {
|
||||
$baseUri = $config['base_uri'] ?? ($config['webdav']['base_url'] ?? ($config['webdav']['endpoint'] ?? ''));
|
||||
$prefix = $config['prefix'] ?? ($config['webdav']['prefix'] ?? '');
|
||||
if (!empty($baseUri)) {
|
||||
$baseUri = rtrim($baseUri, '/');
|
||||
$config['uploads']['base_url'] = $prefix !== '' ? $baseUri . '/' . ltrim($prefix, '/') : $baseUri;
|
||||
}
|
||||
}
|
||||
|
||||
error_log('[Flysystem Offload] Final normalized config: ' . print_r($config, true));
|
||||
|
||||
return $config;
|
||||
|
|
|
|||
|
|
@ -52,21 +52,24 @@ class WebdavAdapter implements FilesystemAdapter
|
|||
|
||||
error_log('[WebdavAdapter] Ensuring base directory exists...');
|
||||
|
||||
$parts = array_filter(explode('/', $this->prefix));
|
||||
// Dividir el prefix en partes (sin slashes iniciales/finales)
|
||||
$parts = array_filter(explode('/', trim($this->prefix, '/')));
|
||||
$path = '';
|
||||
|
||||
foreach ($parts as $part) {
|
||||
$path .= '/' . $part;
|
||||
// Construir path RELATIVO (sin slash inicial)
|
||||
$path .= ($path === '' ? '' : '/') . $part;
|
||||
|
||||
try {
|
||||
// Intentar verificar si existe
|
||||
// Intentar verificar si existe (sin slash inicial = relativo al base_uri)
|
||||
$this->client->propFind($path, ['{DAV:}resourcetype'], 0);
|
||||
error_log(sprintf('[WebdavAdapter] Base directory exists: "%s"', $path));
|
||||
error_log(sprintf('[WebdavAdapter] Directory exists: "%s"', $path));
|
||||
} catch (\Exception $e) {
|
||||
// No existe, crear
|
||||
error_log(sprintf('[WebdavAdapter] Base directory does not exist, creating: "%s"', $path));
|
||||
error_log(sprintf('[WebdavAdapter] Directory does not exist, creating: "%s"', $path));
|
||||
|
||||
try {
|
||||
// IMPORTANTE: Sin slash inicial = relativo al base_uri
|
||||
$response = $this->client->request('MKCOL', $path);
|
||||
|
||||
// Verificar el código de estado
|
||||
|
|
@ -74,10 +77,10 @@ class WebdavAdapter implements FilesystemAdapter
|
|||
|
||||
if ($statusCode >= 200 && $statusCode < 300) {
|
||||
// Éxito (201 Created)
|
||||
error_log(sprintf('[WebdavAdapter] Created base directory: "%s", status: %d', $path, $statusCode));
|
||||
error_log(sprintf('[WebdavAdapter] Created directory: "%s", status: %d', $path, $statusCode));
|
||||
} elseif ($statusCode === 405) {
|
||||
// 405 Method Not Allowed = ya existe
|
||||
error_log(sprintf('[WebdavAdapter] Base directory already exists: "%s"', $path));
|
||||
error_log(sprintf('[WebdavAdapter] Directory already exists: "%s"', $path));
|
||||
} else {
|
||||
// Error
|
||||
$errorMsg = sprintf('Failed to create directory "%s", status: %d', $path, $statusCode);
|
||||
|
|
@ -87,10 +90,10 @@ class WebdavAdapter implements FilesystemAdapter
|
|||
} catch (\Exception $e2) {
|
||||
// Verificar si el error es porque ya existe (405)
|
||||
if (strpos($e2->getMessage(), '405') !== false) {
|
||||
error_log(sprintf('[WebdavAdapter] Base directory already exists (405): "%s"', $path));
|
||||
error_log(sprintf('[WebdavAdapter] Directory already exists (405): "%s"', $path));
|
||||
} else {
|
||||
error_log(sprintf(
|
||||
'[WebdavAdapter] Failed to create base directory: "%s", error: %s',
|
||||
'[WebdavAdapter] Failed to create directory: "%s", error: %s',
|
||||
$path,
|
||||
$e2->getMessage()
|
||||
));
|
||||
|
|
@ -104,21 +107,25 @@ class WebdavAdapter implements FilesystemAdapter
|
|||
error_log('[WebdavAdapter] Base directory ensured successfully');
|
||||
}
|
||||
|
||||
/**
|
||||
* Agregar el prefix a la ruta
|
||||
*
|
||||
* @param string $path
|
||||
* @return string Ruta con prefix (RELATIVA, sin slash inicial)
|
||||
*/
|
||||
private function prefixPath(string $path): string
|
||||
{
|
||||
$path = trim($path, '/');
|
||||
|
||||
if ($this->prefix === '') {
|
||||
$prefixed = '/' . ltrim($path, '/');
|
||||
$result = $path;
|
||||
} else {
|
||||
$prefixed = '/' . $this->prefix . '/' . ltrim($path, '/');
|
||||
$result = trim($this->prefix, '/') . ($path === '' ? '' : '/' . $path);
|
||||
}
|
||||
|
||||
error_log(sprintf(
|
||||
'[WebdavAdapter] prefixPath - input: "%s", output: "%s"',
|
||||
$path,
|
||||
$prefixed
|
||||
));
|
||||
|
||||
return $prefixed;
|
||||
|
||||
error_log(sprintf('[WebdavAdapter] prefixPath - input: "%s", output: "%s"', $path, $result));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function fileExists(string $path): bool
|
||||
|
|
|
|||
|
|
@ -1,247 +1,247 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FlysystemOffload\Media;
|
||||
|
||||
use FlysystemOffload\Helpers\PathHelper;
|
||||
use League\Flysystem\FilesystemOperator;
|
||||
use FlysystemOffload\Plugin;
|
||||
|
||||
final class MediaHooks {
|
||||
private FilesystemOperator $filesystem;
|
||||
private array $config;
|
||||
/**
|
||||
* Hooks para integración con el sistema de medios de WordPress
|
||||
* Intercepta uploads, generación de URLs, eliminación de archivos, etc.
|
||||
*/
|
||||
class MediaHooks
|
||||
{
|
||||
private string $provider;
|
||||
private string $protocol;
|
||||
private string $streamHost;
|
||||
private string $streamRootPrefix;
|
||||
private string $host;
|
||||
private string $rootPrefix;
|
||||
private string $providerPrefix;
|
||||
private string $baseUrl;
|
||||
private string $effectiveBaseUrl;
|
||||
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'] ?? ''));
|
||||
public function __construct(array $config)
|
||||
{
|
||||
$this->provider = $config['provider'] ?? 'webdav';
|
||||
$this->protocol = $config['stream']['protocol'] ?? $this->provider;
|
||||
$this->host = $config['stream']['host'] ?? '';
|
||||
$this->rootPrefix = $config['stream']['root_prefix'] ?? '';
|
||||
$this->providerPrefix = $config['prefix'] ?? '';
|
||||
$this->baseUrl = $config['uploads']['base_url'] ?? '';
|
||||
|
||||
// ✅ Obtener el prefix del provider actual
|
||||
$provider = $config['provider'] ?? 's3';
|
||||
$this->providerPrefix = PathHelper::normalize((string) ($config[$provider]['prefix'] ?? $config['prefix'] ?? ''));
|
||||
|
||||
$this->baseUrl = $this->normaliseBaseUrl((string) ($config['uploads']['base_url'] ?? content_url('uploads')));
|
||||
$this->effectiveBaseUrl = $this->baseUrl;
|
||||
|
||||
$this->deleteRemote = (bool) ($config['uploads']['delete_remote'] ?? true);
|
||||
$this->preferLocal = (bool) ($config['uploads']['prefer_local_for_missing'] ?? false);
|
||||
|
||||
error_log(sprintf(
|
||||
'[MediaHooks] Initialized - provider: %s, protocol: %s, host: %s, root_prefix: %s, provider_prefix: %s, base_url: %s',
|
||||
$provider,
|
||||
$this->provider,
|
||||
$this->protocol,
|
||||
$this->streamHost,
|
||||
$this->streamRootPrefix,
|
||||
$this->host,
|
||||
$this->rootPrefix,
|
||||
$this->providerPrefix,
|
||||
$this->baseUrl
|
||||
));
|
||||
}
|
||||
|
||||
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']);
|
||||
/**
|
||||
* Registra los hooks de WordPress
|
||||
*/
|
||||
public function registerHooks(): void
|
||||
{
|
||||
// Filtros para upload dir
|
||||
add_filter('upload_dir', [$this, 'filterUploadDir']);
|
||||
|
||||
// Filtros para URLs de medios
|
||||
add_filter('wp_get_attachment_url', [$this, 'filterAttachmentUrl'], 10, 2);
|
||||
add_filter('wp_calculate_image_srcset', [$this, 'filterImageSrcset'], 10, 5);
|
||||
|
||||
// Acciones para manejo de archivos
|
||||
add_action('wp_generate_attachment_metadata', [$this, 'handleAttachmentMetadata'], 10, 2);
|
||||
add_action('delete_attachment', [$this, 'handleDeleteAttachment']);
|
||||
|
||||
// Filtro para obtener el path correcto
|
||||
add_filter('get_attached_file', [$this, 'filterAttachedFile'], 10, 2);
|
||||
}
|
||||
|
||||
public function filterUploadDir(array $uploads): array {
|
||||
$subdir = $uploads['subdir'] ?? '';
|
||||
$normalizedSubdir = $subdir !== '' ? PathHelper::normalize($subdir) : '';
|
||||
|
||||
$streamSubdir = $normalizedSubdir !== '' ? '/' . $normalizedSubdir : '';
|
||||
|
||||
// 🚫 No forzar 'default' si no hay streamHost definido
|
||||
$streamBase = $this->protocol . '://';
|
||||
|
||||
if ($this->streamHost !== '') {
|
||||
$streamBase .= $this->streamHost;
|
||||
/**
|
||||
* Filtra el directorio de uploads para usar nuestro sistema de archivos
|
||||
*/
|
||||
public function filterUploadDir(array $uploads): array
|
||||
{
|
||||
// Construir el path con el protocolo correcto
|
||||
$path = $this->protocol . '://';
|
||||
if ($this->host) {
|
||||
$path .= $this->host . '/';
|
||||
}
|
||||
|
||||
$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;
|
||||
$uploads['flysystem_provider_prefix'] = $this->providerPrefix;
|
||||
|
||||
error_log(sprintf(
|
||||
'[MediaHooks] Upload dir filtered - path: %s, url: %s, subdir: %s',
|
||||
$uploads['path'],
|
||||
$uploads['url'],
|
||||
$uploads['subdir']
|
||||
));
|
||||
|
||||
if ($this->rootPrefix) {
|
||||
$path .= ltrim($this->rootPrefix, '/') . '/';
|
||||
}
|
||||
|
||||
// Actualizar uploads array
|
||||
$uploads['path'] = $path;
|
||||
$uploads['url'] = rtrim($this->baseUrl, '/') . '/';
|
||||
$uploads['subdir'] = '';
|
||||
$uploads['basedir'] = $path;
|
||||
|
||||
error_log('[MediaHooks] Upload dir filtered - path: ' . $path . ', url: ' . $uploads['url'] . ', subdir: ' . $uploads['subdir']);
|
||||
|
||||
return $uploads;
|
||||
}
|
||||
|
||||
public function rewriteAttachmentUrl(string $url, int $attachmentId): string {
|
||||
/**
|
||||
* Filtra la URL de un attachment para usar la URL remota
|
||||
*/
|
||||
public function filterAttachmentUrl(string $url, int $attachmentId): string
|
||||
{
|
||||
// Obtener metadata del attachment
|
||||
$metadata = wp_get_attachment_metadata($attachmentId);
|
||||
if (!$metadata) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
// Obtener el path del archivo
|
||||
$file = get_post_meta($attachmentId, '_wp_attached_file', true);
|
||||
if (! $file) {
|
||||
if (!$file) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
// Construir URL remota
|
||||
$remoteUrl = rtrim($this->baseUrl, '/') . '/' . ltrim($file, '/');
|
||||
error_log('[MediaHooks] Attachment URL filtered - local: ' . $url . ', remote: ' . $remoteUrl);
|
||||
|
||||
return $remoteUrl;
|
||||
}
|
||||
|
||||
$relativePath = PathHelper::normalize($file);
|
||||
if ($relativePath === '') {
|
||||
return $url;
|
||||
/**
|
||||
* Filtra el srcset de imágenes para usar URLs remotas
|
||||
*/
|
||||
public function filterImageSrcset(array $sources, array $sizeArray, string $imageSrc, array $imageMeta, int $attachmentId): array
|
||||
{
|
||||
if (empty($sources)) {
|
||||
return $sources;
|
||||
}
|
||||
|
||||
$remoteUrl = $this->buildPublicUrl($relativePath);
|
||||
|
||||
if (! $this->preferLocal) {
|
||||
return $remoteUrl;
|
||||
|
||||
// Obtener el path base del archivo
|
||||
$file = get_post_meta($attachmentId, '_wp_attached_file', true);
|
||||
if (!$file) {
|
||||
return $sources;
|
||||
}
|
||||
|
||||
try {
|
||||
if ($this->filesystem->fileExists($this->toRemotePath($relativePath))) {
|
||||
return $remoteUrl;
|
||||
|
||||
// Calcular el directorio base
|
||||
$basePath = dirname($file);
|
||||
if ($basePath === '.') {
|
||||
$basePath = '';
|
||||
}
|
||||
|
||||
// Actualizar cada source con URL remota
|
||||
foreach ($sources as &$source) {
|
||||
if (isset($source['url'])) {
|
||||
$filename = basename($source['url']);
|
||||
if ($basePath) {
|
||||
$remotePath = $basePath . '/' . $filename;
|
||||
} else {
|
||||
$remotePath = $filename;
|
||||
}
|
||||
$source['url'] = rtrim($this->baseUrl, '/') . '/' . ltrim($remotePath, '/');
|
||||
}
|
||||
} catch (\Throwable $exception) {
|
||||
error_log(sprintf(
|
||||
'[Flysystem Offload] No se pudo verificar la existencia remota de "%s": %s',
|
||||
$relativePath,
|
||||
$exception->getMessage()
|
||||
));
|
||||
}
|
||||
|
||||
return $url;
|
||||
|
||||
return $sources;
|
||||
}
|
||||
|
||||
public function filterImageDownsize(bool|array $out, int $attachmentId, array|string $size): bool|array {
|
||||
return false;
|
||||
/**
|
||||
* Maneja la generación de metadata de attachments
|
||||
*/
|
||||
public function handleAttachmentMetadata(array $metadata, int $attachmentId): array
|
||||
{
|
||||
error_log('[MediaHooks] Handling attachment metadata for ID: ' . $attachmentId);
|
||||
|
||||
// Verificar si se generaron tamaños
|
||||
if (isset($metadata['sizes']) && is_array($metadata['sizes'])) {
|
||||
error_log('[MediaHooks] Generated sizes: ' . print_r(array_keys($metadata['sizes']), true));
|
||||
|
||||
// Para cada tamaño generado, asegurarse de que se suba al sistema remoto
|
||||
foreach ($metadata['sizes'] as $sizeName => $sizeData) {
|
||||
if (isset($sizeData['file'])) {
|
||||
$this->ensureSizeUploaded($attachmentId, $sizeData['file']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
public function deleteRemoteFiles(int $attachmentId): void {
|
||||
if (! $this->deleteRemote) {
|
||||
/**
|
||||
* Asegura que un tamaño específico se haya subido
|
||||
*/
|
||||
private function ensureSizeUploaded(int $attachmentId, string $filename): void
|
||||
{
|
||||
// Obtener el directorio de uploads local
|
||||
$uploadDir = wp_upload_dir();
|
||||
$localPath = $uploadDir['basedir'] . '/' . $filename;
|
||||
|
||||
// Verificar si el archivo local existe
|
||||
if (!file_exists($localPath)) {
|
||||
error_log('[MediaHooks] Local size file not found: ' . $localPath);
|
||||
return;
|
||||
}
|
||||
|
||||
// Construir el path remoto
|
||||
$remotePath = $this->protocol . ':///' . $filename;
|
||||
|
||||
// Copiar el archivo al sistema remoto
|
||||
if (copy($localPath, $remotePath)) {
|
||||
error_log('[MediaHooks] Size uploaded successfully: ' . $filename);
|
||||
} else {
|
||||
error_log('[MediaHooks] Failed to upload size: ' . $filename);
|
||||
}
|
||||
}
|
||||
|
||||
$files = $this->gatherAttachmentFiles($attachmentId);
|
||||
|
||||
foreach ($files as $file) {
|
||||
$key = $this->toRemotePath($file);
|
||||
|
||||
try {
|
||||
if ($this->filesystem->fileExists($key)) {
|
||||
$this->filesystem->delete($key);
|
||||
/**
|
||||
* Maneja la eliminación de attachments
|
||||
*/
|
||||
public function handleDeleteAttachment(int $attachmentId): void
|
||||
{
|
||||
error_log('[MediaHooks] Handling delete attachment: ' . $attachmentId);
|
||||
|
||||
// Obtener el archivo principal
|
||||
$file = get_post_meta($attachmentId, '_wp_attached_file', true);
|
||||
if ($file) {
|
||||
$this->deleteRemoteFile($file);
|
||||
}
|
||||
|
||||
// Obtener metadata para eliminar tamaños
|
||||
$metadata = wp_get_attachment_metadata($attachmentId);
|
||||
if ($metadata && isset($metadata['sizes'])) {
|
||||
foreach ($metadata['sizes'] as $sizeData) {
|
||||
if (isset($sizeData['file'])) {
|
||||
$this->deleteRemoteFile($sizeData['file']);
|
||||
}
|
||||
} catch (\Throwable $exception) {
|
||||
error_log(sprintf(
|
||||
'[Flysystem Offload] No se pudo eliminar el archivo remoto "%s": %s',
|
||||
$key,
|
||||
$exception->getMessage()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
* Elimina un archivo remoto
|
||||
*/
|
||||
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'];
|
||||
}
|
||||
}
|
||||
private function deleteRemoteFile(string $filename): void
|
||||
{
|
||||
$remotePath = $this->protocol . ':///' . ltrim($filename, '/');
|
||||
|
||||
if (file_exists($remotePath)) {
|
||||
if (unlink($remotePath)) {
|
||||
error_log('[MediaHooks] Remote file deleted: ' . $filename);
|
||||
} else {
|
||||
error_log('[MediaHooks] Failed to delete remote file: ' . $filename);
|
||||
}
|
||||
} else {
|
||||
error_log('[MediaHooks] Remote file not found for deletion: ' . $filename);
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convierte una ruta relativa de WordPress a la ruta remota en el filesystem
|
||||
*
|
||||
* Para WebDAV con prefix, solo usa el archivo relativo
|
||||
* Para S3 u otros, puede incluir streamRootPrefix y streamHost
|
||||
* Filtra el path del archivo adjunto
|
||||
*/
|
||||
private function toRemotePath(string $file): string {
|
||||
$segments = [];
|
||||
|
||||
// ✅ Si hay providerPrefix (WebDAV, S3, etc.), NO agregar streamRootPrefix ni streamHost
|
||||
// El prefix del provider ya incluye la ruta base completa
|
||||
if ($this->providerPrefix === '') {
|
||||
// Solo para providers sin prefix (local, etc.)
|
||||
if ($this->streamRootPrefix !== '') {
|
||||
$segments[] = $this->streamRootPrefix;
|
||||
}
|
||||
|
||||
if ($this->streamHost !== '') {
|
||||
$segments[] = $this->streamHost;
|
||||
}
|
||||
}
|
||||
|
||||
// Agregar el archivo relativo
|
||||
$segments[] = $file;
|
||||
|
||||
$remotePath = PathHelper::join(...$segments);
|
||||
|
||||
error_log(sprintf(
|
||||
'[MediaHooks] toRemotePath - file: %s, provider_prefix: %s, remote: %s',
|
||||
$file,
|
||||
$this->providerPrefix,
|
||||
$remotePath
|
||||
));
|
||||
|
||||
return $remotePath;
|
||||
}
|
||||
|
||||
private function normaliseBaseUrl(string $baseUrl): string {
|
||||
$baseUrl = trim($baseUrl);
|
||||
if ($baseUrl === '') {
|
||||
$baseUrl = content_url('uploads');
|
||||
}
|
||||
|
||||
return rtrim($baseUrl, '/');
|
||||
}
|
||||
|
||||
private function buildPublicUrl(string $relativePath): string {
|
||||
$base = rtrim($this->effectiveBaseUrl, '/');
|
||||
|
||||
if ($relativePath === '') {
|
||||
return $base;
|
||||
}
|
||||
|
||||
return $base . '/' . PathHelper::normalize($relativePath);
|
||||
public function filterAttachedFile(string $file, int $attachmentId): string
|
||||
{
|
||||
// Devolver el path con el protocolo correcto
|
||||
$filename = basename($file);
|
||||
return $this->protocol . ':///' . $filename;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,9 +57,9 @@ class Plugin
|
|||
|
||||
error_log('[Flysystem Offload] Stream wrappers: ' . implode(', ', stream_get_wrappers()));
|
||||
|
||||
// Registrar hooks de medios
|
||||
self::$mediaHooks = new MediaHooks(self::$filesystem, self::$config);
|
||||
self::$mediaHooks->register();
|
||||
// Registrar hooks de medios - pasar primero $config, luego $filesystem
|
||||
self::$mediaHooks = new MediaHooks(self::$config, self::$filesystem);
|
||||
self::$mediaHooks->registerHooks(); // <-- CORRECCIÓN: llamar al método que existe
|
||||
|
||||
// Registrar página de ajustes
|
||||
if (is_admin()) {
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ use League\Flysystem\UnableToWriteFile;
|
|||
|
||||
/**
|
||||
* Stream wrapper para Flysystem
|
||||
* Soporta protocolo configurable (por defecto 'flysystem')
|
||||
* Soporta protocolo configurable (por defecto 'fly')
|
||||
*/
|
||||
class FlysystemStreamWrapper
|
||||
{
|
||||
private static ?FilesystemOperator $filesystem = null;
|
||||
private static string $protocol = 'flysystem';
|
||||
private static string $protocol = 'fly';
|
||||
|
||||
/** @var resource|null */
|
||||
private $stream;
|
||||
|
|
@ -22,12 +22,6 @@ class FlysystemStreamWrapper
|
|||
/** @var string Ruta remota normalizada (sin protocolo ni host) */
|
||||
private string $path = '';
|
||||
|
||||
/** @var string Buffer en memoria para modo write */
|
||||
private string $buffer = '';
|
||||
|
||||
/** @var int Posición del puntero */
|
||||
private int $position = 0;
|
||||
|
||||
/** @var string Modo de apertura */
|
||||
private string $mode = '';
|
||||
|
||||
|
|
@ -37,7 +31,7 @@ class FlysystemStreamWrapper
|
|||
/**
|
||||
* Registra el stream wrapper
|
||||
*/
|
||||
public static function register(FilesystemOperator $filesystem, string $protocol = 'flysystem'): void
|
||||
public static function register(FilesystemOperator $filesystem, string $protocol = 'fly'): void
|
||||
{
|
||||
self::$filesystem = $filesystem;
|
||||
self::$protocol = $protocol;
|
||||
|
|
@ -57,24 +51,33 @@ class FlysystemStreamWrapper
|
|||
|
||||
/**
|
||||
* Normaliza ruta removiendo protocolo y host
|
||||
* Ejemplo: flysystem://uploads/2025/11/file.jpg -> 2025/11/file.jpg
|
||||
* Ejemplo: fly://uploads/2025/11/file.jpg -> uploads/2025/11/file.jpg
|
||||
*/
|
||||
private static function normalizePath(string $path): string
|
||||
{
|
||||
// Remover protocolo
|
||||
$p = preg_replace('#^' . preg_quote(self::$protocol, '#') . '://#', '', $path) ?? '';
|
||||
|
||||
// Remover host si existe (primer segmento después de //)
|
||||
// Ejemplo: flysystem://uploads/2025/11/file.jpg -> 2025/11/file.jpg
|
||||
$parts = explode('/', ltrim($p, '/'), 2);
|
||||
if (count($parts) === 2) {
|
||||
// Si hay host, devolver solo la parte después del host
|
||||
return $parts[1];
|
||||
// Remover cualquier host si existe (primer segmento después de //)
|
||||
if (strpos($p, '/') !== false) {
|
||||
$parts = explode('/', $p, 2);
|
||||
if (count($parts) === 2) {
|
||||
return $parts[1];
|
||||
}
|
||||
}
|
||||
|
||||
return ltrim($p, '/');
|
||||
return $p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens file or URL
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $mode
|
||||
* @param int $options
|
||||
* @param string|null $opened_path
|
||||
* @return bool
|
||||
*/
|
||||
public function stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool
|
||||
{
|
||||
if (!self::$filesystem) {
|
||||
|
|
@ -84,137 +87,325 @@ class FlysystemStreamWrapper
|
|||
|
||||
$this->path = self::normalizePath($path);
|
||||
$this->mode = $mode;
|
||||
$this->buffer = '';
|
||||
$this->position = 0;
|
||||
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_open path="%s" normalized="%s" mode="%s"', $path, $this->path, $mode));
|
||||
|
||||
// Para lectura, intentar cargar contenido existente
|
||||
if (str_contains($mode, 'r') || str_contains($mode, '+')) {
|
||||
try {
|
||||
$this->buffer = self::$filesystem->read($this->path);
|
||||
error_log('[FlysystemStreamWrapper] Loaded existing file (' . strlen($this->buffer) . ' bytes)');
|
||||
} catch (\Throwable $e) {
|
||||
// Si no existe y es modo 'r' puro, fallar
|
||||
if ($mode === 'r') {
|
||||
error_log('[FlysystemStreamWrapper] stream_open read error: ' . $e->getMessage());
|
||||
try {
|
||||
// Modo lectura
|
||||
if (strpos($mode, 'r') !== false) {
|
||||
// Verificar si el archivo existe
|
||||
if (!self::$filesystem->fileExists($this->path)) {
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_open error: file does not exist "%s"', $this->path));
|
||||
return false;
|
||||
}
|
||||
// Para otros modos (w, a, etc.) continuar con buffer vacío
|
||||
|
||||
// Leer el contenido del archivo desde el filesystem
|
||||
$contents = self::$filesystem->read($this->path);
|
||||
|
||||
// Crear un stream temporal en memoria
|
||||
$this->stream = fopen('php://temp', 'r+b');
|
||||
|
||||
if ($this->stream === false) {
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_open error: failed to create temp stream for "%s"', $this->path));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Escribir el contenido en el stream temporal
|
||||
fwrite($this->stream, $contents);
|
||||
|
||||
// Volver al inicio del stream
|
||||
rewind($this->stream);
|
||||
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_open read mode: loaded %d bytes for "%s"', strlen($contents), $this->path));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// En modo append, posicionar al final
|
||||
if (str_starts_with($mode, 'a')) {
|
||||
$this->position = strlen($this->buffer);
|
||||
}
|
||||
// Modo escritura
|
||||
if (strpos($mode, 'w') !== false || strpos($mode, 'a') !== false || strpos($mode, 'x') !== false || strpos($mode, 'c') !== false) {
|
||||
// Crear un stream temporal en memoria para escritura
|
||||
$this->stream = fopen('php://temp', 'r+b');
|
||||
|
||||
if ($this->stream === false) {
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_open error: failed to create temp stream for "%s"', $this->path));
|
||||
return false;
|
||||
}
|
||||
|
||||
// En modo append, posicionar al final
|
||||
if (strpos($mode, 'a') !== false) {
|
||||
// Si el archivo existe, cargar su contenido
|
||||
if (self::$filesystem->fileExists($this->path)) {
|
||||
$contents = self::$filesystem->read($this->path);
|
||||
fwrite($this->stream, $contents);
|
||||
}
|
||||
fseek($this->stream, 0, SEEK_END);
|
||||
}
|
||||
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_open write mode for "%s"', $this->path));
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function stream_write(string $data): int
|
||||
{
|
||||
$len = strlen($data);
|
||||
|
||||
// Insertar data en la posición actual
|
||||
$before = substr($this->buffer, 0, $this->position);
|
||||
$after = substr($this->buffer, $this->position);
|
||||
|
||||
$this->buffer = $before . $data . $after;
|
||||
$this->position += $len;
|
||||
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_write %d bytes (buffer now %d bytes)', $len, strlen($this->buffer)));
|
||||
|
||||
return $len;
|
||||
}
|
||||
|
||||
public function stream_read(int $count): string
|
||||
{
|
||||
$chunk = substr($this->buffer, $this->position, $count);
|
||||
$this->position += strlen($chunk);
|
||||
return $chunk;
|
||||
}
|
||||
|
||||
public function stream_tell(): int
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stream_eof(): bool
|
||||
{
|
||||
return $this->position >= strlen($this->buffer);
|
||||
}
|
||||
|
||||
public function stream_seek(int $offset, int $whence = SEEK_SET): bool
|
||||
{
|
||||
$newPos = $this->position;
|
||||
switch ($whence) {
|
||||
case SEEK_SET:
|
||||
$newPos = $offset;
|
||||
break;
|
||||
case SEEK_CUR:
|
||||
$newPos += $offset;
|
||||
break;
|
||||
case SEEK_END:
|
||||
$newPos = strlen($this->buffer) + $offset;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($newPos < 0) {
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_open error: unsupported mode "%s" for "%s"', $mode, $this->path));
|
||||
return false;
|
||||
} catch (\Exception $e) {
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_open exception: %s', $e->getMessage()));
|
||||
return false;
|
||||
}
|
||||
$this->position = $newPos;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function stream_flush(): bool
|
||||
/**
|
||||
* Read from stream
|
||||
*
|
||||
* @param int $count
|
||||
* @return string|false
|
||||
*/
|
||||
public function stream_read(int $count)
|
||||
{
|
||||
// Persistir buffer en remoto
|
||||
if ($this->buffer === '') {
|
||||
return true;
|
||||
if (!$this->stream) {
|
||||
error_log('[FlysystemStreamWrapper] stream_read error: no stream resource');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
error_log('[FlysystemStreamWrapper] stream_flush -> write to "' . $this->path . '" (' . strlen($this->buffer) . ' bytes)');
|
||||
self::$filesystem->write($this->path, $this->buffer);
|
||||
return true;
|
||||
} catch (UnableToWriteFile $e) {
|
||||
error_log('[FlysystemStreamWrapper] stream_flush UnableToWriteFile: ' . $e->getMessage());
|
||||
return false;
|
||||
} catch (\Throwable $e) {
|
||||
error_log('[FlysystemStreamWrapper] stream_flush error: ' . $e->getMessage());
|
||||
$data = fread($this->stream, $count);
|
||||
|
||||
if ($data === false) {
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_read error for "%s"', $this->path));
|
||||
return false;
|
||||
}
|
||||
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_read %d bytes from "%s"', strlen($data), $this->path));
|
||||
return $data;
|
||||
} catch (\Exception $e) {
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_read exception: %s', $e->getMessage()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function stream_close(): void
|
||||
/**
|
||||
* Write to stream
|
||||
*
|
||||
* @param string $data
|
||||
* @return int|false
|
||||
*/
|
||||
public function stream_write(string $data)
|
||||
{
|
||||
// Flush final en close
|
||||
if ($this->buffer !== '' && str_contains($this->mode, 'w') || str_contains($this->mode, 'a') || str_contains($this->mode, '+')) {
|
||||
try {
|
||||
error_log('[FlysystemStreamWrapper] stream_close -> persisting "' . $this->path . '"');
|
||||
self::$filesystem->write($this->path, $this->buffer);
|
||||
} catch (\Throwable $e) {
|
||||
error_log('[FlysystemStreamWrapper] stream_close error: ' . $e->getMessage());
|
||||
if (!$this->stream) {
|
||||
error_log('[FlysystemStreamWrapper] stream_write error: no stream resource');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$bytesWritten = fwrite($this->stream, $data);
|
||||
|
||||
if ($bytesWritten === false) {
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_write error for "%s"', $this->path));
|
||||
return false;
|
||||
}
|
||||
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_write %d bytes to "%s"', $bytesWritten, $this->path));
|
||||
return $bytesWritten;
|
||||
} catch (\Exception $e) {
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_write exception: %s', $e->getMessage()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for end-of-file on a file pointer
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function stream_eof(): bool
|
||||
{
|
||||
if (!$this->stream) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->buffer = '';
|
||||
$this->position = 0;
|
||||
return feof($this->stream);
|
||||
}
|
||||
|
||||
public function stream_stat(): array
|
||||
/**
|
||||
* Retrieve the current position of a stream
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function stream_tell(): int
|
||||
{
|
||||
return $this->getStatArray();
|
||||
if (!$this->stream) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$position = ftell($this->stream);
|
||||
return $position !== false ? $position : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeks to specific location in a stream
|
||||
*
|
||||
* @param int $offset
|
||||
* @param int $whence
|
||||
* @return bool
|
||||
*/
|
||||
public function stream_seek(int $offset, int $whence = SEEK_SET): bool
|
||||
{
|
||||
if (!$this->stream) {
|
||||
error_log('[FlysystemStreamWrapper] stream_seek error: no stream resource');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$result = fseek($this->stream, $offset, $whence);
|
||||
|
||||
if ($result === 0) {
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_seek to %d (whence: %d) for "%s"', $offset, $whence, $this->path));
|
||||
return true;
|
||||
}
|
||||
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_seek failed for "%s"', $this->path));
|
||||
return false;
|
||||
} catch (\Exception $e) {
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_seek exception: %s', $e->getMessage()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve information about a file resource
|
||||
*
|
||||
* @return array|false
|
||||
*/
|
||||
public function stream_stat()
|
||||
{
|
||||
if (!$this->stream) {
|
||||
error_log('[FlysystemStreamWrapper] stream_stat error: no stream resource');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$stat = fstat($this->stream);
|
||||
|
||||
if ($stat === false) {
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_stat failed for "%s"', $this->path));
|
||||
return false;
|
||||
}
|
||||
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_stat for "%s": size=%d', $this->path, $stat['size'] ?? 0));
|
||||
return $stat;
|
||||
} catch (\Exception $e) {
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_stat exception: %s', $e->getMessage()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes the output
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function stream_flush(): bool
|
||||
{
|
||||
if (!$this->stream) {
|
||||
error_log('[FlysystemStreamWrapper] stream_flush error: no stream resource');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Forzar escritura del buffer interno de PHP
|
||||
fflush($this->stream);
|
||||
|
||||
// Obtener la posición actual antes de rebobinar
|
||||
$pos = ftell($this->stream);
|
||||
|
||||
// Rebobinar para leer todo el contenido
|
||||
rewind($this->stream);
|
||||
$contents = stream_get_contents($this->stream);
|
||||
|
||||
// Restaurar posición original
|
||||
if ($pos !== false) {
|
||||
fseek($this->stream, $pos);
|
||||
}
|
||||
|
||||
if ($contents === false) {
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_flush error reading contents for "%s"', $this->path));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Escribir en el sistema de archivos
|
||||
self::$filesystem->write($this->path, $contents);
|
||||
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_flush wrote %d bytes to "%s"', strlen($contents), $this->path));
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_flush exception: %s', $e->getMessage()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close a resource
|
||||
*/
|
||||
public function stream_close(): void
|
||||
{
|
||||
if ($this->stream) {
|
||||
try {
|
||||
// Obtener la posición actual antes de rebobinar
|
||||
$pos = ftell($this->stream);
|
||||
|
||||
// Rebobinar para leer todo el contenido
|
||||
rewind($this->stream);
|
||||
$contents = stream_get_contents($this->stream);
|
||||
|
||||
// Solo escribir si hay contenido y el modo permite escritura
|
||||
if ($contents !== false && strlen($contents) > 0 &&
|
||||
(strpos($this->mode, 'w') !== false ||
|
||||
strpos($this->mode, 'a') !== false ||
|
||||
strpos($this->mode, 'x') !== false ||
|
||||
strpos($this->mode, 'c') !== false ||
|
||||
strpos($this->mode, '+') !== false)) {
|
||||
|
||||
// Escribir en el sistema de archivos
|
||||
self::$filesystem->write($this->path, $contents);
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_close wrote %d bytes to "%s"', strlen($contents), $this->path));
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
error_log(sprintf('[FlysystemStreamWrapper] stream_close exception: %s', $e->getMessage()));
|
||||
}
|
||||
|
||||
fclose($this->stream);
|
||||
$this->stream = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve information about a file
|
||||
*
|
||||
* @param string $path
|
||||
* @param int $flags
|
||||
* @return array|false
|
||||
*/
|
||||
public function url_stat(string $path, int $flags)
|
||||
{
|
||||
$p = self::normalizePath($path);
|
||||
|
||||
try {
|
||||
$size = self::$filesystem->fileSize($p);
|
||||
return $this->getStatArray($size);
|
||||
} catch (\Throwable $e) {
|
||||
// Verificar si es un directorio
|
||||
if (self::$filesystem->directoryExists($p)) {
|
||||
return $this->getStatArray(0, true);
|
||||
}
|
||||
|
||||
// Verificar si es un archivo
|
||||
if (self::$filesystem->fileExists($p)) {
|
||||
$size = self::$filesystem->fileSize($p);
|
||||
return $this->getStatArray($size, false);
|
||||
}
|
||||
|
||||
// No existe
|
||||
if ($flags & STREAM_URL_STAT_QUIET) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
} catch (\Exception $e) {
|
||||
error_log(sprintf('[FlysystemStreamWrapper] url_stat exception for "%s": %s', $p, $e->getMessage()));
|
||||
if ($flags & STREAM_URL_STAT_QUIET) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -222,73 +413,114 @@ class FlysystemStreamWrapper
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a file
|
||||
*
|
||||
* @param string $path
|
||||
* @return bool
|
||||
*/
|
||||
public function unlink(string $path): bool
|
||||
{
|
||||
$p = self::normalizePath($path);
|
||||
|
||||
try {
|
||||
error_log('[FlysystemStreamWrapper] unlink "' . $p . '"');
|
||||
self::$filesystem->delete($p);
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
} catch (\Exception $e) {
|
||||
error_log('[FlysystemStreamWrapper] unlink error: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a directory
|
||||
*
|
||||
* @param string $path
|
||||
* @param int $mode
|
||||
* @param int $options
|
||||
* @return bool
|
||||
*/
|
||||
public function mkdir(string $path, int $mode, int $options): bool
|
||||
{
|
||||
$p = self::normalizePath($path);
|
||||
|
||||
try {
|
||||
error_log('[FlysystemStreamWrapper] mkdir "' . $p . '"');
|
||||
self::$filesystem->createDirectory($p);
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
} catch (\Exception $e) {
|
||||
error_log('[FlysystemStreamWrapper] mkdir error: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a directory
|
||||
*
|
||||
* @param string $path
|
||||
* @param int $options
|
||||
* @return bool
|
||||
*/
|
||||
public function rmdir(string $path, int $options): bool
|
||||
{
|
||||
$p = self::normalizePath($path);
|
||||
|
||||
try {
|
||||
error_log('[FlysystemStreamWrapper] rmdir "' . $p . '"');
|
||||
self::$filesystem->deleteDirectory($p);
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
} catch (\Exception $e) {
|
||||
error_log('[FlysystemStreamWrapper] rmdir error: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename a file or directory
|
||||
*
|
||||
* @param string $path_from
|
||||
* @param string $path_to
|
||||
* @return bool
|
||||
*/
|
||||
public function rename(string $path_from, string $path_to): bool
|
||||
{
|
||||
$from = self::normalizePath($path_from);
|
||||
$to = self::normalizePath($path_to);
|
||||
|
||||
try {
|
||||
error_log('[FlysystemStreamWrapper] rename "' . $from . '" -> "' . $to . '"');
|
||||
self::$filesystem->move($from, $to);
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
} catch (\Exception $e) {
|
||||
error_log('[FlysystemStreamWrapper] rename error: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function getStatArray(int $size = 0): array
|
||||
/**
|
||||
* Genera array stat para archivos y directorios
|
||||
*
|
||||
* @param int $size Tamaño del archivo
|
||||
* @param bool $isDir Si es directorio
|
||||
* @return array
|
||||
*/
|
||||
private function getStatArray(int $size = 0, bool $isDir = false): array
|
||||
{
|
||||
$mode = $isDir ? 0040777 : 0100666; // Directorio o archivo regular
|
||||
|
||||
return [
|
||||
0 => 0, 'dev' => 0,
|
||||
1 => 0, 'ino' => 0,
|
||||
2 => 0100666, 'mode' => 0100666,
|
||||
2 => $mode, 'mode' => $mode,
|
||||
3 => 0, 'nlink' => 0,
|
||||
4 => 0, 'uid' => 0,
|
||||
5 => 0, 'gid' => 0,
|
||||
6 => -1, 'rdev' => -1,
|
||||
7 => $size, 'size' => $size,
|
||||
8 => 0, 'atime' => 0,
|
||||
9 => 0, 'mtime' => 0,
|
||||
10 => 0, 'ctime' => 0,
|
||||
8 => time(), 'atime' => time(),
|
||||
9 => time(), 'mtime' => time(),
|
||||
10 => time(), 'ctime' => time(),
|
||||
11 => -1, 'blksize' => -1,
|
||||
12 => -1, 'blocks' => -1,
|
||||
];
|
||||
|
|
|
|||
Loading…
Reference in New Issue