From f859e64a57c624ff2daa2b21355f65a49fd4807d Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sat, 29 Aug 2026 03:09:12 +0000
Subject: [PATCH] =Some more tweaks. Mostly on the MenuBlock and adding the Meta.php to WooCommerce Products
---
inc/managers/UploadManager.php | 651 ++++++++++++++++++++++++++++++----------------------------
1 files changed, 339 insertions(+), 312 deletions(-)
diff --git a/inc/managers/UploadManager.php b/inc/managers/UploadManager.php
index 00bc05f..4889dbc 100644
--- a/inc/managers/UploadManager.php
+++ b/inc/managers/UploadManager.php
@@ -1,9 +1,14 @@
<?php
namespace JVBase\managers;
-use JVBase\JVB;
+use JVBase\base\Site;
+use JVBase\Instance;
use Exception;
use Imagick;
+use JVBase\managers\queue\executors\UploadExecutor;
+use JVBase\managers\queue\mergers\UploadMerger;
+use JVBase\managers\queue\Queue;
+use JVBase\managers\queue\TypeConfig;
use WP_Error;
if (!defined('ABSPATH')) {
@@ -11,11 +16,32 @@
}
/**
- * Flexible file upload manager for images, videos, and documents
- * Supports configurable directory structures and processing options
+ * The goal here was mainly for membership sites, to organize files by userID, rather than the date structure.
+ * This way, if a user deletes their account, or performs a data request, we can simply copy their directory.
+ *
+ * I started with user name so we could search by name, but issues arose if a user changed their name/display name. So I set it to go to user_id.
+ *
+ * The general flow is:
+ * -> Upload, ensure files are stored in appropriate directory, usually {root}/tmp
+ * -> Conversion is queued with Queue, as with the /tmp cleanup
+ *
+ * Proposed directory structure:
+ * Site::has('membership') || Site::has('is_directory')
+ * -> defaults to {user_id}/{post_type}/file
+ * Default:
+ * -> defaults to {post_type}/file
+ * Drawback: if a post is uploaded to one post type, then used in another, the organization is slightly flawed.
+ *
+ * Also, we wanted to ensure images were converted to webp, for speed.
+ * -> The front-end javascript attempts to do this in browser
+ * -> If this couldn't be handled, it is queued for server processing
+ * Of course, some integrations (even google, sillies) only accept jpeg/png, so in some cases we need a jpeg fallback
+ *
+ *
*/
class UploadManager
{
+ use Instance;
protected array $allowedTypes = [
// Images
'image/jpeg' => 'image',
@@ -73,16 +99,179 @@
'original_retention' => 2592000, // 30 days
];
+ /******************************************************************
+ * UPLOAD SETTINGS
+ ******************************************************************/
+ /**
+ * @var array|int[] Max size of file types
+ */
+ protected array $maxSize = [
+ 'image' => 5242880, // 5MB
+ 'video' => 104857600, // 100MB
+ 'document' => 15728640 // 15MB
+ ];
+ //Image Specific
+ /**
+ * @var bool Whether to convert the image
+ */
+ protected bool $convertImage = true;
+ /**
+ * @var string Default image conversion
+ */
+ protected string $convertImageTo = 'webp';
+ /**
+ * @var int Default image quality
+ */
+ protected int $convertQuality = 80;
+ /**
+ * @var bool Whether to optimize images
+ */
+ protected bool $optimizeImages = true;
+ /**
+ * @var bool Whether to create image thumbnails
+ */
+ protected bool $createThumbnails = true;
+ //Video specific
+ /**
+ * @var bool Whether to automatically extract a thumbnail from a video
+ */
+ protected bool $extractVideoThumbnail = true;
+ /**
+ * @var int Time, in microseconds, to grab thumbnail from
+ */
+ protected int $videoThumbnailPosition = 0;
+ //Document specific
+ /**
+ * @var bool Whether to extract thumbnail from document.
+ */
+ protected bool $extractDocumentThumbnail = false;
+ /**
+ * @var int How long to keep originals (default 3 months)
+ */
+ protected int $keep_originals;
+ /******************************************************************
+ * UPLOAD DIRECTORIES
+ ******************************************************************/
+ /**
+ * @var string WordPress' base upload directory
+ */
protected string $upload_dir;
+ /**
+ * @var string WordPress' base upload directory url
+ */
protected string $upload_url;
- public function __construct()
+ protected string $dir_pattern;
+ /*****************************************************************
+ * FEATURE FLAG
+ *****************************************************************/
+ protected bool $has_imagick = false;
+ private function __construct()
{
$upload = wp_upload_dir();
$this->upload_dir = $upload['basedir'];
$this->upload_url = $upload['baseurl'];
- // Check for Imagick
- $this->defaultConfig['use_imagick'] = extension_loaded('imagick');
+ if (Site::has('membership') || Site::has('is_directory')) {
+ $this->dir_pattern = '{user_id}/{post_type}';
+ } else {
+ $this->dir_pattern = '{post_type}';
+ }
+ $this->has_imagick = extension_loaded('imagick');
+ $this->keep_originals = MONTH_IN_SECONDS * 3;
+
+ add_action('init', [$this, 'registerUploadExecutors'], 5);
+ }
+
+ /**
+ * Register upload operation types with the queue's TypeRegistry
+ */
+ public function registerUploadExecutors(): void
+ {
+ $registry = Queue::getInstance()->registry();
+ $executor = new UploadExecutor();
+ $merger = new UploadMerger('secured_files');
+
+ // Image uploads - chunked at 5 files
+ $registry->register('image_upload', new TypeConfig(
+ mergeable: $merger,
+ executor: $executor,
+ chunkKey: 'secured_files',
+ chunkSize: 3
+ ));
+
+ // Video uploads - one at a time (heavy processing)
+ $registry->register('video_upload', new TypeConfig(
+ mergeable: $merger,
+ executor: $executor,
+ chunkKey: 'secured_files',
+ chunkSize: 1
+ ));
+
+ // Document uploads - chunked at 10
+ $registry->register('document_upload', new TypeConfig(
+ mergeable: $merger,
+ executor: $executor,
+ chunkKey: 'secured_files',
+ chunkSize: 5
+ ));
+
+ // Metadata updates
+ $registry->register('update_image_meta', new TypeConfig(
+ executor: $executor
+ ));
+
+ // Cleanup - chunked at 5
+ $registry->register('temporary_cleanup', new TypeConfig(
+ executor: $executor,
+ chunkKey: 'files',
+ chunkSize: 5
+ ));
+
+ // Attach to content (depends on upload completing)
+ $registry->register('attach_upload_to_content', new TypeConfig(
+ executor: $executor
+ ));
+
+ // Process upload groups into posts
+ $registry->register('process_upload_groups', new TypeConfig(
+ executor: $executor,
+ chunkKey: 'posts',
+ chunkSize: 5
+ ));
+ }
+
+ /**
+ * @param string|array $file_path
+ * @param array $context
+ * @return ?string
+ * @throws Exception
+ */
+ protected function testFile(string|array $file_path, array $context):?string
+ {
+ $file_type = null;
+ if (is_array($file_path)) {
+ if (!isset($file_path['tmp_name']) || $file_path['error'] !== UPLOAD_ERR_OK) {
+ throw new Exception('Invalid file upload');
+ }
+ $file_path = $file_path['tmp_name'];
+ }
+ // Validate file upload
+ if (!isset($file_path)) {
+ throw new Exception('Invalid file upload');
+ }
+
+ $mime_type = mime_content_type($file_path);
+ $file_type = $this->allowedTypes[$mime_type] ?? 'unknown';
+ if ($file_type === 'unknown') {
+ throw new Exception('Unsupported file type: ' . $mime_type);
+ }
+
+ // Check allowed types if specified
+ if (array_key_exists('allowed_types', $context) && !in_array($mime_type, $context['allowed_types'])) {
+ throw new Exception('File type not allowed: ' . $mime_type);
+ }
+
+ return $file_type;
}
/**
@@ -94,26 +283,10 @@
public function secureUploadedFile(array $file_data, array $context): array
{
try {
- // Validate file upload
- if (!isset($file_data['tmp_name']) || $file_data['error'] !== UPLOAD_ERR_OK) {
- throw new Exception('Invalid file upload');
- }
+ $file_type = $this->testFile($file_data, $context);
- $mime_type = mime_content_type($file_data['tmp_name']);
- $file_type = $this->allowedTypes[$mime_type] ?? 'unknown';
- if ($file_type === 'unknown') {
- throw new Exception('Unsupported file type: ' . $mime_type);
- }
-
- // Check allowed types if specified
- if ($this->defaultConfig['allowed_types'] && !in_array($mime_type, $this->defaultConfig['allowed_types'])) {
- throw new Exception('File type not allowed: ' . $mime_type);
- }
-
- $temp_dir = $this->generateDirectory($file_type, "{user_id}/temp", $context);
-
- // Ensure directories exist
- $this->ensureDirectories($temp_dir);
+ $context['pattern'] = $this->dir_pattern.'/tmp';
+ $temp_dir = $this->generateDirectory($file_type, $context);
// Generate a unique filename for temporary storage
$temp_filename = uniqid() . '_' . sanitize_file_name($file_data['name']);
@@ -129,15 +302,15 @@
// Return metadata about the stored file
return [
- 'temp_path' => $temp_path,
+ 'temp_path' => $temp_path,
'original_name' => $file_data['name'],
- 'mime_type' => $file_data['type'],
- 'file_type' => $file_type,
- 'file_size' => $file_data['size'],
- 'stored_at' => current_time('mysql')
+ 'mime_type' => $file_data['type'],
+ 'file_type' => $file_type,
+ 'file_size' => $file_data['size'],
+ 'stored_at' => current_time('mysql')
];
} catch (Exception $e) {
- JVB()->error()->log('Failed to store uploaded file: ' . print_r($e->getMessage(), true));
+ Errors::getInstance()->log('UploadManager','Failed to store uploaded file: ' . print_r($e->getMessage(), true));
return [];
}
}
@@ -152,60 +325,32 @@
public function processUpload(string $temp_path, array $context): array|WP_Error
{
try {
- // Validate temp file exists
- if (!file_exists($temp_path)) {
- throw new Exception('Temporary file not found: ' . $temp_path);
- }
-
- // Validate MIME type
- $mime_type = mime_content_type($temp_path);
- if (!$this->validateMimeType($temp_path, $mime_type)) {
- throw new Exception('Invalid or potentially dangerous file type');
- }
- $file_type = $this->allowedTypes[$mime_type] ?? 'unknown';
-
- if ($file_type === 'unknown') {
- throw new Exception('Unsupported file type: ' . $mime_type);
- }
-
- // Merge config
- $config = array_merge($this->defaultConfig, $context['config'] ?? [], $context);
+ $file_type = $this->testFile($temp_path, $context);
// Extract context
$user_id = $context['user_id'] ?? get_current_user_id();
$post_id = $context['post_id'] ?? 0;
- // Check allowed types if specified
- if (!empty($config['allowed_types']) && !in_array($mime_type, $config['allowed_types'])) {
- throw new Exception('File type not allowed: ' . $mime_type);
- }
-
// Validate file size
- $this->validateFileSize($temp_path, $file_type, $config);
+ $this->validateFileSize($temp_path, $file_type, $context);
// Generate directory structure for final storage
- $directory = $this->generateDirectory($file_type, $config['directory_pattern'] ?? '{user_id}/{content}/{file_type}', $context);
-
- // Ensure directories exist
- $this->ensureDirectories($directory);
+ $directory = $this->generateDirectory($file_type, $context);
// Generate filename
$original_name = $context['original_name'] ?? basename($temp_path);
$filename = $this->generateFilename($original_name, $context);
- // Store original from temp
- $original_path = $this->storeOriginalFromTemp($temp_path, $directory, $filename);
-
// Process based on file type
return match($file_type) {
- 'image' => $this->processImage($original_path, $directory, $filename, $post_id, $config, $context),
- 'video' => $this->processVideo($original_path, $directory, $filename, $post_id, $config, $context),
- 'document' => $this->processDocument($original_path, $directory, $filename, $post_id, $config, $context),
+ 'image' => $this->processImage($temp_path, $directory, $filename, $post_id, $context),
+ 'video' => $this->processVideo($temp_path, $directory, $filename, $post_id, $context),
+ 'document' => $this->processDocument($temp_path, $directory, $filename, $post_id, $context),
default => throw new Exception('Unknown file type')
};
} catch (Exception $e) {
- JVB()->error()->log(
+ Errors::getInstance()->log(
'[UploadManager]:processUpload',
$e->getMessage(),
['temp_path' => $temp_path, 'context' => $context]
@@ -215,21 +360,6 @@
}
}
- /**
- * Store original file from temp location (not from PHP upload)
- */
- protected function storeOriginalFromTemp(string $temp_path, string $directory, string $filename): string
- {
- $ext = pathinfo($temp_path, PATHINFO_EXTENSION);
- $original_path = "{$this->upload_dir}/{$directory}/originals/{$filename}.{$ext}";
-
- // Copy from temp to final location
- if (!copy($temp_path, $original_path)) {
- throw new Exception('Failed to store original file');
- }
-
- return $original_path;
- }
/**
* Clean up empty temporary directories
@@ -262,24 +392,52 @@
@rmdir($temp_base);
}
}
+
/**
* Generate directory structure based on pattern
+ * @param string $file_type
+ * @param array $config
+ * @return string
+ * @throws Exception
*/
- protected function generateDirectory(string $file_type, string $pattern, array $context): string
+ public function generateDirectory(string $file_type, array $config): string
{
- $replacements = [
- '{user_id}' => $context['user_id'] ?? get_current_user_id(),
- '{post_type}' => $context['content'] ?? '',
- '{content}' => $context['content'] ?? '',
- '{file_type}' => $file_type,
- '{year}' => date('Y'),
- '{month}' => date('m'),
- ];
+ $pattern = array_key_exists('pattern', $config) && is_string($config['pattern']) ? $config['pattern'] : $this->dir_pattern;
- $directory = str_replace(array_keys($replacements), array_values($replacements), $pattern);
+ $pattern = explode('/', $pattern);
+ $parts = [];
+ foreach ($pattern as $part) {
+ $newPart = match($part) {
+ '{user_id}' => $context['user_id'] ?? null,
+ '{post_type}', '{content}' => $context['content'] ?? null,
+ '{tax}', '{taxonomy}' => $context['taxonomy'] ?? null,
+ '{term}' => $context['term'] ?? null,
+ '{file_type}' => $file_type,
+ '{year}' => date('Y'),
+ '{month}' => date('m'),
+ '{day}' => date('d'),
+ default => $part
+ };
+ if (is_null($newPart)) {
+ error_log('[Uploader] Pattern key was not provided for '.$part);
+ }
+ if (str_contains($newPart, '{')) {
+ error_log('[Uploader] Unhandled pattern found: '. $newPart);
+ }
+ $newPart = $this->sanitizeFileName($newPart);
+ $parts[] = $newPart;
+ }
+ //Remove any null values
+ $parts = array_filter($parts);
+
+ $directory = implode('/', $parts);
// Allow filtering
- return apply_filters('jvb_upload_directory', $directory, $file_type, $context);
+ $directory = apply_filters('jvb_upload_directory', $directory, $file_type, $config);
+
+ // Ensure directories exist
+ $this->ensureDirectories($directory);
+ return $directory;
}
/**
@@ -288,8 +446,7 @@
protected function ensureDirectories(string $rel_path): void
{
$dirs = [
- "{$this->upload_dir}/{$rel_path}",
- "{$this->upload_dir}/{$rel_path}/originals"
+ "{$this->upload_dir}/{$rel_path}"
];
foreach ($dirs as $dir) {
@@ -299,28 +456,26 @@
}
}
- /**
- * Store original file
- */
- protected function storeOriginal(string $tmp_file, string $directory, string $filename): string
- {
- $ext = pathinfo($tmp_file, PATHINFO_EXTENSION);
- $original_path = "{$this->upload_dir}/{$directory}/originals/{$filename}.{$ext}";
-
- if (!move_uploaded_file($tmp_file, $original_path)) {
- throw new Exception('Failed to store original file');
- }
-
- return $original_path;
- }
/**
* Validate file size based on type and config
+ * @param string $file_path
+ * @param string $file_type
+ * @param array $config
+ * @return void
+ * @throws Exception
*/
protected function validateFileSize(string $file_path, string $file_type, array $config): void
{
$size = filesize($file_path);
- $max_size = $config['max_size'][$file_type] ?? $this->defaultConfig['max_size'][$file_type];
+ $max_size = $this->maxSize[$file_type];
+ if (array_key_exists('max_size', $config)) {
+ if (is_int($config['max_size'])) {
+ $max_size = $config['max_size'];
+ } else if (is_array($config['max_size']) && array_key_exists($file_type, $config['max_size'])) {
+ $max_size = $config['max_size'][$file_type];
+ }
+ }
if ($size > $max_size) {
$max_mb = round($max_size / 1048576, 2);
@@ -354,7 +509,7 @@
* Process image file
* @throws Exception
*/
- protected function processImage(string $original_path, string $directory, string $filename, int $post_id, array $config, array $context): array
+ protected function processImage(string $original_path, string $directory, string $filename, int $post_id, array $context): array
{
$full_dir = "{$this->upload_dir}/{$directory}";
$original_mime = mime_content_type($original_path);
@@ -363,17 +518,18 @@
$should_convert = false;
$target_format = false;
- if ($config['convert'] && $config['convert'] !== false) {
- $target_format = strtolower($config['convert']);
-
- // Validate format
- if (!isset($this->supportedFormats[$target_format])) {
- throw new Exception("Unsupported conversion format: {$target_format}");
+ if ((array_key_exists('convert', $context) && $context['convert'] !== false) || $this->convertImage) {
+ $target_format = empty($context['convertTo']??'') ? $this->convertImage : $context['convertTo'];
+ if (!empty($target_format)) {
+ // Validate format
+ if (!isset($this->supportedFormats[$target_format])) {
+ error_log('[Uploader] Unsupported conversion format: '.$target_format.'... will not convert');
+ } else {
+ // Check if conversion is needed
+ $target_mime = $this->supportedFormats[$target_format];
+ $should_convert = ($original_mime !== $target_mime);
+ }
}
-
- // Check if conversion is needed
- $target_mime = $this->supportedFormats[$target_format];
- $should_convert = ($original_mime !== $target_mime);
}
// Convert or copy based on configuration
@@ -381,15 +537,18 @@
$final_path = "{$full_dir}/{$filename}.{$target_format}";
try {
+
$this->convertImage(
$original_path,
$final_path,
$target_format,
- $config['quality'],
- $config['use_imagick']
+ array_key_exists('quality', $context) && is_int($context['quality']) && $context['quality'] <= 100 ?
+ $context['quality'] :
+ $this->convertQuality,
);
+
} catch (Exception $e) {
- JVB()->error()->log('Image conversion failed: ' . print_r($e->getMessage(), true));
+ Errors::getInstance()->log('Uploader', 'Image conversion failed: ' . print_r($e->getMessage(), true));
// Fallback to original
$ext = pathinfo($original_path, PATHINFO_EXTENSION);
$final_path = "{$full_dir}/{$filename}.{$ext}";
@@ -412,17 +571,19 @@
}
// Generate thumbnails
- if ($config['create_thumbnails']) {
+ $hasOption = array_key_exists('create_thumbnails', $context);
+ if (($hasOption && $context['create_thumbnails']) || (!$hasOption && $this->createThumbnails)) {
$this->generateThumbnails($attachment_id);
}
return [
- 'success' => true,
+ 'success' => true,
'attachment_id' => $attachment_id,
- 'url' => wp_get_attachment_url($attachment_id),
- 'file' => $final_path,
- 'type' => 'image',
- 'format' => $target_format ?: pathinfo($final_path, PATHINFO_EXTENSION)
+ 'url' => wp_get_attachment_url($attachment_id),
+ 'file' => $final_path,
+ 'type' => 'image',
+ 'format' => $target_format ?: pathinfo($final_path, PATHINFO_EXTENSION),
+ 'context' => $context
];
}
@@ -430,7 +591,7 @@
* Process video file
* @throws Exception
*/
- protected function processVideo(string $original_path, string $directory, string $filename, int $post_id, array $config, array $context): array
+ protected function processVideo(string $original_path, string $directory, string $filename, int $post_id, array $context): array
{
$full_dir = "{$this->upload_dir}/{$directory}";
$ext = pathinfo($original_path, PATHINFO_EXTENSION);
@@ -443,29 +604,32 @@
try {
$attachment_id = $this->createAttachment($final_path, $filename, $context, $post_id);
} catch (Exception $e) {
- JVB()->error()->log('Attachment creation failed: '.print_r($e->getMessage(), true));
+ Errors::getInstance()->log('Uploader', 'Attachment creation failed: '.print_r($e->getMessage(), true));
return ['success' => false];
}
// Extract thumbnail if enabled
- if ($config['extract_video_thumbnail']) {
- $this->extractVideoThumbnail($attachment_id, $final_path, $config['video_thumbnail_time']);
+ $hasOption = array_key_exists('extract_thumbnail', $context);
+ if (($hasOption && $context['extract_thumbnail']) || (!$hasOption && $this->extractVideoThumbnail)) {
+ $time = $context['thumbnail_time']??$this->videoThumbnailPosition;
+ $this->extractVideoThumbnail($attachment_id, $final_path, $time);
}
return [
- 'success' => true,
+ 'success' => true,
'attachment_id' => $attachment_id,
- 'url' => wp_get_attachment_url($attachment_id),
- 'file' => $final_path,
- 'type' => 'video'
+ 'url' => wp_get_attachment_url($attachment_id),
+ 'file' => $final_path,
+ 'type' => 'video',
+ 'context' => $context
];
}
/**
* Process document file
*/
- protected function processDocument(string $original_path, string $directory, string $filename, int $post_id, array $config, array $context): array
+ protected function processDocument(string $original_path, string $directory, string $filename, int $post_id, array $context): array
{
$full_dir = "{$this->upload_dir}/{$directory}";
$ext = pathinfo($original_path, PATHINFO_EXTENSION);
@@ -478,36 +642,36 @@
try {
$attachment_id = $this->createAttachment($final_path, $filename, $context, $post_id);
} catch (Exception $e) {
- JVB()->error()->log('Conversion failed: '.print_r($e->getMessage(), true));
+ Errors::getInstance()->log('Uploader','Conversion failed: '.print_r($e->getMessage(), true));
return ['success' => false];
}
-
return [
- 'success' => true,
+ 'success' => true,
'attachment_id' => $attachment_id,
- 'url' => wp_get_attachment_url($attachment_id),
- 'file' => $final_path,
- 'type' => 'document'
+ 'url' => wp_get_attachment_url($attachment_id),
+ 'file' => $final_path,
+ 'type' => 'document',
+ 'context' => $context
];
}
/**
* Generate SEO-friendly filename
*/
- protected function generateFilename(string $original_name, array $context): string
+ public function generateFilename(string $original_name, array $context): string
{
$name_parts = pathinfo($original_name);
- $base_name = sanitize_title($name_parts['filename']);
+ $base_name = $this->sanitizeFileName($name_parts['filename']);
$user_data = array_key_exists('user_id', $context) ? get_userdata((int)$context['user_id']) : get_current_user();
$username = $user_data ? sanitize_title($user_data->display_name) : 'user';
$timestamp = time();
- return apply_filters(
+ return $this->sanitizeFileName(apply_filters(
'jvb_upload_filename',
"{$base_name}-{$timestamp}",
$context
- );
+ ));
}
protected function sanitizeFileName(string $filename): string
@@ -570,7 +734,7 @@
* @param bool $replace Replace original file (default: true)
* @return array|WP_Error Result with new attachment details
*/
- public function convertImageTo(int $attachment_id, string $format, int $quality = 80, bool $replace = true): array|WP_Error
+ public function convertImageTo(int $attachment_id, string $format, int $quality = 80, bool $replace = false): array|WP_Error
{
try {
// Validate format
@@ -608,8 +772,7 @@
$new_path = $path_info['dirname'] . '/' . $new_filename;
// Perform conversion
- $use_imagick = $this->defaultConfig['use_imagick'];
- $this->convertImage($original_path, $new_path, $format, $quality, $use_imagick);
+ $this->convertImage($original_path, $new_path, $format, $quality);
if ($replace) {
// Replace the original file
@@ -645,16 +808,31 @@
/**
* Generic image conversion method
*/
- protected function convertImage(string $source, string $destination, string $format, int $quality, bool $use_imagick): void
+ /**
+ * @param string $source
+ * @param string $destination
+ * @param string $format
+ * @param int $quality
+ * @return void
+ * @throws Exception
+ */
+ protected function convertImage(string $source, string $destination, string $format, int $quality): void
{
- if ($use_imagick) {
+ if ($this->has_imagick) {
$this->convertWithImagick($source, $destination, $format, $quality);
} else {
$this->convertWithGd($source, $destination, $format, $quality);
}
}
+
/**
- * Convert image with Imagick (supports multiple formats)
+ * Convert image with Imagick
+ * @param string $source
+ * @param string $destination
+ * @param string $format
+ * @param int $quality
+ * @return void
+ * @throws Exception
*/
protected function convertWithImagick(string $source, string $destination, string $format, int $quality): void
{
@@ -678,14 +856,19 @@
$image->writeImage($destination);
$image->clear();
- $image->destroy();
} catch (Exception $e) {
throw new Exception("Imagick conversion to {$format} failed: " . $e->getMessage());
}
}
/**
- * Convert image with GD (supports multiple formats)
+ * Convert image with GD
+ * @param string $source
+ * @param string $destination
+ * @param string $format
+ * @param int $quality
+ * @return void
+ * @throws Exception
*/
protected function convertWithGd(string $source, string $destination, string $format, int $quality): void
{
@@ -750,160 +933,4 @@
// Placeholder for future implementation
apply_filters('jvb_extract_video_thumbnail', null, $attachment_id, $video_path, $time);
}
-
-
-
-
- /**
- * Phase 1: Validate and process file on disk (no DB writes)
- * Safe to run outside transactions — only does file I/O
- *
- * @return array Prepared file info for registerFile()
- */
- public function prepareFile(string $temp_path, array $context): array
- {
- if (!file_exists($temp_path)) {
- throw new Exception('Temporary file not found: ' . $temp_path);
- }
-
- $mime_type = mime_content_type($temp_path);
- if (!$this->validateMimeType($temp_path, $mime_type)) {
- throw new Exception('Invalid or potentially dangerous file type');
- }
-
- $file_type = $this->allowedTypes[$mime_type] ?? 'unknown';
- if ($file_type === 'unknown') {
- throw new Exception('Unsupported file type: ' . $mime_type);
- }
-
- $config = array_merge($this->defaultConfig, $context['config'] ?? [], $context);
- $post_id = $context['post_id'] ?? 0;
-
- if (!empty($config['allowed_types']) && !in_array($mime_type, $config['allowed_types'])) {
- throw new Exception('File type not allowed: ' . $mime_type);
- }
-
- $this->validateFileSize($temp_path, $file_type, $config);
-
- $directory = $this->generateDirectory(
- $file_type,
- $config['directory_pattern'] ?? '{user_id}/{content}/{file_type}',
- $context
- );
- $this->ensureDirectories($directory);
-
- $original_name = $context['original_name'] ?? basename($temp_path);
- $filename = $this->generateFilename($original_name, $context);
- $original_path = $this->storeOriginalFromTemp($temp_path, $directory, $filename);
-
- // File-type-specific I/O (conversion, copying) — no DB
- $final_path = match ($file_type) {
- 'image' => $this->prepareImage($original_path, $directory, $filename, $config),
- 'video' => $this->prepareMedia($original_path, $directory, $filename),
- 'document' => $this->prepareMedia($original_path, $directory, $filename),
- default => throw new Exception('Unknown file type'),
- };
-
- return [
- 'final_path' => $final_path,
- 'file_type' => $file_type,
- 'filename' => $filename,
- 'post_id' => $post_id,
- 'config' => $config,
- 'context' => $context,
- ];
- }
-
- /**
- * Phase 2: Register processed file in WordPress (DB writes only)
- * Quick operation — individual WP functions handle their own queries
- *
- * @param array $prepared Output from prepareFile()
- * @return array Standard upload result
- */
- public function registerFile(array $prepared): array
- {
- $attachment_id = $this->createAttachment(
- $prepared['final_path'],
- $prepared['filename'],
- $prepared['context'],
- $prepared['post_id']
- );
-
- // Type-specific post-registration
- if ($prepared['file_type'] === 'image') {
- $alt_text = apply_filters('jvb_upload_alt_text', '', $prepared['context']);
- if ($alt_text !== '') {
- update_post_meta($attachment_id, '_wp_attachment_image_alt', $alt_text);
- }
- }
-
- if ($prepared['file_type'] === 'video' && ($prepared['config']['extract_video_thumbnail'] ?? false)) {
- $this->extractVideoThumbnail(
- $attachment_id,
- $prepared['final_path'],
- $prepared['config']['video_thumbnail_time'] ?? 0
- );
- }
-
- return [
- 'success' => true,
- 'attachment_id' => $attachment_id,
- 'url' => wp_get_attachment_url($attachment_id),
- 'file' => $prepared['final_path'],
- 'type' => $prepared['file_type'],
- ];
- }
-
- /**
- * Image file I/O: convert or copy to final location
- */
- protected function prepareImage(string $original_path, string $directory, string $filename, array $config): string
- {
- $full_dir = "{$this->upload_dir}/{$directory}";
- $original_mime = mime_content_type($original_path);
-
- $should_convert = false;
- $target_format = false;
-
- if ($config['convert'] && $config['convert'] !== false) {
- $target_format = strtolower($config['convert']);
- if (!isset($this->supportedFormats[$target_format])) {
- throw new Exception("Unsupported conversion format: {$target_format}");
- }
- $should_convert = ($original_mime !== $this->supportedFormats[$target_format]);
- }
-
- if ($should_convert) {
- $final_path = "{$full_dir}/{$filename}.{$target_format}";
- try {
- $this->convertImage($original_path, $final_path, $target_format, $config['quality'], $config['use_imagick']);
- } catch (Exception $e) {
- JVB()->error()->log('Image conversion failed: ' . $e->getMessage());
- $ext = pathinfo($original_path, PATHINFO_EXTENSION);
- $final_path = "{$full_dir}/{$filename}.{$ext}";
- copy($original_path, $final_path);
- }
- } else {
- $ext = pathinfo($original_path, PATHINFO_EXTENSION);
- $final_path = "{$full_dir}/{$filename}.{$ext}";
- copy($original_path, $final_path);
- }
-
- return $final_path;
- }
-
- /**
- * Video/document file I/O: copy to final location
- */
- protected function prepareMedia(string $original_path, string $directory, string $filename): string
- {
- $full_dir = "{$this->upload_dir}/{$directory}";
- $ext = pathinfo($original_path, PATHINFO_EXTENSION);
- $final_path = "{$full_dir}/{$filename}.{$ext}";
-
- copy($original_path, $final_path);
-
- return $final_path;
- }
}
--
Gitblit v1.10.0