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 |  498 +++++++++++++++++++++++++++++++++++++-----------------
 1 files changed, 340 insertions(+), 158 deletions(-)

diff --git a/inc/managers/UploadManager.php b/inc/managers/UploadManager.php
index d1cd220..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',
-			"{$username}-{$base_name}-{$timestamp}",
+			"{$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,5 +933,4 @@
 		// Placeholder for future implementation
 		apply_filters('jvb_extract_video_thumbnail', null, $attachment_id, $video_path, $time);
 	}
-
 }

--
Gitblit v1.10.0